1/*
2 * Copyright (C) 2007, 2008, 2012, 2013 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 "Image.h"
42#import "LegacyWebArchive.h"
43#import "Page.h"
44#import "PasteboardStrategy.h"
45#import "PlatformStrategies.h"
46#import "RenderImage.h"
47#import "RuntimeApplicationChecksIOS.h"
48#import "SharedBuffer.h"
49#import "SoftLinking.h"
50#import "Text.h"
51#import "URL.h"
52#import "WebNSAttributedStringExtras.h"
53#import "htmlediting.h"
54#import "markup.h"
55#import <MobileCoreServices/MobileCoreServices.h>
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
65// FIXME: The following soft linking and #define needs to be shared with PlatformPasteboardIOS.mm and EditorIOS.mm
66
67SOFT_LINK_FRAMEWORK(MobileCoreServices)
68
69SOFT_LINK(MobileCoreServices, UTTypeCreatePreferredIdentifierForTag, CFStringRef, (CFStringRef inTagClass, CFStringRef inTag, CFStringRef inConformingToUTI), (inTagClass, inTag, inConformingToUTI))
70SOFT_LINK(MobileCoreServices, UTTypeCopyPreferredTagWithClass, CFStringRef, (CFStringRef inUTI, CFStringRef inTagClass), (inUTI, inTagClass))
71
72SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeText, CFStringRef)
73SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypePNG, CFStringRef)
74SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeJPEG, CFStringRef)
75SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeURL, CFStringRef)
76SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeTIFF, CFStringRef)
77SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeGIF, CFStringRef)
78SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassMIMEType, CFStringRef)
79SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassFilenameExtension, CFStringRef)
80SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeHTML, CFStringRef)
81SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeRTFD, CFStringRef)
82SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeRTF, CFStringRef)
83
84#define kUTTypeText getkUTTypeText()
85#define kUTTypePNG  getkUTTypePNG()
86#define kUTTypeJPEG getkUTTypeJPEG()
87#define kUTTypeURL  getkUTTypeURL()
88#define kUTTypeTIFF getkUTTypeTIFF()
89#define kUTTypeGIF  getkUTTypeGIF()
90#define kUTTagClassMIMEType getkUTTagClassMIMEType()
91#define kUTTagClassFilenameExtension getkUTTagClassFilenameExtension()
92#define kUTTypeHTML getkUTTypeHTML()
93#define kUTTypeRTFD getkUTTypeRTFD()
94#define kUTTypeRTF getkUTTypeRTF()
95
96namespace WebCore {
97
98// FIXME: Does this need to be declared in the header file?
99NSString *WebArchivePboardType = @"Apple Web Archive pasteboard type";
100
101// Making this non-inline so that WebKit 2's decoding doesn't have to include SharedBuffer.h.
102PasteboardWebContent::PasteboardWebContent()
103{
104}
105
106PasteboardWebContent::~PasteboardWebContent()
107{
108}
109
110// Making this non-inline so that WebKit 2's decoding doesn't have to include Image.h.
111PasteboardImage::PasteboardImage()
112{
113}
114
115PasteboardImage::~PasteboardImage()
116{
117}
118
119Pasteboard::Pasteboard()
120    : m_changeCount(platformStrategies()->pasteboardStrategy()->changeCount())
121{
122}
123
124PassOwnPtr<Pasteboard> Pasteboard::createForCopyAndPaste()
125{
126    return adoptPtr(new Pasteboard);
127}
128
129PassOwnPtr<Pasteboard> Pasteboard::createPrivate()
130{
131    return adoptPtr(new Pasteboard);
132}
133
134void Pasteboard::write(const PasteboardWebContent& content)
135{
136    platformStrategies()->pasteboardStrategy()->writeToPasteboard(content);
137}
138
139String Pasteboard::resourceMIMEType(const NSString *mimeType)
140{
141    return String(adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)mimeType, NULL)).get());
142}
143
144void Pasteboard::write(const PasteboardImage& pasteboardImage)
145{
146    platformStrategies()->pasteboardStrategy()->writeToPasteboard(pasteboardImage);
147}
148
149void Pasteboard::writePlainText(const String& text, SmartReplaceOption)
150{
151    platformStrategies()->pasteboardStrategy()->writeToPasteboard(kUTTypeText, text);
152}
153
154void Pasteboard::write(const PasteboardURL& pasteboardURL)
155{
156    platformStrategies()->pasteboardStrategy()->writeToPasteboard(kUTTypeURL, pasteboardURL.url.string());
157}
158
159void Pasteboard::writePasteboard(const Pasteboard&)
160{
161}
162
163bool Pasteboard::canSmartReplace()
164{
165    return false;
166}
167
168void Pasteboard::read(PasteboardPlainText& text)
169{
170    PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
171    text.text = strategy.readStringFromPasteboard(0, kUTTypeText);
172}
173
174static NSArray* supportedImageTypes()
175{
176    return @[(id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF];
177}
178
179void Pasteboard::read(PasteboardWebContentReader& reader)
180{
181    PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
182
183    int numberOfItems = strategy.getPasteboardItemsCount();
184
185    if (!numberOfItems)
186        return;
187
188    NSArray *types = supportedPasteboardTypes();
189    int numberOfTypes = [types count];
190
191    for (int i = 0; i < numberOfItems; i++) {
192        for (int typeIndex = 0; typeIndex < numberOfTypes; typeIndex++) {
193            NSString *type = [types objectAtIndex:typeIndex];
194
195            if ([type isEqualToString:WebArchivePboardType]) {
196                if (RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(i, WebArchivePboardType)) {
197                    if (reader.readWebArchive(buffer.release()))
198                        break;
199                }
200            }
201
202            if ([type isEqualToString:(NSString *)kUTTypeHTML]) {
203                String htmlString = strategy.readStringFromPasteboard(i, kUTTypeHTML);
204                if (!htmlString.isNull() && reader.readHTML(htmlString))
205                    break;
206            }
207
208             if ([type isEqualToString:(NSString *)kUTTypeRTFD]) {
209                if (RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(i, kUTTypeRTFD)) {
210                    if (reader.readRTFD(buffer.release()))
211                        break;
212                }
213            }
214
215            if ([type isEqualToString:(NSString *)kUTTypeRTF]) {
216                if (RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(i, kUTTypeRTF)) {
217                    if (reader.readRTF(buffer.release()))
218                        break;
219                }
220            }
221
222            if ([supportedImageTypes() containsObject:type]) {
223                if (RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(i, type)) {
224                    if (reader.readImage(buffer.release(), type))
225                        break;
226                }
227        }
228
229            if ([type isEqualToString:(NSString *)kUTTypeURL]) {
230                URL url = strategy.readURLFromPasteboard(i, kUTTypeURL);
231                if (!url.isNull() && reader.readURL(url, String()))
232                    break;
233            }
234
235            if ([type isEqualToString:(NSString *)kUTTypeText]) {
236                String string = strategy.readStringFromPasteboard(i, kUTTypeText);
237                if (!string.isNull() && reader.readPlainText(string))
238                    break;
239            }
240
241        }
242    }
243}
244
245NSArray* Pasteboard::supportedPasteboardTypes()
246{
247    return @[(id)WebArchivePboardType, (id)kUTTypeHTML, (id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, (id)kUTTypeURL, (id)kUTTypeText, (id)kUTTypeRTFD, (id)kUTTypeRTF];
248}
249
250bool Pasteboard::hasData()
251{
252    return platformStrategies()->pasteboardStrategy()->getPasteboardItemsCount() != 0;
253}
254
255static String utiTypeFromCocoaType(NSString *type)
256{
257    RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL));
258    if (!utiType)
259        return String();
260    return String(adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType)).get());
261}
262
263static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
264{
265    String strippedType = type.stripWhiteSpace();
266
267    if (strippedType == "Text")
268        return (NSString *)kUTTypeText;
269    if (strippedType == "URL")
270        return (NSString *)kUTTypeURL;
271
272    // Ignore any trailing charset - JS strings are Unicode, which encapsulates the charset issue.
273    if (strippedType.startsWith("text/plain"))
274        return (NSString *)kUTTypeText;
275
276    // Special case because UTI doesn't work with Cocoa's URL type.
277    if (strippedType == "text/uri-list")
278        return (NSString *)kUTTypeURL;
279
280    // Try UTI now.
281    if (NSString *utiType = utiTypeFromCocoaType(strippedType))
282        return utiType;
283
284    // No mapping, just pass the whole string though.
285    return (NSString *)strippedType;
286}
287
288void Pasteboard::clear(const String& type)
289{
290    // Since UIPasteboard enforces changeCount itself on writing, we don't check it here.
291
292    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
293    if (!cocoaType)
294        return;
295
296    platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), String());
297}
298
299void Pasteboard::clear()
300{
301    platformStrategies()->pasteboardStrategy()->writeToPasteboard(String(), String());
302}
303
304String Pasteboard::readString(const String& type)
305{
306    PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
307
308    int numberOfItems = strategy.getPasteboardItemsCount();
309
310    if (!numberOfItems)
311        return String();
312
313    // Grab the value off the pasteboard corresponding to the cocoaType.
314    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
315
316    NSString *cocoaValue = nil;
317
318    if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
319        URL url = strategy.readURLFromPasteboard(0, kUTTypeURL);
320        if (!url.isNull())
321            cocoaValue = [(NSURL *)url absoluteString];
322    } else if ([cocoaType isEqualToString:(NSString *)kUTTypeText]) {
323        String value = strategy.readStringFromPasteboard(0, kUTTypeText);
324        if (!value.isNull())
325            cocoaValue = [(NSString *)value precomposedStringWithCanonicalMapping];;
326    } else if (cocoaType) {
327        if (RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(0, cocoaType.get()))
328            cocoaValue = [[[NSString alloc] initWithData:buffer->createNSData().get() encoding:NSUTF8StringEncoding] autorelease];
329    }
330
331    // Enforce changeCount ourselves for security. We check after reading instead of before to be
332    // sure it doesn't change between our testing the change count and accessing the data.
333    if (cocoaValue && m_changeCount == platformStrategies()->pasteboardStrategy()->changeCount())
334        return cocoaValue;
335
336    return String();
337}
338
339static void addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, NSString *cocoaType)
340{
341    // UTI may not do these right, so make sure we get the right, predictable result.
342    if ([cocoaType isEqualToString:(NSString *)kUTTypeText]) {
343        resultTypes.add(ASCIILiteral("text/plain"));
344        return;
345    }
346    if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
347        resultTypes.add(ASCIILiteral("text/uri-list"));
348        return;
349    }
350    String utiType = utiTypeFromCocoaType(cocoaType);
351    if (!utiType.isEmpty()) {
352        resultTypes.add(utiType);
353        return;
354    }
355    // No mapping, just pass the whole string though.
356    resultTypes.add(cocoaType);
357}
358
359void Pasteboard::writeString(const String& type, const String& data)
360{
361    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
362    if (!cocoaType)
363        return;
364
365    platformStrategies()->pasteboardStrategy()->writeToPasteboard(type, data);
366}
367
368Vector<String> Pasteboard::types()
369{
370    NSArray* types = supportedPasteboardTypes();
371
372    // Enforce changeCount ourselves for security. We check after reading instead of before to be
373    // sure it doesn't change between our testing the change count and accessing the data.
374    if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount())
375        return Vector<String>();
376
377    ListHashSet<String> result;
378    NSUInteger count = [types count];
379    for (NSUInteger i = 0; i < count; i++) {
380        NSString *type = [types objectAtIndex:i];
381        addHTMLClipboardTypesForCocoaType(result, type);
382    }
383
384    Vector<String> vector;
385    copyToVector(result, vector);
386    return vector;
387}
388
389Vector<String> Pasteboard::readFilenames()
390{
391    return Vector<String>();
392}
393
394}
395