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