Skip to content

Commit 8a5d049

Browse files
committed
Make HtmlEscape escape '/' again in UNKNOWN mode.
This is a XSS-prevention recommendation. If escaped code is only ever used inside a quoted attribute or as element text, escapeing '/' is not necessary. However, if the escaped code is inserted inside a tag (for example assuming that it is a well-behavde attribute), then a slash may be meaningful in some cases. Lots of other things can go wrong in that case, so we recommend against it. [email protected] Review URL: https://codereview.chromium.org//1084473003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@45153 260f80e4-7a28-3924-810f-c04153c831b5
1 parent f79bc25 commit 8a5d049

File tree

2 files changed

+45
-13
lines changed

2 files changed

+45
-13
lines changed

sdk/lib/convert/html_escape.dart

+42-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,26 @@
44

55
part of dart.convert;
66

7-
// TODO(floitsch) - Document - Issue 13097
7+
/**
8+
* A `String` converter that converts characters to HTML entities.
9+
*
10+
* This is intended to sanitice text before inserting the text into an HTML
11+
* document. Characters that are meaningful in HTML are converted to
12+
* HTML entities (like `&` for `&`).
13+
*
14+
* The general converter escapes all characters that are meaningful in HTML
15+
* attributes or normal element context. Elements with special content types
16+
* (like CSS or JavaScript) may need a more specialized escaping that
17+
* understands that content type.
18+
*
19+
* If the context where the text will be inserted is known in more detail,
20+
* it's possible to omit escaping some characters (like quotes when not
21+
* inside an attribute value).
22+
*
23+
* The escaped text should only be used inside quoted HTML attributes values
24+
* or as text content of a normal element. Using the escaped text inside a
25+
* tag, but not inside a quoted attribute value, is still dangerous.
26+
*/
827
const HtmlEscape HTML_ESCAPE = const HtmlEscape();
928

1029
/**
@@ -28,6 +47,13 @@ class HtmlEscapeMode {
2847
final bool escapeQuot;
2948
/** Whether to escape "'" (apostrophe). */
3049
final bool escapeApos;
50+
/**
51+
* Whether to escape "/" (forward slash, solidus).
52+
*
53+
* Escaping a slash is recommended to avoid cross-site scripting attacks by
54+
* [the Open Web Application Security Project](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content)
55+
*/
56+
final bool escapeSlash;
3157

3258
/**
3359
* Default escaping mode which escape all characters.
@@ -40,7 +66,7 @@ class HtmlEscapeMode {
4066
* which require escapes matching their particular content syntax.
4167
*/
4268
static const HtmlEscapeMode UNKNOWN =
43-
const HtmlEscapeMode._('unknown', true, true, true);
69+
const HtmlEscapeMode._('unknown', true, true, true, true);
4470

4571
/**
4672
* Escaping mode for text going into double-quoted HTML attribute values.
@@ -51,7 +77,7 @@ class HtmlEscapeMode {
5177
* Escapes only double quotes (`"`) but not single quotes (`'`).
5278
*/
5379
static const HtmlEscapeMode ATTRIBUTE =
54-
const HtmlEscapeMode._('attribute', false, true, false);
80+
const HtmlEscapeMode._('attribute', false, true, false, false);
5581

5682
/**
5783
* Escaping mode for text going into single-quoted HTML attribute values.
@@ -62,7 +88,7 @@ class HtmlEscapeMode {
6288
* Escapes only single quotes (`'`) but not double quotes (`"`).
6389
*/
6490
static const HtmlEscapeMode SQ_ATTRIBUTE =
65-
const HtmlEscapeMode._('attribute', false, false, true);
91+
const HtmlEscapeMode._('attribute', false, false, true, false);
6692

6793
/**
6894
* Escaping mode for text going into HTML element content.
@@ -74,22 +100,26 @@ class HtmlEscapeMode {
74100
* Escapes `<` and `>` characters.
75101
*/
76102
static const HtmlEscapeMode ELEMENT =
77-
const HtmlEscapeMode._('element', true, false, false);
103+
const HtmlEscapeMode._('element', true, false, false, false);
78104

79-
const HtmlEscapeMode._(
80-
this._name, this.escapeLtGt, this.escapeQuot, this.escapeApos);
105+
const HtmlEscapeMode._(this._name,
106+
this.escapeLtGt,
107+
this.escapeQuot,
108+
this.escapeApos,
109+
this.escapeSlash);
81110

82111
/**
83112
* Create a custom escaping mode.
84113
*
85114
* All modes escape `&`.
86115
* The mode can further be set to escape `<` and `>` ([escapeLtGt]),
87-
* `"` ([escapeQuot]) and/or `'` ([escapeApos]).
116+
* `"` ([escapeQuot]), `'` ([escapeApos]), and/or `/` ([escapeSlash]).
88117
*/
89118
const HtmlEscapeMode({String name: "custom",
90119
this.escapeLtGt: false,
91120
this.escapeQuot: false,
92-
this.escapeApos: false}) : _name = name;
121+
this.escapeApos: false,
122+
this.escapeSlash: false}) : _name = name;
93123

94124
String toString() => _name;
95125
}
@@ -108,6 +138,8 @@ class HtmlEscapeMode {
108138
* * `'` (apostrophe) when inside a single-quoted attribute value.
109139
* Apostrophe is escaped as `&#39;` instead of `&apos;` since
110140
* not all browsers understand `&apos;`.
141+
* * `/` (slash) is recommended to be escaped because it may be used
142+
* to terminate an element in some HTML dialects.
111143
*
112144
* Escaping `>` (greater than) isn't necessary, but the result is often
113145
* found to be easier to read if greater-than is also escaped whenever
@@ -150,6 +182,7 @@ class HtmlEscape extends Converter<String, String> {
150182
case "'": if (mode.escapeApos) replacement = '&#39;'; break;
151183
case '<': if (mode.escapeLtGt) replacement = '&lt;'; break;
152184
case '>': if (mode.escapeLtGt) replacement = '&gt;'; break;
185+
case '/': if (mode.escapeSlash) replacement = '&#46;'; break;
153186
}
154187
if (replacement != null) {
155188
if (result == null) result = new StringBuffer();

tests/lib/convert/html_escape_test.dart

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ const _NOOP = 'Nothing_to_escape';
1010

1111
const _TEST_INPUT = """<A </test> of \xA0 "double" & 'single' values>""";
1212

13-
const _OUTPUT_UNKNOWN = '&lt;A &lt;/test&gt; of \xA0 &quot;double&quot; &amp; '
14-
'&#39;single&#39; values&gt;';
13+
const _OUTPUT_UNKNOWN = '&lt;A &lt;&#46;test&gt; of \xA0 &quot;double&quot; '
14+
'&amp; &#39;single&#39; values&gt;';
1515

1616
const _OUTPUT_ATTRIBUTE =
1717
"<A </test> of \xA0 &quot;double&quot; &amp; 'single' values>";
@@ -48,8 +48,7 @@ void _testConvert(HtmlEscape escape, String input, String expected) {
4848
void _testTransform(HtmlEscape escape, String input, String expected) {
4949
var controller = new StreamController(sync: true);
5050

51-
var stream = controller.stream
52-
.transform(escape);
51+
var stream = controller.stream.transform(escape);
5352

5453
var done = false;
5554
int count = 0;

0 commit comments

Comments
 (0)