1 /* 2 * Copyright (C) 2006, 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 "core/dom/Clipboard.h" 28 29 #include "HTMLNames.h" 30 #include "core/dom/DataTransferItem.h" 31 #include "core/dom/DataTransferItemList.h" 32 #include "core/editing/markup.h" 33 #include "core/fetch/ImageResource.h" 34 #include "core/fileapi/FileList.h" 35 #include "core/html/HTMLImageElement.h" 36 #include "core/frame/Frame.h" 37 #include "core/platform/DragImage.h" 38 #include "core/platform/chromium/ChromiumDataObject.h" 39 #include "core/rendering/RenderImage.h" 40 #include "core/rendering/RenderObject.h" 41 #include "platform/MIMETypeRegistry.h" 42 #include "platform/clipboard/ClipboardMimeTypes.h" 43 #include "platform/clipboard/ClipboardUtilities.h" 44 45 namespace WebCore { 46 47 // These "conversion" methods are called by both WebCore and WebKit, and never make sense to JS, so we don't 48 // worry about security for these. They don't allow access to the pasteboard anyway. 49 static DragOperation dragOpFromIEOp(const String& op) 50 { 51 // yep, it's really just this fixed set 52 if (op == "uninitialized") 53 return DragOperationEvery; 54 if (op == "none") 55 return DragOperationNone; 56 if (op == "copy") 57 return DragOperationCopy; 58 if (op == "link") 59 return DragOperationLink; 60 if (op == "move") 61 return (DragOperation)(DragOperationGeneric | DragOperationMove); 62 if (op == "copyLink") 63 return (DragOperation)(DragOperationCopy | DragOperationLink); 64 if (op == "copyMove") 65 return (DragOperation)(DragOperationCopy | DragOperationGeneric | DragOperationMove); 66 if (op == "linkMove") 67 return (DragOperation)(DragOperationLink | DragOperationGeneric | DragOperationMove); 68 if (op == "all") 69 return DragOperationEvery; 70 return DragOperationPrivate; // really a marker for "no conversion" 71 } 72 73 static String IEOpFromDragOp(DragOperation op) 74 { 75 bool moveSet = !!((DragOperationGeneric | DragOperationMove) & op); 76 77 if ((moveSet && (op & DragOperationCopy) && (op & DragOperationLink)) 78 || (op == DragOperationEvery)) 79 return "all"; 80 if (moveSet && (op & DragOperationCopy)) 81 return "copyMove"; 82 if (moveSet && (op & DragOperationLink)) 83 return "linkMove"; 84 if ((op & DragOperationCopy) && (op & DragOperationLink)) 85 return "copyLink"; 86 if (moveSet) 87 return "move"; 88 if (op & DragOperationCopy) 89 return "copy"; 90 if (op & DragOperationLink) 91 return "link"; 92 return "none"; 93 } 94 95 // We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft 96 // see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3 97 static String normalizeType(const String& type, bool* convertToURL = 0) 98 { 99 String cleanType = type.stripWhiteSpace().lower(); 100 if (cleanType == mimeTypeText || cleanType.startsWith(mimeTypeTextPlainEtc)) 101 return mimeTypeTextPlain; 102 if (cleanType == mimeTypeURL) { 103 if (convertToURL) 104 *convertToURL = true; 105 return mimeTypeTextURIList; 106 } 107 return cleanType; 108 } 109 110 PassRefPtr<Clipboard> Clipboard::create(ClipboardType type, ClipboardAccessPolicy policy, PassRefPtr<ChromiumDataObject> dataObject) 111 { 112 return adoptRef(new Clipboard(type, policy , dataObject)); 113 } 114 115 Clipboard::~Clipboard() 116 { 117 } 118 119 void Clipboard::setDropEffect(const String &effect) 120 { 121 if (!isForDragAndDrop()) 122 return; 123 124 // The attribute must ignore any attempts to set it to a value other than none, copy, link, and move. 125 if (effect != "none" && effect != "copy" && effect != "link" && effect != "move") 126 return; 127 128 // FIXME: The spec actually allows this in all circumstances, even though there's no point in 129 // setting the drop effect when this condition is not true. 130 if (canReadTypes()) 131 m_dropEffect = effect; 132 } 133 134 void Clipboard::setEffectAllowed(const String &effect) 135 { 136 if (!isForDragAndDrop()) 137 return; 138 139 if (dragOpFromIEOp(effect) == DragOperationPrivate) { 140 // This means that there was no conversion, and the effectAllowed that 141 // we are passed isn't a valid effectAllowed, so we should ignore it, 142 // and not set m_effectAllowed. 143 144 // The attribute must ignore any attempts to set it to a value other than 145 // none, copy, copyLink, copyMove, link, linkMove, move, all, and uninitialized. 146 return; 147 } 148 149 150 if (canWriteData()) 151 m_effectAllowed = effect; 152 } 153 154 void Clipboard::clearData(const String& type) 155 { 156 if (!canWriteData()) 157 return; 158 159 if (type.isNull()) 160 m_dataObject->clearAll(); 161 else 162 m_dataObject->clearData(normalizeType(type)); 163 } 164 165 String Clipboard::getData(const String& type) const 166 { 167 if (!canReadData()) 168 return String(); 169 170 bool convertToURL = false; 171 String data = m_dataObject->getData(normalizeType(type, &convertToURL)); 172 if (!convertToURL) 173 return data; 174 return convertURIListToURL(data); 175 } 176 177 bool Clipboard::setData(const String& type, const String& data) 178 { 179 if (!canWriteData()) 180 return false; 181 182 return m_dataObject->setData(normalizeType(type), data); 183 } 184 185 // extensions beyond IE's API 186 Vector<String> Clipboard::types() const 187 { 188 Vector<String> types; 189 if (!canReadTypes()) 190 return types; 191 192 ListHashSet<String> typesSet = m_dataObject->types(); 193 types.appendRange(typesSet.begin(), typesSet.end()); 194 return types; 195 } 196 197 PassRefPtr<FileList> Clipboard::files() const 198 { 199 RefPtr<FileList> files = FileList::create(); 200 if (!canReadData()) 201 return files.release(); 202 203 for (size_t i = 0; i < m_dataObject->length(); ++i) { 204 if (m_dataObject->item(i)->kind() == ChromiumDataObjectItem::FileKind) { 205 RefPtr<Blob> blob = m_dataObject->item(i)->getAsFile(); 206 if (blob && blob->isFile()) 207 files->append(toFile(blob.get())); 208 } 209 } 210 211 return files.release(); 212 } 213 214 void Clipboard::setDragImage(Element* image, int x, int y, ExceptionState& exceptionState) 215 { 216 if (!isForDragAndDrop()) 217 return; 218 219 if (!image) { 220 exceptionState.throwTypeError("setDragImage: Invalid first argument"); 221 return; 222 } 223 IntPoint location(x, y); 224 if (image->hasTagName(HTMLNames::imgTag) && !image->inDocument()) 225 setDragImageResource(toHTMLImageElement(image)->cachedImage(), location); 226 else 227 setDragImageElement(image, location); 228 } 229 230 void Clipboard::setDragImageResource(ImageResource* img, const IntPoint& loc) 231 { 232 setDragImage(img, 0, loc); 233 } 234 235 void Clipboard::setDragImageElement(Node* node, const IntPoint& loc) 236 { 237 setDragImage(0, node, loc); 238 } 239 240 PassOwnPtr<DragImage> Clipboard::createDragImage(IntPoint& loc, Frame* frame) const 241 { 242 if (m_dragImageElement) { 243 loc = m_dragLoc; 244 return frame->nodeImage(m_dragImageElement.get()); 245 } 246 if (m_dragImage) { 247 loc = m_dragLoc; 248 return DragImage::create(m_dragImage->image()); 249 } 250 return nullptr; 251 } 252 253 static ImageResource* getImageResource(Element* element) 254 { 255 // Attempt to pull ImageResource from element 256 ASSERT(element); 257 RenderObject* renderer = element->renderer(); 258 if (!renderer || !renderer->isImage()) 259 return 0; 260 261 RenderImage* image = toRenderImage(renderer); 262 if (image->cachedImage() && !image->cachedImage()->errorOccurred()) 263 return image->cachedImage(); 264 265 return 0; 266 } 267 268 static void writeImageToDataObject(ChromiumDataObject* dataObject, Element* element, const KURL& url) 269 { 270 // Shove image data into a DataObject for use as a file 271 ImageResource* cachedImage = getImageResource(element); 272 if (!cachedImage || !cachedImage->imageForRenderer(element->renderer()) || !cachedImage->isLoaded()) 273 return; 274 275 SharedBuffer* imageBuffer = cachedImage->imageForRenderer(element->renderer())->data(); 276 if (!imageBuffer || !imageBuffer->size()) 277 return; 278 279 String imageExtension = cachedImage->image()->filenameExtension(); 280 ASSERT(!imageExtension.isEmpty()); 281 282 // Determine the filename for the file contents of the image. 283 String filename = cachedImage->response().suggestedFilename(); 284 if (filename.isEmpty()) 285 filename = url.lastPathComponent(); 286 287 String fileExtension; 288 if (filename.isEmpty()) { 289 filename = element->getAttribute(HTMLNames::altAttr); 290 } else { 291 // Strip any existing extension. Assume that alt text is usually not a filename. 292 int extensionIndex = filename.reverseFind('.'); 293 if (extensionIndex != -1) { 294 fileExtension = filename.substring(extensionIndex + 1); 295 filename.truncate(extensionIndex); 296 } 297 } 298 299 if (!fileExtension.isEmpty() && fileExtension != imageExtension) { 300 String imageMimeType = MIMETypeRegistry::getMIMETypeForExtension(imageExtension); 301 ASSERT(imageMimeType.startsWith("image/")); 302 // Use the file extension only if it has imageMimeType: it's untrustworthy otherwise. 303 if (imageMimeType == MIMETypeRegistry::getMIMETypeForExtension(fileExtension)) 304 imageExtension = fileExtension; 305 } 306 307 imageExtension = "." + imageExtension; 308 validateFilename(filename, imageExtension); 309 310 dataObject->addSharedBuffer(filename + imageExtension, imageBuffer); 311 } 312 313 void Clipboard::declareAndWriteDragImage(Element* element, const KURL& url, const String& title) 314 { 315 if (!m_dataObject) 316 return; 317 318 m_dataObject->setURLAndTitle(url, title); 319 320 // Write the bytes in the image to the file format. 321 writeImageToDataObject(m_dataObject.get(), element, url); 322 323 // Put img tag on the clipboard referencing the image 324 m_dataObject->setData(mimeTypeTextHTML, createMarkup(element, IncludeNode, 0, ResolveAllURLs)); 325 } 326 327 void Clipboard::writeURL(const KURL& url, const String& title) 328 { 329 if (!m_dataObject) 330 return; 331 ASSERT(!url.isEmpty()); 332 333 m_dataObject->setURLAndTitle(url, title); 334 335 // The URL can also be used as plain text. 336 m_dataObject->setData(mimeTypeTextPlain, url.string()); 337 338 // The URL can also be used as an HTML fragment. 339 m_dataObject->setHTMLAndBaseURL(urlToMarkup(url, title), url); 340 } 341 342 void Clipboard::writeRange(Range* selectedRange, Frame* frame) 343 { 344 ASSERT(selectedRange); 345 if (!m_dataObject) 346 return; 347 348 m_dataObject->setHTMLAndBaseURL(createMarkup(selectedRange, 0, AnnotateForInterchange, false, ResolveNonLocalURLs), frame->document()->url()); 349 350 String str = frame->selectedTextForClipboard(); 351 #if OS(WIN) 352 replaceNewlinesWithWindowsStyleNewlines(str); 353 #endif 354 replaceNBSPWithSpace(str); 355 m_dataObject->setData(mimeTypeTextPlain, str); 356 } 357 358 void Clipboard::writePlainText(const String& text) 359 { 360 if (!m_dataObject) 361 return; 362 363 String str = text; 364 #if OS(WIN) 365 replaceNewlinesWithWindowsStyleNewlines(str); 366 #endif 367 replaceNBSPWithSpace(str); 368 369 m_dataObject->setData(mimeTypeTextPlain, str); 370 } 371 372 bool Clipboard::hasData() 373 { 374 ASSERT(isForDragAndDrop()); 375 376 return m_dataObject->length() > 0; 377 } 378 379 void Clipboard::setAccessPolicy(ClipboardAccessPolicy policy) 380 { 381 // once you go numb, can never go back 382 ASSERT(m_policy != ClipboardNumb || policy == ClipboardNumb); 383 m_policy = policy; 384 } 385 386 bool Clipboard::canReadTypes() const 387 { 388 return m_policy == ClipboardReadable || m_policy == ClipboardTypesReadable || m_policy == ClipboardWritable; 389 } 390 391 bool Clipboard::canReadData() const 392 { 393 return m_policy == ClipboardReadable || m_policy == ClipboardWritable; 394 } 395 396 bool Clipboard::canWriteData() const 397 { 398 return m_policy == ClipboardWritable; 399 } 400 401 bool Clipboard::canSetDragImage() const 402 { 403 return m_policy == ClipboardImageWritable || m_policy == ClipboardWritable; 404 } 405 406 DragOperation Clipboard::sourceOperation() const 407 { 408 DragOperation op = dragOpFromIEOp(m_effectAllowed); 409 ASSERT(op != DragOperationPrivate); 410 return op; 411 } 412 413 DragOperation Clipboard::destinationOperation() const 414 { 415 DragOperation op = dragOpFromIEOp(m_dropEffect); 416 ASSERT(op == DragOperationCopy || op == DragOperationNone || op == DragOperationLink || op == (DragOperation)(DragOperationGeneric | DragOperationMove) || op == DragOperationEvery); 417 return op; 418 } 419 420 void Clipboard::setSourceOperation(DragOperation op) 421 { 422 ASSERT_ARG(op, op != DragOperationPrivate); 423 m_effectAllowed = IEOpFromDragOp(op); 424 } 425 426 void Clipboard::setDestinationOperation(DragOperation op) 427 { 428 ASSERT_ARG(op, op == DragOperationCopy || op == DragOperationNone || op == DragOperationLink || op == DragOperationGeneric || op == DragOperationMove || op == (DragOperation)(DragOperationGeneric | DragOperationMove)); 429 m_dropEffect = IEOpFromDragOp(op); 430 } 431 432 bool Clipboard::hasDropZoneType(const String& keyword) 433 { 434 if (keyword.startsWith("file:")) 435 return hasFileOfType(keyword.substring(5)); 436 437 if (keyword.startsWith("string:")) 438 return hasStringOfType(keyword.substring(7)); 439 440 return false; 441 } 442 443 PassRefPtr<DataTransferItemList> Clipboard::items() 444 { 445 // FIXME: According to the spec, we are supposed to return the same collection of items each 446 // time. We now return a wrapper that always wraps the *same* set of items, so JS shouldn't be 447 // able to tell, but we probably still want to fix this. 448 return DataTransferItemList::create(this, m_dataObject); 449 } 450 451 PassRefPtr<ChromiumDataObject> Clipboard::dataObject() const 452 { 453 return m_dataObject; 454 } 455 456 Clipboard::Clipboard(ClipboardType type, ClipboardAccessPolicy policy, PassRefPtr<ChromiumDataObject> dataObject) 457 : m_policy(policy) 458 , m_dropEffect("uninitialized") 459 , m_effectAllowed("uninitialized") 460 , m_clipboardType(type) 461 , m_dataObject(dataObject) 462 { 463 ScriptWrappable::init(this); 464 } 465 466 void Clipboard::setDragImage(ImageResource* image, Node* node, const IntPoint& loc) 467 { 468 if (!canSetDragImage()) 469 return; 470 471 m_dragImage = image; 472 m_dragLoc = loc; 473 m_dragImageElement = node; 474 } 475 476 bool Clipboard::hasFileOfType(const String& type) const 477 { 478 if (!canReadTypes()) 479 return false; 480 481 RefPtr<FileList> fileList = files(); 482 if (fileList->isEmpty()) 483 return false; 484 485 for (unsigned f = 0; f < fileList->length(); f++) { 486 if (equalIgnoringCase(fileList->item(f)->type(), type)) 487 return true; 488 } 489 return false; 490 } 491 492 bool Clipboard::hasStringOfType(const String& type) const 493 { 494 if (!canReadTypes()) 495 return false; 496 497 return types().contains(type); 498 } 499 500 DragOperation convertDropZoneOperationToDragOperation(const String& dragOperation) 501 { 502 if (dragOperation == "copy") 503 return DragOperationCopy; 504 if (dragOperation == "move") 505 return DragOperationMove; 506 if (dragOperation == "link") 507 return DragOperationLink; 508 return DragOperationNone; 509 } 510 511 String convertDragOperationToDropZoneOperation(DragOperation operation) 512 { 513 switch (operation) { 514 case DragOperationCopy: 515 return String("copy"); 516 case DragOperationMove: 517 return String("move"); 518 case DragOperationLink: 519 return String("link"); 520 default: 521 return String("copy"); 522 } 523 } 524 525 } // namespace WebCore 526