fd4180e6e0a4e202890114b02848801550564f74
[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 static bool urlFromPath(CFStringRef path, String& url)
269 {
270     if (!path)
271         return false;
272
273     RetainPtr<CFURLRef> cfURL(AdoptCF, CFURLCreateWithFileSystemPath(0, path, kCFURLWindowsPathStyle, false));
274     if (!cfURL)
275         return false;
276
277     url = String(CFURLGetString(cfURL.get()));
278     return true;
279 }
280
281 String getURL(IDataObject* dataObject, bool& success, String* title)
282 {
283     STGMEDIUM store;
284     String url;
285     success = false;
286     if (getWebLocData(dataObject, url, title)) {
287         success = true;
288         return url;
289     } else if (SUCCEEDED(dataObject->GetData(urlWFormat(), &store))) {
290         //URL using unicode
291         UChar* data = (UChar*)GlobalLock(store.hGlobal);
292         url = extractURL(String(data), title);
293         GlobalUnlock(store.hGlobal);      
294         ReleaseStgMedium(&store);
295         success = true;
296     } else if (SUCCEEDED(dataObject->GetData(urlFormat(), &store))) {
297         //URL using ascii
298         char* data = (char*)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(filenameWFormat(), &store))) {
304         //file using unicode
305         wchar_t* data = (wchar_t*)GlobalLock(store.hGlobal);
306         if (data && data[0] && (PathFileExists(data) || PathIsUNC(data))) {
307             RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar*)data, wcslen(data)));
308             if (urlFromPath(pathAsCFString.get(), url)) {
309                 if (title)
310                     *title = url;
311                 success = true;
312             }
313         }
314         GlobalUnlock(store.hGlobal);      
315         ReleaseStgMedium(&store);
316     } else if (SUCCEEDED(dataObject->GetData(filenameFormat(), &store))) {
317         //filename using ascii
318         char* data = (char*)GlobalLock(store.hGlobal);       
319         if (data && data[0] && (PathFileExistsA(data) || PathIsUNCA(data))) {
320             RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCString(kCFAllocatorDefault, data, kCFStringEncodingASCII));
321             if (urlFromPath(pathAsCFString.get(), url)) {
322                 if (title)
323                     *title = url;
324                 success = true;
325             }
326         }
327         GlobalUnlock(store.hGlobal);      
328         ReleaseStgMedium(&store);
329     }
330     return url;
331 }
332
333 String getPlainText(IDataObject* dataObject, bool& success)
334 {
335     STGMEDIUM store;
336     String text;
337     success = false;
338     if (SUCCEEDED(dataObject->GetData(plainTextWFormat(), &store))) {
339         //unicode text
340         UChar* data = (UChar*)GlobalLock(store.hGlobal);
341         text = String(data);
342         GlobalUnlock(store.hGlobal);      
343         ReleaseStgMedium(&store);
344         success = true;
345     } else if (SUCCEEDED(dataObject->GetData(plainTextFormat(), &store))) {
346         //ascii text
347         char* data = (char*)GlobalLock(store.hGlobal);
348         text = String(data);
349         GlobalUnlock(store.hGlobal);      
350         ReleaseStgMedium(&store);
351         success = true;
352     } else {
353         //If a file is dropped on the window, it does not provide either of the 
354         //plain text formats, so here we try to forcibly get a url.
355         text = getURL(dataObject, success);
356         success = true;
357     }
358     return text;
359 }
360
361 PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const IDataObject*)
362 {
363     //FIXME: We should be able to create fragments from files
364     return 0;
365 }
366
367 bool containsFilenames(const IDataObject*)
368 {
369     //FIXME: We'll want to update this once we can produce fragments from files
370     return false;
371 }
372
373 //Convert a String containing CF_HTML formatted text to a DocumentFragment
374 PassRefPtr<DocumentFragment> fragmentFromCF_HTML(Document* doc, const String& cf_html)
375 {        
376     // obtain baseURL if present
377     String srcURLStr("sourceURL:");
378     String srcURL;
379     unsigned lineStart = cf_html.find(srcURLStr, 0, false);
380     if (lineStart != -1) {
381         unsigned srcEnd = cf_html.find("\n", lineStart, false);
382         unsigned srcStart = lineStart+srcURLStr.length();
383         String rawSrcURL = cf_html.substring(srcStart, srcEnd-srcStart);
384         replaceNBSPWithSpace(rawSrcURL);
385         srcURL = rawSrcURL.stripWhiteSpace();
386     }
387
388     // find the markup between "<!--StartFragment -->" and "<!--EndFragment -->", accounting for browser quirks
389     unsigned markupStart = cf_html.find("<html", 0, false);
390     unsigned tagStart = cf_html.find("startfragment", markupStart, false);
391     unsigned fragmentStart = cf_html.find('>', tagStart) + 1;
392     unsigned tagEnd = cf_html.find("endfragment", fragmentStart, false);
393     unsigned fragmentEnd = cf_html.reverseFind('<', tagEnd);
394     String markup = cf_html.substring(fragmentStart, fragmentEnd - fragmentStart).stripWhiteSpace();
395
396     return createFragmentFromMarkup(doc, markup, srcURL).releaseRef();
397 }
398
399
400 PassRefPtr<DocumentFragment> fragmentFromHTML(Document* doc, IDataObject* data) 
401 {
402     if (!doc || !data)
403         return 0;
404
405     STGMEDIUM store;
406     String html;
407     String srcURL;
408     if (SUCCEEDED(data->GetData(htmlFormat(), &store))) {
409         //MS HTML Format parsing
410         char* data = (char*)GlobalLock(store.hGlobal);
411         SIZE_T dataSize = ::GlobalSize(store.hGlobal);
412         String cf_html(UTF8Encoding().decode(data, dataSize));         
413         GlobalUnlock(store.hGlobal);
414         ReleaseStgMedium(&store); 
415         if (PassRefPtr<DocumentFragment> fragment = fragmentFromCF_HTML(doc, cf_html))
416             return fragment;
417     } 
418     if (SUCCEEDED(data->GetData(texthtmlFormat(), &store))) {
419         //raw html
420         UChar* data = (UChar*)GlobalLock(store.hGlobal);
421         html = String(data);
422         GlobalUnlock(store.hGlobal);      
423         ReleaseStgMedium(&store);
424         return createFragmentFromMarkup(doc, html, srcURL);
425     } 
426
427     return 0;
428 }
429
430 bool containsHTML(IDataObject* data)
431 {
432     return SUCCEEDED(data->QueryGetData(texthtmlFormat())) || SUCCEEDED(data->QueryGetData(htmlFormat()));
433 }
434
435 } // namespace WebCore