View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.context;
21  
22  import java.io.IOException;
23  import java.io.Writer;
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import javax.faces.component.UIComponent;
28  import javax.faces.context.PartialResponseWriter;
29  import javax.faces.context.ResponseWriter;
30  
31  import org.apache.myfaces.util.CDataEndEscapeFilterWriter;
32  
33  /**
34   * <p/>
35   * Double buffering partial response writer
36   * to take care if embedded CDATA blocks in update delete etc...
37   * <p/>
38   * According to the spec 13.4.4.1 Writing The Partial Response
39   * implementations have to take care to handle nested cdata blocks properly
40   * <p/>
41   * This means we cannot allow nested CDATA
42   * according to the xml spec http://www.w3.org/TR/REC-xml/#sec-cdata-sect
43   * everything within a CDATA block is unparsed except for ]]>
44   * <p/>
45   * Now we have following problem, that CDATA inserts can happen everywhere
46   * not only within the CDATA instructions.
47   * <p/>
48   * What we have to do now is to double buffer CDATA blocks until their end
49   * and also!!! parse their content for CDATA embedding and replace it with an escaped end sequence.
50   * <p/>
51   * Now parsing CDATA embedding is a little bit problematic in case of PPR because
52   * it can happen that someone simply adds a CDATA in a javascript string or somewhere else.
53   * Because he/she is not aware that we wrap the entire content into CDATA.
54   * Simply encoding and decoding of the CDATA is similarly problematic
55   * because the browser then chokes on embedded //<![CDATA[ //]]> sections
56   * <p/>
57   * What we do for now is to simply remove //<![CDATA[ and //]]>
58   * and replace all other pending cdatas with their cdata escapes
59   * ]]&gt; becomes &lt;![CDATA[]]]]&gt;&lt;![CDATA[&gt;
60   * <p/>
61   * If this causes problems in corner cases we also can add a second encoding step in
62   * case of the cdata Javascript comment removal is not enough to cover all corner cases.
63   * <p/>
64   * For now I will only implement this in the impl, due to the spec stating
65   * that implementations are responsible of the correct CDATA handling!
66   *
67   * @author Werner Punz (latest modification by $Author: lu4242 $)
68   * @version $Revision: 1237538 $ $Date: 2012-01-29 22:43:13 -0500 (Sun, 29 Jan 2012) $
69   */
70  
71  public class PartialResponseWriterImpl extends PartialResponseWriter
72  {
73  
74      class StackEntry
75      {
76          ResponseWriter writer;
77          Writer _doubleBuffer;
78  
79          StackEntry(ResponseWriter writer, Writer doubleBuffer)
80          {
81              this.writer = writer;
82              _doubleBuffer = doubleBuffer;
83          }
84  
85          public ResponseWriter getWriter()
86          {
87              return writer;
88          }
89  
90          public void setWriter(ResponseWriter writer)
91          {
92              this.writer = writer;
93          }
94  
95          public Writer getDoubleBuffer()
96          {
97              return _doubleBuffer;
98          }
99  
100         public void setDoubleBuffer(Writer doubleBuffer)
101         {
102             _doubleBuffer = doubleBuffer;
103         }
104     }
105 
106     ResponseWriter _cdataDoubleBufferWriter = null;
107     Writer _doubleBuffer = null;
108     List<StackEntry> _nestingStack = new ArrayList<StackEntry>(4);
109 
110     public PartialResponseWriterImpl(ResponseWriter writer)
111     {
112         super(writer);
113     }
114 
115     @Override
116     public void startCDATA() throws IOException
117     {
118         if (!isDoubleBufferEnabled())
119         {
120             super.startCDATA();
121         }
122         else
123         {
124             _cdataDoubleBufferWriter.write("<![CDATA[");
125         }
126         openDoubleBuffer();
127     }
128 
129     private void openDoubleBuffer()
130     {
131         _doubleBuffer = new CDataEndEscapeFilterWriter(_cdataDoubleBufferWriter == null ? 
132                 this.getWrapped() : _cdataDoubleBufferWriter );
133         _cdataDoubleBufferWriter = getWrapped().cloneWithWriter(_doubleBuffer);
134 
135         StackEntry entry = new StackEntry(_cdataDoubleBufferWriter, _doubleBuffer);
136 
137         _nestingStack.add(0, entry);
138     }
139 
140     @Override
141     public void endCDATA() throws IOException
142     {
143         closeDoubleBuffer(false);
144         if (isDoubleBufferEnabled())
145         {
146             _cdataDoubleBufferWriter.write("]]>");
147         }
148         else
149         {
150             super.endCDATA();
151         }
152     }
153 
154     /**
155      * Close double buffer condition
156      * This does either a normal close or a force
157      * close in case of a force close
158      * the entire buffer  is pushed with the post processing
159      * operations into the originating writer
160      *
161      * @param force if set to true the close is a forced close which in any condition
162      *              immediately pushes the buffer content into our writer with a pre operation
163      *              done upfront, in case of a false, the buffer is only swept out if our
164      *              internal CDATA nesting counter is at the nesting depth 1
165      * @throws IOException
166      */
167     private void closeDoubleBuffer(boolean force) throws IOException
168     {
169         if (!isDoubleBufferEnabled())
170         {
171             return;
172         }
173         /*
174         * if a force close is issued we reset the condition
175         * to 1 to reach the underlying closing block
176         */
177 
178         if (force)
179         {
180             while (!_nestingStack.isEmpty())
181             {
182                 popAndEncodeCurrentStackEntry();
183 
184             }
185         }
186         else
187         {
188             popAndEncodeCurrentStackEntry();
189         }
190     }
191 
192     private void popAndEncodeCurrentStackEntry() throws IOException
193     {
194         StackEntry elem = _nestingStack.remove(0);
195         StackEntry parent = (_nestingStack.isEmpty()) ? null : _nestingStack.get(0);
196         if (parent != null)
197         {
198             _cdataDoubleBufferWriter = parent.getWriter();
199             _doubleBuffer = parent.getDoubleBuffer();
200         }
201         else
202         {
203             _cdataDoubleBufferWriter = null;
204             _doubleBuffer = null;
205         }
206     }
207 
208     //--- we need to override ppr specifics to cover the case
209 
210     @Override
211     public void endInsert() throws IOException
212     {
213         //we use a force close here to fix possible user CDATA corrections
214         //under normal conditions the force close just processes the same
215         //the underlying close cdata does, but nevertheless
216         //it is better to have an additional layer of fixup
217         closeDoubleBuffer(true);
218         super.endInsert();
219     }
220 
221     @Override
222     public void endUpdate() throws IOException
223     {
224         //we use a force close here to fix possible user CDATA corrections
225         //under normal conditions the force close just processes the same
226         //the underlying close cdata does, but nevertheless
227         //it is better to have an additional layer of fixup
228         closeDoubleBuffer(true);
229         super.endUpdate();    //To change body of overridden methods use File | Settings | File Templates.
230     }
231 
232     @Override
233     public void endExtension() throws IOException
234     {
235         //we use a force close here to fix possible user CDATA corrections
236         //under normal conditions the force close just processes the same
237         //the underlying close cdata does, but nevertheless
238         //it is better to have an additional layer of fixup
239         closeDoubleBuffer(true);
240         super.endExtension();    //To change body of overridden methods use File | Settings | File Templates.
241     }
242 
243     @Override
244     public void endEval() throws IOException
245     {
246         //we use a force close here to fix possible user CDATA corrections
247         //under normal conditions the force close just processes the same
248         //the underlying close cdata does, but nevertheless
249         //it is better to have an additional layer of fixup
250         closeDoubleBuffer(true);
251         super.endEval();    //To change body of overridden methods use File | Settings | File Templates.
252     }
253 
254     @Override
255     public void endError() throws IOException
256     {
257         //we use a force close here to fix possible user CDATA corrections
258         //under normal conditions the force close just processes the same
259         //the underlying close cdata does, but nevertheless
260         //it is better to have an additional layer of fixup
261         closeDoubleBuffer(true);
262         super.endError();    //To change body of overridden methods use File | Settings | File Templates.
263     }
264 
265     //--- optional delegation method ---
266 
267     @Override
268     public void endElement(String name) throws IOException
269     {
270         if (isDoubleBufferEnabled())
271         {
272             _cdataDoubleBufferWriter.endElement(name);
273         }
274         else
275         {
276             super.endElement(name);
277         }
278     }
279 
280     @Override
281     public void writeComment(Object comment) throws IOException
282     {
283         if (isDoubleBufferEnabled())
284         {
285             _cdataDoubleBufferWriter.writeComment(comment);
286         }
287         else
288         {
289             super.writeComment(comment);
290         }
291     }
292 
293     private boolean isDoubleBufferEnabled()
294     {
295         return !_nestingStack.isEmpty();
296     }
297 
298     @Override
299     public void startElement(String name, UIComponent component) throws IOException
300     {
301         if (isDoubleBufferEnabled())
302         {
303             _cdataDoubleBufferWriter.startElement(name, component);
304         }
305         else
306         {
307             super.startElement(name, component);
308         }
309     }
310 
311     @Override
312     public void writeText(Object text, String property) throws IOException
313     {
314         if (isDoubleBufferEnabled())
315         {
316             _cdataDoubleBufferWriter.writeText(text, property);
317         }
318         else
319         {
320             super.writeText(text, property);
321         }
322     }
323 
324     @Override
325     public void writeText(char[] text, int off, int len) throws IOException
326     {
327         if (isDoubleBufferEnabled())
328         {
329             _cdataDoubleBufferWriter.writeText(text, off, len);
330         }
331         else
332         {
333             super.writeText(text, off, len);
334         }
335     }
336 
337     @Override
338     public void write(char[] cbuf, int off, int len) throws IOException
339     {
340         if (isDoubleBufferEnabled())
341         {
342             _cdataDoubleBufferWriter.write(cbuf, off, len);
343         }
344         else
345         {
346             super.write(cbuf, off, len);
347         }
348     }
349 
350     @Override
351     public ResponseWriter cloneWithWriter(Writer writer)
352     {
353         return super.cloneWithWriter(writer);
354     }
355 
356     @Override
357     public void writeURIAttribute(String name, Object value, String property) throws IOException
358     {
359         if (isDoubleBufferEnabled())
360         {
361             _cdataDoubleBufferWriter.writeURIAttribute(name, value, property);
362         }
363         else
364         {
365             super.writeURIAttribute(name, value, property);
366         }
367     }
368 
369     @Override
370     public void close() throws IOException
371     {
372         //in case of a close
373         //we have a user error of a final CDATA block
374         //we do some error correction here
375         //since a close is issued we do not care about
376         //a proper closure of the cdata block here anymore
377         if (isDoubleBufferEnabled())
378         {
379             //we have to properly close all nested cdata stacks
380             //end end our cdata block if open
381             closeDoubleBuffer(true);
382 
383             super.endCDATA();
384         }
385         super.close();
386     }
387 
388     @Override
389     public void flush() throws IOException
390     {
391         if (isDoubleBufferEnabled())
392         {
393             _cdataDoubleBufferWriter.flush();
394         }
395         super.flush();
396     }
397 
398     @Override
399     public void writeAttribute(String name, Object value, String property) throws IOException
400     {
401         if (isDoubleBufferEnabled())
402         {
403             _cdataDoubleBufferWriter.writeAttribute(name, value, property);
404         }
405         else
406         {
407             super.writeAttribute(name, value, property);
408         }
409     }
410 
411     @Override
412     public void writeText(Object object, UIComponent component, String string) throws IOException
413     {
414         if (isDoubleBufferEnabled())
415         {
416             _cdataDoubleBufferWriter.writeText(object, component, string);
417         }
418         else
419         {
420             super.writeText(object, component, string);
421         }
422     }
423 
424     @Override
425     public Writer append(char c) throws IOException
426     {
427         if (isDoubleBufferEnabled())
428         {
429             _cdataDoubleBufferWriter.append(c);
430             return this;
431         }
432         else
433         {
434             return super.append(c);
435         }
436     }
437 
438     @Override
439     public Writer append(CharSequence csq, int start, int end) throws IOException
440     {
441         if (isDoubleBufferEnabled())
442         {
443             _cdataDoubleBufferWriter.append(csq, start, end);
444             return this;
445         }
446         else
447         {
448             return super.append(csq, start, end);
449         }
450     }
451 
452     @Override
453     public Writer append(CharSequence csq) throws IOException
454     {
455         if (isDoubleBufferEnabled())
456         {
457             _cdataDoubleBufferWriter.append(csq);
458             return this;
459         }
460         else
461         {
462             return super.append(csq);
463         }
464     }
465 
466     @Override
467     public void write(char[] cbuf) throws IOException
468     {
469         if (isDoubleBufferEnabled())
470         {
471             _cdataDoubleBufferWriter.write(cbuf);
472         }
473         else
474         {
475             super.write(cbuf);
476         }
477     }
478 
479     @Override
480     public void write(int c) throws IOException
481     {
482         if (isDoubleBufferEnabled())
483         {
484             _cdataDoubleBufferWriter.write(c);
485         }
486         else
487         {
488             super.write(c);
489         }
490     }
491 
492     @Override
493     public void write(String str, int off, int len) throws IOException
494     {
495         if (isDoubleBufferEnabled())
496         {
497             _cdataDoubleBufferWriter.write(str, off, len);
498         }
499         else
500         {
501             super.write(str, off, len);
502         }
503     }
504 
505     @Override
506     public void write(String str) throws IOException
507     {
508         if (isDoubleBufferEnabled())
509         {
510             _cdataDoubleBufferWriter.write(str);
511         }
512         else
513         {
514             super.write(str);
515         }
516     }
517 }