cd1b746e252c5709c26d3dfadd06dadc7c9fe81e
[WebKit-https.git] / Source / WebCore / platform / ios / PasteboardIOS.mm
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 "DragData.h"
29 #import "Image.h"
30 #import "NotImplemented.h"
31 #import "PasteboardStrategy.h"
32 #import "PlatformPasteboard.h"
33 #import "PlatformStrategies.h"
34 #import "SharedBuffer.h"
35 #import "URL.h"
36 #import "UTIUtilities.h"
37 #import "WebNSAttributedStringExtras.h"
38 #import <MobileCoreServices/MobileCoreServices.h>
39 #import <wtf/text/StringHash.h>
40
41 @interface NSAttributedString (NSAttributedStringKitAdditions)
42 - (id)initWithRTF:(NSData *)data documentAttributes:(NSDictionary **)dict;
43 - (id)initWithRTFD:(NSData *)data documentAttributes:(NSDictionary **)dict;
44 - (NSData *)RTFFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
45 - (NSData *)RTFDFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
46 - (BOOL)containsAttachments;
47 @end
48
49 namespace WebCore {
50
51 #if ENABLE(DRAG_SUPPORT)
52
53 Pasteboard::Pasteboard(const String& pasteboardName)
54     : m_pasteboardName(pasteboardName)
55     , m_changeCount(platformStrategies()->pasteboardStrategy()->changeCount(pasteboardName))
56 {
57 }
58
59 void Pasteboard::setDragImage(DragImage, const IntPoint&)
60 {
61     notImplemented();
62 }
63
64 std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop()
65 {
66     return std::make_unique<Pasteboard>("data interaction pasteboard");
67 }
68
69 std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData)
70 {
71     return std::make_unique<Pasteboard>(dragData.pasteboardName());
72 }
73
74 #endif
75
76 static long changeCountForPasteboard(const String& pasteboardName = { })
77 {
78     return platformStrategies()->pasteboardStrategy()->changeCount(pasteboardName);
79 }
80
81 // FIXME: Does this need to be declared in the header file?
82 WEBCORE_EXPORT NSString *WebArchivePboardType = @"Apple Web Archive pasteboard type";
83
84 Pasteboard::Pasteboard()
85     : m_changeCount(0)
86 {
87 }
88
89 Pasteboard::Pasteboard(long changeCount)
90     : m_changeCount(changeCount)
91 {
92 }
93
94 void Pasteboard::writeMarkup(const String&)
95 {
96 }
97
98 std::unique_ptr<Pasteboard> Pasteboard::createForCopyAndPaste()
99 {
100     return std::make_unique<Pasteboard>(changeCountForPasteboard());
101 }
102
103 void Pasteboard::write(const PasteboardWebContent& content)
104 {
105     platformStrategies()->pasteboardStrategy()->writeToPasteboard(content, m_pasteboardName);
106 }
107
108 String Pasteboard::resourceMIMEType(NSString *mimeType)
109 {
110     return UTIFromMIMEType(mimeType);
111 }
112
113 void Pasteboard::write(const PasteboardImage& pasteboardImage)
114 {
115     platformStrategies()->pasteboardStrategy()->writeToPasteboard(pasteboardImage, m_pasteboardName);
116 }
117
118 void Pasteboard::writePlainText(const String& text, SmartReplaceOption)
119 {
120     // FIXME: We vend "public.text" here for backwards compatibility with pre-iOS 11 apps. In the future, we should stop vending this UTI,
121     // and instead set data for concrete plain text types. See <https://bugs.webkit.org/show_bug.cgi?id=173317>.
122     platformStrategies()->pasteboardStrategy()->writeToPasteboard(kUTTypeText, text, m_pasteboardName);
123 }
124
125 void Pasteboard::write(const PasteboardURL& pasteboardURL)
126 {
127     platformStrategies()->pasteboardStrategy()->writeToPasteboard(pasteboardURL, m_pasteboardName);
128 }
129
130 void Pasteboard::writeTrustworthyWebURLsPboardType(const PasteboardURL&)
131 {
132     // A trustworthy URL pasteboard type needs to be decided on
133     // before we allow calls to this function. A page data transfer
134     // should not use the same pasteboard type as this function for
135     // URLs.
136     ASSERT_NOT_REACHED();
137 }
138
139 bool Pasteboard::canSmartReplace()
140 {
141     return false;
142 }
143
144 void Pasteboard::read(PasteboardPlainText& text)
145 {
146     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
147     text.text = strategy.readStringFromPasteboard(0, kUTTypeURL, m_pasteboardName);
148     if (!text.text.isNull() && !text.text.isEmpty()) {
149         text.isURL = true;
150         return;
151     }
152
153     text.text = strategy.readStringFromPasteboard(0, kUTTypeText, m_pasteboardName);
154     if (text.text.isEmpty())
155         text.text = strategy.readStringFromPasteboard(0, kUTTypePlainText, m_pasteboardName);
156
157     text.isURL = false;
158 }
159
160 static NSArray* supportedImageTypes()
161 {
162     return @[(id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF];
163 }
164
165 Pasteboard::ReaderResult Pasteboard::readPasteboardWebContentDataForType(PasteboardWebContentReader& reader, PasteboardStrategy& strategy, NSString *type, int itemIndex)
166 {
167     if ([type isEqualToString:WebArchivePboardType]) {
168         auto buffer = strategy.readBufferFromPasteboard(itemIndex, WebArchivePboardType, m_pasteboardName);
169         if (m_changeCount != changeCount())
170             return ReaderResult::PasteboardWasChangedExternally;
171         return buffer && reader.readWebArchive(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
172     }
173
174     if ([type isEqualToString:(NSString *)kUTTypeHTML]) {
175         String htmlString = strategy.readStringFromPasteboard(itemIndex, kUTTypeHTML, m_pasteboardName);
176         if (m_changeCount != changeCount())
177             return ReaderResult::PasteboardWasChangedExternally;
178         return !htmlString.isNull() && reader.readHTML(htmlString) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
179     }
180
181     if ([type isEqualToString:(NSString *)kUTTypeFlatRTFD]) {
182         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeFlatRTFD, m_pasteboardName);
183         if (m_changeCount != changeCount())
184             return ReaderResult::PasteboardWasChangedExternally;
185         return buffer && reader.readRTFD(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
186     }
187
188     if ([type isEqualToString:(NSString *)kUTTypeRTF]) {
189         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeRTF, m_pasteboardName);
190         if (m_changeCount != changeCount())
191             return ReaderResult::PasteboardWasChangedExternally;
192         return buffer && reader.readRTF(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
193     }
194
195     if ([supportedImageTypes() containsObject:type]) {
196         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, type, m_pasteboardName);
197         if (m_changeCount != changeCount())
198             return ReaderResult::PasteboardWasChangedExternally;
199         return buffer && reader.readImage(buffer.releaseNonNull(), type) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
200     }
201
202     if ([type isEqualToString:(NSString *)kUTTypeURL]) {
203         String title;
204         URL url = strategy.readURLFromPasteboard(itemIndex, kUTTypeURL, m_pasteboardName, title);
205         if (m_changeCount != changeCount())
206             return ReaderResult::PasteboardWasChangedExternally;
207         return !url.isNull() && reader.readURL(url, title) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
208     }
209
210     if (UTTypeConformsTo((CFStringRef)type, kUTTypePlainText)) {
211         String string = strategy.readStringFromPasteboard(itemIndex, kUTTypePlainText, m_pasteboardName);
212         if (m_changeCount != changeCount())
213             return ReaderResult::PasteboardWasChangedExternally;
214         return !string.isNull() && reader.readPlainText(string) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
215     }
216
217     if (UTTypeConformsTo((CFStringRef)type, kUTTypeText)) {
218         String string = strategy.readStringFromPasteboard(itemIndex, kUTTypeText, m_pasteboardName);
219         if (m_changeCount != changeCount())
220             return ReaderResult::PasteboardWasChangedExternally;
221         return !string.isNull() && reader.readPlainText(string) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
222     }
223
224     return ReaderResult::DidNotReadType;
225 }
226
227 void Pasteboard::read(PasteboardWebContentReader& reader)
228 {
229     reader.contentOrigin = readOrigin();
230     if (respectsUTIFidelities()) {
231         readRespectingUTIFidelities(reader);
232         return;
233     }
234
235     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
236
237     int numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName);
238
239     if (!numberOfItems)
240         return;
241
242     NSArray *types = supportedWebContentPasteboardTypes();
243     int numberOfTypes = [types count];
244
245     for (int i = 0; i < numberOfItems; i++) {
246         for (int typeIndex = 0; typeIndex < numberOfTypes; typeIndex++) {
247             auto result = readPasteboardWebContentDataForType(reader, strategy, [types objectAtIndex:typeIndex], i);
248             if (result == ReaderResult::PasteboardWasChangedExternally)
249                 return;
250             if (result == ReaderResult::ReadType)
251                 break;
252         }
253     }
254 }
255
256 bool Pasteboard::respectsUTIFidelities() const
257 {
258     // For now, data interaction is the only feature that uses item-provider-based pasteboard representations.
259     // In the future, we may need to consult the client layer to determine whether or not the pasteboard supports
260     // item types ranked by fidelity.
261     return m_pasteboardName == "data interaction pasteboard";
262 }
263
264 void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader)
265 {
266     ASSERT(respectsUTIFidelities());
267     auto& strategy = *platformStrategies()->pasteboardStrategy();
268     for (NSUInteger index = 0, numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); index < numberOfItems; ++index) {
269         // Try to read data from each type identifier that this pasteboard item supports, and WebKit also recognizes. Type identifiers are
270         // read in order of fidelity, as specified by each pasteboard item.
271         Vector<String> typesForItemInOrderOfFidelity;
272         strategy.getTypesByFidelityForItemAtIndex(typesForItemInOrderOfFidelity, index, m_pasteboardName);
273         for (auto& type : typesForItemInOrderOfFidelity) {
274             auto result = readPasteboardWebContentDataForType(reader, strategy, type, index);
275             if (result == ReaderResult::PasteboardWasChangedExternally)
276                 return;
277             if (result == ReaderResult::ReadType)
278                 break;
279         }
280     }
281 }
282
283 NSArray *Pasteboard::supportedWebContentPasteboardTypes()
284 {
285     return @[(id)WebArchivePboardType, (id)kUTTypeFlatRTFD, (id)kUTTypeRTF, (id)kUTTypeHTML, (id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, (id)kUTTypeURL, (id)kUTTypeText];
286 }
287
288 NSArray *Pasteboard::supportedFileUploadPasteboardTypes()
289 {
290     return @[ (NSString *)kUTTypeContent, (NSString *)kUTTypeZipArchive, (NSString *)kUTTypeFolder ];
291 }
292
293 bool Pasteboard::hasData()
294 {
295     return !!platformStrategies()->pasteboardStrategy()->getPasteboardItemsCount(m_pasteboardName);
296 }
297
298 static String utiTypeFromCocoaType(NSString *type)
299 {
300     RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL));
301     if (!utiType)
302         return String();
303     return String(adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType)).get());
304 }
305
306 static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
307 {
308     if (NSString *platformType = PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type)) {
309         if (platformType.length)
310             return platformType;
311     }
312
313     // Try UTI now.
314     if (NSString *utiType = utiTypeFromCocoaType(type))
315         return utiType;
316
317     // No mapping, just pass the whole string though.
318     return (NSString *)type;
319 }
320
321 void Pasteboard::clear(const String& type)
322 {
323     // Since UIPasteboard enforces changeCount itself on writing, we don't check it here.
324
325     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
326     if (!cocoaType)
327         return;
328
329     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), String(), m_pasteboardName);
330 }
331
332 void Pasteboard::clear()
333 {
334     platformStrategies()->pasteboardStrategy()->writeToPasteboard(String(), String(), m_pasteboardName);
335 }
336
337 String Pasteboard::readPlatformValueAsString(const String& domType, long changeCount, const String& pasteboardName)
338 {
339     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
340
341     int numberOfItems = strategy.getPasteboardItemsCount(pasteboardName);
342
343     if (!numberOfItems)
344         return String();
345
346     // Grab the value off the pasteboard corresponding to the cocoaType.
347     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(domType);
348
349     NSString *cocoaValue = nil;
350
351     if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
352         String title;
353         URL url = strategy.readURLFromPasteboard(0, kUTTypeURL, pasteboardName, title);
354         if (!url.isNull())
355             cocoaValue = [(NSURL *)url absoluteString];
356     } else if ([cocoaType isEqualToString:(NSString *)kUTTypePlainText]) {
357         String value = strategy.readStringFromPasteboard(0, kUTTypePlainText, pasteboardName);
358         if (!value.isNull())
359             cocoaValue = [(NSString *)value precomposedStringWithCanonicalMapping];
360     } else if (cocoaType)
361         cocoaValue = (NSString *)strategy.readStringFromPasteboard(0, cocoaType.get(), pasteboardName);
362
363     // Enforce changeCount ourselves for security. We check after reading instead of before to be
364     // sure it doesn't change between our testing the change count and accessing the data.
365     if (cocoaValue && changeCount == changeCountForPasteboard(pasteboardName))
366         return cocoaValue;
367
368     return String();
369 }
370
371 void Pasteboard::addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, const String& cocoaType)
372 {
373     // UTI may not do these right, so make sure we get the right, predictable result.
374     if ([cocoaType isEqualToString:(NSString *)kUTTypePlainText]
375         || [cocoaType isEqualToString:(NSString *)kUTTypeUTF8PlainText]
376         || [cocoaType isEqualToString:(NSString *)kUTTypeUTF16PlainText]) {
377         resultTypes.add(ASCIILiteral("text/plain"));
378         return;
379     }
380     if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
381         resultTypes.add(ASCIILiteral("text/uri-list"));
382         return;
383     }
384     if ([cocoaType isEqualToString:(NSString *)kUTTypeHTML]) {
385         resultTypes.add(ASCIILiteral("text/html"));
386         // We don't return here for App compatibility.
387     }
388     if (Pasteboard::shouldTreatCocoaTypeAsFile(cocoaType))
389         return;
390     String utiType = utiTypeFromCocoaType(cocoaType);
391     if (!utiType.isEmpty()) {
392         resultTypes.add(utiType);
393         return;
394     }
395     // No mapping, just pass the whole string though.
396     resultTypes.add(cocoaType);
397 }
398
399 void Pasteboard::writeString(const String& type, const String& data)
400 {
401     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
402     if (!cocoaType)
403         return;
404
405     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), data, m_pasteboardName);
406 }
407
408 Vector<String> Pasteboard::readFilenames()
409 {
410     Vector<String> filenames;
411     // Currently, data interaction is the only case on iOS where the pasteboard may contain relevant filenames.
412     platformStrategies()->pasteboardStrategy()->getFilenamesForDataInteraction(filenames, m_pasteboardName);
413     return filenames;
414 }
415
416 }