1<HTML> 2<BODY BGCOLOR="white"> 3<PRE> 4<FONT color="green">001</FONT> // Copyright (c) 2011, Mike Samuel<a name="line.1"></a> 5<FONT color="green">002</FONT> // All rights reserved.<a name="line.2"></a> 6<FONT color="green">003</FONT> //<a name="line.3"></a> 7<FONT color="green">004</FONT> // Redistribution and use in source and binary forms, with or without<a name="line.4"></a> 8<FONT color="green">005</FONT> // modification, are permitted provided that the following conditions<a name="line.5"></a> 9<FONT color="green">006</FONT> // are met:<a name="line.6"></a> 10<FONT color="green">007</FONT> //<a name="line.7"></a> 11<FONT color="green">008</FONT> // Redistributions of source code must retain the above copyright<a name="line.8"></a> 12<FONT color="green">009</FONT> // notice, this list of conditions and the following disclaimer.<a name="line.9"></a> 13<FONT color="green">010</FONT> // Redistributions in binary form must reproduce the above copyright<a name="line.10"></a> 14<FONT color="green">011</FONT> // notice, this list of conditions and the following disclaimer in the<a name="line.11"></a> 15<FONT color="green">012</FONT> // documentation and/or other materials provided with the distribution.<a name="line.12"></a> 16<FONT color="green">013</FONT> // Neither the name of the OWASP nor the names of its contributors may<a name="line.13"></a> 17<FONT color="green">014</FONT> // be used to endorse or promote products derived from this software<a name="line.14"></a> 18<FONT color="green">015</FONT> // without specific prior written permission.<a name="line.15"></a> 19<FONT color="green">016</FONT> // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS<a name="line.16"></a> 20<FONT color="green">017</FONT> // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT<a name="line.17"></a> 21<FONT color="green">018</FONT> // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS<a name="line.18"></a> 22<FONT color="green">019</FONT> // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE<a name="line.19"></a> 23<FONT color="green">020</FONT> // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,<a name="line.20"></a> 24<FONT color="green">021</FONT> // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,<a name="line.21"></a> 25<FONT color="green">022</FONT> // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;<a name="line.22"></a> 26<FONT color="green">023</FONT> // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER<a name="line.23"></a> 27<FONT color="green">024</FONT> // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT<a name="line.24"></a> 28<FONT color="green">025</FONT> // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN<a name="line.25"></a> 29<FONT color="green">026</FONT> // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE<a name="line.26"></a> 30<FONT color="green">027</FONT> // POSSIBILITY OF SUCH DAMAGE.<a name="line.27"></a> 31<FONT color="green">028</FONT> <a name="line.28"></a> 32<FONT color="green">029</FONT> package org.owasp.html;<a name="line.29"></a> 33<FONT color="green">030</FONT> <a name="line.30"></a> 34<FONT color="green">031</FONT> import com.google.common.annotations.VisibleForTesting;<a name="line.31"></a> 35<FONT color="green">032</FONT> import java.io.Closeable;<a name="line.32"></a> 36<FONT color="green">033</FONT> import java.io.Flushable;<a name="line.33"></a> 37<FONT color="green">034</FONT> import java.io.IOException;<a name="line.34"></a> 38<FONT color="green">035</FONT> import java.util.Iterator;<a name="line.35"></a> 39<FONT color="green">036</FONT> import java.util.List;<a name="line.36"></a> 40<FONT color="green">037</FONT> import javax.annotation.WillCloseWhenClosed;<a name="line.37"></a> 41<FONT color="green">038</FONT> import javax.annotation.concurrent.NotThreadSafe;<a name="line.38"></a> 42<FONT color="green">039</FONT> <a name="line.39"></a> 43<FONT color="green">040</FONT> /**<a name="line.40"></a> 44<FONT color="green">041</FONT> * Given a series of HTML tokens, writes valid, normalized HTML to the output.<a name="line.41"></a> 45<FONT color="green">042</FONT> * The output will have well-defined tag boundaries, but there may be orphaned<a name="line.42"></a> 46<FONT color="green">043</FONT> * or missing close and open tags.<a name="line.43"></a> 47<FONT color="green">044</FONT> * The result of two renderers can always be concatenated to produce a larger<a name="line.44"></a> 48<FONT color="green">045</FONT> * snippet of HTML, but if the first was called with<a name="line.45"></a> 49<FONT color="green">046</FONT> * {@code writeOpenTag("plaintext", ...)}, then any tags in the second will not<a name="line.46"></a> 50<FONT color="green">047</FONT> * be interpreted as tags in the concatenated version.<a name="line.47"></a> 51<FONT color="green">048</FONT> */<a name="line.48"></a> 52<FONT color="green">049</FONT> @TCB<a name="line.49"></a> 53<FONT color="green">050</FONT> @NotThreadSafe<a name="line.50"></a> 54<FONT color="green">051</FONT> public class HtmlStreamRenderer implements HtmlStreamEventReceiver {<a name="line.51"></a> 55<FONT color="green">052</FONT> <a name="line.52"></a> 56<FONT color="green">053</FONT> private final Appendable output;<a name="line.53"></a> 57<FONT color="green">054</FONT> private final Handler<? super IOException> ioExHandler;<a name="line.54"></a> 58<FONT color="green">055</FONT> private final Handler<? super String> badHtmlHandler;<a name="line.55"></a> 59<FONT color="green">056</FONT> private String lastTagOpened;<a name="line.56"></a> 60<FONT color="green">057</FONT> private StringBuilder pendingUnescaped;<a name="line.57"></a> 61<FONT color="green">058</FONT> private boolean open;<a name="line.58"></a> 62<FONT color="green">059</FONT> <a name="line.59"></a> 63<FONT color="green">060</FONT> /**<a name="line.60"></a> 64<FONT color="green">061</FONT> * Factory.<a name="line.61"></a> 65<FONT color="green">062</FONT> * @param output the buffer to which HTML is streamed.<a name="line.62"></a> 66<FONT color="green">063</FONT> * @param ioExHandler called with any exception raised by output.<a name="line.63"></a> 67<FONT color="green">064</FONT> * @param badHtmlHandler receives alerts when HTML cannot be rendered because<a name="line.64"></a> 68<FONT color="green">065</FONT> * there is not valid HTML tree that results from that series of calls.<a name="line.65"></a> 69<FONT color="green">066</FONT> * E.g. it is not possible to create an HTML {@code <style>} element whose<a name="line.66"></a> 70<FONT color="green">067</FONT> * textual content is {@code "</style>"}.<a name="line.67"></a> 71<FONT color="green">068</FONT> */<a name="line.68"></a> 72<FONT color="green">069</FONT> public static HtmlStreamRenderer create(<a name="line.69"></a> 73<FONT color="green">070</FONT> @WillCloseWhenClosed Appendable output,<a name="line.70"></a> 74<FONT color="green">071</FONT> Handler<? super IOException> ioExHandler,<a name="line.71"></a> 75<FONT color="green">072</FONT> Handler<? super String> badHtmlHandler) {<a name="line.72"></a> 76<FONT color="green">073</FONT> if (output instanceof Closeable) {<a name="line.73"></a> 77<FONT color="green">074</FONT> return new CloseableHtmlStreamRenderer(<a name="line.74"></a> 78<FONT color="green">075</FONT> output, ioExHandler, badHtmlHandler);<a name="line.75"></a> 79<FONT color="green">076</FONT> } else {<a name="line.76"></a> 80<FONT color="green">077</FONT> return new HtmlStreamRenderer(output, ioExHandler, badHtmlHandler);<a name="line.77"></a> 81<FONT color="green">078</FONT> }<a name="line.78"></a> 82<FONT color="green">079</FONT> }<a name="line.79"></a> 83<FONT color="green">080</FONT> <a name="line.80"></a> 84<FONT color="green">081</FONT> /**<a name="line.81"></a> 85<FONT color="green">082</FONT> * Factory.<a name="line.82"></a> 86<FONT color="green">083</FONT> * @param output the buffer to which HTML is streamed.<a name="line.83"></a> 87<FONT color="green">084</FONT> * @param badHtmlHandler receives alerts when HTML cannot be rendered because<a name="line.84"></a> 88<FONT color="green">085</FONT> * there is not valid HTML tree that results from that series of calls.<a name="line.85"></a> 89<FONT color="green">086</FONT> * E.g. it is not possible to create an HTML {@code <style>} element whose<a name="line.86"></a> 90<FONT color="green">087</FONT> * textual content is {@code "</style>"}.<a name="line.87"></a> 91<FONT color="green">088</FONT> */<a name="line.88"></a> 92<FONT color="green">089</FONT> public static HtmlStreamRenderer create(<a name="line.89"></a> 93<FONT color="green">090</FONT> StringBuilder output, Handler<? super String> badHtmlHandler) {<a name="line.90"></a> 94<FONT color="green">091</FONT> // Propagate since StringBuilder should not throw IOExceptions.<a name="line.91"></a> 95<FONT color="green">092</FONT> return create(output, Handler.PROPAGATE, badHtmlHandler);<a name="line.92"></a> 96<FONT color="green">093</FONT> }<a name="line.93"></a> 97<FONT color="green">094</FONT> <a name="line.94"></a> 98<FONT color="green">095</FONT> private HtmlStreamRenderer(<a name="line.95"></a> 99<FONT color="green">096</FONT> Appendable output, Handler<? super IOException> ioExHandler,<a name="line.96"></a> 100<FONT color="green">097</FONT> Handler<? super String> badHtmlHandler) {<a name="line.97"></a> 101<FONT color="green">098</FONT> this.output = output;<a name="line.98"></a> 102<FONT color="green">099</FONT> this.ioExHandler = ioExHandler;<a name="line.99"></a> 103<FONT color="green">100</FONT> this.badHtmlHandler = badHtmlHandler;<a name="line.100"></a> 104<FONT color="green">101</FONT> }<a name="line.101"></a> 105<FONT color="green">102</FONT> <a name="line.102"></a> 106<FONT color="green">103</FONT> /**<a name="line.103"></a> 107<FONT color="green">104</FONT> * Called when the series of calls make no sense.<a name="line.104"></a> 108<FONT color="green">105</FONT> * May be overridden to throw an unchecked throwable, to log, or to take some<a name="line.105"></a> 109<FONT color="green">106</FONT> * other action.<a name="line.106"></a> 110<FONT color="green">107</FONT> *<a name="line.107"></a> 111<FONT color="green">108</FONT> * @param message for human consumption.<a name="line.108"></a> 112<FONT color="green">109</FONT> * @param identifier an HTML identifier associated with the message.<a name="line.109"></a> 113<FONT color="green">110</FONT> */<a name="line.110"></a> 114<FONT color="green">111</FONT> private final void error(String message, CharSequence identifier) {<a name="line.111"></a> 115<FONT color="green">112</FONT> if (badHtmlHandler != Handler.DO_NOTHING) { // Avoid string append.<a name="line.112"></a> 116<FONT color="green">113</FONT> badHtmlHandler.handle(message + " : " + identifier);<a name="line.113"></a> 117<FONT color="green">114</FONT> }<a name="line.114"></a> 118<FONT color="green">115</FONT> }<a name="line.115"></a> 119<FONT color="green">116</FONT> <a name="line.116"></a> 120<FONT color="green">117</FONT> public final void openDocument() throws IllegalStateException {<a name="line.117"></a> 121<FONT color="green">118</FONT> if (open) { throw new IllegalStateException(); }<a name="line.118"></a> 122<FONT color="green">119</FONT> open = true;<a name="line.119"></a> 123<FONT color="green">120</FONT> }<a name="line.120"></a> 124<FONT color="green">121</FONT> <a name="line.121"></a> 125<FONT color="green">122</FONT> public final void closeDocument() throws IllegalStateException {<a name="line.122"></a> 126<FONT color="green">123</FONT> if (!open) { throw new IllegalStateException(); }<a name="line.123"></a> 127<FONT color="green">124</FONT> if (pendingUnescaped != null) {<a name="line.124"></a> 128<FONT color="green">125</FONT> closeTag(lastTagOpened);<a name="line.125"></a> 129<FONT color="green">126</FONT> }<a name="line.126"></a> 130<FONT color="green">127</FONT> open = false;<a name="line.127"></a> 131<FONT color="green">128</FONT> if (output instanceof Flushable) {<a name="line.128"></a> 132<FONT color="green">129</FONT> try {<a name="line.129"></a> 133<FONT color="green">130</FONT> ((Flushable) output).flush();<a name="line.130"></a> 134<FONT color="green">131</FONT> } catch (IOException ex) {<a name="line.131"></a> 135<FONT color="green">132</FONT> ioExHandler.handle(ex);<a name="line.132"></a> 136<FONT color="green">133</FONT> }<a name="line.133"></a> 137<FONT color="green">134</FONT> }<a name="line.134"></a> 138<FONT color="green">135</FONT> }<a name="line.135"></a> 139<FONT color="green">136</FONT> <a name="line.136"></a> 140<FONT color="green">137</FONT> public final boolean isDocumentOpen() {<a name="line.137"></a> 141<FONT color="green">138</FONT> return open;<a name="line.138"></a> 142<FONT color="green">139</FONT> }<a name="line.139"></a> 143<FONT color="green">140</FONT> <a name="line.140"></a> 144<FONT color="green">141</FONT> public final void openTag(String elementName, List<String> attrs) {<a name="line.141"></a> 145<FONT color="green">142</FONT> try {<a name="line.142"></a> 146<FONT color="green">143</FONT> writeOpenTag(elementName, attrs);<a name="line.143"></a> 147<FONT color="green">144</FONT> } catch (IOException ex) {<a name="line.144"></a> 148<FONT color="green">145</FONT> ioExHandler.handle(ex);<a name="line.145"></a> 149<FONT color="green">146</FONT> }<a name="line.146"></a> 150<FONT color="green">147</FONT> }<a name="line.147"></a> 151<FONT color="green">148</FONT> <a name="line.148"></a> 152<FONT color="green">149</FONT> private void writeOpenTag(String elementName, List<? extends String> attrs)<a name="line.149"></a> 153<FONT color="green">150</FONT> throws IOException {<a name="line.150"></a> 154<FONT color="green">151</FONT> if (!open) { throw new IllegalStateException(); }<a name="line.151"></a> 155<FONT color="green">152</FONT> elementName = safeName(elementName);<a name="line.152"></a> 156<FONT color="green">153</FONT> if (!isValidHtmlName(elementName)) {<a name="line.153"></a> 157<FONT color="green">154</FONT> error("Invalid element name", elementName);<a name="line.154"></a> 158<FONT color="green">155</FONT> return;<a name="line.155"></a> 159<FONT color="green">156</FONT> }<a name="line.156"></a> 160<FONT color="green">157</FONT> if (pendingUnescaped != null) {<a name="line.157"></a> 161<FONT color="green">158</FONT> error("Tag content cannot appear inside CDATA element", elementName);<a name="line.158"></a> 162<FONT color="green">159</FONT> return;<a name="line.159"></a> 163<FONT color="green">160</FONT> }<a name="line.160"></a> 164<FONT color="green">161</FONT> <a name="line.161"></a> 165<FONT color="green">162</FONT> switch (HtmlTextEscapingMode.getModeForTag(elementName)) {<a name="line.162"></a> 166<FONT color="green">163</FONT> case CDATA_SOMETIMES:<a name="line.163"></a> 167<FONT color="green">164</FONT> case CDATA:<a name="line.164"></a> 168<FONT color="green">165</FONT> case PLAIN_TEXT:<a name="line.165"></a> 169<FONT color="green">166</FONT> lastTagOpened = elementName;<a name="line.166"></a> 170<FONT color="green">167</FONT> pendingUnescaped = new StringBuilder();<a name="line.167"></a> 171<FONT color="green">168</FONT> break;<a name="line.168"></a> 172<FONT color="green">169</FONT> default:<a name="line.169"></a> 173<FONT color="green">170</FONT> }<a name="line.170"></a> 174<FONT color="green">171</FONT> <a name="line.171"></a> 175<FONT color="green">172</FONT> output.append('<').append(elementName);<a name="line.172"></a> 176<FONT color="green">173</FONT> <a name="line.173"></a> 177<FONT color="green">174</FONT> for (Iterator<? extends String> attrIt = attrs.iterator();<a name="line.174"></a> 178<FONT color="green">175</FONT> attrIt.hasNext();) {<a name="line.175"></a> 179<FONT color="green">176</FONT> String name = attrIt.next();<a name="line.176"></a> 180<FONT color="green">177</FONT> String value = attrIt.next();<a name="line.177"></a> 181<FONT color="green">178</FONT> name = HtmlLexer.canonicalName(name);<a name="line.178"></a> 182<FONT color="green">179</FONT> if (!isValidHtmlName(name)) {<a name="line.179"></a> 183<FONT color="green">180</FONT> error("Invalid attr name", name);<a name="line.180"></a> 184<FONT color="green">181</FONT> continue;<a name="line.181"></a> 185<FONT color="green">182</FONT> }<a name="line.182"></a> 186<FONT color="green">183</FONT> output.append(' ').append(name).append('=').append('"');<a name="line.183"></a> 187<FONT color="green">184</FONT> Encoding.encodeHtmlOnto(value, output);<a name="line.184"></a> 188<FONT color="green">185</FONT> if (value.indexOf('`') != -1) {<a name="line.185"></a> 189<FONT color="green">186</FONT> // Apparently, in quirks mode, IE8 does a poor job producing innerHTML<a name="line.186"></a> 190<FONT color="green">187</FONT> // values. Given<a name="line.187"></a> 191<FONT color="green">188</FONT> // <div attr="``foo=bar"><a name="line.188"></a> 192<FONT color="green">189</FONT> // we encode &#96; but if JavaScript does:<a name="line.189"></a> 193<FONT color="green">190</FONT> // nodeA.innerHTML = nodeB.innerHTML;<a name="line.190"></a> 194<FONT color="green">191</FONT> // and nodeB contains the DIV above, then IE8 will produce<a name="line.191"></a> 195<FONT color="green">192</FONT> // <div attr=``foo=bar><a name="line.192"></a> 196<FONT color="green">193</FONT> // as the value of nodeB.innerHTML and assign it to nodeA.<a name="line.193"></a> 197<FONT color="green">194</FONT> // IE8's HTML parser treats `` as a blank attribute value and foo=bar<a name="line.194"></a> 198<FONT color="green">195</FONT> // becomes a separate attribute.<a name="line.195"></a> 199<FONT color="green">196</FONT> // Adding a space at the end of the attribute prevents this by forcing<a name="line.196"></a> 200<FONT color="green">197</FONT> // IE8 to put double quotes around the attribute when computing<a name="line.197"></a> 201<FONT color="green">198</FONT> // nodeB.innerHTML.<a name="line.198"></a> 202<FONT color="green">199</FONT> output.append(' ');<a name="line.199"></a> 203<FONT color="green">200</FONT> }<a name="line.200"></a> 204<FONT color="green">201</FONT> output.append('"');<a name="line.201"></a> 205<FONT color="green">202</FONT> }<a name="line.202"></a> 206<FONT color="green">203</FONT> <a name="line.203"></a> 207<FONT color="green">204</FONT> // Limit our output to the intersection of valid XML and valid HTML5 when<a name="line.204"></a> 208<FONT color="green">205</FONT> // the output contains no special HTML5 elements like <title>, <script>, or<a name="line.205"></a> 209<FONT color="green">206</FONT> // <textarea>.<a name="line.206"></a> 210<FONT color="green">207</FONT> if (HtmlTextEscapingMode.isVoidElement(elementName)) {<a name="line.207"></a> 211<FONT color="green">208</FONT> output.append(" /");<a name="line.208"></a> 212<FONT color="green">209</FONT> }<a name="line.209"></a> 213<FONT color="green">210</FONT> <a name="line.210"></a> 214<FONT color="green">211</FONT> output.append('>');<a name="line.211"></a> 215<FONT color="green">212</FONT> }<a name="line.212"></a> 216<FONT color="green">213</FONT> <a name="line.213"></a> 217<FONT color="green">214</FONT> public final void closeTag(String elementName) {<a name="line.214"></a> 218<FONT color="green">215</FONT> try {<a name="line.215"></a> 219<FONT color="green">216</FONT> writeCloseTag(safeName(elementName));<a name="line.216"></a> 220<FONT color="green">217</FONT> } catch (IOException ex) {<a name="line.217"></a> 221<FONT color="green">218</FONT> ioExHandler.handle(ex);<a name="line.218"></a> 222<FONT color="green">219</FONT> }<a name="line.219"></a> 223<FONT color="green">220</FONT> }<a name="line.220"></a> 224<FONT color="green">221</FONT> <a name="line.221"></a> 225<FONT color="green">222</FONT> private final void writeCloseTag(String elementName)<a name="line.222"></a> 226<FONT color="green">223</FONT> throws IOException {<a name="line.223"></a> 227<FONT color="green">224</FONT> if (!open) { throw new IllegalStateException(); }<a name="line.224"></a> 228<FONT color="green">225</FONT> elementName = HtmlLexer.canonicalName(elementName);<a name="line.225"></a> 229<FONT color="green">226</FONT> if (!isValidHtmlName(elementName)) {<a name="line.226"></a> 230<FONT color="green">227</FONT> error("Invalid element name", elementName);<a name="line.227"></a> 231<FONT color="green">228</FONT> return;<a name="line.228"></a> 232<FONT color="green">229</FONT> }<a name="line.229"></a> 233<FONT color="green">230</FONT> <a name="line.230"></a> 234<FONT color="green">231</FONT> if (pendingUnescaped != null) {<a name="line.231"></a> 235<FONT color="green">232</FONT> if (!lastTagOpened.equals(elementName)) {<a name="line.232"></a> 236<FONT color="green">233</FONT> error("Tag content cannot appear inside CDATA element", elementName);<a name="line.233"></a> 237<FONT color="green">234</FONT> return;<a name="line.234"></a> 238<FONT color="green">235</FONT> } else {<a name="line.235"></a> 239<FONT color="green">236</FONT> StringBuilder cdataContent = pendingUnescaped;<a name="line.236"></a> 240<FONT color="green">237</FONT> pendingUnescaped = null;<a name="line.237"></a> 241<FONT color="green">238</FONT> Encoding.stripBannedCodeunits(cdataContent);<a name="line.238"></a> 242<FONT color="green">239</FONT> int problemIndex = checkHtmlCdataCloseable(lastTagOpened, cdataContent);<a name="line.239"></a> 243<FONT color="green">240</FONT> if (problemIndex == -1) {<a name="line.240"></a> 244<FONT color="green">241</FONT> output.append(cdataContent);<a name="line.241"></a> 245<FONT color="green">242</FONT> } else {<a name="line.242"></a> 246<FONT color="green">243</FONT> error(<a name="line.243"></a> 247<FONT color="green">244</FONT> "Invalid CDATA text content",<a name="line.244"></a> 248<FONT color="green">245</FONT> cdataContent.subSequence(<a name="line.245"></a> 249<FONT color="green">246</FONT> problemIndex,<a name="line.246"></a> 250<FONT color="green">247</FONT> Math.min(problemIndex + 10, cdataContent.length())));<a name="line.247"></a> 251<FONT color="green">248</FONT> // Still output the close tag.<a name="line.248"></a> 252<FONT color="green">249</FONT> }<a name="line.249"></a> 253<FONT color="green">250</FONT> }<a name="line.250"></a> 254<FONT color="green">251</FONT> if ("plaintext".equals(elementName)) { return; }<a name="line.251"></a> 255<FONT color="green">252</FONT> }<a name="line.252"></a> 256<FONT color="green">253</FONT> output.append("</").append(elementName).append(">");<a name="line.253"></a> 257<FONT color="green">254</FONT> }<a name="line.254"></a> 258<FONT color="green">255</FONT> <a name="line.255"></a> 259<FONT color="green">256</FONT> public final void text(String text) {<a name="line.256"></a> 260<FONT color="green">257</FONT> try {<a name="line.257"></a> 261<FONT color="green">258</FONT> writeText(text);<a name="line.258"></a> 262<FONT color="green">259</FONT> } catch (IOException ex) {<a name="line.259"></a> 263<FONT color="green">260</FONT> ioExHandler.handle(ex);<a name="line.260"></a> 264<FONT color="green">261</FONT> }<a name="line.261"></a> 265<FONT color="green">262</FONT> }<a name="line.262"></a> 266<FONT color="green">263</FONT> <a name="line.263"></a> 267<FONT color="green">264</FONT> private final void writeText(String text) throws IOException {<a name="line.264"></a> 268<FONT color="green">265</FONT> if (!open) { throw new IllegalStateException(); }<a name="line.265"></a> 269<FONT color="green">266</FONT> if (pendingUnescaped != null) {<a name="line.266"></a> 270<FONT color="green">267</FONT> pendingUnescaped.append(text);<a name="line.267"></a> 271<FONT color="green">268</FONT> } else {<a name="line.268"></a> 272<FONT color="green">269</FONT> Encoding.encodeHtmlOnto(text, output); // Works for RCDATA.<a name="line.269"></a> 273<FONT color="green">270</FONT> }<a name="line.270"></a> 274<FONT color="green">271</FONT> }<a name="line.271"></a> 275<FONT color="green">272</FONT> <a name="line.272"></a> 276<FONT color="green">273</FONT> private static int checkHtmlCdataCloseable(<a name="line.273"></a> 277<FONT color="green">274</FONT> String localName, StringBuilder sb) {<a name="line.274"></a> 278<FONT color="green">275</FONT> int escapingTextSpanStart = -1;<a name="line.275"></a> 279<FONT color="green">276</FONT> for (int i = 0, n = sb.length(); i < n; ++i) {<a name="line.276"></a> 280<FONT color="green">277</FONT> char ch = sb.charAt(i);<a name="line.277"></a> 281<FONT color="green">278</FONT> switch (ch) {<a name="line.278"></a> 282<FONT color="green">279</FONT> case '<':<a name="line.279"></a> 283<FONT color="green">280</FONT> if (i + 3 < n<a name="line.280"></a> 284<FONT color="green">281</FONT> && '!' == sb.charAt(i + 1)<a name="line.281"></a> 285<FONT color="green">282</FONT> && '-' == sb.charAt(i + 2)<a name="line.282"></a> 286<FONT color="green">283</FONT> && '-' == sb.charAt(i + 3)) {<a name="line.283"></a> 287<FONT color="green">284</FONT> if (escapingTextSpanStart == -1) {<a name="line.284"></a> 288<FONT color="green">285</FONT> escapingTextSpanStart = i;<a name="line.285"></a> 289<FONT color="green">286</FONT> } else {<a name="line.286"></a> 290<FONT color="green">287</FONT> return i;<a name="line.287"></a> 291<FONT color="green">288</FONT> }<a name="line.288"></a> 292<FONT color="green">289</FONT> } else if (i + 1 + localName.length() < n<a name="line.289"></a> 293<FONT color="green">290</FONT> && '/' == sb.charAt(i + 1)<a name="line.290"></a> 294<FONT color="green">291</FONT> && Strings.regionMatchesIgnoreCase(<a name="line.291"></a> 295<FONT color="green">292</FONT> sb, i + 2, localName, 0, localName.length())) {<a name="line.292"></a> 296<FONT color="green">293</FONT> // A close tag contained in the content.<a name="line.293"></a> 297<FONT color="green">294</FONT> if (escapingTextSpanStart < 0) {<a name="line.294"></a> 298<FONT color="green">295</FONT> // We could try some recovery strategies here.<a name="line.295"></a> 299<FONT color="green">296</FONT> // E.g. prepending "/<!--\n" to sb if "script".equals(localName)<a name="line.296"></a> 300<FONT color="green">297</FONT> return i;<a name="line.297"></a> 301<FONT color="green">298</FONT> }<a name="line.298"></a> 302<FONT color="green">299</FONT> if (!"script".equals(localName)) {<a name="line.299"></a> 303<FONT color="green">300</FONT> // Script tags are commonly included inside script tags.<a name="line.300"></a> 304<FONT color="green">301</FONT> // <script><!--document.write('<script>f()</script>');--></script><a name="line.301"></a> 305<FONT color="green">302</FONT> // but this does not happen in other CDATA element types.<a name="line.302"></a> 306<FONT color="green">303</FONT> // Actually allowing an end tag inside others is problematic.<a name="line.303"></a> 307<FONT color="green">304</FONT> // Specifically,<a name="line.304"></a> 308<FONT color="green">305</FONT> // <style><!--</style>-->/* foo */</style><a name="line.305"></a> 309<FONT color="green">306</FONT> // displays the text "/* foo */" on some browsers.<a name="line.306"></a> 310<FONT color="green">307</FONT> return i;<a name="line.307"></a> 311<FONT color="green">308</FONT> }<a name="line.308"></a> 312<FONT color="green">309</FONT> }<a name="line.309"></a> 313<FONT color="green">310</FONT> break;<a name="line.310"></a> 314<FONT color="green">311</FONT> case '>':<a name="line.311"></a> 315<FONT color="green">312</FONT> // From the HTML5 spec:<a name="line.312"></a> 316<FONT color="green">313</FONT> // The text in style, script, title, and textarea elements must not<a name="line.313"></a> 317<FONT color="green">314</FONT> // have an escaping text span start that is not followed by an<a name="line.314"></a> 318<FONT color="green">315</FONT> // escaping text span end.<a name="line.315"></a> 319<FONT color="green">316</FONT> // We look left since the HTML 5 spec allows the escaping text span<a name="line.316"></a> 320<FONT color="green">317</FONT> // end to share dashes with the start.<a name="line.317"></a> 321<FONT color="green">318</FONT> if (i >= 2 && '-' == sb.charAt(i - 1) && '-' == sb.charAt(i - 2)) {<a name="line.318"></a> 322<FONT color="green">319</FONT> if (escapingTextSpanStart < 0) { return i - 2; }<a name="line.319"></a> 323<FONT color="green">320</FONT> escapingTextSpanStart = -1;<a name="line.320"></a> 324<FONT color="green">321</FONT> }<a name="line.321"></a> 325<FONT color="green">322</FONT> break;<a name="line.322"></a> 326<FONT color="green">323</FONT> default:<a name="line.323"></a> 327<FONT color="green">324</FONT> break;<a name="line.324"></a> 328<FONT color="green">325</FONT> }<a name="line.325"></a> 329<FONT color="green">326</FONT> }<a name="line.326"></a> 330<FONT color="green">327</FONT> if (escapingTextSpanStart >= 0) {<a name="line.327"></a> 331<FONT color="green">328</FONT> // We could try recovery strategies here.<a name="line.328"></a> 332<FONT color="green">329</FONT> // E.g. appending "//-->" to the buffer if "script".equals(localName)<a name="line.329"></a> 333<FONT color="green">330</FONT> return escapingTextSpanStart;<a name="line.330"></a> 334<FONT color="green">331</FONT> }<a name="line.331"></a> 335<FONT color="green">332</FONT> return -1;<a name="line.332"></a> 336<FONT color="green">333</FONT> }<a name="line.333"></a> 337<FONT color="green">334</FONT> <a name="line.334"></a> 338<FONT color="green">335</FONT> <a name="line.335"></a> 339<FONT color="green">336</FONT> @VisibleForTesting<a name="line.336"></a> 340<FONT color="green">337</FONT> static boolean isValidHtmlName(String name) {<a name="line.337"></a> 341<FONT color="green">338</FONT> int n = name.length();<a name="line.338"></a> 342<FONT color="green">339</FONT> if (n == 0) { return false; }<a name="line.339"></a> 343<FONT color="green">340</FONT> if (n > 128) { return false; }<a name="line.340"></a> 344<FONT color="green">341</FONT> boolean isNamespaced = false;<a name="line.341"></a> 345<FONT color="green">342</FONT> for (int i = 0; i < n; ++i) {<a name="line.342"></a> 346<FONT color="green">343</FONT> char ch = name.charAt(i);<a name="line.343"></a> 347<FONT color="green">344</FONT> switch (ch) {<a name="line.344"></a> 348<FONT color="green">345</FONT> case ':':<a name="line.345"></a> 349<FONT color="green">346</FONT> if (isNamespaced) { return false; }<a name="line.346"></a> 350<FONT color="green">347</FONT> isNamespaced = true;<a name="line.347"></a> 351<FONT color="green">348</FONT> if (i == 0 || i + 1 == n) { return false; }<a name="line.348"></a> 352<FONT color="green">349</FONT> break;<a name="line.349"></a> 353<FONT color="green">350</FONT> case '-':<a name="line.350"></a> 354<FONT color="green">351</FONT> if (i == 0 || i + 1 == n) { return false; }<a name="line.351"></a> 355<FONT color="green">352</FONT> break;<a name="line.352"></a> 356<FONT color="green">353</FONT> default:<a name="line.353"></a> 357<FONT color="green">354</FONT> if (ch <= '9') {<a name="line.354"></a> 358<FONT color="green">355</FONT> if (i == 0 || ch < '0') { return false; }<a name="line.355"></a> 359<FONT color="green">356</FONT> } else if ('A' <= ch && ch <= 'z') {<a name="line.356"></a> 360<FONT color="green">357</FONT> if ('Z' < ch && ch < 'a') { return false; }<a name="line.357"></a> 361<FONT color="green">358</FONT> } else {<a name="line.358"></a> 362<FONT color="green">359</FONT> return false;<a name="line.359"></a> 363<FONT color="green">360</FONT> }<a name="line.360"></a> 364<FONT color="green">361</FONT> break;<a name="line.361"></a> 365<FONT color="green">362</FONT> }<a name="line.362"></a> 366<FONT color="green">363</FONT> }<a name="line.363"></a> 367<FONT color="green">364</FONT> return true;<a name="line.364"></a> 368<FONT color="green">365</FONT> }<a name="line.365"></a> 369<FONT color="green">366</FONT> <a name="line.366"></a> 370<FONT color="green">367</FONT> /**<a name="line.367"></a> 371<FONT color="green">368</FONT> * Canonicalizes the element name and possibly substitutes an alternative<a name="line.368"></a> 372<FONT color="green">369</FONT> * that has more consistent semantics.<a name="line.369"></a> 373<FONT color="green">370</FONT> */<a name="line.370"></a> 374<FONT color="green">371</FONT> static String safeName(String elementName) {<a name="line.371"></a> 375<FONT color="green">372</FONT> elementName = HtmlLexer.canonicalName(elementName);<a name="line.372"></a> 376<FONT color="green">373</FONT> <a name="line.373"></a> 377<FONT color="green">374</FONT> // Substitute a reliably non-raw-text element for raw-text and<a name="line.374"></a> 378<FONT color="green">375</FONT> // plain-text elements.<a name="line.375"></a> 379<FONT color="green">376</FONT> switch (elementName.length()) {<a name="line.376"></a> 380<FONT color="green">377</FONT> case 3:<a name="line.377"></a> 381<FONT color="green">378</FONT> if ("xmp".equals(elementName)) { return "pre"; }<a name="line.378"></a> 382<FONT color="green">379</FONT> break;<a name="line.379"></a> 383<FONT color="green">380</FONT> case 7:<a name="line.380"></a> 384<FONT color="green">381</FONT> if ("listing".equals(elementName)) { return "pre"; }<a name="line.381"></a> 385<FONT color="green">382</FONT> break;<a name="line.382"></a> 386<FONT color="green">383</FONT> case 9:<a name="line.383"></a> 387<FONT color="green">384</FONT> if ("plaintext".equals(elementName)) { return "pre"; }<a name="line.384"></a> 388<FONT color="green">385</FONT> break;<a name="line.385"></a> 389<FONT color="green">386</FONT> }<a name="line.386"></a> 390<FONT color="green">387</FONT> return elementName;<a name="line.387"></a> 391<FONT color="green">388</FONT> }<a name="line.388"></a> 392<FONT color="green">389</FONT> <a name="line.389"></a> 393<FONT color="green">390</FONT> static class CloseableHtmlStreamRenderer extends HtmlStreamRenderer<a name="line.390"></a> 394<FONT color="green">391</FONT> implements Closeable {<a name="line.391"></a> 395<FONT color="green">392</FONT> private final Closeable closeable;<a name="line.392"></a> 396<FONT color="green">393</FONT> <a name="line.393"></a> 397<FONT color="green">394</FONT> CloseableHtmlStreamRenderer(<a name="line.394"></a> 398<FONT color="green">395</FONT> @WillCloseWhenClosed<a name="line.395"></a> 399<FONT color="green">396</FONT> Appendable output, Handler<? super IOException> errorHandler,<a name="line.396"></a> 400<FONT color="green">397</FONT> Handler<? super String> badHtmlHandler) {<a name="line.397"></a> 401<FONT color="green">398</FONT> super(output, errorHandler, badHtmlHandler);<a name="line.398"></a> 402<FONT color="green">399</FONT> this.closeable = (Closeable) output;<a name="line.399"></a> 403<FONT color="green">400</FONT> }<a name="line.400"></a> 404<FONT color="green">401</FONT> <a name="line.401"></a> 405<FONT color="green">402</FONT> public void close() throws IOException {<a name="line.402"></a> 406<FONT color="green">403</FONT> if (isDocumentOpen()) { closeDocument(); }<a name="line.403"></a> 407<FONT color="green">404</FONT> closeable.close();<a name="line.404"></a> 408<FONT color="green">405</FONT> }<a name="line.405"></a> 409<FONT color="green">406</FONT> }<a name="line.406"></a> 410<FONT color="green">407</FONT> }<a name="line.407"></a> 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471</PRE> 472</BODY> 473</HTML> 474