/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.faces.test.servlet30.renderkit; import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.context.ResponseWriter; import java.io.IOException; import java.io.Writer; import com.sun.faces.util.HtmlUtils; import com.sun.faces.util.MessageUtils; /** * <p><strong>CustomResponseWriter</strong> is an Html specific implementation * of the <code>ResponseWriter</code> abstract class. * Kudos to Adam Winer (Oracle) for much of this code. */ public class CustomResponseWriter extends ResponseWriter { // Content Type for this Writer. // private String contentType = "text/html"; // Character encoding of that Writer - this may be null // if the encoding isn't known. // private String encoding = null; // Writer to use for output; // private Writer writer = null; // True when we need to close a start tag // private boolean closeStart; // True when we shouldn't be escaping output (basically, // inside of <script> and <style> elements). // private boolean dontEscape; // Internal buffer used when outputting properly escaped information // using HtmlUtils class. // private char[] buffer = new char[1028]; // Internal buffer for to store the result of String.getChars() for // values passed to the writer as String to reduce the overhead // of String.charAt(). This buffer will be grown, if necessary, to // accomodate larger values. private char[] textBuffer = new char[128]; private char[] charHolder = new char[1]; /** * Constructor sets the <code>ResponseWriter</code> and * encoding. * * @param writer the <code>ResponseWriter</code> * @param contentType the content type. * @param encoding the character encoding. * @throws if the encoding is not recognized. */ public CustomResponseWriter(Writer writer, String contentType, String encoding) throws FacesException { this.writer = writer; if (null != contentType) { this.contentType = contentType; } this.encoding = encoding; // Check the character encoding // Check the character encoding if (!HtmlUtils.validateEncoding(encoding)) { throw new IllegalArgumentException(MessageUtils.getExceptionMessageString( MessageUtils.ENCODING_ERROR_MESSAGE_ID)); } } /** * @return the content type such as "text/html" for this ResponseWriter. */ public String getContentType() { return contentType; } /** * @return the character encoding, such as "ISO-8859-1" for this * ResponseWriter. Refer to: * <a href="http://www.iana.org/assignments/character-sets">theIANA</a> * for a list of character encodings. */ public String getCharacterEncoding() { return encoding; } /** * <p>Write the text that should begin a response.</p> * * @throws IOException if an input/output error occurs */ public void startDocument() throws IOException { // do nothing; } /** * Output the text for the end of a document. */ public void endDocument() throws IOException { writer.flush(); } /** * Flush any buffered output to the contained writer. * * @throws IOException if an input/output error occurs. */ public void flush() throws IOException { // NOTE: Internal buffer's contents (the ivar "buffer") is // written to the contained writer in the HtmlUtils class - see // HtmlUtils.flushBuffer method; Buffering is done during // writeAttribute/writeText - otherwise, output is written // directly to the writer (ex: writer.write(....).. // // close any previously started element, if necessary closeStartIfNecessary(); } /** * <p>Write the start of an element, up to and including the * element name. Clients call <code>writeAttribute()</code> or * <code>writeURIAttribute()</code> methods to add attributes after * calling this method. * * @param name Name of the starting element * @param componentForElement The UIComponent instance that applies to this * element. This argument may be <code>null</code>. * @throws IOException if an input/output error occurs * @throws NullPointerException if <code>name</code> * is <code>null</code> */ public void startElement(String name, UIComponent componentForElement) throws IOException { if (name == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "name")); } closeStartIfNecessary(); char firstChar = name.charAt(0); if ((firstChar == 's') || (firstChar == 'S')) { if ("script".equalsIgnoreCase(name) || "style".equalsIgnoreCase(name)) { dontEscape = true; } } //PENDING (horwat) using String as a result of Tomcat char writer // ArrayIndexOutOfBoundsException (3584) writer.write("<"); writer.write(name); closeStart = true; } /** * <p>Write the end of an element. This method will first * close any open element created by a call to * <code>startElement()</code>. * * @param name Name of the element to be ended * @throws IOException if an input/output error occurs * @throws NullPointerException if <code>name</code> * is <code>null</code> */ public void endElement(String name) throws IOException { if (name == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "name")); } // always turn escaping back on once an element ends dontEscape = false; // See if we need to close the start of the last element if (closeStart) { boolean isEmptyElement = HtmlUtils.isEmptyElement(name); if (isEmptyElement) { writer.write(" />"); closeStart = false; return; } writer.write(">"); closeStart = false; } writer.write("</"); writer.write(name); //PENDING (horwat) using String as a result of Tomcat char writer // ArrayIndexOutOfBoundsException (3584) writer.write(">"); } /** * <p>Write a properly escaped attribute name and the corresponding * value. The value text will be converted to a String if * necessary. This method may only be called after a call to * <code>startElement()</code>, and before the opened element has been * closed.</p> * * @param name Attribute name to be added * @param value Attribute value to be added * @param componentPropertyName The name of the component property to * which this attribute argument applies. This argument may be * <code>null</code>. * @throws IllegalStateException if this method is called when there * is no currently open element * @throws IOException if an input/output error occurs * @throws NullPointerException if <code>name</code> or * <code>value</code> is <code>null</code> */ public void writeAttribute(String name, Object value, String componentPropertyName) throws IOException { if (name == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "name")); } if (value == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "value")); } Class valueClass = value.getClass(); // Output Boolean values specially if (valueClass == Boolean.class) { if (Boolean.TRUE.equals(value)) { //PENDING (horwat) using String as a result of //Tomcat char writer ArrayIndexOutOfBoundsException (3584) writer.write(" "); writer.write(name); } else { // Don't write anything for "false" booleans } } else { writer.write(" "); writer.write(name); writer.write("=\""); // write the attribute value ensureTextBufferCapacity(value.toString()); HtmlUtils.writeAttribute(writer, true, true, buffer, value.toString(), textBuffer, true); //PENDING (horwat) using String as a result of Tomcat char // writer ArrayIndexOutOfBoundsException (3584) writer.write("\""); } } /** * <p>Write a properly encoded URI attribute name and the corresponding * value. The value text will be converted to a String if necessary). * This method may only be called after a call to * <code>startElement()</code>, and before the opened element has been * closed.</p> * * @param name Attribute name to be added * @param value Attribute value to be added * @param componentPropertyName The name of the component property to * which this attribute argument applies. This argument may be * <code>null</code>. * @throws IllegalStateException if this method is called when there * is no currently open element * @throws IOException if an input/output error occurs * @throws NullPointerException if <code>name</code> or * <code>value</code> is <code>null</code> */ public void writeURIAttribute(String name, Object value, String componentPropertyName) throws IOException { if (name == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "name")); } if (value == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "value")); } //PENDING (horwat) using String as a result of Tomcat char writer // ArrayIndexOutOfBoundsException (3584) writer.write(" "); writer.write(name); writer.write("=\""); String stringValue = value.toString(); ensureTextBufferCapacity(stringValue); // Javascript URLs should not be URL-encoded if (stringValue.startsWith("javascript:")) { HtmlUtils.writeAttribute(writer, true, true, buffer, stringValue, textBuffer, true); } else { HtmlUtils.writeURL(writer, stringValue, textBuffer, encoding); } //PENDING (horwat) using String as a result of Tomcat char writer // ArrayIndexOutOfBoundsException (3584) writer.write("\""); } /** * <p>Write a comment string containing the specified text. * The text will be converted to a String if necessary. * If there is an open element that has been created by a call * to <code>startElement()</code>, that element will be closed * first.</p> * * @param comment Text content of the comment * @throws IOException if an input/output error occurs * @throws NullPointerException if <code>comment</code> * is <code>null</code> */ public void writeComment(Object comment) throws IOException { if (comment == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "comment")); } closeStartIfNecessary(); writer.write("<!-- "); writer.write(comment.toString()); writer.write(" -->"); } /** * <p>Write a properly escaped object. The object will be converted * to a String if necessary. If there is an open element * that has been created by a call to <code>startElement()</code>, * that element will be closed first.</p> * * @param text Text to be written * @param componentPropertyName The name of the component property to * which this text argument applies. This argument may be <code>null</code>. * @throws IOException if an input/output error occurs * @throws NullPointerException if <code>text</code> * is <code>null</code> */ public void writeText(Object text, String componentPropertyName) throws IOException { if (text == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "text")); } closeStartIfNecessary(); if (dontEscape) { writer.write(text.toString()); } else { ensureTextBufferCapacity(text.toString()); HtmlUtils.writeText(writer, true, true, buffer, text.toString(), textBuffer); } } /** * <p>Write a properly escaped single character, If there * is an open element that has been created by a call to * <code>startElement()</code>, that element will be closed first.</p> * <p/> * <p>All angle bracket occurrences in the argument must be escaped * using the &gt; &lt; syntax.</p> * * @param text Text to be written * @throws IOException if an input/output error occurs */ public void writeText(char text) throws IOException { closeStartIfNecessary(); if (dontEscape) { writer.write(text); } else { charHolder[0] = text; HtmlUtils.writeText(writer, true, true, buffer, charHolder); } } /** * <p>Write properly escaped text from a character array. * The output from this command is identical to the invocation: * <code>writeText(c, 0, c.length)</code>. * If there is an open element that has been created by a call to * <code>startElement()</code>, that element will be closed first.</p> * </p> * <p/> * <p>All angle bracket occurrences in the argument must be escaped * using the &gt; &lt; syntax.</p> * * @param text Text to be written * @throws IOException if an input/output error occurs * @throws NullPointerException if <code>text</code> * is <code>null</code> */ public void writeText(char text[]) throws IOException { if (text == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "text")); } closeStartIfNecessary(); if (dontEscape) { writer.write(text); } else { HtmlUtils.writeText(writer, true, true, buffer, text); } } /** * <p>Write properly escaped text from a character array. * If there is an open element that has been created by a call * to <code>startElement()</code>, that element will be closed * first.</p> * <p/> * <p>All angle bracket occurrences in the argument must be escaped * using the &gt; &lt; syntax.</p> * * @param text Text to be written * @param off Starting offset (zero-relative) * @param len Number of characters to be written * @throws IndexOutOfBoundsException if the calculated starting or * ending position is outside the bounds of the character array * @throws IOException if an input/output error occurs * @throws NullPointerException if <code>text</code> * is <code>null</code> */ public void writeText(char text[], int off, int len) throws IOException { if (text == null) { throw new NullPointerException(MessageUtils.getExceptionMessageString( MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "text")); } if (off < 0 || off > text.length || len < 0 || len > text.length) { throw new IndexOutOfBoundsException(); } closeStartIfNecessary(); if (dontEscape) { writer.write(text, off, len); } else { HtmlUtils.writeText(writer, true, true, buffer, text, off, len); } } // PENDING - Do we need to implement these for this test? public void startCDATA() { throw new IllegalStateException(); } public void endCDATA() { throw new IllegalStateException(); } /** * <p>Create a new instance of this <code>ResponseWriter</code> using * a different <code>Writer</code>. * * @param writer The <code>Writer</code> that will be used to create * another <code>ResponseWriter</code>. */ public ResponseWriter cloneWithWriter(Writer writer) { try { return new CustomResponseWriter(writer, getContentType(), getCharacterEncoding()); } catch (FacesException e) { // This should never happen throw new IllegalStateException(); } } /** * This method automatically closes a previous element (if not * already closed). */ private void closeStartIfNecessary() throws IOException { if (closeStart) { //PENDING (horwat) using String as a result of Tomcat char // writer ArrayIndexOutOfBoundsException (3584) writer.write(">"); closeStart = false; } } /** * Methods From <code>java.io.Writer</code> */ public void close() throws IOException { closeStartIfNecessary(); writer.close(); } public void write(char cbuf) throws IOException { closeStartIfNecessary(); writer.write(cbuf); } public void write(char[] cbuf, int off, int len) throws IOException { closeStartIfNecessary(); writer.write(cbuf, off, len); } public void write(int c) throws IOException { closeStartIfNecessary(); writer.write(c); } public void write(String str) throws IOException { closeStartIfNecessary(); writer.write(str); } public void write(String str, int off, int len) throws IOException { closeStartIfNecessary(); writer.write(str, off, len); } private void ensureTextBufferCapacity(String source) { int len = source.length(); if (textBuffer.length < len) { textBuffer = new char[len * 2]; } } }