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 "DocumentFragment.h"
30#include "KURL.h"
31#include "PlatformString.h"
32#include "TextEncoding.h"
33#include "markup.h"
34#include <shlobj.h>
35#include <shlwapi.h>
36#include <wininet.h> // for INTERNET_MAX_URL_LENGTH
37#include <wtf/StringExtras.h>
38#include <wtf/text/CString.h>
39#include <wtf/text/StringConcatenate.h>
40
41#if USE(CF)
42#include <CoreFoundation/CoreFoundation.h>
43#include <wtf/RetainPtr.h>
44#endif
45
46namespace WebCore {
47
48#if USE(CF)
49FORMATETC* cfHDropFormat()
50{
51    static FORMATETC urlFormat = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
52    return &urlFormat;
53}
54
55static bool urlFromPath(CFStringRef path, String& url)
56{
57    if (!path)
58        return false;
59
60    RetainPtr<CFURLRef> cfURL(AdoptCF, CFURLCreateWithFileSystemPath(0, path, kCFURLWindowsPathStyle, false));
61    if (!cfURL)
62        return false;
63
64    url = CFURLGetString(cfURL.get());
65
66    // Work around <rdar://problem/6708300>, where CFURLCreateWithFileSystemPath makes URLs with "localhost".
67    if (url.startsWith("file://localhost/"))
68        url.remove(7, 9);
69
70    return true;
71}
72#endif
73
74static bool getDataMapItem(const DragDataMap* dataObject, FORMATETC* format, String& item)
75{
76    DragDataMap::const_iterator found = dataObject->find(format->cfFormat);
77    if (found == dataObject->end())
78        return false;
79    item = found->second[0];
80    return true;
81}
82
83static bool getWebLocData(IDataObject* dataObject, String& url, String* title)
84{
85    bool succeeded = false;
86#if USE(CF)
87    WCHAR filename[MAX_PATH];
88    WCHAR urlBuffer[INTERNET_MAX_URL_LENGTH];
89
90    STGMEDIUM medium;
91    if (FAILED(dataObject->GetData(cfHDropFormat(), &medium)))
92        return false;
93
94    HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
95
96    if (!hdrop)
97        return false;
98
99    if (!DragQueryFileW(hdrop, 0, filename, WTF_ARRAY_LENGTH(filename)))
100        goto exit;
101
102    if (_wcsicmp(PathFindExtensionW(filename), L".url"))
103        goto exit;
104
105    if (!GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, urlBuffer, WTF_ARRAY_LENGTH(urlBuffer), filename))
106        goto exit;
107
108    if (title) {
109        PathRemoveExtension(filename);
110        *title = String((UChar*)filename);
111    }
112
113    url = String((UChar*)urlBuffer);
114    succeeded = true;
115
116exit:
117    // Free up memory.
118    DragFinish(hdrop);
119    GlobalUnlock(medium.hGlobal);
120#endif
121    return succeeded;
122}
123
124static bool getWebLocData(const DragDataMap* dataObject, String& url, String* title)
125{
126#if USE(CF)
127    WCHAR filename[MAX_PATH];
128    WCHAR urlBuffer[INTERNET_MAX_URL_LENGTH];
129
130    if (!dataObject->contains(cfHDropFormat()->cfFormat))
131        return false;
132
133    wcscpy(filename, dataObject->get(cfHDropFormat()->cfFormat)[0].charactersWithNullTermination());
134    if (_wcsicmp(PathFindExtensionW(filename), L".url"))
135        return false;
136
137    if (!GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, urlBuffer, WTF_ARRAY_LENGTH(urlBuffer), filename))
138        return false;
139
140    if (title) {
141        PathRemoveExtension(filename);
142        *title = filename;
143    }
144
145    url = urlBuffer;
146    return true;
147#else
148    return false;
149#endif
150}
151
152static String extractURL(const String &inURL, String* title)
153{
154    String url = inURL;
155    int splitLoc = url.find('\n');
156    if (splitLoc > 0) {
157        if (title)
158            *title = url.substring(splitLoc+1);
159        url.truncate(splitLoc);
160    } else if (title)
161        *title = url;
162    return url;
163}
164
165// Firefox text/html
166static FORMATETC* texthtmlFormat()
167{
168    static UINT cf = RegisterClipboardFormat(L"text/html");
169    static FORMATETC texthtmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
170    return &texthtmlFormat;
171}
172
173HGLOBAL createGlobalData(const KURL& url, const String& title)
174{
175    String mutableURL(url.string());
176    String mutableTitle(title);
177    SIZE_T size = mutableURL.length() + mutableTitle.length() + 2; // +1 for "\n" and +1 for null terminator
178    HGLOBAL cbData = ::GlobalAlloc(GPTR, size * sizeof(UChar));
179
180    if (cbData) {
181        PWSTR buffer = static_cast<PWSTR>(GlobalLock(cbData));
182        _snwprintf(buffer, size, L"%s\n%s", mutableURL.charactersWithNullTermination(), mutableTitle.charactersWithNullTermination());
183        GlobalUnlock(cbData);
184    }
185    return cbData;
186}
187
188HGLOBAL createGlobalData(const String& str)
189{
190    HGLOBAL globalData = ::GlobalAlloc(GPTR, (str.length() + 1) * sizeof(UChar));
191    if (!globalData)
192        return 0;
193    UChar* buffer = static_cast<UChar*>(GlobalLock(globalData));
194    memcpy(buffer, str.characters(), str.length() * sizeof(UChar));
195    buffer[str.length()] = 0;
196    GlobalUnlock(globalData);
197    return globalData;
198}
199
200HGLOBAL createGlobalData(const Vector<char>& vector)
201{
202    HGLOBAL globalData = ::GlobalAlloc(GPTR, vector.size() + 1);
203    if (!globalData)
204        return 0;
205    char* buffer = static_cast<char*>(GlobalLock(globalData));
206    memcpy(buffer, vector.data(), vector.size());
207    buffer[vector.size()] = 0;
208    GlobalUnlock(globalData);
209    return globalData;
210}
211
212static String getFullCFHTML(IDataObject* data, bool& success)
213{
214    STGMEDIUM store;
215    if (SUCCEEDED(data->GetData(htmlFormat(), &store))) {
216        // MS HTML Format parsing
217        char* data = static_cast<char*>(GlobalLock(store.hGlobal));
218        SIZE_T dataSize = ::GlobalSize(store.hGlobal);
219        String cfhtml(UTF8Encoding().decode(data, dataSize));
220        GlobalUnlock(store.hGlobal);
221        ReleaseStgMedium(&store);
222        success = true;
223        return cfhtml;
224    }
225    success = false;
226    return String();
227}
228
229static void append(Vector<char>& vector, const char* string)
230{
231    vector.append(string, strlen(string));
232}
233
234static void append(Vector<char>& vector, const CString& string)
235{
236    vector.append(string.data(), string.length());
237}
238
239// Find the markup between "<!--StartFragment -->" and "<!--EndFragment -->", accounting for browser quirks.
240static String extractMarkupFromCFHTML(const String& cfhtml)
241{
242    unsigned markupStart = cfhtml.find("<html", 0, false);
243    unsigned tagStart = cfhtml.find("startfragment", markupStart, false);
244    unsigned fragmentStart = cfhtml.find('>', tagStart) + 1;
245    unsigned tagEnd = cfhtml.find("endfragment", fragmentStart, false);
246    unsigned fragmentEnd = cfhtml.reverseFind('<', tagEnd);
247    return cfhtml.substring(fragmentStart, fragmentEnd - fragmentStart).stripWhiteSpace();
248}
249
250// Documentation for the CF_HTML format is available at http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp
251void markupToCFHTML(const String& markup, const String& srcURL, Vector<char>& result)
252{
253    if (markup.isEmpty())
254        return;
255
256    #define MAX_DIGITS 10
257    #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
258    #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
259    #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
260
261    const char* header = "Version:0.9\n"
262        "StartHTML:" NUMBER_FORMAT "\n"
263        "EndHTML:" NUMBER_FORMAT "\n"
264        "StartFragment:" NUMBER_FORMAT "\n"
265        "EndFragment:" NUMBER_FORMAT "\n";
266    const char* sourceURLPrefix = "SourceURL:";
267
268    const char* startMarkup = "<HTML>\n<BODY>\n<!--StartFragment-->\n";
269    const char* endMarkup = "\n<!--EndFragment-->\n</BODY>\n</HTML>";
270
271    CString sourceURLUTF8 = srcURL == blankURL() ? "" : srcURL.utf8();
272    CString markupUTF8 = markup.utf8();
273
274    // calculate offsets
275    unsigned startHTMLOffset = strlen(header) - strlen(NUMBER_FORMAT) * 4 + MAX_DIGITS * 4;
276    if (sourceURLUTF8.length())
277        startHTMLOffset += strlen(sourceURLPrefix) + sourceURLUTF8.length() + 1;
278    unsigned startFragmentOffset = startHTMLOffset + strlen(startMarkup);
279    unsigned endFragmentOffset = startFragmentOffset + markupUTF8.length();
280    unsigned endHTMLOffset = endFragmentOffset + strlen(endMarkup);
281
282    unsigned headerBufferLength = startHTMLOffset + 1; // + 1 for '\0' terminator.
283    char* headerBuffer = (char*)malloc(headerBufferLength);
284    snprintf(headerBuffer, headerBufferLength, header, startHTMLOffset, endHTMLOffset, startFragmentOffset, endFragmentOffset);
285    append(result, CString(headerBuffer));
286    free(headerBuffer);
287    if (sourceURLUTF8.length()) {
288        append(result, sourceURLPrefix);
289        append(result, sourceURLUTF8);
290        result.append('\n');
291    }
292    append(result, startMarkup);
293    append(result, markupUTF8);
294    append(result, endMarkup);
295
296    #undef MAX_DIGITS
297    #undef MAKE_NUMBER_FORMAT_1
298    #undef MAKE_NUMBER_FORMAT_2
299    #undef NUMBER_FORMAT
300}
301
302void replaceNewlinesWithWindowsStyleNewlines(String& str)
303{
304    static const UChar Newline = '\n';
305    static const char* const WindowsNewline("\r\n");
306    str.replace(Newline, WindowsNewline);
307}
308
309void replaceNBSPWithSpace(String& str)
310{
311    static const UChar NonBreakingSpaceCharacter = 0xA0;
312    static const UChar SpaceCharacter = ' ';
313    str.replace(NonBreakingSpaceCharacter, SpaceCharacter);
314}
315
316FORMATETC* urlWFormat()
317{
318    static UINT cf = RegisterClipboardFormat(L"UniformResourceLocatorW");
319    static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
320    return &urlFormat;
321}
322
323FORMATETC* urlFormat()
324{
325    static UINT cf = RegisterClipboardFormat(L"UniformResourceLocator");
326    static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
327    return &urlFormat;
328}
329
330FORMATETC* plainTextFormat()
331{
332    static FORMATETC textFormat = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
333    return &textFormat;
334}
335
336FORMATETC* plainTextWFormat()
337{
338    static FORMATETC textFormat = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
339    return &textFormat;
340}
341
342FORMATETC* filenameWFormat()
343{
344    static UINT cf = RegisterClipboardFormat(L"FileNameW");
345    static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
346    return &urlFormat;
347}
348
349FORMATETC* filenameFormat()
350{
351    static UINT cf = RegisterClipboardFormat(L"FileName");
352    static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
353    return &urlFormat;
354}
355
356// MSIE HTML Format
357FORMATETC* htmlFormat()
358{
359    static UINT cf = RegisterClipboardFormat(L"HTML Format");
360    static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
361    return &htmlFormat;
362}
363
364FORMATETC* smartPasteFormat()
365{
366    static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format");
367    static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
368    return &htmlFormat;
369}
370
371String getURL(IDataObject* dataObject, DragData::FilenameConversionPolicy filenamePolicy, bool& success, String* title)
372{
373    STGMEDIUM store;
374    String url;
375    success = false;
376    if (getWebLocData(dataObject, url, title))
377        success = true;
378    else if (SUCCEEDED(dataObject->GetData(urlWFormat(), &store))) {
379        // URL using Unicode
380        UChar* data = static_cast<UChar*>(GlobalLock(store.hGlobal));
381        url = extractURL(String(data), title);
382        GlobalUnlock(store.hGlobal);
383        ReleaseStgMedium(&store);
384        success = true;
385    } else if (SUCCEEDED(dataObject->GetData(urlFormat(), &store))) {
386        // URL using ASCII
387        char* data = static_cast<char*>(GlobalLock(store.hGlobal));
388        url = extractURL(String(data), title);
389        GlobalUnlock(store.hGlobal);
390        ReleaseStgMedium(&store);
391        success = true;
392    }
393#if USE(CF)
394    else if (filenamePolicy == DragData::ConvertFilenames) {
395        if (SUCCEEDED(dataObject->GetData(filenameWFormat(), &store))) {
396            // file using unicode
397            wchar_t* data = static_cast<wchar_t*>(GlobalLock(store.hGlobal));
398            if (data && data[0] && (PathFileExists(data) || PathIsUNC(data))) {
399                RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar*)data, wcslen(data)));
400                if (urlFromPath(pathAsCFString.get(), url)) {
401                    if (title)
402                        *title = url;
403                    success = true;
404                }
405            }
406            GlobalUnlock(store.hGlobal);
407            ReleaseStgMedium(&store);
408        } else if (SUCCEEDED(dataObject->GetData(filenameFormat(), &store))) {
409            // filename using ascii
410            char* data = static_cast<char*>(GlobalLock(store.hGlobal));
411            if (data && data[0] && (PathFileExistsA(data) || PathIsUNCA(data))) {
412                RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCString(kCFAllocatorDefault, data, kCFStringEncodingASCII));
413                if (urlFromPath(pathAsCFString.get(), url)) {
414                    if (title)
415                        *title = url;
416                    success = true;
417                }
418            }
419            GlobalUnlock(store.hGlobal);
420            ReleaseStgMedium(&store);
421        }
422    }
423#endif
424    return url;
425}
426
427String getURL(const DragDataMap* data, DragData::FilenameConversionPolicy filenamePolicy, String* title)
428{
429    String url;
430
431    if (getWebLocData(data, url, title))
432        return url;
433    if (getDataMapItem(data, urlWFormat(), url))
434        return extractURL(url, title);
435    if (getDataMapItem(data, urlFormat(), url))
436        return extractURL(url, title);
437#if USE(CF)
438    if (filenamePolicy != DragData::ConvertFilenames)
439        return url;
440
441    String stringData;
442    if (!getDataMapItem(data, filenameWFormat(), stringData))
443        getDataMapItem(data, filenameFormat(), stringData);
444
445    if (stringData.isEmpty() || (!PathFileExists(stringData.charactersWithNullTermination()) && !PathIsUNC(stringData.charactersWithNullTermination())))
446        return url;
447    RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar *)stringData.charactersWithNullTermination(), wcslen(stringData.charactersWithNullTermination())));
448    if (urlFromPath(pathAsCFString.get(), url) && title)
449        *title = url;
450#endif
451    return url;
452}
453
454String getPlainText(IDataObject* dataObject, bool& success)
455{
456    STGMEDIUM store;
457    String text;
458    success = false;
459    if (SUCCEEDED(dataObject->GetData(plainTextWFormat(), &store))) {
460        // Unicode text
461        UChar* data = static_cast<UChar*>(GlobalLock(store.hGlobal));
462        text = String(data);
463        GlobalUnlock(store.hGlobal);
464        ReleaseStgMedium(&store);
465        success = true;
466    } else if (SUCCEEDED(dataObject->GetData(plainTextFormat(), &store))) {
467        // ASCII text
468        char* data = static_cast<char*>(GlobalLock(store.hGlobal));
469        text = String(data);
470        GlobalUnlock(store.hGlobal);
471        ReleaseStgMedium(&store);
472        success = true;
473    } else {
474        // FIXME: Originally, we called getURL() here because dragging and dropping files doesn't
475        // populate the drag with text data. Per https://bugs.webkit.org/show_bug.cgi?id=38826, this
476        // is undesirable, so maybe this line can be removed.
477        text = getURL(dataObject, DragData::DoNotConvertFilenames, success);
478        success = true;
479    }
480    return text;
481}
482
483String getPlainText(const DragDataMap* data)
484{
485    String text;
486
487    if (getDataMapItem(data, plainTextWFormat(), text))
488        return text;
489    if (getDataMapItem(data, plainTextFormat(), text))
490        return text;
491    return getURL(data, DragData::DoNotConvertFilenames);
492}
493
494String getTextHTML(IDataObject* data, bool& success)
495{
496    STGMEDIUM store;
497    String html;
498    success = false;
499    if (SUCCEEDED(data->GetData(texthtmlFormat(), &store))) {
500        UChar* data = static_cast<UChar*>(GlobalLock(store.hGlobal));
501        html = String(data);
502        GlobalUnlock(store.hGlobal);
503        ReleaseStgMedium(&store);
504        success = true;
505    }
506    return html;
507}
508
509String getTextHTML(const DragDataMap* data)
510{
511    String text;
512    getDataMapItem(data, texthtmlFormat(), text);
513    return text;
514}
515
516String getCFHTML(IDataObject* data, bool& success)
517{
518    String cfhtml = getFullCFHTML(data, success);
519    if (success)
520        return extractMarkupFromCFHTML(cfhtml);
521    return String();
522}
523
524String getCFHTML(const DragDataMap* dataMap)
525{
526    String cfhtml;
527    getDataMapItem(dataMap, htmlFormat(), cfhtml);
528    return extractMarkupFromCFHTML(cfhtml);
529}
530
531PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const IDataObject*)
532{
533    // FIXME: We should be able to create fragments from files
534    return 0;
535}
536
537PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const DragDataMap*)
538{
539    // FIXME: We should be able to create fragments from files
540    return 0;
541}
542
543bool containsFilenames(const IDataObject*)
544{
545    // FIXME: We'll want to update this once we can produce fragments from files
546    return false;
547}
548
549bool containsFilenames(const DragDataMap*)
550{
551    // FIXME: We'll want to update this once we can produce fragments from files
552    return false;
553}
554
555// Convert a String containing CF_HTML formatted text to a DocumentFragment
556PassRefPtr<DocumentFragment> fragmentFromCFHTML(Document* doc, const String& cfhtml)
557{
558    // obtain baseURL if present
559    String srcURLStr("sourceURL:");
560    String srcURL;
561    unsigned lineStart = cfhtml.find(srcURLStr, 0, false);
562    if (lineStart != -1) {
563        unsigned srcEnd = cfhtml.find("\n", lineStart, false);
564        unsigned srcStart = lineStart+srcURLStr.length();
565        String rawSrcURL = cfhtml.substring(srcStart, srcEnd-srcStart);
566        replaceNBSPWithSpace(rawSrcURL);
567        srcURL = rawSrcURL.stripWhiteSpace();
568    }
569
570    String markup = extractMarkupFromCFHTML(cfhtml);
571    return createFragmentFromMarkup(doc, markup, srcURL, FragmentScriptingNotAllowed);
572}
573
574PassRefPtr<DocumentFragment> fragmentFromHTML(Document* doc, IDataObject* data)
575{
576    if (!doc || !data)
577        return 0;
578
579    bool success = false;
580    String cfhtml = getFullCFHTML(data, success);
581    if (success) {
582        if (RefPtr<DocumentFragment> fragment = fragmentFromCFHTML(doc, cfhtml))
583            return fragment.release();
584    }
585
586    String html = getTextHTML(data, success);
587    String srcURL;
588    if (success)
589        return createFragmentFromMarkup(doc, html, srcURL, FragmentScriptingNotAllowed);
590
591    return 0;
592}
593
594PassRefPtr<DocumentFragment> fragmentFromHTML(Document* document, const DragDataMap* data)
595{
596    if (!document || !data || data->isEmpty())
597        return 0;
598
599    String stringData;
600    if (getDataMapItem(data, htmlFormat(), stringData)) {
601        if (RefPtr<DocumentFragment> fragment = fragmentFromCFHTML(document, stringData))
602            return fragment.release();
603    }
604
605    String srcURL;
606    if (getDataMapItem(data, texthtmlFormat(), stringData))
607        return createFragmentFromMarkup(document, stringData, srcURL, FragmentScriptingNotAllowed);
608
609    return 0;
610}
611
612bool containsHTML(IDataObject* data)
613{
614    return SUCCEEDED(data->QueryGetData(texthtmlFormat())) || SUCCEEDED(data->QueryGetData(htmlFormat()));
615}
616
617bool containsHTML(const DragDataMap* data)
618{
619    return data->contains(texthtmlFormat()->cfFormat) || data->contains(htmlFormat()->cfFormat);
620}
621
622typedef void (*GetStringFunction)(IDataObject*, FORMATETC*, Vector<String>&);
623typedef void (*SetStringFunction)(IDataObject*, FORMATETC*, const Vector<String>&);
624
625struct ClipboardDataItem {
626    GetStringFunction getString;
627    SetStringFunction setString;
628    FORMATETC* format;
629
630    ClipboardDataItem(FORMATETC* format, GetStringFunction getString, SetStringFunction setString): format(format), getString(getString), setString(setString) { }
631};
632
633typedef HashMap<UINT, ClipboardDataItem*> ClipboardFormatMap;
634
635// Getter functions.
636
637template<typename T> void getStringData(IDataObject* data, FORMATETC* format, Vector<String>& dataStrings)
638{
639    STGMEDIUM store;
640    if (FAILED(data->GetData(format, &store)))
641        return;
642    dataStrings.append(String(static_cast<T*>(GlobalLock(store.hGlobal)), ::GlobalSize(store.hGlobal) / sizeof(T)));
643    GlobalUnlock(store.hGlobal);
644    ReleaseStgMedium(&store);
645}
646
647void getUtf8Data(IDataObject* data, FORMATETC* format, Vector<String>& dataStrings)
648{
649    STGMEDIUM store;
650    if (FAILED(data->GetData(format, &store)))
651        return;
652    dataStrings.append(String(UTF8Encoding().decode(static_cast<char*>(GlobalLock(store.hGlobal)), GlobalSize(store.hGlobal))));
653    GlobalUnlock(store.hGlobal);
654    ReleaseStgMedium(&store);
655}
656
657#if USE(CF)
658void getCFData(IDataObject* data, FORMATETC* format, Vector<String>& dataStrings)
659{
660    STGMEDIUM store;
661    if (FAILED(data->GetData(format, &store)))
662        return;
663
664    HDROP hdrop = reinterpret_cast<HDROP>(GlobalLock(store.hGlobal));
665    if (!hdrop)
666        return;
667
668    WCHAR filename[MAX_PATH];
669    UINT fileCount = DragQueryFileW(hdrop, 0xFFFFFFFF, 0, 0);
670    for (UINT i = 0; i < fileCount; i++) {
671        if (!DragQueryFileW(hdrop, i, filename, WTF_ARRAY_LENGTH(filename)))
672            continue;
673        dataStrings.append(static_cast<UChar*>(filename));
674    }
675
676    GlobalUnlock(store.hGlobal);
677    ReleaseStgMedium(&store);
678}
679#endif
680
681// Setter functions.
682
683void setUCharData(IDataObject* data, FORMATETC* format, const Vector<String>& dataStrings)
684{
685    STGMEDIUM medium = {0};
686    medium.tymed = TYMED_HGLOBAL;
687
688    medium.hGlobal = createGlobalData(dataStrings.first());
689    if (!medium.hGlobal)
690        return;
691    data->SetData(format, &medium, FALSE);
692    ::GlobalFree(medium.hGlobal);
693}
694
695void setUtf8Data(IDataObject* data, FORMATETC* format, const Vector<String>& dataStrings)
696{
697    STGMEDIUM medium = {0};
698    medium.tymed = TYMED_HGLOBAL;
699
700    CString charString = dataStrings.first().utf8();
701    size_t stringLength = charString.length();
702    medium.hGlobal = ::GlobalAlloc(GPTR, stringLength + 1);
703    if (!medium.hGlobal)
704        return;
705    char* buffer = static_cast<char*>(GlobalLock(medium.hGlobal));
706    memcpy(buffer, charString.data(), stringLength);
707    buffer[stringLength] = 0;
708    GlobalUnlock(medium.hGlobal);
709    data->SetData(format, &medium, FALSE);
710    ::GlobalFree(medium.hGlobal);
711}
712
713#if USE(CF)
714void setCFData(IDataObject* data, FORMATETC* format, const Vector<String>& dataStrings)
715{
716    STGMEDIUM medium = {0};
717    SIZE_T dropFilesSize = sizeof(DROPFILES) + (sizeof(WCHAR) * (dataStrings.first().length() + 2));
718    medium.hGlobal = ::GlobalAlloc(GHND | GMEM_SHARE, dropFilesSize);
719    if (!medium.hGlobal)
720        return;
721
722    DROPFILES* dropFiles = reinterpret_cast<DROPFILES *>(GlobalLock(medium.hGlobal));
723    dropFiles->pFiles = sizeof(DROPFILES);
724    dropFiles->fWide = TRUE;
725    String filename = dataStrings.first();
726    wcscpy(reinterpret_cast<LPWSTR>(dropFiles + 1), filename.charactersWithNullTermination());
727    GlobalUnlock(medium.hGlobal);
728    data->SetData(format, &medium, FALSE);
729    ::GlobalFree(medium.hGlobal);
730}
731#endif
732
733static const ClipboardFormatMap& getClipboardMap()
734{
735    static ClipboardFormatMap formatMap;
736    if (formatMap.isEmpty()) {
737        formatMap.add(htmlFormat()->cfFormat, new ClipboardDataItem(htmlFormat(), getUtf8Data, setUtf8Data));
738        formatMap.add(texthtmlFormat()->cfFormat, new ClipboardDataItem(texthtmlFormat(), getStringData<UChar>, setUCharData));
739        formatMap.add(plainTextFormat()->cfFormat,  new ClipboardDataItem(plainTextFormat(), getStringData<char>, setUtf8Data));
740        formatMap.add(plainTextWFormat()->cfFormat,  new ClipboardDataItem(plainTextWFormat(), getStringData<UChar>, setUCharData));
741#if USE(CF)
742        formatMap.add(cfHDropFormat()->cfFormat,  new ClipboardDataItem(cfHDropFormat(), getCFData, setCFData));
743#endif
744        formatMap.add(filenameFormat()->cfFormat,  new ClipboardDataItem(filenameFormat(), getStringData<char>, setUtf8Data));
745        formatMap.add(filenameWFormat()->cfFormat,  new ClipboardDataItem(filenameWFormat(), getStringData<UChar>, setUCharData));
746        formatMap.add(urlFormat()->cfFormat,  new ClipboardDataItem(urlFormat(), getStringData<char>, setUtf8Data));
747        formatMap.add(urlWFormat()->cfFormat,  new ClipboardDataItem(urlWFormat(), getStringData<UChar>, setUCharData));
748    }
749    return formatMap;
750}
751
752void getClipboardData(IDataObject* dataObject, FORMATETC* format, Vector<String>& dataStrings)
753{
754    const ClipboardFormatMap& formatMap = getClipboardMap();
755    ClipboardFormatMap::const_iterator found = formatMap.find(format->cfFormat);
756    if (found == formatMap.end())
757        return;
758    found->second->getString(dataObject, found->second->format, dataStrings);
759}
760
761void setClipboardData(IDataObject* dataObject, UINT format, const Vector<String>& dataStrings)
762{
763    const ClipboardFormatMap& formatMap = getClipboardMap();
764    ClipboardFormatMap::const_iterator found = formatMap.find(format);
765    if (found == formatMap.end())
766        return;
767    found->second->setString(dataObject, found->second->format, dataStrings);
768}
769
770} // namespace WebCore
771