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