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