Don't convert pasted content to use blob URL in WebKit1
[WebKit-https.git] / Source / WebCore / editing / cocoa / WebContentReaderCocoa.mm
1 /*
2  * Copyright (C) 2006-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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WebContentReader.h"
28
29 #import "ArchiveResource.h"
30 #import "Blob.h"
31 #import "BlobURL.h"
32 #import "CachedResourceLoader.h"
33 #import "DOMURL.h"
34 #import "DeprecatedGlobalSettings.h"
35 #import "Document.h"
36 #import "DocumentFragment.h"
37 #import "DocumentLoader.h"
38 #import "File.h"
39 #import "Frame.h"
40 #import "FrameLoader.h"
41 #import "FrameLoaderClient.h"
42 #import "HTMLAttachmentElement.h"
43 #import "HTMLBodyElement.h"
44 #import "HTMLIFrameElement.h"
45 #import "HTMLImageElement.h"
46 #import "HTMLObjectElement.h"
47 #import "LegacyWebArchive.h"
48 #import "MainFrame.h"
49 #import "Page.h"
50 #import "PublicURLManager.h"
51 #import "RuntimeEnabledFeatures.h"
52 #import "Settings.h"
53 #import "SocketProvider.h"
54 #import "TypedElementDescendantIterator.h"
55 #import "URLParser.h"
56 #import "WebArchiveResourceFromNSAttributedString.h"
57 #import "WebArchiveResourceWebResourceHandler.h"
58 #import "WebNSAttributedStringExtras.h"
59 #import "markup.h"
60 #import <pal/spi/cocoa/NSAttributedStringSPI.h>
61 #import <wtf/SoftLinking.h>
62 #import <wtf/UUID.h>
63
64 #if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300)
65 @interface NSAttributedString ()
66 - (NSString *)_htmlDocumentFragmentString:(NSRange)range documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
67 @end
68 #elif PLATFORM(IOS)
69 SOFT_LINK_PRIVATE_FRAMEWORK(WebKitLegacy)
70 #elif PLATFORM(MAC)
71 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(WebKit, WebKitLegacy)
72 #endif
73
74 #if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED < 110000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101300)
75 SOFT_LINK(WebKitLegacy, _WebCreateFragment, void, (WebCore::Document& document, NSAttributedString *string, WebCore::FragmentAndResources& result), (document, string, result))
76 #endif
77
78 namespace WebCore {
79
80 #if ENABLE(ATTACHMENT_ELEMENT)
81
82 struct AttachmentReplacementInfo {
83     AttachmentDisplayMode displayMode;
84     Ref<File> file;
85     Ref<Element> elementToReplace;
86 };
87
88 void replaceRichContentWithAttachmentsIfNecessary(DocumentFragment& fragment, BlobReplacementInfo&& replacementInfo)
89 {
90     if (!RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled() || replacementInfo.isEmpty())
91         return;
92
93     Vector<Ref<Element>> elementsToRemove;
94     Vector<AttachmentReplacementInfo> attachmentReplacementInfo;
95     for (auto& image : descendantsOfType<HTMLImageElement>(fragment)) {
96         auto url = image.attributeWithoutSynchronization(HTMLNames::srcAttr);
97         if (url.isEmpty())
98             continue;
99
100         auto blob = replacementInfo.blobURLToBlobMap.get(url);
101         if (!blob)
102             continue;
103
104         auto title = URLParser { replacementInfo.blobURLToReplacedURLMap.get(url) }.result().lastPathComponent();
105         if (title.isEmpty())
106             title = AtomicString("media");
107
108         attachmentReplacementInfo.append({ AttachmentDisplayMode::InPlace, File::create(*blob, title), image });
109     }
110
111     for (auto& object : descendantsOfType<HTMLObjectElement>(fragment)) {
112         auto url = object.attributeWithoutSynchronization(HTMLNames::dataAttr);
113         if (url.isEmpty()) {
114             elementsToRemove.append(object);
115             continue;
116         }
117
118         auto blob = replacementInfo.blobURLToBlobMap.get(url);
119         if (!blob) {
120             elementsToRemove.append(object);
121             continue;
122         }
123
124         auto title = URLParser { replacementInfo.blobURLToReplacedURLMap.get(url) }.result().lastPathComponent();
125         if (title.isEmpty())
126             title = AtomicString("file");
127
128         attachmentReplacementInfo.append({ AttachmentDisplayMode::AsIcon, File::create(*blob, title), object });
129     }
130
131     for (auto& info : attachmentReplacementInfo) {
132         auto file = WTFMove(info.file);
133         auto elementToReplace = WTFMove(info.elementToReplace);
134         auto parent = makeRefPtr(elementToReplace->parentNode());
135         if (!parent)
136             continue;
137
138         auto attachment = HTMLAttachmentElement::create(HTMLNames::attachmentTag, fragment.document());
139         attachment->setUniqueIdentifier(createCanonicalUUIDString());
140         attachment->setFile(WTFMove(file), HTMLAttachmentElement::UpdateDisplayAttributes::Yes);
141         attachment->updateDisplayMode(info.displayMode);
142         parent->replaceChild(attachment, elementToReplace);
143     }
144
145     for (auto& elementToRemove : elementsToRemove)
146         elementToRemove->remove();
147 }
148
149 #else
150
151 void replaceRichContentWithAttachmentsIfNecessary(DocumentFragment&, HashMap<AtomicString, RefPtr<Blob>>&&)
152 {
153 }
154
155 #endif
156
157 #if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300)
158
159 static NSDictionary *attributesForAttributedStringConversion()
160 {
161     // This function needs to be kept in sync with identically named one in WebKitLegacy, which is used on older OS versions.
162     RetainPtr<NSMutableArray> excludedElements = adoptNS([[NSMutableArray alloc] initWithObjects:
163         // Omit style since we want style to be inline so the fragment can be easily inserted.
164         @"style",
165         // Omit xml so the result is not XHTML.
166         @"xml",
167         // Omit tags that will get stripped when converted to a fragment anyway.
168         @"doctype", @"html", @"head", @"body",
169         // Omit deprecated tags.
170         @"applet", @"basefont", @"center", @"dir", @"font", @"menu", @"s", @"strike", @"u",
171 #if !ENABLE(ATTACHMENT_ELEMENT)
172         // Omit object so no file attachments are part of the fragment.
173         @"object",
174 #endif
175         nil]);
176
177 #if ENABLE(ATTACHMENT_ELEMENT)
178     if (!RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled())
179         [excludedElements addObject:@"object"];
180 #endif
181
182 #if PLATFORM(IOS)
183     static NSString * const NSExcludedElementsDocumentAttribute = @"ExcludedElements";
184 #endif
185
186     return @{
187         NSExcludedElementsDocumentAttribute: excludedElements.get(),
188         @"InterchangeNewline": @YES,
189         @"CoalesceTabSpans": @YES,
190         @"OutputBaseURL": [(NSURL *)URL::fakeURLWithRelativePart(emptyString()) retain], // The value needs +1 refcount, as NSAttributedString over-releases it.
191         @"WebResourceHandler": [[WebArchiveResourceWebResourceHandler new] autorelease],
192     };
193 }
194
195 static FragmentAndResources createFragment(Frame& frame, NSAttributedString *string)
196 {
197     FragmentAndResources result;
198     Document& document = *frame.document();
199
200     NSArray *subresources = nil;
201     NSString *fragmentString = [string _htmlDocumentFragmentString:NSMakeRange(0, [string length]) documentAttributes:attributesForAttributedStringConversion() subresources:&subresources];
202     auto fragment = DocumentFragment::create(document);
203     fragment->parseHTML(fragmentString, document.body(), DisallowScriptingAndPluginContent);
204
205     result.fragment = WTFMove(fragment);
206     for (WebArchiveResourceFromNSAttributedString *resource in subresources)
207         result.resources.append(*resource->resource);
208
209     return result;
210 }
211
212 #else
213
214 static FragmentAndResources createFragment(Frame& frame, NSAttributedString *string)
215 {
216     FragmentAndResources result;
217     _WebCreateFragment(*frame.document(), string, result);
218     return result;
219 }
220
221 #endif
222
223 class DeferredLoadingScope {
224 public:
225     DeferredLoadingScope(Frame& frame)
226         : m_frame(frame)
227         , m_cachedResourceLoader(frame.document()->cachedResourceLoader())
228     {
229         if (!frame.page()->defersLoading()) {
230             frame.page()->setDefersLoading(true);
231             m_didEnabledDeferredLoading = true;
232         }
233
234         if (m_cachedResourceLoader->imagesEnabled()) {
235             m_cachedResourceLoader->setImagesEnabled(false);
236             m_didDisableImage = true;
237         }
238     }
239
240     ~DeferredLoadingScope()
241     {
242         if (m_didEnabledDeferredLoading)
243             m_cachedResourceLoader->setImagesEnabled(true);
244         if (m_didDisableImage)
245             m_frame->page()->setDefersLoading(false);
246     }
247
248 private:
249     Ref<Frame> m_frame;
250     Ref<CachedResourceLoader> m_cachedResourceLoader;
251     bool m_didEnabledDeferredLoading { false };
252     bool m_didDisableImage { false };
253 };
254
255 RefPtr<DocumentFragment> createFragmentAndAddResources(Frame& frame, NSAttributedString *string, BlobReplacementInfo& replacementInfo)
256 {
257     if (!frame.page() || !frame.document())
258         return nullptr;
259
260     auto& document = *frame.document();
261     if (!document.isHTMLDocument() || !string)
262         return nullptr;
263
264     DeferredLoadingScope scope(frame);
265     auto fragmentAndResources = createFragment(frame, string);
266     if (!fragmentAndResources.fragment)
267         return nullptr;
268
269     if (!DeprecatedGlobalSettings::customPasteboardDataEnabled()) {
270         if (DocumentLoader* loader = frame.loader().documentLoader()) {
271             for (auto& resource : fragmentAndResources.resources)
272                 loader->addArchiveResource(resource.copyRef());
273         }
274         return WTFMove(fragmentAndResources.fragment);
275     }
276
277     HashMap<AtomicString, AtomicString> blobURLMap;
278     for (const Ref<ArchiveResource>& subresource : fragmentAndResources.resources) {
279         auto blob = Blob::create(subresource->data(), subresource->mimeType());
280         String blobURL = DOMURL::createObjectURL(document, blob);
281         blobURLMap.set(subresource->url().string(), blobURL);
282         replacementInfo.blobURLToBlobMap.set(blobURL, WTFMove(blob));
283         replacementInfo.blobURLToReplacedURLMap.set(blobURL, subresource->url().string());
284     }
285     replaceSubresourceURLs(*fragmentAndResources.fragment, WTFMove(blobURLMap));
286
287     return WTFMove(fragmentAndResources.fragment);
288 }
289
290 struct MarkupAndArchive {
291     String markup;
292     Ref<ArchiveResource> mainResource;
293     Ref<Archive> archive;
294 };
295
296 static std::optional<MarkupAndArchive> extractMarkupAndArchive(SharedBuffer& buffer, const std::function<bool(const String)>& canShowMIMETypeAsHTML)
297 {
298     auto archive = LegacyWebArchive::create(URL(), buffer);
299     if (!archive)
300         return std::nullopt;
301
302     RefPtr<ArchiveResource> mainResource = archive->mainResource();
303     if (!mainResource)
304         return std::nullopt;
305
306     auto type = mainResource->mimeType();
307     if (!canShowMIMETypeAsHTML(type))
308         return std::nullopt;
309
310     return MarkupAndArchive { String::fromUTF8(mainResource->data().data(), mainResource->data().size()), mainResource.releaseNonNull(), archive.releaseNonNull() };
311 }
312
313 static String sanitizeMarkupWithArchive(Document& destinationDocument, MarkupAndArchive& markupAndArchive, const std::function<bool(const String)>& canShowMIMETypeAsHTML, BlobReplacementInfo& replacementInfo)
314 {
315     auto page = createPageForSanitizingWebContent();
316     Document* stagingDocument = page->mainFrame().document();
317     ASSERT(stagingDocument);
318     auto fragment = createFragmentFromMarkup(*stagingDocument, markupAndArchive.markup, markupAndArchive.mainResource->url(), DisallowScriptingAndPluginContent);
319
320     HashMap<AtomicString, AtomicString> blobURLMap;
321     for (const Ref<ArchiveResource>& subresource : markupAndArchive.archive->subresources()) {
322         auto blob = Blob::create(subresource->data(), subresource->mimeType());
323         String blobURL = DOMURL::createObjectURL(destinationDocument, blob);
324         blobURLMap.set(subresource->url().string(), blobURL);
325         replacementInfo.blobURLToBlobMap.set(blobURL, WTFMove(blob));
326         replacementInfo.blobURLToReplacedURLMap.set(blobURL, subresource->url().string());
327     }
328
329     auto contentOrigin = SecurityOrigin::create(markupAndArchive.mainResource->url());
330     for (const Ref<Archive>& subframeArchive : markupAndArchive.archive->subframeArchives()) {
331         RefPtr<ArchiveResource> subframeMainResource = subframeArchive->mainResource();
332         if (!subframeMainResource)
333             continue;
334
335         auto type = subframeMainResource->mimeType();
336         if (!canShowMIMETypeAsHTML(type))
337             continue;
338
339         auto subframeURL = subframeMainResource->url();
340         MarkupAndArchive subframeContent = { String::fromUTF8(subframeMainResource->data().data(), subframeMainResource->data().size()),
341             subframeMainResource.releaseNonNull(), subframeArchive.copyRef() };
342         auto subframeMarkup = sanitizeMarkupWithArchive(destinationDocument, subframeContent, canShowMIMETypeAsHTML, replacementInfo);
343
344         CString utf8 = subframeMarkup.utf8();
345         Vector<uint8_t> blobBuffer;
346         blobBuffer.reserveCapacity(utf8.length());
347         blobBuffer.append(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
348         auto blob = Blob::create(WTFMove(blobBuffer), type);
349
350         String subframeBlobURL = DOMURL::createObjectURL(destinationDocument, blob);
351         blobURLMap.set(subframeURL.string(), subframeBlobURL);
352         replacementInfo.blobURLToBlobMap.set(subframeBlobURL, WTFMove(blob));
353         replacementInfo.blobURLToReplacedURLMap.set(subframeBlobURL, subframeURL.string());
354     }
355
356     replaceSubresourceURLs(fragment.get(), WTFMove(blobURLMap));
357
358     auto* bodyElement = stagingDocument->body();
359     ASSERT(bodyElement);
360     bodyElement->appendChild(fragment);
361
362     auto range = Range::create(*stagingDocument);
363     range->selectNodeContents(*bodyElement);
364     return createMarkup(range.get(), nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
365 }
366
367 bool WebContentReader::readWebArchive(SharedBuffer& buffer)
368 {
369     if (frame.settings().preferMIMETypeForImages() || !frame.document())
370         return false;
371
372     DeferredLoadingScope scope(frame);
373     auto result = extractMarkupAndArchive(buffer, [&] (const String& type) {
374         return frame.loader().client().canShowMIMETypeAsHTML(type);
375     });
376     if (!result)
377         return false;
378     
379     if (!DeprecatedGlobalSettings::customPasteboardDataEnabled()) {
380         fragment = createFragmentFromMarkup(*frame.document(), result->markup, result->mainResource->url(), DisallowScriptingAndPluginContent);
381         if (DocumentLoader* loader = frame.loader().documentLoader())
382             loader->addAllArchiveResources(result->archive.get());
383         return true;
384     }
385
386     if (!shouldSanitize()) {
387         fragment = createFragmentFromMarkup(*frame.document(), result->markup, result->mainResource->url(), DisallowScriptingAndPluginContent);
388         return true;
389     }
390
391     BlobReplacementInfo replacementInfo;
392     String sanitizedMarkup = sanitizeMarkupWithArchive(*frame.document(), *result, [&] (const String& type) {
393         return frame.loader().client().canShowMIMETypeAsHTML(type);
394     }, replacementInfo);
395     fragment = createFragmentFromMarkup(*frame.document(), sanitizedMarkup, blankURL(), DisallowScriptingAndPluginContent);
396
397     if (!fragment)
398         return false;
399
400     replaceRichContentWithAttachmentsIfNecessary(*fragment, WTFMove(replacementInfo));
401     return true;
402 }
403
404 bool WebContentMarkupReader::readWebArchive(SharedBuffer& buffer)
405 {
406     if (!frame.document())
407         return false;
408
409     auto result = extractMarkupAndArchive(buffer, [&] (const String& type) {
410         return frame.loader().client().canShowMIMETypeAsHTML(type);
411     });
412     if (!result)
413         return false;
414
415     if (!shouldSanitize()) {
416         markup = result->markup;
417         return true;
418     }
419
420     BlobReplacementInfo replacementInfo;
421     markup = sanitizeMarkupWithArchive(*frame.document(), *result, [&] (const String& type) {
422         return frame.loader().client().canShowMIMETypeAsHTML(type);
423     }, replacementInfo);
424
425     return true;
426 }
427
428 static String stripMicrosoftPrefix(const String& string)
429 {
430 #if PLATFORM(MAC)
431     // This code was added to make HTML paste from Microsoft Word on Mac work, back in 2004.
432     // It's a simple-minded way to ignore the CF_HTML clipboard format, just skipping over the
433     // description part and parsing the entire context plus fragment.
434     if (string.startsWith("Version:")) {
435         size_t location = string.findIgnoringASCIICase("<html");
436         if (location != notFound)
437             return string.substring(location);
438     }
439 #endif
440     return string;
441 }
442
443 bool WebContentReader::readHTML(const String& string)
444 {
445     if (frame.settings().preferMIMETypeForImages() || !frame.document())
446         return false;
447     Document& document = *frame.document();
448
449     String stringOmittingMicrosoftPrefix = stripMicrosoftPrefix(string);
450     if (stringOmittingMicrosoftPrefix.isEmpty())
451         return false;
452
453     addFragment(createFragmentFromMarkup(document, stringOmittingMicrosoftPrefix, emptyString(), DisallowScriptingAndPluginContent));
454     return true;
455 }
456
457 bool WebContentMarkupReader::readHTML(const String& string)
458 {
459     if (!frame.document())
460         return false;
461
462     String rawHTML = stripMicrosoftPrefix(string);
463     if (shouldSanitize())
464         markup = sanitizeMarkup(rawHTML);
465     else
466         markup = rawHTML;
467
468     return !markup.isEmpty();
469 }
470
471 static RefPtr<DocumentFragment> createFragmentFromAttributedString(Frame& frame, NSAttributedString *string)
472 {
473     BlobReplacementInfo replacementInfo;
474     auto fragment = createFragmentAndAddResources(frame, string, replacementInfo);
475     if (!fragment)
476         return nullptr;
477
478     replaceRichContentWithAttachmentsIfNecessary(*fragment, WTFMove(replacementInfo));
479     return fragment;
480 }
481
482 bool WebContentReader::readRTFD(SharedBuffer& buffer)
483 {
484     if (frame.settings().preferMIMETypeForImages() || !frame.document())
485         return false;
486
487     auto string = adoptNS([[NSAttributedString alloc] initWithRTFD:buffer.createNSData().get() documentAttributes:nullptr]);
488     auto fragment = createFragmentFromAttributedString(frame, string.get());
489     if (!fragment)
490         return false;
491     addFragment(fragment.releaseNonNull());
492
493     return true;
494 }
495
496 bool WebContentMarkupReader::readRTFD(SharedBuffer& buffer)
497 {
498     if (!frame.document())
499         return false;
500     auto string = adoptNS([[NSAttributedString alloc] initWithRTFD:buffer.createNSData().get() documentAttributes:nullptr]);
501     auto fragment = createFragmentFromAttributedString(frame, string.get());
502     if (!fragment)
503         return false;
504
505     markup = createMarkup(*fragment);
506     return true;
507 }
508
509 bool WebContentReader::readRTF(SharedBuffer& buffer)
510 {
511     if (frame.settings().preferMIMETypeForImages())
512         return false;
513
514     auto string = adoptNS([[NSAttributedString alloc] initWithRTF:buffer.createNSData().get() documentAttributes:nullptr]);
515     auto fragment = createFragmentFromAttributedString(frame, string.get());
516     if (!fragment)
517         return false;
518     addFragment(fragment.releaseNonNull());
519
520     return true;
521 }
522
523 bool WebContentMarkupReader::readRTF(SharedBuffer& buffer)
524 {
525     if (!frame.document())
526         return false;
527     auto string = adoptNS([[NSAttributedString alloc] initWithRTF:buffer.createNSData().get() documentAttributes:nullptr]);
528     auto fragment = createFragmentFromAttributedString(frame, string.get());
529     if (!fragment)
530         return false;
531     markup = createMarkup(*fragment);
532     return true;
533 }
534
535 bool WebContentReader::readPlainText(const String& text)
536 {
537     if (!allowPlainText)
538         return false;
539
540     addFragment(createFragmentFromText(context, [text precomposedStringWithCanonicalMapping]));
541
542     madeFragmentFromPlainText = true;
543     return true;
544 }
545
546 bool WebContentReader::readImage(Ref<SharedBuffer>&& buffer, const String& type)
547 {
548     auto blob = Blob::create(buffer.get(), type);
549     ASSERT(frame.document());
550     auto& document = *frame.document();
551     String blobURL = DOMURL::createObjectURL(document, blob);
552     addFragment(createFragmentForImageAndURL(document, blobURL));
553
554     if (!fragment)
555         return false;
556
557     replaceRichContentWithAttachmentsIfNecessary(*fragment, {{{ blobURL, WTFMove(blob) }}, { }});
558     return true;
559 }
560
561 }