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  package org.apache.myfaces.shared.renderkit.html;
20  
21  import java.io.IOException;
22  import java.io.Writer;
23  import java.nio.charset.Charset;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  import javax.el.ValueExpression;
30  
31  import javax.faces.FacesException;
32  import javax.faces.component.UIComponent;
33  import javax.faces.context.FacesContext;
34  import javax.faces.context.ResponseWriter;
35  import javax.faces.render.Renderer;
36  
37  import org.apache.myfaces.shared.config.MyfacesConfig;
38  import org.apache.myfaces.shared.renderkit.ContentTypeUtils;
39  import org.apache.myfaces.shared.renderkit.RendererUtils;
40  import org.apache.myfaces.shared.renderkit.html.util.UnicodeEncoder;
41  import org.apache.myfaces.shared.util.CommentUtils;
42  import org.apache.myfaces.shared.util.StreamCharBuffer;
43  
44  public class HtmlResponseWriterImpl
45          extends ResponseWriter
46  {
47      private static final Logger log = Logger.getLogger(HtmlResponseWriterImpl.class.getName());
48  
49      private static final String DEFAULT_CONTENT_TYPE = "text/html";
50      private static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
51      private static final String UTF8 = "UTF-8";
52  
53      private static final String APPLICATION_XML_CONTENT_TYPE = "application/xml";
54      private static final String TEXT_XML_CONTENT_TYPE = "text/xml";
55      
56      //private boolean _writeDummyForm = false;
57      //private Set _dummyFormParams = null;
58  
59      /**
60       * The writer used as output, or in other words, the one passed on the constructor
61       */
62      private Writer _outputWriter;
63      
64      /**
65       * The writer we are using to store data.
66       */
67      private Writer _currentWriter;
68      
69      /**
70       * The writer used to buffer script and style content
71       */
72      private StreamCharBuffer _buffer;
73      
74      private String _contentType;
75      
76      private String _writerContentTypeMode;
77      
78      /**
79       * This var prevents check if the contentType is for xhtml multiple times.
80       */
81      private boolean _isXhtmlContentType;
82      
83      /**
84       * Indicate the current response writer should not close automatically html elements
85       * and let the writer close them.
86       */
87      private boolean _useStraightXml;
88      
89      private String _characterEncoding;
90      private boolean _wrapScriptContentWithXmlCommentTag;
91      private boolean _isUTF8;
92      
93      private String _startElementName;
94      private boolean _isInsideScript = false;
95      private boolean _isStyle = false;
96      private Boolean _isTextArea;
97      private UIComponent _startElementUIComponent;
98      private boolean _startTagOpen;
99      private Map<String, Object> _passThroughAttributesMap;
100     private FacesContext _facesContext;
101 
102     private boolean _cdataOpen;
103     
104     private List<String> _startedChangedElements;
105     private List<Integer> _startedElementsCount;
106 
107     private static final String CDATA_START = "<![CDATA[ \n";
108     private static final String CDATA_START_NO_LINE_RETURN = "<![CDATA[";
109     private static final String COMMENT_START = "<!--\n";
110     private static final String CDATA_COMMENT_START = "//<![CDATA[ \n";
111     private static final String CDATA_COMMENT_END = "\n//]]>";
112     private static final String CDATA_END = "\n]]>";
113     private static final String CDATA_END_NO_LINE_RETURN = "]]>";
114     private static final String COMMENT_COMMENT_END = "\n//-->";
115     private static final String COMMENT_END = "\n-->";
116 
117     static private final String[][] EMPTY_ELEMENT_ARR = new String[256][];
118 
119     static private final String[] A_NAMES = new String[]
120     {
121       "area",
122     };
123 
124     static private final String[] B_NAMES = new String[]
125     {
126       "br",
127       "base",
128       "basefont",
129     };
130 
131     static private final String[] C_NAMES = new String[]
132     {
133       "col",
134     };
135 
136     static private final String[] E_NAMES = new String[]
137     {
138       "embed",
139     };
140 
141     static private final String[] F_NAMES = new String[]
142     {
143       "frame",
144     };
145 
146     static private final String[] H_NAMES = new String[]
147     {
148       "hr",
149     };
150 
151     static private final String[] I_NAMES = new String[]
152     {
153       "img",
154       "input",
155       "isindex",
156     };
157 
158     static private final String[] L_NAMES = new String[]
159     {
160       "link",
161     };
162 
163     static private final String[] M_NAMES = new String[]
164     {
165       "meta",
166     };
167 
168     static private final String[] P_NAMES = new String[]
169     {
170       "param",
171     };
172 
173     static
174     {
175       EMPTY_ELEMENT_ARR['a'] = A_NAMES;
176       EMPTY_ELEMENT_ARR['A'] = A_NAMES;
177       EMPTY_ELEMENT_ARR['b'] = B_NAMES;
178       EMPTY_ELEMENT_ARR['B'] = B_NAMES;
179       EMPTY_ELEMENT_ARR['c'] = C_NAMES;
180       EMPTY_ELEMENT_ARR['C'] = C_NAMES;
181       EMPTY_ELEMENT_ARR['e'] = E_NAMES;
182       EMPTY_ELEMENT_ARR['E'] = E_NAMES;
183       EMPTY_ELEMENT_ARR['f'] = F_NAMES;
184       EMPTY_ELEMENT_ARR['F'] = F_NAMES;
185       EMPTY_ELEMENT_ARR['h'] = H_NAMES;
186       EMPTY_ELEMENT_ARR['H'] = H_NAMES;
187       EMPTY_ELEMENT_ARR['i'] = I_NAMES;
188       EMPTY_ELEMENT_ARR['I'] = I_NAMES;
189       EMPTY_ELEMENT_ARR['l'] = L_NAMES;
190       EMPTY_ELEMENT_ARR['L'] = L_NAMES;
191       EMPTY_ELEMENT_ARR['m'] = M_NAMES;
192       EMPTY_ELEMENT_ARR['M'] = M_NAMES;
193       EMPTY_ELEMENT_ARR['p'] = P_NAMES;
194       EMPTY_ELEMENT_ARR['P'] = P_NAMES;
195     }    
196     
197     public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding)
198     {
199         this(writer,contentType,characterEncoding,true);
200     }
201 
202     public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding,
203             boolean wrapScriptContentWithXmlCommentTag)
204     {
205         this(writer,contentType, characterEncoding, wrapScriptContentWithXmlCommentTag, 
206                 contentType != null && HtmlRendererUtils.isXHTMLContentType(contentType) ? 
207                     ContentTypeUtils.XHTML_CONTENT_TYPE : ContentTypeUtils.HTML_CONTENT_TYPE);
208     }
209     
210     public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding,
211              boolean wrapScriptContentWithXmlCommentTag, String writerContentTypeMode)
212     throws FacesException
213     {
214         _outputWriter = writer;
215         //The current writer to be used is the one used as output 
216         _currentWriter = _outputWriter;
217         _wrapScriptContentWithXmlCommentTag = wrapScriptContentWithXmlCommentTag;
218         
219         _contentType = contentType;
220         if (_contentType == null)
221         {
222             if (log.isLoggable(Level.FINE))
223             {
224                 log.fine("No content type given, using default content type " + DEFAULT_CONTENT_TYPE);
225             }
226             _contentType = DEFAULT_CONTENT_TYPE;
227         }
228         _writerContentTypeMode = writerContentTypeMode;
229         _isXhtmlContentType = writerContentTypeMode.indexOf(ContentTypeUtils.XHTML_CONTENT_TYPE) != -1;
230         
231         _useStraightXml = _isXhtmlContentType && (_contentType.indexOf(APPLICATION_XML_CONTENT_TYPE) != -1 ||
232                           _contentType.indexOf(TEXT_XML_CONTENT_TYPE) != -1);
233 
234         if (characterEncoding == null)
235         {
236             if (log.isLoggable(Level.FINE))
237             {
238                 log.fine("No character encoding given, using default character encoding " +
239                         DEFAULT_CHARACTER_ENCODING);
240             }
241             _characterEncoding = DEFAULT_CHARACTER_ENCODING;
242         }
243         else
244         {
245             // canonize to uppercase, that's the standard format
246             _characterEncoding = characterEncoding.toUpperCase();
247             
248             // Check if encoding is valid by javadoc of RenderKit.createResponseWriter()
249             if (!Charset.isSupported(_characterEncoding))
250             {
251                 throw new IllegalArgumentException("Encoding "+_characterEncoding
252                         +" not supported by HtmlResponseWriterImpl");
253             }
254         }
255         _isUTF8 = UTF8.equals(_characterEncoding);
256         _startedChangedElements = new ArrayList<String>();
257         _startedElementsCount = new ArrayList<Integer>();
258     }
259 
260     public static boolean supportsContentType(String contentType)
261     {
262         String[] supportedContentTypes = HtmlRendererUtils.getSupportedContentTypes();
263 
264         for (int i = 0; i < supportedContentTypes.length; i++)
265         {
266             String supportedContentType = supportedContentTypes[i];
267 
268             if(supportedContentType.indexOf(contentType)!=-1)
269             {
270                 return true;
271             }
272         }
273         return false;
274     }
275 
276     public String getContentType()
277     {
278         return _contentType;
279     }
280     
281     public String getWriterContentTypeMode()
282     {
283         return _writerContentTypeMode;
284     }
285 
286     public String getCharacterEncoding()
287     {
288         return _characterEncoding;
289     }
290 
291     public void flush() throws IOException
292     {
293         // API doc says we should not flush the underlying writer
294         //_writer.flush();
295         // but rather clear any values buffered by this ResponseWriter:
296         closeStartTagIfNecessary();
297     }
298 
299     public void startDocument()
300     {
301         // do nothing
302     }
303 
304     public void endDocument() throws IOException
305     {
306         MyfacesConfig myfacesConfig = MyfacesConfig.getCurrentInstance(
307             FacesContext.getCurrentInstance().getExternalContext());
308         if (myfacesConfig.isEarlyFlushEnabled())
309         {
310             _currentWriter.flush();
311         }
312         _facesContext = null;
313     }
314 
315     public void startElement(String name, UIComponent uiComponent) throws IOException
316     {
317         if (name == null)
318         {
319             throw new NullPointerException("elementName name must not be null");
320         }
321 
322         closeStartTagIfNecessary();
323         _currentWriter.write('<');
324 
325         resetStartedElement();
326 
327         _startElementName = name;
328         _startElementUIComponent = uiComponent;
329         _startTagOpen = true;
330         _passThroughAttributesMap = (_startElementUIComponent != null) ?
331             _startElementUIComponent.getPassThroughAttributes(false) : null;
332 
333         if (_passThroughAttributesMap != null)
334         {
335             Object value = _passThroughAttributesMap.get(
336                 Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY);
337             if (value != null)
338             {
339                 if (value instanceof ValueExpression)
340                 {
341                     value = ((ValueExpression)value).getValue(
342                         getFacesContext().getELContext());
343                 }
344                 String elementName = value.toString().trim();
345                 
346                 if (!name.equals(elementName))
347                 {
348                     _startElementName = elementName;
349                     _startedChangedElements.add(elementName);
350                     _startedElementsCount.add(0);
351                 }
352                 _currentWriter.write((String) elementName);
353             }
354             else
355             {
356                 _currentWriter.write(name);
357             }
358         }
359         else
360         {
361             _currentWriter.write(name);
362         }
363 
364         if (!_startedElementsCount.isEmpty())
365         {
366             int i = _startedElementsCount.size()-1;
367             _startedElementsCount.set(i, _startedElementsCount.get(i)+1);
368         }
369         
370         // Each time we start a element, it is necessary to check <script> or <style>,
371         // because we need to buffer all content to post process it later when it reach its end
372         // according to the initialization properties used.
373         if(isScript(_startElementName))
374         {
375             // handle a <script> start
376             _isInsideScript = true;
377             _isStyle = false;
378             _isTextArea = Boolean.FALSE;
379         }
380         else if (isStyle(_startElementName))
381         {
382             _isInsideScript = false;
383             _isStyle = true;
384             _isTextArea = Boolean.FALSE;
385         }
386     }
387 
388     @Override
389     public void startCDATA() throws IOException
390     {
391         if (!_cdataOpen)
392         {
393             write(CDATA_START_NO_LINE_RETURN);
394             _cdataOpen = true;
395         }
396     }
397 
398     @Override
399     public void endCDATA() throws IOException
400     {
401         if (_cdataOpen)
402         {
403             write(CDATA_END_NO_LINE_RETURN);
404             _cdataOpen = false;
405         }
406     }
407 
408     private void closeStartTagIfNecessary() throws IOException
409     {
410         if (_startTagOpen)
411         {
412             if (_passThroughAttributesMap != null)
413             {
414                 for (Map.Entry<String, Object> entry : _passThroughAttributesMap.entrySet())
415                 {
416                     String key = entry.getKey();
417                     Object value = entry.getValue();
418                     if (Renderer.PASSTHROUGH_RENDERER_LOCALNAME_KEY.equals(key))
419                     {
420                         // Special attribute stored in passthrough attribute map,
421                         // skip rendering
422                         continue;
423                     }
424                     if (value instanceof ValueExpression)
425                     {
426                         value = ((ValueExpression)value).getValue(getFacesContext().getELContext());
427                     }
428                     // encodeAndWriteURIAttribute(key, value, key);
429                     // JSF 2.2 In the renderkit javadoc of jsf 2.2 spec says this 
430                     // (Rendering Pass Through Attributes):
431                     // "... The ResponseWriter must ensure that any pass through attributes are 
432                     // rendered on the outer-most markup element for the component. If there is 
433                     // a pass through attribute with the same name as a renderer specific 
434                     // attribute, the pass through attribute takes precedence. Pass through 
435                     // attributes are rendered as if they were passed to 
436                     // ResponseWriter.writeURIAttribute(). ..."
437                     // Note here it says "as if they were passed", instead say "... attributes are
438                     // encoded and rendered as if ...". Black box testing against RI shows that there
439                     // is no URI encoding at all in this part, so in this case the best is do the
440                     // same here. After all, it is resposibility of the one who set the passthrough
441                     // attribute to do the proper encoding in cases when a URI is provided. However,
442                     // that does not means the attribute should not be encoded as other attributes.
443                     encodeAndWriteAttribute(key, value);
444                 }
445             }
446 
447             if (!_useStraightXml && isEmptyElement(_startElementName))
448             {
449                 _currentWriter.write(" />");
450                 // make null, this will cause NullPointer in some invalid element nestings
451                 // (better than doing nothing)
452                 resetStartedElement();
453             }
454             else
455             {
456                 _currentWriter.write('>');
457                 /*
458                 if(isScript(_startElementName))
459                 {
460                     if(HtmlRendererUtils.isXHTMLContentType(_contentType))
461                     {
462                         if(HtmlRendererUtils.isAllowedCdataSection(FacesContext.getCurrentInstance()))
463                         {
464                             _currentWriter.write(CDATA_START);
465                         }
466                     }
467                     else
468                     {
469                         _currentWriter.write(COMMENT_START);
470                     }
471                 }*/
472                 if (isScript(_startElementName) && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
473                 {
474                     //_bufferedWriter.reset();
475                     //_currentWriter = _bufferedWriter;
476                     getInternalBuffer(true);
477                     _currentWriter = getInternalBuffer().getWriter();
478                 }                
479                 if (isStyle(_startElementName) && _isXhtmlContentType)
480                 {
481                     //_bufferedWriter.reset();
482                     //_currentWriter = _bufferedWriter;
483                     getInternalBuffer(true);
484                     _currentWriter = getInternalBuffer().getWriter();
485                 }
486             }
487             _startTagOpen = false;
488         }
489     }
490     
491     private boolean isEmptyElement(String elem)
492     {
493         // Code taken from trinidad
494         // =-=AEW Performance?  Certainly slower to use a hashtable,
495         // at least if we can't assume the input name is lowercased.
496         // -= Leonardo Uribe =- elem.toLowerCase() internally creates an array,
497         // and the contains() force a call to hashCode(). The array uses simple
498         // char comparison, which at the end is faster and use less memory.
499         // Note this call is very frequent, so at the end it is worth to do it.
500         String[] array = EMPTY_ELEMENT_ARR[elem.charAt(0)];
501         if (array != null)
502         {
503             for (int i = array.length - 1; i >= 0; i--)
504             {
505                 if (elem.equalsIgnoreCase(array[i]))
506                 {
507                     return true;
508                 }
509             }
510         }
511         return false;
512     }
513 
514     private void resetStartedElement()
515     {
516         _startElementName = null;
517         _startElementUIComponent = null;
518         _passThroughAttributesMap = null;
519         _isStyle = false;
520         _isTextArea = null;
521     }
522 
523     public void endElement(String name) throws IOException
524     {
525         if (name == null)
526         {
527             throw new NullPointerException("elementName name must not be null");
528         }
529 
530         String elementName = name;
531 
532         if (!_startedElementsCount.isEmpty())
533         {
534             int i = _startedElementsCount.size()-1;
535             _startedElementsCount.set(i, _startedElementsCount.get(i)-1);
536             if (_startedElementsCount.get(i) == 0)
537             {
538                 elementName = _startedChangedElements.get(i);
539                 _startedChangedElements.remove(i);
540                 _startedElementsCount.remove(i);
541             }
542         }
543 
544         if (log.isLoggable(Level.WARNING))
545         {
546             if (_startElementName != null &&
547                 !elementName.equals(_startElementName))
548             {
549                 log.warning("HTML nesting warning on closing " + elementName + ": element " + _startElementName +
550                         (_startElementUIComponent==null?"":(" rendered by component : "+
551                         RendererUtils.getPathToComponent(_startElementUIComponent)))+" not explicitly closed");
552             }
553         }
554 
555         if(_startTagOpen)
556         {
557 
558             // we will get here only if no text or attribute was written after the start element was opened
559             // now we close out the started tag - if it is an empty tag, this is then fully closed
560             closeStartTagIfNecessary();
561 
562             //tag was no empty tag - it has no accompanying end tag now.
563             if(_startElementName!=null)
564             {                
565                 if (isScript() && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
566                 {
567                     writeScriptContent();
568                     _currentWriter = _outputWriter;
569                 }
570                 else if (isStyle() && _isXhtmlContentType)
571                 {
572                     writeStyleContent();
573                     _currentWriter = _outputWriter;
574                 }
575 
576                 //write closing tag
577                 writeEndTag(elementName);
578             }
579         }
580         else
581         {
582             if (!_useStraightXml && isEmptyElement(elementName))
583             {
584            /*
585            Should this be here?  It warns even when you have an x:htmlTag value="br", it should just close.
586 
587                 if (log.isWarnEnabled())
588                     log.warn("HTML nesting warning on closing " + name + 
589                         ": This element must not contain nested elements or text in HTML");
590                     */
591             }
592             else
593             {
594                 if (isScript() && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
595                 {
596                     writeScriptContent();
597                     _currentWriter = _outputWriter;
598                 }
599                 else if (isStyle() && _isXhtmlContentType)
600                 {
601                     writeStyleContent();
602                     _currentWriter = _outputWriter;
603                 }
604                 writeEndTag(elementName);
605             }
606         }
607 
608         resetStartedElement();
609     }
610 
611 
612     
613     private void writeStyleContent() throws IOException
614     {
615         String content = getInternalBuffer().toString();
616         
617         if(_isXhtmlContentType)
618         {
619             // In xhtml, the content inside <style> tag is PCDATA, but
620             // in html the content is CDATA, so in order to preserve 
621             // compatibility we need to wrap the content inside proper
622             // CDATA tags.
623             // Since the response content type is xhtml, we can use
624             // simple CDATA without comments, but note we need to check
625             // when we are using any valid notation (simple CDATA, commented CDATA, xml comment) 
626             String trimmedContent = content.trim();
627             if (trimmedContent.startsWith(CommentUtils.CDATA_SIMPLE_START) && trimmedContent.endsWith(
628                     CommentUtils.CDATA_SIMPLE_END))
629             {
630                 _outputWriter.write(content);
631                 return;
632             }
633             else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) && 
634                     CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
635             {
636                 _outputWriter.write(content);
637                 return;
638             }
639             else if (trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) && 
640                     trimmedContent.endsWith(CommentUtils.COMMENT_SIMPLE_END))
641             {
642                 //Use comment wrap is valid, but for xhtml it is preferred to use CDATA
643                 _outputWriter.write(CDATA_START);
644                 _outputWriter.write(trimmedContent.substring(4,trimmedContent.length()-3));
645                 _outputWriter.write(CDATA_END);
646                 return;
647             }
648             else
649             {
650                 _outputWriter.write(CDATA_START);
651                 _outputWriter.write(content);
652                 _outputWriter.write(CDATA_END);
653                 return;                
654             }
655         }
656         // If the response is handled as text/html, 
657         // it is not necessary to wrap with xml comment tag,
658         // so we can just write the content as is.
659         _outputWriter.write(content);
660     }
661     
662     private void writeScriptContent() throws IOException
663     {
664         String content = getInternalBuffer().toString();
665         String trimmedContent = null;
666         
667         if(_isXhtmlContentType)
668         {
669             trimmedContent = content.trim();
670             
671             if ( trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) && 
672                     CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
673             {
674                 // In xhtml use xml comment to wrap is invalid, so it is only required to remove the <!--
675                 // the ending //--> will be parsed as a comment, so it will not be a problem. Let it on the content.
676                 if (_cdataOpen)
677                 {
678                     _outputWriter.write("//\n");
679                 }
680                 else
681                 {
682                    _outputWriter.write(CDATA_COMMENT_START);
683                 }
684 
685                 _outputWriter.write(trimmedContent.substring(4));
686 
687                 if (_cdataOpen)
688                 {
689                     _outputWriter.write("\n");
690                 }
691                 else
692                 {
693                     _outputWriter.write(CDATA_COMMENT_END);
694                 }
695                 
696                 return;
697             }
698             else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) && 
699                     CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
700             {
701                 _outputWriter.write(content);
702                 return;
703             }
704             else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) && 
705                     CommentUtils.isEndMatchWithInlineCommentedCDATA(trimmedContent))
706             {
707                 _outputWriter.write(content);
708                 return;
709             }
710             else
711             {
712                 // <script> in xhtml has as content type PCDATA, but in html it is CDATA,
713                 // so we need to wrap here to prevent problems.
714                 if (_cdataOpen)
715                 {
716                     _outputWriter.write("//\n");
717                 }
718                 else
719                 {
720                    _outputWriter.write(CDATA_COMMENT_START);
721                 }
722 
723                 _outputWriter.write(content);
724 
725                 if (_cdataOpen)
726                 {
727                     _outputWriter.write("\n");
728                 }
729                 else
730                 {
731                     _outputWriter.write(CDATA_COMMENT_END);
732                 }
733 
734                 return;
735             }
736         }
737         else
738         {
739             if (_wrapScriptContentWithXmlCommentTag)
740             {
741                 trimmedContent = content.trim();
742                 
743                 if ( trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) && 
744                         CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
745                 {
746                     _outputWriter.write(content);
747                     return;
748                 }
749                 else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) && 
750                         CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
751                 {
752                     _outputWriter.write(content);
753                     return;
754                 }
755                 else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) && 
756                         CommentUtils.isEndMatchWithInlineCommentedCDATA(trimmedContent))
757                 {
758                     _outputWriter.write(content);
759                     return;
760                 }
761                 else
762                 {
763                     _outputWriter.write(COMMENT_START);
764                     _outputWriter.write(content);
765                     _outputWriter.write(COMMENT_COMMENT_END);
766                     return;
767                 }
768             }
769         }
770         
771         //If no return, just write everything
772         _outputWriter.write(content);
773     }
774     
775 
776     private void writeEndTag(String name)
777         throws IOException
778     {
779         /*
780         if(isScript(name)) 
781         {
782             if(HtmlRendererUtils.isXHTMLContentType(_contentType))
783             {
784                 if(HtmlRendererUtils.isAllowedCdataSection(FacesContext.getCurrentInstance()))
785                 {
786                     _currentWriter.write(CDATA_COMMENT_END);
787                 }
788             }
789             else
790             {
791                 _currentWriter.write(COMMENT_COMMENT_END);
792             }
793             
794             // reset _isInsideScript
795             _isInsideScript = Boolean.FALSE;
796         }*/
797         
798         if (isScript(name))
799         {
800             // reset _isInsideScript
801             _isInsideScript = false;
802         }
803         else if (isStyle(name))
804         {
805             _isStyle = false;
806         }
807 
808         _currentWriter.write("</");
809         _currentWriter.write(name);
810         _currentWriter.write('>');
811     }
812 
813     public void writeAttribute(String name, Object value, String componentPropertyName) throws IOException
814     {
815         if (name == null)
816         {
817             throw new NullPointerException("attributeName name must not be null");
818         }
819         if (!_startTagOpen)
820         {
821             throw new IllegalStateException("Must be called before the start element is closed (attribute '"
822                     + name + "')");
823         }
824         // From JSF 2.2 RenderKit javadoc: "... If there is a pass through attribute with the same 
825         // name as a renderer specific attribute, the pass through attribute takes precedence. ..."
826         if (_passThroughAttributesMap != null && _passThroughAttributesMap.containsKey(name))
827         {
828             return;
829         }
830 
831         if (value instanceof Boolean)
832         {
833             if (((Boolean)value).booleanValue())
834             {
835                 // name as value for XHTML compatibility
836                 _currentWriter.write(' ');
837                 _currentWriter.write(name);
838                 _currentWriter.write("=\"");
839                 _currentWriter.write(name);
840                 _currentWriter.write('"');
841             }
842         }
843         else
844         {
845             String strValue = (value==null)?"":value.toString();
846             _currentWriter.write(' ');
847             _currentWriter.write(name);
848             _currentWriter.write("=\"");
849             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
850                     strValue, false, false, !_isUTF8);
851             _currentWriter.write('"');
852         }
853     }
854     
855     private void encodeAndWriteAttribute(String name, Object value) throws IOException
856     {
857         String strValue = (value==null)?"":value.toString();
858         _currentWriter.write(' ');
859         _currentWriter.write(name);
860         _currentWriter.write("=\"");
861         org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
862                 strValue, false, false, !_isUTF8);
863         _currentWriter.write('"');
864     }
865 
866     public void writeURIAttribute(String name, Object value, String componentPropertyName) throws IOException
867     {
868         if (name == null)
869         {
870             throw new NullPointerException("attributeName name must not be null");
871         }
872         if (!_startTagOpen)
873         {
874             throw new IllegalStateException("Must be called before the start element is closed (attribute '"
875                     + name + "')");
876         }
877         // From JSF 2.2 RenderKit javadoc: "... If there is a pass through attribute with the same 
878         // name as a renderer specific attribute, the pass through attribute takes precedence. ..."
879         if (_passThroughAttributesMap != null && _passThroughAttributesMap.containsKey(name))
880         {
881             return;
882         }
883         
884         encodeAndWriteURIAttribute(name, value);
885     }
886     
887     private void encodeAndWriteURIAttribute(String name, Object value) throws IOException
888     {
889         String strValue = value.toString();
890         _currentWriter.write(' ');
891         _currentWriter.write(name);
892         _currentWriter.write("=\"");
893         if (strValue.toLowerCase().startsWith("javascript:"))
894         {
895             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
896                     strValue, false, false, !_isUTF8);
897         }
898         else
899         {
900             /*
901             Todo: what is this section about? still needed?
902             client side state saving is now done via javascript...
903 
904             if (_startElementName.equalsIgnoreCase(HTML.ANCHOR_ELEM) && //Also support image and button urls?
905                 name.equalsIgnoreCase(HTML.HREF_ATTR) &&
906                 !strValue.startsWith("#"))
907             {
908                 FacesContext facesContext = FacesContext.getCurrentInstance();
909                 if (facesContext.getApplication().getStateManager().isSavingStateInClient(facesContext))
910                 {
911                     // saving state in url depends on the work together
912                     // of 3 (theoretically) pluggable components:
913                     // ViewHandler, ResponseWriter and ViewTag
914                     // We should try to make this HtmlResponseWriterImpl able
915                     // to handle this alone!
916                     if (strValue.indexOf('?') < 0)
917                     {
918                         strValue = strValue + '?' + JspViewHandlerImpl.URL_STATE_MARKER;
919                     }
920                     else
921                     {
922                         strValue = strValue + '&' + JspViewHandlerImpl.URL_STATE_MARKER;
923                     }
924                 }
925             }
926             */
927             //_writer.write(strValue);
928             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encodeURIAttribute(_currentWriter,
929                             strValue, _characterEncoding);
930         }
931         _currentWriter.write('"');
932     }
933 
934     public void writeComment(Object value) throws IOException
935     {
936         if (value == null)
937         {
938             throw new NullPointerException("comment name must not be null");
939         }
940 
941         closeStartTagIfNecessary();
942         _currentWriter.write("<!--");
943         _currentWriter.write(value.toString());    //TODO: Escaping: must not have "-->" inside!
944         _currentWriter.write("-->");
945     }
946 
947     public void writeText(Object value, String componentPropertyName) throws IOException
948     {
949         if (value == null)
950         {
951             throw new NullPointerException("Text must not be null.");
952         }
953 
954         closeStartTagIfNecessary();
955 
956         String strValue = value.toString();
957 
958         if (isScriptOrStyle())
959         {
960             // Don't bother encoding anything if chosen character encoding is UTF-8
961             if (_isUTF8)
962             {
963                 _currentWriter.write(strValue);
964             }
965             else
966             {
967                 UnicodeEncoder.encode(_currentWriter, strValue);
968             }
969         }
970         else
971         {
972             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
973                       strValue, false, false, !_isUTF8);
974         }
975     }
976 
977     public void writeText(char cbuf[], int off, int len) throws IOException
978     {
979         if (cbuf == null)
980         {
981             throw new NullPointerException("cbuf name must not be null");
982         }
983         if (cbuf.length < off + len)
984         {
985             throw new IndexOutOfBoundsException((off + len) + " > " + cbuf.length);
986         }
987 
988         closeStartTagIfNecessary();
989 
990         if (isScriptOrStyle())
991         {
992             String strValue = new String(cbuf, off, len);
993             // Don't bother encoding anything if chosen character encoding is UTF-8
994             if (_isUTF8)
995             {
996                 _currentWriter.write(strValue);
997             }
998             else
999             {
1000                 UnicodeEncoder.encode(_currentWriter, strValue);
1001             }
1002         }
1003         else if (isTextarea())
1004         {
1005             // For textareas we must *not* map successive spaces to &nbsp or Newlines to <br/>
1006             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(
1007                     cbuf, off, len, false, false, !_isUTF8, _currentWriter);
1008         }
1009         else
1010         {
1011             // We map successive spaces to &nbsp; and Newlines to <br/>
1012             org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(
1013                     cbuf, off, len, true, true, !_isUTF8, _currentWriter);
1014         }
1015     }
1016 
1017     private boolean isScriptOrStyle()
1018     {
1019         //initializeStartedTagInfo();
1020 
1021         return _isStyle || _isInsideScript;
1022     }
1023     
1024     /**
1025      * Is the given element a script tag?
1026      * @param element
1027      * @return
1028      */
1029     private boolean isScript(String element)
1030     {
1031         return (HTML.SCRIPT_ELEM.equalsIgnoreCase(element));
1032     }
1033     
1034     private boolean isScript()
1035     {
1036         return _isInsideScript;
1037     }
1038     
1039     private boolean isStyle(String element)
1040     {
1041         return (HTML.STYLE_ELEM.equalsIgnoreCase(element));
1042     }
1043     
1044     private boolean isStyle()
1045     {
1046         return _isStyle;
1047     }
1048 
1049     private boolean isTextarea()
1050     {
1051         initializeStartedTagInfo();
1052 
1053         return _isTextArea != null && _isTextArea.booleanValue();
1054     }
1055 
1056     private void initializeStartedTagInfo()
1057     {
1058         if(_startElementName != null)
1059         {
1060             /*
1061             if(_isStyle == null)
1062             {
1063                 if(_startElementName.equalsIgnoreCase(org.apache.myfaces.shared.renderkit.html.HTML.STYLE_ELEM))
1064                 {
1065                     _isStyle = Boolean.TRUE;
1066                     _isTextArea = Boolean.FALSE;
1067                 }
1068                 else
1069                 {
1070                     _isStyle = Boolean.FALSE;
1071                 }
1072             }*/
1073 
1074             if(_isTextArea == null)
1075             {
1076                 if(_startElementName.equalsIgnoreCase(HTML.TEXTAREA_ELEM))
1077                 {
1078                     _isTextArea = Boolean.TRUE;
1079                 }
1080                 else
1081                 {
1082                     _isTextArea = Boolean.FALSE;
1083                 }
1084             }
1085         }
1086     }
1087 
1088     public ResponseWriter cloneWithWriter(Writer writer)
1089     {
1090         HtmlResponseWriterImpl newWriter
1091                 = new HtmlResponseWriterImpl(writer, getContentType(), getCharacterEncoding(), 
1092                         _wrapScriptContentWithXmlCommentTag, _writerContentTypeMode);
1093         //newWriter._writeDummyForm = _writeDummyForm;
1094         //newWriter._dummyFormParams = _dummyFormParams;
1095         return newWriter;
1096     }
1097 
1098 
1099     // Writer methods
1100 
1101     public void close() throws IOException
1102     {
1103         closeStartTagIfNecessary();
1104         _currentWriter.close();
1105         _facesContext = null;
1106     }
1107 
1108     public void write(char cbuf[], int off, int len) throws IOException
1109     {
1110         closeStartTagIfNecessary();
1111         // Don't bother encoding anything if chosen character encoding is UTF-8
1112         if (_isUTF8)
1113         {
1114             _currentWriter.write(cbuf, off, len);
1115         }
1116         else
1117         {
1118             UnicodeEncoder.encode(_currentWriter, cbuf, off, len);
1119         }
1120     }
1121 
1122     public void write(int c) throws IOException
1123     {
1124         closeStartTagIfNecessary();
1125         _currentWriter.write(c);
1126     }
1127 
1128     public void write(char cbuf[]) throws IOException
1129     {
1130         closeStartTagIfNecessary();
1131         // Don't bother encoding anything if chosen character encoding is UTF-8
1132         if (_isUTF8)
1133         {
1134             _currentWriter.write(cbuf);
1135         }
1136         else
1137         {
1138             UnicodeEncoder.encode(_currentWriter, cbuf, 0, cbuf.length);
1139         }
1140     }
1141 
1142     public void write(String str) throws IOException
1143     {
1144         closeStartTagIfNecessary();
1145         // empty string commonly used to force the start tag to be closed.
1146         // in such case, do not call down the writer chain
1147         if (str != null && str.length() > 0)
1148         {
1149             // Don't bother encoding anything if chosen character encoding is UTF-8
1150             if (_isUTF8)
1151             {
1152                 _currentWriter.write(str);
1153             }
1154             else
1155             {
1156                 UnicodeEncoder.encode(_currentWriter, str);
1157             }
1158         }
1159     }
1160 
1161     public void write(String str, int off, int len) throws IOException
1162     {
1163         closeStartTagIfNecessary();
1164         // Don't bother encoding anything if chosen character encoding is UTF-8
1165         if (_isUTF8)
1166         {
1167             _currentWriter.write(str, off, len);
1168         }
1169         else
1170         {
1171             UnicodeEncoder.encode(_currentWriter, str, off, len);
1172         }
1173     }
1174     
1175     /**
1176      * This method ignores the <code>UIComponent</code> provided and simply calls
1177      * <code>writeText(Object,String)</code>
1178      * @since 1.2
1179      */
1180     public void writeText(Object object, UIComponent component, String string) throws IOException
1181     {
1182         writeText(object,string);
1183     }
1184     
1185     protected StreamCharBuffer getInternalBuffer()
1186     {
1187         return getInternalBuffer(false);
1188     }
1189     
1190     protected StreamCharBuffer getInternalBuffer(boolean reset)
1191     {
1192         if (_buffer == null)
1193         {
1194             _buffer = new StreamCharBuffer(256, 100);
1195         }
1196         else if (reset)
1197         {
1198             _buffer.reset();
1199         }
1200         return _buffer;
1201     }
1202     
1203     protected FacesContext getFacesContext()
1204     {
1205         if (_facesContext == null)
1206         {
1207             _facesContext = FacesContext.getCurrentInstance();
1208         }
1209         return _facesContext;
1210     }
1211     
1212     protected boolean getWrapScriptContentWithXmlCommentTag()
1213     {
1214         return _wrapScriptContentWithXmlCommentTag;
1215     }
1216     
1217     protected void forceFlush() throws IOException
1218     {
1219         _currentWriter.flush();
1220     }
1221 }