001    package railo.runtime.writer;
002    
003    import java.io.IOException;
004    
005    import javax.servlet.http.HttpServletRequest;
006    import javax.servlet.http.HttpServletResponse;
007     
008    /**
009     * JSP Writer that Remove WhiteSpace from given content while preserving pre-formatted spaces 
010     * in Tags like <CODE> <PRE> and <TEXTAREA>
011     */
012    public final class CFMLWriterWSPref extends CFMLWriterImpl implements WhiteSpaceWriter {
013    
014            public static final char CHAR_NL = '\n';
015            public static final char CHAR_RETURN = '\r';
016            public static final char CHAR_SPACE = ' ';
017    
018            private static final char CHAR_GT = '>';
019            private static final char CHAR_LT = '<';
020            private static final char CHAR_SL = '/';
021            private static final String[] EXCLUDE_TAGS      = { "code", "pre", "textarea" };
022            
023            private static int minTagLen = 64;
024    
025            private static final boolean doChangeWsToSpace = true;                          // can help fight XSS attacks with characters like Vertical Tab
026            
027            private int[] depths;
028            private int depthSum = 0;
029            private char lastChar = 0;
030            private boolean isFirstChar = true;     
031            private StringBuilder sb = new StringBuilder();
032    
033    
034            static {
035                    
036                    // TODO: set EXCLUDE_TAGS (and perhaps doChangeWsToSpace) to values from WebConfigImpl
037                    
038                    for ( String s : EXCLUDE_TAGS )
039                            if ( s.length() < minTagLen )
040                                    minTagLen = s.length();
041                    
042                    minTagLen++;                                                                                                                    // add 1 for LessThan symbol
043            }
044            
045    
046            /**
047             * constructor of the class
048             * @param rsp
049             * @param bufferSize 
050             * @param autoFlush 
051             */
052            public CFMLWriterWSPref(HttpServletRequest req, HttpServletResponse rsp, int bufferSize, boolean autoFlush, boolean closeConn, 
053                            boolean showVersion, boolean contentLength, boolean allowCompression) {
054                    super(req,rsp, bufferSize, autoFlush,closeConn,showVersion,contentLength,allowCompression);
055                    depths = new int[ EXCLUDE_TAGS.length ];
056            }
057    
058    
059            /**
060             * prints the characters from the buffer and resets it
061             * 
062             * TODO: make sure that printBuffer() is called at the end of the stream in case we have some characters there! (flush() ?)
063             */
064            synchronized void printBuffer() throws IOException {                            // TODO: is synchronized really needed here?
065                    int len = sb.length();
066                    if ( len > 0 ) {
067                            char[] chars = new char[ len ];
068                            sb.getChars( 0, len, chars, 0 );
069                            sb.setLength( 0 );
070                            super.write( chars, 0, chars.length );
071                    }
072            }
073    
074            void printBufferEL() {
075                    if( sb.length() > 0 ) {
076                            try {
077                                    printBuffer();
078                            } 
079                            catch (IOException e) {}
080                    }
081            }
082    
083            /**
084             * checks if a character is part of an open html tag or close html tag, and if so adds it to the buffer, otherwise returns false.
085             * 
086             * @param c
087             * @return true if the char was added to the buffer, false otherwise
088             */
089            boolean addToBuffer( char c ) throws IOException {
090                    int len = sb.length();
091                    if ( len == 0 && c != CHAR_LT )
092                            return false;                                                                                                           // buffer must starts with '<'
093    
094                    sb.append( c );                                                                                                                 // if we reached this point then we will return true
095                    if ( ++len >= minTagLen ) {                                                                                          // increment len as it was sampled before we appended c
096                            boolean isClosingTag = ( len >= 2 && sb.charAt( 1 ) == CHAR_SL );
097                            String substr;
098                            if ( isClosingTag )
099                                    substr = sb.substring( 2 );                                                                             // we know that the 1st two chars are "</"
100                            else
101                                    substr = sb.substring( 1 );                                                                             // we know that the 1st char is "<"
102                            for ( int i=0; i<EXCLUDE_TAGS.length; i++ ) {                                                                // loop thru list of WS-preserving tags
103                                    if ( substr.equalsIgnoreCase( EXCLUDE_TAGS[ i ] ) ) {                                   // we have a match
104                                            if ( isClosingTag ) {
105                                                    depthDec( i );                                                                                  // decrement the depth at i and calc depthSum
106                                                    printBuffer();
107                                                    lastChar = 0;                                                                                   // needed to allow WS after buffer was printed
108                                            } else {
109                                                    depthInc( i );                                                                                  // increment the depth at i and calc depthSum
110                                            }
111                                    }
112                            }       
113                    }
114                    return true;
115            }
116    
117            /**
118             * decrement the depth at index and calc the new depthSum
119             * @param index
120             */
121            private void depthDec( int index ) {
122                    if ( --depths[ index ] < 0 )
123                            depths[ index ] = 0;
124                    depthCalc();
125            }
126    
127            /**
128             * increment the depth at index and calc the new depthSum
129             * @param index
130             */
131            private void depthInc( int index ) {
132                    depths[ index ]++;
133                    depthCalc();
134            }
135    
136            /**
137             * calc the new depthSum
138             */
139            private void depthCalc() {
140                    int sum = 0;
141                    for ( int d : depths )
142                            sum += d;
143                    depthSum = sum;
144            }
145    
146    
147            /**
148             * sends a character to output stream if it is not a consecutive white-space unless we're inside a PRE or TEXTAREA tag.
149             * 
150             * @param c
151             * @throws IOException 
152             */
153            @Override
154            public void print( char c ) throws IOException {
155                    boolean isWS = Character.isWhitespace( c );
156                    if ( isWS ) {
157                            if ( isFirstChar )                                                                                                      // ignore all WS before non-WS content
158                                    return;
159                            if ( c == CHAR_RETURN )                                                                                                 // ignore Carriage-Return chars
160                                    return;
161                            if ( sb.length() > 0 ) {
162                                    printBuffer();                                                                                                  // buffer should never contain WS so flush it
163                    lastChar = c == CHAR_NL ? CHAR_NL : ( doChangeWsToSpace ? CHAR_SPACE : c );
164                                    super.print( lastChar );
165                    return;
166                            }
167                    }
168    
169                    isFirstChar = false;
170                    if ( c == CHAR_GT && sb.length() > 0 )       
171                            printBuffer();                                                                                                          // buffer should never contain ">" so flush it
172    
173                    if ( isWS || !addToBuffer( c ) ) {
174                            if ( depthSum == 0 ) {                                                                                          // we're not in a WS-preserving tag; suppress whitespace
175                                    if ( isWS ) {                                                                                                   // this char is WS
176                                            if ( lastChar == CHAR_NL )                                                                      // lastChar was NL; discard this WS char
177                                                    return;
178                                            if ( c != CHAR_NL ) {                                                                           // this WS char is not NL
179                                                    if ( Character.isWhitespace( lastChar ) )
180                                                            return;                                                                                         // lastChar was WS but Not NL; discard this WS char
181                                                    if ( doChangeWsToSpace )
182                                                            c = CHAR_SPACE;                                                                         // this char is WS and not NL; change it to a regular space
183                                            }
184                                    }
185                            }
186                            lastChar = c;                                                                                                           // remember c as lastChar and write it to output stream
187                            super.print( c );
188                    }
189            }
190            
191    
192            /**
193             * @see railo.runtime.writer.CFMLWriter#writeRaw(java.lang.String)
194             */
195            public void writeRaw(String str) throws IOException {
196                    printBuffer();
197                    super.write(str);
198            }    
199            
200            /**
201         * just a wrapper function for ACF
202         * @throws IOException 
203         */
204        public void initHeaderBuffer() throws IOException{
205            resetHTMLHead();
206        }
207        
208        
209        
210        
211        
212        
213        
214        
215        
216        
217        
218    
219            /**
220             * @see railo.runtime.writer.CFMLWriterImpl#clear()
221             */
222            public final void clear() throws IOException {
223                    printBuffer();
224                    super.clear();
225            }
226    
227            /**
228             * @see railo.runtime.writer.CFMLWriterImpl#clearBuffer()
229             */
230            public final void clearBuffer() {
231                    printBufferEL();
232                    super.clearBuffer();
233            }
234    
235            /**
236             * @see railo.runtime.writer.CFMLWriterImpl#close()
237             */
238            public final void close() throws IOException {
239                    printBuffer();
240                    super.close();
241            }
242    
243            /**
244             * @see railo.runtime.writer.CFMLWriterImpl#flush()
245             */
246            public final void flush() throws IOException {
247                    printBuffer();
248                    super.flush();
249            }
250    
251            /**
252             * @see railo.runtime.writer.CFMLWriterImpl#getRemaining()
253             */
254            public final int getRemaining() {
255                    printBufferEL();
256                    return super.getRemaining();
257            }
258    
259            /**
260             * @see railo.runtime.writer.CFMLWriterImpl#newLine()
261             */
262            public final void newLine() throws IOException {
263                    print(CHAR_NL);
264            }
265    
266            /**
267             * @see railo.runtime.writer.CFMLWriterImpl#print(boolean)
268             */
269            public final void print(boolean b) throws IOException {
270                    printBuffer();
271                    super.print(b);
272            }
273    
274            /**
275             * @see railo.runtime.writer.CFMLWriterImpl#print(char[])
276             */
277            public final void print(char[] chars) throws IOException {
278                    write(chars,0,chars.length);
279            }
280    
281            /**
282             * @see railo.runtime.writer.CFMLWriterImpl#print(double)
283             */
284            public final void print(double d) throws IOException {
285                    printBuffer();
286                    super.print(d);
287            }
288    
289            /**
290             * @see railo.runtime.writer.CFMLWriterImpl#print(float)
291             */
292            public final void print(float f) throws IOException {
293                    printBuffer();
294                    super.print(f);
295            }
296    
297            /**
298             * @see railo.runtime.writer.CFMLWriterImpl#print(int)
299             */
300            public final void print(int i) throws IOException {
301                    printBuffer();
302                    super.print(i);
303            }
304    
305            /**
306             * @see railo.runtime.writer.CFMLWriterImpl#print(long)
307             */
308            public final void print(long l) throws IOException {
309                    printBuffer();
310                    super.print(l);
311            }
312    
313            /**
314             * @see railo.runtime.writer.CFMLWriterImpl#print(java.lang.Object)
315             */
316            public final void print(Object obj) throws IOException {
317                    print(obj.toString());
318            }
319    
320            /**
321             * @see railo.runtime.writer.CFMLWriterImpl#print(java.lang.String)
322             */
323            public final void print(String str) throws IOException {
324                    write(str.toCharArray(),0,str.length());
325            }
326    
327            /**
328             * @see railo.runtime.writer.CFMLWriterImpl#println()
329             */
330            public final void println() throws IOException {
331                    print(CHAR_NL);
332            }
333    
334            /**
335             * @see railo.runtime.writer.CFMLWriterImpl#println(boolean)
336             */
337            public final void println(boolean b) throws IOException {
338                    printBuffer();
339                    super.print(b);
340                    print(CHAR_NL);
341            }
342    
343            /**
344             * @see railo.runtime.writer.CFMLWriterImpl#println(char)
345             */
346            public final void println(char c) throws IOException {
347                    print(c);
348                    print(CHAR_NL);
349            }
350    
351            /**
352             * @see railo.runtime.writer.CFMLWriterImpl#println(char[])
353             */
354            public final void println(char[] chars) throws IOException {
355                    write(chars,0,chars.length);
356                    print(CHAR_NL);
357            }
358    
359            /**
360             * @see railo.runtime.writer.CFMLWriterImpl#println(double)
361             */
362            public final void println(double d) throws IOException {
363                    printBuffer();
364                    super.print(d);
365                    print(CHAR_NL);
366            }
367    
368            /**
369             * @see railo.runtime.writer.CFMLWriterImpl#println(float)
370             */
371            public final void println(float f) throws IOException {
372                    printBuffer();
373                    super.print(f);
374                    print(CHAR_NL);
375            }
376    
377            /**
378             * @see railo.runtime.writer.CFMLWriterImpl#println(int)
379             */
380            public final void println(int i) throws IOException {
381                    printBuffer();
382                    super.print(i);
383                    print(CHAR_NL);
384            }
385    
386            /**
387             * @see railo.runtime.writer.CFMLWriterImpl#println(long)
388             */
389            public final void println(long l) throws IOException {
390                    printBuffer();
391                    super.print(l);
392                    print(CHAR_NL);
393            }
394    
395            /**
396             * @see railo.runtime.writer.CFMLWriterImpl#println(java.lang.Object)
397             */
398            public final void println(Object obj) throws IOException {
399                    println(obj.toString());
400            }
401    
402            /**
403             * @see railo.runtime.writer.CFMLWriterImpl#println(java.lang.String)
404             */
405            public final void println(String str) throws IOException {
406                    print(str);
407                    print(CHAR_NL);
408                    
409            }
410    
411            /**
412             * @see railo.runtime.writer.CFMLWriterImpl#write(char[], int, int)
413             */
414            public final void write(char[] chars, int off, int len) throws IOException {
415                    for(int i=off;i<len;i++) {
416                            print(chars[i]);
417                    }
418            }
419            
420            /**
421             * @see railo.runtime.writer.CFMLWriterImpl#write(java.lang.String, int, int)
422             */
423            public final void write(String str, int off, int len) throws IOException {
424                    write(str.toCharArray(),off,len);
425            }
426            
427    
428            /**
429             * @see railo.runtime.writer.CFMLWriterImpl#write(char[])
430             */
431            public final void write(char[] chars) throws IOException {
432                    write(chars,0,chars.length);
433            }
434    
435            /**
436             * @see railo.runtime.writer.CFMLWriterImpl#write(int)
437             */
438            public final void write(int i) throws IOException {
439                    print(i);
440            }
441    
442            /**
443             * @see railo.runtime.writer.CFMLWriterImpl#write(java.lang.String)
444             */
445            public final void write(String str) throws IOException {
446            write(str.toCharArray(),0,str.length());
447            }
448    }