1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.HashSet;
25 import java.util.Set;
26 import java.util.logging.Level;
27 import java.util.logging.Logger;
28
29 import javax.faces.FacesException;
30 import javax.faces.component.UIComponent;
31 import javax.faces.context.ResponseWriter;
32
33 import org.apache.myfaces.shared.renderkit.ContentTypeUtils;
34 import org.apache.myfaces.shared.renderkit.RendererUtils;
35 import org.apache.myfaces.shared.renderkit.html.util.UnicodeEncoder;
36 import org.apache.myfaces.shared.util.CommentUtils;
37 import org.apache.myfaces.shared.util.StreamCharBuffer;
38
39
40
41
42
43
44 public class HtmlResponseWriterImpl
45 extends ResponseWriter
46 {
47
48 private static final Logger log = Logger.getLogger(HtmlResponseWriterImpl.class.getName());
49
50 private static final String DEFAULT_CONTENT_TYPE = "text/html";
51 private static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
52 private static final String UTF8 = "UTF-8";
53
54 private static final String APPLICATION_XML_CONTENT_TYPE = "application/xml";
55 private static final String TEXT_XML_CONTENT_TYPE = "text/xml";
56
57
58
59
60
61
62
63 private Writer _outputWriter;
64
65
66
67
68 private Writer _currentWriter;
69
70
71
72
73 private StreamCharBuffer _buffer;
74
75 private String _contentType;
76
77 private String _writerContentTypeMode;
78
79
80
81
82 private boolean _isXhtmlContentType;
83
84
85
86
87
88 private boolean _useStraightXml;
89
90 private String _characterEncoding;
91 private boolean _wrapScriptContentWithXmlCommentTag;
92 private boolean _isUTF8;
93
94 private String _startElementName;
95 private Boolean _isInsideScript;
96 private Boolean _isStyle;
97 private Boolean _isTextArea;
98 private UIComponent _startElementUIComponent;
99 private boolean _startTagOpen;
100
101 private boolean _cdataOpen;
102
103 private static final Set<String> S_EMPTY_HTML_ELEMENTS = new HashSet<String>();
104
105 private static final String CDATA_START = "<![CDATA[ \n";
106 private static final String CDATA_START_NO_LINE_RETURN = "<![CDATA[";
107 private static final String COMMENT_START = "<!--\n";
108 private static final String CDATA_COMMENT_START = "//<![CDATA[ \n";
109 private static final String CDATA_COMMENT_END = "\n//]]>";
110 private static final String CDATA_END = "\n]]>";
111 private static final String CDATA_END_NO_LINE_RETURN = "]]>";
112 private static final String COMMENT_COMMENT_END = "\n//-->";
113 private static final String COMMENT_END = "\n-->";
114
115 static
116 {
117 S_EMPTY_HTML_ELEMENTS.add("area");
118 S_EMPTY_HTML_ELEMENTS.add("br");
119 S_EMPTY_HTML_ELEMENTS.add("base");
120 S_EMPTY_HTML_ELEMENTS.add("basefont");
121 S_EMPTY_HTML_ELEMENTS.add("col");
122 S_EMPTY_HTML_ELEMENTS.add("frame");
123 S_EMPTY_HTML_ELEMENTS.add("hr");
124 S_EMPTY_HTML_ELEMENTS.add("img");
125 S_EMPTY_HTML_ELEMENTS.add("input");
126 S_EMPTY_HTML_ELEMENTS.add("isindex");
127 S_EMPTY_HTML_ELEMENTS.add("link");
128 S_EMPTY_HTML_ELEMENTS.add("meta");
129 S_EMPTY_HTML_ELEMENTS.add("param");
130 }
131
132 public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding)
133 {
134 this(writer,contentType,characterEncoding,true);
135 }
136
137 public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding,
138 boolean wrapScriptContentWithXmlCommentTag)
139 {
140 this(writer,contentType, characterEncoding, wrapScriptContentWithXmlCommentTag,
141 contentType != null && HtmlRendererUtils.isXHTMLContentType(contentType) ?
142 ContentTypeUtils.XHTML_CONTENT_TYPE : ContentTypeUtils.HTML_CONTENT_TYPE);
143 }
144
145 public HtmlResponseWriterImpl(Writer writer, String contentType, String characterEncoding,
146 boolean wrapScriptContentWithXmlCommentTag, String writerContentTypeMode)
147 throws FacesException
148 {
149 _outputWriter = writer;
150
151 _currentWriter = _outputWriter;
152 _wrapScriptContentWithXmlCommentTag = wrapScriptContentWithXmlCommentTag;
153
154 _contentType = contentType;
155 if (_contentType == null)
156 {
157 if (log.isLoggable(Level.FINE))
158 {
159 log.fine("No content type given, using default content type " + DEFAULT_CONTENT_TYPE);
160 }
161 _contentType = DEFAULT_CONTENT_TYPE;
162 }
163 _writerContentTypeMode = writerContentTypeMode;
164 _isXhtmlContentType = writerContentTypeMode.indexOf(ContentTypeUtils.XHTML_CONTENT_TYPE) != -1;
165
166 _useStraightXml = _isXhtmlContentType && (_contentType.indexOf(APPLICATION_XML_CONTENT_TYPE) != -1 ||
167 _contentType.indexOf(TEXT_XML_CONTENT_TYPE) != -1);
168
169 if (characterEncoding == null)
170 {
171 if (log.isLoggable(Level.FINE))
172 {
173 log.fine("No character encoding given, using default character encoding " +
174 DEFAULT_CHARACTER_ENCODING);
175 }
176 _characterEncoding = DEFAULT_CHARACTER_ENCODING;
177 }
178 else
179 {
180
181 _characterEncoding = characterEncoding.toUpperCase();
182
183
184 if (!Charset.isSupported(_characterEncoding))
185 {
186 throw new IllegalArgumentException("Encoding "+_characterEncoding
187 +" not supported by HtmlResponseWriterImpl");
188 }
189 }
190 _isUTF8 = UTF8.equals(_characterEncoding);
191 }
192
193 public static boolean supportsContentType(String contentType)
194 {
195 String[] supportedContentTypes = HtmlRendererUtils.getSupportedContentTypes();
196
197 for (int i = 0; i < supportedContentTypes.length; i++)
198 {
199 String supportedContentType = supportedContentTypes[i];
200
201 if(supportedContentType.indexOf(contentType)!=-1)
202 {
203 return true;
204 }
205 }
206 return false;
207 }
208
209 public String getContentType()
210 {
211 return _contentType;
212 }
213
214 public String getWriterContentTypeMode()
215 {
216 return _writerContentTypeMode;
217 }
218
219 public String getCharacterEncoding()
220 {
221 return _characterEncoding;
222 }
223
224 public void flush() throws IOException
225 {
226
227
228
229 closeStartTagIfNecessary();
230 }
231
232 public void startDocument()
233 {
234
235 }
236
237 public void endDocument() throws IOException
238 {
239 _currentWriter.flush();
240 }
241
242 public void startElement(String name, UIComponent uiComponent) throws IOException
243 {
244 if (name == null)
245 {
246 throw new NullPointerException("elementName name must not be null");
247 }
248
249 closeStartTagIfNecessary();
250 _currentWriter.write('<');
251 _currentWriter.write(name);
252
253 resetStartedElement();
254
255 _startElementName = name;
256 _startElementUIComponent = uiComponent;
257 _startTagOpen = true;
258
259
260
261
262 if(isScript(name))
263 {
264
265 _isInsideScript = Boolean.TRUE;
266 _isStyle = Boolean.FALSE;
267 _isTextArea = Boolean.FALSE;
268 }
269 else if (isStyle(name))
270 {
271 _isInsideScript = Boolean.FALSE;
272 _isStyle = Boolean.TRUE;
273 _isTextArea = Boolean.FALSE;
274 }
275 }
276
277 @Override
278 public void startCDATA() throws IOException
279 {
280 if (!_cdataOpen)
281 {
282 write(CDATA_START_NO_LINE_RETURN);
283 _cdataOpen = true;
284 }
285 }
286
287 @Override
288 public void endCDATA() throws IOException
289 {
290 if (_cdataOpen)
291 {
292 write(CDATA_END_NO_LINE_RETURN);
293 _cdataOpen = false;
294 }
295 }
296
297 private void closeStartTagIfNecessary() throws IOException
298 {
299 if (_startTagOpen)
300 {
301 if (!_useStraightXml && S_EMPTY_HTML_ELEMENTS.contains(_startElementName.toLowerCase()))
302 {
303 _currentWriter.write(" />");
304
305
306 resetStartedElement();
307 }
308 else
309 {
310 _currentWriter.write('>');
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327 if (isScript(_startElementName) && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
328 {
329
330
331 getInternalBuffer(true);
332 _currentWriter = getInternalBuffer().getWriter();
333 }
334 if (isStyle(_startElementName) && _isXhtmlContentType)
335 {
336
337
338 getInternalBuffer(true);
339 _currentWriter = getInternalBuffer().getWriter();
340 }
341 }
342 _startTagOpen = false;
343 }
344 }
345
346 private void resetStartedElement()
347 {
348 _startElementName = null;
349 _startElementUIComponent = null;
350 _isStyle = null;
351 _isTextArea = null;
352 }
353
354 public void endElement(String name) throws IOException
355 {
356 if (name == null)
357 {
358 throw new NullPointerException("elementName name must not be null");
359 }
360
361 if (log.isLoggable(Level.WARNING))
362 {
363 if (_startElementName != null &&
364 !name.equals(_startElementName))
365 {
366 log.warning("HTML nesting warning on closing " + name + ": element " + _startElementName +
367 (_startElementUIComponent==null?"":(" rendered by component : "+
368 RendererUtils.getPathToComponent(_startElementUIComponent)))+" not explicitly closed");
369 }
370 }
371
372 if(_startTagOpen)
373 {
374
375
376
377 closeStartTagIfNecessary();
378
379
380 if(_startElementName!=null)
381 {
382 if (isScript() && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
383 {
384 writeScriptContent();
385 _currentWriter = _outputWriter;
386 }
387 else if (isStyle() && _isXhtmlContentType)
388 {
389 writeStyleContent();
390 _currentWriter = _outputWriter;
391 }
392
393
394 writeEndTag(name);
395 }
396 }
397 else
398 {
399 if (!_useStraightXml && S_EMPTY_HTML_ELEMENTS.contains(name.toLowerCase()))
400 {
401
402
403
404
405
406
407
408 }
409 else
410 {
411 if (isScript() && (_isXhtmlContentType || _wrapScriptContentWithXmlCommentTag))
412 {
413 writeScriptContent();
414 _currentWriter = _outputWriter;
415 }
416 else if (isStyle() && _isXhtmlContentType)
417 {
418 writeStyleContent();
419 _currentWriter = _outputWriter;
420 }
421 writeEndTag(name);
422 }
423 }
424
425 resetStartedElement();
426 }
427
428
429
430 private void writeStyleContent() throws IOException
431 {
432 String content = getInternalBuffer().toString();
433
434 if(_isXhtmlContentType)
435 {
436
437
438
439
440
441
442
443 String trimmedContent = content.trim();
444 if (trimmedContent.startsWith(CommentUtils.CDATA_SIMPLE_START) && trimmedContent.endsWith(
445 CommentUtils.CDATA_SIMPLE_END))
446 {
447 _outputWriter.write(content);
448 return;
449 }
450 else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) &&
451 CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
452 {
453 _outputWriter.write(content);
454 return;
455 }
456 else if (trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) &&
457 trimmedContent.endsWith(CommentUtils.COMMENT_SIMPLE_END))
458 {
459
460 _outputWriter.write(CDATA_START);
461 _outputWriter.write(trimmedContent.substring(4,trimmedContent.length()-3));
462 _outputWriter.write(CDATA_END);
463 return;
464 }
465 else
466 {
467 _outputWriter.write(CDATA_START);
468 _outputWriter.write(content);
469 _outputWriter.write(CDATA_END);
470 return;
471 }
472 }
473
474
475
476 _outputWriter.write(content);
477 }
478
479 private void writeScriptContent() throws IOException
480 {
481 String content = getInternalBuffer().toString();
482 String trimmedContent = null;
483
484 if(_isXhtmlContentType)
485 {
486 trimmedContent = content.trim();
487
488 if ( trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) &&
489 CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
490 {
491
492
493 if (_cdataOpen)
494 {
495 _outputWriter.write("//\n");
496 }
497 else
498 {
499 _outputWriter.write(CDATA_COMMENT_START);
500 }
501
502 _outputWriter.write(trimmedContent.substring(4));
503
504 if (_cdataOpen)
505 {
506 _outputWriter.write("\n");
507 }
508 else
509 {
510 _outputWriter.write(CDATA_COMMENT_END);
511 }
512
513 return;
514 }
515 else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) &&
516 CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
517 {
518 _outputWriter.write(content);
519 return;
520 }
521 else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) &&
522 CommentUtils.isEndMatchWithInlineCommentedCDATA(trimmedContent))
523 {
524 _outputWriter.write(content);
525 return;
526 }
527 else
528 {
529
530
531 if (_cdataOpen)
532 {
533 _outputWriter.write("//\n");
534 }
535 else
536 {
537 _outputWriter.write(CDATA_COMMENT_START);
538 }
539
540 _outputWriter.write(content);
541
542 if (_cdataOpen)
543 {
544 _outputWriter.write("\n");
545 }
546 else
547 {
548 _outputWriter.write(CDATA_COMMENT_END);
549 }
550
551 return;
552 }
553 }
554 else
555 {
556 if (_wrapScriptContentWithXmlCommentTag)
557 {
558 trimmedContent = content.trim();
559
560 if ( trimmedContent.startsWith(CommentUtils.COMMENT_SIMPLE_START) &&
561 CommentUtils.isEndMatchtWithInlineCommentedXmlCommentTag(trimmedContent))
562 {
563 _outputWriter.write(content);
564 return;
565 }
566 else if (CommentUtils.isStartMatchWithCommentedCDATA(trimmedContent) &&
567 CommentUtils.isEndMatchWithCommentedCDATA(trimmedContent))
568 {
569 _outputWriter.write(content);
570 return;
571 }
572 else if (CommentUtils.isStartMatchWithInlineCommentedCDATA(trimmedContent) &&
573 CommentUtils.isEndMatchWithInlineCommentedCDATA(trimmedContent))
574 {
575 _outputWriter.write(content);
576 return;
577 }
578 else
579 {
580 _outputWriter.write(COMMENT_START);
581 _outputWriter.write(content);
582 _outputWriter.write(COMMENT_COMMENT_END);
583 return;
584 }
585 }
586 }
587
588
589 _outputWriter.write(content);
590 }
591
592
593 private void writeEndTag(String name)
594 throws IOException
595 {
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615 if (isScript(name))
616 {
617
618 _isInsideScript = Boolean.FALSE;
619 }
620 else if (isStyle(name))
621 {
622 _isStyle = Boolean.FALSE;
623 }
624
625 _currentWriter.write("</");
626 _currentWriter.write(name);
627 _currentWriter.write('>');
628 }
629
630 public void writeAttribute(String name, Object value, String componentPropertyName) throws IOException
631 {
632 if (name == null)
633 {
634 throw new NullPointerException("attributeName name must not be null");
635 }
636 if (!_startTagOpen)
637 {
638 throw new IllegalStateException("Must be called before the start element is closed (attribute '"
639 + name + "')");
640 }
641
642 if (value instanceof Boolean)
643 {
644 if (((Boolean)value).booleanValue())
645 {
646
647 _currentWriter.write(' ');
648 _currentWriter.write(name);
649 _currentWriter.write("=\"");
650 _currentWriter.write(name);
651 _currentWriter.write('"');
652 }
653 }
654 else
655 {
656 String strValue = (value==null)?"":value.toString();
657 _currentWriter.write(' ');
658 _currentWriter.write(name);
659 _currentWriter.write("=\"");
660 org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
661 strValue, false, false, !_isUTF8);
662 _currentWriter.write('"');
663 }
664 }
665
666 public void writeURIAttribute(String name, Object value, String componentPropertyName) throws IOException
667 {
668 if (name == null)
669 {
670 throw new NullPointerException("attributeName name must not be null");
671 }
672 if (!_startTagOpen)
673 {
674 throw new IllegalStateException("Must be called before the start element is closed (attribute '"
675 + name + "')");
676 }
677
678 String strValue = value.toString();
679 _currentWriter.write(' ');
680 _currentWriter.write(name);
681 _currentWriter.write("=\"");
682 if (strValue.toLowerCase().startsWith("javascript:"))
683 {
684 org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
685 strValue, false, false, !_isUTF8);
686 }
687 else
688 {
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717 org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encodeURIAtributte(_currentWriter,
718 strValue, _characterEncoding);
719 }
720 _currentWriter.write('"');
721 }
722
723 public void writeComment(Object value) throws IOException
724 {
725 if (value == null)
726 {
727 throw new NullPointerException("comment name must not be null");
728 }
729
730 closeStartTagIfNecessary();
731 _currentWriter.write("<!--");
732 _currentWriter.write(value.toString());
733 _currentWriter.write("-->");
734 }
735
736 public void writeText(Object value, String componentPropertyName) throws IOException
737 {
738 if (value == null)
739 {
740 throw new NullPointerException("Text must not be null.");
741 }
742
743 closeStartTagIfNecessary();
744
745 String strValue = value.toString();
746
747 if (isScriptOrStyle())
748 {
749
750 if (_isUTF8)
751 {
752 _currentWriter.write(strValue);
753 }
754 else
755 {
756 UnicodeEncoder.encode(_currentWriter, strValue);
757 }
758 }
759 else
760 {
761 org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(_currentWriter,
762 strValue, false, false, !_isUTF8);
763 }
764 }
765
766 public void writeText(char cbuf[], int off, int len) throws IOException
767 {
768 if (cbuf == null)
769 {
770 throw new NullPointerException("cbuf name must not be null");
771 }
772 if (cbuf.length < off + len)
773 {
774 throw new IndexOutOfBoundsException((off + len) + " > " + cbuf.length);
775 }
776
777 closeStartTagIfNecessary();
778
779 if (isScriptOrStyle())
780 {
781 String strValue = new String(cbuf, off, len);
782
783 if (_isUTF8)
784 {
785 _currentWriter.write(strValue);
786 }
787 else
788 {
789 UnicodeEncoder.encode(_currentWriter, strValue);
790 }
791 }
792 else if (isTextarea())
793 {
794
795 org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(
796 cbuf, off, len, false, false, !_isUTF8, _currentWriter);
797 }
798 else
799 {
800
801 org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(
802 cbuf, off, len, true, true, !_isUTF8, _currentWriter);
803 }
804 }
805
806 private boolean isScriptOrStyle()
807 {
808
809
810 return (_isStyle != null && _isStyle.booleanValue()) ||
811 (_isInsideScript != null && _isInsideScript.booleanValue());
812 }
813
814
815
816
817
818
819 private boolean isScript(String element)
820 {
821 return (HTML.SCRIPT_ELEM.equalsIgnoreCase(element));
822 }
823
824 private boolean isScript()
825 {
826 return (_isInsideScript != null && _isInsideScript.booleanValue());
827 }
828
829 private boolean isStyle(String element)
830 {
831 return (HTML.STYLE_ELEM.equalsIgnoreCase(element));
832 }
833
834 private boolean isStyle()
835 {
836 return (_isStyle != null && _isStyle.booleanValue());
837 }
838
839 private boolean isTextarea()
840 {
841 initializeStartedTagInfo();
842
843 return _isTextArea != null && _isTextArea.booleanValue();
844 }
845
846 private void initializeStartedTagInfo()
847 {
848 if(_startElementName != null)
849 {
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864 if(_isTextArea == null)
865 {
866 if(_startElementName.equalsIgnoreCase(HTML.TEXTAREA_ELEM))
867 {
868 _isTextArea = Boolean.TRUE;
869 }
870 else
871 {
872 _isTextArea = Boolean.FALSE;
873 }
874 }
875 }
876 }
877
878 public ResponseWriter cloneWithWriter(Writer writer)
879 {
880 HtmlResponseWriterImpl newWriter
881 = new HtmlResponseWriterImpl(writer, getContentType(), getCharacterEncoding(),
882 _wrapScriptContentWithXmlCommentTag, _writerContentTypeMode);
883
884
885 return newWriter;
886 }
887
888
889
890
891 public void close() throws IOException
892 {
893 closeStartTagIfNecessary();
894 _currentWriter.close();
895 }
896
897 public void write(char cbuf[], int off, int len) throws IOException
898 {
899 closeStartTagIfNecessary();
900
901 if (_isUTF8)
902 {
903 _currentWriter.write(cbuf, off, len);
904 }
905 else
906 {
907 UnicodeEncoder.encode(_currentWriter, cbuf, off, len);
908 }
909 }
910
911 public void write(int c) throws IOException
912 {
913 closeStartTagIfNecessary();
914 _currentWriter.write(c);
915 }
916
917 public void write(char cbuf[]) throws IOException
918 {
919 closeStartTagIfNecessary();
920
921 if (_isUTF8)
922 {
923 _currentWriter.write(cbuf);
924 }
925 else
926 {
927 UnicodeEncoder.encode(_currentWriter, cbuf, 0, cbuf.length);
928 }
929 }
930
931 public void write(String str) throws IOException
932 {
933 closeStartTagIfNecessary();
934
935
936 if (str.length() > 0)
937 {
938
939 if (_isUTF8)
940 {
941 _currentWriter.write(str);
942 }
943 else
944 {
945 UnicodeEncoder.encode(_currentWriter, str);
946 }
947 }
948 }
949
950 public void write(String str, int off, int len) throws IOException
951 {
952 closeStartTagIfNecessary();
953
954 if (_isUTF8)
955 {
956 _currentWriter.write(str, off, len);
957 }
958 else
959 {
960 UnicodeEncoder.encode(_currentWriter, str, off, len);
961 }
962 }
963
964
965
966
967
968
969 public void writeText(Object object, UIComponent component, String string) throws IOException
970 {
971 writeText(object,string);
972 }
973
974 protected StreamCharBuffer getInternalBuffer()
975 {
976 return getInternalBuffer(false);
977 }
978
979 protected StreamCharBuffer getInternalBuffer(boolean reset)
980 {
981 if (_buffer == null)
982 {
983 _buffer = new StreamCharBuffer(256, 100);
984 }
985 else if (reset)
986 {
987 _buffer.reset();
988 }
989 return _buffer;
990 }
991 }