--- /dev/null
+package com.meterware.servletunit;
+/********************************************************************************************************************
+* $Id: ServletUnitHttpResponse.java 751 2006-03-24 19:59:12Z russgold $
+*
+* Copyright (c) 2000-2004,2006, Russell Gold
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions
+* of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+* DEALINGS IN THE SOFTWARE.
+*
+*******************************************************************************************************************/
+import com.meterware.httpunit.HttpUnitUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import java.util.*;
+import java.text.SimpleDateFormat;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+
+class ServletUnitHttpResponse implements HttpServletResponse {
+
+ // rfc1123-date is "Sun, 06 Nov 1994 08:49:37 GMT"
+ private static final String RFC1123_DATE_SPEC = "EEE, dd MMM yyyy HH:mm:ss z";
+ private boolean _committed;
+ private Locale _locale = Locale.getDefault();
+
+ private static final Hashtable ENCODING_MAP = new Hashtable();
+
+ /**
+ * @deprecated Use encodeURL(String url)
+ */
+ public String encodeUrl( String url ) {
+ return encodeURL( url );
+ }
+
+
+ /**
+ * Adds the specified cookie to the response. It can be called
+ * multiple times to set more than one cookie.
+ */
+ public void addCookie( Cookie cookie ) {
+ _cookies.addElement( cookie );
+ }
+
+
+ /**
+ * Checks whether the response message header has a field with
+ * the specified name.
+ */
+ public boolean containsHeader( String name ) {
+ return _headers.containsKey( name.toUpperCase() );
+ }
+
+
+ /**
+ * @deprecated Use encodeRedirectURL(String url)
+ **/
+ public String encodeRedirectUrl( String url ) {
+ return encodeRedirectURL( url );
+ }
+
+
+ /**
+ * Encodes the specified URL by including the session ID in it,
+ * or, if encoding is not needed, returns the URL unchanged.
+ * The implementation of this method should include the logic to
+ * determine whether the session ID needs to be encoded in the URL.
+ * For example, if the browser supports cookies, or session
+ * tracking is turned off, URL encoding is unnecessary.
+ **/
+ public String encodeURL( String url ) {
+ return url;
+ }
+
+
+ /**
+ * Encodes the specified URL for use in the
+ * <code>sendRedirect</code> method or, if encoding is not needed,
+ * returns the URL unchanged. The implementation of this method
+ * should include the logic to determine whether the session ID
+ * needs to be encoded in the URL. Because the rules for making
+ * this determination differ from those used to decide whether to
+ * encode a normal link, this method is seperate from the
+ * <code>encodeUrl</code> method.
+ **/
+ public String encodeRedirectURL( String url ) {
+ return url;
+ }
+
+
+ /**
+ * Sends a temporary redirect response to the client using the
+ * specified redirect location URL. The URL must be absolute (for
+ * example, <code><em>https://hostname/path/file.html</em></code>).
+ * Relative URLs are not permitted here.
+ */
+ public void sendRedirect( String location ) throws IOException {
+ setStatus( HttpServletResponse.SC_MOVED_TEMPORARILY );
+ setHeader( "Location", location );
+ }
+
+
+ /**
+ * Sends an error response to the client using the specified status
+ * code and descriptive message. If setStatus has previously been
+ * called, it is reset to the error status code. The message is
+ * sent as the body of an HTML page, which is returned to the user
+ * to describe the problem. The page is sent with a default HTML
+ * header; the message is enclosed in simple body tags
+ * (<body></body>).
+ **/
+ public void sendError( int sc ) throws IOException {
+ sendError( sc, "" );
+ }
+
+
+ /**
+ * Sends an error response to the client using the specified status
+ * code and descriptive message. If setStatus has previously been
+ * called, it is reset to the error status code. The message is
+ * sent as the body of an HTML page, which is returned to the user
+ * to describe the problem. The page is sent with a default HTML
+ * header; the message is enclosed in simple body tags
+ * (<body></body>).
+ **/
+ public void sendError(int sc, String msg) throws IOException {
+ setStatus( sc );
+ _statusMessage = msg;
+
+ _writer = null;
+ _servletStream = null;
+
+ setContentType( "text/html" );
+ getWriter().println( "<html><head><title>" + msg + "</title></head><body>" + msg + "</body></html>" );
+ }
+
+
+ /**
+ * Sets the status code for this response. This method is used to
+ * set the return status code when there is no error (for example,
+ * for the status codes SC_OK or SC_MOVED_TEMPORARILY). If there
+ * is an error, the <code>sendError</code> method should be used
+ * instead.
+ **/
+ public void setStatus( int sc ) {
+ _status = sc;
+ }
+
+
+ /**
+ * @deprecated As of version 2.1, due to ambiguous meaning of the message parameter.
+ * To set a status code use setStatus(int), to send an error with a description
+ * use sendError(int, String). Sets the status code and message for this response.
+ **/
+ public void setStatus( int sc, String msg ) {
+ setStatus( sc );
+ }
+
+
+ /**
+ * Adds a field to the response header with the given name and value.
+ * If the field had already been set, the new value overwrites the
+ * previous one. The <code>containsHeader</code> method can be
+ * used to test for the presence of a header before setting its
+ * value.
+ **/
+ public void setHeader( String name, String value ) {
+ ArrayList values = new ArrayList();
+ values.add( value );
+ synchronized (_headers) {
+ _headers.put( name.toUpperCase(), values );
+ }
+ }
+
+
+ /**
+ * Adds a field to the response header with the given name and
+ * integer value. If the field had already been set, the new value
+ * overwrites the previous one. The <code>containsHeader</code>
+ * method can be used to test for the presence of a header before
+ * setting its value.
+ **/
+ public void setIntHeader( String name, int value ) {
+ setHeader( name, asHeaderValue( value ) );
+ }
+
+
+ private String asHeaderValue( int value ) {
+ return Integer.toString( value );
+ }
+
+
+ /**
+ * Adds a field to the response header with the given name and
+ * date-valued field. The date is specified in terms of
+ * milliseconds since the epoch. If the date field had already
+ * been set, the new value overwrites the previous one. The
+ * <code>containsHeader</code> method can be used to test for the
+ * presence of a header before setting its value.
+ **/
+ public void setDateHeader( String name, long date ) {
+ setHeader( name, asDateHeaderValue( date ) );
+ }
+
+
+ private String asDateHeaderValue( long date ) {
+ Date value = new Date( date );
+ SimpleDateFormat formatter = new SimpleDateFormat( RFC1123_DATE_SPEC, Locale.US );
+ formatter.setTimeZone( TimeZone.getTimeZone( "Greenwich Mean Time" ) );
+ return formatter.format( value );
+ }
+
+
+ /**
+ * Returns the name of the character set encoding used for
+ * the MIME body sent by this response.
+ **/
+ public String getCharacterEncoding() {
+ return _encoding == null ? HttpUnitUtils.DEFAULT_CHARACTER_SET : _encoding;
+ }
+
+
+ /**
+ * Sets the content type of the response the server sends to
+ * the client. The content type may include the type of character
+ * encoding used, for example, <code>text/html; charset=ISO-8859-4</code>.
+ *
+ * <p>You can only use this method once, and you should call it
+ * before you obtain a <code>PrintWriter</code> or
+ * {@link ServletOutputStream} object to return a response.
+ **/
+ public void setContentType( String type ) {
+ String[] typeAndEncoding = HttpUnitUtils.parseContentTypeHeader( type );
+
+ _contentType = typeAndEncoding[0];
+ if (typeAndEncoding[1] != null) _encoding = typeAndEncoding[1];
+ }
+
+
+ /**
+ * Returns a {@link ServletOutputStream} suitable for writing binary
+ * data in the response. The servlet engine does not encode the
+ * binary data.
+ *
+ * @exception IllegalStateException if you have already called the <code>getWriter</code> method
+ **/
+ public ServletOutputStream getOutputStream() throws IOException {
+ if (_writer != null) throw new IllegalStateException( "Tried to create output stream; writer already exists" );
+ if (_servletStream == null) {
+ _outputStream = new ByteArrayOutputStream();
+ _servletStream = new ServletUnitOutputStream( _outputStream );
+ }
+ return _servletStream;
+ }
+
+
+ /**
+ * Returns a <code>PrintWriter</code> object that you
+ * can use to send character text to the client.
+ * The character encoding used is the one specified
+ * in the <code>charset=</code> property of the
+ * {@link #setContentType} method, which you must call
+ * <i>before</i> you call this method.
+ *
+ * <p>If necessary, the MIME type of the response is
+ * modified to reflect the character encoding used.
+ *
+ * <p> You cannot use this method if you have already
+ * called {@link #getOutputStream} for this
+ * <code>ServletResponse</code> object.
+ *
+ * @exception UnsupportedEncodingException if the character encoding specified in
+ * <code>setContentType</code> cannot be
+ * used
+ *
+ * @exception IllegalStateException if the <code>getOutputStream</code>
+ * method has already been called for this
+ * response object; in that case, you can't
+ * use this method
+ *
+ **/
+ public PrintWriter getWriter() throws UnsupportedEncodingException {
+ if (_servletStream != null) throw new IllegalStateException( "Tried to create writer; output stream already exists" );
+ if (_writer == null) {
+ _outputStream = new ByteArrayOutputStream();
+ _writer = new PrintWriter( new OutputStreamWriter( _outputStream, getCharacterEncoding() ) );
+ }
+ return _writer;
+ }
+
+
+ /**
+ * Sets the length of the content the server returns
+ * to the client. In HTTP servlets, this method sets the
+ * HTTP Content-Length header.
+ **/
+ public void setContentLength( int len ) {
+ setIntHeader( "Content-Length", len );
+ }
+
+
+//------------------------------- the following methods are new in JSDK 2.2 ----------------------
+
+
+ /**
+ * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+ **/
+ public void addHeader( String name, String value ) {
+ synchronized (_headers) {
+ String key = name.toUpperCase();
+ ArrayList values = (ArrayList) _headers.get( key );
+ if (values == null) {
+ values = new ArrayList();
+ _headers.put( key, values );
+ }
+ values.add( value );
+ }
+ }
+
+
+ /**
+ * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+ **/
+ public void addIntHeader( String name, int value ) {
+ addHeader( name, asHeaderValue( value ) );
+ }
+
+
+ /**
+ * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+ **/
+ public void addDateHeader( String name, long value ) {
+ addHeader( name, asDateHeaderValue( value ) );
+ }
+
+
+ /**
+ * Sets the preferred buffer size for the body of the response. The servlet container
+ * will use a buffer at least as large as the size requested. The actual buffer size
+ * used can be found using getBufferSize.
+ **/
+ public void setBufferSize( int size ) {
+ if (getContents().length != 0) throw new IllegalStateException( "May not set buffer size after data is written" );
+ }
+
+
+ /**
+ * Returns the actual buffer size used for the response. If no buffering is used, this method returns 0.
+ **/
+ public int getBufferSize() {
+ return 0;
+ }
+
+
+ /**
+ * Returns a boolean indicating if the response has been committed. A committed response has
+ * already had its status code and headers written.
+ **/
+ public boolean isCommitted() {
+ return _committed;
+ }
+
+
+ /**
+ * Forces any content in the buffer to be written to the client. A call to this method automatically
+ * commits the response, meaning the status code and headers will be written.
+ **/
+ public void flushBuffer() throws IOException {
+ _committed = true;
+ }
+
+
+ /**
+ * Clears any data that exists in the buffer as well as the status code and headers.
+ * If the response has been committed, this method throws an IllegalStateException.
+ **/
+ public void reset() {
+ resetBuffer();
+ _headers.clear();
+ _headersComplete = false;
+ _status = SC_OK;
+ }
+
+
+ /**
+ * Sets the locale of the response, setting the headers (including the Content-Type's charset)
+ * as appropriate. This method should be called before a call to getWriter().
+ * By default, the response locale is the default locale for the server.
+ **/
+ public void setLocale( Locale locale ) {
+ _locale = locale;
+ if (_encoding == null) {
+ for (Iterator it = ENCODING_MAP.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String locales = (String) entry.getValue();
+ if (locales.indexOf( locale.getLanguage() ) >= 0 || locales.indexOf( locale.toString() ) >= 0) {
+ _encoding = (String) entry.getKey();
+ return;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Returns the locale assigned to the response.
+ **/
+ public Locale getLocale() {
+ return _locale;
+ }
+
+
+//----------------------------- methods added to ServletResponse in JSDK 2.3 --------------------------------------
+
+
+ /**
+ * Clears the content of the underlying buffer in the response without clearing headers or status code.
+ * If the response has been committed, this method throws an IllegalStateException.
+ *
+ * @since 1.3
+ */
+ public void resetBuffer() {
+ if (_committed) throw new IllegalStateException( "May not resetBuffer after response is committed" );
+ _outputStream = null;
+ _servletStream = null;
+ _writer = null;
+ }
+
+
+//---------------------------------------------- package methods --------------------------------------------------
+
+ /**
+ * Returns the contents of this response.
+ **/
+ byte[] getContents() {
+ if (_outputStream == null) {
+ return new byte[0];
+ } else {
+ if (_writer != null) _writer.flush();
+ return _outputStream.toByteArray();
+ }
+ }
+
+
+ /**
+ * Returns the status of this response.
+ **/
+ int getStatus() {
+ return _status;
+ }
+
+
+ /**
+ * Returns the message associated with this response's status.
+ **/
+ String getMessage() {
+ return _statusMessage;
+ }
+
+
+ public String[] getHeaderFieldNames() {
+ if (!_headersComplete) completeHeaders();
+ Vector names = new Vector();
+ for (Enumeration e = _headers.keys(); e.hasMoreElements();) {
+ names.addElement( e.nextElement() );
+ }
+ String[] result = new String[ names.size() ];
+ names.copyInto( result );
+ return result;
+ }
+
+
+ /**
+ * Returns the headers defined for this response.
+ **/
+ String getHeaderField( String name ) {
+ if (!_headersComplete) completeHeaders();
+
+ ArrayList values;
+ synchronized (_headers) {
+ values = (ArrayList) _headers.get( name.toUpperCase() );
+ }
+
+ return values == null ? null : (String) values.get( 0 );
+ }
+
+
+ /**
+ * Return an array of all the header values associated with the
+ * specified header name, or an zero-length array if there are no such
+ * header values.
+ *
+ * @param name Header name to look up
+ */
+ public String[] getHeaderFields(String name) {
+ if (!_headersComplete) completeHeaders();
+ ArrayList values;
+ synchronized (_headers) {
+ values = (ArrayList) _headers.get(name.toUpperCase());
+ }
+ if (values == null)
+ return (new String[0]);
+ String results[] = new String[values.size()];
+ return ((String[]) values.toArray(results));
+
+ }
+
+//--------------------------------------- methods added to ServletRequest in Servlet API 2.4 ----------------------------
+
+ public void setCharacterEncoding(String string) {
+ _encoding = string;
+ }
+
+ /**
+ * Returns the content type defined for this response.
+ **/
+ public String getContentType() {
+ return _contentType;
+ }
+
+
+//------------------------------------------- private members ------------------------------------
+
+
+ private String _contentType = "text/plain";
+
+ private String _encoding;
+
+ private PrintWriter _writer;
+
+ private ServletOutputStream _servletStream;
+
+ private ByteArrayOutputStream _outputStream;
+
+ private int _status = SC_OK;
+
+ private String _statusMessage = "OK";
+
+ private final Hashtable _headers = new Hashtable();
+
+ private boolean _headersComplete;
+
+ private Vector _cookies = new Vector();
+
+
+ private void completeHeaders() {
+ if (_headersComplete) return;
+ addCookieHeader();
+ setHeader( "Content-Type", _contentType + "; charset=" + getCharacterEncoding() );
+ _headersComplete = true;
+ }
+
+
+ private void addCookieHeader() {
+ if (_cookies.isEmpty()) return;
+
+ StringBuffer sb = new StringBuffer();
+ for (Enumeration e = _cookies.elements(); e.hasMoreElements();) {
+ Cookie cookie = (Cookie) e.nextElement();
+ sb.append( cookie.getName() ).append( '=' ).append( cookie.getValue() );
+ if (cookie.getPath() != null) sb.append( ";path=" ).append( cookie.getPath() );
+ if (cookie.getDomain() != null) sb.append( ";domain=" ).append( cookie.getDomain() );
+ if (e.hasMoreElements()) sb.append( ',' );
+ }
+ setHeader( "Set-Cookie", sb.toString() );
+ }
+
+
+
+ static {
+ ENCODING_MAP.put( "iso-8859-1", "ca da de en es fi fr is it nl no pt sv " );
+ ENCODING_MAP.put( "iso-8859-2", "cs hr hu pl ro sh sk sl sq " );
+ ENCODING_MAP.put( "iso-8859-4", "et lt lv ");
+ ENCODING_MAP.put( "iso-8859-5", "be bg mk ru sr uk " );
+ ENCODING_MAP.put( "iso-8859-6", "ar " );
+ ENCODING_MAP.put( "iso-8859-7", "el " );
+ ENCODING_MAP.put( "iso-8859-8", "iw he " );
+ ENCODING_MAP.put( "iso-8859-9", "tr " );
+
+ ENCODING_MAP.put("Shift_JIS", "ja ");
+ ENCODING_MAP.put("EUC-KR", "ko ");
+ ENCODING_MAP.put("TIS-620", "th ");
+ ENCODING_MAP.put("GB2312", "zh " );
+ ENCODING_MAP.put("Big5", "zh_TW zh_HK " );
+ }
+
+}
+
+
+
+class ServletUnitOutputStream extends ServletOutputStream {
+
+ ServletUnitOutputStream( ByteArrayOutputStream stream ) {
+ _stream = stream;
+ }
+
+
+ public void write( int aByte ) throws IOException {
+ _stream.write( aByte );
+ }
+
+ private ByteArrayOutputStream _stream;
+}