Skip to content

Commit 3134804

Browse files
committed
move the htmlescaping logic out and refactor it
1 parent 90ce42a commit 3134804

File tree

4 files changed

+178
-90
lines changed

4 files changed

+178
-90
lines changed

compiler/src/main/java/com/github/mustachejava/DefaultMustacheFactory.java

Lines changed: 3 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import java.util.concurrent.ExecutionException;
1515
import java.util.concurrent.ExecutorService;
1616

17+
import static com.github.mustachejava.util.HtmlEscaper.escape;
18+
1719
/**
1820
* Simplest possible code factory
1921
*/
@@ -82,95 +84,7 @@ public Reader getReader(String resourceName) {
8284
// to change the way encoding works.
8385
@Override
8486
public void encode(String value, Writer writer) {
85-
try {
86-
int position = 0;
87-
int length = value.length();
88-
for (int i = 0; i < length; i++) {
89-
char c = value.charAt(i);
90-
switch (c) {
91-
case '&':
92-
// If we match an entity or char ref then keep it
93-
// as is in the text. Otherwise, replace it.
94-
if (matchesEntityRef(i + 1, length, value)) {
95-
// If we are at the beginning we can just keep going
96-
if (position != 0) {
97-
position = append(value, writer, position, i, "&");
98-
}
99-
} else {
100-
position = append(value, writer, position, i, "&amp;");
101-
}
102-
break;
103-
case '<':
104-
position = append(value, writer, position, i, "&lt;");
105-
break;
106-
case '>':
107-
position = append(value, writer, position, i, "&gt;");
108-
break;
109-
case '"':
110-
position = append(value, writer, position, i, "&quot;");
111-
break;
112-
case '\'':
113-
position = append(value, writer, position, i, "&#39;");
114-
break;
115-
case '/':
116-
position = append(value, writer, position, i, "&#x2F;");
117-
break;
118-
case '\n':
119-
position = append(value, writer, position, i, "&#10;");
120-
break;
121-
}
122-
}
123-
writer.append(value, position, length);
124-
} catch (IOException e) {
125-
throw new MustacheException("Failed to encode value: " + value);
126-
}
127-
}
128-
129-
private int append(String value, Writer writer, int position, int i, String replace) throws IOException {
130-
// Append the clean text
131-
writer.append(value, position, i);
132-
// Append the encoded value
133-
writer.append(replace);
134-
// and advance the position past it
135-
return i + 1;
136-
}
137-
138-
// Matches all HTML named and character entity references
139-
private boolean matchesEntityRef(int position, int length, String value) {
140-
for (int i = position; i < length; i++) {
141-
char c = value.charAt(i);
142-
switch (c) {
143-
case ';':
144-
// End of the entity
145-
return i == position;
146-
case '#':
147-
// Switch to char reference
148-
return i == position && matchesCharRef(i + 1, length, value);
149-
default:
150-
// Letters can be at the start
151-
if (c >= 'a' && c <= 'z') continue;
152-
if (c >= 'A' && c <= 'Z') continue;
153-
if (i != position) {
154-
// Can only appear in the middle
155-
if (c >= '0' && c <= '9') continue;
156-
}
157-
return false;
158-
}
159-
}
160-
// Didn't find ending ;
161-
return false;
162-
}
163-
164-
private boolean matchesCharRef(int position, int length, String value) {
165-
for (int i = position; i < length; i++) {
166-
char c = value.charAt(i);
167-
if (c == ';') {
168-
return i == position;
169-
} else if (c < '0' || c > '9') {
170-
return false;
171-
}
172-
}
173-
return false;
87+
escape(value, writer);
17488
}
17589

17690
@Override
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.github.mustachejava.util;
2+
3+
import com.github.mustachejava.MustacheException;
4+
5+
import java.io.IOException;
6+
import java.io.Writer;
7+
8+
/**
9+
* Escapes user data that you wish to include in HTML pages.
10+
*/
11+
public class HtmlEscaper {
12+
public static void escape(String value, Writer writer) {
13+
try {
14+
int position = 0;
15+
int length = value.length();
16+
for (int i = 0; i < length; i++) {
17+
char c = value.charAt(i);
18+
switch (c) {
19+
case '&':
20+
// If we match an entity or char ref then keep it
21+
// as is in the text. Otherwise, replace it.
22+
if (matchesEntityRef(i + 1, length, value)) {
23+
// If we are at the beginning we can just keep going
24+
if (position != 0) {
25+
position = append(value, writer, position, i, "&");
26+
}
27+
} else {
28+
position = append(value, writer, position, i, "&amp;");
29+
}
30+
break;
31+
case '<':
32+
position = append(value, writer, position, i, "&lt;");
33+
break;
34+
case '>':
35+
position = append(value, writer, position, i, "&gt;");
36+
break;
37+
case '"':
38+
position = append(value, writer, position, i, "&quot;");
39+
break;
40+
case '\'':
41+
position = append(value, writer, position, i, "&#39;");
42+
break;
43+
case '/':
44+
position = append(value, writer, position, i, "&#x2F;");
45+
break;
46+
case '\n':
47+
position = append(value, writer, position, i, "&#10;");
48+
break;
49+
}
50+
}
51+
writer.append(value, position, length);
52+
} catch (IOException e) {
53+
throw new MustacheException("Failed to encode value: " + value);
54+
}
55+
}
56+
57+
private static int append(String value, Writer writer, int position, int i, String replace) throws IOException {
58+
// Append the clean text
59+
writer.append(value, position, i);
60+
// Append the encoded value
61+
writer.append(replace);
62+
// and advance the position past it
63+
return i + 1;
64+
}
65+
66+
// Matches all HTML named and character entity references
67+
private static boolean matchesEntityRef(int position, int length, String value) {
68+
for (int i = position; i < length; i++) {
69+
char c = value.charAt(i);
70+
switch (c) {
71+
case ';':
72+
// End of the entity
73+
return i != position;
74+
case '#':
75+
// Switch to char reference
76+
return i == position && matchesCharRef(i + 1, length, value);
77+
default:
78+
// Letters can be at the start
79+
if (c >= 'a' && c <= 'z') continue;
80+
if (c >= 'A' && c <= 'Z') continue;
81+
if (i != position) {
82+
// Can only appear in the middle
83+
if (c >= '0' && c <= '9') continue;
84+
}
85+
return false;
86+
}
87+
}
88+
// Didn't find ending ;
89+
return false;
90+
}
91+
92+
private static boolean matchesCharRef(int position, int length, String value) {
93+
for (int i = position; i < length; i++) {
94+
char c = value.charAt(i);
95+
if (c == ';') {
96+
return i != position;
97+
} else if ((c >= '0' && c <= '9') || c == 'x' || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
98+
} else return false;
99+
}
100+
return false;
101+
}
102+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.github.mustachejava.util;
2+
3+
import junit.framework.TestCase;
4+
5+
import java.io.StringWriter;
6+
7+
import static com.github.mustachejava.util.HtmlEscaper.escape;
8+
9+
public class HtmlEscaperTest extends TestCase {
10+
public void testEscape() throws Exception {
11+
{
12+
StringWriter sw = new StringWriter();
13+
escape("Hello, world!", sw);
14+
assertEquals("Hello, world!", sw.toString());
15+
}
16+
{
17+
StringWriter sw = new StringWriter();
18+
escape("Hello & world!", sw);
19+
assertEquals("Hello &amp; world!", sw.toString());
20+
}
21+
{
22+
StringWriter sw = new StringWriter();
23+
escape("Hello &amp; world!", sw);
24+
assertEquals("Hello &amp; world!", sw.toString());
25+
}
26+
{
27+
StringWriter sw = new StringWriter();
28+
escape("Hello &amp world!", sw);
29+
assertEquals("Hello &amp;amp world!", sw.toString());
30+
}
31+
{
32+
StringWriter sw = new StringWriter();
33+
escape("\"Hello\" &amp world!", sw);
34+
assertEquals("&quot;Hello&quot; &amp;amp world!", sw.toString());
35+
}
36+
{
37+
StringWriter sw = new StringWriter();
38+
escape("\"Hello\" &amp world!&#10;", sw);
39+
assertEquals("&quot;Hello&quot; &amp;amp world!&#10;", sw.toString());
40+
}
41+
{
42+
StringWriter sw = new StringWriter();
43+
escape("\"Hello\" &amp <world>!\n", sw);
44+
assertEquals("&quot;Hello&quot; &amp;amp &lt;world&gt;!&#10;", sw.toString());
45+
}
46+
{
47+
StringWriter sw = new StringWriter();
48+
escape("\"Hello\" &amp world!\n&sam", sw);
49+
assertEquals("&quot;Hello&quot; &amp;amp world!&#10;&amp;sam", sw.toString());
50+
}
51+
{
52+
StringWriter sw = new StringWriter();
53+
escape("\"Hello\" &amp 'world'!\n&sam", sw);
54+
assertEquals("&quot;Hello&quot; &amp;amp &#39;world&#39;!&#10;&amp;sam", sw.toString());
55+
}
56+
{
57+
StringWriter sw = new StringWriter();
58+
escape("\"/Hello/\" &amp 'world'!\n&sam", sw);
59+
assertEquals("&quot;&#x2F;Hello&#x2F;&quot; &amp;amp &#39;world&#39;!&#10;&amp;sam", sw.toString());
60+
}
61+
{
62+
StringWriter sw = new StringWriter();
63+
escape("\"/Hello/\" &amp&#zz 'world'!\n&sam", sw);
64+
assertEquals("&quot;&#x2F;Hello&#x2F;&quot; &amp;amp&amp;#zz &#39;world&#39;!&#10;&amp;sam", sw.toString());
65+
}
66+
{
67+
StringWriter sw = new StringWriter();
68+
escape("\"/Hello/\" &amp&#zz 'world'!\n&sam&#", sw);
69+
assertEquals("&quot;&#x2F;Hello&#x2F;&quot; &amp;amp&amp;#zz &#39;world&#39;!&#10;&amp;sam&amp;#", sw.toString());
70+
}
71+
}
72+
}

indy/src/test/java/IndyDemo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public static void timeIndyOHNoGuard(IndyDemo indyDemo) throws Throwable {
6767
for (int i = 0; i < 100000000; i++) {
6868
INDY_NOGUARD.call(scopes);
6969
}
70-
System.out.println("indy OH: " + (System.currentTimeMillis() - start));
70+
System.out.println("indy OH no guard: " + (System.currentTimeMillis() - start));
7171
}
7272

7373
public static void timeReflection(IndyDemo indyDemo) throws Throwable {

0 commit comments

Comments
 (0)