/*
* Copyright (C) 2000 - 2008 TagServlet Ltd
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://www.openbluedragon.org/license/README.txt
*
* http://www.openbluedragon.org/
*/
package com.naryx.tagfusion.cfm.engine;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import com.nary.util.BMPattern;
import com.naryx.tagfusion.cfm.application.cfApplicationData;
import com.naryx.tagfusion.cfm.application.cfClientSessionData;
import com.naryx.tagfusion.cfm.cookie.cfCookieData;
public class cfHttpServletResponse extends HttpServletResponseWrapper {
// for buffering the entire response (buffer can't be larger than this)
public static final int MAX_SIZE = 128 * 1024;
public static final int UNLIMITED_SIZE = Integer.MAX_VALUE;
private static int DEFAULT_SIZE = 32 * 1024;
private static final int CHILD_DEFAULT_SIZE = 4 * 1024;
private static int USER_SIZE = DEFAULT_SIZE;
public static void setUserSize(int size) {
if (size <= 0) {
USER_SIZE = UNLIMITED_SIZE;
} else {
USER_SIZE = size;
if (USER_SIZE < DEFAULT_SIZE)
DEFAULT_SIZE = USER_SIZE;
}
}
public static int getUserSize() {
return USER_SIZE;
}
private static String DEFAULT_CONTENT_TYPE = "text/html; charset=" + cfEngine.getDefaultCharset();
private cfStringWriter writer; // for writing string data
private ServletOutputStream outputStream; // for writing binary data
private cfHttpServletResponse parent;
private boolean jspInclude = false;
public cfHttpServletResponse(cfSession _session, HttpServletResponse response) {
super(response);
writer = new cfStringWriter(_session, response, DEFAULT_SIZE, USER_SIZE);
}
public cfHttpServletResponse createChild(cfSession _session) {
return new cfHttpServletResponse(this, _session);
}
// used only for creating children
private cfHttpServletResponse(cfHttpServletResponse _parent, cfSession _session) {
super((HttpServletResponse) _parent.getResponse());
writer = new cfStringWriter(_session, (HttpServletResponse) _parent.getResponse(), CHILD_DEFAULT_SIZE, UNLIMITED_SIZE);
writer.setSuppressWhiteSpace(_parent.isSuppressWhiteSpace());
parent = _parent;
}
public void setJspInclude(boolean include) {
jspInclude = include;
}
public boolean isJspInclude() {
return jspInclude;
}
public boolean isSuppressWhiteSpace() {
return writer.isSuppressWhiteSpace();
}
public void setSuppressWhiteSpace(boolean suppress) {
writer.setSuppressWhiteSpace(suppress);
}
public void write(String str) {
writer.write(str);
}
public void write(char[] c, int off, int len) {
writer.write(c, off, len);
}
public void write(cfSession _session, byte[] buf) throws IOException {
if (outputStream == null) {
super.setContentType(writer.getContentType());
addCookies(_session);
outputStream = getOutputStream();
}
outputStream.write(buf);
}
public void write(cfSession _session, byte[] buf, int off, int len) throws IOException {
if (outputStream == null) {
super.setContentType(writer.getContentType());
addCookies(_session);
outputStream = getOutputStream();
}
outputStream.write(buf, off, len);
}
public ServletOutputStream getOutputStream() throws IOException {
// With WLS 8.1SP4 and 9.0, a rd.include() causes it to call
// getOutputStream()
// so we need to return our own ouput stream that will write the data to our
// internal buffer. Refer to bug #2375.
if (jspInclude)
return new _ServletOutputStream();
outputStream = (parent != null ? parent.getOutputStream() : super.getOutputStream());
return outputStream;
}
// ---[ Inner class to provide the handling for the ServletOutput Stream
class _ServletOutputStream extends ServletOutputStream {
public _ServletOutputStream() {
}
public void write(int c) {
writer.write(c);
}
}
public void setContentType(String contentType) {
if (parent != null) {
parent.setContentType(contentType);
}
writer.setContentType(contentType);
}
public void setStatus(int statusCode) {
if (!jspInclude) {
if (isCommitted()) {
throw new IllegalStateException("Response committed, cannot set status");
}
super.setStatus(statusCode);
}
}
public void setStatus(int statusCode, String value) {
if (!jspInclude) {
if (isCommitted()) {
throw new IllegalStateException("Response committed, cannot set status");
}
super.setStatus(statusCode, value);
}
}
public void addHeader(String name, String value) {
if (!jspInclude) {
if (isCommitted()) {
throw new IllegalStateException("Response committed, cannot add header");
}
super.addHeader(name, value);
}
}
public void setHeader(String name, String value) {
if (!jspInclude) {
if (isCommitted()) {
throw new IllegalStateException("Response committed, cannot set header");
}
super.setHeader(name, value);
}
}
public void setHeadElement(String str, boolean append) {
if (parent != null) {
parent.setHeadElement(str,append);
} else {
if (isCommitted()) {
throw new IllegalStateException("Response committed, cannot set HEAD element");
}
writer.setHeadElement(str,append);
}
}
public void setBodyElement(String str, boolean append) {
if (parent != null) {
parent.setBodyElement(str,append);
} else {
if (isCommitted()) {
throw new IllegalStateException("Response committed, cannot set BODY element");
}
writer.setBodyElement(str,append);
}
}
public void flush() {
// if there's a parent, flush its output first
if (parent != null) {
parent.flush();
}
// now flush our own output
if (outputStream != null) {
try {
outputStream.flush();
} catch (IOException ignore) {
// only happens if client disconnects
}
} else if (parent != null) {
// write to the parent, then flush
parent.write(writer.toString());
parent.flush();
writer.resetBuffer();
} else if (!jspInclude) {
writer.flush();
}
}
public void reset() {
writer.reset();
}
public void resetBuffer() {
if (parent != null) {
parent.resetBuffer();
}
writer.resetBuffer();
}
public PrintWriter getWriter() {
return new PrintWriter(writer);
}
public void sendRedirect(String location) throws IOException {
writer.addCookies();
super.sendRedirect(location);
}
public void setBufferSize(int size) {
writer.setBufferSize(size > MAX_SIZE ? UNLIMITED_SIZE : size);
}
public String getOutputAsString() {
if (parent == null) {
writer.writeHeadBodyElement();
}
return writer.toString();
}
/*
* getString
*
* This method was added to support the CfmlJspWriter.getString() method.
*/
public String getString() {
String s;
if (parent != null)
s = parent.getOutputAsString();
else
s = "";
return s + writer.toString();
}
private static void addCookies(cfSession _session) {
if (_session.RES.isCommitted()){
return;
}
/**
* If the client data is saved as cookies then we want these to be sent out
* on a flush. Otherwise they'll not appear at all.
*/
cfApplicationData appData = _session.getApplicationData();
if ((appData != null) && appData.isClientEnabled()) {
cfData clientData = _session.getQualifiedData(variableStore.CLIENT_SCOPE);
if ((clientData != null) && (clientData instanceof cfClientSessionData)) {
// creates cookie if storage type == COOKIE
((cfClientSessionData) clientData).flush(_session);
}
}
// Setup any cookies that may exist
cfData cookieData = _session.getQualifiedData(variableStore.COOKIE_SCOPE);
if ((cookieData != null) && (cookieData instanceof cfCookieData)) {
// add cookies to the servlet response
((cfCookieData) cookieData).addCookies();
}
}
/**
* This class is used to wrap the servlet response writer--all output written
* to the client must be done via this class. This class performs several
* functions:
*
* 1. Buffering. The first 32K of client output is buffered by this class.
* After the first 32K is flushed to the client, the remaining output is
* written directly to the underlying servlet response writer.
*
* 2. Whitespace suppression. If whitespace suppression is enabled, all runs
* of whitespace are collapsed to a single space character.
*/
private class cfStringWriter extends Writer {
private StringBuilder sb;
private int bufferSize; // the maximum size of the buffer
private String contentType = DEFAULT_CONTENT_TYPE;
private boolean suppressWhiteSpace;
private boolean lastCharWhiteSpace;
private boolean fullFlush = false;
private cfSession session;
private HttpServletResponse response;
private PrintWriter responseWriter;
private StringBuilder headElement;
private boolean headAppend = true;
private StringBuilder bodyElement;
private boolean bodyAppend = true;
private cfStringWriter(cfSession _session, HttpServletResponse _response, int initialSize, int maxSize) {
sb = new StringBuilder(initialSize);
bufferSize = maxSize;
session = _session;
response = _response;
}
private void setBufferSize(int size) {
if (size != UNLIMITED_SIZE) {
if (sb.length() >= size)
flush();
// do a full flush when buffer is full because that's what the
// user wants (this was invoked from CFFLUSH INTERVAL="<size>")
fullFlush = true;
}
bufferSize = size;
}
private void setContentType(String _contentType) {
if (responseWriter != null) {
throw new IllegalStateException("Response committed, cannot set content type");
}
contentType = _contentType;
}
private String getContentType() {
return contentType;
}
private boolean isSuppressWhiteSpace() {
return suppressWhiteSpace;
}
private void setSuppressWhiteSpace(boolean suppress) {
suppressWhiteSpace = suppress;
lastCharWhiteSpace = false;
}
public void write(char[] cbuf, int off, int len) {
//sb.append(cbuf, off, len);
if (suppressWhiteSpace) {
for ( int i=0;i<len;i++)
write(cbuf[i]);
}else{
// If we are paging out; delegate to the write(str)
if ((bufferSize > 0) && (bufferSize < cfHttpServletResponse.UNLIMITED_SIZE)) {
write( new String(cbuf,off,len) );
return;
}
// Output to the response
if (responseWriter != null) {
responseWriter.write(cbuf,off,len);
} else {
if (sb.append(cbuf,off,len).length() >= bufferSize)
flushToResponseWriter();
}
}
}
public void write(String str) {
if (suppressWhiteSpace) {
for (int i = 0; i < str.length(); i++) {
write(str.charAt(i));
}
} else {
if ((bufferSize > 0) && (bufferSize < cfHttpServletResponse.UNLIMITED_SIZE)) {
int available = bufferSize - sb.length();
while (str.length() > available) {
write(str.substring(0, available)); // causes flush()
str = str.substring(available);
available = bufferSize; // sb.length() == 0 after flush()
}
}
if (responseWriter != null) {
responseWriter.write(str);
} else {
if (sb.append(str).length() >= bufferSize)
flushToResponseWriter();
}
}
}
public final void write(int c) {
if (suppressWhiteSpace) {
boolean isWhiteSpace = Character.isWhitespace((char) c);
if (lastCharWhiteSpace && isWhiteSpace) {
// replace previous whitespace char with newline
if (((char) c == '\n') && (sb.length() > 0) && (responseWriter == null))
sb.setCharAt(sb.length() - 1, (char) c);
} else {
writeChar(c);
}
lastCharWhiteSpace = isWhiteSpace;
} else {
writeChar(c);
}
}
private final void writeChar(int c) {
if (responseWriter != null) {
responseWriter.write(c);
} else {
if (sb.append((char)c).length() >= bufferSize)
flushToResponseWriter();
}
}
private void setHeadElement(String str, boolean append) {
if (headElement == null) {
headElement = new StringBuilder(str.length());
}
headAppend = append;
headElement.append(str);
}
private void setBodyElement(String str, boolean append) {
if (bodyElement == null) {
bodyElement = new StringBuilder(str.length());
}
bodyAppend = append;
bodyElement.append(str);
}
private void writeHeadBodyElement() {
if ( headElement != null && headElement.length() > 0 ) {
// --[ Find the position to insert into
if ( headAppend ){
BMPattern pattern = new BMPattern("</head>", true);
int headEndTagPos = pattern.matches(sb.toString(), 0, sb.length());
sb.insert(headEndTagPos == -1 ? 0 : headEndTagPos, headElement.toString());
}else{
BMPattern pattern = new BMPattern("<head>", true);
int headEndTagPos = pattern.matches(sb.toString(), 0, sb.length());
sb.insert(headEndTagPos == -1 ? 0 : headEndTagPos + 6, headElement.toString());
}
headElement = null;
}
if ( bodyElement != null && bodyElement.length() > 0 ) {
// --[ Find the position to insert into
if ( bodyAppend ){
BMPattern pattern = new BMPattern("</body>", true);
int bodyEndTagPos = pattern.matches(sb.toString(), 0, sb.length());
sb.insert(bodyEndTagPos == -1 ? 0 : bodyEndTagPos, bodyElement.toString());
}else{
BMPattern pattern = new BMPattern("<body>", true);
int bodyEndTagPos = pattern.matches(sb.toString(), 0, sb.length());
sb.insert(bodyEndTagPos == -1 ? 0 : bodyEndTagPos + 6, bodyElement.toString());
}
bodyElement = null;
}
}
public void reset() {
sb.setLength(0);
response.reset();
contentType = DEFAULT_CONTENT_TYPE;
}
public void resetBuffer() {
sb.setLength(0);
try {
if (responseWriter != null) {
response.resetBuffer();
}
} catch (NoSuchMethodError e) { // for WebSphere 4.0
response.reset();
}
}
public void flush() {
if (jspInclude)
return;
if (responseWriter == null) {
fullFlush = true;
flushToResponseWriter();
} else {
responseWriter.flush();
}
}
private void flushToResponseWriter() {
try {
response.setContentType(contentType);
// Bug #2468:
// the Servlet API states that you can't call both getOutputstream() and
// getWriter()
// In the case where getOutputStream() has already been called (only
// where CFCONTENT has been used) we create a dummy
// writer essentially thus any remaining output is not written to the
// real response
// Note that the outputStream should only be null if CFCONTENT has been
// used to return binary data
if (outputStream != null) {
responseWriter = new PrintWriter(new ByteArrayOutputStream());
} else {
responseWriter = response.getWriter();
}
addCookies();
writeHeadBodyElement();
responseWriter.write(sb.toString());
sb.setLength(0);
if (fullFlush) {
responseWriter.flush();
}
// after flushing once, we're going to write all output
// to the underlying responseWriter
bufferSize = UNLIMITED_SIZE;
} catch (IOException e) {
cfEngine.log("Error flushing response buffer: " + e);
}
}
private void addCookies() {
cfHttpServletResponse.addCookies(session);
}
public String toString() {
if (responseWriter != null) { // response has already been sent to browser
return "";
} else {
return sb.toString();
}
}
public void close() throws IOException {
}
}
}