2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "ClipboardUtilitiesWin.h"
31 #include "DocumentFragment.h"
33 #include "PlatformString.h"
34 #include "TextEncoding.h"
35 #include <CoreFoundation/CoreFoundation.h>
36 #include <wtf/RetainPtr.h>
38 #include <wininet.h> // for INTERNET_MAX_URL_LENGTH
42 FORMATETC* cfHDropFormat()
44 static FORMATETC urlFormat = {CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
48 static bool getWebLocData(IDataObject* dataObject, String& url, String* title)
50 bool succeeded = false;
51 WCHAR filename[MAX_PATH];
52 WCHAR urlBuffer[INTERNET_MAX_URL_LENGTH];
55 if (FAILED(dataObject->GetData(cfHDropFormat(), &medium)))
58 HDROP hdrop = (HDROP)GlobalLock(medium.hGlobal);
63 if (!DragQueryFileW(hdrop, 0, filename, ARRAYSIZE(filename)))
66 if (_wcsicmp(PathFindExtensionW(filename), L".url"))
69 if (!GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, urlBuffer, ARRAYSIZE(urlBuffer), filename))
73 PathRemoveExtension(filename);
74 *title = String((UChar*)filename);
77 url = String((UChar*)urlBuffer);
83 GlobalUnlock(medium.hGlobal);
87 static String extractURL(const String &inURL, String* title)
90 int splitLoc = url.find('\n');
93 *title = url.substring(splitLoc+1);
94 url.truncate(splitLoc);
101 static FORMATETC* texthtmlFormat()
103 static UINT cf = RegisterClipboardFormat(L"text/html");
104 static FORMATETC texthtmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
105 return &texthtmlFormat;
108 HGLOBAL createGlobalData(const KURL& url, const String& title)
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));
116 PWSTR buffer = (PWSTR)::GlobalLock(cbData);
117 swprintf_s(buffer, size, L"%s\n%s", mutableURL.charactersWithNullTermination(), mutableTitle.charactersWithNullTermination());
118 ::GlobalUnlock(cbData);
123 HGLOBAL createGlobalData(String str)
125 SIZE_T size = (str.length() + 1) * sizeof(UChar);
126 HGLOBAL cbData = ::GlobalAlloc(GPTR, size);
128 void* buffer = ::GlobalLock(cbData);
129 memcpy(buffer, str.charactersWithNullTermination(), size);
130 ::GlobalUnlock(cbData);
135 HGLOBAL createGlobalData(CString str)
137 SIZE_T size = str.length() * sizeof(char);
138 HGLOBAL cbData = ::GlobalAlloc(GPTR, size + 1);
140 char* buffer = static_cast<char*>(::GlobalLock(cbData));
141 memcpy(buffer, str.data(), size);
143 ::GlobalUnlock(cbData);
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)
151 if (!markup.length())
152 return DeprecatedCString();
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:");
161 bool shouldFillSourceURL = !srcURL.isEmpty() && (srcURL != "about:blank");
162 if (shouldFillSourceURL)
163 sourceURL.append(srcURL.utf8().data());
165 DeprecatedCString startMarkup ("\n<HTML>\n<BODY>\n<!--StartFragment-->\n");
166 DeprecatedCString endMarkup ("\n<!--EndFragment-->\n</BODY>\n</HTML>");
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();
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());
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);
196 String urlToMarkup(const KURL& url, const String& title)
198 String markup("<a href=\"");
199 markup.append(url.string());
200 markup.append("\">");
201 markup.append(title);
202 markup.append("</a>");
206 void replaceNewlinesWithWindowsStyleNewlines(String& str)
208 static const UChar Newline = '\n';
209 static const String WindowsNewline("\r\n");
210 str.replace(Newline, WindowsNewline);
213 void replaceNBSPWithSpace(String& str)
215 static const UChar NonBreakingSpaceCharacter = 0xA0;
216 static const UChar SpaceCharacter = ' ';
217 str.replace(NonBreakingSpaceCharacter, SpaceCharacter);
220 FORMATETC* urlWFormat()
222 static UINT cf = RegisterClipboardFormat(L"UniformResourceLocatorW");
223 static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
227 FORMATETC* urlFormat()
229 static UINT cf = RegisterClipboardFormat(L"UniformResourceLocator");
230 static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
234 FORMATETC* plainTextFormat()
236 static FORMATETC textFormat = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
240 FORMATETC* plainTextWFormat()
242 static FORMATETC textFormat = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
246 FORMATETC* filenameWFormat()
248 static UINT cf = RegisterClipboardFormat(L"FileNameW");
249 static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
253 FORMATETC* filenameFormat()
255 static UINT cf = RegisterClipboardFormat(L"FileName");
256 static FORMATETC urlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
261 FORMATETC* htmlFormat()
263 static UINT cf = RegisterClipboardFormat(L"HTML Format");
264 static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
268 FORMATETC* smartPasteFormat()
270 static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format");
271 static FORMATETC htmlFormat = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
275 static bool urlFromPath(CFStringRef path, String& url)
280 RetainPtr<CFURLRef> cfURL(AdoptCF, CFURLCreateWithFileSystemPath(0, path, kCFURLWindowsPathStyle, false));
284 url = String(CFURLGetString(cfURL.get()));
288 String getURL(IDataObject* dataObject, bool& success, String* title)
293 if (getWebLocData(dataObject, url, title)) {
296 } else if (SUCCEEDED(dataObject->GetData(urlWFormat(), &store))) {
298 UChar* data = (UChar*)GlobalLock(store.hGlobal);
299 url = extractURL(String(data), title);
300 GlobalUnlock(store.hGlobal);
301 ReleaseStgMedium(&store);
303 } else if (SUCCEEDED(dataObject->GetData(urlFormat(), &store))) {
305 char* data = (char*)GlobalLock(store.hGlobal);
306 url = extractURL(String(data), title);
307 GlobalUnlock(store.hGlobal);
308 ReleaseStgMedium(&store);
310 } else if (SUCCEEDED(dataObject->GetData(filenameWFormat(), &store))) {
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)) {
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)) {
334 GlobalUnlock(store.hGlobal);
335 ReleaseStgMedium(&store);
340 String getPlainText(IDataObject* dataObject, bool& success)
345 if (SUCCEEDED(dataObject->GetData(plainTextWFormat(), &store))) {
347 UChar* data = (UChar*)GlobalLock(store.hGlobal);
349 GlobalUnlock(store.hGlobal);
350 ReleaseStgMedium(&store);
352 } else if (SUCCEEDED(dataObject->GetData(plainTextFormat(), &store))) {
354 char* data = (char*)GlobalLock(store.hGlobal);
356 GlobalUnlock(store.hGlobal);
357 ReleaseStgMedium(&store);
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);
368 PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const IDataObject*)
370 //FIXME: We should be able to create fragments from files
374 bool containsFilenames(const IDataObject*)
376 //FIXME: We'll want to update this once we can produce fragments from files
380 //Convert a String containing CF_HTML formatted text to a DocumentFragment
381 PassRefPtr<DocumentFragment> fragmentFromCF_HTML(Document* doc, const String& cf_html)
383 // obtain baseURL if present
384 String srcURLStr("sourceURL:");
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();
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();
403 return createFragmentFromMarkup(doc, markup, srcURL).releaseRef();
407 PassRefPtr<DocumentFragment> fragmentFromHTML(Document* doc, IDataObject* data)
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))
425 if (SUCCEEDED(data->GetData(texthtmlFormat(), &store))) {
427 UChar* data = (UChar*)GlobalLock(store.hGlobal);
429 GlobalUnlock(store.hGlobal);
430 ReleaseStgMedium(&store);
431 return createFragmentFromMarkup(doc, html, srcURL);
437 bool containsHTML(IDataObject* data)
439 return SUCCEEDED(data->QueryGetData(texthtmlFormat())) || SUCCEEDED(data->QueryGetData(htmlFormat()));
442 } // namespace WebCore