2647480bda72bf36332e542fe177ffc2698ff287
[WebKit.git] / Source / WebCore / platform / cocoa / PasteboardCocoa.mm
1 /*
2  * Copyright (C) 2017 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''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "Pasteboard.h"
28
29 #include "LegacyNSPasteboardTypes.h"
30 #include "PasteboardStrategy.h"
31 #include "PlatformStrategies.h"
32 #include "SharedBuffer.h"
33 #include <ImageIO/ImageIO.h>
34 #include <wtf/ListHashSet.h>
35 #include <wtf/text/StringHash.h>
36
37 #if PLATFORM(IOS_FAMILY)
38 #include <MobileCoreServices/MobileCoreServices.h>
39 #endif
40
41 namespace WebCore {
42
43 #if PLATFORM(MAC)
44 static NSBitmapImageFileType bitmapPNGFileType()
45 {
46     return NSBitmapImageFileTypePNG;
47 }
48 #endif // PLATFORM(MAC)
49
50 // Making this non-inline so that WebKit 2's decoding doesn't have to include SharedBuffer.h.
51 PasteboardWebContent::PasteboardWebContent() = default;
52 PasteboardWebContent::~PasteboardWebContent() = default;
53
54 const char* PasteboardCustomData::cocoaType()
55 {
56     return "com.apple.WebKit.custom-pasteboard-data";
57 }
58
59 enum class ImageType {
60     Invalid = 0,
61     TIFF,
62     PNG,
63     JPEG,
64     GIF,
65 };
66
67 static ImageType cocoaTypeToImageType(const String& cocoaType)
68 {
69 #if PLATFORM(MAC)
70     if (cocoaType == String(legacyTIFFPasteboardType()))
71         return ImageType::TIFF;
72 #endif
73     if (cocoaType == String(kUTTypeTIFF))
74         return ImageType::TIFF;
75 #if PLATFORM(MAC)
76     if (cocoaType == "Apple PNG pasteboard type") // NSPNGPboardType
77         return ImageType::PNG;
78 #endif
79     if (cocoaType == String(kUTTypePNG))
80         return ImageType::PNG;
81     if (cocoaType == String(kUTTypeJPEG))
82         return ImageType::JPEG;
83     if (cocoaType == String(kUTTypeGIF))
84         return ImageType::GIF;
85     return ImageType::Invalid;
86 }
87
88 // String literals returned by this function must be defined exactly once
89 // since read(PasteboardFileReader&) uses HashMap<const char*> to check uniqueness.
90 static const char* imageTypeToMIMEType(ImageType type)
91 {
92     switch (type) {
93     case ImageType::Invalid:
94         return nullptr;
95     case ImageType::TIFF:
96 #if PLATFORM(MAC)
97         return "image/png"; // For Web compatibility, we pretend to have PNG instead.
98 #else
99         return nullptr; // Don't support pasting TIFF on iOS for now.
100 #endif
101     case ImageType::PNG:
102         return "image/png";
103     case ImageType::JPEG:
104         return "image/jpeg";
105     case ImageType::GIF:
106         return "image/gif";
107     }
108 }
109
110 static const char* imageTypeToFakeFilename(ImageType type)
111 {
112     switch (type) {
113     case ImageType::Invalid:
114         ASSERT_NOT_REACHED();
115         return nullptr;
116     case ImageType::TIFF:
117 #if PLATFORM(MAC)
118         return "image.png"; // For Web compatibility, we pretend to have PNG instead.
119 #else
120         ASSERT_NOT_REACHED();
121         return nullptr;
122 #endif
123     case ImageType::PNG:
124         return "image.png";
125     case ImageType::JPEG:
126         return "image.jpeg";
127     case ImageType::GIF:
128         return "image.gif";
129     }
130 }
131
132 bool Pasteboard::shouldTreatCocoaTypeAsFile(const String& cocoaType)
133 {
134     return cocoaTypeToImageType(cocoaType) != ImageType::Invalid;
135 }
136
137 Pasteboard::FileContentState Pasteboard::fileContentState()
138 {
139     bool mayContainFilePaths = platformStrategies()->pasteboardStrategy()->getNumberOfFiles(m_pasteboardName);
140
141 #if PLATFORM(IOS_FAMILY)
142     if (mayContainFilePaths) {
143         // On iOS, files are not written to the pasteboard using file URLs, so we need a heuristic to determine
144         // whether or not the pasteboard contains items that represent files. An example of when this gets tricky
145         // is differentiating between cases where the user is dragging a plain text file, versus selected text.
146         // Also, the presence of any other declared non-text data in the same item indicates that the content
147         // being dropped can take on another non-text format, which could be a file.
148         // If the item can't be treated as an attachment, it's very likely that the content being dropped is just
149         // an inline piece of text, with no files in the pasteboard (and therefore, no risk of leaking file paths
150         // to web content). In cases such as these, we should not suppress DataTransfer access.
151         auto items = platformStrategies()->pasteboardStrategy()->allPasteboardItemInfo(m_pasteboardName);
152         mayContainFilePaths = items.size() != 1 || notFound != items.findMatching([] (auto& item) {
153             return item.canBeTreatedAsAttachmentOrFile() || item.isNonTextType || item.containsFileURLAndFileUploadContent;
154         });
155     }
156 #endif
157
158     if (!mayContainFilePaths) {
159         Vector<String> cocoaTypes;
160         platformStrategies()->pasteboardStrategy()->getTypes(cocoaTypes, m_pasteboardName);
161         if (cocoaTypes.findMatching([](const String& cocoaType) { return shouldTreatCocoaTypeAsFile(cocoaType); }) == notFound)
162             return FileContentState::NoFileOrImageData;
163
164         bool containsURL = notFound != cocoaTypes.findMatching([] (auto& cocoaType) {
165 #if PLATFORM(MAC)
166             if (cocoaType == String(legacyURLPasteboardType()))
167                 return true;
168 #endif
169             return cocoaType == String(kUTTypeURL);
170         });
171         mayContainFilePaths = containsURL && !Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(readString("text/uri-list"_s));
172     }
173
174     // Enforce changeCount ourselves for security. We check after reading instead of before to be
175     // sure it doesn't change between our testing the change count and accessing the data.
176     if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
177         return FileContentState::NoFileOrImageData;
178
179     // Even when there's only image data in the pasteboard and no file representations, we still run the risk of exposing file paths
180     // to the page if the app has written image data to the pasteboard with a corresponding file path as plain text. An example of
181     // this is copying an image with a local `src` in Safari. To mitigate this, we additionally require that the app has not also
182     // written URLs to the pasteboard, as this would suggest that the plain text data might contain file paths.
183     return mayContainFilePaths ? FileContentState::MayContainFilePaths : FileContentState::InMemoryImage;
184 }
185
186 Vector<String> Pasteboard::typesSafeForBindings(const String& origin)
187 {
188     Vector<String> types = platformStrategies()->pasteboardStrategy()->typesSafeForDOMToReadAndWrite(m_pasteboardName, origin);
189
190     // Enforce changeCount ourselves for security. We check after reading instead of before to be
191     // sure it doesn't change between our testing the change count and accessing the data.
192     if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
193         return { };
194
195     return types;
196 }
197
198 Vector<String> Pasteboard::typesForLegacyUnsafeBindings()
199 {
200     auto cocoaTypes = readTypesWithSecurityCheck();
201     if (cocoaTypes.isEmpty())
202         return cocoaTypes;
203
204     ListHashSet<String> result;
205     for (auto& cocoaType : cocoaTypes)
206         addHTMLClipboardTypesForCocoaType(result, cocoaType);
207
208     return copyToVector(result);
209 }
210
211 #if PLATFORM(MAC)
212 static Ref<SharedBuffer> convertTIFFToPNG(SharedBuffer& tiffBuffer)
213 {
214     auto image = adoptNS([[NSBitmapImageRep alloc] initWithData: tiffBuffer.createNSData().get()]);
215     NSData *pngData = [image representationUsingType:bitmapPNGFileType() properties:@{ }];
216     return SharedBuffer::create(pngData);
217 }
218 #endif
219
220 void Pasteboard::read(PasteboardFileReader& reader)
221 {
222     auto filenames = readFilePaths();
223     if (!filenames.isEmpty()) {
224         for (auto& filename : filenames)
225             reader.readFilename(filename);
226         return;
227     }
228
229     auto cocoaTypes = readTypesWithSecurityCheck();
230     HashSet<const char*> existingMIMEs;
231     for (auto& cocoaType : cocoaTypes) {
232         auto imageType = cocoaTypeToImageType(cocoaType);
233         const char* mimeType = imageTypeToMIMEType(imageType);
234         if (!mimeType)
235             continue;
236         if (existingMIMEs.contains(mimeType))
237             continue;
238         auto buffer = readBufferForTypeWithSecurityCheck(cocoaType);
239 #if PLATFORM(MAC)
240         if (buffer && imageType == ImageType::TIFF)
241             buffer = convertTIFFToPNG(buffer.releaseNonNull());
242 #endif
243         if (!buffer)
244             continue;
245         existingMIMEs.add(mimeType);
246         reader.readBuffer(imageTypeToFakeFilename(imageType), mimeType, buffer.releaseNonNull());
247     }
248 }
249
250 Vector<String> Pasteboard::readAllStrings(const String& type)
251 {
252     return readPlatformValuesAsStrings(type, m_changeCount, m_pasteboardName);
253 }
254
255 String Pasteboard::readString(const String& type)
256 {
257     auto values = readPlatformValuesAsStrings(type, m_changeCount, m_pasteboardName);
258     return values.isEmpty() ? String() : values.first();
259 }
260
261 String Pasteboard::readStringInCustomData(const String& type)
262 {
263     return readCustomData().sameOriginCustomData.get(type);
264 }
265
266 String Pasteboard::readOrigin()
267 {
268     return readCustomData().origin;
269 }
270
271 const PasteboardCustomData& Pasteboard::readCustomData()
272 {
273     if (m_customDataCache)
274         return *m_customDataCache;
275
276     if (auto buffer = readBufferForTypeWithSecurityCheck(PasteboardCustomData::cocoaType()))
277         m_customDataCache = PasteboardCustomData::fromSharedBuffer(*buffer);
278     else
279         m_customDataCache = PasteboardCustomData { };
280     return *m_customDataCache; 
281 }
282
283 void Pasteboard::writeCustomData(const PasteboardCustomData& data)
284 {
285     m_changeCount = platformStrategies()->pasteboardStrategy()->writeCustomData(data, m_pasteboardName);
286 }
287
288 long Pasteboard::changeCount() const
289 {
290     return platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName);
291 }
292
293 Vector<String> Pasteboard::readTypesWithSecurityCheck()
294 {
295     Vector<String> cocoaTypes;
296     platformStrategies()->pasteboardStrategy()->getTypes(cocoaTypes, m_pasteboardName);
297
298     // Enforce changeCount ourselves for security. We check after reading instead of before to be
299     // sure it doesn't change between our testing the change count and accessing the data.
300     if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
301         return { };
302
303     return cocoaTypes;
304 }
305
306 RefPtr<SharedBuffer> Pasteboard::readBufferForTypeWithSecurityCheck(const String& type)
307 {
308     auto buffer = platformStrategies()->pasteboardStrategy()->bufferForType(type, m_pasteboardName);
309
310     // Enforce changeCount ourselves for security. We check after reading instead of before to be
311     // sure it doesn't change between our testing the change count and accessing the data.
312     if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
313         return nullptr;
314
315     return buffer;
316 }
317
318 }