Move URL from WebCore to WTF
[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         bool canReadAttachment = policy == WebContentReadingPolicy::AnyType && RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled() && !info.pathForFileUpload.isEmpty();
298         if (canReadAttachment && info.preferredPresentationStyle == PasteboardItemPresentationStyle::Attachment) {
299             reader.readFilePaths({ info.pathForFileUpload });
300             continue;
301         }
302 #endif
303         // Try to read data from each type identifier that this pasteboard item supports, and WebKit also recognizes. Type identifiers are
304         // read in order of fidelity, as specified by each pasteboard item.
305         Vector<String> typesForItemInOrderOfFidelity;
306         strategy.getTypesByFidelityForItemAtIndex(typesForItemInOrderOfFidelity, index, m_pasteboardName);
307         ReaderResult result = ReaderResult::DidNotReadType;
308         for (auto& type : typesForItemInOrderOfFidelity) {
309             if (!isTypeAllowedByReadingPolicy(type, policy))
310                 continue;
311
312             result = readPasteboardWebContentDataForType(reader, strategy, type, index);
313             if (result == ReaderResult::PasteboardWasChangedExternally)
314                 return;
315             if (result == ReaderResult::ReadType)
316                 break;
317         }
318 #if ENABLE(ATTACHMENT_ELEMENT)
319         if (canReadAttachment && result == ReaderResult::DidNotReadType)
320             reader.readFilePaths({ info.pathForFileUpload });
321 #endif
322     }
323 }
324
325 NSArray *Pasteboard::supportedWebContentPasteboardTypes()
326 {
327     return @[
328 #if !PLATFORM(IOSMAC)
329         WebArchivePboardType,
330 #endif
331         (__bridge NSString *)kUTTypeWebArchive,
332 #if !PLATFORM(IOSMAC)
333         (__bridge NSString *)kUTTypeFlatRTFD,
334         (__bridge NSString *)kUTTypeRTF,
335 #endif
336         (__bridge NSString *)kUTTypeHTML,
337         (__bridge NSString *)kUTTypePNG,
338         (__bridge NSString *)kUTTypeTIFF,
339         (__bridge NSString *)kUTTypeJPEG,
340         (__bridge NSString *)kUTTypeGIF,
341         (__bridge NSString *)kUTTypeURL,
342         (__bridge NSString *)kUTTypeText
343     ];
344 }
345
346 NSArray *Pasteboard::supportedFileUploadPasteboardTypes()
347 {
348     return @[ (NSString *)kUTTypeContent, (NSString *)kUTTypeZipArchive, (NSString *)kUTTypeFolder ];
349 }
350
351 bool Pasteboard::hasData()
352 {
353     return !!platformStrategies()->pasteboardStrategy()->getPasteboardItemsCount(m_pasteboardName);
354 }
355
356 static String utiTypeFromCocoaType(NSString *type)
357 {
358     RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL));
359     if (!utiType)
360         return String();
361     return String(adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType)).get());
362 }
363
364 static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
365 {
366     if (NSString *platformType = PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type)) {
367         if (platformType.length)
368             return platformType;
369     }
370
371     // Try UTI now.
372     if (NSString *utiType = utiTypeFromCocoaType(type))
373         return utiType;
374
375     // No mapping, just pass the whole string though.
376     return (NSString *)type;
377 }
378
379 void Pasteboard::clear(const String& type)
380 {
381     // Since UIPasteboard enforces changeCount itself on writing, we don't check it here.
382
383     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
384     if (!cocoaType)
385         return;
386
387     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), String(), m_pasteboardName);
388 }
389
390 void Pasteboard::clear()
391 {
392     platformStrategies()->pasteboardStrategy()->writeToPasteboard(String(), String(), m_pasteboardName);
393 }
394
395 Vector<String> Pasteboard::readPlatformValuesAsStrings(const String& domType, long changeCount, const String& pasteboardName)
396 {
397     auto& strategy = *platformStrategies()->pasteboardStrategy();
398
399     // Grab the value off the pasteboard corresponding to the cocoaType.
400     auto cocoaType = cocoaTypeFromHTMLClipboardType(domType);
401     if (!cocoaType)
402         return { };
403
404     auto values = strategy.allStringsForType(cocoaType.get(), pasteboardName);
405     if ([cocoaType isEqualToString:(__bridge NSString *)kUTTypePlainText]) {
406         values = values.map([&] (auto& value) -> String {
407             return [value precomposedStringWithCanonicalMapping];
408         });
409     }
410
411     // Enforce changeCount ourselves for security. We check after reading instead of before to be
412     // sure it doesn't change between our testing the change count and accessing the data.
413     if (changeCount != changeCountForPasteboard(pasteboardName))
414         return { };
415
416     return values;
417 }
418
419 void Pasteboard::addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, const String& cocoaType)
420 {
421     // UTI may not do these right, so make sure we get the right, predictable result.
422     if ([cocoaType isEqualToString:(NSString *)kUTTypePlainText]
423         || [cocoaType isEqualToString:(NSString *)kUTTypeUTF8PlainText]
424         || [cocoaType isEqualToString:(NSString *)kUTTypeUTF16PlainText]) {
425         resultTypes.add("text/plain"_s);
426         return;
427     }
428     if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
429         resultTypes.add("text/uri-list"_s);
430         return;
431     }
432     if ([cocoaType isEqualToString:(NSString *)kUTTypeHTML]) {
433         resultTypes.add("text/html"_s);
434         // We don't return here for App compatibility.
435     }
436     if (Pasteboard::shouldTreatCocoaTypeAsFile(cocoaType))
437         return;
438     String utiType = utiTypeFromCocoaType(cocoaType);
439     if (!utiType.isEmpty()) {
440         resultTypes.add(utiType);
441         return;
442     }
443     // No mapping, just pass the whole string though.
444     resultTypes.add(cocoaType);
445 }
446
447 void Pasteboard::writeString(const String& type, const String& data)
448 {
449     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
450     if (!cocoaType)
451         return;
452
453     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), data, m_pasteboardName);
454 }
455
456 Vector<String> Pasteboard::readFilePaths()
457 {
458     Vector<String> filePaths;
459     auto& strategy = *platformStrategies()->pasteboardStrategy();
460     for (NSUInteger index = 0, numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); index < numberOfItems; ++index) {
461         // Currently, drag and drop is the only case on iOS where the "pasteboard" may contain file paths.
462         auto filePath = strategy.informationForItemAtIndex(index, m_pasteboardName).pathForFileUpload;
463         if (!filePath.isEmpty())
464             filePaths.append(WTFMove(filePath));
465     }
466     return filePaths;
467 }
468
469 }
470
471 #endif // PLATFORM(IOS_FAMILY)