/** * Copyright (c) 2014, the Railo Company Ltd. * Copyright (c) 2015, Lucee Assosication Switzerland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.runtime.writer; import java.io.IOException; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; import lucee.runtime.PageContextImpl; import lucee.runtime.cache.legacy.CacheItem; import lucee.runtime.net.http.HttpServletResponseWrap; import lucee.runtime.net.http.ReqRspUtil; import lucee.runtime.op.Caster; /** * Implementation of a JSpWriter */ public class CFMLWriterImpl extends CFMLWriter { private static final int BUFFER_SIZE = 100000; //private static final String VERSIONj = Info.getVersionAsString(); private OutputStream out; private HttpServletResponse response; private boolean flushed; private StringBuilder htmlHead; private StringBuilder htmlBody; private StringBuilder buffer=new StringBuilder(BUFFER_SIZE); private boolean closed=false; private boolean closeConn; private boolean showVersion; private boolean contentLength; private CacheItem cacheItem; private HttpServletRequest request; private Boolean _allowCompression; private PageContext pc; private String version; /** * constructor of the class * @param response Response Object * @param bufferSize buffer Size * @param autoFlush do auto flush Content */ public CFMLWriterImpl(PageContext pc,HttpServletRequest request, HttpServletResponse response, int bufferSize, boolean autoFlush, boolean closeConn, boolean showVersion, boolean contentLength) { super(bufferSize, autoFlush); this.pc=pc; this.request=request; this.response=response; this.autoFlush=autoFlush; this.bufferSize=bufferSize; this.closeConn=closeConn; this.showVersion=showVersion; this.contentLength=contentLength; //this.allowCompression=allowCompression; version=pc.getConfig().getFactory().getEngine().getInfo().getVersion().toString(); } /* * * constructor of the class * @param response Response Object * / public JspWriterImpl(HttpServletResponse response) { this(response, BUFFER_SIZE, false); }*/ private void _check() throws IOException { if(autoFlush && buffer.length()>bufferSize) { _flush(true); } } /** * @throws IOException */ protected void initOut() throws IOException { if (out == null) { out=getOutputStream(false); //out=response.getWriter(); } } /** * @see javax.servlet.jsp.JspWriter#print(char[]) */ @Override public void print(char[] arg) throws IOException { buffer.append(arg); _check(); } /** * reset configuration of buffer * @param bufferSize size of the buffer * @param autoFlush does the buffer autoflush * @throws IOException */ @Override public void setBufferConfig(int bufferSize, boolean autoFlush) throws IOException { this.bufferSize=bufferSize; this.autoFlush=autoFlush; _check(); } @Override public void appendHTMLBody(String text) throws IOException { if (htmlBody == null) htmlBody = new StringBuilder(256); htmlBody.append(text); } @Override public void writeHTMLBody(String text) throws IOException { if (flushed) throw new IOException("Page is already flushed"); htmlBody = new StringBuilder(text); } @Override public String getHTMLBody() throws IOException { if (flushed) throw new IOException("Page is already flushed"); return htmlBody == null ? "" : htmlBody.toString(); } @Override public void flushHTMLBody() throws IOException { if (htmlBody != null) { buffer.append(htmlBody); resetHTMLBody(); } } /** * @see lucee.runtime.writer.CFMLWriter#resetHTMLHead() */ @Override public void resetHTMLBody() throws IOException { if(flushed) throw new IOException("Page is already flushed"); htmlBody = null; } /** * * @param text * @throws IOException */ @Override public void appendHTMLHead(String text) throws IOException { if (flushed) throw new IOException("Page is already flushed"); if (htmlHead == null) htmlHead = new StringBuilder(256); htmlHead.append(text); } @Override public void writeHTMLHead(String text) throws IOException { if (flushed) throw new IOException("Page is already flushed"); htmlHead = new StringBuilder(text); } /** * @see lucee.runtime.writer.CFMLWriter#getHTMLHead() */ @Override public String getHTMLHead() throws IOException { if (flushed) throw new IOException("Page is already flushed"); return htmlHead == null ? "" : htmlHead.toString(); } @Override public void flushHTMLHead() throws IOException { if (htmlHead != null) { buffer.append(htmlHead); resetHTMLHead(); } } /** * @see lucee.runtime.writer.CFMLWriter#resetHTMLHead() */ @Override public void resetHTMLHead() throws IOException { if(flushed) throw new IOException("Page is already flushed"); htmlHead =null; } /** * just a wrapper function for ACF * @throws IOException */ public void initHeaderBuffer() throws IOException{ resetHTMLHead(); } /** * @see java.io.Writer#write(char[], int, int) */ @Override public void write(char[] cbuf, int off, int len) throws IOException { buffer.append(cbuf,off,len); _check(); } /** * @see javax.servlet.jsp.JspWriter#clear() */ @Override public void clear() throws IOException { if (flushed) throw new IOException("Response buffer is already flushed"); clearBuffer(); } /** * @see javax.servlet.jsp.JspWriter#clearBuffer() */ @Override public void clearBuffer() { buffer=new StringBuilder(BUFFER_SIZE); } /** * @see java.io.Writer#flush() */ @Override public void flush() throws IOException { flushBuffer(true); // weil flushbuffer das out erstellt muss ich nicht mehr checken out.flush(); } /** * @see java.io.Writer#flush() */ private void _flush(boolean closeConn) throws IOException { flushBuffer(closeConn); // weil flushbuffer das out erstellt muss ich nicht mehr checken out.flush(); } /** * Flush the output buffer to the underlying character stream, without * flushing the stream itself. This method is non-private only so that it * may be invoked by PrintStream. * @throws IOException * @throws */ protected final void flushBuffer(boolean closeConn) throws IOException { if(!flushed && closeConn) { response.setHeader("connection", "close"); //if(showVersion)response.setHeader(Constants.NAME+"-Version", version); } initOut(); byte[] barr = _toString(true).getBytes(ReqRspUtil.getCharacterEncoding(null,response)); if(cacheItem!=null && cacheItem.isValid()) { cacheItem.store(barr, flushed); // writeCache(barr,flushed); } flushed = true; out.write(barr); buffer=new StringBuilder(BUFFER_SIZE); // to not change to clearBuffer, produce problem with CFMLWriterWhiteSpace.clearBuffer } private String _toString(boolean releaseHeadData) { if (htmlBody == null && htmlHead == null) return buffer.toString(); String str = buffer.toString(); if (htmlHead != null) { int index = StringUtil.indexOfIgnoreCase(str, "</head>"); if (index > -1) { str = StringUtil.insertAt(str, htmlHead, index); } else { index = StringUtil.indexOfIgnoreCase(str, "<head>") + 7; if (index > 6) { str = StringUtil.insertAt(str, htmlHead, index); } else { str = htmlHead.append(str).toString(); } } } if (htmlBody != null) { int index=StringUtil.indexOfIgnoreCase(str,"</body>"); if (index > -1) { str = StringUtil.insertAt(str, htmlBody, index); } else { str += htmlBody.toString(); } } if (releaseHeadData) { htmlBody = null; htmlHead = null; } return str; } /** * @see java.lang.Object#toString() */ @Override public String toString() { return _toString(false); } /** * @see java.io.Writer#close() */ @Override public void close() throws IOException { if (response == null || closed) return; //boolean closeConn=true; if(out==null) { if(response.isCommitted()) { closed=true; return; } //print.out(_toString()); byte[] barr = _toString(true).getBytes(ReqRspUtil.getCharacterEncoding(null,response)); if(cacheItem!=null) { cacheItem.store(barr, false); // writeCache(barr,false); } if(closeConn)response.setHeader("connection", "close"); //if(showVersion)response.setHeader(Constants.NAME+"-Version", version); boolean allowCompression; if(barr.length<=512) allowCompression=false; else if(_allowCompression!=null) allowCompression=_allowCompression.booleanValue(); else allowCompression=((PageContextImpl)pc).getAllowCompression(); out = getOutputStream(allowCompression); if(contentLength && !(out instanceof GZIPOutputStream))ReqRspUtil.setContentLength(response,barr.length); out.write(barr); out.flush(); out.close(); out = null; } else { _flush(closeConn); out.close(); out = null; } closed = true; } private OutputStream getOutputStream(boolean allowCompression) throws IOException { if (allowCompression){ String encodings = ReqRspUtil.getHeader(request, "Accept-Encoding", ""); if( encodings.indexOf("gzip")!=-1 ) { boolean inline=HttpServletResponseWrap.get(); if(!inline) { ServletOutputStream os = response.getOutputStream(); response.setHeader("Content-Encoding", "gzip"); return new GZIPOutputStream(os); } } } return response.getOutputStream(); } /*private void writeCache(byte[] barr,boolean append) throws IOException { cacheItem.store(barr, append); //IOUtil.copy(new ByteArrayInputStream(barr), cacheItem.getResource().getOutputStream(append),true,true); //MetaData.getInstance(cacheItem.getDirectory()).add(cacheItem.getName(), cacheItem.getRaw()); }*/ /** * @see javax.servlet.jsp.JspWriter#getRemaining() */ @Override public int getRemaining() { return bufferSize - buffer.length(); } /** * @see javax.servlet.jsp.JspWriter#newLine() */ @Override public void newLine() throws IOException { println(); } /** * @see javax.servlet.jsp.JspWriter#print(boolean) */ @Override public void print(boolean arg) throws IOException { print(arg?new char[]{'t','r','u','e'}:new char[]{'f','a','l','s','e'}); } /** * @see javax.servlet.jsp.JspWriter#print(char) */ @Override public void print(char arg) throws IOException { buffer.append(arg); _check(); } /** * @see javax.servlet.jsp.JspWriter#print(int) */ @Override public void print(int arg) throws IOException { _print(String.valueOf(arg)); } /** * @see javax.servlet.jsp.JspWriter#print(long) */ @Override public void print(long arg) throws IOException { _print(String.valueOf(arg)); } /** * @see javax.servlet.jsp.JspWriter#print(float) */ @Override public void print(float arg) throws IOException { _print(String.valueOf(arg)); } /** * @see javax.servlet.jsp.JspWriter#print(double) */ @Override public void print(double arg) throws IOException { _print(String.valueOf(arg)); } /** * @see javax.servlet.jsp.JspWriter#print(java.lang.String) */ @Override public void print(String arg) throws IOException { buffer.append(arg); _check(); } /** * @see javax.servlet.jsp.JspWriter#print(java.lang.Object) */ @Override public void print(Object arg) throws IOException { _print(String.valueOf(arg)); } /** * @see javax.servlet.jsp.JspWriter#println() */ @Override public void println() throws IOException { _print("\n"); } /** * @see javax.servlet.jsp.JspWriter#println(boolean) */ @Override public void println(boolean arg) throws IOException { print(arg?new char[]{'t','r','u','e','\n'}:new char[]{'f','a','l','s','e','\n'}); } /** * @see javax.servlet.jsp.JspWriter#println(char) */ @Override public void println(char arg) throws IOException { print(new char[]{arg,'\n'}); } /** * @see javax.servlet.jsp.JspWriter#println(int) */ @Override public void println(int arg) throws IOException { print(arg); println(); } /** * @see javax.servlet.jsp.JspWriter#println(long) */ @Override public void println(long arg) throws IOException { print(arg); println(); } /** * @see javax.servlet.jsp.JspWriter#println(float) */ @Override public void println(float arg) throws IOException { print(arg); println(); } /** * @see javax.servlet.jsp.JspWriter#println(double) */ @Override public void println(double arg) throws IOException { print(arg); println(); } /** * @see javax.servlet.jsp.JspWriter#println(char[]) */ @Override public void println(char[] arg) throws IOException { print(arg); println(); } /** * @see javax.servlet.jsp.JspWriter#println(java.lang.String) */ @Override public void println(String arg) throws IOException { _print(arg); println(); } /** * @see javax.servlet.jsp.JspWriter#println(java.lang.Object) */ @Override public void println(Object arg) throws IOException { print(arg); println(); } /** * @see java.io.Writer#write(char[]) */ @Override public void write(char[] cbuf) throws IOException { print(cbuf); } /** * @see java.io.Writer#write(int) */ @Override public void write(int c) throws IOException { print(c); } /** * @see java.io.Writer#write(java.lang.String, int, int) */ @Override public void write(String str, int off, int len) throws IOException { write(str.toCharArray(),off,len); } /** * @see java.io.Writer#write(java.lang.String) */ @Override public void write(String str) throws IOException { buffer.append(str); _check(); } /** * @see lucee.runtime.writer.CFMLWriter#writeRaw(java.lang.String) */ @Override public void writeRaw(String str) throws IOException { _print(str); } /** * @return Returns the flushed. */ public boolean isFlushed() { return flushed; } @Override public void setClosed(boolean closed) { this.closed=closed; } private void _print(String arg) throws IOException { buffer.append(arg); _check(); } /** * @see lucee.runtime.writer.CFMLWriter#getResponseStream() */ @Override public OutputStream getResponseStream() throws IOException { initOut(); return out; } @Override public void doCache(lucee.runtime.cache.legacy.CacheItem ci) { this.cacheItem=ci; } /** * @return the cacheResource */ @Override public CacheItem getCacheItem() { return cacheItem; } // only for compatibility to other vendors public String getString() { return toString(); } @Override public void setAllowCompression(boolean allowCompression) { this._allowCompression=Caster.toBoolean(allowCompression); } }