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