1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.jasper.compiler;
18
19 import java.io.CharArrayWriter;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InputStream;
23
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.jar.JarFile;
27
28 import javax.servlet.jsp.tagext.TagFileInfo;
29 import javax.servlet.jsp.tagext.TagInfo;
30 import javax.servlet.jsp.tagext.TagLibraryInfo;
31 import javax.xml.parsers.SAXParser;
32 import javax.xml.parsers.SAXParserFactory;
33
34 import org.apache.jasper.JasperException;
35 import org.apache.jasper.JspCompilationContext;
36 import org.xml.sax.Attributes;
37 import org.xml.sax.InputSource;
38 import org.xml.sax.Locator;
39 import org.xml.sax.SAXException;
40 import org.xml.sax.SAXParseException;
41 import org.xml.sax.XMLReader;
42 import org.xml.sax.ext.LexicalHandler;
43 import org.xml.sax.helpers.AttributesImpl;
44 import org.xml.sax.helpers.DefaultHandler;
45
46 /**
47 * Class implementing a parser for a JSP document, that is, a JSP page in XML
48 * syntax.
49 *
50 * @author Jan Luehe
51 * @author Kin-man Chung
52 */
53
54 class JspDocumentParser
55 extends DefaultHandler
56 implements LexicalHandler, TagConstants {
57
58 private static final String JSP_VERSION = "version";
59 private static final String LEXICAL_HANDLER_PROPERTY =
60 "http://xml.org/sax/properties/lexical-handler";
61 private static final String JSP_URI = "http://java.sun.com/JSP/Page";
62
63 private static final EnableDTDValidationException ENABLE_DTD_VALIDATION_EXCEPTION =
64 new EnableDTDValidationException(
65 "jsp.error.enable_dtd_validation",
66 null);
67
68 private ParserController parserController;
69 private JspCompilationContext ctxt;
70 private PageInfo pageInfo;
71 private String path;
72 private StringBuffer charBuffer;
73
74 // Node representing the XML element currently being parsed
75 private Node current;
76
77 /*
78 * Outermost (in the nesting hierarchy) node whose body is declared to be
79 * scriptless. If a node's body is declared to be scriptless, all its
80 * nested nodes must be scriptless, too.
81 */
82 private Node scriptlessBodyNode;
83
84 private Locator locator;
85
86 //Mark representing the start of the current element. Note
87 //that locator.getLineNumber() and locator.getColumnNumber()
88 //return the line and column numbers for the character
89 //immediately _following_ the current element. The underlying
90 //XMl parser eats white space that is not part of character
91 //data, so for Nodes that are not created from character data,
92 //this is the best we can do. But when we parse character data,
93 //we get an accurate starting location by starting with startMark
94 //as set by the previous element, and updating it as we advance
95 //through the characters.
96 private Mark startMark;
97
98 // Flag indicating whether we are inside DTD declarations
99 private boolean inDTD;
100
101 private boolean isValidating;
102
103 private ErrorDispatcher err;
104 private boolean isTagFile;
105 private boolean directivesOnly;
106 private boolean isTop;
107
108 // Nesting level of Tag dependent bodies
109 private int tagDependentNesting = 0;
110 // Flag set to delay incrmenting tagDependentNesting until jsp:body
111 // is first encountered
112 private boolean tagDependentPending = false;
113
114 /*
115 * Constructor
116 */
117 public JspDocumentParser(
118 ParserController pc,
119 String path,
120 boolean isTagFile,
121 boolean directivesOnly) {
122 this.parserController = pc;
123 this.ctxt = pc.getJspCompilationContext();
124 this.pageInfo = pc.getCompiler().getPageInfo();
125 this.err = pc.getCompiler().getErrorDispatcher();
126 this.path = path;
127 this.isTagFile = isTagFile;
128 this.directivesOnly = directivesOnly;
129 this.isTop = true;
130 }
131
132 /*
133 * Parses a JSP document by responding to SAX events.
134 *
135 * @throws JasperException
136 */
137 public static Node.Nodes parse(
138 ParserController pc,
139 String path,
140 JarFile jarFile,
141 Node parent,
142 boolean isTagFile,
143 boolean directivesOnly,
144 String pageEnc,
145 String jspConfigPageEnc,
146 boolean isEncodingSpecifiedInProlog,
147 boolean isBomPresent)
148 throws JasperException {
149
150 JspDocumentParser jspDocParser =
151 new JspDocumentParser(pc, path, isTagFile, directivesOnly);
152 Node.Nodes pageNodes = null;
153
154 try {
155
156 // Create dummy root and initialize it with given page encodings
157 Node.Root dummyRoot = new Node.Root(null, parent, true);
158 dummyRoot.setPageEncoding(pageEnc);
159 dummyRoot.setJspConfigPageEncoding(jspConfigPageEnc);
160 dummyRoot.setIsEncodingSpecifiedInProlog(
161 isEncodingSpecifiedInProlog);
162 dummyRoot.setIsBomPresent(isBomPresent);
163 jspDocParser.current = dummyRoot;
164 if (parent == null) {
165 jspDocParser.addInclude(
166 dummyRoot,
167 jspDocParser.pageInfo.getIncludePrelude());
168 } else {
169 jspDocParser.isTop = false;
170 }
171
172 // Parse the input
173 SAXParser saxParser = getSAXParser(false, jspDocParser);
174 InputStream inStream = null;
175 try {
176 inStream = JspUtil.getInputStream(path, jarFile,
177 jspDocParser.ctxt,
178 jspDocParser.err);
179 saxParser.parse(new InputSource(inStream), jspDocParser);
180 } catch (EnableDTDValidationException e) {
181 saxParser = getSAXParser(true, jspDocParser);
182 jspDocParser.isValidating = true;
183 if (inStream != null) {
184 try {
185 inStream.close();
186 } catch (Exception any) {
187 }
188 }
189 inStream = JspUtil.getInputStream(path, jarFile,
190 jspDocParser.ctxt,
191 jspDocParser.err);
192 saxParser.parse(new InputSource(inStream), jspDocParser);
193 } finally {
194 if (inStream != null) {
195 try {
196 inStream.close();
197 } catch (Exception any) {
198 }
199 }
200 }
201
202 if (parent == null) {
203 jspDocParser.addInclude(
204 dummyRoot,
205 jspDocParser.pageInfo.getIncludeCoda());
206 }
207
208 // Create Node.Nodes from dummy root
209 pageNodes = new Node.Nodes(dummyRoot);
210
211 } catch (IOException ioe) {
212 jspDocParser.err.jspError("jsp.error.data.file.read", path, ioe);
213 } catch (SAXParseException e) {
214 jspDocParser.err.jspError
215 (new Mark(jspDocParser.ctxt, path, e.getLineNumber(),
216 e.getColumnNumber()),
217 e.getMessage());
218 } catch (Exception e) {
219 jspDocParser.err.jspError(e);
220 }
221
222 return pageNodes;
223 }
224
225 /*
226 * Processes the given list of included files.
227 *
228 * This is used to implement the include-prelude and include-coda
229 * subelements of the jsp-config element in web.xml
230 */
231 private void addInclude(Node parent, List files) throws SAXException {
232 if (files != null) {
233 Iterator iter = files.iterator();
234 while (iter.hasNext()) {
235 String file = (String)iter.next();
236 AttributesImpl attrs = new AttributesImpl();
237 attrs.addAttribute("", "file", "file", "CDATA", file);
238
239 // Create a dummy Include directive node
240 Node includeDir =
241 new Node.IncludeDirective(attrs, null, // XXX
242 parent);
243 processIncludeDirective(file, includeDir);
244 }
245 }
246 }
247
248 /*
249 * Receives notification of the start of an element.
250 *
251 * This method assigns the given tag attributes to one of 3 buckets:
252 *
253 * - "xmlns" attributes that represent (standard or custom) tag libraries.
254 * - "xmlns" attributes that do not represent tag libraries.
255 * - all remaining attributes.
256 *
257 * For each "xmlns" attribute that represents a custom tag library, the
258 * corresponding TagLibraryInfo object is added to the set of custom
259 * tag libraries.
260 */
261 public void startElement(
262 String uri,
263 String localName,
264 String qName,
265 Attributes attrs)
266 throws SAXException {
267
268 AttributesImpl taglibAttrs = null;
269 AttributesImpl nonTaglibAttrs = null;
270 AttributesImpl nonTaglibXmlnsAttrs = null;
271
272 processChars();
273
274 checkPrefixes(uri, qName, attrs);
275
276 if (directivesOnly &&
277 !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) {
278 return;
279 }
280
281 // jsp:text must not have any subelements
282 if (JSP_URI.equals(uri) && TEXT_ACTION.equals(current.getLocalName())) {
283 throw new SAXParseException(
284 Localizer.getMessage("jsp.error.text.has_subelement"),
285 locator);
286 }
287
288 startMark = new Mark(ctxt, path, locator.getLineNumber(),
289 locator.getColumnNumber());
290
291 if (attrs != null) {
292 /*
293 * Notice that due to a bug in the underlying SAX parser, the
294 * attributes must be enumerated in descending order.
295 */
296 boolean isTaglib = false;
297 for (int i = attrs.getLength() - 1; i >= 0; i--) {
298 isTaglib = false;
299 String attrQName = attrs.getQName(i);
300 if (!attrQName.startsWith("xmlns")) {
301 if (nonTaglibAttrs == null) {
302 nonTaglibAttrs = new AttributesImpl();
303 }
304 nonTaglibAttrs.addAttribute(
305 attrs.getURI(i),
306 attrs.getLocalName(i),
307 attrs.getQName(i),
308 attrs.getType(i),
309 attrs.getValue(i));
310 } else {
311 if (attrQName.startsWith("xmlns:jsp")) {
312 isTaglib = true;
313 } else {
314 String attrUri = attrs.getValue(i);
315 // TaglibInfo for this uri already established in
316 // startPrefixMapping
317 isTaglib = pageInfo.hasTaglib(attrUri);
318 }
319 if (isTaglib) {
320 if (taglibAttrs == null) {
321 taglibAttrs = new AttributesImpl();
322 }
323 taglibAttrs.addAttribute(
324 attrs.getURI(i),
325 attrs.getLocalName(i),
326 attrs.getQName(i),
327 attrs.getType(i),
328 attrs.getValue(i));
329 } else {
330 if (nonTaglibXmlnsAttrs == null) {
331 nonTaglibXmlnsAttrs = new AttributesImpl();
332 }
333 nonTaglibXmlnsAttrs.addAttribute(
334 attrs.getURI(i),
335 attrs.getLocalName(i),
336 attrs.getQName(i),
337 attrs.getType(i),
338 attrs.getValue(i));
339 }
340 }
341 }
342 }
343
344 Node node = null;
345
346 if (tagDependentPending && JSP_URI.equals(uri) &&
347 localName.equals(BODY_ACTION)) {
348 tagDependentPending = false;
349 tagDependentNesting++;
350 current =
351 parseStandardAction(
352 qName,
353 localName,
354 nonTaglibAttrs,
355 nonTaglibXmlnsAttrs,
356 taglibAttrs,
357 startMark,
358 current);
359 return;
360 }
361
362 if (tagDependentPending && JSP_URI.equals(uri) &&
363 localName.equals(ATTRIBUTE_ACTION)) {
364 current =
365 parseStandardAction(
366 qName,
367 localName,
368 nonTaglibAttrs,
369 nonTaglibXmlnsAttrs,
370 taglibAttrs,
371 startMark,
372 current);
373 return;
374 }
375
376 if (tagDependentPending) {
377 tagDependentPending = false;
378 tagDependentNesting++;
379 }
380
381 if (tagDependentNesting > 0) {
382 node =
383 new Node.UninterpretedTag(
384 qName,
385 localName,
386 nonTaglibAttrs,
387 nonTaglibXmlnsAttrs,
388 taglibAttrs,
389 startMark,
390 current);
391 } else if (JSP_URI.equals(uri)) {
392 node =
393 parseStandardAction(
394 qName,
395 localName,
396 nonTaglibAttrs,
397 nonTaglibXmlnsAttrs,
398 taglibAttrs,
399 startMark,
400 current);
401 } else {
402 node =
403 parseCustomAction(
404 qName,
405 localName,
406 uri,
407 nonTaglibAttrs,
408 nonTaglibXmlnsAttrs,
409 taglibAttrs,
410 startMark,
411 current);
412 if (node == null) {
413 node =
414 new Node.UninterpretedTag(
415 qName,
416 localName,
417 nonTaglibAttrs,
418 nonTaglibXmlnsAttrs,
419 taglibAttrs,
420 startMark,
421 current);
422 } else {
423 // custom action
424 String bodyType = getBodyType((Node.CustomTag) node);
425
426 if (scriptlessBodyNode == null
427 && bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
428 scriptlessBodyNode = node;
429 }
430 else if (TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType)) {
431 tagDependentPending = true;
432 }
433 }
434 }
435
436 current = node;
437 }
438
439 /*
440 * Receives notification of character data inside an element.
441 *
442 * The SAX does not call this method with all of the template text, but may
443 * invoke this method with chunks of it. This is a problem when we try
444 * to determine if the text contains only whitespaces, or when we are
445 * looking for an EL expression string. Therefore it is necessary to
446 * buffer and concatenate the chunks and process the concatenated text
447 * later (at beginTag and endTag)
448 *
449 * @param buf The characters
450 * @param offset The start position in the character array
451 * @param len The number of characters to use from the character array
452 *
453 * @throws SAXException
454 */
455 public void characters(char[] buf, int offset, int len) {
456
457 if (charBuffer == null) {
458 charBuffer = new StringBuffer();
459 }
460 charBuffer.append(buf, offset, len);
461 }
462
463 private void processChars() throws SAXException {
464
465 if (charBuffer == null || directivesOnly) {
466 return;
467 }
468
469 /*
470 * JSP.6.1.1: All textual nodes that have only white space are to be
471 * dropped from the document, except for nodes in a jsp:text element,
472 * and any leading and trailing white-space-only textual nodes in a
473 * jsp:attribute whose 'trim' attribute is set to FALSE, which are to
474 * be kept verbatim.
475 * JSP.6.2.3 defines white space characters.
476 */
477 boolean isAllSpace = true;
478 if (!(current instanceof Node.JspText)
479 && !(current instanceof Node.NamedAttribute)) {
480 for (int i = 0; i < charBuffer.length(); i++) {
481 if (!(charBuffer.charAt(i) == ' '
482 || charBuffer.charAt(i) == '\n'
483 || charBuffer.charAt(i) == '\r'
484 || charBuffer.charAt(i) == '\t')) {
485 isAllSpace = false;
486 break;
487 }
488 }
489 }
490
491 if (!isAllSpace && tagDependentPending) {
492 tagDependentPending = false;
493 tagDependentNesting++;
494 }
495
496 if (tagDependentNesting > 0) {
497 if (charBuffer.length() > 0) {
498 new Node.TemplateText(charBuffer.toString(), startMark, current);
499 }
500 startMark = new Mark(ctxt, path, locator.getLineNumber(),
501 locator.getColumnNumber());
502 charBuffer = null;
503 return;
504 }
505
506 if ((current instanceof Node.JspText)
507 || (current instanceof Node.NamedAttribute)
508 || !isAllSpace) {
509
510 int line = startMark.getLineNumber();
511 int column = startMark.getColumnNumber();
512
513 CharArrayWriter ttext = new CharArrayWriter();
514 int lastCh = 0, elType = 0;
515 for (int i = 0; i < charBuffer.length(); i++) {
516
517 int ch = charBuffer.charAt(i);
518 if (ch == '\n') {
519 column = 1;
520 line++;
521 } else {
522 column++;
523 }
524 if ((lastCh == '$' || lastCh == '#') && ch == '{') {
525 elType = lastCh;
526 if (ttext.size() > 0) {
527 new Node.TemplateText(
528 ttext.toString(),
529 startMark,
530 current);
531 ttext = new CharArrayWriter();
532 //We subtract two from the column number to
533 //account for the '[$,#]{' that we've already parsed
534 startMark = new Mark(ctxt, path, line, column - 2);
535 }
536 // following "${" || "#{" to first unquoted "}"
537 i++;
538 boolean singleQ = false;
539 boolean doubleQ = false;
540 lastCh = 0;
541 for (;; i++) {
542 if (i >= charBuffer.length()) {
543 throw new SAXParseException(
544 Localizer.getMessage(
545 "jsp.error.unterminated",
546 (char) elType + "{"),
547 locator);
548
549 }
550 ch = charBuffer.charAt(i);
551 if (ch == '\n') {
552 column = 1;
553 line++;
554 } else {
555 column++;
556 }
557 if (lastCh == '\\' && (singleQ || doubleQ)) {
558 ttext.write(ch);
559 lastCh = 0;
560 continue;
561 }
562 if (ch == '}') {
563 new Node.ELExpression((char) elType,
564 ttext.toString(),
565 startMark,
566 current);
567 ttext = new CharArrayWriter();
568 startMark = new Mark(ctxt, path, line, column);
569 break;
570 }
571 if (ch == '"')
572 doubleQ = !doubleQ;
573 else if (ch == '\'')
574 singleQ = !singleQ;
575
576 ttext.write(ch);
577 lastCh = ch;
578 }
579 } else if (lastCh == '\\' && (ch == '$' || ch == '#')) {
580 ttext.write(ch);
581 ch = 0; // Not start of EL anymore
582 } else {
583 if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
584 ttext.write(lastCh);
585 }
586 if (ch != '$' && ch != '#' && ch != '\\') {
587 ttext.write(ch);
588 }
589 }
590 lastCh = ch;
591 }
592 if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
593 ttext.write(lastCh);
594 }
595 if (ttext.size() > 0) {
596 new Node.TemplateText(ttext.toString(), startMark, current);
597 }
598 }
599 startMark = new Mark(ctxt, path, locator.getLineNumber(),
600 locator.getColumnNumber());
601
602 charBuffer = null;
603 }
604
605 /*
606 * Receives notification of the end of an element.
607 */
608 public void endElement(String uri, String localName, String qName)
609 throws SAXException {
610
611 processChars();
612
613 if (directivesOnly &&
614 !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) {
615 return;
616 }
617
618 if (current instanceof Node.NamedAttribute) {
619 boolean isTrim = ((Node.NamedAttribute)current).isTrim();
620 Node.Nodes subElems = ((Node.NamedAttribute)current).getBody();
621 for (int i = 0; subElems != null && i < subElems.size(); i++) {
622 Node subElem = subElems.getNode(i);
623 if (!(subElem instanceof Node.TemplateText)) {
624 continue;
625 }
626 // Ignore any whitespace (including spaces, carriage returns,
627 // line feeds, and tabs, that appear at the beginning and at
628 // the end of the body of the <jsp:attribute> action, if the
629 // action's 'trim' attribute is set to TRUE (default).
630 // In addition, any textual nodes in the <jsp:attribute> that
631 // have only white space are dropped from the document, with
632 // the exception of leading and trailing white-space-only
633 // textual nodes in a <jsp:attribute> whose 'trim' attribute
634 // is set to FALSE, which must be kept verbatim.
635 if (i == 0) {
636 if (isTrim) {
637 ((Node.TemplateText)subElem).ltrim();
638 }
639 } else if (i == subElems.size() - 1) {
640 if (isTrim) {
641 ((Node.TemplateText)subElem).rtrim();
642 }
643 } else {
644 if (((Node.TemplateText)subElem).isAllSpace()) {
645 subElems.remove(subElem);
646 }
647 }
648 }
649 } else if (current instanceof Node.ScriptingElement) {
650 checkScriptingBody((Node.ScriptingElement)current);
651 }
652
653 if ( isTagDependent(current)) {
654 tagDependentNesting--;
655 }
656
657 if (scriptlessBodyNode != null
658 && current.equals(scriptlessBodyNode)) {
659 scriptlessBodyNode = null;
660 }
661
662 if (current.getParent() != null) {
663 current = current.getParent();
664 }
665 }
666
667 /*
668 * Receives the document locator.
669 *
670 * @param locator the document locator
671 */
672 public void setDocumentLocator(Locator locator) {
673 this.locator = locator;
674 }
675
676 /*
677 * See org.xml.sax.ext.LexicalHandler.
678 */
679 public void comment(char[] buf, int offset, int len) throws SAXException {
680
681 processChars(); // Flush char buffer and remove white spaces
682
683 // ignore comments in the DTD
684 if (!inDTD) {
685 startMark =
686 new Mark(
687 ctxt,
688 path,
689 locator.getLineNumber(),
690 locator.getColumnNumber());
691 new Node.Comment(new String(buf, offset, len), startMark, current);
692 }
693 }
694
695 /*
696 * See org.xml.sax.ext.LexicalHandler.
697 */
698 public void startCDATA() throws SAXException {
699
700 processChars(); // Flush char buffer and remove white spaces
701 startMark = new Mark(ctxt, path, locator.getLineNumber(),
702 locator.getColumnNumber());
703 }
704
705 /*
706 * See org.xml.sax.ext.LexicalHandler.
707 */
708 public void endCDATA() throws SAXException {
709 processChars(); // Flush char buffer and remove white spaces
710 }
711
712 /*
713 * See org.xml.sax.ext.LexicalHandler.
714 */
715 public void startEntity(String name) throws SAXException {
716 // do nothing
717 }
718
719 /*
720 * See org.xml.sax.ext.LexicalHandler.
721 */
722 public void endEntity(String name) throws SAXException {
723 // do nothing
724 }
725
726 /*
727 * See org.xml.sax.ext.LexicalHandler.
728 */
729 public void startDTD(String name, String publicId, String systemId)
730 throws SAXException {
731 if (!isValidating) {
732 fatalError(ENABLE_DTD_VALIDATION_EXCEPTION);
733 }
734
735 inDTD = true;
736 }
737
738 /*
739 * See org.xml.sax.ext.LexicalHandler.
740 */
741 public void endDTD() throws SAXException {
742 inDTD = false;
743 }
744
745 /*
746 * Receives notification of a non-recoverable error.
747 */
748 public void fatalError(SAXParseException e) throws SAXException {
749 throw e;
750 }
751
752 /*
753 * Receives notification of a recoverable error.
754 */
755 public void error(SAXParseException e) throws SAXException {
756 throw e;
757 }
758
759 /*
760 * Receives notification of the start of a Namespace mapping.
761 */
762 public void startPrefixMapping(String prefix, String uri)
763 throws SAXException {
764 TagLibraryInfo taglibInfo;
765
766 if (directivesOnly && !(JSP_URI.equals(uri))) {
767 return;
768 }
769
770 try {
771 taglibInfo = getTaglibInfo(prefix, uri);
772 } catch (JasperException je) {
773 throw new SAXParseException(
774 Localizer.getMessage("jsp.error.could.not.add.taglibraries"),
775 locator,
776 je);
777 }
778
779 if (taglibInfo != null) {
780 if (pageInfo.getTaglib(uri) == null) {
781 pageInfo.addTaglib(uri, taglibInfo);
782 }
783 pageInfo.pushPrefixMapping(prefix, uri);
784 } else {
785 pageInfo.pushPrefixMapping(prefix, null);
786 }
787 }
788
789 /*
790 * Receives notification of the end of a Namespace mapping.
791 */
792 public void endPrefixMapping(String prefix) throws SAXException {
793
794 if (directivesOnly) {
795 String uri = pageInfo.getURI(prefix);
796 if (!JSP_URI.equals(uri)) {
797 return;
798 }
799 }
800
801 pageInfo.popPrefixMapping(prefix);
802 }
803
804 //*********************************************************************
805 // Private utility methods
806
807 private Node parseStandardAction(
808 String qName,
809 String localName,
810 Attributes nonTaglibAttrs,
811 Attributes nonTaglibXmlnsAttrs,
812 Attributes taglibAttrs,
813 Mark start,
814 Node parent)
815 throws SAXException {
816
817 Node node = null;
818
819 if (localName.equals(ROOT_ACTION)) {
820 if (!(current instanceof Node.Root)) {
821 throw new SAXParseException(
822 Localizer.getMessage("jsp.error.nested_jsproot"),
823 locator);
824 }
825 node =
826 new Node.JspRoot(
827 qName,
828 nonTaglibAttrs,
829 nonTaglibXmlnsAttrs,
830 taglibAttrs,
831 start,
832 current);
833 if (isTop) {
834 pageInfo.setHasJspRoot(true);
835 }
836 } else if (localName.equals(PAGE_DIRECTIVE_ACTION)) {
837 if (isTagFile) {
838 throw new SAXParseException(
839 Localizer.getMessage(
840 "jsp.error.action.istagfile",
841 localName),
842 locator);
843 }
844 node =
845 new Node.PageDirective(
846 qName,
847 nonTaglibAttrs,
848 nonTaglibXmlnsAttrs,
849 taglibAttrs,
850 start,
851 current);
852 String imports = nonTaglibAttrs.getValue("import");
853 // There can only be one 'import' attribute per page directive
854 if (imports != null) {
855 ((Node.PageDirective)node).addImport(imports);
856 }
857 } else if (localName.equals(INCLUDE_DIRECTIVE_ACTION)) {
858 node =
859 new Node.IncludeDirective(
860 qName,
861 nonTaglibAttrs,
862 nonTaglibXmlnsAttrs,
863 taglibAttrs,
864 start,
865 current);
866 processIncludeDirective(nonTaglibAttrs.getValue("file"), node);
867 } else if (localName.equals(DECLARATION_ACTION)) {
868 if (scriptlessBodyNode != null) {
869 // We're nested inside a node whose body is
870 // declared to be scriptless
871 throw new SAXParseException(
872 Localizer.getMessage(
873 "jsp.error.no.scriptlets",
874 localName),
875 locator);
876 }
877 node =
878 new Node.Declaration(
879 qName,
880 nonTaglibXmlnsAttrs,
881 taglibAttrs,
882 start,
883 current);
884 } else if (localName.equals(SCRIPTLET_ACTION)) {
885 if (scriptlessBodyNode != null) {
886 // We're nested inside a node whose body is
887 // declared to be scriptless
888 throw new SAXParseException(
889 Localizer.getMessage(
890 "jsp.error.no.scriptlets",
891 localName),
892 locator);
893 }
894 node =
895 new Node.Scriptlet(
896 qName,
897 nonTaglibXmlnsAttrs,
898 taglibAttrs,
899 start,
900 current);
901 } else if (localName.equals(EXPRESSION_ACTION)) {
902 if (scriptlessBodyNode != null) {
903 // We're nested inside a node whose body is
904 // declared to be scriptless
905 throw new SAXParseException(
906 Localizer.getMessage(
907 "jsp.error.no.scriptlets",
908 localName),
909 locator);
910 }
911 node =
912 new Node.Expression(
913 qName,
914 nonTaglibXmlnsAttrs,
915 taglibAttrs,
916 start,
917 current);
918 } else if (localName.equals(USE_BEAN_ACTION)) {
919 node =
920 new Node.UseBean(
921 qName,
922 nonTaglibAttrs,
923 nonTaglibXmlnsAttrs,
924 taglibAttrs,
925 start,
926 current);
927 } else if (localName.equals(SET_PROPERTY_ACTION)) {
928 node =
929 new Node.SetProperty(
930 qName,
931 nonTaglibAttrs,
932 nonTaglibXmlnsAttrs,
933 taglibAttrs,
934 start,
935 current);
936 } else if (localName.equals(GET_PROPERTY_ACTION)) {
937 node =
938 new Node.GetProperty(
939 qName,
940 nonTaglibAttrs,
941 nonTaglibXmlnsAttrs,
942 taglibAttrs,
943 start,
944 current);
945 } else if (localName.equals(INCLUDE_ACTION)) {
946 node =
947 new Node.IncludeAction(
948 qName,
949 nonTaglibAttrs,
950 nonTaglibXmlnsAttrs,
951 taglibAttrs,
952 start,
953 current);
954 } else if (localName.equals(FORWARD_ACTION)) {
955 node =
956 new Node.ForwardAction(
957 qName,
958 nonTaglibAttrs,
959 nonTaglibXmlnsAttrs,
960 taglibAttrs,
961 start,
962 current);
963 } else if (localName.equals(PARAM_ACTION)) {
964 node =
965 new Node.ParamAction(
966 qName,
967 nonTaglibAttrs,
968 nonTaglibXmlnsAttrs,
969 taglibAttrs,
970 start,
971 current);
972 } else if (localName.equals(PARAMS_ACTION)) {
973 node =
974 new Node.ParamsAction(
975 qName,
976 nonTaglibXmlnsAttrs,
977 taglibAttrs,
978 start,
979 current);
980 } else if (localName.equals(PLUGIN_ACTION)) {
981 node =
982 new Node.PlugIn(
983 qName,
984 nonTaglibAttrs,
985 nonTaglibXmlnsAttrs,
986 taglibAttrs,
987 start,
988 current);
989 } else if (localName.equals(TEXT_ACTION)) {
990 node =
991 new Node.JspText(
992 qName,
993 nonTaglibXmlnsAttrs,
994 taglibAttrs,
995 start,
996 current);
997 } else if (localName.equals(BODY_ACTION)) {
998 node =
999 new Node.JspBody(
1000 qName,
1001 nonTaglibXmlnsAttrs,
1002 taglibAttrs,
1003 start,
1004 current);
1005 } else if (localName.equals(ATTRIBUTE_ACTION)) {
1006 node =
1007 new Node.NamedAttribute(
1008 qName,
1009 nonTaglibAttrs,
1010 nonTaglibXmlnsAttrs,
1011 taglibAttrs,
1012 start,
1013 current);
1014 } else if (localName.equals(OUTPUT_ACTION)) {
1015 node =
1016 new Node.JspOutput(
1017 qName,
1018 nonTaglibAttrs,
1019 nonTaglibXmlnsAttrs,
1020 taglibAttrs,
1021 start,
1022 current);
1023 } else if (localName.equals(TAG_DIRECTIVE_ACTION)) {
1024 if (!isTagFile) {
1025 throw new SAXParseException(
1026 Localizer.getMessage(
1027 "jsp.error.action.isnottagfile",
1028 localName),
1029 locator);
1030 }
1031 node =
1032 new Node.TagDirective(
1033 qName,
1034 nonTaglibAttrs,
1035 nonTaglibXmlnsAttrs,
1036 taglibAttrs,
1037 start,
1038 current);
1039 String imports = nonTaglibAttrs.getValue("import");
1040 // There can only be one 'import' attribute per tag directive
1041 if (imports != null) {
1042 ((Node.TagDirective)node).addImport(imports);
1043 }
1044 } else if (localName.equals(ATTRIBUTE_DIRECTIVE_ACTION)) {
1045 if (!isTagFile) {
1046 throw new SAXParseException(
1047 Localizer.getMessage(
1048 "jsp.error.action.isnottagfile",
1049 localName),
1050 locator);
1051 }
1052 node =
1053 new Node.AttributeDirective(
1054 qName,
1055 nonTaglibAttrs,
1056 nonTaglibXmlnsAttrs,
1057 taglibAttrs,
1058 start,
1059 current);
1060 } else if (localName.equals(VARIABLE_DIRECTIVE_ACTION)) {
1061 if (!isTagFile) {
1062 throw new SAXParseException(
1063 Localizer.getMessage(
1064 "jsp.error.action.isnottagfile",
1065 localName),
1066 locator);
1067 }
1068 node =
1069 new Node.VariableDirective(
1070 qName,
1071 nonTaglibAttrs,
1072 nonTaglibXmlnsAttrs,
1073 taglibAttrs,
1074 start,
1075 current);
1076 } else if (localName.equals(INVOKE_ACTION)) {
1077 if (!isTagFile) {
1078 throw new SAXParseException(
1079 Localizer.getMessage(
1080 "jsp.error.action.isnottagfile",
1081 localName),
1082 locator);
1083 }
1084 node =
1085 new Node.InvokeAction(
1086 qName,
1087 nonTaglibAttrs,
1088 nonTaglibXmlnsAttrs,
1089 taglibAttrs,
1090 start,
1091 current);
1092 } else if (localName.equals(DOBODY_ACTION)) {
1093 if (!isTagFile) {
1094 throw new SAXParseException(
1095 Localizer.getMessage(
1096 "jsp.error.action.isnottagfile",
1097 localName),
1098 locator);
1099 }
1100 node =
1101 new Node.DoBodyAction(
1102 qName,
1103 nonTaglibAttrs,
1104 nonTaglibXmlnsAttrs,
1105 taglibAttrs,
1106 start,
1107 current);
1108 } else if (localName.equals(ELEMENT_ACTION)) {
1109 node =
1110 new Node.JspElement(
1111 qName,
1112 nonTaglibAttrs,
1113 nonTaglibXmlnsAttrs,
1114 taglibAttrs,
1115 start,
1116 current);
1117 } else if (localName.equals(FALLBACK_ACTION)) {
1118 node =
1119 new Node.FallBackAction(
1120 qName,
1121 nonTaglibXmlnsAttrs,
1122 taglibAttrs,
1123 start,
1124 current);
1125 } else {
1126 throw new SAXParseException(
1127 Localizer.getMessage(
1128 "jsp.error.xml.badStandardAction",
1129 localName),
1130 locator);
1131 }
1132
1133 return node;
1134 }
1135
1136 /*
1137 * Checks if the XML element with the given tag name is a custom action,
1138 * and returns the corresponding Node object.
1139 */
1140 private Node parseCustomAction(
1141 String qName,
1142 String localName,
1143 String uri,
1144 Attributes nonTaglibAttrs,
1145 Attributes nonTaglibXmlnsAttrs,
1146 Attributes taglibAttrs,
1147 Mark start,
1148 Node parent)
1149 throws SAXException {
1150
1151 // Check if this is a user-defined (custom) tag
1152 TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
1153 if (tagLibInfo == null) {
1154 return null;
1155 }
1156
1157 TagInfo tagInfo = tagLibInfo.getTag(localName);
1158 TagFileInfo tagFileInfo = tagLibInfo.getTagFile(localName);
1159 if (tagInfo == null && tagFileInfo == null) {
1160 throw new SAXException(
1161 Localizer.getMessage("jsp.error.xml.bad_tag", localName, uri));
1162 }
1163 Class tagHandlerClass = null;
1164 if (tagInfo != null) {
1165 String handlerClassName = tagInfo.getTagClassName();
1166 try {
1167 tagHandlerClass =
1168 ctxt.getClassLoader().loadClass(handlerClassName);
1169 } catch (Exception e) {
1170 throw new SAXException(
1171 Localizer.getMessage("jsp.error.loadclass.taghandler",
1172 handlerClassName,
1173 qName),
1174 e);
1175 }
1176 }
1177
1178 String prefix = "";
1179 int colon = qName.indexOf(':');
1180 if (colon != -1) {
1181 prefix = qName.substring(0, colon);
1182 }
1183
1184 Node.CustomTag ret = null;
1185 if (tagInfo != null) {
1186 ret =
1187 new Node.CustomTag(
1188 qName,
1189 prefix,
1190 localName,
1191 uri,
1192 nonTaglibAttrs,
1193 nonTaglibXmlnsAttrs,
1194 taglibAttrs,
1195 start,
1196 parent,
1197 tagInfo,
1198 tagHandlerClass);
1199 } else {
1200 ret =
1201 new Node.CustomTag(
1202 qName,
1203 prefix,
1204 localName,
1205 uri,
1206 nonTaglibAttrs,
1207 nonTaglibXmlnsAttrs,
1208 taglibAttrs,
1209 start,
1210 parent,
1211 tagFileInfo);
1212 }
1213
1214 return ret;
1215 }
1216
1217 /*
1218 * Creates the tag library associated with the given uri namespace, and
1219 * returns it.
1220 *
1221 * @param prefix The prefix of the xmlns attribute
1222 * @param uri The uri namespace (value of the xmlns attribute)
1223 *
1224 * @return The tag library associated with the given uri namespace
1225 */
1226 private TagLibraryInfo getTaglibInfo(String prefix, String uri)
1227 throws JasperException {
1228
1229 TagLibraryInfo result = null;
1230
1231 if (uri.startsWith(URN_JSPTAGDIR)) {
1232 // uri (of the form "urn:jsptagdir:path") references tag file dir
1233 String tagdir = uri.substring(URN_JSPTAGDIR.length());
1234 result =
1235 new ImplicitTagLibraryInfo(
1236 ctxt,
1237 parserController,
1238 pageInfo,
1239 prefix,
1240 tagdir,
1241 err);
1242 } else {
1243 // uri references TLD file
1244 boolean isPlainUri = false;
1245 if (uri.startsWith(URN_JSPTLD)) {
1246 // uri is of the form "urn:jsptld:path"
1247 uri = uri.substring(URN_JSPTLD.length());
1248 } else {
1249 isPlainUri = true;
1250 }
1251
1252 String[] location = ctxt.getTldLocation(uri);
1253 if (location != null || !isPlainUri) {
1254 if (ctxt.getOptions().isCaching()) {
1255 result = (TagLibraryInfoImpl) ctxt.getOptions().getCache().get(uri);
1256 }
1257 if (result == null) {
1258 /*
1259 * If the uri value is a plain uri, a translation error must
1260 * not be generated if the uri is not found in the taglib map.
1261 * Instead, any actions in the namespace defined by the uri
1262 * value must be treated as uninterpreted.
1263 */
1264 result =
1265 new TagLibraryInfoImpl(
1266 ctxt,
1267 parserController,
1268 pageInfo,
1269 prefix,
1270 uri,
1271 location,
1272 err);
1273 if (ctxt.getOptions().isCaching()) {
1274 ctxt.getOptions().getCache().put(uri, result);
1275 }
1276 }
1277 }
1278 }
1279
1280 return result;
1281 }
1282
1283 /*
1284 * Ensures that the given body only contains nodes that are instances of
1285 * TemplateText.
1286 *
1287 * This check is performed only for the body of a scripting (that is:
1288 * declaration, scriptlet, or expression) element, after the end tag of a
1289 * scripting element has been reached.
1290 */
1291 private void checkScriptingBody(Node.ScriptingElement scriptingElem)
1292 throws SAXException {
1293 Node.Nodes body = scriptingElem.getBody();
1294 if (body != null) {
1295 int size = body.size();
1296 for (int i = 0; i < size; i++) {
1297 Node n = body.getNode(i);
1298 if (!(n instanceof Node.TemplateText)) {
1299 String elemType = SCRIPTLET_ACTION;
1300 if (scriptingElem instanceof Node.Declaration)
1301 elemType = DECLARATION_ACTION;
1302 if (scriptingElem instanceof Node.Expression)
1303 elemType = EXPRESSION_ACTION;
1304 String msg =
1305 Localizer.getMessage(
1306 "jsp.error.parse.xml.scripting.invalid.body",
1307 elemType);
1308 throw new SAXException(msg);
1309 }
1310 }
1311 }
1312 }
1313
1314 /*
1315 * Parses the given file included via an include directive.
1316 *
1317 * @param fname The path to the included resource, as specified by the
1318 * 'file' attribute of the include directive
1319 * @param parent The Node representing the include directive
1320 */
1321 private void processIncludeDirective(String fname, Node parent)
1322 throws SAXException {
1323
1324 if (fname == null) {
1325 return;
1326 }
1327
1328 try {
1329 parserController.parse(fname, parent, null);
1330 } catch (FileNotFoundException fnfe) {
1331 throw new SAXParseException(
1332 Localizer.getMessage("jsp.error.file.not.found", fname),
1333 locator,
1334 fnfe);
1335 } catch (Exception e) {
1336 throw new SAXException(e);
1337 }
1338 }
1339
1340 /*
1341 * Checks an element's given URI, qname, and attributes to see if any
1342 * of them hijack the 'jsp' prefix, that is, bind it to a namespace other
1343 * than http://java.sun.com/JSP/Page.
1344 *
1345 * @param uri The element's URI
1346 * @param qName The element's qname
1347 * @param attrs The element's attributes
1348 */
1349 private void checkPrefixes(String uri, String qName, Attributes attrs) {
1350
1351 checkPrefix(uri, qName);
1352
1353 int len = attrs.getLength();
1354 for (int i = 0; i < len; i++) {
1355 checkPrefix(attrs.getURI(i), attrs.getQName(i));
1356 }
1357 }
1358
1359 /*
1360 * Checks the given URI and qname to see if they hijack the 'jsp' prefix,
1361 * which would be the case if qName contained the 'jsp' prefix and
1362 * uri was different from http://java.sun.com/JSP/Page.
1363 *
1364 * @param uri The URI to check
1365 * @param qName The qname to check
1366 */
1367 private void checkPrefix(String uri, String qName) {
1368
1369 int index = qName.indexOf(':');
1370 if (index != -1) {
1371 String prefix = qName.substring(0, index);
1372 pageInfo.addPrefix(prefix);
1373 if ("jsp".equals(prefix) && !JSP_URI.equals(uri)) {
1374 pageInfo.setIsJspPrefixHijacked(true);
1375 }
1376 }
1377 }
1378
1379 /*
1380 * Gets SAXParser.
1381 *
1382 * @param validating Indicates whether the requested SAXParser should
1383 * be validating
1384 * @param jspDocParser The JSP document parser
1385 *
1386 * @return The SAXParser
1387 */
1388 private static SAXParser getSAXParser(
1389 boolean validating,
1390 JspDocumentParser jspDocParser)
1391 throws Exception {
1392
1393 SAXParserFactory factory = SAXParserFactory.newInstance();
1394 factory.setNamespaceAware(true);
1395
1396 // Preserve xmlns attributes
1397 factory.setFeature(
1398 "http://xml.org/sax/features/namespace-prefixes",
1399 true);
1400 factory.setValidating(validating);
1401 //factory.setFeature(
1402 // "http://xml.org/sax/features/validation",
1403 // validating);
1404
1405 // Configure the parser
1406 SAXParser saxParser = factory.newSAXParser();
1407 XMLReader xmlReader = saxParser.getXMLReader();
1408 xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY, jspDocParser);
1409 xmlReader.setErrorHandler(jspDocParser);
1410
1411 return saxParser;
1412 }
1413
1414 /*
1415 * Exception indicating that a DOCTYPE declaration is present, but
1416 * validation is turned off.
1417 */
1418 private static class EnableDTDValidationException
1419 extends SAXParseException {
1420
1421 EnableDTDValidationException(String message, Locator loc) {
1422 super(message, loc);
1423 }
1424 }
1425
1426 private static String getBodyType(Node.CustomTag custom) {
1427
1428 if (custom.getTagInfo() != null) {
1429 return custom.getTagInfo().getBodyContent();
1430 }
1431
1432 return custom.getTagFileInfo().getTagInfo().getBodyContent();
1433 }
1434
1435 private boolean isTagDependent(Node n) {
1436
1437 if (n instanceof Node.CustomTag) {
1438 String bodyType = getBodyType((Node.CustomTag) n);
1439 return
1440 TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType);
1441 }
1442 return false;
1443 }
1444 }