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/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