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