Home | History | Annotate | Download | only in win
      1 /*
      2  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "ClipboardUtilitiesWin.h"
     28 
     29 #include "CString.h"
     30 #include "DocumentFragment.h"
     31 #include "KURL.h"
     32 #include "PlatformString.h"
     33 #include "TextEncoding.h"
     34 #include "markup.h"
     35 #include <CoreFoundation/CoreFoundation.h>
     36 #include <wtf/RetainPtr.h>
     37 #include <shlwapi.h>
     38 #include <wininet.h>    // for INTERNET_MAX_URL_LENGTH
     39 
     40 namespace WebCore {
     41 
     42 FORMATETC* cfHDropFormat()
     43 {
     44     static FORMATETC urlFormat = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
     45     return &urlFormat;
     46 }
     47 
     48 static bool getWebLocData(IDataObject* dataObject, String& url, String* title)
     49 {
     50     bool succeeded = false;
     51     WCHAR filename[MAX_PATH];
     52     WCHAR urlBuffer[INTERNET_MAX_URL_LENGTH];
     53 
     54     STGMEDIUM medium;
     55     if (FAILED(dataObject->GetData(cfHDropFormat(), &medium)))
     56         return false;
     57 
     58     HDROP hdrop = (HDROP)GlobalLock(medium.hGlobal);
     59 
     60     if (!hdrop)
     61         return false;
     62 
     63     if (!DragQueryFileW(hdrop, 0, filename, ARRAYSIZE(filename)))
     64         goto exit;
     65 
     66     if (_wcsicmp(PathFindExtensionW(filename), L".url"))
     67         goto exit;
     68 
     69     if (!GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, urlBuffer, ARRAYSIZE(urlBuffer), filename))
     70         goto exit;
     71 
     72     if (title) {
     73         PathRemoveExtension(filename);
     74         *title = String((UChar*)filename);
     75     }
     76 
     77     url = String((UChar*)urlBuffer);
     78     succeeded = true;
     79 
     80 exit:
     81     // Free up memory.
     82     DragFinish(hdrop);
     83     GlobalUnlock(medium.hGlobal);
     84     return succeeded;
     85 }
     86 
     87 static String extractURL(const String &inURL, String* title)
     88 {
     89     String url = inURL;
     90     int splitLoc = url.find('\n');
     91     if (splitLoc > 0) {
     92         if (title)
     93             *title = url.substring(splitLoc+1);
     94         url.truncate(splitLoc);
     95     } else if (title)
     96         *title = url;
     97     return url;
     98 }
     99 
    100 //Firefox text/html
    101 static FORMATETC* texthtmlFormat()
    102 {
    103     static UINT cf = RegisterClipboardFormat(L"text/html");
    104     static FORMATETC texthtmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    105     return &texthtmlFormat;
    106 }
    107 
    108 HGLOBAL createGlobalData(const KURL& url, const String& title)
    109 {
    110     String mutableURL(url.string());
    111     String mutableTitle(title);
    112     SIZE_T size = mutableURL.length() + mutableTitle.length() + 2;  // +1 for "\n" and +1 for null terminator
    113     HGLOBAL cbData = ::GlobalAlloc(GPTR, size * sizeof(UChar));
    114 
    115     if (cbData) {
    116         PWSTR buffer = (PWSTR)::GlobalLock(cbData);
    117         swprintf_s(buffer, size, L"%s\n%s", mutableURL.charactersWithNullTermination(), mutableTitle.charactersWithNullTermination());
    118         ::GlobalUnlock(cbData);
    119     }
    120     return cbData;
    121 }
    122 
    123 HGLOBAL createGlobalData(const String& str)
    124 {
    125     HGLOBAL globalData = ::GlobalAlloc(GPTR, (str.length() + 1) * sizeof(UChar));
    126     if (!globalData)
    127         return 0;
    128     UChar* buffer = static_cast<UChar*>(::GlobalLock(globalData));
    129     memcpy(buffer, str.characters(), str.length() * sizeof(UChar));
    130     buffer[str.length()] = 0;
    131     ::GlobalUnlock(globalData);
    132     return globalData;
    133 }
    134 
    135 HGLOBAL createGlobalData(const Vector<char>& vector)
    136 {
    137     HGLOBAL globalData = ::GlobalAlloc(GPTR, vector.size() + 1);
    138     if (!globalData)
    139         return 0;
    140     char* buffer = static_cast<char*>(::GlobalLock(globalData));
    141     memcpy(buffer, vector.data(), vector.size());
    142     buffer[vector.size()] = 0;
    143     ::GlobalUnlock(globalData);
    144     return globalData;
    145 }
    146 
    147 static void append(Vector<char>& vector, const char* string)
    148 {
    149     vector.append(string, strlen(string));
    150 }
    151 
    152 static void append(Vector<char>& vector, const CString& string)
    153 {
    154     vector.append(string.data(), string.length());
    155 }
    156 
    157 // Documentation for the CF_HTML format is available at http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp
    158 void markupToCF_HTML(const String& markup, const String& srcURL, Vector<char>& result)
    159 {
    160     if (markup.isEmpty())
    161         return;
    162 
    163     #define MAX_DIGITS 10
    164     #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
    165     #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
    166     #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
    167 
    168     const char* header = "Version:0.9\n"
    169         "StartHTML:" NUMBER_FORMAT "\n"
    170         "EndHTML:" NUMBER_FORMAT "\n"
    171         "StartFragment:" NUMBER_FORMAT "\n"
    172         "EndFragment:" NUMBER_FORMAT "\n";
    173     const char* sourceURLPrefix = "SourceURL:";
    174 
    175     const char* startMarkup = "<HTML>\n<BODY>\n<!--StartFragment-->\n";
    176     const char* endMarkup = "\n<!--EndFragment-->\n</BODY>\n</HTML>";
    177 
    178     CString sourceURLUTF8 = srcURL == blankURL() ? "" : srcURL.utf8();
    179     CString markupUTF8 = markup.utf8();
    180 
    181     // calculate offsets
    182     unsigned startHTMLOffset = strlen(header) - strlen(NUMBER_FORMAT) * 4 + MAX_DIGITS * 4;
    183     if (sourceURLUTF8.length())
    184         startHTMLOffset += strlen(sourceURLPrefix) + sourceURLUTF8.length() + 1;
    185     unsigned startFragmentOffset = startHTMLOffset + strlen(startMarkup);
    186     unsigned endFragmentOffset = startFragmentOffset + markupUTF8.length();
    187     unsigned endHTMLOffset = endFragmentOffset + strlen(endMarkup);
    188 
    189     append(result, String::format(header, startHTMLOffset, endHTMLOffset, startFragmentOffset, endFragmentOffset).utf8());
    190     if (sourceURLUTF8.length()) {
    191         append(result, sourceURLPrefix);
    192         append(result, sourceURLUTF8);
    193         result.append('\n');
    194     }
    195     append(result, startMarkup);
    196     append(result, markupUTF8);
    197     append(result, endMarkup);
    198 
    199     #undef MAX_DIGITS
    200     #undef MAKE_NUMBER_FORMAT_1
    201     #undef MAKE_NUMBER_FORMAT_2
    202     #undef NUMBER_FORMAT
    203 }
    204 
    205 String urlToMarkup(const KURL& url, const String& title)
    206 {
    207     Vector<UChar> markup;
    208     append(markup, "<a href=\"");
    209     append(markup, url.string());
    210     append(markup, "\">");
    211     append(markup, title);
    212     append(markup, "</a>");
    213     return String::adopt(markup);
    214 }
    215 
    216 void replaceNewlinesWithWindowsStyleNewlines(String& str)
    217 {
    218     static const UChar Newline = '\n';
    219     static const char* const WindowsNewline("\r\n");
    220     str.replace(Newline, WindowsNewline);
    221 }
    222 
    223 void replaceNBSPWithSpace(String& str)
    224 {
    225     static const UChar NonBreakingSpaceCharacter = 0xA0;
    226     static const UChar SpaceCharacter = ' ';
    227     str.replace(NonBreakingSpaceCharacter, SpaceCharacter);
    228 }
    229 
    230 FORMATETC* urlWFormat()
    231 {
    232     static UINT cf = RegisterClipboardFormat(L"UniformResourceLocatorW");
    233     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    234     return &urlFormat;
    235 }
    236 
    237 FORMATETC* urlFormat()
    238 {
    239     static UINT cf = RegisterClipboardFormat(L"UniformResourceLocator");
    240     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    241     return &urlFormat;
    242 }
    243 
    244 FORMATETC* plainTextFormat()
    245 {
    246     static FORMATETC textFormat = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    247     return &textFormat;
    248 }
    249 
    250 FORMATETC* plainTextWFormat()
    251 {
    252     static FORMATETC textFormat = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    253     return &textFormat;
    254 }
    255 
    256 FORMATETC* filenameWFormat()
    257 {
    258     static UINT cf = RegisterClipboardFormat(L"FileNameW");
    259     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    260     return &urlFormat;
    261 }
    262 
    263 FORMATETC* filenameFormat()
    264 {
    265     static UINT cf = RegisterClipboardFormat(L"FileName");
    266     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    267     return &urlFormat;
    268 }
    269 
    270 //MSIE HTML Format
    271 FORMATETC* htmlFormat()
    272 {
    273     static UINT cf = RegisterClipboardFormat(L"HTML Format");
    274     static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    275     return &htmlFormat;
    276 }
    277 
    278 FORMATETC* smartPasteFormat()
    279 {
    280     static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format");
    281     static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    282     return &htmlFormat;
    283 }
    284 
    285 static bool urlFromPath(CFStringRef path, String& url)
    286 {
    287     if (!path)
    288         return false;
    289 
    290     RetainPtr<CFURLRef> cfURL(AdoptCF, CFURLCreateWithFileSystemPath(0, path, kCFURLWindowsPathStyle, false));
    291     if (!cfURL)
    292         return false;
    293 
    294     url = CFURLGetString(cfURL.get());
    295 
    296     // Work around <rdar://problem/6708300>, where CFURLCreateWithFileSystemPath makes URLs with "localhost".
    297     if (url.startsWith("file://localhost/"))
    298         url.remove(7, 9);
    299 
    300     return true;
    301 }
    302 
    303 String getURL(IDataObject* dataObject, bool& success, String* title)
    304 {
    305     STGMEDIUM store;
    306     String url;
    307     success = false;
    308     if (getWebLocData(dataObject, url, title)) {
    309         success = true;
    310         return url;
    311     } else if (SUCCEEDED(dataObject->GetData(urlWFormat(), &store))) {
    312         //URL using unicode
    313         UChar* data = (UChar*)GlobalLock(store.hGlobal);
    314         url = extractURL(String(data), title);
    315         GlobalUnlock(store.hGlobal);
    316         ReleaseStgMedium(&store);
    317         success = true;
    318     } else if (SUCCEEDED(dataObject->GetData(urlFormat(), &store))) {
    319         //URL using ascii
    320         char* data = (char*)GlobalLock(store.hGlobal);
    321         url = extractURL(String(data), title);
    322         GlobalUnlock(store.hGlobal);
    323         ReleaseStgMedium(&store);
    324         success = true;
    325     } else if (SUCCEEDED(dataObject->GetData(filenameWFormat(), &store))) {
    326         //file using unicode
    327         wchar_t* data = (wchar_t*)GlobalLock(store.hGlobal);
    328         if (data && data[0] && (PathFileExists(data) || PathIsUNC(data))) {
    329             RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar*)data, wcslen(data)));
    330             if (urlFromPath(pathAsCFString.get(), url)) {
    331                 if (title)
    332                     *title = url;
    333                 success = true;
    334             }
    335         }
    336         GlobalUnlock(store.hGlobal);
    337         ReleaseStgMedium(&store);
    338     } else if (SUCCEEDED(dataObject->GetData(filenameFormat(), &store))) {
    339         //filename using ascii
    340         char* data = (char*)GlobalLock(store.hGlobal);
    341         if (data && data[0] && (PathFileExistsA(data) || PathIsUNCA(data))) {
    342             RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCString(kCFAllocatorDefault, data, kCFStringEncodingASCII));
    343             if (urlFromPath(pathAsCFString.get(), url)) {
    344                 if (title)
    345                     *title = url;
    346                 success = true;
    347             }
    348         }
    349         GlobalUnlock(store.hGlobal);
    350         ReleaseStgMedium(&store);
    351     }
    352     return url;
    353 }
    354 
    355 String getPlainText(IDataObject* dataObject, bool& success)
    356 {
    357     STGMEDIUM store;
    358     String text;
    359     success = false;
    360     if (SUCCEEDED(dataObject->GetData(plainTextWFormat(), &store))) {
    361         //unicode text
    362         UChar* data = (UChar*)GlobalLock(store.hGlobal);
    363         text = String(data);
    364         GlobalUnlock(store.hGlobal);
    365         ReleaseStgMedium(&store);
    366         success = true;
    367     } else if (SUCCEEDED(dataObject->GetData(plainTextFormat(), &store))) {
    368         //ascii text
    369         char* data = (char*)GlobalLock(store.hGlobal);
    370         text = String(data);
    371         GlobalUnlock(store.hGlobal);
    372         ReleaseStgMedium(&store);
    373         success = true;
    374     } else {
    375         //If a file is dropped on the window, it does not provide either of the
    376         //plain text formats, so here we try to forcibly get a url.
    377         text = getURL(dataObject, success);
    378         success = true;
    379     }
    380     return text;
    381 }
    382 
    383 PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const IDataObject*)
    384 {
    385     //FIXME: We should be able to create fragments from files
    386     return 0;
    387 }
    388 
    389 bool containsFilenames(const IDataObject*)
    390 {
    391     //FIXME: We'll want to update this once we can produce fragments from files
    392     return false;
    393 }
    394 
    395 //Convert a String containing CF_HTML formatted text to a DocumentFragment
    396 PassRefPtr<DocumentFragment> fragmentFromCF_HTML(Document* doc, const String& cf_html)
    397 {
    398     // obtain baseURL if present
    399     String srcURLStr("sourceURL:");
    400     String srcURL;
    401     unsigned lineStart = cf_html.find(srcURLStr, 0, false);
    402     if (lineStart != -1) {
    403         unsigned srcEnd = cf_html.find("\n", lineStart, false);
    404         unsigned srcStart = lineStart+srcURLStr.length();
    405         String rawSrcURL = cf_html.substring(srcStart, srcEnd-srcStart);
    406         replaceNBSPWithSpace(rawSrcURL);
    407         srcURL = rawSrcURL.stripWhiteSpace();
    408     }
    409 
    410     // find the markup between "<!--StartFragment -->" and "<!--EndFragment -->", accounting for browser quirks
    411     unsigned markupStart = cf_html.find("<html", 0, false);
    412     unsigned tagStart = cf_html.find("startfragment", markupStart, false);
    413     unsigned fragmentStart = cf_html.find('>', tagStart) + 1;
    414     unsigned tagEnd = cf_html.find("endfragment", fragmentStart, false);
    415     unsigned fragmentEnd = cf_html.reverseFind('<', tagEnd);
    416     String markup = cf_html.substring(fragmentStart, fragmentEnd - fragmentStart).stripWhiteSpace();
    417 
    418     return createFragmentFromMarkup(doc, markup, srcURL, FragmentScriptingNotAllowed);
    419 }
    420 
    421 
    422 PassRefPtr<DocumentFragment> fragmentFromHTML(Document* doc, IDataObject* data)
    423 {
    424     if (!doc || !data)
    425         return 0;
    426 
    427     STGMEDIUM store;
    428     String html;
    429     String srcURL;
    430     if (SUCCEEDED(data->GetData(htmlFormat(), &store))) {
    431         //MS HTML Format parsing
    432         char* data = (char*)GlobalLock(store.hGlobal);
    433         SIZE_T dataSize = ::GlobalSize(store.hGlobal);
    434         String cf_html(UTF8Encoding().decode(data, dataSize));
    435         GlobalUnlock(store.hGlobal);
    436         ReleaseStgMedium(&store);
    437         if (PassRefPtr<DocumentFragment> fragment = fragmentFromCF_HTML(doc, cf_html))
    438             return fragment;
    439     }
    440     if (SUCCEEDED(data->GetData(texthtmlFormat(), &store))) {
    441         //raw html
    442         UChar* data = (UChar*)GlobalLock(store.hGlobal);
    443         html = String(data);
    444         GlobalUnlock(store.hGlobal);
    445         ReleaseStgMedium(&store);
    446         return createFragmentFromMarkup(doc, html, srcURL, FragmentScriptingNotAllowed);
    447     }
    448 
    449     return 0;
    450 }
    451 
    452 bool containsHTML(IDataObject* data)
    453 {
    454     return SUCCEEDED(data->QueryGetData(texthtmlFormat())) || SUCCEEDED(data->QueryGetData(htmlFormat()));
    455 }
    456 
    457 } // namespace WebCore
    458