[Attachment Support] Create attachment elements when dropping files on iOS
[WebKit-https.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)
38 #include <MobileCoreServices/MobileCoreServices.h>
39 #endif
40
41 namespace WebCore {
42
43 #if PLATFORM(MAC)
44 static NSBitmapImageFileType bitmapPNGFileType()
45 {
46 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
47     return NSBitmapImageFileTypePNG;
48 #else
49     return NSPNGFileType;
50 #endif
51 }
52 #endif // PLATFORM(MAC)
53
54 // Making this non-inline so that WebKit 2's decoding doesn't have to include SharedBuffer.h.
55 PasteboardWebContent::PasteboardWebContent() = default;
56 PasteboardWebContent::~PasteboardWebContent() = default;
57
58 const char* PasteboardCustomData::cocoaType()
59 {
60     return "com.apple.WebKit.custom-pasteboard-data";
61 }
62
63 enum class ImageType {
64     Invalid = 0,
65     TIFF,
66     PNG,
67     JPEG,
68     GIF,
69 };
70
71 static ImageType cocoaTypeToImageType(const String& cocoaType)
72 {
73 #if PLATFORM(MAC)
74     if (cocoaType == String(legacyTIFFPasteboardType()))
75         return ImageType::TIFF;
76 #endif
77     if (cocoaType == String(kUTTypeTIFF))
78         return ImageType::TIFF;
79 #if PLATFORM(MAC)
80     if (cocoaType == "Apple PNG pasteboard type") // NSPNGPboardType
81         return ImageType::PNG;
82 #endif
83     if (cocoaType == String(kUTTypePNG))
84         return ImageType::PNG;
85     if (cocoaType == String(kUTTypeJPEG))
86         return ImageType::JPEG;
87     if (cocoaType == String(kUTTypeGIF))
88         return ImageType::GIF;
89     return ImageType::Invalid;
90 }
91
92 // String literals returned by this function must be defined exactly once
93 // since read(PasteboardFileReader&) uses HashMap<const char*> to check uniqueness.
94 static const char* imageTypeToMIMEType(ImageType type)
95 {
96     switch (type) {
97     case ImageType::Invalid:
98         return nullptr;
99     case ImageType::TIFF:
100 #if PLATFORM(MAC)
101         return "image/png"; // For Web compatibility, we pretend to have PNG instead.
102 #else
103         return nullptr; // Don't support pasting TIFF on iOS for now.
104 #endif
105     case ImageType::PNG:
106         return "image/png";
107     case ImageType::JPEG:
108         return "image/jpeg";
109     case ImageType::GIF:
110         return "image/gif";
111     }
112 }
113
114 static const char* imageTypeToFakeFilename(ImageType type)
115 {
116     switch (type) {
117     case ImageType::Invalid:
118         ASSERT_NOT_REACHED();
119         return nullptr;
120     case ImageType::TIFF:
121 #if PLATFORM(MAC)
122         return "image.png"; // For Web compatibility, we pretend to have PNG instead.
123 #else
124         ASSERT_NOT_REACHED();
125         return nullptr;
126 #endif
127     case ImageType::PNG:
128         return "image.png";
129     case ImageType::JPEG:
130         return "image.jpeg";
131     case ImageType::GIF:
132         return "image.gif";
133     }
134 }
135
136 bool Pasteboard::shouldTreatCocoaTypeAsFile(const String& cocoaType)
137 {
138     return cocoaTypeToImageType(cocoaType) != ImageType::Invalid;
139 }
140
141 bool Pasteboard::containsFiles()
142 {
143     if (!platformStrategies()->pasteboardStrategy()->getNumberOfFiles(m_pasteboardName)) {
144         Vector<String> cocoaTypes;
145         platformStrategies()->pasteboardStrategy()->getTypes(cocoaTypes, m_pasteboardName);
146         if (cocoaTypes.findMatching([](const String& cocoaType) { return shouldTreatCocoaTypeAsFile(cocoaType); }) == notFound)
147             return false;
148     }
149
150     // Enforce changeCount ourselves for security. We check after reading instead of before to be
151     // sure it doesn't change between our testing the change count and accessing the data.
152     if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
153         return false;
154
155     return true;
156 }
157
158 Vector<String> Pasteboard::typesSafeForBindings(const String& origin)
159 {
160     Vector<String> types = platformStrategies()->pasteboardStrategy()->typesSafeForDOMToReadAndWrite(m_pasteboardName, origin);
161
162     // Enforce changeCount ourselves for security. We check after reading instead of before to be
163     // sure it doesn't change between our testing the change count and accessing the data.
164     if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
165         return { };
166
167     return types;
168 }
169
170 Vector<String> Pasteboard::typesForLegacyUnsafeBindings()
171 {
172     auto cocoaTypes = readTypesWithSecurityCheck();
173     if (cocoaTypes.isEmpty())
174         return cocoaTypes;
175
176     ListHashSet<String> result;
177     for (auto& cocoaType : cocoaTypes)
178         addHTMLClipboardTypesForCocoaType(result, cocoaType);
179
180     return copyToVector(result);
181 }
182
183 #if PLATFORM(MAC)
184 static Ref<SharedBuffer> convertTIFFToPNG(SharedBuffer& tiffBuffer)
185 {
186     auto image = adoptNS([[NSBitmapImageRep alloc] initWithData: tiffBuffer.createNSData().get()]);
187     NSData *pngData = [image representationUsingType:bitmapPNGFileType() properties:@{ }];
188     return SharedBuffer::create(pngData);
189 }
190 #endif
191
192 void Pasteboard::read(PasteboardFileReader& reader)
193 {
194     auto filenames = readFilePaths();
195     if (!filenames.isEmpty()) {
196         for (auto& filename : filenames)
197             reader.readFilename(filename);
198         return;
199     }
200
201     auto cocoaTypes = readTypesWithSecurityCheck();
202     HashSet<const char*> existingMIMEs;
203     for (auto& cocoaType : cocoaTypes) {
204         auto imageType = cocoaTypeToImageType(cocoaType);
205         const char* mimeType = imageTypeToMIMEType(imageType);
206         if (!mimeType)
207             continue;
208         if (existingMIMEs.contains(mimeType))
209             continue;
210         auto buffer = readBufferForTypeWithSecurityCheck(cocoaType);
211 #if PLATFORM(MAC)
212         if (buffer && imageType == ImageType::TIFF)
213             buffer = convertTIFFToPNG(buffer.releaseNonNull());
214 #endif
215         if (!buffer)
216             continue;
217         existingMIMEs.add(mimeType);
218         reader.readBuffer(imageTypeToFakeFilename(imageType), mimeType, buffer.releaseNonNull());
219     }
220 }
221
222 String Pasteboard::readString(const String& type)
223 {
224     return readPlatformValueAsString(type, m_changeCount, m_pasteboardName);
225 }
226
227 String Pasteboard::readStringInCustomData(const String& type)
228 {
229     return readCustomData().sameOriginCustomData.get(type);
230 }
231
232 String Pasteboard::readOrigin()
233 {
234     return readCustomData().origin;
235 }
236
237 const PasteboardCustomData& Pasteboard::readCustomData()
238 {
239     if (m_customDataCache)
240         return *m_customDataCache;
241
242     if (auto buffer = readBufferForTypeWithSecurityCheck(PasteboardCustomData::cocoaType()))
243         m_customDataCache = PasteboardCustomData::fromSharedBuffer(*buffer);
244     else
245         m_customDataCache = PasteboardCustomData { };
246     return *m_customDataCache; 
247 }
248
249 void Pasteboard::writeCustomData(const PasteboardCustomData& data)
250 {
251     m_changeCount = platformStrategies()->pasteboardStrategy()->writeCustomData(data, m_pasteboardName);
252 }
253
254 long Pasteboard::changeCount() const
255 {
256     return platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName);
257 }
258
259 Vector<String> Pasteboard::readTypesWithSecurityCheck()
260 {
261     Vector<String> cocoaTypes;
262     platformStrategies()->pasteboardStrategy()->getTypes(cocoaTypes, m_pasteboardName);
263
264     // Enforce changeCount ourselves for security. We check after reading instead of before to be
265     // sure it doesn't change between our testing the change count and accessing the data.
266     if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
267         return { };
268
269     return cocoaTypes;
270 }
271
272 RefPtr<SharedBuffer> Pasteboard::readBufferForTypeWithSecurityCheck(const String& type)
273 {
274     auto buffer = platformStrategies()->pasteboardStrategy()->bufferForType(type, m_pasteboardName);
275
276     // Enforce changeCount ourselves for security. We check after reading instead of before to be
277     // sure it doesn't change between our testing the change count and accessing the data.
278     if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
279         return nullptr;
280
281     return buffer;
282 }
283
284 }