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