001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.writer;
020
021import java.io.IOException;
022import java.io.OutputStream;
023import java.util.zip.GZIPOutputStream;
024
025import javax.servlet.ServletOutputStream;
026import javax.servlet.http.HttpServletRequest;
027import javax.servlet.http.HttpServletResponse;
028
029import lucee.commons.lang.StringUtil;
030import lucee.runtime.Info;
031import lucee.runtime.PageContext;
032import lucee.runtime.PageContextImpl;
033import lucee.runtime.cache.legacy.CacheItem;
034import lucee.runtime.net.http.HttpServletResponseWrap;
035import lucee.runtime.net.http.ReqRspUtil;
036import lucee.runtime.op.Caster;
037
038/**
039 * Implementation of a JSpWriter
040 */
041public class CFMLWriterImpl extends CFMLWriter { 
042     
043        private static final int BUFFER_SIZE = 100000;
044        private static final String VERSION = Info.getVersionAsString();  
045    private OutputStream out;
046        private HttpServletResponse response;
047    private boolean flushed;
048    private StringBuilder htmlHead;
049    private StringBuilder htmlBody;
050    private StringBuilder buffer=new StringBuilder(BUFFER_SIZE);
051    private boolean closed=false;
052    private boolean closeConn;
053    private boolean showVersion;
054    private boolean contentLength;
055    private CacheItem cacheItem;
056        private HttpServletRequest request;
057        private Boolean _allowCompression;
058        private PageContext pc; 
059    
060    /**
061     * constructor of the class
062     * @param response Response Object
063     * @param bufferSize buffer Size
064     * @param autoFlush do auto flush Content
065     */
066    public CFMLWriterImpl(PageContext pc,HttpServletRequest request, HttpServletResponse response, int bufferSize, boolean autoFlush, boolean closeConn, boolean showVersion, boolean contentLength) {
067        super(bufferSize, autoFlush);
068        this.pc=pc;
069        this.request=request;
070        this.response=response;
071        this.autoFlush=autoFlush;
072        this.bufferSize=bufferSize;
073        this.closeConn=closeConn;
074        this.showVersion=showVersion;
075        this.contentLength=contentLength;
076        //this.allowCompression=allowCompression;
077    }
078
079    /* *
080     * constructor of the class
081     * @param response Response Object
082     * /
083    public JspWriterImpl(HttpServletResponse response) {
084        this(response, BUFFER_SIZE, false);
085    }*/
086    
087    private void _check() throws IOException {
088        if(autoFlush && buffer.length()>bufferSize)  {
089            _flush(true);
090        }
091    }
092
093    /**
094     * @throws IOException
095     */
096    protected void initOut() throws IOException {
097        if (out == null) {
098                out=getOutputStream(false);
099            //out=response.getWriter();
100        }
101    }
102
103
104        /**
105     * @see javax.servlet.jsp.JspWriter#print(char[]) 
106     */ 
107    public void print(char[] arg) throws IOException { 
108        buffer.append(arg);
109        _check();
110    }
111    
112    /**
113     * reset configuration of buffer
114     * @param bufferSize size of the buffer
115     * @param autoFlush does the buffer autoflush
116     * @throws IOException
117     */
118    public void setBufferConfig(int bufferSize, boolean autoFlush) throws IOException {
119        this.bufferSize=bufferSize;
120        this.autoFlush=autoFlush;
121        _check();
122    }
123
124        public void appendHTMLBody(String text) throws IOException {
125
126                if (htmlBody == null)
127                        htmlBody = new StringBuilder(256);
128
129                htmlBody.append(text);
130        }
131
132        public void writeHTMLBody(String text) throws IOException {
133
134                if (flushed)    throw new IOException("Page is already flushed");
135
136                htmlBody = new StringBuilder(text);
137        }
138
139        public String getHTMLBody() throws IOException {
140
141                if (flushed)    throw new IOException("Page is already flushed");
142
143                return htmlBody == null ? "" : htmlBody.toString();
144        }
145
146        public void flushHTMLBody() throws IOException {
147
148                if (htmlBody != null) {
149
150                        buffer.append(htmlBody);
151                        resetHTMLBody();
152                }
153        }
154
155        /**
156         * @see lucee.runtime.writer.CFMLWriter#resetHTMLHead()
157         */
158        public void resetHTMLBody() throws IOException {
159                if(flushed) throw new IOException("Page is already flushed");
160                htmlBody = null;
161        }
162
163    /**
164     * 
165     * @param text
166     * @throws IOException
167     */
168    public void appendHTMLHead(String text) throws IOException {
169
170            if (flushed)    throw new IOException("Page is already flushed");
171
172        if (htmlHead == null)
173                htmlHead = new StringBuilder(256);
174
175        htmlHead.append(text);
176    }
177    
178    public void writeHTMLHead(String text) throws IOException {
179
180            if (flushed)    throw new IOException("Page is already flushed");
181
182            htmlHead = new StringBuilder(text);
183    }
184    
185    /** 
186     * @see lucee.runtime.writer.CFMLWriter#getHTMLHead()
187     */
188    public String getHTMLHead() throws IOException {
189
190            if (flushed)    throw new IOException("Page is already flushed");
191
192        return htmlHead == null ? "" : htmlHead.toString();
193    }
194
195        public void flushHTMLHead() throws IOException {
196
197                if (htmlHead != null) {
198
199                        buffer.append(htmlHead);
200                        resetHTMLHead();
201                }
202        }
203
204    /** 
205     * @see lucee.runtime.writer.CFMLWriter#resetHTMLHead()
206     */
207    public void resetHTMLHead() throws IOException {
208        if(flushed) throw new IOException("Page is already flushed");
209        htmlHead =null;
210    }
211    
212    /**
213     * just a wrapper function for ACF
214     * @throws IOException 
215     */
216    public void initHeaderBuffer() throws IOException{
217        resetHTMLHead();
218    }
219
220
221    /** 
222     * @see java.io.Writer#write(char[], int, int) 
223     */ 
224    public void write(char[] cbuf, int off, int len) throws IOException { 
225        buffer.append(cbuf,off,len);
226        _check();
227    }
228
229    /** 
230     * @see javax.servlet.jsp.JspWriter#clear() 
231     */ 
232    public void clear() throws IOException { 
233        if (flushed)  throw new IOException("Response buffer is already flushed");
234        clearBuffer();
235    } 
236
237    /** 
238     * @see javax.servlet.jsp.JspWriter#clearBuffer() 
239     */ 
240    public void clearBuffer() { 
241        buffer=new StringBuilder(BUFFER_SIZE);
242    } 
243
244    /** 
245     * @see java.io.Writer#flush() 
246     */ 
247    public void flush() throws IOException { 
248        flushBuffer(true);
249        // weil flushbuffer das out erstellt muss ich nicht mehr checken
250        out.flush();
251    } 
252    
253    /** 
254     * @see java.io.Writer#flush() 
255     */ 
256    private void _flush(boolean closeConn) throws IOException { 
257        flushBuffer(closeConn);
258        // weil flushbuffer das out erstellt muss ich nicht mehr checken
259        out.flush();
260        
261    } 
262
263    /**
264     * Flush the output buffer to the underlying character stream, without
265     * flushing the stream itself.  This method is non-private only so that it
266     * may be invoked by PrintStream.
267     * @throws IOException
268     * @throws  
269     */
270    protected final void flushBuffer(boolean closeConn) throws IOException {
271        if(!flushed && closeConn) {
272                response.setHeader("connection", "close");
273                if(showVersion)response.setHeader("Lucee-Version", VERSION);
274                
275        }
276        initOut();
277        byte[] barr = _toString(true).getBytes(ReqRspUtil.getCharacterEncoding(null,response));
278        
279        if(cacheItem!=null && cacheItem.isValid()) {
280                cacheItem.store(barr, flushed);
281                // writeCache(barr,flushed);
282        }
283        flushed = true;
284        out.write(barr);
285        
286        buffer=new StringBuilder(BUFFER_SIZE); // to not change to clearBuffer, produce problem with CFMLWriterWhiteSpace.clearBuffer 
287    } 
288    
289    
290
291    private String _toString(boolean releaseHeadData) {
292
293            if (htmlBody == null && htmlHead == null)
294            return buffer.toString();
295
296        String str = buffer.toString();
297
298            if (htmlHead != null) {
299
300                    int index = StringUtil.indexOfIgnoreCase(str, "</head>");
301                    if (index > -1) {
302
303                            str = StringUtil.insertAt(str, htmlHead, index);
304                    }
305                    else {
306
307                            index = StringUtil.indexOfIgnoreCase(str, "<head>") + 7;
308                            if (index > 6) {
309
310                                    str = StringUtil.insertAt(str, htmlHead, index);
311                            }
312                            else {
313
314                                    str = htmlHead.append(str).toString();
315                            }
316                    }
317            }
318
319            if (htmlBody != null) {
320
321                    int index=StringUtil.indexOfIgnoreCase(str,"</body>");
322                    if (index > -1) {
323
324                            str = StringUtil.insertAt(str, htmlBody, index);
325                    }
326                    else {
327
328                            str += htmlBody.toString();
329                    }
330            }
331
332        if (releaseHeadData) {
333                htmlBody = null;
334                htmlHead = null;
335        }
336
337        return str;
338    }
339    
340        /**
341         * @see java.lang.Object#toString()
342         */
343        public String toString() {
344        return _toString(false);
345    }
346    
347    
348    
349    
350
351    /** 
352     * @see java.io.Writer#close() 
353     */ 
354    public void close() throws IOException {
355        if (response == null || closed) return;
356        //boolean closeConn=true;
357        if(out==null) { 
358                if(response.isCommitted()) {
359                        closed=true;
360                        return;
361                }
362                //print.out(_toString());
363                byte[] barr = _toString(true).getBytes(ReqRspUtil.getCharacterEncoding(null,response));
364            
365                if(cacheItem!=null)     {
366                        cacheItem.store(barr, false);
367                // writeCache(barr,false);
368                }
369                
370                if(closeConn)response.setHeader("connection", "close");
371                if(showVersion)response.setHeader("Lucee-Version", VERSION);
372                boolean allowCompression;
373            if(barr.length<=512) 
374                allowCompression=false;
375            else if(_allowCompression!=null) 
376                allowCompression=_allowCompression.booleanValue();
377            else
378                allowCompression=((PageContextImpl)pc).getAllowCompression();
379            out = getOutputStream(allowCompression);
380                
381                
382                if(contentLength && !(out instanceof GZIPOutputStream))ReqRspUtil.setContentLength(response,barr.length);
383            
384                out.write(barr);
385                    out.flush();
386                    out.close();
387            
388            
389            out = null;
390        }
391        else {
392            _flush(closeConn);
393            out.close();
394            out = null;
395        }
396        closed = true;
397    } 
398    
399
400    private OutputStream getOutputStream(boolean allowCompression) throws IOException {
401        
402        if (allowCompression){
403                
404            String encodings = ReqRspUtil.getHeader(request, "Accept-Encoding", "");
405            if( encodings.indexOf("gzip")!=-1 ) {
406                boolean inline=HttpServletResponseWrap.get();
407                if(!inline) {
408                        ServletOutputStream os = response.getOutputStream();
409                        response.setHeader("Content-Encoding", "gzip");
410                                return new GZIPOutputStream(os);
411                }
412            }
413        }
414        return response.getOutputStream();
415        }
416    
417    
418
419    /*private void writeCache(byte[] barr,boolean append) throws IOException {
420        cacheItem.store(barr, append);
421        //IOUtil.copy(new ByteArrayInputStream(barr), cacheItem.getResource().getOutputStream(append),true,true);
422        //MetaData.getInstance(cacheItem.getDirectory()).add(cacheItem.getName(), cacheItem.getRaw());
423        }*/
424
425    /** 
426     * @see javax.servlet.jsp.JspWriter#getRemaining() 
427     */ 
428    public int getRemaining() { 
429        return bufferSize - buffer.length();
430    }
431
432    /** 
433     * @see javax.servlet.jsp.JspWriter#newLine() 
434     */ 
435    public void newLine() throws IOException { 
436        println();
437    } 
438
439    /** 
440     * @see javax.servlet.jsp.JspWriter#print(boolean) 
441     */ 
442    public void print(boolean arg) throws IOException { 
443        print(arg?new char[]{'t','r','u','e'}:new char[]{'f','a','l','s','e'}); 
444    }
445
446    /** 
447     * @see javax.servlet.jsp.JspWriter#print(char) 
448     */ 
449    public void print(char arg) throws IOException { 
450        buffer.append(arg);
451        _check();
452    } 
453
454    /** 
455     * @see javax.servlet.jsp.JspWriter#print(int) 
456     */ 
457    public void print(int arg) throws IOException { 
458        _print(String.valueOf(arg)); 
459    } 
460
461    /** 
462     * @see javax.servlet.jsp.JspWriter#print(long) 
463     */ 
464    public void print(long arg) throws IOException { 
465        _print(String.valueOf(arg)); 
466
467    } 
468
469    /** 
470     * @see javax.servlet.jsp.JspWriter#print(float) 
471     */ 
472    public void print(float arg) throws IOException { 
473        _print(String.valueOf(arg)); 
474    } 
475
476    /** 
477     * @see javax.servlet.jsp.JspWriter#print(double) 
478     */ 
479    public void print(double arg) throws IOException { 
480        _print(String.valueOf(arg)); 
481    } 
482
483    /** 
484     * @see javax.servlet.jsp.JspWriter#print(java.lang.String) 
485     */ 
486    public void print(String arg) throws IOException { 
487        buffer.append(arg);
488        _check();
489    } 
490
491    /** 
492     * @see javax.servlet.jsp.JspWriter#print(java.lang.Object) 
493     */ 
494    public void print(Object arg) throws IOException { 
495        _print(String.valueOf(arg)); 
496    } 
497
498    /** 
499     * @see javax.servlet.jsp.JspWriter#println() 
500     */ 
501    public void println() throws IOException { 
502        _print("\n"); 
503
504    } 
505
506    /** 
507     * @see javax.servlet.jsp.JspWriter#println(boolean) 
508     */ 
509    public void println(boolean arg) throws IOException { 
510        print(arg?new char[]{'t','r','u','e','\n'}:new char[]{'f','a','l','s','e','\n'}); 
511    } 
512
513    /** 
514     * @see javax.servlet.jsp.JspWriter#println(char) 
515     */ 
516    public void println(char arg) throws IOException { 
517        print(new char[]{arg,'\n'}); 
518    } 
519
520    /** 
521     * @see javax.servlet.jsp.JspWriter#println(int) 
522     */ 
523    public void println(int arg) throws IOException { 
524        print(arg); 
525        println(); 
526    } 
527
528    /** 
529     * @see javax.servlet.jsp.JspWriter#println(long) 
530     */ 
531    public void println(long arg) throws IOException { 
532        print(arg); 
533        println(); 
534    } 
535
536    /** 
537     * @see javax.servlet.jsp.JspWriter#println(float) 
538     */ 
539    public void println(float arg) throws IOException { 
540        print(arg); 
541        println(); 
542    } 
543
544    /** 
545     * @see javax.servlet.jsp.JspWriter#println(double) 
546     */ 
547    public void println(double arg) throws IOException { 
548        print(arg); 
549        println(); 
550    } 
551
552    /** 
553     * @see javax.servlet.jsp.JspWriter#println(char[]) 
554     */ 
555    public void println(char[] arg) throws IOException { 
556        print(arg); 
557        println(); 
558    } 
559
560    /** 
561     * @see javax.servlet.jsp.JspWriter#println(java.lang.String) 
562     */ 
563    public void println(String arg) throws IOException { 
564        _print(arg); 
565        println(); 
566    } 
567
568    /** 
569     * @see javax.servlet.jsp.JspWriter#println(java.lang.Object) 
570     */ 
571    public void println(Object arg) throws IOException { 
572        print(arg); 
573        println(); 
574    }
575    
576    /** 
577     * @see java.io.Writer#write(char[]) 
578     */ 
579    public void write(char[] cbuf) throws IOException { 
580        print(cbuf); 
581    } 
582
583    /** 
584     * @see java.io.Writer#write(int) 
585     */ 
586    public void write(int c) throws IOException { 
587        print(c); 
588    } 
589
590    /** 
591     * @see java.io.Writer#write(java.lang.String, int, int) 
592     */ 
593    public void write(String str, int off, int len) throws IOException { 
594        write(str.toCharArray(),off,len);
595    } 
596
597    /** 
598     * @see java.io.Writer#write(java.lang.String) 
599     */ 
600    public void write(String str) throws IOException { 
601        buffer.append(str);
602        _check();
603    }
604    
605    /**
606         * @see lucee.runtime.writer.CFMLWriter#writeRaw(java.lang.String)
607         */
608        public void writeRaw(String str) throws IOException {
609                _print(str);
610        }
611
612    /**
613     * @return Returns the flushed.
614     */
615    public boolean isFlushed() {
616        return flushed;
617    }
618
619    public void setClosed(boolean closed) {
620        this.closed=closed;
621    }
622
623    private void _print(String arg) throws IOException { 
624        buffer.append(arg);
625        _check();
626    }
627
628        /**
629         * @see lucee.runtime.writer.CFMLWriter#getResponseStream()
630         */
631        public OutputStream getResponseStream() throws IOException {
632                initOut();
633                return out;
634        }
635
636        public void doCache(lucee.runtime.cache.legacy.CacheItem ci) {
637                this.cacheItem=ci;
638        }
639
640        /**
641         * @return the cacheResource
642         */
643        public CacheItem getCacheItem() {
644                return cacheItem;
645        }
646        
647
648        // only for compatibility to other vendors
649        public String getString() {
650                return toString();
651        }
652
653        @Override
654        public void setAllowCompression(boolean allowCompression) {
655                this._allowCompression=Caster.toBoolean(allowCompression);
656        }
657        
658        
659
660}