2011-02-23 Patrick Gansterer <paroga@webkit.org>
[WebKit-https.git] / Source / WebCore / platform / win / ClipboardUtilitiesWin.cpp
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
46 namespace WebCore {
47
48 #if USE(CF)
49 FORMATETC* cfHDropFormat()
50 {
51     static FORMATETC urlFormat = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
52     return &urlFormat;
53 }
54
55 static 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
74 static 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
83 static 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
116 exit:
117     // Free up memory.
118     DragFinish(hdrop);
119     GlobalUnlock(medium.hGlobal);
120 #endif
121     return succeeded;
122 }
123
124 static 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].characters());
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
152 static 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
166 static 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
173 HGLOBAL 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
188 HGLOBAL 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
200 HGLOBAL 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
212 static 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
229 static void append(Vector<char>& vector, const char* string)
230 {
231     vector.append(string, strlen(string));
232 }
233
234 static 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.
240 static 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
251 void 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
302 void 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
309 void replaceNBSPWithSpace(String& str)
310 {
311     static const UChar NonBreakingSpaceCharacter = 0xA0;
312     static const UChar SpaceCharacter = ' ';
313     str.replace(NonBreakingSpaceCharacter, SpaceCharacter);
314 }
315
316 FORMATETC* 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
323 FORMATETC* 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
330 FORMATETC* plainTextFormat()
331 {
332     static FORMATETC textFormat = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
333     return &textFormat;
334 }
335
336 FORMATETC* plainTextWFormat()
337 {
338     static FORMATETC textFormat = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
339     return &textFormat;
340 }
341
342 FORMATETC* 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
349 FORMATETC* 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
357 FORMATETC* 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
364 FORMATETC* 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
371 String 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
427 String 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(), stringData.length()));
448     if (urlFromPath(pathAsCFString.get(), url) && title)
449         *title = url;
450 #endif
451     return url;
452 }
453
454 String 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
483 String 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
494 String 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
509 String getTextHTML(const DragDataMap* data)
510 {
511     String text;
512     getDataMapItem(data, texthtmlFormat(), text);
513     return text;
514 }
515
516 String getCFHTML(IDataObject* data, bool& success)
517 {
518     String cfhtml = getFullCFHTML(data, success);
519     if (success)
520         return extractMarkupFromCFHTML(cfhtml);
521     return String();
522 }
523
524 String getCFHTML(const DragDataMap* dataMap)
525 {
526     String cfhtml;
527     getDataMapItem(dataMap, htmlFormat(), cfhtml);
528     return extractMarkupFromCFHTML(cfhtml);
529 }
530
531 PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const IDataObject*)
532 {
533     // FIXME: We should be able to create fragments from files
534     return 0;
535 }
536
537 PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const DragDataMap*)
538 {
539     // FIXME: We should be able to create fragments from files
540     return 0;
541 }
542
543 bool containsFilenames(const IDataObject*)
544 {
545     // FIXME: We'll want to update this once we can produce fragments from files
546     return false;
547 }
548
549 bool 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
556 PassRefPtr<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
574 PassRefPtr<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
594 PassRefPtr<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
612 bool containsHTML(IDataObject* data)
613 {
614     return SUCCEEDED(data->QueryGetData(texthtmlFormat())) || SUCCEEDED(data->QueryGetData(htmlFormat()));
615 }
616
617 bool containsHTML(const DragDataMap* data)
618 {
619     return data->contains(texthtmlFormat()->cfFormat) || data->contains(htmlFormat()->cfFormat);
620 }
621
622 typedef void (*GetStringFunction)(IDataObject*, FORMATETC*, Vector<String>&);
623 typedef void (*SetStringFunction)(IDataObject*, FORMATETC*, const Vector<String>&);
624
625 struct 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
633 typedef HashMap<UINT, ClipboardDataItem*> ClipboardFormatMap;
634
635 // Getter functions.
636
637 template<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
647 void 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)
658 void 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
683 void 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
695 void 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)
714 void 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
733 static 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
752 void 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
761 void 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