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