1/*
2 * Copyright (C) 2007, 2008, 2012 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 INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#import "config.h"
26#import "Pasteboard.h"
27
28#import "CachedImage.h"
29#import "DOMRangeInternal.h"
30#import "Document.h"
31#import "DocumentFragment.h"
32#import "DocumentLoader.h"
33#import "Editor.h"
34#import "EditorClient.h"
35#import "Frame.h"
36#import "FrameLoader.h"
37#import "FrameLoaderClient.h"
38#import "HTMLElement.h"
39#import "HTMLNames.h"
40#import "HTMLParserIdioms.h"
41#import "KURL.h"
42#import "LegacyWebArchive.h"
43#import "Page.h"
44#import "RenderImage.h"
45#import "SoftLinking.h"
46#import "Text.h"
47#import "htmlediting.h"
48#import "markup.h"
49#import "WebNSAttributedStringExtras.h"
50#import <MobileCoreServices/MobileCoreServices.h>
51
52@interface NSHTMLReader
53- (id)initWithDOMRange:(DOMRange *)domRange;
54- (NSAttributedString *)attributedString;
55@end
56
57@interface NSAttributedString (NSAttributedStringKitAdditions)
58- (id)initWithRTF:(NSData *)data documentAttributes:(NSDictionary **)dict;
59- (id)initWithRTFD:(NSData *)data documentAttributes:(NSDictionary **)dict;
60- (NSData *)RTFFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
61- (NSData *)RTFDFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
62- (BOOL)containsAttachments;
63@end
64
65SOFT_LINK_PRIVATE_FRAMEWORK(UIFoundation)
66SOFT_LINK_CLASS(UIFoundation, NSHTMLReader)
67
68SOFT_LINK_FRAMEWORK(MobileCoreServices)
69
70SOFT_LINK(MobileCoreServices, UTTypeConformsTo, Boolean, (CFStringRef inUTI, CFStringRef inConformsToUTI), (inUTI, inConformsToUTI))
71SOFT_LINK(MobileCoreServices, UTTypeCreatePreferredIdentifierForTag, CFStringRef, (CFStringRef inTagClass, CFStringRef inTag, CFStringRef inConformingToUTI), (inTagClass, inTag, inConformingToUTI))
72SOFT_LINK(MobileCoreServices, UTTypeCopyPreferredTagWithClass, CFStringRef, (CFStringRef inUTI, CFStringRef inTagClass), (inUTI, inTagClass))
73
74SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeText, CFStringRef)
75SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypePNG, CFStringRef)
76SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeJPEG, CFStringRef)
77SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeURL, CFStringRef)
78SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeTIFF, CFStringRef)
79SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeGIF, CFStringRef)
80SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassMIMEType, CFStringRef)
81SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassFilenameExtension, CFStringRef)
82SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeRTFD, CFStringRef)
83SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeRTF, CFStringRef)
84
85#define kUTTypeText getkUTTypeText()
86#define kUTTypePNG  getkUTTypePNG()
87#define kUTTypeJPEG getkUTTypeJPEG()
88#define kUTTypeURL  getkUTTypeURL()
89#define kUTTypeTIFF getkUTTypeTIFF()
90#define kUTTypeGIF  getkUTTypeGIF()
91#define kUTTagClassMIMEType getkUTTagClassMIMEType()
92#define kUTTagClassFilenameExtension getkUTTagClassFilenameExtension()
93#define kUTTypeRTFD getkUTTypeRTFD()
94#define kUTTypeRTF getkUTTypeRTF()
95
96SOFT_LINK_FRAMEWORK(AppSupport)
97SOFT_LINK(AppSupport, CPSharedResourcesDirectory, CFStringRef, (void), ())
98
99namespace WebCore {
100
101NSString *WebArchivePboardType = @"Apple Web Archive pasteboard type";
102
103Pasteboard* Pasteboard::generalPasteboard()
104{
105    static Pasteboard* pasteboard = new Pasteboard();
106    return pasteboard;
107}
108
109Pasteboard::Pasteboard()
110{
111}
112
113void Pasteboard::clear()
114{
115}
116
117void Pasteboard::writeSelection(Range* selectedRange, bool /*canSmartCopyOrDelete*/, Frame *frame, ShouldSerializeSelectedTextForClipboard shouldSerializeSelectedTextForClipboard)
118{
119    ASSERT(selectedRange);
120    ASSERT(frame);
121
122    // If the selection is at the beginning of content inside an anchor tag
123    // we move the selection start to include the anchor.
124    ExceptionCode ec;
125    Node* commonAncestor = selectedRange->commonAncestorContainer(ec);
126    ASSERT(commonAncestor);
127    Node* enclosingAnchor = enclosingNodeWithTag(firstPositionInNode(commonAncestor), HTMLNames::aTag);
128    if (enclosingAnchor && comparePositions(firstPositionInOrBeforeNode(selectedRange->startPosition().anchorNode()), selectedRange->startPosition()) >= 0)
129        selectedRange->setStart(enclosingAnchor, 0, ec);
130
131    frame->editor().client()->didSetSelectionTypesForPasteboard();
132
133    RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]);
134
135    // Put WebArchive on the pasteboard.
136    RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(frame);
137    RetainPtr<CFDataRef> data = archive ? archive->rawDataRepresentation() : 0;
138    if (data)
139        [representations.get() setValue:(NSData *)data.get() forKey:WebArchivePboardType];
140
141    RetainPtr<NSHTMLReader> converter = adoptNS([[getNSHTMLReaderClass() alloc] initWithDOMRange:kit(selectedRange)]);
142    if (converter) {
143        NSAttributedString *attributedString = [converter.get() attributedString];
144        NSData* RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
145        if (RTFDData)
146            [representations.get() setValue:RTFDData forKey:(NSString *)kUTTypeRTFD];
147        if ([attributedString containsAttachments])
148            attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
149        NSData* RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil];
150        if (RTFData)
151            [representations.get() setValue:RTFData forKey:(NSString *)kUTTypeRTF];
152    }
153
154    // Put plain string on the pasteboard.
155    String text = shouldSerializeSelectedTextForClipboard == IncludeImageAltTextForClipboard
156        ? frame->editor().selectedTextForClipboard() : frame->editor().selectedText();
157    text.replace(noBreakSpace, ' ');
158    [representations.get() setValue:text forKey:(NSString *)kUTTypeText];
159
160    frame->editor().client()->writeDataToPasteboard(representations.get());
161}
162
163void Pasteboard::writePlainText(const String& text, Frame *frame)
164{
165    ASSERT(frame);
166
167    RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]);
168    [representations.get() setValue:text forKey:(NSString *)kUTTypeText];
169    frame->editor().client()->writeDataToPasteboard(representations.get());
170}
171
172void Pasteboard::writeImage(Node* node, Frame* frame)
173{
174    ASSERT(node);
175
176    if (!(node->renderer() && node->renderer()->isImage()))
177        return;
178
179    RenderImage* renderer = toRenderImage(node->renderer());
180    CachedImage* cachedImage = renderer->cachedImage();
181    if (!cachedImage || cachedImage->errorOccurred())
182        return;
183
184    Image* image = cachedImage->imageForRenderer(renderer);
185    ASSERT(image);
186
187    RetainPtr<NSData> imageData = image->data()->createNSData();
188
189    if (!imageData)
190        return;
191
192    RetainPtr<NSMutableDictionary> dictionary = adoptNS([[NSMutableDictionary alloc] init]);
193    NSString *mimeType = cachedImage->response().mimeType();
194    RetainPtr<CFStringRef> uti = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)mimeType, NULL));
195    if (uti) {
196        [dictionary.get() setObject:imageData.get() forKey:(NSString *)uti.get()];
197        [dictionary.get() setObject:(NSString *)node->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(static_cast<HTMLElement*>(node)->getAttribute("src"))) forKey:(NSString *)kUTTypeURL];
198    }
199    frame->editor().client()->writeDataToPasteboard(dictionary.get());
200}
201
202void Pasteboard::writePlainText(const String&, SmartReplaceOption)
203{
204}
205
206void Pasteboard::writeClipboard(Clipboard*)
207{
208}
209
210bool Pasteboard::canSmartReplace()
211{
212    return false;
213}
214
215String Pasteboard::plainText(Frame* frame)
216{
217    RetainPtr<NSArray> pasteboardItem = frame->editor().client()->readDataFromPasteboard((NSString *)kUTTypeText, 0);
218
219    if ([pasteboardItem.get() count] == 0)
220        return String();
221
222    id value = [pasteboardItem.get() objectAtIndex:0];
223    if ([value isKindOfClass:[NSString class]])
224        return String(value);
225
226    ASSERT([value isKindOfClass:[NSString class]]);
227    return String();
228}
229
230static NSArray* supportedImageTypes()
231{
232    return [NSArray arrayWithObjects:(id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, nil];
233}
234
235NSArray* Pasteboard::supportedPasteboardTypes()
236{
237    return [NSArray arrayWithObjects:(id)WebArchivePboardType, (id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, (id)kUTTypeURL, (id)kUTTypeText, (id)kUTTypeRTFD, (id)kUTTypeRTF, nil];
238}
239
240#define WebDataProtocolScheme @"webkit-fake-url"
241
242static NSURL* uniqueURLWithRelativePart(NSString *relativePart)
243{
244    CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault);
245    NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef);
246    CFRelease(UUIDRef);
247    NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", WebDataProtocolScheme, UUIDString, relativePart]];
248    CFRelease(UUIDString);
249
250    return URL;
251}
252
253static PassRefPtr<DocumentFragment> documentFragmentWithImageResource(Frame* frame, PassRefPtr<ArchiveResource> resource)
254{
255    RefPtr<Element> imageElement = frame->document()->createElement(HTMLNames::imgTag, false);
256
257    if (DocumentLoader* loader = frame->loader()->documentLoader())
258        loader->addArchiveResource(resource.get());
259
260    NSURL *URL = resource->url();
261    imageElement->setAttribute(HTMLNames::srcAttr, [URL isFileURL] ? [URL absoluteString] : resource->url());
262    RefPtr<DocumentFragment> fragment = frame->document()->createDocumentFragment();
263    fragment->appendChild(imageElement.release());
264    return fragment.release();
265}
266
267static PassRefPtr<DocumentFragment> documentFragmentWithLink(Document* document, const String& urlString)
268{
269    RefPtr<Element> anchorElement = document->createElement(HTMLNames::aTag, false);
270
271    anchorElement->setAttribute(HTMLNames::hrefAttr, urlString);
272    anchorElement->appendChild(document->createTextNode(urlString));
273
274    RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
275    fragment->appendChild(anchorElement.release());
276    return fragment.release();
277}
278
279static PassRefPtr<DocumentFragment> documentFragmentWithRTF(Frame* frame, NSString *pasteboardType, NSData* pasteboardData)
280{
281    if (!frame || !frame->document() || !frame->document()->isHTMLDocument())
282        return 0;
283
284    RetainPtr<NSAttributedString> string;
285    if ([pasteboardType isEqualToString:(NSString*)kUTTypeRTFD])
286        string = [[NSAttributedString alloc] initWithRTFD:pasteboardData documentAttributes:NULL];
287
288    if (!string)
289        string = [[NSAttributedString alloc] initWithRTF:pasteboardData documentAttributes:NULL];
290
291    if (!string)
292        return 0;
293
294    bool wasDeferringCallbacks = frame->page()->defersLoading();
295    if (!wasDeferringCallbacks)
296        frame->page()->setDefersLoading(true);
297
298    Vector<RefPtr<ArchiveResource> > resources;
299    RefPtr<DocumentFragment> fragment = frame->editor().client()->documentFragmentFromAttributedString(string.get(), resources);
300
301    size_t size = resources.size();
302    if (size) {
303        DocumentLoader* loader = frame->loader()->documentLoader();
304        for (size_t i = 0; i < size; ++i)
305            loader->addArchiveResource(resources[i]);
306    }
307
308    if (!wasDeferringCallbacks)
309        frame->page()->setDefersLoading(false);
310
311    return fragment.release();
312}
313
314PassRefPtr<DocumentFragment> Pasteboard::documentFragmentForPasteboardItemAtIndex(Frame* frame, int index, bool allowPlainText, bool& chosePlainText)
315{
316    RefPtr<DocumentFragment> fragment = frame->editor().client()->documentFragmentFromDelegate(index);
317    if (fragment)
318        return fragment.release();
319
320    // First try to ask the client about the supported types. It will return null if the client
321    // has no selection.
322    NSArray *supportedTypes = frame->editor().client()->supportedPasteboardTypesForCurrentSelection();
323    if (!supportedTypes)
324        supportedTypes = supportedPasteboardTypes();
325    int numberOfTypes = [supportedTypes count];
326
327    for (int i = 0; i < numberOfTypes; i++) {
328        NSString *type = [supportedTypes objectAtIndex:i];
329        RetainPtr<NSArray> pasteboardItem = frame->editor().client()->readDataFromPasteboard(type, index);
330
331        if ([pasteboardItem.get() count] == 0)
332            continue;
333
334        if ([type isEqualToString:WebArchivePboardType]) {
335            if (!frame->document())
336                return 0;
337
338            // We put [WebArchive data] on the pasteboard in -copy: instead of the archive itself until there is API to provide the WebArchive.
339            NSData *data = [pasteboardItem.get() objectAtIndex:0];
340            RefPtr<LegacyWebArchive> coreArchive = LegacyWebArchive::create(SharedBuffer::wrapNSData(data).get());
341            if (coreArchive) {
342                RefPtr<ArchiveResource> mainResource = coreArchive->mainResource();
343                if (mainResource) {
344                    NSString *MIMEType = mainResource->mimeType();
345                    if (frame->loader()->client()->canShowMIMETypeAsHTML(MIMEType)) {
346                        RetainPtr<NSString> markupString = adoptNS([[NSString alloc] initWithData:[mainResource->data()->createNSData() autorelease] encoding:NSUTF8StringEncoding]);
347                        if (DocumentLoader* loader = frame->loader()->documentLoader())
348                            loader->addAllArchiveResources(coreArchive.get());
349
350                        fragment = createFragmentFromMarkup(frame->document(), markupString.get(), mainResource->url(), DisallowScriptingContent);
351                    }
352                }
353                if (fragment)
354                    return fragment.release();
355            }
356        }
357
358        if ([type isEqualToString:(NSString *)kUTTypeRTFD])
359            return documentFragmentWithRTF(frame, (NSString *)kUTTypeRTFD, [pasteboardItem.get() objectAtIndex:0]);
360
361        if ([type isEqualToString:(NSString *)kUTTypeRTF])
362            return documentFragmentWithRTF(frame, (NSString *)kUTTypeRTF, [pasteboardItem.get() objectAtIndex:0]);
363
364        if ([supportedImageTypes() containsObject:type]) {
365            RetainPtr<NSString> filenameExtension = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)type, kUTTagClassFilenameExtension));
366            NSString *relativeURLPart = [@"image" stringByAppendingString:filenameExtension.get()];
367            RetainPtr<NSString> mimeType = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)type, kUTTagClassMIMEType));
368            NSData *data = [pasteboardItem.get() objectAtIndex:0];
369            return documentFragmentWithImageResource(frame, ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), uniqueURLWithRelativePart(relativeURLPart), mimeType.get(), "", ""));
370        }
371        if ([type isEqualToString:(NSString *)kUTTypeURL]) {
372            id value = [pasteboardItem.get() objectAtIndex:0];
373            if (![value isKindOfClass:[NSURL class]]) {
374                ASSERT([value isKindOfClass:[NSURL class]]);
375                return 0;
376            }
377            NSURL *url = (NSURL *)value;
378
379            if (!frame->editor().client()->hasRichlyEditableSelection()) {
380                fragment = createFragmentFromText(frame->selection()->toNormalizedRange().get(), [url absoluteString]);
381                if (fragment)
382                    return fragment.release();
383            }
384
385            if ([url isFileURL]) {
386                NSString *localPath = [url relativePath];
387                // Only allow url attachments from ~/Media for now.
388                if (![localPath hasPrefix:[(NSString *)CPSharedResourcesDirectory() stringByAppendingString:@"/Media/DCIM/"]])
389                    continue;
390
391                RetainPtr<NSString> fileType = adoptNS((NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[localPath pathExtension], NULL));
392                NSData *data = [NSData dataWithContentsOfFile:localPath];
393                if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypePNG))
394                    return documentFragmentWithImageResource(frame, ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), uniqueURLWithRelativePart(@"image.png"), @"image/png", "", ""));
395                else if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypeJPEG))
396                    return documentFragmentWithImageResource(frame, ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), uniqueURLWithRelativePart(@"image.jpg"), @"image/jpg", "", ""));
397            } else {
398                // Create a link with URL text.
399                return documentFragmentWithLink(frame->document(), [url absoluteString]);
400            }
401        }
402        if (allowPlainText && [type isEqualToString:(NSString *)kUTTypeText]) {
403            id value = [pasteboardItem.get() objectAtIndex:0];
404            if (![value isKindOfClass:[NSString class]]) {
405                ASSERT([value isKindOfClass:[NSString class]]);
406                return 0;
407            }
408
409            chosePlainText = true;
410            fragment = createFragmentFromText(frame->selection()->toNormalizedRange().get(), (NSString*)value);
411            if (fragment)
412                return fragment.release();
413        }
414    }
415
416    return 0;
417}
418
419PassRefPtr<DocumentFragment> Pasteboard::documentFragment(Frame* frame, PassRefPtr<Range> /*context*/, bool allowPlainText, bool& chosePlainText)
420{
421    chosePlainText = false;
422
423    if (!frame)
424        return 0;
425
426    int numberOfItems = frame->editor().client()->getPasteboardItemsCount();
427
428    if (!numberOfItems)
429        return 0;
430
431    // In the common case there is just one item on the pasteboard, avoid the expense of transferring the content of
432    // fragmentForCurrentItem to the main fragment.
433    RefPtr<DocumentFragment> fragment = documentFragmentForPasteboardItemAtIndex(frame, 0, allowPlainText, chosePlainText);
434
435    for (int i = 1; i < numberOfItems; i++) {
436        RefPtr<DocumentFragment> fragmentForCurrentItem = documentFragmentForPasteboardItemAtIndex(frame, i, allowPlainText, chosePlainText);
437        if (!fragment)
438            fragment = fragmentForCurrentItem;
439        else if (fragmentForCurrentItem && fragmentForCurrentItem->firstChild()) {
440            ExceptionCode ec;
441            fragment->appendChild(fragmentForCurrentItem->firstChild(), ec);
442        }
443    }
444
445    if (fragment)
446        return fragment.release();
447
448    return 0;
449}
450
451}
452