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 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(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 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 147 static void append(Vector<char>& vector, const char* string) 148 { 149 vector.append(string, strlen(string)); 150 } 151 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 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 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 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 223 void replaceNBSPWithSpace(String& str) 224 { 225 static const UChar NonBreakingSpaceCharacter = 0xA0; 226 static const UChar SpaceCharacter = ' '; 227 str.replace(NonBreakingSpaceCharacter, SpaceCharacter); 228 } 229 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 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 244 FORMATETC* plainTextFormat() 245 { 246 static FORMATETC textFormat = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; 247 return &textFormat; 248 } 249 250 FORMATETC* plainTextWFormat() 251 { 252 static FORMATETC textFormat = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; 253 return &textFormat; 254 } 255 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 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 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 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 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 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 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 383 PassRefPtr<DocumentFragment> fragmentFromFilenames(Document*, const IDataObject*) 384 { 385 //FIXME: We should be able to create fragments from files 386 return 0; 387 } 388 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 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 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 452 bool containsHTML(IDataObject* data) 453 { 454 return SUCCEEDED(data->QueryGetData(texthtmlFormat())) || SUCCEEDED(data->QueryGetData(htmlFormat())); 455 } 456 457 } // namespace WebCore 458