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