Cannot access images included in the content pasted from Microsoft Word
[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     if (respectsUTIFidelities()) {
230         readRespectingUTIFidelities(reader);
231         return;
232     }
233
234     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
235
236     int numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName);
237
238     if (!numberOfItems)
239         return;
240
241     reader.contentOrigin = readOrigin();
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 result = readPasteboardWebContentDataForType(reader, strategy, [types objectAtIndex:typeIndex], i);
249             if (result == ReaderResult::PasteboardWasChangedExternally)
250                 return;
251             if (result == 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         // Try to read data from each type identifier that this pasteboard item supports, and WebKit also recognizes. Type identifiers are
271         // read in order of fidelity, as specified by each pasteboard item.
272         Vector<String> typesForItemInOrderOfFidelity;
273         strategy.getTypesByFidelityForItemAtIndex(typesForItemInOrderOfFidelity, index, m_pasteboardName);
274         for (auto& type : typesForItemInOrderOfFidelity) {
275             auto result = readPasteboardWebContentDataForType(reader, strategy, type, index);
276             if (result == ReaderResult::PasteboardWasChangedExternally)
277                 return;
278             if (result == ReaderResult::ReadType)
279                 break;
280         }
281     }
282 }
283
284 NSArray *Pasteboard::supportedWebContentPasteboardTypes()
285 {
286     return @[(id)WebArchivePboardType, (id)kUTTypeFlatRTFD, (id)kUTTypeRTF, (id)kUTTypeHTML, (id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, (id)kUTTypeURL, (id)kUTTypeText];
287 }
288
289 NSArray *Pasteboard::supportedFileUploadPasteboardTypes()
290 {
291     return @[ (NSString *)kUTTypeContent, (NSString *)kUTTypeZipArchive, (NSString *)kUTTypeFolder ];
292 }
293
294 bool Pasteboard::hasData()
295 {
296     return !!platformStrategies()->pasteboardStrategy()->getPasteboardItemsCount(m_pasteboardName);
297 }
298
299 static String utiTypeFromCocoaType(NSString *type)
300 {
301     RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL));
302     if (!utiType)
303         return String();
304     return String(adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType)).get());
305 }
306
307 static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
308 {
309     if (NSString *platformType = PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type)) {
310         if (platformType.length)
311             return platformType;
312     }
313
314     // Try UTI now.
315     if (NSString *utiType = utiTypeFromCocoaType(type))
316         return utiType;
317
318     // No mapping, just pass the whole string though.
319     return (NSString *)type;
320 }
321
322 void Pasteboard::clear(const String& type)
323 {
324     // Since UIPasteboard enforces changeCount itself on writing, we don't check it here.
325
326     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
327     if (!cocoaType)
328         return;
329
330     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), String(), m_pasteboardName);
331 }
332
333 void Pasteboard::clear()
334 {
335     platformStrategies()->pasteboardStrategy()->writeToPasteboard(String(), String(), m_pasteboardName);
336 }
337
338 String Pasteboard::readPlatformValueAsString(const String& domType, long changeCount, const String& pasteboardName)
339 {
340     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
341
342     int numberOfItems = strategy.getPasteboardItemsCount(pasteboardName);
343
344     if (!numberOfItems)
345         return String();
346
347     // Grab the value off the pasteboard corresponding to the cocoaType.
348     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(domType);
349
350     NSString *cocoaValue = nil;
351
352     if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
353         String title;
354         URL url = strategy.readURLFromPasteboard(0, kUTTypeURL, pasteboardName, title);
355         if (!url.isNull())
356             cocoaValue = [(NSURL *)url absoluteString];
357     } else if ([cocoaType isEqualToString:(NSString *)kUTTypePlainText]) {
358         String value = strategy.readStringFromPasteboard(0, kUTTypePlainText, pasteboardName);
359         if (!value.isNull())
360             cocoaValue = [(NSString *)value precomposedStringWithCanonicalMapping];
361     } else if (cocoaType)
362         cocoaValue = (NSString *)strategy.readStringFromPasteboard(0, cocoaType.get(), pasteboardName);
363
364     // Enforce changeCount ourselves for security. We check after reading instead of before to be
365     // sure it doesn't change between our testing the change count and accessing the data.
366     if (cocoaValue && changeCount == changeCountForPasteboard(pasteboardName))
367         return cocoaValue;
368
369     return String();
370 }
371
372 void Pasteboard::addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, const String& cocoaType)
373 {
374     // UTI may not do these right, so make sure we get the right, predictable result.
375     if ([cocoaType isEqualToString:(NSString *)kUTTypePlainText]
376         || [cocoaType isEqualToString:(NSString *)kUTTypeUTF8PlainText]
377         || [cocoaType isEqualToString:(NSString *)kUTTypeUTF16PlainText]) {
378         resultTypes.add(ASCIILiteral("text/plain"));
379         return;
380     }
381     if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
382         resultTypes.add(ASCIILiteral("text/uri-list"));
383         return;
384     }
385     if ([cocoaType isEqualToString:(NSString *)kUTTypeHTML]) {
386         resultTypes.add(ASCIILiteral("text/html"));
387         // We don't return here for App compatibility.
388     }
389     if (Pasteboard::shouldTreatCocoaTypeAsFile(cocoaType))
390         return;
391     String utiType = utiTypeFromCocoaType(cocoaType);
392     if (!utiType.isEmpty()) {
393         resultTypes.add(utiType);
394         return;
395     }
396     // No mapping, just pass the whole string though.
397     resultTypes.add(cocoaType);
398 }
399
400 void Pasteboard::writeString(const String& type, const String& data)
401 {
402     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
403     if (!cocoaType)
404         return;
405
406     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), data, m_pasteboardName);
407 }
408
409 Vector<String> Pasteboard::readFilenames()
410 {
411     Vector<String> filenames;
412     // Currently, data interaction is the only case on iOS where the pasteboard may contain relevant filenames.
413     platformStrategies()->pasteboardStrategy()->getFilenamesForDataInteraction(filenames, m_pasteboardName);
414     return filenames;
415 }
416
417 }