• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "CString.h"
30 #include "DocumentFragment.h"
31 #include "KURL.h"
32 #include "PlatformString.h"
33 #include "TextEncoding.h"
34 #include "markup.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 
cfHDropFormat()42 FORMATETC* cfHDropFormat()
43 {
44     static FORMATETC urlFormat = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
45     return &urlFormat;
46 }
47 
getWebLocData(IDataObject * dataObject,String & url,String * title)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 
extractURL(const String & inURL,String * title)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
texthtmlFormat()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 
createGlobalData(const KURL & url,const String & title)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 
createGlobalData(const String & str)123 HGLOBAL createGlobalData(const String& str)
124 {
125     HGLOBAL globalData = ::GlobalAlloc(GPTR, (str.length() + 1) * sizeof(UChar));
126     if (!globalData)
127         return 0;
128     UChar* buffer = static_cast<UChar*>(::GlobalLock(globalData));
129     memcpy(buffer, str.characters(), str.length() * sizeof(UChar));
130     buffer[str.length()] = 0;
131     ::GlobalUnlock(globalData);
132     return globalData;
133 }
134 
createGlobalData(const Vector<char> & vector)135 HGLOBAL createGlobalData(const Vector<char>& vector)
136 {
137     HGLOBAL globalData = ::GlobalAlloc(GPTR, vector.size() + 1);
138     if (!globalData)
139         return 0;
140     char* buffer = static_cast<char*>(::GlobalLock(globalData));
141     memcpy(buffer, vector.data(), vector.size());
142     buffer[vector.size()] = 0;
143     ::GlobalUnlock(globalData);
144     return globalData;
145 }
146 
append(Vector<char> & vector,const char * string)147 static void append(Vector<char>& vector, const char* string)
148 {
149     vector.append(string, strlen(string));
150 }
151 
append(Vector<char> & vector,const CString & string)152 static void append(Vector<char>& vector, const CString& string)
153 {
154     vector.append(string.data(), string.length());
155 }
156 
157 // Documentation for the CF_HTML format is available at http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp
markupToCF_HTML(const String & markup,const String & srcURL,Vector<char> & result)158 void markupToCF_HTML(const String& markup, const String& srcURL, Vector<char>& result)
159 {
160     if (markup.isEmpty())
161         return;
162 
163     #define MAX_DIGITS 10
164     #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
165     #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
166     #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
167 
168     const char* header = "Version:0.9\n"
169         "StartHTML:" NUMBER_FORMAT "\n"
170         "EndHTML:" NUMBER_FORMAT "\n"
171         "StartFragment:" NUMBER_FORMAT "\n"
172         "EndFragment:" NUMBER_FORMAT "\n";
173     const char* sourceURLPrefix = "SourceURL:";
174 
175     const char* startMarkup = "<HTML>\n<BODY>\n<!--StartFragment-->\n";
176     const char* endMarkup = "\n<!--EndFragment-->\n</BODY>\n</HTML>";
177 
178     CString sourceURLUTF8 = srcURL == blankURL() ? "" : srcURL.utf8();
179     CString markupUTF8 = markup.utf8();
180 
181     // calculate offsets
182     unsigned startHTMLOffset = strlen(header) - strlen(NUMBER_FORMAT) * 4 + MAX_DIGITS * 4;
183     if (sourceURLUTF8.length())
184         startHTMLOffset += strlen(sourceURLPrefix) + sourceURLUTF8.length() + 1;
185     unsigned startFragmentOffset = startHTMLOffset + strlen(startMarkup);
186     unsigned endFragmentOffset = startFragmentOffset + markupUTF8.length();
187     unsigned endHTMLOffset = endFragmentOffset + strlen(endMarkup);
188 
189     append(result, String::format(header, startHTMLOffset, endHTMLOffset, startFragmentOffset, endFragmentOffset).utf8());
190     if (sourceURLUTF8.length()) {
191         append(result, sourceURLPrefix);
192         append(result, sourceURLUTF8);
193         result.append('\n');
194     }
195     append(result, startMarkup);
196     append(result, markupUTF8);
197     append(result, endMarkup);
198 
199     #undef MAX_DIGITS
200     #undef MAKE_NUMBER_FORMAT_1
201     #undef MAKE_NUMBER_FORMAT_2
202     #undef NUMBER_FORMAT
203 }
204 
urlToMarkup(const KURL & url,const String & title)205 String urlToMarkup(const KURL& url, const String& title)
206 {
207     Vector<UChar> markup;
208     append(markup, "<a href=\"");
209     append(markup, url.string());
210     append(markup, "\">");
211     append(markup, title);
212     append(markup, "</a>");
213     return String::adopt(markup);
214 }
215 
replaceNewlinesWithWindowsStyleNewlines(String & str)216 void replaceNewlinesWithWindowsStyleNewlines(String& str)
217 {
218     static const UChar Newline = '\n';
219     static const char* const WindowsNewline("\r\n");
220     str.replace(Newline, WindowsNewline);
221 }
222 
replaceNBSPWithSpace(String & str)223 void replaceNBSPWithSpace(String& str)
224 {
225     static const UChar NonBreakingSpaceCharacter = 0xA0;
226     static const UChar SpaceCharacter = ' ';
227     str.replace(NonBreakingSpaceCharacter, SpaceCharacter);
228 }
229 
urlWFormat()230 FORMATETC* urlWFormat()
231 {
232     static UINT cf = RegisterClipboardFormat(L"UniformResourceLocatorW");
233     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
234     return &urlFormat;
235 }
236 
urlFormat()237 FORMATETC* urlFormat()
238 {
239     static UINT cf = RegisterClipboardFormat(L"UniformResourceLocator");
240     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
241     return &urlFormat;
242 }
243 
plainTextFormat()244 FORMATETC* plainTextFormat()
245 {
246     static FORMATETC textFormat = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
247     return &textFormat;
248 }
249 
plainTextWFormat()250 FORMATETC* plainTextWFormat()
251 {
252     static FORMATETC textFormat = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
253     return &textFormat;
254 }
255 
filenameWFormat()256 FORMATETC* filenameWFormat()
257 {
258     static UINT cf = RegisterClipboardFormat(L"FileNameW");
259     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
260     return &urlFormat;
261 }
262 
filenameFormat()263 FORMATETC* filenameFormat()
264 {
265     static UINT cf = RegisterClipboardFormat(L"FileName");
266     static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
267     return &urlFormat;
268 }
269 
270 //MSIE HTML Format
htmlFormat()271 FORMATETC* htmlFormat()
272 {
273     static UINT cf = RegisterClipboardFormat(L"HTML Format");
274     static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
275     return &htmlFormat;
276 }
277 
smartPasteFormat()278 FORMATETC* smartPasteFormat()
279 {
280     static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format");
281     static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
282     return &htmlFormat;
283 }
284 
urlFromPath(CFStringRef path,String & url)285 static bool urlFromPath(CFStringRef path, String& url)
286 {
287     if (!path)
288         return false;
289 
290     RetainPtr<CFURLRef> cfURL(AdoptCF, CFURLCreateWithFileSystemPath(0, path, kCFURLWindowsPathStyle, false));
291     if (!cfURL)
292         return false;
293 
294     url = CFURLGetString(cfURL.get());
295 
296     // Work around <rdar://problem/6708300>, where CFURLCreateWithFileSystemPath makes URLs with "localhost".
297     if (url.startsWith("file://localhost/"))
298         url.remove(7, 9);
299 
300     return true;
301 }
302 
getURL(IDataObject * dataObject,bool & success,String * title)303 String getURL(IDataObject* dataObject, bool& success, String* title)
304 {
305     STGMEDIUM store;
306     String url;
307     success = false;
308     if (getWebLocData(dataObject, url, title)) {
309         success = true;
310         return url;
311     } else if (SUCCEEDED(dataObject->GetData(urlWFormat(), &store))) {
312         //URL using unicode
313         UChar* data = (UChar*)GlobalLock(store.hGlobal);
314         url = extractURL(String(data), title);
315         GlobalUnlock(store.hGlobal);
316         ReleaseStgMedium(&store);
317         success = true;
318     } else if (SUCCEEDED(dataObject->GetData(urlFormat(), &store))) {
319         //URL using ascii
320         char* data = (char*)GlobalLock(store.hGlobal);
321         url = extractURL(String(data), title);
322         GlobalUnlock(store.hGlobal);
323         ReleaseStgMedium(&store);
324         success = true;
325     } else if (SUCCEEDED(dataObject->GetData(filenameWFormat(), &store))) {
326         //file using unicode
327         wchar_t* data = (wchar_t*)GlobalLock(store.hGlobal);
328         if (data && data[0] && (PathFileExists(data) || PathIsUNC(data))) {
329             RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar*)data, wcslen(data)));
330             if (urlFromPath(pathAsCFString.get(), url)) {
331                 if (title)
332                     *title = url;
333                 success = true;
334             }
335         }
336         GlobalUnlock(store.hGlobal);
337         ReleaseStgMedium(&store);
338     } else if (SUCCEEDED(dataObject->GetData(filenameFormat(), &store))) {
339         //filename using ascii
340         char* data = (char*)GlobalLock(store.hGlobal);
341         if (data && data[0] && (PathFileExistsA(data) || PathIsUNCA(data))) {
342             RetainPtr<CFStringRef> pathAsCFString(AdoptCF, CFStringCreateWithCString(kCFAllocatorDefault, data, kCFStringEncodingASCII));
343             if (urlFromPath(pathAsCFString.get(), url)) {
344                 if (title)
345                     *title = url;
346                 success = true;
347             }
348         }
349         GlobalUnlock(store.hGlobal);
350         ReleaseStgMedium(&store);
351     }
352     return url;
353 }
354 
getPlainText(IDataObject * dataObject,bool & success)355 String getPlainText(IDataObject* dataObject, bool& success)
356 {
357     STGMEDIUM store;
358     String text;
359     success = false;
360     if (SUCCEEDED(dataObject->GetData(plainTextWFormat(), &store))) {
361         //unicode text
362         UChar* data = (UChar*)GlobalLock(store.hGlobal);
363         text = String(data);
364         GlobalUnlock(store.hGlobal);
365         ReleaseStgMedium(&store);
366         success = true;
367     } else if (SUCCEEDED(dataObject->GetData(plainTextFormat(), &store))) {
368         //ascii text
369         char* data = (char*)GlobalLock(store.hGlobal);
370         text = String(data);
371         GlobalUnlock(store.hGlobal);
372         ReleaseStgMedium(&store);
373         success = true;
374     } else {
375         //If a file is dropped on the window, it does not provide either of the
376         //plain text formats, so here we try to forcibly get a url.
377         text = getURL(dataObject, success);
378         success = true;
379     }
380     return text;
381 }
382 
fragmentFromFilenames(Document *,const IDataObject *)383 PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const IDataObject*)
384 {
385     //FIXME: We should be able to create fragments from files
386     return 0;
387 }
388 
containsFilenames(const IDataObject *)389 bool containsFilenames(const IDataObject*)
390 {
391     //FIXME: We'll want to update this once we can produce fragments from files
392     return false;
393 }
394 
395 //Convert a String containing CF_HTML formatted text to a DocumentFragment
fragmentFromCF_HTML(Document * doc,const String & cf_html)396 PassRefPtr<DocumentFragment> fragmentFromCF_HTML(Document* doc, const String& cf_html)
397 {
398     // obtain baseURL if present
399     String srcURLStr("sourceURL:");
400     String srcURL;
401     unsigned lineStart = cf_html.find(srcURLStr, 0, false);
402     if (lineStart != -1) {
403         unsigned srcEnd = cf_html.find("\n", lineStart, false);
404         unsigned srcStart = lineStart+srcURLStr.length();
405         String rawSrcURL = cf_html.substring(srcStart, srcEnd-srcStart);
406         replaceNBSPWithSpace(rawSrcURL);
407         srcURL = rawSrcURL.stripWhiteSpace();
408     }
409 
410     // find the markup between "<!--StartFragment -->" and "<!--EndFragment -->", accounting for browser quirks
411     unsigned markupStart = cf_html.find("<html", 0, false);
412     unsigned tagStart = cf_html.find("startfragment", markupStart, false);
413     unsigned fragmentStart = cf_html.find('>', tagStart) + 1;
414     unsigned tagEnd = cf_html.find("endfragment", fragmentStart, false);
415     unsigned fragmentEnd = cf_html.reverseFind('<', tagEnd);
416     String markup = cf_html.substring(fragmentStart, fragmentEnd - fragmentStart).stripWhiteSpace();
417 
418     return createFragmentFromMarkup(doc, markup, srcURL, FragmentScriptingNotAllowed);
419 }
420 
421 
fragmentFromHTML(Document * doc,IDataObject * data)422 PassRefPtr<DocumentFragment> fragmentFromHTML(Document* doc, IDataObject* data)
423 {
424     if (!doc || !data)
425         return 0;
426 
427     STGMEDIUM store;
428     String html;
429     String srcURL;
430     if (SUCCEEDED(data->GetData(htmlFormat(), &store))) {
431         //MS HTML Format parsing
432         char* data = (char*)GlobalLock(store.hGlobal);
433         SIZE_T dataSize = ::GlobalSize(store.hGlobal);
434         String cf_html(UTF8Encoding().decode(data, dataSize));
435         GlobalUnlock(store.hGlobal);
436         ReleaseStgMedium(&store);
437         if (PassRefPtr<DocumentFragment> fragment = fragmentFromCF_HTML(doc, cf_html))
438             return fragment;
439     }
440     if (SUCCEEDED(data->GetData(texthtmlFormat(), &store))) {
441         //raw html
442         UChar* data = (UChar*)GlobalLock(store.hGlobal);
443         html = String(data);
444         GlobalUnlock(store.hGlobal);
445         ReleaseStgMedium(&store);
446         return createFragmentFromMarkup(doc, html, srcURL, FragmentScriptingNotAllowed);
447     }
448 
449     return 0;
450 }
451 
containsHTML(IDataObject * data)452 bool containsHTML(IDataObject* data)
453 {
454     return SUCCEEDED(data->QueryGetData(texthtmlFormat())) || SUCCEEDED(data->QueryGetData(htmlFormat()));
455 }
456 
457 } // namespace WebCore
458