Title: [248243] releases/WebKitGTK/webkit-2.24
Revision
248243
Author
[email protected]
Date
2019-08-03 20:23:39 -0700 (Sat, 03 Aug 2019)

Log Message

Merge r246868 - ReplacementFragment should not have script observable side effects
https://bugs.webkit.org/show_bug.cgi?id=199147

Reviewed by Wenson Hsieh.

Source/WebCore:

Fixed the bug that ReplacementFragment has script observable side effects.

Use a brand new document for sanitization where the script is disabled for test rendering,
and remove style and script elements as well as event handlers before the test rendering
and the actual pasting.

Test: editing/pasteboard/paste-contents-with-side-effects.html

* editing/ReplaceSelectionCommand.cpp:
(WebCore::ReplacementFragment::document): Deleted.
(WebCore::ReplacementFragment::ReplacementFragment): Use createPageForSanitizingWebContent
to create our own document for test rendering. We need to copy over the computed style
from the root editable element (editing host) to respect whitespace treatment, etc...
(WebCore::ReplacementFragment::removeContentsWithSideEffects): Moved from removeHeadContents.
Now removes event handlers and _javascript_ URLs.
(WebCore::ReplacementFragment::insertFragmentForTestRendering): Renamed variable names.
(WebCore::ReplaceSelectionCommand::willApplyCommand): Create the plain text and HTML markup
for beforeinput and input events before ReplacementFragment removes contents with side effects.
(WebCore::ReplaceSelectionCommand::ensureReplacementFragment): The removal of head elements
is now done in ReplacementFragment's constructor.

LayoutTests:

Added regression tests.

* editing/pasteboard/paste-contents-with-side-effects-expected.txt: Added.
* editing/pasteboard/paste-contents-with-side-effects.html: Added.

Modified Paths

Added Paths

Diff

Modified: releases/WebKitGTK/webkit-2.24/LayoutTests/ChangeLog (248242 => 248243)


--- releases/WebKitGTK/webkit-2.24/LayoutTests/ChangeLog	2019-08-04 03:23:36 UTC (rev 248242)
+++ releases/WebKitGTK/webkit-2.24/LayoutTests/ChangeLog	2019-08-04 03:23:39 UTC (rev 248243)
@@ -1,3 +1,15 @@
+2019-06-26  Ryosuke Niwa  <[email protected]>
+
+        ReplacementFragment should not have script observable side effects
+        https://bugs.webkit.org/show_bug.cgi?id=199147
+
+        Reviewed by Wenson Hsieh.
+
+        Added regression tests.
+
+        * editing/pasteboard/paste-contents-with-side-effects-expected.txt: Added.
+        * editing/pasteboard/paste-contents-with-side-effects.html: Added.
+
 2019-06-03  Yusuke Suzuki  <[email protected]>
 
         [JSC] JSObject::attemptToInterceptPutByIndexOnHole should use getPrototype instead of getPrototypeDirect

Added: releases/WebKitGTK/webkit-2.24/LayoutTests/editing/pasteboard/paste-contents-with-side-effects-expected.txt (0 => 248243)


--- releases/WebKitGTK/webkit-2.24/LayoutTests/editing/pasteboard/paste-contents-with-side-effects-expected.txt	                        (rev 0)
+++ releases/WebKitGTK/webkit-2.24/LayoutTests/editing/pasteboard/paste-contents-with-side-effects-expected.txt	2019-08-04 03:23:39 UTC (rev 248243)
@@ -0,0 +1,22 @@
+This tests inserting content with an event handler. WebKit should never execute event handlers.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Inserting with load event handler
+PASS event.dataTransfer.getData('text/html').includes('_onload_="') is true
+PASS event.dataTransfer.getData('text/html').includes('_onmouseover_="') is true
+PASS didExecute is false
+
+Inserting with script element
+PASS event.dataTransfer.getData('text/html').includes('script') is true
+PASS didExecute is false
+
+Inserting iframe with a name into plaintext-only
+PASS event.dataTransfer.getData("text/html").includes("iframe name=") is true
+PASS didExecute is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: releases/WebKitGTK/webkit-2.24/LayoutTests/editing/pasteboard/paste-contents-with-side-effects.html (0 => 248243)


--- releases/WebKitGTK/webkit-2.24/LayoutTests/editing/pasteboard/paste-contents-with-side-effects.html	                        (rev 0)
+++ releases/WebKitGTK/webkit-2.24/LayoutTests/editing/pasteboard/paste-contents-with-side-effects.html	2019-08-04 03:23:39 UTC (rev 248243)
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<div id="editor" contenteditable></div>
+<script>
+
+description('This tests inserting content with an event handler. WebKit should never execute event handlers.');
+
+editor.focus();
+
+function insertHTML(markup) {
+    editor.textContent = '';
+    editor.focus();
+    document.execCommand('insertHTML', false, markup);
+}
+
+let didExecute = false;
+debug('');
+debug('Inserting with load event handler');
+editor.addEventListener('beforeinput', () => {
+    shouldBeTrue(`event.dataTransfer.getData('text/html').includes('_onload_="')`);
+    shouldBeTrue(`event.dataTransfer.getData('text/html').includes('_onmouseover_="')`);
+}, {once: true});
+insertHTML('<iframe _onload_="didExecute = true" _onmouseover_="alert(\'FAIL\')"></iframe>');
+shouldBeFalse('didExecute');
+
+didExecute = false;
+debug('');
+debug('Inserting with script element');
+editor.addEventListener('beforeinput', () => {
+    shouldBeTrue(`event.dataTransfer.getData('text/html').includes('script')`);
+}, {once: true});
+insertHTML(`<iframe src="" html><b>hi</b><script>alert("FAIL")</scr` + 'ipt>"></iframe>');
+shouldBeFalse('didExecute');
+
+didExecute = false;
+debug('');
+debug('Inserting iframe with a name into plaintext-only');
+editor.setAttribute('contenteditable', 'plaintext-only');
+
+let i = 0;
+function insertObjectElement() {
+    const object = document.createElement('object');
+    object.data = '';
+    object._onload_ = () => {
+        try {
+            if (window.open('about:blank', 'named-frame').frameElement.parentNode)
+                didExecute = true;
+        } catch (e) { }
+        if (!didExecute)
+            insertObjectElement();
+    }
+    document.body.appendChild(object);
+}
+insertObjectElement();
+editor.focus();
+editor.addEventListener('beforeinput', () => {
+    shouldBeTrue(`event.dataTransfer.getData("text/html").includes("iframe name=")`);
+}, {once: true});
+insertHTML(`<iframe name='named-frame'></iframe>`);
+shouldBeFalse('didExecute');
+didExecute = true;
+
+</script>
+</body>
+</html>

Modified: releases/WebKitGTK/webkit-2.24/Source/WebCore/ChangeLog (248242 => 248243)


--- releases/WebKitGTK/webkit-2.24/Source/WebCore/ChangeLog	2019-08-04 03:23:36 UTC (rev 248242)
+++ releases/WebKitGTK/webkit-2.24/Source/WebCore/ChangeLog	2019-08-04 03:23:39 UTC (rev 248243)
@@ -1,3 +1,31 @@
+2019-06-26  Ryosuke Niwa  <[email protected]>
+
+        ReplacementFragment should not have script observable side effects
+        https://bugs.webkit.org/show_bug.cgi?id=199147
+
+        Reviewed by Wenson Hsieh.
+
+        Fixed the bug that ReplacementFragment has script observable side effects.
+
+        Use a brand new document for sanitization where the script is disabled for test rendering,
+        and remove style and script elements as well as event handlers before the test rendering
+        and the actual pasting.
+
+        Test: editing/pasteboard/paste-contents-with-side-effects.html
+
+        * editing/ReplaceSelectionCommand.cpp:
+        (WebCore::ReplacementFragment::document): Deleted.
+        (WebCore::ReplacementFragment::ReplacementFragment): Use createPageForSanitizingWebContent
+        to create our own document for test rendering. We need to copy over the computed style
+        from the root editable element (editing host) to respect whitespace treatment, etc...
+        (WebCore::ReplacementFragment::removeContentsWithSideEffects): Moved from removeHeadContents.
+        Now removes event handlers and _javascript_ URLs.
+        (WebCore::ReplacementFragment::insertFragmentForTestRendering): Renamed variable names.
+        (WebCore::ReplaceSelectionCommand::willApplyCommand): Create the plain text and HTML markup
+        for beforeinput and input events before ReplacementFragment removes contents with side effects.
+        (WebCore::ReplaceSelectionCommand::ensureReplacementFragment): The removal of head elements
+        is now done in ReplacementFragment's constructor.
+
 2019-06-25  Keith Miller  <[email protected]>
 
         Add didBecomePrototype() calls to global context prototypes

Modified: releases/WebKitGTK/webkit-2.24/Source/WebCore/editing/ReplaceSelectionCommand.cpp (248242 => 248243)


--- releases/WebKitGTK/webkit-2.24/Source/WebCore/editing/ReplaceSelectionCommand.cpp	2019-08-04 03:23:36 UTC (rev 248242)
+++ releases/WebKitGTK/webkit-2.24/Source/WebCore/editing/ReplaceSelectionCommand.cpp	2019-08-04 03:23:39 UTC (rev 248243)
@@ -31,6 +31,7 @@
 #include "ApplyStyleCommand.h"
 #include "BeforeTextInsertedEvent.h"
 #include "BreakBlockquoteCommand.h"
+#include "CSSComputedStyleDeclaration.h"
 #include "CSSStyleDeclaration.h"
 #include "DOMWrapperWorld.h"
 #include "DataTransfer.h"
@@ -43,6 +44,7 @@
 #include "FrameSelection.h"
 #include "HTMLBRElement.h"
 #include "HTMLBaseElement.h"
+#include "HTMLBodyElement.h"
 #include "HTMLInputElement.h"
 #include "HTMLLIElement.h"
 #include "HTMLLinkElement.h"
@@ -54,6 +56,7 @@
 #include "NodeRenderStyle.h"
 #include "RenderInline.h"
 #include "RenderText.h"
+#include "ScriptElement.h"
 #include "SimplifyMarkupCommand.h"
 #include "SmartReplace.h"
 #include "StyleProperties.h"
@@ -70,8 +73,6 @@
 
 enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
 
-static void removeHeadContents(ReplacementFragment&);
-
 // --- ReplacementFragment helper class
 
 class ReplacementFragment {
@@ -78,7 +79,7 @@
     WTF_MAKE_FAST_ALLOCATED;
     WTF_MAKE_NONCOPYABLE(ReplacementFragment);
 public:
-    ReplacementFragment(Document&, DocumentFragment*, const VisibleSelection&);
+    ReplacementFragment(DocumentFragment*, const VisibleSelection&);
 
     DocumentFragment* fragment() { return m_fragment.get(); }
 
@@ -94,6 +95,7 @@
     void removeNodePreservingChildren(Node&);
 
 private:
+    void removeContentsWithSideEffects();
     Ref<HTMLElement> insertFragmentForTestRendering(Node* rootEditableNode);
     void removeUnrenderedNodes(Node*);
     void restoreAndRemoveTestRenderingNodesToFragment(StyledElement*);
@@ -101,9 +103,6 @@
     
     void insertNodeBefore(Node&, Node& refNode);
 
-    Document& document() { return *m_document; }
-
-    RefPtr<Document> m_document;
     RefPtr<DocumentFragment> m_fragment;
     bool m_hasInterchangeNewlineAtStart;
     bool m_hasInterchangeNewlineAtEnd;
@@ -151,9 +150,8 @@
     return position;
 }
 
-ReplacementFragment::ReplacementFragment(Document& document, DocumentFragment* fragment, const VisibleSelection& selection)
-    : m_document(&document)
-    , m_fragment(fragment)
+ReplacementFragment::ReplacementFragment(DocumentFragment* fragment, const VisibleSelection& selection)
+    : m_fragment(fragment)
     , m_hasInterchangeNewlineAtStart(false)
     , m_hasInterchangeNewlineAtEnd(false)
 {
@@ -161,12 +159,14 @@
         return;
     if (!m_fragment->firstChild())
         return;
-    
+
+    removeContentsWithSideEffects();
+
     RefPtr<Element> editableRoot = selection.rootEditableElement();
     ASSERT(editableRoot);
     if (!editableRoot)
         return;
-    
+
     auto* shadowHost = editableRoot->shadowHost();
     if (!editableRoot->attributeEventListener(eventNames().webkitBeforeTextInsertedEvent, mainThreadNormalWorld())
         && !(shadowHost && shadowHost->renderer() && shadowHost->renderer()->isTextControl())
@@ -175,7 +175,14 @@
         return;
     }
 
-    RefPtr<StyledElement> holder = insertFragmentForTestRendering(editableRoot.get());
+    auto page = createPageForSanitizingWebContent();
+    Document* stagingDocument = page->mainFrame().document();
+    ASSERT(stagingDocument->body());
+
+    ComputedStyleExtractor computedStyleOfEditableRoot(editableRoot.get());
+    stagingDocument->body()->setAttributeWithoutSynchronization(styleAttr, computedStyleOfEditableRoot.copyProperties()->asText());
+
+    RefPtr<StyledElement> holder = insertFragmentForTestRendering(stagingDocument->body());
     if (!holder) {
         removeInterchangeNodes(m_fragment.get());
         return;
@@ -202,7 +209,7 @@
         if (!m_fragment->firstChild())
             return;
 
-        holder = insertFragmentForTestRendering(editableRoot.get());
+        holder = insertFragmentForTestRendering(stagingDocument->body());
         removeInterchangeNodes(holder.get());
         removeUnrenderedNodes(holder.get());
         restoreAndRemoveTestRenderingNodesToFragment(holder.get());
@@ -209,6 +216,37 @@
     }
 }
 
+void ReplacementFragment::removeContentsWithSideEffects()
+{
+    Vector<Ref<Element>> elementsToRemove;
+    Vector<std::pair<Ref<Element>, QualifiedName>> attributesToRemove;
+
+    auto it = descendantsOfType<Element>(*m_fragment).begin();
+    auto end = descendantsOfType<Element>(*m_fragment).end();
+    while (it != end) {
+        auto element = makeRef(*it);
+        if (isScriptElement(element) || (is<HTMLStyleElement>(element) && element->getAttribute(classAttr) != WebKitMSOListQuirksStyle)
+            || is<HTMLBaseElement>(element) || is<HTMLLinkElement>(element) || is<HTMLMetaElement>(element) || is<HTMLTitleElement>(element)) {
+            elementsToRemove.append(WTFMove(element));
+            it.traverseNextSkippingChildren();
+            continue;
+        }
+        if (element->hasAttributes()) {
+            for (auto& attribute : element->attributesIterator()) {
+                if (element->isEventHandlerAttribute(attribute) || element->isJavaScriptURLAttribute(attribute))
+                    attributesToRemove.append({ element.copyRef(), attribute.name() });
+            }
+        }
+        ++it;
+    }
+
+    for (auto& element : elementsToRemove)
+        removeNode(WTFMove(element));
+
+    for (auto& item : attributesToRemove)
+        item.first->removeAttribute(item.second);
+}
+
 bool ReplacementFragment::isEmpty() const
 {
     return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd;
@@ -252,13 +290,14 @@
     parent->insertBefore(node, &refNode);
 }
 
-Ref<HTMLElement> ReplacementFragment::insertFragmentForTestRendering(Node* rootEditableElement)
+Ref<HTMLElement> ReplacementFragment::insertFragmentForTestRendering(Node* rootNode)
 {
-    auto holder = createDefaultParagraphElement(document());
+    auto document = makeRef(rootNode->document());
+    auto holder = createDefaultParagraphElement(document.get());
 
     holder->appendChild(*m_fragment);
-    rootEditableElement->appendChild(holder);
-    document().updateLayoutIgnorePendingStylesheets();
+    rootNode->appendChild(holder);
+    document->updateLayoutIgnorePendingStylesheets();
 
     return holder;
 }
@@ -726,29 +765,6 @@
     return m_startOfInsertedContent;
 }
 
-static void removeHeadContents(ReplacementFragment& fragment)
-{
-    if (fragment.isEmpty())
-        return;
-
-    Vector<Element*> toRemove;
-
-    auto it = descendantsOfType<Element>(*fragment.fragment()).begin();
-    auto end = descendantsOfType<Element>(*fragment.fragment()).end();
-    while (it != end) {
-        if (is<HTMLBaseElement>(*it) || is<HTMLLinkElement>(*it) || is<HTMLMetaElement>(*it) || is<HTMLTitleElement>(*it)
-            || (is<HTMLStyleElement>(*it) && it->getAttribute(classAttr) != WebKitMSOListQuirksStyle)) {
-            toRemove.append(&*it);
-            it.traverseNextSkippingChildren();
-            continue;
-        }
-        ++it;
-    }
-
-    for (auto& element : toRemove)
-        fragment.removeNode(*element);
-}
-
 // Remove style spans before insertion if they are unnecessary.  It's faster because we'll 
 // avoid doing a layout.
 static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const Position& insertionPos)
@@ -919,9 +935,9 @@
 
 bool ReplaceSelectionCommand::willApplyCommand()
 {
-    ensureReplacementFragment();
     m_documentFragmentPlainText = m_documentFragment->textContent();
     m_documentFragmentHTMLMarkup = serializeFragment(*m_documentFragment, SerializedNodes::SubtreeIncludingNode);
+    ensureReplacementFragment();
     return CompositeEditCommand::willApplyCommand();
 }
 
@@ -1544,11 +1560,8 @@
 
 ReplacementFragment* ReplaceSelectionCommand::ensureReplacementFragment()
 {
-    if (!m_replacementFragment) {
-        m_replacementFragment = std::make_unique<ReplacementFragment>(document(), m_documentFragment.get(), endingSelection());
-        removeHeadContents(*m_replacementFragment);
-    }
-
+    if (!m_replacementFragment)
+        m_replacementFragment = std::make_unique<ReplacementFragment>(m_documentFragment.get(), endingSelection());
     return m_replacementFragment.get();
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to