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