mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 520189 - Fix copy and test for the HTML editor; f=bzbarsky,dbaron r=sayrer,peterv,bzbarsky sr=roc
This commit is contained in:
parent
8003226855
commit
1d27c39d4a
@ -215,6 +215,10 @@
|
||||
#define NS_HTMLPARANOIDFRAGMENTSINK_CID \
|
||||
{ 0xa47e9526, 0x6e48, 0x4574, { 0x9d, 0x6c, 0x31, 0x64, 0xe2, 0x71, 0xf7, 0x4e } }
|
||||
|
||||
// {A47EF526-6E48-4574-9D60-3164E271F75E}
|
||||
#define NS_HTMLPARANOIDFRAGMENTSINK2_CID \
|
||||
{ 0xa47ef526, 0x6e48, 0x4574, { 0x9d, 0x60, 0x31, 0x64, 0xe2, 0x71, 0xf7, 0x5e } }
|
||||
|
||||
// {4B664E54-72A2-4bbf-A5C2-66D4DC3066A0}
|
||||
#define NS_XMLFRAGMENTSINK_CID \
|
||||
{ 0x4b664e54, 0x72a2, 0x4bbf, { 0xa5, 0xc2, 0x66, 0xd4, 0xdc, 0x30, 0x66, 0xa0 } }
|
||||
@ -227,6 +231,10 @@
|
||||
#define NS_XHTMLPARANOIDFRAGMENTSINK_CID \
|
||||
{ 0x2d78bbf0, 0xe26c, 0x482b, { 0x92, 0xb3, 0x78, 0xa7, 0xb2, 0xaf, 0xc8, 0xf7} }
|
||||
|
||||
// {AD78BBF0-E261-482B-32B3-78A7B2AFC8F7}
|
||||
#define NS_XHTMLPARANOIDFRAGMENTSINK2_CID \
|
||||
{ 0xad78bbf0, 0xe261, 0x482b, { 0x32, 0xb3, 0x78, 0xa7, 0xb2, 0xaf, 0xc8, 0xf7} }
|
||||
|
||||
// {3986B301-097C-11d3-BF87-00105A1B0627}
|
||||
#define NS_XULPOPUPLISTENER_CID \
|
||||
{ 0x3986b301, 0x97c, 0x11d3, { 0xbf, 0x87, 0x0, 0x10, 0x5a, 0x1b, 0x6, 0x27 } }
|
||||
|
@ -77,6 +77,7 @@ INCLUDES += \
|
||||
-I$(srcdir)/../../content/src \
|
||||
-I$(srcdir)/../../../../layout/style \
|
||||
-I$(srcdir)/../../../../dom/base \
|
||||
-I$(srcdir)/../../../../xpcom/io \
|
||||
$(NULL)
|
||||
|
||||
DEFINES += -D_IMPL_NS_LAYOUT
|
||||
|
@ -66,6 +66,16 @@
|
||||
#include "nsContentSink.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsCSSParser.h"
|
||||
#include "nsCSSProperty.h"
|
||||
#include "nsCSSDeclaration.h"
|
||||
#include "nsICSSStyleRule.h"
|
||||
#include "nsUnicharInputStream.h"
|
||||
#include "nsCSSStyleSheet.h"
|
||||
#include "nsICSSRuleList.h"
|
||||
#include "nsCSSDeclaration.h"
|
||||
#include "nsCSSProperty.h"
|
||||
#include "nsIDOMCSSRule.h"
|
||||
|
||||
//
|
||||
// XXX THIS IS TEMPORARY CODE
|
||||
@ -77,6 +87,9 @@
|
||||
class nsHTMLFragmentContentSink : public nsIFragmentContentSink,
|
||||
public nsIHTMLContentSink {
|
||||
public:
|
||||
/**
|
||||
* @param aAllContent Whether there is context information available for the fragment.
|
||||
*/
|
||||
nsHTMLFragmentContentSink(PRBool aAllContent = PR_FALSE);
|
||||
virtual ~nsHTMLFragmentContentSink();
|
||||
|
||||
@ -136,8 +149,6 @@ public:
|
||||
nsresult AddText(const nsAString& aString);
|
||||
nsresult FlushText();
|
||||
|
||||
nsresult Init();
|
||||
|
||||
PRPackedBool mAllContent;
|
||||
PRPackedBool mProcessing;
|
||||
PRPackedBool mSeenBody;
|
||||
@ -767,10 +778,11 @@ nsHTMLFragmentContentSink::AddAttributes(const nsIParserNode& aNode,
|
||||
// Find the whitelist of allowed elements and attributes in
|
||||
// nsContentSink.h We share it with nsHTMLParanoidFragmentSink
|
||||
|
||||
class nsHTMLParanoidFragmentSink : public nsHTMLFragmentContentSink
|
||||
class nsHTMLParanoidFragmentSink : public nsHTMLFragmentContentSink,
|
||||
public nsIParanoidFragmentContentSink
|
||||
{
|
||||
public:
|
||||
nsHTMLParanoidFragmentSink();
|
||||
nsHTMLParanoidFragmentSink(PRBool aAllContent = PR_FALSE);
|
||||
|
||||
static nsresult Init();
|
||||
static void Cleanup();
|
||||
@ -786,14 +798,22 @@ public:
|
||||
|
||||
nsresult AddAttributes(const nsIParserNode& aNode,
|
||||
nsIContent* aContent);
|
||||
|
||||
// nsIParanoidFragmentContentSink
|
||||
virtual void AllowStyles();
|
||||
|
||||
protected:
|
||||
nsresult NameFromType(const nsHTMLTag aTag,
|
||||
nsIAtom **aResult);
|
||||
|
||||
nsresult NameFromNode(const nsIParserNode& aNode,
|
||||
nsIAtom **aResult);
|
||||
|
||||
void SanitizeStyleRule(nsICSSStyleRule *aRule, nsAutoString &aRuleText);
|
||||
|
||||
PRBool mSkip; // used when we descend into <style> or <script>
|
||||
PRPackedBool mSkip; // used when we descend into <style> or <script>
|
||||
PRPackedBool mProcessStyle; // used when style is explicitly white-listed
|
||||
PRPackedBool mInStyle; // whether we're inside a style element
|
||||
|
||||
// Use nsTHashTable as a hash set for our whitelists
|
||||
static nsTHashtable<nsISupportsHashKey>* sAllowedTags;
|
||||
@ -803,8 +823,9 @@ protected:
|
||||
nsTHashtable<nsISupportsHashKey>* nsHTMLParanoidFragmentSink::sAllowedTags;
|
||||
nsTHashtable<nsISupportsHashKey>* nsHTMLParanoidFragmentSink::sAllowedAttributes;
|
||||
|
||||
nsHTMLParanoidFragmentSink::nsHTMLParanoidFragmentSink():
|
||||
nsHTMLFragmentContentSink(PR_FALSE), mSkip(PR_FALSE)
|
||||
nsHTMLParanoidFragmentSink::nsHTMLParanoidFragmentSink(PRBool aAllContent):
|
||||
nsHTMLFragmentContentSink(aAllContent), mSkip(PR_FALSE),
|
||||
mProcessStyle(PR_FALSE), mInStyle(PR_FALSE)
|
||||
{
|
||||
}
|
||||
|
||||
@ -875,14 +896,27 @@ NS_NewHTMLParanoidFragmentSink(nsIFragmentContentSink** aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
NS_NewHTMLParanoidFragmentSink2(nsIFragmentContentSink** aResult)
|
||||
{
|
||||
nsHTMLParanoidFragmentSink* it = new nsHTMLParanoidFragmentSink(PR_TRUE);
|
||||
if (!it) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
nsresult rv = nsHTMLParanoidFragmentSink::Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ADDREF(*aResult = it);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
NS_HTMLParanoidFragmentSinkShutdown()
|
||||
{
|
||||
nsHTMLParanoidFragmentSink::Cleanup();
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED0(nsHTMLParanoidFragmentSink,
|
||||
nsHTMLFragmentContentSink)
|
||||
NS_IMPL_ISUPPORTS_INHERITED1(nsHTMLParanoidFragmentSink, nsHTMLFragmentContentSink, nsIParanoidFragmentContentSink)
|
||||
|
||||
nsresult
|
||||
nsHTMLParanoidFragmentSink::NameFromType(const nsHTMLTag aTag,
|
||||
@ -919,6 +953,12 @@ nsHTMLParanoidFragmentSink::NameFromNode(const nsIParserNode& aNode,
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
nsHTMLParanoidFragmentSink::AllowStyles()
|
||||
{
|
||||
mProcessStyle = PR_TRUE;
|
||||
}
|
||||
|
||||
// nsHTMLFragmentContentSink
|
||||
|
||||
nsresult
|
||||
@ -946,7 +986,10 @@ nsHTMLParanoidFragmentSink::AddAttributes(const nsIParserNode& aNode,
|
||||
|
||||
// not an allowed attribute
|
||||
if (!sAllowedAttributes || !sAllowedAttributes->GetEntry(keyAtom)) {
|
||||
continue;
|
||||
// unless it's style, and we're allowing it
|
||||
if (!mProcessStyle || keyAtom != nsGkAtoms::style) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Get value and remove mandatory quotes
|
||||
@ -975,7 +1018,27 @@ nsHTMLParanoidFragmentSink::AddAttributes(const nsIParserNode& aNode,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nodeType == eHTMLTag_a && keyAtom == nsGkAtoms::name) {
|
||||
// Filter unsafe stuff from style attributes if they're allowed
|
||||
if (mProcessStyle && keyAtom == nsGkAtoms::style) {
|
||||
if (!baseURI) {
|
||||
baseURI = aContent->GetBaseURI();
|
||||
}
|
||||
nsCSSParser parser;
|
||||
nsCOMPtr<nsICSSStyleRule> rule;
|
||||
rv = parser.ParseStyleAttribute(aNode.GetValueAt(i),
|
||||
mTargetDocument->GetDocumentURI(),
|
||||
baseURI,
|
||||
mTargetDocument->NodePrincipal(),
|
||||
getter_AddRefs(rule));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsAutoString cleanValue;
|
||||
SanitizeStyleRule(rule, cleanValue);
|
||||
aContent->SetAttr(kNameSpaceID_None, keyAtom, cleanValue, PR_FALSE);
|
||||
} else {
|
||||
// we couldn't sanitize the style attribute, ignore it
|
||||
continue;
|
||||
}
|
||||
} else if (nodeType == eHTMLTag_a && keyAtom == nsGkAtoms::name) {
|
||||
NS_ConvertUTF16toUTF8 cname(v);
|
||||
NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting()));
|
||||
// Add attribute to content
|
||||
@ -994,9 +1057,10 @@ nsHTMLParanoidFragmentSink::OpenContainer(const nsIParserNode& aNode)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// bail if it's a script or style, or we're already inside one of those
|
||||
// bail if it's a script or style (when we don't allow processing of stylesheets),
|
||||
// or we're already inside one of those
|
||||
eHTMLTags type = (eHTMLTags)aNode.GetNodeType();
|
||||
if (type == eHTMLTag_script || type == eHTMLTag_style) {
|
||||
if (type == eHTMLTag_script || (!mProcessStyle && type == eHTMLTag_style)) {
|
||||
mSkip = PR_TRUE;
|
||||
return rv;
|
||||
}
|
||||
@ -1007,7 +1071,14 @@ nsHTMLParanoidFragmentSink::OpenContainer(const nsIParserNode& aNode)
|
||||
|
||||
// not on whitelist
|
||||
if (!sAllowedTags || !sAllowedTags->GetEntry(name)) {
|
||||
return NS_OK;
|
||||
// unless it's style, and we're allowing it
|
||||
if (!mProcessStyle || name != nsGkAtoms::style) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == eHTMLTag_style) {
|
||||
mInStyle = PR_TRUE;
|
||||
}
|
||||
|
||||
return nsHTMLFragmentContentSink::OpenContainer(aNode);
|
||||
@ -1029,12 +1100,123 @@ nsHTMLParanoidFragmentSink::CloseContainer(const nsHTMLTag aTag)
|
||||
|
||||
// not on whitelist
|
||||
if (!sAllowedTags || !sAllowedTags->GetEntry(name)) {
|
||||
return NS_OK;
|
||||
// unless it's style, and we're allowing it
|
||||
if (!mProcessStyle || name != nsGkAtoms::style) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (mInStyle && name == nsGkAtoms::style) {
|
||||
mInStyle = PR_FALSE;
|
||||
|
||||
// Flush the text to make sure that the style text is complete.
|
||||
FlushText();
|
||||
|
||||
// sanitizedStyleText will hold the permitted CSS text.
|
||||
// We use a white-listing approach, so we explicitly allow
|
||||
// the CSS style and font-face rule types. We also clear
|
||||
// -moz-binding CSS properties.
|
||||
nsAutoString sanitizedStyleText;
|
||||
nsIContent* style = GetCurrentContent();
|
||||
if (style) {
|
||||
// styleText will hold the text inside the style element.
|
||||
nsAutoString styleText;
|
||||
nsContentUtils::GetNodeTextContent(style, PR_FALSE, styleText);
|
||||
// Create a unichar input stream for the CSS parser.
|
||||
nsCOMPtr<nsIUnicharInputStream> uin;
|
||||
rv = nsSimpleUnicharStreamFactory::GetInstance()->
|
||||
CreateInstanceFromString(styleText, getter_AddRefs(uin));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Create a sheet to hold the parsed CSS
|
||||
nsRefPtr<nsCSSStyleSheet> sheet;
|
||||
rv = NS_NewCSSStyleSheet(getter_AddRefs(sheet));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsCOMPtr<nsIURI> baseURI = style->GetBaseURI();
|
||||
sheet->SetURIs(mTargetDocument->GetDocumentURI(), nsnull, baseURI);
|
||||
sheet->SetPrincipal(mTargetDocument->NodePrincipal());
|
||||
// Create the CSS parser, and parse the CSS text.
|
||||
nsCSSParser parser(nsnull, sheet);
|
||||
rv = parser.Parse(uin, mTargetDocument->GetDocumentURI(),
|
||||
baseURI, mTargetDocument->NodePrincipal(),
|
||||
0, PR_FALSE);
|
||||
// Mark the sheet as complete.
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
sheet->SetModified(PR_FALSE);
|
||||
sheet->SetComplete();
|
||||
}
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Loop through all the rules found in the CSS text
|
||||
PRInt32 ruleCount = sheet->StyleRuleCount();
|
||||
for (PRInt32 i = 0; i < ruleCount; ++i) {
|
||||
nsRefPtr<nsICSSRule> rule;
|
||||
rv = sheet->GetStyleRuleAt(i, *getter_AddRefs(rule));
|
||||
if (NS_FAILED(rv))
|
||||
continue;
|
||||
NS_ASSERTION(rule, "We should have a rule by now");
|
||||
PRInt32 type;
|
||||
rv = rule->GetType(type);
|
||||
if (NS_FAILED(rv))
|
||||
continue;
|
||||
switch (type) {
|
||||
case nsICSSRule::UNKNOWN_RULE:
|
||||
case nsICSSRule::CHARSET_RULE:
|
||||
case nsICSSRule::IMPORT_RULE:
|
||||
case nsICSSRule::MEDIA_RULE:
|
||||
case nsICSSRule::PAGE_RULE:
|
||||
// Ignore these rule types.
|
||||
break;
|
||||
case nsICSSRule::NAMESPACE_RULE:
|
||||
case nsICSSRule::FONT_FACE_RULE: {
|
||||
// Append @namespace and @font-face rules verbatim.
|
||||
nsAutoString cssText;
|
||||
nsCOMPtr<nsIDOMCSSRule> styleRule = do_QueryInterface(rule);
|
||||
if (styleRule) {
|
||||
rv = styleRule->GetCssText(cssText);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
sanitizedStyleText.Append(cssText);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case nsICSSRule::STYLE_RULE: {
|
||||
// For style rules, we will just look for and remove the
|
||||
// -moz-binding properties.
|
||||
nsCOMPtr<nsICSSStyleRule> styleRule = do_QueryInterface(rule);
|
||||
NS_ASSERTION(styleRule, "Must be a style rule");
|
||||
nsAutoString decl;
|
||||
SanitizeStyleRule(styleRule, decl);
|
||||
rv = styleRule->GetCssText(decl);
|
||||
// Only add the rule when sanitized.
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
sanitizedStyleText.Append(decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Replace the style element content with its sanitized style text
|
||||
nsContentUtils::SetNodeTextContent(style, sanitizedStyleText, PR_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
return nsHTMLFragmentContentSink::CloseContainer(aTag);
|
||||
}
|
||||
|
||||
void
|
||||
nsHTMLParanoidFragmentSink::SanitizeStyleRule(nsICSSStyleRule *aRule, nsAutoString &aRuleText)
|
||||
{
|
||||
aRuleText.Truncate();
|
||||
nsCSSDeclaration *style = aRule->GetDeclaration();
|
||||
if (style) {
|
||||
nsresult rv = style->RemoveProperty(eCSSProperty_binding);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
style->ToString(aRuleText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLParanoidFragmentSink::AddLeaf(const nsIParserNode& aNode)
|
||||
{
|
||||
@ -1057,7 +1239,9 @@ nsHTMLParanoidFragmentSink::AddLeaf(const nsIParserNode& aNode)
|
||||
}
|
||||
|
||||
if (!sAllowedTags || !sAllowedTags->GetEntry(name)) {
|
||||
return NS_OK;
|
||||
if (!mProcessStyle || name != nsGkAtoms::style) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,7 +486,7 @@ nsXMLFragmentContentSink::IgnoreFirstContainer()
|
||||
class nsXHTMLParanoidFragmentSink : public nsXMLFragmentContentSink
|
||||
{
|
||||
public:
|
||||
nsXHTMLParanoidFragmentSink();
|
||||
nsXHTMLParanoidFragmentSink(PRBool aAllContent = PR_FALSE);
|
||||
|
||||
static nsresult Init();
|
||||
static void Cleanup();
|
||||
@ -525,8 +525,8 @@ protected:
|
||||
nsTHashtable<nsISupportsHashKey>* nsXHTMLParanoidFragmentSink::sAllowedTags;
|
||||
nsTHashtable<nsISupportsHashKey>* nsXHTMLParanoidFragmentSink::sAllowedAttributes;
|
||||
|
||||
nsXHTMLParanoidFragmentSink::nsXHTMLParanoidFragmentSink():
|
||||
nsXMLFragmentContentSink(PR_FALSE), mSkipLevel(0)
|
||||
nsXHTMLParanoidFragmentSink::nsXHTMLParanoidFragmentSink(PRBool aAllContent):
|
||||
nsXMLFragmentContentSink(aAllContent), mSkipLevel(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -596,6 +596,20 @@ NS_NewXHTMLParanoidFragmentSink(nsIFragmentContentSink** aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
NS_NewXHTMLParanoidFragmentSink2(nsIFragmentContentSink** aResult)
|
||||
{
|
||||
nsXHTMLParanoidFragmentSink* it = new nsXHTMLParanoidFragmentSink(PR_TRUE);
|
||||
if (!it) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
nsresult rv = nsXHTMLParanoidFragmentSink::Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ADDREF(*aResult = it);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
NS_XHTMLParanoidFragmentSinkShutdown()
|
||||
{
|
||||
|
@ -2708,14 +2708,19 @@ nsresult nsHTMLEditor::ParseFragment(const nsAString & aFragStr,
|
||||
// create the html fragment sink
|
||||
nsCOMPtr<nsIContentSink> sink;
|
||||
if (bContext)
|
||||
sink = do_CreateInstance(NS_HTMLFRAGMENTSINK2_CONTRACTID);
|
||||
sink = do_CreateInstance(NS_HTMLPARANOIDFRAGMENTSINK2_CONTRACTID);
|
||||
else
|
||||
sink = do_CreateInstance(NS_HTMLFRAGMENTSINK_CONTRACTID);
|
||||
sink = do_CreateInstance(NS_HTMLPARANOIDFRAGMENTSINK_CONTRACTID);
|
||||
|
||||
NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE);
|
||||
nsCOMPtr<nsIFragmentContentSink> fragSink(do_QueryInterface(sink));
|
||||
NS_ENSURE_TRUE(fragSink, NS_ERROR_FAILURE);
|
||||
|
||||
// Allow style elements and attributes
|
||||
nsCOMPtr<nsIParanoidFragmentContentSink> paranoidSink(do_QueryInterface(sink));
|
||||
NS_ASSERTION(paranoidSink, "Our content sink is paranoid");
|
||||
paranoidSink->AllowStyles();
|
||||
|
||||
fragSink->SetTargetDocument(aTargetDocument);
|
||||
|
||||
// parse the fragment
|
||||
|
@ -55,6 +55,7 @@ _TEST_FILES = \
|
||||
test_bug480972.html \
|
||||
test_bug484181.html \
|
||||
test_bug487524.html \
|
||||
test_bug520189.html \
|
||||
test_bug525389.html \
|
||||
test_bug537046.html \
|
||||
test_bug550434.html \
|
||||
|
358
editor/libeditor/html/tests/test_bug520189.html
Normal file
358
editor/libeditor/html/tests/test_bug520189.html
Normal file
@ -0,0 +1,358 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=520182
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 520182</title>
|
||||
<script type="application/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=520182">Mozilla Bug 520182</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<iframe id="a" src="about:blank"></iframe>
|
||||
<iframe id="b" src="about:blank"></iframe>
|
||||
<iframe id="c" src="about:blank"></iframe>
|
||||
<div id="d" contenteditable="true"></div>
|
||||
<div id="e" contenteditable="true"></div>
|
||||
<div id="f" contenteditable="true"></div>
|
||||
<iframe id="g" src="about:blank"></iframe>
|
||||
<iframe id="h" src="about:blank"></iframe>
|
||||
<div id="i" contenteditable="true"></div>
|
||||
<div id="j" contenteditable="true"></div>
|
||||
<iframe id="k" src="about:blank"></iframe>
|
||||
<div id="l" contenteditable="true"></div>
|
||||
<iframe id="m" src="about:blank"></iframe>
|
||||
<div id="n" contenteditable="true"></div>
|
||||
<iframe id="o" src="about:blank"></iframe>
|
||||
<div id="p" contenteditable="true"></div>
|
||||
<iframe id="q" src="about:blank"></iframe>
|
||||
<div id="r" contenteditable="true"></div>
|
||||
<iframe id="s" src="about:blank"></iframe>
|
||||
<div id="t" contenteditable="true"></div>
|
||||
<iframe id="u" src="about:blank"></iframe>
|
||||
<div id="v" contenteditable="true"></div>
|
||||
<iframe id="w" src="about:blank"></iframe>
|
||||
<div id="x" contenteditable="true"></div>
|
||||
<iframe id="y" src="about:blank"></iframe>
|
||||
<div id="z" contenteditable="true"></div>
|
||||
<iframe id="aa" src="about:blank"></iframe>
|
||||
<div id="bb" contenteditable="true"></div>
|
||||
<iframe id="cc" src="about:blank"></iframe>
|
||||
<div id="dd" contenteditable="true"></div>
|
||||
<iframe id="ee" src="about:blank"></iframe>
|
||||
<div id="ff" contenteditable="true"></div>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 520182 **/
|
||||
|
||||
const dataPayload = "foo<iframe src=\"data:text/html,bar\"></iframe>baz";
|
||||
const jsPayload = "foo<iframe src=\"javascript:void('bar');\"></iframe>baz";
|
||||
const httpPayload = "foo<iframe src=\"http://mochi.test:8888/\"></iframe>baz";
|
||||
const scriptPayload ="foo<script>document.write(\"<iframe></iframe>\");</sc" + "ript>baz";
|
||||
const scriptExternalPayload = "foo<script src=\"data:text/javascript,document.write('<iframe></iframe>');\"></sc" + "ript>baz";
|
||||
const validStyle1Payload = "foo<style>#bar{color:red;}</style>baz";
|
||||
const validStyle2Payload = "foo<span style=\"color:red\">bar</span>baz";
|
||||
const validStyle3Payload = "foo<style>@font-face{font-family:xxx;src:'xxx.ttf';}</style>baz";
|
||||
const validStyle4Payload = "foo<style>@namespace xxx url(http://example.com/);</style>baz";
|
||||
const invalidStyle1Payload = "foo<style>#bar{-moz-binding:url('data:text/xml,<?xml version=\"1.0\"><binding xmlns=\"http://www.mozilla.org/xbl\"/>');}</style>baz";
|
||||
const invalidStyle2Payload = "foo<span style=\"-moz-binding:url('data:text/xml,<?xml version="1.0"><binding xmlns="http://www.mozilla.org/xbl"/>');\">bar</span>baz";
|
||||
const invalidStyle3Payload = "foo<style>@import 'xxx.css';</style>baz";
|
||||
const invalidStyle4Payload = "foo<span style=\"@import 'xxx.css';\">bar</span>baz";
|
||||
const invalidStyle5Payload = "foo<span style=\"@font-face{font-family:xxx;src:'xxx.ttf';}\">bar</span>baz";
|
||||
const invalidStyle6Payload = "foo<span style=\"@namespace xxx url(http://example.com/);\">bar</span>baz";
|
||||
const nestedStylePayload = "foo<style>#bar1{-moz-binding:url('data:text/xml,<?xml version="1.0"><binding xmlns="http://www.mozilla.org/xbl" id="binding-1"/>');<style></style>#bar2{-moz-binding:url('data:text/xml,<?xml version="1.0"><binding xmlns="http://www.mozilla.org/xbl" id="binding-2"/>');</style>baz";
|
||||
|
||||
var tests = [
|
||||
{
|
||||
id: "a",
|
||||
isIFrame: true,
|
||||
payload: dataPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("a").contentDocument.documentElement
|
||||
},
|
||||
{
|
||||
id: "b",
|
||||
isIFrame: true,
|
||||
payload: jsPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("b").contentDocument.documentElement
|
||||
},
|
||||
{
|
||||
id: "c",
|
||||
isIFrame: true,
|
||||
payload: httpPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("c").contentDocument.documentElement
|
||||
},
|
||||
{
|
||||
id: "g",
|
||||
isIFrame: true,
|
||||
payload: scriptPayload,
|
||||
rootElement: function() document.getElementById("g").contentDocument.documentElement,
|
||||
iframeCount: 0
|
||||
},
|
||||
{
|
||||
id: "h",
|
||||
isIFrame: true,
|
||||
payload: scriptExternalPayload,
|
||||
rootElement: function() document.getElementById("h").contentDocument.documentElement,
|
||||
iframeCount: 0
|
||||
},
|
||||
{
|
||||
id: "d",
|
||||
payload: dataPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("d")
|
||||
},
|
||||
{
|
||||
id: "e",
|
||||
payload: jsPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("e")
|
||||
},
|
||||
{
|
||||
id: "f",
|
||||
payload: httpPayload,
|
||||
iframeCount: 0,
|
||||
rootElement: function() document.getElementById("f")
|
||||
},
|
||||
{
|
||||
id: "i",
|
||||
payload: scriptPayload,
|
||||
rootElement: function() document.getElementById("i"),
|
||||
iframeCount: 0
|
||||
},
|
||||
{
|
||||
id: "j",
|
||||
payload: scriptExternalPayload,
|
||||
rootElement: function() document.getElementById("j"),
|
||||
iframeCount: 0
|
||||
},
|
||||
{
|
||||
id: "k",
|
||||
isIFrame: true,
|
||||
payload: validStyle1Payload,
|
||||
rootElement: function() document.getElementById("k").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("style"), -1, "Should have retained style")
|
||||
},
|
||||
{
|
||||
id: "l",
|
||||
payload: validStyle1Payload,
|
||||
rootElement: function() document.getElementById("l"),
|
||||
checkResult: function(html) isnot(html.indexOf("style"), -1, "Should have retained style")
|
||||
},
|
||||
{
|
||||
id: "m",
|
||||
isIFrame: true,
|
||||
payload: validStyle2Payload,
|
||||
rootElement: function() document.getElementById("m").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("style"), -1, "Should have retained style")
|
||||
},
|
||||
{
|
||||
id: "n",
|
||||
payload: validStyle2Payload,
|
||||
rootElement: function() document.getElementById("n"),
|
||||
checkResult: function(html) isnot(html.indexOf("style"), -1, "Should have retained style")
|
||||
},
|
||||
{
|
||||
id: "o",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle1Payload,
|
||||
rootElement: function() document.getElementById("o").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("binding"), -1, "Should not have retained the binding style")
|
||||
},
|
||||
{
|
||||
id: "p",
|
||||
payload: invalidStyle1Payload,
|
||||
rootElement: function() document.getElementById("p"),
|
||||
checkResult: function(html) is(html.indexOf("binding"), -1, "Should not have retained the binding style")
|
||||
},
|
||||
{
|
||||
id: "q",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle2Payload,
|
||||
rootElement: function() document.getElementById("q").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("binding"), -1, "Should not have retained the binding style")
|
||||
},
|
||||
{
|
||||
id: "r",
|
||||
payload: invalidStyle2Payload,
|
||||
rootElement: function() document.getElementById("r"),
|
||||
checkResult: function(html) is(html.indexOf("binding"), -1, "Should not have retained the binding style")
|
||||
},
|
||||
{
|
||||
id: "s",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle1Payload,
|
||||
rootElement: function() document.getElementById("s").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the import style")
|
||||
},
|
||||
{
|
||||
id: "t",
|
||||
payload: invalidStyle1Payload,
|
||||
rootElement: function() document.getElementById("t"),
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the import style")
|
||||
},
|
||||
{
|
||||
id: "u",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle2Payload,
|
||||
rootElement: function() document.getElementById("u").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the import style")
|
||||
},
|
||||
{
|
||||
id: "v",
|
||||
payload: invalidStyle2Payload,
|
||||
rootElement: function() document.getElementById("v"),
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the import style")
|
||||
},
|
||||
{
|
||||
id: "w",
|
||||
isIFrame: true,
|
||||
payload: validStyle3Payload,
|
||||
rootElement: function() document.getElementById("w").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should have retained the font-face style")
|
||||
},
|
||||
{
|
||||
id: "x",
|
||||
payload: validStyle3Payload,
|
||||
rootElement: function() document.getElementById("x"),
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should have retained the font-face style")
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle5Payload,
|
||||
rootElement: function() document.getElementById("y").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the font-face style")
|
||||
},
|
||||
{
|
||||
id: "z",
|
||||
payload: invalidStyle5Payload,
|
||||
rootElement: function() document.getElementById("z"),
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the font-face style")
|
||||
},
|
||||
{
|
||||
id: "aa",
|
||||
isIFrame: true,
|
||||
payload: nestedStylePayload,
|
||||
rootElement: function() document.getElementById("aa").contentDocument.documentElement,
|
||||
checkResult: function(html, text) {
|
||||
is(html.indexOf("binding-1"), -1, "Should not have retained the binding-1 style");
|
||||
isnot(text.indexOf("#bar2"), -1, "Should have retained binding-2 as text content");
|
||||
is(text.indexOf("binding-2"), -1, "Should not have retained binding-2 as a tag");
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "bb",
|
||||
payload: nestedStylePayload,
|
||||
rootElement: function() document.getElementById("bb"),
|
||||
checkResult: function(html, text) {
|
||||
is(html.indexOf("binding-1"), -1, "Should not have retained the binding-1 style");
|
||||
isnot(text.indexOf("#bar2"), -1, "Should have retained binding-2 as text content");
|
||||
is(text.indexOf("binding-2"), -1, "Should not have retained binding-2 as a tag");
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "cc",
|
||||
isIFrame: true,
|
||||
payload: validStyle4Payload,
|
||||
rootElement: function() document.getElementById("cc").contentDocument.documentElement,
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should have retained the namespace style")
|
||||
},
|
||||
{
|
||||
id: "dd",
|
||||
payload: validStyle4Payload,
|
||||
rootElement: function() document.getElementById("dd"),
|
||||
checkResult: function(html) isnot(html.indexOf("xxx"), -1, "Should have retained the namespace style")
|
||||
},
|
||||
{
|
||||
id: "ee",
|
||||
isIFrame: true,
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("ee").contentDocument.documentElement,
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the namespace style")
|
||||
},
|
||||
{
|
||||
id: "ff",
|
||||
payload: invalidStyle6Payload,
|
||||
rootElement: function() document.getElementById("ff"),
|
||||
checkResult: function(html) is(html.indexOf("xxx"), -1, "Should not have retained the namespace style")
|
||||
}
|
||||
];
|
||||
|
||||
function doNextTest() {
|
||||
if (typeof testCounter == "undefined")
|
||||
testCounter = 0;
|
||||
else if (++testCounter == tests.length) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
runTest(tests[testCounter]);
|
||||
|
||||
doNextTest();
|
||||
}
|
||||
|
||||
function runTest(test) {
|
||||
var elem = document.getElementById(test.id);
|
||||
if ("isIFrame" in test) {
|
||||
elem.contentDocument.designMode = "on";
|
||||
elem.contentWindow.focus();
|
||||
} else
|
||||
elem.focus();
|
||||
|
||||
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
||||
|
||||
var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
|
||||
.getService(Components.interfaces.nsIClipboard);
|
||||
|
||||
var trans = Components.classes["@mozilla.org/widget/transferable;1"]
|
||||
.createInstance(Components.interfaces.nsITransferable);
|
||||
var data = Components.classes["@mozilla.org/supports-string;1"]
|
||||
.createInstance(Components.interfaces.nsISupportsString);
|
||||
data.data = test.payload;
|
||||
trans.addDataFlavor("text/html");
|
||||
trans.setTransferData("text/html", data, data.data.length * 2);
|
||||
clipboard.setData(trans, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
|
||||
|
||||
var mainWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIWebNavigation)
|
||||
.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindow);
|
||||
|
||||
mainWindow.goDoCommand("cmd_paste");
|
||||
|
||||
if ("checkResult" in test) {
|
||||
if ("isIFrame" in test) {
|
||||
test.checkResult(elem.contentDocument.documentElement.innerHTML,
|
||||
elem.contentDocument.documentElement.textContent);
|
||||
} else {
|
||||
test.checkResult(elem.innerHTML, elem.textContent);
|
||||
}
|
||||
} else {
|
||||
var iframes = test.rootElement().querySelectorAll("iframe");
|
||||
var expectedIFrameCount = ("iframeCount" in test) ? test.iframeCount : 1;
|
||||
is(iframes.length, expectedIFrameCount, "Only " + expectedIFrameCount + " iframe should be pasted");
|
||||
if (expectedIFrameCount > 0) {
|
||||
ok(!iframes[0].hasAttribute("src"), "iframe should not have a src attrib");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(doNextTest);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -515,9 +515,11 @@ MAKE_CTOR(CreatePlainTextSerializer, nsIContentSerializer, NS_NewPla
|
||||
MAKE_CTOR(CreateHTMLFragmentSink, nsIFragmentContentSink, NS_NewHTMLFragmentContentSink)
|
||||
MAKE_CTOR(CreateHTMLFragmentSink2, nsIFragmentContentSink, NS_NewHTMLFragmentContentSink2)
|
||||
MAKE_CTOR(CreateHTMLParanoidFragmentSink, nsIFragmentContentSink, NS_NewHTMLParanoidFragmentSink)
|
||||
MAKE_CTOR(CreateHTMLParanoidFragmentSink2,nsIFragmentContentSink, NS_NewHTMLParanoidFragmentSink2)
|
||||
MAKE_CTOR(CreateXMLFragmentSink, nsIFragmentContentSink, NS_NewXMLFragmentContentSink)
|
||||
MAKE_CTOR(CreateXMLFragmentSink2, nsIFragmentContentSink, NS_NewXMLFragmentContentSink2)
|
||||
MAKE_CTOR(CreateXHTMLParanoidFragmentSink,nsIFragmentContentSink, NS_NewXHTMLParanoidFragmentSink)
|
||||
MAKE_CTOR(CreateXHTMLParanoidFragmentSink2,nsIFragmentContentSink, NS_NewXHTMLParanoidFragmentSink2)
|
||||
MAKE_CTOR(CreateSanitizingHTMLSerializer, nsIContentSerializer, NS_NewSanitizingHTMLSerializer)
|
||||
MAKE_CTOR(CreateXBLService, nsIXBLService, NS_NewXBLService)
|
||||
MAKE_CTOR(CreateContentPolicy, nsIContentPolicy, NS_NewContentPolicy)
|
||||
@ -1301,6 +1303,11 @@ static const nsModuleComponentInfo gLayoutComponents[] = {
|
||||
NS_HTMLPARANOIDFRAGMENTSINK_CONTRACTID,
|
||||
CreateHTMLParanoidFragmentSink },
|
||||
|
||||
{ "html paranoid fragment sink 2",
|
||||
NS_HTMLPARANOIDFRAGMENTSINK2_CID,
|
||||
NS_HTMLPARANOIDFRAGMENTSINK2_CONTRACTID,
|
||||
CreateHTMLParanoidFragmentSink2 },
|
||||
|
||||
{ "HTML sanitizing content serializer",
|
||||
MOZ_SANITIZINGHTMLSERIALIZER_CID,
|
||||
MOZ_SANITIZINGHTMLSERIALIZER_CONTRACTID,
|
||||
@ -1321,6 +1328,11 @@ static const nsModuleComponentInfo gLayoutComponents[] = {
|
||||
NS_XHTMLPARANOIDFRAGMENTSINK_CONTRACTID,
|
||||
CreateXHTMLParanoidFragmentSink },
|
||||
|
||||
{ "xhtml paranoid fragment sink 2",
|
||||
NS_XHTMLPARANOIDFRAGMENTSINK2_CID,
|
||||
NS_XHTMLPARANOIDFRAGMENTSINK2_CONTRACTID,
|
||||
CreateXHTMLParanoidFragmentSink2 },
|
||||
|
||||
{ "XBL Service",
|
||||
NS_XBLSERVICE_CID,
|
||||
"@mozilla.org/xbl;1",
|
||||
|
@ -235,6 +235,8 @@ NS_NewHTMLFragmentContentSink2(nsIFragmentContentSink** aInstancePtrResult);
|
||||
// in nsContentSink.h
|
||||
nsresult
|
||||
NS_NewHTMLParanoidFragmentSink(nsIFragmentContentSink** aInstancePtrResult);
|
||||
nsresult
|
||||
NS_NewHTMLParanoidFragmentSink2(nsIFragmentContentSink** aInstancePtrResult);
|
||||
void
|
||||
NS_HTMLParanoidFragmentSinkShutdown();
|
||||
#endif /* nsHTMLParts_h___ */
|
||||
|
@ -46,6 +46,10 @@ class nsIDocument;
|
||||
{ 0x1ecdb30d, 0x1f10, 0x45d2, \
|
||||
{ 0xa4, 0xf4, 0xec, 0xbc, 0x03, 0x52, 0x9a, 0x7e } }
|
||||
|
||||
#define NS_I_PARANOID_FRAGMENT_CONTENT_SINK_IID \
|
||||
{ 0x59ec77f5, 0x9e9b, 0x4040, \
|
||||
{ 0xbd, 0xe7, 0x4e, 0xd0, 0x13, 0xa6, 0x21, 0x74 } }
|
||||
|
||||
/**
|
||||
* The fragment sink allows a client to parse a fragment of sink, possibly
|
||||
* surrounded in context. Also see nsIParser::ParseFragment().
|
||||
@ -101,6 +105,23 @@ public:
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsIFragmentContentSink,
|
||||
NS_I_FRAGMENT_CONTENT_SINK_IID)
|
||||
|
||||
/**
|
||||
* This interface is implemented by paranoid content sinks, and allows consumers
|
||||
* to add tags and attributes to the default white-list set.
|
||||
*/
|
||||
class nsIParanoidFragmentContentSink : public nsISupports {
|
||||
public:
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_I_PARANOID_FRAGMENT_CONTENT_SINK_IID)
|
||||
|
||||
/**
|
||||
* Allow the content sink to accept style elements and attributes.
|
||||
*/
|
||||
virtual void AllowStyles() = 0;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsIParanoidFragmentContentSink,
|
||||
NS_I_PARANOID_FRAGMENT_CONTENT_SINK_IID)
|
||||
|
||||
/**
|
||||
* Base version takes string nested in context, content surrounded by
|
||||
* WillBuildContent()/DidBuildContent() calls. The 2nd version just loads
|
||||
@ -111,11 +132,15 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIFragmentContentSink,
|
||||
#define NS_HTMLFRAGMENTSINK2_CONTRACTID "@mozilla.org/layout/htmlfragmentsink;2"
|
||||
#define NS_HTMLPARANOIDFRAGMENTSINK_CONTRACTID \
|
||||
"@mozilla.org/htmlparanoidfragmentsink;1"
|
||||
#define NS_HTMLPARANOIDFRAGMENTSINK2_CONTRACTID \
|
||||
"@mozilla.org/htmlparanoidfragmentsink;2"
|
||||
|
||||
#define NS_XMLFRAGMENTSINK_CONTRACTID "@mozilla.org/layout/xmlfragmentsink;1"
|
||||
#define NS_XMLFRAGMENTSINK2_CONTRACTID "@mozilla.org/layout/xmlfragmentsink;2"
|
||||
#define NS_XHTMLPARANOIDFRAGMENTSINK_CONTRACTID \
|
||||
"@mozilla.org/xhtmlparanoidfragmentsink;1"
|
||||
#define NS_XHTMLPARANOIDFRAGMENTSINK2_CONTRACTID \
|
||||
"@mozilla.org/xhtmlparanoidfragmentsink;2"
|
||||
|
||||
|
||||
// the HTML versions are in nsHTMLParts.h
|
||||
@ -128,6 +153,8 @@ NS_NewXMLFragmentContentSink2(nsIFragmentContentSink** aInstancePtrResult);
|
||||
// in nsContentSink.h
|
||||
nsresult
|
||||
NS_NewXHTMLParanoidFragmentSink(nsIFragmentContentSink** aInstancePtrResult);
|
||||
nsresult
|
||||
NS_NewXHTMLParanoidFragmentSink2(nsIFragmentContentSink** aInstancePtrResult);
|
||||
void
|
||||
NS_XHTMLParanoidFragmentSinkShutdown();
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user