View Javadoc

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