summaryrefslogtreecommitdiff
path: root/distrib
diff options
context:
space:
mode:
authormikesamuel <mikesamuel@ad8eed46-c659-4a31-e19d-951d88f54425>2011-04-04 23:27:09 +0000
committermikesamuel <mikesamuel@ad8eed46-c659-4a31-e19d-951d88f54425>2011-04-04 23:27:09 +0000
commit8560af5e2982092cb27cce62aa9cfa5bb45ea387 (patch)
tree545c85e3e4dc53ea86530ef0508dc66b59cd05f4 /distrib
parent846d5d0377617bd20ac271a486f07bfe757cc7a2 (diff)
downloadsanitizer-8560af5e2982092cb27cce62aa9cfa5bb45ea387.tar.gz
Fixed CDATA rendering
git-svn-id: http://owasp-java-html-sanitizer.googlecode.com/svn/trunk@27 ad8eed46-c659-4a31-e19d-951d88f54425
Diffstat (limited to 'distrib')
-rw-r--r--distrib/javadoc/org/owasp/html/HtmlStreamRenderer.html2
-rw-r--r--distrib/javadoc/src-html/org/owasp/html/HtmlStreamRenderer.html354
-rw-r--r--distrib/lib/owasp-java-html-sanitizer-src.jarbin70302 -> 70636 bytes
-rw-r--r--distrib/lib/owasp-java-html-sanitizer.jarbin79724 -> 79877 bytes
4 files changed, 192 insertions, 164 deletions
diff --git a/distrib/javadoc/org/owasp/html/HtmlStreamRenderer.html b/distrib/javadoc/org/owasp/html/HtmlStreamRenderer.html
index 6d2639c..8ef44ae 100644
--- a/distrib/javadoc/org/owasp/html/HtmlStreamRenderer.html
+++ b/distrib/javadoc/org/owasp/html/HtmlStreamRenderer.html
@@ -328,7 +328,7 @@ public final void <A HREF="../../../src-html/org/owasp/html/HtmlStreamRenderer.h
<A NAME="text(java.lang.String)"><!-- --></A><H3>
text</H3>
<PRE>
-public final void <A HREF="../../../src-html/org/owasp/html/HtmlStreamRenderer.html#line.215"><B>text</B></A>(java.lang.String&nbsp;text)</PRE>
+public final void <A HREF="../../../src-html/org/owasp/html/HtmlStreamRenderer.html#line.221"><B>text</B></A>(java.lang.String&nbsp;text)</PRE>
<DL>
<DD><DL>
<DT><B>Specified by:</B><DD><CODE><A HREF="../../../org/owasp/html/HtmlStreamEventReceiver.html#text(java.lang.String)">text</A></CODE> in interface <CODE><A HREF="../../../org/owasp/html/HtmlStreamEventReceiver.html" title="interface in org.owasp.html">HtmlStreamEventReceiver</A></CODE></DL>
diff --git a/distrib/javadoc/src-html/org/owasp/html/HtmlStreamRenderer.html b/distrib/javadoc/src-html/org/owasp/html/HtmlStreamRenderer.html
index 40dce0e..380cbfe 100644
--- a/distrib/javadoc/src-html/org/owasp/html/HtmlStreamRenderer.html
+++ b/distrib/javadoc/src-html/org/owasp/html/HtmlStreamRenderer.html
@@ -201,175 +201,203 @@
<FONT color="green">198</FONT> error("Tag content cannot appear inside CDATA element", elementName);<a name="line.198"></a>
<FONT color="green">199</FONT> return;<a name="line.199"></a>
<FONT color="green">200</FONT> } else {<a name="line.200"></a>
-<FONT color="green">201</FONT> String unescaped = pendingUnescaped.toString();<a name="line.201"></a>
+<FONT color="green">201</FONT> StringBuilder cdataContent = pendingUnescaped;<a name="line.201"></a>
<FONT color="green">202</FONT> pendingUnescaped = null;<a name="line.202"></a>
-<FONT color="green">203</FONT> if (!containsCloseTag(unescaped, lastTagOpened)) {<a name="line.203"></a>
-<FONT color="green">204</FONT> output.append(unescaped);<a name="line.204"></a>
-<FONT color="green">205</FONT> } else {<a name="line.205"></a>
-<FONT color="green">206</FONT> error("Unescaped text content contains close tag", elementName);<a name="line.206"></a>
-<FONT color="green">207</FONT> // Still output the close tag.<a name="line.207"></a>
-<FONT color="green">208</FONT> }<a name="line.208"></a>
-<FONT color="green">209</FONT> }<a name="line.209"></a>
-<FONT color="green">210</FONT> if ("plaintext".equals(elementName)) { return; }<a name="line.210"></a>
-<FONT color="green">211</FONT> }<a name="line.211"></a>
-<FONT color="green">212</FONT> output.append("&lt;/").append(elementName).append("&gt;");<a name="line.212"></a>
-<FONT color="green">213</FONT> }<a name="line.213"></a>
-<FONT color="green">214</FONT> <a name="line.214"></a>
-<FONT color="green">215</FONT> public final void text(String text) {<a name="line.215"></a>
-<FONT color="green">216</FONT> try {<a name="line.216"></a>
-<FONT color="green">217</FONT> writeText(text);<a name="line.217"></a>
-<FONT color="green">218</FONT> } catch (IOException ex) {<a name="line.218"></a>
-<FONT color="green">219</FONT> ioExHandler.handle(ex);<a name="line.219"></a>
-<FONT color="green">220</FONT> }<a name="line.220"></a>
-<FONT color="green">221</FONT> }<a name="line.221"></a>
-<FONT color="green">222</FONT> <a name="line.222"></a>
-<FONT color="green">223</FONT> private final void writeText(String text) throws IOException {<a name="line.223"></a>
-<FONT color="green">224</FONT> if (!open) { throw new IllegalStateException(); }<a name="line.224"></a>
-<FONT color="green">225</FONT> if (pendingUnescaped != null) {<a name="line.225"></a>
-<FONT color="green">226</FONT> pendingUnescaped.append(text.replaceAll("\0", ""));<a name="line.226"></a>
-<FONT color="green">227</FONT> } else {<a name="line.227"></a>
-<FONT color="green">228</FONT> escapeHtmlOnto(text, output); // Works for RCDATA.<a name="line.228"></a>
-<FONT color="green">229</FONT> }<a name="line.229"></a>
-<FONT color="green">230</FONT> }<a name="line.230"></a>
-<FONT color="green">231</FONT> <a name="line.231"></a>
-<FONT color="green">232</FONT> private static boolean containsCloseTag(String unescaped, String tagName) {<a name="line.232"></a>
-<FONT color="green">233</FONT> boolean allowEscapingTextSpan = HtmlTextEscapingMode.allowsEscapingTextSpan(<a name="line.233"></a>
-<FONT color="green">234</FONT> tagName);<a name="line.234"></a>
-<FONT color="green">235</FONT> <a name="line.235"></a>
-<FONT color="green">236</FONT> int unescapedLength = unescaped.length();<a name="line.236"></a>
-<FONT color="green">237</FONT> int tagNameLength = tagName.length();<a name="line.237"></a>
-<FONT color="green">238</FONT> int limit = unescapedLength - tagName.length() - 2;<a name="line.238"></a>
-<FONT color="green">239</FONT> for (int i = -1; (i = unescaped.indexOf('&lt;', i + 1)) &gt;= 0;) {<a name="line.239"></a>
-<FONT color="green">240</FONT> if (i &lt;= limit &amp;&amp; '/' == unescaped.charAt(i + 1)<a name="line.240"></a>
-<FONT color="green">241</FONT> &amp;&amp; Strings.regionMatchesIgnoreCase(<a name="line.241"></a>
-<FONT color="green">242</FONT> unescaped, i + 2, tagName, 0, tagNameLength)) {<a name="line.242"></a>
-<FONT color="green">243</FONT> // Content cannot be embedded.<a name="line.243"></a>
-<FONT color="green">244</FONT> return true;<a name="line.244"></a>
-<FONT color="green">245</FONT> } else if (allowEscapingTextSpan &amp;&amp; i + 4 &lt;= unescapedLength<a name="line.245"></a>
-<FONT color="green">246</FONT> &amp;&amp; '!' == unescaped.charAt(i + 1)<a name="line.246"></a>
-<FONT color="green">247</FONT> &amp;&amp; '-' == unescaped.charAt(i + 2)<a name="line.247"></a>
-<FONT color="green">248</FONT> &amp;&amp; '-' == unescaped.charAt(i + 3)) {<a name="line.248"></a>
-<FONT color="green">249</FONT> // HTML 5 allows the end of an escaping text span to share dashes with<a name="line.249"></a>
-<FONT color="green">250</FONT> // the open : &lt;!--&gt; and &lt;!---&gt; are both fully formed.<a name="line.250"></a>
-<FONT color="green">251</FONT> if (i + 4 &lt; unescapedLength &amp;&amp; unescaped.charAt(i + 4) == '&gt;') {<a name="line.251"></a>
-<FONT color="green">252</FONT> i = i + 5;<a name="line.252"></a>
-<FONT color="green">253</FONT> } else if (i + 5 &lt; unescapedLength<a name="line.253"></a>
-<FONT color="green">254</FONT> &amp;&amp; unescaped.charAt(i + 4) == '-'<a name="line.254"></a>
-<FONT color="green">255</FONT> &amp;&amp; unescaped.charAt(i + 5) == '&gt;') {<a name="line.255"></a>
-<FONT color="green">256</FONT> i = i + 6;<a name="line.256"></a>
-<FONT color="green">257</FONT> } else {<a name="line.257"></a>
-<FONT color="green">258</FONT> i = unescaped.indexOf("--&gt;", i + 4);<a name="line.258"></a>
-<FONT color="green">259</FONT> if (i &lt; 0) {<a name="line.259"></a>
-<FONT color="green">260</FONT> // If the escaping text span is not closed, then final close tag<a name="line.260"></a>
-<FONT color="green">261</FONT> // would be covered by the unclosed escaping text span.<a name="line.261"></a>
-<FONT color="green">262</FONT> return true;<a name="line.262"></a>
-<FONT color="green">263</FONT> }<a name="line.263"></a>
-<FONT color="green">264</FONT> }<a name="line.264"></a>
-<FONT color="green">265</FONT> }<a name="line.265"></a>
-<FONT color="green">266</FONT> }<a name="line.266"></a>
-<FONT color="green">267</FONT> return false;<a name="line.267"></a>
-<FONT color="green">268</FONT> }<a name="line.268"></a>
-<FONT color="green">269</FONT> <a name="line.269"></a>
-<FONT color="green">270</FONT> <a name="line.270"></a>
-<FONT color="green">271</FONT> @VisibleForTesting<a name="line.271"></a>
-<FONT color="green">272</FONT> static boolean isValidHtmlName(String name) {<a name="line.272"></a>
-<FONT color="green">273</FONT> int n = name.length();<a name="line.273"></a>
-<FONT color="green">274</FONT> if (n == 0) { return false; }<a name="line.274"></a>
-<FONT color="green">275</FONT> if (n &gt; 128) { return false; }<a name="line.275"></a>
-<FONT color="green">276</FONT> boolean isNamespaced = false;<a name="line.276"></a>
-<FONT color="green">277</FONT> for (int i = 0; i &lt; n; ++i) {<a name="line.277"></a>
-<FONT color="green">278</FONT> char ch = name.charAt(i);<a name="line.278"></a>
-<FONT color="green">279</FONT> switch (ch) {<a name="line.279"></a>
-<FONT color="green">280</FONT> case ':':<a name="line.280"></a>
-<FONT color="green">281</FONT> if (isNamespaced) { return false; }<a name="line.281"></a>
-<FONT color="green">282</FONT> isNamespaced = true;<a name="line.282"></a>
-<FONT color="green">283</FONT> if (i == 0 || i + 1 == n) { return false; }<a name="line.283"></a>
-<FONT color="green">284</FONT> break;<a name="line.284"></a>
-<FONT color="green">285</FONT> case '-':<a name="line.285"></a>
-<FONT color="green">286</FONT> if (i == 0 || i + 1 == n) { return false; }<a name="line.286"></a>
+<FONT color="green">203</FONT> int problemIndex = checkHtmlCdataCloseable(lastTagOpened, cdataContent);<a name="line.203"></a>
+<FONT color="green">204</FONT> if (problemIndex == -1) {<a name="line.204"></a>
+<FONT color="green">205</FONT> output.append(cdataContent);<a name="line.205"></a>
+<FONT color="green">206</FONT> } else {<a name="line.206"></a>
+<FONT color="green">207</FONT> error(<a name="line.207"></a>
+<FONT color="green">208</FONT> "Invalid CDATA text content",<a name="line.208"></a>
+<FONT color="green">209</FONT> cdataContent.subSequence(<a name="line.209"></a>
+<FONT color="green">210</FONT> problemIndex,<a name="line.210"></a>
+<FONT color="green">211</FONT> Math.min(problemIndex + 10, cdataContent.length()))<a name="line.211"></a>
+<FONT color="green">212</FONT> .toString());<a name="line.212"></a>
+<FONT color="green">213</FONT> // Still output the close tag.<a name="line.213"></a>
+<FONT color="green">214</FONT> }<a name="line.214"></a>
+<FONT color="green">215</FONT> }<a name="line.215"></a>
+<FONT color="green">216</FONT> if ("plaintext".equals(elementName)) { return; }<a name="line.216"></a>
+<FONT color="green">217</FONT> }<a name="line.217"></a>
+<FONT color="green">218</FONT> output.append("&lt;/").append(elementName).append("&gt;");<a name="line.218"></a>
+<FONT color="green">219</FONT> }<a name="line.219"></a>
+<FONT color="green">220</FONT> <a name="line.220"></a>
+<FONT color="green">221</FONT> public final void text(String text) {<a name="line.221"></a>
+<FONT color="green">222</FONT> try {<a name="line.222"></a>
+<FONT color="green">223</FONT> writeText(text);<a name="line.223"></a>
+<FONT color="green">224</FONT> } catch (IOException ex) {<a name="line.224"></a>
+<FONT color="green">225</FONT> ioExHandler.handle(ex);<a name="line.225"></a>
+<FONT color="green">226</FONT> }<a name="line.226"></a>
+<FONT color="green">227</FONT> }<a name="line.227"></a>
+<FONT color="green">228</FONT> <a name="line.228"></a>
+<FONT color="green">229</FONT> private final void writeText(String text) throws IOException {<a name="line.229"></a>
+<FONT color="green">230</FONT> if (!open) { throw new IllegalStateException(); }<a name="line.230"></a>
+<FONT color="green">231</FONT> if (pendingUnescaped != null) {<a name="line.231"></a>
+<FONT color="green">232</FONT> pendingUnescaped.append(text.replaceAll("\0", ""));<a name="line.232"></a>
+<FONT color="green">233</FONT> } else {<a name="line.233"></a>
+<FONT color="green">234</FONT> escapeHtmlOnto(text, output); // Works for RCDATA.<a name="line.234"></a>
+<FONT color="green">235</FONT> }<a name="line.235"></a>
+<FONT color="green">236</FONT> }<a name="line.236"></a>
+<FONT color="green">237</FONT> <a name="line.237"></a>
+<FONT color="green">238</FONT> private static int checkHtmlCdataCloseable(<a name="line.238"></a>
+<FONT color="green">239</FONT> String localName, StringBuilder sb) {<a name="line.239"></a>
+<FONT color="green">240</FONT> int escapingTextSpanStart = -1;<a name="line.240"></a>
+<FONT color="green">241</FONT> for (int i = 0, n = sb.length(); i &lt; n; ++i) {<a name="line.241"></a>
+<FONT color="green">242</FONT> char ch = sb.charAt(i);<a name="line.242"></a>
+<FONT color="green">243</FONT> switch (ch) {<a name="line.243"></a>
+<FONT color="green">244</FONT> case '&lt;':<a name="line.244"></a>
+<FONT color="green">245</FONT> if (i + 3 &lt; n<a name="line.245"></a>
+<FONT color="green">246</FONT> &amp;&amp; '!' == sb.charAt(i + 1)<a name="line.246"></a>
+<FONT color="green">247</FONT> &amp;&amp; '-' == sb.charAt(i + 2)<a name="line.247"></a>
+<FONT color="green">248</FONT> &amp;&amp; '-' == sb.charAt(i + 3)) {<a name="line.248"></a>
+<FONT color="green">249</FONT> if (escapingTextSpanStart == -1) {<a name="line.249"></a>
+<FONT color="green">250</FONT> escapingTextSpanStart = i;<a name="line.250"></a>
+<FONT color="green">251</FONT> } else {<a name="line.251"></a>
+<FONT color="green">252</FONT> return i;<a name="line.252"></a>
+<FONT color="green">253</FONT> }<a name="line.253"></a>
+<FONT color="green">254</FONT> } else if (i + 1 + localName.length() &lt; n<a name="line.254"></a>
+<FONT color="green">255</FONT> &amp;&amp; '/' == sb.charAt(i + 1)<a name="line.255"></a>
+<FONT color="green">256</FONT> &amp;&amp; Strings.regionMatchesIgnoreCase(<a name="line.256"></a>
+<FONT color="green">257</FONT> sb, i + 2, localName, 0, localName.length())) {<a name="line.257"></a>
+<FONT color="green">258</FONT> // A close tag contained in the content.<a name="line.258"></a>
+<FONT color="green">259</FONT> if (escapingTextSpanStart &lt; 0) {<a name="line.259"></a>
+<FONT color="green">260</FONT> // We could try some recovery strategies here.<a name="line.260"></a>
+<FONT color="green">261</FONT> // E.g. prepending "/&lt;!--\n" to sb if "script".equals(localName)<a name="line.261"></a>
+<FONT color="green">262</FONT> return i;<a name="line.262"></a>
+<FONT color="green">263</FONT> }<a name="line.263"></a>
+<FONT color="green">264</FONT> if (!"script".equals(localName)) {<a name="line.264"></a>
+<FONT color="green">265</FONT> // Script tags are commonly included inside script tags.<a name="line.265"></a>
+<FONT color="green">266</FONT> // &lt;script&gt;&lt;!--document.write('&lt;script&gt;f()&lt;/script&gt;');--&gt;&lt;/script&gt;<a name="line.266"></a>
+<FONT color="green">267</FONT> // but this does not happen in other CDATA element types.<a name="line.267"></a>
+<FONT color="green">268</FONT> // Actually allowing an end tag inside others is problematic.<a name="line.268"></a>
+<FONT color="green">269</FONT> // Specifically,<a name="line.269"></a>
+<FONT color="green">270</FONT> // &lt;style&gt;&lt;!--&lt;/style&gt;--&gt;/* foo */&lt;/style&gt;<a name="line.270"></a>
+<FONT color="green">271</FONT> // displays the text "/* foo */" on some browsers.<a name="line.271"></a>
+<FONT color="green">272</FONT> return i;<a name="line.272"></a>
+<FONT color="green">273</FONT> }<a name="line.273"></a>
+<FONT color="green">274</FONT> }<a name="line.274"></a>
+<FONT color="green">275</FONT> break;<a name="line.275"></a>
+<FONT color="green">276</FONT> case '&gt;':<a name="line.276"></a>
+<FONT color="green">277</FONT> // From the HTML5 spec:<a name="line.277"></a>
+<FONT color="green">278</FONT> // The text in style, script, title, and textarea elements must not<a name="line.278"></a>
+<FONT color="green">279</FONT> // have an escaping text span start that is not followed by an<a name="line.279"></a>
+<FONT color="green">280</FONT> // escaping text span end.<a name="line.280"></a>
+<FONT color="green">281</FONT> // We look left since the HTML 5 spec allows the escaping text span<a name="line.281"></a>
+<FONT color="green">282</FONT> // end to share dashes with the start.<a name="line.282"></a>
+<FONT color="green">283</FONT> if (i &gt;= 2 &amp;&amp; '-' == sb.charAt(i - 1) &amp;&amp; '-' == sb.charAt(i - 2)) {<a name="line.283"></a>
+<FONT color="green">284</FONT> if (escapingTextSpanStart &lt; 0) { return i - 2; }<a name="line.284"></a>
+<FONT color="green">285</FONT> escapingTextSpanStart = -1;<a name="line.285"></a>
+<FONT color="green">286</FONT> }<a name="line.286"></a>
<FONT color="green">287</FONT> break;<a name="line.287"></a>
-<FONT color="green">288</FONT> default:<a name="line.288"></a>
-<FONT color="green">289</FONT> if (ch &lt;= '9') {<a name="line.289"></a>
-<FONT color="green">290</FONT> if (i == 0 || ch &lt; '0') { return false; }<a name="line.290"></a>
-<FONT color="green">291</FONT> } else if ('A' &lt;= ch &amp;&amp; ch &lt;= 'z') {<a name="line.291"></a>
-<FONT color="green">292</FONT> if ('Z' &lt; ch &amp;&amp; ch &lt; 'a') { return false; }<a name="line.292"></a>
-<FONT color="green">293</FONT> } else {<a name="line.293"></a>
-<FONT color="green">294</FONT> return false;<a name="line.294"></a>
-<FONT color="green">295</FONT> }<a name="line.295"></a>
-<FONT color="green">296</FONT> break;<a name="line.296"></a>
-<FONT color="green">297</FONT> }<a name="line.297"></a>
-<FONT color="green">298</FONT> }<a name="line.298"></a>
-<FONT color="green">299</FONT> return true;<a name="line.299"></a>
-<FONT color="green">300</FONT> }<a name="line.300"></a>
-<FONT color="green">301</FONT> <a name="line.301"></a>
-<FONT color="green">302</FONT> @SuppressWarnings("fallthrough")<a name="line.302"></a>
-<FONT color="green">303</FONT> static void escapeHtmlOnto(String plainText, Appendable output)<a name="line.303"></a>
-<FONT color="green">304</FONT> throws IOException {<a name="line.304"></a>
-<FONT color="green">305</FONT> int n = plainText.length();<a name="line.305"></a>
-<FONT color="green">306</FONT> int pos = 0;<a name="line.306"></a>
-<FONT color="green">307</FONT> for (int i = 0; i &lt; n; ++i) {<a name="line.307"></a>
-<FONT color="green">308</FONT> char ch = plainText.charAt(i);<a name="line.308"></a>
-<FONT color="green">309</FONT> switch (ch) {<a name="line.309"></a>
-<FONT color="green">310</FONT> case '&lt;':<a name="line.310"></a>
-<FONT color="green">311</FONT> output.append(plainText, pos, i).append("&amp;lt;");<a name="line.311"></a>
-<FONT color="green">312</FONT> pos = i + 1;<a name="line.312"></a>
-<FONT color="green">313</FONT> break;<a name="line.313"></a>
-<FONT color="green">314</FONT> case '&gt;':<a name="line.314"></a>
-<FONT color="green">315</FONT> output.append(plainText, pos, i).append("&amp;gt;");<a name="line.315"></a>
-<FONT color="green">316</FONT> pos = i + 1;<a name="line.316"></a>
-<FONT color="green">317</FONT> break;<a name="line.317"></a>
-<FONT color="green">318</FONT> case '&amp;':<a name="line.318"></a>
-<FONT color="green">319</FONT> output.append(plainText, pos, i).append("&amp;amp;");<a name="line.319"></a>
-<FONT color="green">320</FONT> pos = i + 1;<a name="line.320"></a>
-<FONT color="green">321</FONT> break;<a name="line.321"></a>
-<FONT color="green">322</FONT> case '"':<a name="line.322"></a>
-<FONT color="green">323</FONT> output.append(plainText, pos, i).append("&amp;#34;");<a name="line.323"></a>
-<FONT color="green">324</FONT> pos = i + 1;<a name="line.324"></a>
-<FONT color="green">325</FONT> break;<a name="line.325"></a>
-<FONT color="green">326</FONT> case '\r': case '\n': break;<a name="line.326"></a>
-<FONT color="green">327</FONT> default:<a name="line.327"></a>
-<FONT color="green">328</FONT> if (0x20 &lt;= ch &amp;&amp; ch &lt; 0xff00) {<a name="line.328"></a>
-<FONT color="green">329</FONT> continue;<a name="line.329"></a>
-<FONT color="green">330</FONT> }<a name="line.330"></a>
-<FONT color="green">331</FONT> // Is a control character or possible full-width version of a<a name="line.331"></a>
-<FONT color="green">332</FONT> // special character.<a name="line.332"></a>
-<FONT color="green">333</FONT> // FALL-THROUGH<a name="line.333"></a>
-<FONT color="green">334</FONT> case '+': // UTF-7<a name="line.334"></a>
-<FONT color="green">335</FONT> case '=': // Special in attributes.<a name="line.335"></a>
-<FONT color="green">336</FONT> case '@': // Conditional compilation<a name="line.336"></a>
-<FONT color="green">337</FONT> case '\'': case '`': // Quoting character<a name="line.337"></a>
-<FONT color="green">338</FONT> output.append(plainText, pos, i).append("&amp;#")<a name="line.338"></a>
-<FONT color="green">339</FONT> .append(String.valueOf((int) ch)).append(';');<a name="line.339"></a>
+<FONT color="green">288</FONT> }<a name="line.288"></a>
+<FONT color="green">289</FONT> }<a name="line.289"></a>
+<FONT color="green">290</FONT> if (escapingTextSpanStart &gt;= 0) {<a name="line.290"></a>
+<FONT color="green">291</FONT> // We could try recovery strategies here.<a name="line.291"></a>
+<FONT color="green">292</FONT> // E.g. appending "//--&gt;" to the buffer if "script".equals(localName)<a name="line.292"></a>
+<FONT color="green">293</FONT> return escapingTextSpanStart;<a name="line.293"></a>
+<FONT color="green">294</FONT> }<a name="line.294"></a>
+<FONT color="green">295</FONT> return -1;<a name="line.295"></a>
+<FONT color="green">296</FONT> }<a name="line.296"></a>
+<FONT color="green">297</FONT> <a name="line.297"></a>
+<FONT color="green">298</FONT> <a name="line.298"></a>
+<FONT color="green">299</FONT> @VisibleForTesting<a name="line.299"></a>
+<FONT color="green">300</FONT> static boolean isValidHtmlName(String name) {<a name="line.300"></a>
+<FONT color="green">301</FONT> int n = name.length();<a name="line.301"></a>
+<FONT color="green">302</FONT> if (n == 0) { return false; }<a name="line.302"></a>
+<FONT color="green">303</FONT> if (n &gt; 128) { return false; }<a name="line.303"></a>
+<FONT color="green">304</FONT> boolean isNamespaced = false;<a name="line.304"></a>
+<FONT color="green">305</FONT> for (int i = 0; i &lt; n; ++i) {<a name="line.305"></a>
+<FONT color="green">306</FONT> char ch = name.charAt(i);<a name="line.306"></a>
+<FONT color="green">307</FONT> switch (ch) {<a name="line.307"></a>
+<FONT color="green">308</FONT> case ':':<a name="line.308"></a>
+<FONT color="green">309</FONT> if (isNamespaced) { return false; }<a name="line.309"></a>
+<FONT color="green">310</FONT> isNamespaced = true;<a name="line.310"></a>
+<FONT color="green">311</FONT> if (i == 0 || i + 1 == n) { return false; }<a name="line.311"></a>
+<FONT color="green">312</FONT> break;<a name="line.312"></a>
+<FONT color="green">313</FONT> case '-':<a name="line.313"></a>
+<FONT color="green">314</FONT> if (i == 0 || i + 1 == n) { return false; }<a name="line.314"></a>
+<FONT color="green">315</FONT> break;<a name="line.315"></a>
+<FONT color="green">316</FONT> default:<a name="line.316"></a>
+<FONT color="green">317</FONT> if (ch &lt;= '9') {<a name="line.317"></a>
+<FONT color="green">318</FONT> if (i == 0 || ch &lt; '0') { return false; }<a name="line.318"></a>
+<FONT color="green">319</FONT> } else if ('A' &lt;= ch &amp;&amp; ch &lt;= 'z') {<a name="line.319"></a>
+<FONT color="green">320</FONT> if ('Z' &lt; ch &amp;&amp; ch &lt; 'a') { return false; }<a name="line.320"></a>
+<FONT color="green">321</FONT> } else {<a name="line.321"></a>
+<FONT color="green">322</FONT> return false;<a name="line.322"></a>
+<FONT color="green">323</FONT> }<a name="line.323"></a>
+<FONT color="green">324</FONT> break;<a name="line.324"></a>
+<FONT color="green">325</FONT> }<a name="line.325"></a>
+<FONT color="green">326</FONT> }<a name="line.326"></a>
+<FONT color="green">327</FONT> return true;<a name="line.327"></a>
+<FONT color="green">328</FONT> }<a name="line.328"></a>
+<FONT color="green">329</FONT> <a name="line.329"></a>
+<FONT color="green">330</FONT> @SuppressWarnings("fallthrough")<a name="line.330"></a>
+<FONT color="green">331</FONT> static void escapeHtmlOnto(String plainText, Appendable output)<a name="line.331"></a>
+<FONT color="green">332</FONT> throws IOException {<a name="line.332"></a>
+<FONT color="green">333</FONT> int n = plainText.length();<a name="line.333"></a>
+<FONT color="green">334</FONT> int pos = 0;<a name="line.334"></a>
+<FONT color="green">335</FONT> for (int i = 0; i &lt; n; ++i) {<a name="line.335"></a>
+<FONT color="green">336</FONT> char ch = plainText.charAt(i);<a name="line.336"></a>
+<FONT color="green">337</FONT> switch (ch) {<a name="line.337"></a>
+<FONT color="green">338</FONT> case '&lt;':<a name="line.338"></a>
+<FONT color="green">339</FONT> output.append(plainText, pos, i).append("&amp;lt;");<a name="line.339"></a>
<FONT color="green">340</FONT> pos = i + 1;<a name="line.340"></a>
<FONT color="green">341</FONT> break;<a name="line.341"></a>
-<FONT color="green">342</FONT> case 0:<a name="line.342"></a>
-<FONT color="green">343</FONT> output.append(plainText, pos, i);<a name="line.343"></a>
+<FONT color="green">342</FONT> case '&gt;':<a name="line.342"></a>
+<FONT color="green">343</FONT> output.append(plainText, pos, i).append("&amp;gt;");<a name="line.343"></a>
<FONT color="green">344</FONT> pos = i + 1;<a name="line.344"></a>
<FONT color="green">345</FONT> break;<a name="line.345"></a>
-<FONT color="green">346</FONT> }<a name="line.346"></a>
-<FONT color="green">347</FONT> }<a name="line.347"></a>
-<FONT color="green">348</FONT> output.append(plainText, pos, n);<a name="line.348"></a>
-<FONT color="green">349</FONT> }<a name="line.349"></a>
-<FONT color="green">350</FONT> <a name="line.350"></a>
-<FONT color="green">351</FONT> <a name="line.351"></a>
-<FONT color="green">352</FONT> static class CloseableHtmlStreamRenderer extends HtmlStreamRenderer<a name="line.352"></a>
-<FONT color="green">353</FONT> implements Closeable {<a name="line.353"></a>
-<FONT color="green">354</FONT> private final Closeable closeable;<a name="line.354"></a>
-<FONT color="green">355</FONT> <a name="line.355"></a>
-<FONT color="green">356</FONT> CloseableHtmlStreamRenderer(<a name="line.356"></a>
-<FONT color="green">357</FONT> @WillCloseWhenClosed<a name="line.357"></a>
-<FONT color="green">358</FONT> Appendable output, Handler&lt;? super IOException&gt; errorHandler,<a name="line.358"></a>
-<FONT color="green">359</FONT> Handler&lt;? super String&gt; badHtmlHandler) {<a name="line.359"></a>
-<FONT color="green">360</FONT> super(output, errorHandler, badHtmlHandler);<a name="line.360"></a>
-<FONT color="green">361</FONT> this.closeable = (Closeable) output;<a name="line.361"></a>
-<FONT color="green">362</FONT> }<a name="line.362"></a>
-<FONT color="green">363</FONT> <a name="line.363"></a>
-<FONT color="green">364</FONT> public void close() throws IOException {<a name="line.364"></a>
-<FONT color="green">365</FONT> if (isDocumentOpen()) { closeDocument(); }<a name="line.365"></a>
-<FONT color="green">366</FONT> closeable.close();<a name="line.366"></a>
-<FONT color="green">367</FONT> }<a name="line.367"></a>
-<FONT color="green">368</FONT> }<a name="line.368"></a>
-<FONT color="green">369</FONT> }<a name="line.369"></a>
+<FONT color="green">346</FONT> case '&amp;':<a name="line.346"></a>
+<FONT color="green">347</FONT> output.append(plainText, pos, i).append("&amp;amp;");<a name="line.347"></a>
+<FONT color="green">348</FONT> pos = i + 1;<a name="line.348"></a>
+<FONT color="green">349</FONT> break;<a name="line.349"></a>
+<FONT color="green">350</FONT> case '"':<a name="line.350"></a>
+<FONT color="green">351</FONT> output.append(plainText, pos, i).append("&amp;#34;");<a name="line.351"></a>
+<FONT color="green">352</FONT> pos = i + 1;<a name="line.352"></a>
+<FONT color="green">353</FONT> break;<a name="line.353"></a>
+<FONT color="green">354</FONT> case '\r': case '\n': break;<a name="line.354"></a>
+<FONT color="green">355</FONT> default:<a name="line.355"></a>
+<FONT color="green">356</FONT> if (0x20 &lt;= ch &amp;&amp; ch &lt; 0xff00) {<a name="line.356"></a>
+<FONT color="green">357</FONT> continue;<a name="line.357"></a>
+<FONT color="green">358</FONT> }<a name="line.358"></a>
+<FONT color="green">359</FONT> // Is a control character or possible full-width version of a<a name="line.359"></a>
+<FONT color="green">360</FONT> // special character.<a name="line.360"></a>
+<FONT color="green">361</FONT> // FALL-THROUGH<a name="line.361"></a>
+<FONT color="green">362</FONT> case '+': // UTF-7<a name="line.362"></a>
+<FONT color="green">363</FONT> case '=': // Special in attributes.<a name="line.363"></a>
+<FONT color="green">364</FONT> case '@': // Conditional compilation<a name="line.364"></a>
+<FONT color="green">365</FONT> case '\'': case '`': // Quoting character<a name="line.365"></a>
+<FONT color="green">366</FONT> output.append(plainText, pos, i).append("&amp;#")<a name="line.366"></a>
+<FONT color="green">367</FONT> .append(String.valueOf((int) ch)).append(';');<a name="line.367"></a>
+<FONT color="green">368</FONT> pos = i + 1;<a name="line.368"></a>
+<FONT color="green">369</FONT> break;<a name="line.369"></a>
+<FONT color="green">370</FONT> case 0:<a name="line.370"></a>
+<FONT color="green">371</FONT> output.append(plainText, pos, i);<a name="line.371"></a>
+<FONT color="green">372</FONT> pos = i + 1;<a name="line.372"></a>
+<FONT color="green">373</FONT> break;<a name="line.373"></a>
+<FONT color="green">374</FONT> }<a name="line.374"></a>
+<FONT color="green">375</FONT> }<a name="line.375"></a>
+<FONT color="green">376</FONT> output.append(plainText, pos, n);<a name="line.376"></a>
+<FONT color="green">377</FONT> }<a name="line.377"></a>
+<FONT color="green">378</FONT> <a name="line.378"></a>
+<FONT color="green">379</FONT> <a name="line.379"></a>
+<FONT color="green">380</FONT> static class CloseableHtmlStreamRenderer extends HtmlStreamRenderer<a name="line.380"></a>
+<FONT color="green">381</FONT> implements Closeable {<a name="line.381"></a>
+<FONT color="green">382</FONT> private final Closeable closeable;<a name="line.382"></a>
+<FONT color="green">383</FONT> <a name="line.383"></a>
+<FONT color="green">384</FONT> CloseableHtmlStreamRenderer(<a name="line.384"></a>
+<FONT color="green">385</FONT> @WillCloseWhenClosed<a name="line.385"></a>
+<FONT color="green">386</FONT> Appendable output, Handler&lt;? super IOException&gt; errorHandler,<a name="line.386"></a>
+<FONT color="green">387</FONT> Handler&lt;? super String&gt; badHtmlHandler) {<a name="line.387"></a>
+<FONT color="green">388</FONT> super(output, errorHandler, badHtmlHandler);<a name="line.388"></a>
+<FONT color="green">389</FONT> this.closeable = (Closeable) output;<a name="line.389"></a>
+<FONT color="green">390</FONT> }<a name="line.390"></a>
+<FONT color="green">391</FONT> <a name="line.391"></a>
+<FONT color="green">392</FONT> public void close() throws IOException {<a name="line.392"></a>
+<FONT color="green">393</FONT> if (isDocumentOpen()) { closeDocument(); }<a name="line.393"></a>
+<FONT color="green">394</FONT> closeable.close();<a name="line.394"></a>
+<FONT color="green">395</FONT> }<a name="line.395"></a>
+<FONT color="green">396</FONT> }<a name="line.396"></a>
+<FONT color="green">397</FONT> }<a name="line.397"></a>
diff --git a/distrib/lib/owasp-java-html-sanitizer-src.jar b/distrib/lib/owasp-java-html-sanitizer-src.jar
index 240253c..5315319 100644
--- a/distrib/lib/owasp-java-html-sanitizer-src.jar
+++ b/distrib/lib/owasp-java-html-sanitizer-src.jar
Binary files differ
diff --git a/distrib/lib/owasp-java-html-sanitizer.jar b/distrib/lib/owasp-java-html-sanitizer.jar
index d9d9ed2..dfcb5e0 100644
--- a/distrib/lib/owasp-java-html-sanitizer.jar
+++ b/distrib/lib/owasp-java-html-sanitizer.jar
Binary files differ