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/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"
     20 
     21 namespace ui {
     22 
     23 namespace {
     24 
     25 bool HasData(IDataObject* data_object, const Clipboard::FormatType& format) {
     26   FORMATETC format_etc = format.ToFormatEtc();
     27   return SUCCEEDED(data_object->QueryGetData(&format_etc));
     28 }
     29 
     30 bool GetData(IDataObject* data_object,
     31              const Clipboard::FormatType& format,
     32              STGMEDIUM* medium) {
     33   FORMATETC format_etc = format.ToFormatEtc();
     34   return SUCCEEDED(data_object->GetData(&format_etc, medium));
     35 }
     36 
     37 bool GetUrlFromHDrop(IDataObject* data_object, string16* url, string16* title) {
     38   DCHECK(data_object && url && title);
     39 
     40   STGMEDIUM medium;
     41   if (!GetData(data_object, Clipboard::GetCFHDropFormatType(), &medium))
     42     return false;
     43 
     44   HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
     45 
     46   if (!hdrop)
     47     return false;
     48 
     49   bool success = false;
     50   wchar_t filename[MAX_PATH];
     51   if (DragQueryFileW(hdrop, 0, filename, arraysize(filename))) {
     52     wchar_t url_buffer[INTERNET_MAX_URL_LENGTH];
     53     if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") &&
     54         GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, url_buffer,
     55                                  arraysize(url_buffer), filename)) {
     56       url->assign(url_buffer);
     57       PathRemoveExtension(filename);
     58       title->assign(PathFindFileName(filename));
     59       success = true;
     60     }
     61   }
     62 
     63   DragFinish(hdrop);
     64   GlobalUnlock(medium.hGlobal);
     65   // We don't need to call ReleaseStgMedium here because as far as I can tell,
     66   // DragFinish frees the hGlobal for us.
     67   return success;
     68 }
     69 
     70 void SplitUrlAndTitle(const string16& str,
     71                       string16* url,
     72                       string16* title) {
     73   DCHECK(url && title);
     74   size_t newline_pos = str.find('\n');
     75   if (newline_pos != string16::npos) {
     76     url->assign(str, 0, newline_pos);
     77     title->assign(str, newline_pos + 1, string16::npos);
     78   } else {
     79     url->assign(str);
     80     title->assign(str);
     81   }
     82 }
     83 
     84 bool GetFileUrl(IDataObject* data_object, string16* url,
     85                 string16* title) {
     86   STGMEDIUM store;
     87   if (GetData(data_object, Clipboard::GetFilenameWFormatType(), &store)) {
     88     bool success = false;
     89     {
     90       // filename using unicode
     91       base::win::ScopedHGlobal<wchar_t> data(store.hGlobal);
     92       if (data.get() && data.get()[0] &&
     93           (PathFileExists(data.get()) || PathIsUNC(data.get()))) {
     94         wchar_t file_url[INTERNET_MAX_URL_LENGTH];
     95         DWORD file_url_len = arraysize(file_url);
     96         if (SUCCEEDED(::UrlCreateFromPathW(data.get(), file_url, &file_url_len,
     97                                            0))) {
     98           url->assign(file_url);
     99           title->assign(file_url);
    100           success = true;
    101         }
    102       }
    103     }
    104     ReleaseStgMedium(&store);
    105     if (success)
    106       return true;
    107   }
    108 
    109   if (GetData(data_object, Clipboard::GetFilenameFormatType(), &store)) {
    110     bool success = false;
    111     {
    112       // filename using ascii
    113       base::win::ScopedHGlobal<char> data(store.hGlobal);
    114       if (data.get() && data.get()[0] && (PathFileExistsA(data.get()) ||
    115                                           PathIsUNCA(data.get()))) {
    116         char file_url[INTERNET_MAX_URL_LENGTH];
    117         DWORD file_url_len = arraysize(file_url);
    118         if (SUCCEEDED(::UrlCreateFromPathA(data.get(), file_url, &file_url_len,
    119                                            0))) {
    120           url->assign(UTF8ToWide(file_url));
    121           title->assign(*url);
    122           success = true;
    123         }
    124       }
    125     }
    126     ReleaseStgMedium(&store);
    127     if (success)
    128       return true;
    129   }
    130   return false;
    131 }
    132 
    133 }  // namespace
    134 
    135 bool ClipboardUtil::HasUrl(IDataObject* data_object) {
    136   DCHECK(data_object);
    137   return HasData(data_object, Clipboard::GetMozUrlFormatType()) ||
    138          HasData(data_object, Clipboard::GetUrlWFormatType()) ||
    139          HasData(data_object, Clipboard::GetUrlFormatType()) ||
    140          HasData(data_object, Clipboard::GetFilenameWFormatType()) ||
    141          HasData(data_object, Clipboard::GetFilenameFormatType());
    142 }
    143 
    144 bool ClipboardUtil::HasFilenames(IDataObject* data_object) {
    145   DCHECK(data_object);
    146   return HasData(data_object, Clipboard::GetCFHDropFormatType());
    147 }
    148 
    149 bool ClipboardUtil::HasFileContents(IDataObject* data_object) {
    150   DCHECK(data_object);
    151   return HasData(data_object, Clipboard::GetFileContentZeroFormatType());
    152 }
    153 
    154 bool ClipboardUtil::HasHtml(IDataObject* data_object) {
    155   DCHECK(data_object);
    156   return HasData(data_object, Clipboard::GetHtmlFormatType()) ||
    157          HasData(data_object, Clipboard::GetTextHtmlFormatType());
    158 }
    159 
    160 bool ClipboardUtil::HasPlainText(IDataObject* data_object) {
    161   DCHECK(data_object);
    162   return HasData(data_object, Clipboard::GetPlainTextWFormatType()) ||
    163          HasData(data_object, Clipboard::GetPlainTextFormatType());
    164 }
    165 
    166 bool ClipboardUtil::GetUrl(IDataObject* data_object,
    167     string16* url, string16* title, bool convert_filenames) {
    168   DCHECK(data_object && url && title);
    169   if (!HasUrl(data_object))
    170     return false;
    171 
    172   // Try to extract a URL from |data_object| in a variety of formats.
    173   STGMEDIUM store;
    174   if (GetUrlFromHDrop(data_object, url, title))
    175     return true;
    176 
    177   if (GetData(data_object, Clipboard::GetMozUrlFormatType(), &store) ||
    178       GetData(data_object, Clipboard::GetUrlWFormatType(), &store)) {
    179     {
    180       // Mozilla URL format or unicode URL
    181       base::win::ScopedHGlobal<wchar_t> data(store.hGlobal);
    182       SplitUrlAndTitle(data.get(), url, title);
    183     }
    184     ReleaseStgMedium(&store);
    185     return true;
    186   }
    187 
    188   if (GetData(data_object, Clipboard::GetUrlFormatType(), &store)) {
    189     {
    190       // URL using ascii
    191       base::win::ScopedHGlobal<char> data(store.hGlobal);
    192       SplitUrlAndTitle(UTF8ToWide(data.get()), url, title);
    193     }
    194     ReleaseStgMedium(&store);
    195     return true;
    196   }
    197 
    198   if (convert_filenames) {
    199     return GetFileUrl(data_object, url, title);
    200   } else {
    201     return false;
    202   }
    203 }
    204 
    205 bool ClipboardUtil::GetFilenames(IDataObject* data_object,
    206                                  std::vector<string16>* filenames) {
    207   DCHECK(data_object && filenames);
    208   if (!HasFilenames(data_object))
    209     return false;
    210 
    211   STGMEDIUM medium;
    212   if (!GetData(data_object, Clipboard::GetCFHDropFormatType(), &medium))
    213     return false;
    214 
    215   HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
    216   if (!hdrop)
    217     return false;
    218 
    219   const int kMaxFilenameLen = 4096;
    220   const unsigned num_files = DragQueryFileW(hdrop, 0xffffffff, 0, 0);
    221   for (unsigned int i = 0; i < num_files; ++i) {
    222     wchar_t filename[kMaxFilenameLen];
    223     if (!DragQueryFileW(hdrop, i, filename, kMaxFilenameLen))
    224       continue;
    225     filenames->push_back(filename);
    226   }
    227 
    228   DragFinish(hdrop);
    229   GlobalUnlock(medium.hGlobal);
    230   // We don't need to call ReleaseStgMedium here because as far as I can tell,
    231   // DragFinish frees the hGlobal for us.
    232   return true;
    233 }
    234 
    235 bool ClipboardUtil::GetPlainText(IDataObject* data_object,
    236                                  string16* plain_text) {
    237   DCHECK(data_object && plain_text);
    238   if (!HasPlainText(data_object))
    239     return false;
    240 
    241   STGMEDIUM store;
    242   if (GetData(data_object, Clipboard::GetPlainTextWFormatType(), &store)) {
    243     {
    244       // Unicode text
    245       base::win::ScopedHGlobal<wchar_t> data(store.hGlobal);
    246       plain_text->assign(data.get());
    247     }
    248     ReleaseStgMedium(&store);
    249     return true;
    250   }
    251 
    252   if (GetData(data_object, Clipboard::GetPlainTextFormatType(), &store)) {
    253     {
    254       // ascii text
    255       base::win::ScopedHGlobal<char> data(store.hGlobal);
    256       plain_text->assign(UTF8ToWide(data.get()));
    257     }
    258     ReleaseStgMedium(&store);
    259     return true;
    260   }
    261 
    262   // If a file is dropped on the window, it does not provide either of the
    263   // plain text formats, so here we try to forcibly get a url.
    264   string16 title;
    265   return GetUrl(data_object, plain_text, &title, false);
    266 }
    267 
    268 bool ClipboardUtil::GetHtml(IDataObject* data_object,
    269                             string16* html, std::string* base_url) {
    270   DCHECK(data_object && html && base_url);
    271 
    272   STGMEDIUM store;
    273   if (HasData(data_object, Clipboard::GetHtmlFormatType()) &&
    274       GetData(data_object, Clipboard::GetHtmlFormatType(), &store)) {
    275     {
    276       // MS CF html
    277       base::win::ScopedHGlobal<char> data(store.hGlobal);
    278 
    279       std::string html_utf8;
    280       CFHtmlToHtml(std::string(data.get(), data.Size()), &html_utf8, base_url);
    281       html->assign(UTF8ToWide(html_utf8));
    282     }
    283     ReleaseStgMedium(&store);
    284     return true;
    285   }
    286 
    287   if (!HasData(data_object, Clipboard::GetTextHtmlFormatType()))
    288     return false;
    289 
    290   if (!GetData(data_object, Clipboard::GetTextHtmlFormatType(), &store))
    291     return false;
    292 
    293   {
    294     // text/html
    295     base::win::ScopedHGlobal<wchar_t> data(store.hGlobal);
    296     html->assign(data.get());
    297   }
    298   ReleaseStgMedium(&store);
    299   return true;
    300 }
    301 
    302 bool ClipboardUtil::GetFileContents(IDataObject* data_object,
    303     string16* filename, std::string* file_contents) {
    304   DCHECK(data_object && filename && file_contents);
    305   if (!HasData(data_object, Clipboard::GetFileContentZeroFormatType()) &&
    306       !HasData(data_object, Clipboard::GetFileDescriptorFormatType()))
    307     return false;
    308 
    309   STGMEDIUM content;
    310   // The call to GetData can be very slow depending on what is in
    311   // |data_object|.
    312   if (GetData(
    313           data_object, Clipboard::GetFileContentZeroFormatType(), &content)) {
    314     if (TYMED_HGLOBAL == content.tymed) {
    315       base::win::ScopedHGlobal<char> data(content.hGlobal);
    316       file_contents->assign(data.get(), data.Size());
    317     }
    318     ReleaseStgMedium(&content);
    319   }
    320 
    321   STGMEDIUM description;
    322   if (GetData(data_object,
    323               Clipboard::GetFileDescriptorFormatType(),
    324               &description)) {
    325     {
    326       base::win::ScopedHGlobal<FILEGROUPDESCRIPTOR> fgd(description.hGlobal);
    327       // We expect there to be at least one file in here.
    328       DCHECK_GE(fgd->cItems, 1u);
    329       filename->assign(fgd->fgd[0].cFileName);
    330     }
    331     ReleaseStgMedium(&description);
    332   }
    333   return true;
    334 }
    335 
    336 bool ClipboardUtil::GetWebCustomData(
    337     IDataObject* data_object, std::map<string16, string16>* custom_data) {
    338   DCHECK(data_object && custom_data);
    339 
    340   if (!HasData(data_object, Clipboard::GetWebCustomDataFormatType()))
    341     return false;
    342 
    343   STGMEDIUM store;
    344   if (GetData(data_object, Clipboard::GetWebCustomDataFormatType(), &store)) {
    345     {
    346       base::win::ScopedHGlobal<char> data(store.hGlobal);
    347       ReadCustomDataIntoMap(data.get(), data.Size(), custom_data);
    348     }
    349     ReleaseStgMedium(&store);
    350     return true;
    351   }
    352   return false;
    353 }
    354 
    355 
    356 // HtmlToCFHtml and CFHtmlToHtml are based on similar methods in
    357 // WebCore/platform/win/ClipboardUtilitiesWin.cpp.
    358 /*
    359  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
    360  *
    361  * Redistribution and use in source and binary forms, with or without
    362  * modification, are permitted provided that the following conditions
    363  * are met:
    364  * 1. Redistributions of source code must retain the above copyright
    365  *    notice, this list of conditions and the following disclaimer.
    366  * 2. Redistributions in binary form must reproduce the above copyright
    367  *    notice, this list of conditions and the following disclaimer in the
    368  *    documentation and/or other materials provided with the distribution.
    369  *
    370  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
    371  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    372  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    373  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
    374  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    375  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    376  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    377  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
    378  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    379  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    380  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    381  */
    382 
    383 // Helper method for converting from text/html to MS CF_HTML.
    384 // Documentation for the CF_HTML format is available at
    385 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx
    386 std::string ClipboardUtil::HtmlToCFHtml(const std::string& html,
    387                                         const std::string& base_url) {
    388   if (html.empty())
    389     return std::string();
    390 
    391   #define MAX_DIGITS 10
    392   #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
    393   #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
    394   #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
    395 
    396   static const char* header = "Version:0.9\r\n"
    397       "StartHTML:" NUMBER_FORMAT "\r\n"
    398       "EndHTML:" NUMBER_FORMAT "\r\n"
    399       "StartFragment:" NUMBER_FORMAT "\r\n"
    400       "EndFragment:" NUMBER_FORMAT "\r\n";
    401   static const char* source_url_prefix = "SourceURL:";
    402 
    403   static const char* start_markup =
    404       "<html>\r\n<body>\r\n<!--StartFragment-->";
    405   static const char* end_markup =
    406       "<!--EndFragment-->\r\n</body>\r\n</html>";
    407 
    408   // Calculate offsets
    409   size_t start_html_offset = strlen(header) - strlen(NUMBER_FORMAT) * 4 +
    410       MAX_DIGITS * 4;
    411   if (!base_url.empty()) {
    412     start_html_offset += strlen(source_url_prefix) +
    413         base_url.length() + 2;  // Add 2 for \r\n.
    414   }
    415   size_t start_fragment_offset = start_html_offset + strlen(start_markup);
    416   size_t end_fragment_offset = start_fragment_offset + html.length();
    417   size_t end_html_offset = end_fragment_offset + strlen(end_markup);
    418 
    419   std::string result = base::StringPrintf(header,
    420                                           start_html_offset,
    421                                           end_html_offset,
    422                                           start_fragment_offset,
    423                                           end_fragment_offset);
    424   if (!base_url.empty()) {
    425     result.append(source_url_prefix);
    426     result.append(base_url);
    427     result.append("\r\n");
    428   }
    429   result.append(start_markup);
    430   result.append(html);
    431   result.append(end_markup);
    432 
    433   #undef MAX_DIGITS
    434   #undef MAKE_NUMBER_FORMAT_1
    435   #undef MAKE_NUMBER_FORMAT_2
    436   #undef NUMBER_FORMAT
    437 
    438   return result;
    439 }
    440 
    441 // Helper method for converting from MS CF_HTML to text/html.
    442 void ClipboardUtil::CFHtmlToHtml(const std::string& cf_html,
    443                                  std::string* html,
    444                                  std::string* base_url) {
    445   size_t fragment_start = std::string::npos;
    446   size_t fragment_end = std::string::npos;
    447 
    448   ClipboardUtil::CFHtmlExtractMetadata(
    449       cf_html, base_url, NULL, &fragment_start, &fragment_end);
    450 
    451   if (html &&
    452       fragment_start != std::string::npos &&
    453       fragment_end != std::string::npos) {
    454     *html = cf_html.substr(fragment_start, fragment_end - fragment_start);
    455     TrimWhitespace(*html, TRIM_ALL, html);
    456   }
    457 }
    458 
    459 void ClipboardUtil::CFHtmlExtractMetadata(const std::string& cf_html,
    460                                           std::string* base_url,
    461                                           size_t* html_start,
    462                                           size_t* fragment_start,
    463                                           size_t* fragment_end) {
    464   // Obtain base_url if present.
    465   if (base_url) {
    466     static std::string src_url_str("SourceURL:");
    467     size_t line_start = cf_html.find(src_url_str);
    468     if (line_start != std::string::npos) {
    469       size_t src_end = cf_html.find("\n", line_start);
    470       size_t src_start = line_start + src_url_str.length();
    471       if (src_end != std::string::npos && src_start != std::string::npos) {
    472         *base_url = cf_html.substr(src_start, src_end - src_start);
    473         TrimWhitespace(*base_url, TRIM_ALL, base_url);
    474       }
    475     }
    476   }
    477 
    478   // Find the markup between "<!--StartFragment-->" and "<!--EndFragment-->".
    479   // If the comments cannot be found, like copying from OpenOffice Writer,
    480   // we simply fall back to using StartFragment/EndFragment bytecount values
    481   // to determine the fragment indexes.
    482   std::string cf_html_lower = StringToLowerASCII(cf_html);
    483   size_t markup_start = cf_html_lower.find("<html", 0);
    484   if (html_start) {
    485     *html_start = markup_start;
    486   }
    487   size_t tag_start = cf_html.find("<!--StartFragment", markup_start);
    488   if (tag_start == std::string::npos) {
    489     static std::string start_fragment_str("StartFragment:");
    490     size_t start_fragment_start = cf_html.find(start_fragment_str);
    491     if (start_fragment_start != std::string::npos) {
    492       *fragment_start = static_cast<size_t>(atoi(cf_html.c_str() +
    493           start_fragment_start + start_fragment_str.length()));
    494     }
    495 
    496     static std::string end_fragment_str("EndFragment:");
    497     size_t end_fragment_start = cf_html.find(end_fragment_str);
    498     if (end_fragment_start != std::string::npos) {
    499       *fragment_end = static_cast<size_t>(atoi(cf_html.c_str() +
    500           end_fragment_start + end_fragment_str.length()));
    501     }
    502   } else {
    503     *fragment_start = cf_html.find('>', tag_start) + 1;
    504     size_t tag_end = cf_html.rfind("<!--EndFragment", std::string::npos);
    505     *fragment_end = cf_html.rfind('<', tag_end);
    506   }
    507 }
    508 
    509 }  // namespace ui
    510