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