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();
}