Support smart copy and paste during drag and drop
[WebKit-https.git] / WebCore / platform / win / ClipboardUtilitiesWin.cpp
1 /*
2  * Copyright (C) 2007 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 "KURL.h"
30 #include "CString.h"
31 #include "DocumentFragment.h"
32 #include "markup.h"
33 #include "PlatformString.h"
34 #include "TextEncoding.h"
35 #include <CoreFoundation/CoreFoundation.h>
36 #include <wtf/RetainPtr.h>
37 #include <shlwapi.h>
38 #include <wininet.h>    // for INTERNET_MAX_URL_LENGTH
39
40 namespace WebCore {
41
42 FORMATETC* cfHDropFormat()
43 {
44     static FORMATETC urlFormat = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
45     return &urlFormat;
46 }
47
48 static bool getWebLocData(IDataObject* dataObject, String& url, String* title) 
49 {
50     bool succeeded = false;
51     WCHAR filename[MAX_PATH];
52     WCHAR urlBuffer[INTERNET_MAX_URL_LENGTH];
53
54     STGMEDIUM medium;
55     if (FAILED(dataObject->GetData(cfHDropFormat(), &medium)))
56         return false;
57
58     HDROP hdrop = (HDROP)GlobalLock(medium.hGlobal);
59    
60     if (!hdrop)
61         return false;
62
63     if (!DragQueryFileW(hdrop, 0, filename, ARRAYSIZE(filename)))
64         goto exit;
65
66     if (_wcsicmp(PathFindExtensionW(filename), L".url"))
67         goto exit;    
68     
69     if (!GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, urlBuffer, ARRAYSIZE(urlBuffer), filename))
70         goto exit;
71     
72     if (title) {
73         PathRemoveExtension(filename);
74         *title = String((UChar*)filename);
75     }
76     
77     url = String((UChar*)urlBuffer);
78     succeeded = true;
79
80 exit:
81     // Free up memory.
82     DragFinish(hdrop);
83     GlobalUnlock(medium.hGlobal);
84     return succeeded;
85 }
86
87 static String extractURL(const String &inURL, String* title)
88 {
89     String url = inURL;
90     int splitLoc = url.find('\n');
91     if (splitLoc > 0) {
92         if (title)
93             *title = url.substring(splitLoc+1);
94         url.truncate(splitLoc);
95     } else if (title)
96         *title = url;
97     return url;
98 }
99
100 //Firefox text/html
101 static FORMATETC* texthtmlFormat() 
102 {
103     static UINT cf = RegisterClipboardFormat(L"text/html");
104     static FORMATETC texthtmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
105     return &texthtmlFormat;
106 }
107
108 HGLOBAL createGlobalData(const KURL& url, const String& title)
109 {
110     String mutableURL(url.string());
111     String mutableTitle(title);
112     SIZE_T size = mutableURL.length() + mutableTitle.length() + 2;  // +1 for "\n" and +1 for null terminator
113     HGLOBAL cbData = ::GlobalAlloc(GPTR, size * sizeof(UChar));
114
115     if (cbData) {
116         PWSTR buffer = (PWSTR)::GlobalLock(cbData);
117         swprintf_s(buffer, size, L"%s\n%s", mutableURL.charactersWithNullTermination(), mutableTitle.charactersWithNullTermination());
118         ::GlobalUnlock(cbData);
119     }
120     return cbData;
121 }
122
123 HGLOBAL createGlobalData(String str)
124 {   
125     SIZE_T size = (str.length() + 1) * sizeof(UChar);
126     HGLOBAL cbData = ::GlobalAlloc(GPTR, size);
127     if (cbData) {
128         void* buffer = ::GlobalLock(cbData);
129         memcpy(buffer, str.charactersWithNullTermination(), size);
130         ::GlobalUnlock(cbData);
131     }
132     return cbData;
133 }
134
135 HGLOBAL createGlobalData(CString str)
136 {
137     SIZE_T size = str.length() * sizeof(char);
138     HGLOBAL cbData = ::GlobalAlloc(GPTR, size + 1);
139     if (cbData) {
140         char* buffer = static_cast<char*>(::GlobalLock(cbData));
141         memcpy(buffer, str.data(), size);
142         buffer[size] = 0;
143         ::GlobalUnlock(cbData);
144     }
145     return cbData;
146 }
147
148 // Documentation for the CF_HTML format is available at http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp
149 DeprecatedCString markupToCF_HTML(const String& markup, const String& srcURL)
150 {
151     if (!markup.length())
152         return DeprecatedCString();
153
154     DeprecatedCString cf_html        ("Version:0.9");
155     DeprecatedCString startHTML      ("\nStartHTML:");
156     DeprecatedCString endHTML        ("\nEndHTML:");
157     DeprecatedCString startFragment  ("\nStartFragment:");
158     DeprecatedCString endFragment    ("\nEndFragment:");
159     DeprecatedCString sourceURL      ("\nSourceURL:");
160
161     bool shouldFillSourceURL = !srcURL.isEmpty() && (srcURL != "about:blank");
162     if (shouldFillSourceURL)
163         sourceURL.append(srcURL.utf8().data());
164
165     DeprecatedCString startMarkup    ("\n<HTML>\n<BODY>\n<!--StartFragment-->\n");
166     DeprecatedCString endMarkup      ("\n<!--EndFragment-->\n</BODY>\n</HTML>");
167
168     // calculate offsets
169     const unsigned UINT_MAXdigits = 10; // number of digits in UINT_MAX in base 10
170     unsigned startHTMLOffset = cf_html.length() + startHTML.length() + endHTML.length() + startFragment.length() + endFragment.length() + (shouldFillSourceURL ? sourceURL.length() : 0) + (4*UINT_MAXdigits);
171     unsigned startFragmentOffset = startHTMLOffset + startMarkup.length();
172     CString markupUTF8 = markup.utf8();
173     unsigned endFragmentOffset = startFragmentOffset + markupUTF8.length();
174     unsigned endHTMLOffset = endFragmentOffset + endMarkup.length();
175
176     // fill in needed data
177     startHTML.append(String::format("%010u", startHTMLOffset).deprecatedString().utf8());
178     endHTML.append(String::format("%010u", endHTMLOffset).deprecatedString().utf8());
179     startFragment.append(String::format("%010u", startFragmentOffset).deprecatedString().utf8());
180     endFragment.append(String::format("%010u", endFragmentOffset).deprecatedString().utf8());
181     startMarkup.append(markupUTF8.data());
182
183     // create full cf_html string from the fragments
184     cf_html.append(startHTML);
185     cf_html.append(endHTML);
186     cf_html.append(startFragment);
187     cf_html.append(endFragment);
188     if (shouldFillSourceURL)
189         cf_html.append(sourceURL);
190     cf_html.append(startMarkup);
191     cf_html.append(endMarkup);
192
193     return cf_html;
194 }
195
196 String urlToMarkup(const KURL& url, const String& title)
197 {
198     String markup("<a href=\"");
199     markup.append(url.string());
200     markup.append("\">");
201     markup.append(title);
202     markup.append("</a>");
203     return markup;
204 }
205
206 void replaceNewlinesWithWindowsStyleNewlines(String& str)
207 {
208     static const UChar Newline = '\n';
209     static const String WindowsNewline("\r\n");
210     str.replace(Newline, WindowsNewline);
211 }
212
213 void replaceNBSPWithSpace(String& str)
214 {
215     static const UChar NonBreakingSpaceCharacter = 0xA0;
216     static const UChar SpaceCharacter = ' ';
217     str.replace(NonBreakingSpaceCharacter, SpaceCharacter);
218 }
219
220 FORMATETC* urlWFormat()
221 {
222     static UINT cf = RegisterClipboardFormat(L"UniformResourceLocatorW");
223     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
224     return &urlFormat;
225 }
226
227 FORMATETC* urlFormat()
228 {
229     static UINT cf = RegisterClipboardFormat(L"UniformResourceLocator");
230     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
231     return &urlFormat;
232 }
233
234 FORMATETC* plainTextFormat()
235 {
236     static FORMATETC textFormat = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
237     return &textFormat;
238 }
239
240 FORMATETC* plainTextWFormat()
241 {
242     static FORMATETC textFormat = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
243     return &textFormat;
244 }
245
246 FORMATETC* filenameWFormat()
247 {
248     static UINT cf = RegisterClipboardFormat(L"FileNameW");
249     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
250     return &urlFormat;
251 }
252
253 FORMATETC* filenameFormat()
254 {
255     static UINT cf = RegisterClipboardFormat(L"FileName");
256     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
257     return &urlFormat;
258 }
259
260 //MSIE HTML Format
261 FORMATETC* htmlFormat() 
262 {
263     static UINT cf = RegisterClipboardFormat(L"HTML Format");
264     static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
265     return &htmlFormat;
266 }
267
268 FORMATETC* smartPasteFormat()
269 {
270     static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format");
271     static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
272     return &htmlFormat;
273 }
274
275 static bool urlFromPath(CFStringRef path, String& url)
276 {
277     if (!path)
278         return false;
279
280     RetainPtr<CFURLRef> cfURL(AdoptCF, CFURLCreateWithFileSystemPath(0, path, kCFURLWindowsPathStyle, false));
281     if (!cfURL)
282         return false;
283
284     url = String(CFURLGetString(cfURL.get()));
285     return true;
286 }
287
288 String getURL(IDataObject* dataObject, bool& success, String* title)
289 {
290     STGMEDIUM store;
291     String url;
292     success = false;
293     if (getWebLocData(dataObject, url, title)) {
294         success = true;
295         return url;
296     } else if (SUCCEEDED(dataObject->GetData(urlWFormat(), &store))) {
297         //URL using unicode
298         UChar* data = (UChar*)GlobalLock(store.hGlobal);
299         url = extractURL(String(data), title);
300         GlobalUnlock(store.hGlobal);      
301         ReleaseStgMedium(&store);
302         success = true;
303     } else if (SUCCEEDED(dataObject->GetData(urlFormat(), &store))) {
304         //URL using ascii
305         char* data = (char*)GlobalLock(store.hGlobal);
306         url = extractURL(String(data), title);
307         GlobalUnlock(store.hGlobal);      
308         ReleaseStgMedium(&store);
309         success = true;
310     } else if (SUCCEEDED(dataObject->GetData(filenameWFormat(), &store))) {
311         //file using unicode
312         wchar_t* data = (wchar_t*)GlobalLock(store.hGlobal);
313         if (data && data[0] && (PathFileExists(data) || PathIsUNC(data))) {
314             RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar*)data, wcslen(data)));
315             if (urlFromPath(pathAsCFString.get(), url)) {
316                 if (title)
317                     *title = url;
318                 success = true;
319             }
320         }
321         GlobalUnlock(store.hGlobal);      
322         ReleaseStgMedium(&store);
323     } else if (SUCCEEDED(dataObject->GetData(filenameFormat(), &store))) {
324         //filename using ascii
325         char* data = (char*)GlobalLock(store.hGlobal);       
326         if (data && data[0] && (PathFileExistsA(data) || PathIsUNCA(data))) {
327             RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCString(kCFAllocatorDefault, data, kCFStringEncodingASCII));
328             if (urlFromPath(pathAsCFString.get(), url)) {
329                 if (title)
330                     *title = url;
331                 success = true;
332             }
333         }
334         GlobalUnlock(store.hGlobal);      
335         ReleaseStgMedium(&store);
336     }
337     return url;
338 }
339
340 String getPlainText(IDataObject* dataObject, bool& success)
341 {
342     STGMEDIUM store;
343     String text;
344     success = false;
345     if (SUCCEEDED(dataObject->GetData(plainTextWFormat(), &store))) {
346         //unicode text
347         UChar* data = (UChar*)GlobalLock(store.hGlobal);
348         text = String(data);
349         GlobalUnlock(store.hGlobal);      
350         ReleaseStgMedium(&store);
351         success = true;
352     } else if (SUCCEEDED(dataObject->GetData(plainTextFormat(), &store))) {
353         //ascii text
354         char* data = (char*)GlobalLock(store.hGlobal);
355         text = String(data);
356         GlobalUnlock(store.hGlobal);      
357         ReleaseStgMedium(&store);
358         success = true;
359     } else {
360         //If a file is dropped on the window, it does not provide either of the 
361         //plain text formats, so here we try to forcibly get a url.
362         text = getURL(dataObject, success);
363         success = true;
364     }
365     return text;
366 }
367
368 PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const IDataObject*)
369 {
370     //FIXME: We should be able to create fragments from files
371     return 0;
372 }
373
374 bool containsFilenames(const IDataObject*)
375 {
376     //FIXME: We'll want to update this once we can produce fragments from files
377     return false;
378 }
379
380 //Convert a String containing CF_HTML formatted text to a DocumentFragment
381 PassRefPtr<DocumentFragment> fragmentFromCF_HTML(Document* doc, const String& cf_html)
382 {        
383     // obtain baseURL if present
384     String srcURLStr("sourceURL:");
385     String srcURL;
386     unsigned lineStart = cf_html.find(srcURLStr, 0, false);
387     if (lineStart != -1) {
388         unsigned srcEnd = cf_html.find("\n", lineStart, false);
389         unsigned srcStart = lineStart+srcURLStr.length();
390         String rawSrcURL = cf_html.substring(srcStart, srcEnd-srcStart);
391         replaceNBSPWithSpace(rawSrcURL);
392         srcURL = rawSrcURL.stripWhiteSpace();
393     }
394
395     // find the markup between "<!--StartFragment -->" and "<!--EndFragment -->", accounting for browser quirks
396     unsigned markupStart = cf_html.find("<html", 0, false);
397     unsigned tagStart = cf_html.find("startfragment", markupStart, false);
398     unsigned fragmentStart = cf_html.find('>', tagStart) + 1;
399     unsigned tagEnd = cf_html.find("endfragment", fragmentStart, false);
400     unsigned fragmentEnd = cf_html.reverseFind('<', tagEnd);
401     String markup = cf_html.substring(fragmentStart, fragmentEnd - fragmentStart).stripWhiteSpace();
402
403     return createFragmentFromMarkup(doc, markup, srcURL).releaseRef();
404 }
405
406
407 PassRefPtr<DocumentFragment> fragmentFromHTML(Document* doc, IDataObject* data) 
408 {
409     if (!doc || !data)
410         return 0;
411
412     STGMEDIUM store;
413     String html;
414     String srcURL;
415     if (SUCCEEDED(data->GetData(htmlFormat(), &store))) {
416         //MS HTML Format parsing
417         char* data = (char*)GlobalLock(store.hGlobal);
418         SIZE_T dataSize = ::GlobalSize(store.hGlobal);
419         String cf_html(UTF8Encoding().decode(data, dataSize));         
420         GlobalUnlock(store.hGlobal);
421         ReleaseStgMedium(&store); 
422         if (PassRefPtr<DocumentFragment> fragment = fragmentFromCF_HTML(doc, cf_html))
423             return fragment;
424     } 
425     if (SUCCEEDED(data->GetData(texthtmlFormat(), &store))) {
426         //raw html
427         UChar* data = (UChar*)GlobalLock(store.hGlobal);
428         html = String(data);
429         GlobalUnlock(store.hGlobal);      
430         ReleaseStgMedium(&store);
431         return createFragmentFromMarkup(doc, html, srcURL);
432     } 
433
434     return 0;
435 }
436
437 bool containsHTML(IDataObject* data)
438 {
439     return SUCCEEDED(data->QueryGetData(texthtmlFormat())) || SUCCEEDED(data->QueryGetData(htmlFormat()));
440 }
441
442 } // namespace WebCore