[iOS] Draw caps lock indicator in password fields
[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 "URL.h"
39 #import "UTIUtilities.h"
40 #import "WebNSAttributedStringExtras.h"
41 #import <MobileCoreServices/MobileCoreServices.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 @[(id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF];
172 }
173
174 static bool isTypeAllowedByReadingPolicy(NSString *type, WebContentReadingPolicy policy)
175 {
176     return policy == WebContentReadingPolicy::AnyType
177         || [type isEqualToString:WebArchivePboardType]
178         || [type isEqualToString:(NSString *)kUTTypeHTML]
179         || [type isEqualToString:(NSString *)kUTTypeRTF]
180         || [type isEqualToString:(NSString *)kUTTypeFlatRTFD];
181 }
182
183 Pasteboard::ReaderResult Pasteboard::readPasteboardWebContentDataForType(PasteboardWebContentReader& reader, PasteboardStrategy& strategy, NSString *type, int itemIndex)
184 {
185     if ([type isEqualToString:WebArchivePboardType]) {
186         auto buffer = strategy.readBufferFromPasteboard(itemIndex, WebArchivePboardType, m_pasteboardName);
187         if (m_changeCount != changeCount())
188             return ReaderResult::PasteboardWasChangedExternally;
189         return buffer && reader.readWebArchive(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
190     }
191
192     if ([type isEqualToString:(NSString *)kUTTypeHTML]) {
193         String htmlString = strategy.readStringFromPasteboard(itemIndex, kUTTypeHTML, m_pasteboardName);
194         if (m_changeCount != changeCount())
195             return ReaderResult::PasteboardWasChangedExternally;
196         return !htmlString.isNull() && reader.readHTML(htmlString) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
197     }
198
199     if ([type isEqualToString:(NSString *)kUTTypeFlatRTFD]) {
200         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeFlatRTFD, m_pasteboardName);
201         if (m_changeCount != changeCount())
202             return ReaderResult::PasteboardWasChangedExternally;
203         return buffer && reader.readRTFD(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
204     }
205
206     if ([type isEqualToString:(NSString *)kUTTypeRTF]) {
207         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeRTF, m_pasteboardName);
208         if (m_changeCount != changeCount())
209             return ReaderResult::PasteboardWasChangedExternally;
210         return buffer && reader.readRTF(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
211     }
212
213     if ([supportedImageTypes() containsObject:type]) {
214         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, type, m_pasteboardName);
215         if (m_changeCount != changeCount())
216             return ReaderResult::PasteboardWasChangedExternally;
217         return buffer && reader.readImage(buffer.releaseNonNull(), type) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
218     }
219
220     if ([type isEqualToString:(NSString *)kUTTypeURL]) {
221         String title;
222         URL url = strategy.readURLFromPasteboard(itemIndex, m_pasteboardName, title);
223         if (m_changeCount != changeCount())
224             return ReaderResult::PasteboardWasChangedExternally;
225         return !url.isNull() && reader.readURL(url, title) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
226     }
227
228     if (UTTypeConformsTo((CFStringRef)type, kUTTypePlainText)) {
229         String string = strategy.readStringFromPasteboard(itemIndex, kUTTypePlainText, m_pasteboardName);
230         if (m_changeCount != changeCount())
231             return ReaderResult::PasteboardWasChangedExternally;
232         return !string.isNull() && reader.readPlainText(string) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
233     }
234
235     if (UTTypeConformsTo((CFStringRef)type, kUTTypeText)) {
236         String string = strategy.readStringFromPasteboard(itemIndex, kUTTypeText, m_pasteboardName);
237         if (m_changeCount != changeCount())
238             return ReaderResult::PasteboardWasChangedExternally;
239         return !string.isNull() && reader.readPlainText(string) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
240     }
241
242     return ReaderResult::DidNotReadType;
243 }
244
245 void Pasteboard::read(PasteboardWebContentReader& reader, WebContentReadingPolicy policy)
246 {
247     reader.contentOrigin = readOrigin();
248     if (respectsUTIFidelities()) {
249         readRespectingUTIFidelities(reader, policy);
250         return;
251     }
252
253     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
254
255     int numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName);
256
257     if (!numberOfItems)
258         return;
259
260     NSArray *types = supportedWebContentPasteboardTypes();
261     int numberOfTypes = [types count];
262
263     for (int i = 0; i < numberOfItems; i++) {
264         for (int typeIndex = 0; typeIndex < numberOfTypes; typeIndex++) {
265             NSString *type = [types objectAtIndex:typeIndex];
266             if (!isTypeAllowedByReadingPolicy(type, policy))
267                 continue;
268
269             auto itemResult = readPasteboardWebContentDataForType(reader, strategy, type, i);
270             if (itemResult == ReaderResult::PasteboardWasChangedExternally)
271                 return;
272
273             if (itemResult == ReaderResult::ReadType)
274                 break;
275         }
276     }
277 }
278
279 bool Pasteboard::respectsUTIFidelities() const
280 {
281     // For now, data interaction is the only feature that uses item-provider-based pasteboard representations.
282     // In the future, we may need to consult the client layer to determine whether or not the pasteboard supports
283     // item types ranked by fidelity.
284     return m_pasteboardName == "data interaction pasteboard";
285 }
286
287 void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader, WebContentReadingPolicy policy)
288 {
289     ASSERT(respectsUTIFidelities());
290     auto& strategy = *platformStrategies()->pasteboardStrategy();
291     for (NSUInteger index = 0, numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); index < numberOfItems; ++index) {
292 #if ENABLE(ATTACHMENT_ELEMENT)
293         auto info = strategy.informationForItemAtIndex(index, m_pasteboardName);
294         bool canReadAttachment = policy == WebContentReadingPolicy::AnyType && RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled() && !info.pathForFileUpload.isEmpty();
295         if (canReadAttachment && info.preferredPresentationStyle == PasteboardItemPresentationStyle::Attachment) {
296             reader.readFilePaths({ info.pathForFileUpload });
297             continue;
298         }
299 #endif
300         // Try to read data from each type identifier that this pasteboard item supports, and WebKit also recognizes. Type identifiers are
301         // read in order of fidelity, as specified by each pasteboard item.
302         Vector<String> typesForItemInOrderOfFidelity;
303         strategy.getTypesByFidelityForItemAtIndex(typesForItemInOrderOfFidelity, index, m_pasteboardName);
304         ReaderResult result = ReaderResult::DidNotReadType;
305         for (auto& type : typesForItemInOrderOfFidelity) {
306             if (!isTypeAllowedByReadingPolicy(type, policy))
307                 continue;
308
309             result = readPasteboardWebContentDataForType(reader, strategy, type, index);
310             if (result == ReaderResult::PasteboardWasChangedExternally)
311                 return;
312             if (result == ReaderResult::ReadType)
313                 break;
314         }
315 #if ENABLE(ATTACHMENT_ELEMENT)
316         if (canReadAttachment && result == ReaderResult::DidNotReadType)
317             reader.readFilePaths({ info.pathForFileUpload });
318 #endif
319     }
320 }
321
322 NSArray *Pasteboard::supportedWebContentPasteboardTypes()
323 {
324     return @[(id)WebArchivePboardType, (id)kUTTypeFlatRTFD, (id)kUTTypeRTF, (id)kUTTypeHTML, (id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, (id)kUTTypeURL, (id)kUTTypeText];
325 }
326
327 NSArray *Pasteboard::supportedFileUploadPasteboardTypes()
328 {
329     return @[ (NSString *)kUTTypeContent, (NSString *)kUTTypeZipArchive, (NSString *)kUTTypeFolder ];
330 }
331
332 bool Pasteboard::hasData()
333 {
334     return !!platformStrategies()->pasteboardStrategy()->getPasteboardItemsCount(m_pasteboardName);
335 }
336
337 static String utiTypeFromCocoaType(NSString *type)
338 {
339     RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL));
340     if (!utiType)
341         return String();
342     return String(adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType)).get());
343 }
344
345 static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
346 {
347     if (NSString *platformType = PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type)) {
348         if (platformType.length)
349             return platformType;
350     }
351
352     // Try UTI now.
353     if (NSString *utiType = utiTypeFromCocoaType(type))
354         return utiType;
355
356     // No mapping, just pass the whole string though.
357     return (NSString *)type;
358 }
359
360 void Pasteboard::clear(const String& type)
361 {
362     // Since UIPasteboard enforces changeCount itself on writing, we don't check it here.
363
364     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
365     if (!cocoaType)
366         return;
367
368     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), String(), m_pasteboardName);
369 }
370
371 void Pasteboard::clear()
372 {
373     platformStrategies()->pasteboardStrategy()->writeToPasteboard(String(), String(), m_pasteboardName);
374 }
375
376 Vector<String> Pasteboard::readPlatformValuesAsStrings(const String& domType, long changeCount, const String& pasteboardName)
377 {
378     auto& strategy = *platformStrategies()->pasteboardStrategy();
379
380     // Grab the value off the pasteboard corresponding to the cocoaType.
381     auto cocoaType = cocoaTypeFromHTMLClipboardType(domType);
382     if (!cocoaType)
383         return { };
384
385     auto values = strategy.allStringsForType(cocoaType.get(), pasteboardName);
386     if ([cocoaType isEqualToString:(__bridge NSString *)kUTTypePlainText]) {
387         values = values.map([&] (auto& value) -> String {
388             return [value precomposedStringWithCanonicalMapping];
389         });
390     }
391
392     // Enforce changeCount ourselves for security. We check after reading instead of before to be
393     // sure it doesn't change between our testing the change count and accessing the data.
394     if (changeCount != changeCountForPasteboard(pasteboardName))
395         return { };
396
397     return values;
398 }
399
400 void Pasteboard::addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, const String& cocoaType)
401 {
402     // UTI may not do these right, so make sure we get the right, predictable result.
403     if ([cocoaType isEqualToString:(NSString *)kUTTypePlainText]
404         || [cocoaType isEqualToString:(NSString *)kUTTypeUTF8PlainText]
405         || [cocoaType isEqualToString:(NSString *)kUTTypeUTF16PlainText]) {
406         resultTypes.add("text/plain"_s);
407         return;
408     }
409     if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
410         resultTypes.add("text/uri-list"_s);
411         return;
412     }
413     if ([cocoaType isEqualToString:(NSString *)kUTTypeHTML]) {
414         resultTypes.add("text/html"_s);
415         // We don't return here for App compatibility.
416     }
417     if (Pasteboard::shouldTreatCocoaTypeAsFile(cocoaType))
418         return;
419     String utiType = utiTypeFromCocoaType(cocoaType);
420     if (!utiType.isEmpty()) {
421         resultTypes.add(utiType);
422         return;
423     }
424     // No mapping, just pass the whole string though.
425     resultTypes.add(cocoaType);
426 }
427
428 void Pasteboard::writeString(const String& type, const String& data)
429 {
430     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
431     if (!cocoaType)
432         return;
433
434     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), data, m_pasteboardName);
435 }
436
437 Vector<String> Pasteboard::readFilePaths()
438 {
439     Vector<String> filePaths;
440     auto& strategy = *platformStrategies()->pasteboardStrategy();
441     for (NSUInteger index = 0, numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); index < numberOfItems; ++index) {
442         // Currently, drag and drop is the only case on iOS where the "pasteboard" may contain file paths.
443         auto filePath = strategy.informationForItemAtIndex(index, m_pasteboardName).pathForFileUpload;
444         if (!filePath.isEmpty())
445             filePaths.append(WTFMove(filePath));
446     }
447     return filePaths;
448 }
449
450 }
451
452 #endif // PLATFORM(IOS_FAMILY)