Appropriately escape CSS identifiers when serializing. (Bug 543428) r=bzbarsky

This commit is contained in:
L. David Baron 2010-02-04 12:49:29 -08:00
parent 3391f48d52
commit 66eda80913
9 changed files with 200 additions and 24 deletions

View File

@ -251,8 +251,11 @@ nsCSSDeclaration::AppendCSSValueToString(nsCSSProperty aProperty,
aValue.GetStringValue(buffer);
if (unit == eCSSUnit_String) {
nsStyleUtil::AppendEscapedCSSString(buffer, aResult);
} else {
} else if (unit == eCSSUnit_Families) {
// XXX We really need to do *some* escaping.
aResult.Append(buffer);
} else {
nsStyleUtil::AppendEscapedCSSIdent(buffer, aResult);
}
}
else if (eCSSUnit_Array <= unit && unit <= eCSSUnit_Cubic_Bezier) {
@ -306,7 +309,9 @@ nsCSSDeclaration::AppendCSSValueToString(nsCSSProperty aProperty,
// We assume that the first argument is always of nsCSSKeyword type.
const nsCSSKeyword functionId =
static_cast<nsCSSKeyword>(functionName.GetIntValue());
AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(functionId), aResult);
nsStyleUtil::AppendEscapedCSSIdent(
NS_ConvertASCIItoUTF16(nsCSSKeywords::GetStringValue(functionId)),
aResult);
} else {
AppendCSSValueToString(aProperty, functionName, aResult);
}

View File

@ -598,7 +598,7 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
"without a prefix?");
nsAutoString prefix;
prefixAtom->ToString(prefix);
aString.Append(prefix);
nsStyleUtil::AppendEscapedCSSIdent(prefix, aString);
aString.Append(PRUnichar('|'));
wroteNamespace = PR_TRUE;
} else {
@ -625,6 +625,8 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
}
} else {
// Append the tag name
nsAutoString tag;
(isPseudoElement ? mLowercaseTag : mCasedTag)->ToString(tag);
if (isPseudoElement) {
if (!mNext) {
// Lone pseudo-element selector -- toss in a wildcard type selector
@ -634,10 +636,14 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
if (!nsCSSPseudoElements::IsCSS2PseudoElement(mLowercaseTag)) {
aString.Append(PRUnichar(':'));
}
// This should not be escaped since (a) the pseudo-element string
// has a ":" that can't be escaped and (b) all pseudo-elements at
// this point are known, and therefore we know they don't need
// escaping.
aString.Append(tag);
} else {
nsStyleUtil::AppendEscapedCSSIdent(tag, aString);
}
nsAutoString prefix;
(isPseudoElement ? mLowercaseTag : mCasedTag)->ToString(prefix);
aString.Append(prefix);
}
// Append the id, if there is one
@ -646,7 +652,7 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
while (list != nsnull) {
list->mAtom->ToString(temp);
aString.Append(PRUnichar('#'));
aString.Append(temp);
nsStyleUtil::AppendEscapedCSSIdent(temp, aString);
list = list->mNext;
}
}
@ -657,7 +663,7 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
while (list != nsnull) {
list->mAtom->ToString(temp);
aString.Append(PRUnichar('.'));
aString.Append(temp);
nsStyleUtil::AppendEscapedCSSIdent(temp, aString);
list = list->mNext;
}
}
@ -682,13 +688,13 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
"is unknown?");
nsAutoString prefix;
prefixAtom->ToString(prefix);
aString.Append(prefix);
nsStyleUtil::AppendEscapedCSSIdent(prefix, aString);
aString.Append(PRUnichar('|'));
}
}
// Append the attribute name
list->mCasedAttr->ToString(temp);
aString.Append(temp);
nsStyleUtil::AppendEscapedCSSIdent(temp, aString);
if (list->mFunction != NS_ATTR_FUNC_SET) {
// Append the function
@ -725,7 +731,7 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
for (nsPseudoClassList* list = mPseudoClassList; list;
list = list->mNext) {
list->mAtom->ToString(temp);
aString.Append(temp);
nsStyleUtil::AppendEscapedCSSIdent(temp, aString);
NS_ABORT_IF_FALSE(!list->u.mMemory, "data not expected");
aString.Append(PRUnichar(','));
}
@ -738,11 +744,16 @@ nsCSSSelector::AppendToStringWithoutCombinatorsOrNegations
} else {
for (nsPseudoClassList* list = mPseudoClassList; list; list = list->mNext) {
list->mAtom->ToString(temp);
// This should not be escaped since (a) the pseudo-class string
// has a ":" that can't be escaped and (b) all pseudo-classes at
// this point are known, and therefore we know they don't need
// escaping.
aString.Append(temp);
if (list->u.mMemory) {
aString.Append(PRUnichar('('));
if (nsCSSPseudoClasses::HasStringArg(list->mAtom)) {
aString.Append(list->u.mString);
nsStyleUtil::AppendEscapedCSSIdent(
nsDependentString(list->u.mString), aString);
} else {
NS_ASSERTION(nsCSSPseudoClasses::HasNthPairArg(list->mAtom),
"unexpected pseudo-class");

View File

@ -828,8 +828,12 @@ nsComputedDOMStyle::GetContent(nsIDOMCSSValue** aValue)
}
break;
case eStyleContentType_Attr:
val->SetString(nsDependentString(data.mContent.mString),
nsIDOMCSSPrimitiveValue::CSS_ATTR);
{
nsAutoString str;
nsStyleUtil::AppendEscapedCSSIdent(
nsDependentString(data.mContent.mString), str);
val->SetString(str, nsIDOMCSSPrimitiveValue::CSS_ATTR);
}
break;
case eStyleContentType_Counter:
case eStyleContentType_Counters:
@ -845,7 +849,8 @@ nsComputedDOMStyle::GetContent(nsIDOMCSSValue** aValue)
// WRITE ME
nsCSSValue::Array *a = data.mContent.mCounters;
str.Append(a->Item(0).GetStringBufferValue());
nsStyleUtil::AppendEscapedCSSIdent(
nsDependentString(a->Item(0).GetStringBufferValue()), str);
PRInt32 typeItem = 1;
if (data.mType == eStyleContentType_Counters) {
typeItem = 2;
@ -920,7 +925,9 @@ nsComputedDOMStyle::GetCounterIncrement(nsIDOMCSSValue** aValue)
}
const nsStyleCounterData *data = content->GetCounterIncrementAt(i);
name->SetString(data->mCounter);
nsAutoString escaped;
nsStyleUtil::AppendEscapedCSSIdent(data->mCounter, escaped);
name->SetString(escaped);
value->SetNumber(data->mValue); // XXX This should really be integer
}
@ -1072,7 +1079,9 @@ nsComputedDOMStyle::GetCounterReset(nsIDOMCSSValue** aValue)
}
const nsStyleCounterData *data = content->GetCounterResetAt(i);
name->SetString(data->mCounter);
nsAutoString escaped;
nsStyleUtil::AppendEscapedCSSIdent(data->mCounter, escaped);
name->SetString(escaped);
value->SetNumber(data->mValue); // XXX This should really be integer
}
@ -4285,7 +4294,9 @@ nsComputedDOMStyle::GetTransitionProperty(nsIDOMCSSValue** aValue)
{
const char *str;
transition->GetUnknownProperty()->GetUTF8String(&str);
property->SetString(nsDependentCString(str)); // really want SetIdent
nsAutoString escaped;
nsStyleUtil::AppendEscapedCSSIdent(NS_ConvertUTF8toUTF16(str), escaped);
property->SetString(escaped); // really want SetIdent
}
else
property->SetString(nsCSSProps::GetStringValue(cssprop));

View File

@ -557,6 +557,63 @@ void nsStyleUtil::AppendEscapedCSSString(const nsString& aString,
aReturn.Append(PRUnichar('"'));
}
/* static */ void
nsStyleUtil::AppendEscapedCSSIdent(const nsString& aIdent, nsAString& aReturn)
{
// The relevant parts of the CSS grammar are:
// ident [-]?{nmstart}{nmchar}*
// nmstart [_a-z]|{nonascii}|{escape}
// nmchar [_a-z0-9-]|{nonascii}|{escape}
// nonascii [^\0-\177]
// escape {unicode}|\\[^\n\r\f0-9a-f]
// unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
// from http://www.w3.org/TR/CSS21/syndata.html#tokenization
const nsString::char_type* in = aIdent.get();
const nsString::char_type* const end = in + aIdent.Length();
// Deal with the leading dash separately so we don't need to
// unnecessarily escape digits.
if (in != end && *in == '-') {
aReturn.Append(PRUnichar('-'));
++in;
}
PRBool first = PR_TRUE;
for (; in != end; ++in, first = PR_FALSE)
{
if (*in < 0x20 || (first && '0' <= *in && *in <= '9'))
{
// Escape all characters below 0x20, and digits at the start
// (including after a dash), numerically. If we didn't escape
// digits numerically, they'd get interpreted as a numeric escape
// for the wrong character.
/*
This is the buffer into which snprintf should write. As the hex.
value is, for numbers below 0x7F, max. 2 characters long, we
don't need more than 5 characters ("\XX "+NUL).
*/
PRUnichar buf[5];
nsTextFormatter::snprintf(buf, NS_ARRAY_LENGTH(buf),
NS_LITERAL_STRING("\\%hX ").get(), *in);
aReturn.Append(buf);
} else {
PRUnichar ch = *in;
if (!((ch == PRUnichar('_')) ||
(PRUnichar('A') <= ch && ch <= PRUnichar('Z')) ||
(PRUnichar('a') <= ch && ch <= PRUnichar('z')) ||
PRUnichar(0x80) <= ch ||
(!first && ch == PRUnichar('-')) ||
(PRUnichar('0') <= ch && ch <= PRUnichar('9')))) {
// Character needs to be escaped
aReturn.Append(PRUnichar('\\'));
}
aReturn.Append(ch);
}
}
}
/* static */ void
nsStyleUtil::AppendBitmaskCSSValue(nsCSSProperty aProperty,
PRInt32 aMaskedValue,

View File

@ -82,6 +82,11 @@ public:
// Append a quoted (with "") and escaped version of aString to aResult.
static void AppendEscapedCSSString(const nsString& aString,
nsAString& aResult);
// Append the identifier given by |aIdent| to |aResult|, with
// appropriate escaping so that it can be reparsed to the same
// identifier.
static void AppendEscapedCSSIdent(const nsString& aIdent,
nsAString& aResult);
// Append a bitmask-valued property's value(s) (space-separated) to aResult.
static void AppendBitmaskCSSValue(nsCSSProperty aProperty,

View File

@ -119,6 +119,7 @@ _TEST_FILES = test_acid3_test46.html \
test_font_face_parser.html \
test_garbage_at_end_of_declarations.html \
test_hover.html \
test_ident_escaping.html \
test_inherit_computation.html \
test_inherit_storage.html \
test_initial_computation.html \

View File

@ -1200,15 +1200,15 @@ var gCSSProperties = {
type: CSS_TYPE_LONGHAND,
/* XXX needs to be on pseudo-elements */
initial_values: [ "normal", "none" ],
other_values: [ '""', "''", '"hello"', "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')", 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', 'counter(foo)', 'counter(bar, upper-roman)', 'counters(foo, ".")', "counters(bar, '-', lower-greek)", "'-' counter(foo) '.'", "attr(title)", "open-quote", "close-quote", "no-open-quote", "no-close-quote", "close-quote attr(title) counters(foo, '.', upper-alpha)", "counter(foo, none)", "counters(bar, '.', none)" ],
invalid_values: [ 'counters(foo)', 'counter(foo, ".")', 'attr("title")', "attr('title')" ]
other_values: [ '""', "''", '"hello"', "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)", "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==')", 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==")', 'counter(foo)', 'counter(bar, upper-roman)', 'counters(foo, ".")', "counters(bar, '-', lower-greek)", "'-' counter(foo) '.'", "attr(title)", "open-quote", "close-quote", "no-open-quote", "no-close-quote", "close-quote attr(title) counters(foo, '.', upper-alpha)", "counter(foo, none)", "counters(bar, '.', none)", "attr(\\32)", "attr(\\2)", "attr(-\\2)", "attr(-\\32)", "counter(\\2)", "counters(\\32, '.')", "counter(-\\32, upper-roman)", "counters(-\\2, '-', lower-greek)", "counter(\\()", "counters(a\\+b, '.')", "counter(\\}, upper-alpha)" ],
invalid_values: [ 'counters(foo)', 'counter(foo, ".")', 'attr("title")', "attr('title')", "attr(2)", "attr(-2)", "counter(2)", "counters(-2, '.')" ]
},
"counter-increment": {
domProp: "counterIncrement",
inherited: false,
type: CSS_TYPE_LONGHAND,
initial_values: [ "none" ],
other_values: [ "foo 1", "bar", "foo 3 bar baz 2" ],
other_values: [ "foo 1", "bar", "foo 3 bar baz 2", "\\32 1", "-\\32 1", "-c 1", "\\32 1", "-\\32 1", "\\2 1", "-\\2 1", "-c 1", "\\2 1", "-\\2 1" ],
invalid_values: []
},
"counter-reset": {
@ -1216,7 +1216,7 @@ var gCSSProperties = {
inherited: false,
type: CSS_TYPE_LONGHAND,
initial_values: [ "none" ],
other_values: [ "bar 0", "foo", "foo 3 bar baz 2" ],
other_values: [ "foo 1", "bar", "foo 3 bar baz 2", "\\32 1", "-\\32 1", "-c 1", "\\32 1", "-\\32 1", "\\2 1", "-\\2 1", "-c 1", "\\2 1", "-\\2 1" ],
invalid_values: []
},
"cue": {
@ -1965,7 +1965,7 @@ var gCSSProperties = {
type: CSS_TYPE_TRUE_SHORTHAND,
subproperties: [ "-moz-transition-property", "-moz-transition-duration", "-moz-transition-timing-function", "-moz-transition-delay" ],
initial_values: [ "all 0s ease 0s" ],
other_values: [ "width 1s linear 2s", "width 1s 2s linear", "width linear 1s 2s", "linear width 1s 2s", "linear 1s width 2s", "linear 1s 2s width", "1s width linear 2s", "1s width 2s linear", "1s 2s width linear", "1s linear width 2s", "1s linear 2s width", "1s 2s linear width", "width linear 1s", "width 1s linear", "linear width 1s", "linear 1s width", "1s width linear", "1s linear width", "1s 2s width", "1s width 2s", "width 1s 2s", "1s 2s linear", "1s linear 2s", "linear 1s 2s", "width 1s", "1s width", "linear 1s", "1s linear", "1s 2s", "2s 1s", "width", "linear", "1s", "height", "2s", "ease-in-out", "2s ease-in", "opacity linear", "ease-out 2s", "2s color, 1s width, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)" ],
other_values: [ "width 1s linear 2s", "width 1s 2s linear", "width linear 1s 2s", "linear width 1s 2s", "linear 1s width 2s", "linear 1s 2s width", "1s width linear 2s", "1s width 2s linear", "1s 2s width linear", "1s linear width 2s", "1s linear 2s width", "1s 2s linear width", "width linear 1s", "width 1s linear", "linear width 1s", "linear 1s width", "1s width linear", "1s linear width", "1s 2s width", "1s width 2s", "width 1s 2s", "1s 2s linear", "1s linear 2s", "linear 1s 2s", "width 1s", "1s width", "linear 1s", "1s linear", "1s 2s", "2s 1s", "width", "linear", "1s", "height", "2s", "ease-in-out", "2s ease-in", "opacity linear", "ease-out 2s", "2s color, 1s width, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)", "1s \\32width linear 2s", "1s -width linear 2s", "1s -\\32width linear 2s", "1s \\32 0width linear 2s", "1s -\\32 0width linear 2s", "1s \\2width linear 2s", "1s -\\2width linear 2s" ],
invalid_values: [ "2s, 1s width", "1s width, 2s", "2s all, 1s width", "1s width, 2s all", "1s width, 2s none", "2s none, 1s width", "2s inherit", "inherit 2s", "2s width, 1s inherit", "2s inherit, 1s width", "2s initial", "2s all, 1s width", "2s width, 1s all" ]
},
"-moz-transition-delay": {
@ -1989,7 +1989,7 @@ var gCSSProperties = {
inherited: false,
type: CSS_TYPE_LONGHAND,
initial_values: [ "all" ],
other_values: [ "none", "left", "top", "color", "width, height, opacity", "foobar", "auto" ],
other_values: [ "none", "left", "top", "color", "width, height, opacity", "foobar", "auto", "\\32width", "-width", "-\\32width", "\\32 0width", "-\\32 0width", "\\2width", "-\\2width" ],
invalid_values: [ "none, none", "all, all", "color, none", "none, color", "all, color", "color, all", "inherit, color", "color, inherit", "initial, color", "color, initial", "none, color", "color, none", "all, color", "color, all" ]
},
"-moz-transition-timing-function": {

View File

@ -0,0 +1,57 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=543428
-->
<head>
<title>Test for Bug 543428</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<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"/>
<style type="text/css" id="sheet">p { color: blue; }</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=543428">Mozilla Bug 543428</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 543428 **/
var sheet = document.getElementById("sheet").sheet;
var rule = sheet.cssRules[0];
function set_selector_text(selector)
// no cssText or selectorText setter implemented yet
{
try {
// insertRule might throw on syntax error
sheet.insertRule(selector + " { color : green }", 0);
sheet.deleteRule(1);
} catch(ex) {}
rule = sheet.cssRules[0];
}
is(rule.selectorText, "p", "simple identifier not escaped");
set_selector_text('\\P');
is(rule.selectorText, "P", "simple identifier not escaped");
set_selector_text('\\70');
is(rule.selectorText, "p", "simple identifier not escaped");
set_selector_text('font-family_72756');
is(rule.selectorText, "font-family_72756", "simple identifier not escaped");
set_selector_text('-font-family_72756');
is(rule.selectorText, "-font-family_72756", "simple identifier not escaped");
set_selector_text('-0invalid');
set_selector_text('0invalid');
is(rule.selectorText, "-font-family_72756", "setting invalid value ignored");
set_selector_text('Håkon\\ Lie');
is(rule.selectorText, "Håkon\\ Lie", "escaping done only where needed");
</script>
</pre>
</body>
</html>

View File

@ -764,6 +764,35 @@ function run() {
test_selector_in_html(":-moz-tree-column(a, b (){} a", single_a,
empty_set, set_single, html_default_ns);
// Bug 543428 (escaping)
test_selector_in_html("\\32|a", single_a, set_single, empty_set,
"@namespace \\32 url(http://www.w3.org/1999/xhtml);");
test_selector_in_html("-\\32|a", single_a, set_single, empty_set,
"@namespace -\\32 url(http://www.w3.org/1999/xhtml);");
test_selector_in_html("\\2|a", single_a, set_single, empty_set,
"@namespace \\0002 url(http://www.w3.org/1999/xhtml);");
test_selector_in_html("-\\2|a", single_a, set_single, empty_set,
"@namespace -\\000002 url(http://www.w3.org/1999/xhtml);");
var spans = "<span class='2'></span><span class='&#x2;'></span>" +
"<span id='2'></span><span id='&#x2;'></span>"
test_selector_in_html(".\\32", spans,
bodychildset([0]), bodychildset([1, 2, 3]));
test_selector_in_html("[class=\\32]", spans,
bodychildset([0]), bodychildset([1, 2, 3]));
test_selector_in_html(".\\2", spans,
bodychildset([1]), bodychildset([0, 2, 3]));
test_selector_in_html("[class=\\2]", spans,
bodychildset([1]), bodychildset([0, 2, 3]));
test_selector_in_html("#\\32", spans,
bodychildset([2]), bodychildset([0, 1, 3]));
test_selector_in_html("[id=\\32]", spans,
bodychildset([2]), bodychildset([0, 1, 3]));
test_selector_in_html("#\\2", spans,
bodychildset([3]), bodychildset([0, 1, 2]));
test_selector_in_html("[id=\\2]", spans,
bodychildset([3]), bodychildset([0, 1, 2]));
test_balanced_unparseable("#2");
run_deferred_tests();
}