JspDocumentParser.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jasper.compiler;
import java.io.CharArrayWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.AccessController;
import java.util.Collection;
import javax.servlet.jsp.tagext.TagFileInfo;
import javax.servlet.jsp.tagext.TagInfo;
import javax.servlet.jsp.tagext.TagLibraryInfo;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.apache.tomcat.Jar;
import org.apache.tomcat.util.descriptor.DigesterFactory;
import org.apache.tomcat.util.descriptor.LocalResolver;
import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
import org.apache.tomcat.util.security.PrivilegedGetTccl;
import org.apache.tomcat.util.security.PrivilegedSetTccl;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DefaultHandler2;
import org.xml.sax.ext.EntityResolver2;
import org.xml.sax.helpers.AttributesImpl;
/**
* Class implementing a parser for a JSP document, that is, a JSP page in XML syntax.
*
* @author Jan Luehe
* @author Kin-man Chung
*/
class JspDocumentParser extends DefaultHandler2 implements TagConstants {
private static final String LEXICAL_HANDLER_PROPERTY = "http://xml.org/sax/properties/lexical-handler";
private static final String JSP_URI = "http://java.sun.com/JSP/Page";
private final ParserController parserController;
private final JspCompilationContext ctxt;
private final PageInfo pageInfo;
private final String path;
private StringBuilder charBuffer;
// Node representing the XML element currently being parsed
private Node current;
/*
* Outermost (in the nesting hierarchy) node whose body is declared to be scriptless. If a node's body is declared
* to be scriptless, all its nested nodes must be scriptless, too.
*/
private Node scriptlessBodyNode;
private Locator locator;
// Mark representing the start of the current element. Note
// that locator.getLineNumber() and locator.getColumnNumber()
// return the line and column numbers for the character
// immediately _following_ the current element. The underlying
// XMl parser eats white space that is not part of character
// data, so for Nodes that are not created from character data,
// this is the best we can do. But when we parse character data,
// we get an accurate starting location by starting with startMark
// as set by the previous element, and updating it as we advance
// through the characters.
private Mark startMark;
// Flag indicating whether we are inside DTD declarations
private boolean inDTD;
private boolean isValidating;
private final EntityResolver2 entityResolver;
private final ErrorDispatcher err;
private final boolean isTagFile;
private final boolean directivesOnly;
private boolean isTop;
// Nesting level of Tag dependent bodies
private int tagDependentNesting = 0;
// Flag set to delay incrementing tagDependentNesting until jsp:body
// is first encountered
private boolean tagDependentPending = false;
/*
* Constructor
*/
JspDocumentParser(ParserController pc, String path, boolean isTagFile, boolean directivesOnly) {
this.parserController = pc;
this.ctxt = pc.getJspCompilationContext();
this.pageInfo = pc.getCompiler().getPageInfo();
this.err = pc.getCompiler().getErrorDispatcher();
this.path = path;
this.isTagFile = isTagFile;
this.directivesOnly = directivesOnly;
this.isTop = true;
String blockExternalString = ctxt.getServletContext().getInitParameter(Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
boolean blockExternal;
if (blockExternalString == null) {
blockExternal = true;
} else {
blockExternal = Boolean.parseBoolean(blockExternalString);
}
this.entityResolver = new LocalResolver(DigesterFactory.SERVLET_API_PUBLIC_IDS,
DigesterFactory.SERVLET_API_SYSTEM_IDS, blockExternal);
}
/*
* Parses a JSP document by responding to SAX events.
*
* @throws JasperException
*/
public static Node.Nodes parse(ParserController pc, String path, Jar jar, Node parent, boolean isTagFile,
boolean directivesOnly, String pageEnc, String jspConfigPageEnc, boolean isEncodingSpecifiedInProlog,
boolean isBomPresent) throws JasperException {
JspDocumentParser jspDocParser = new JspDocumentParser(pc, path, isTagFile, directivesOnly);
Node.Nodes pageNodes = null;
try {
// Create dummy root and initialize it with given page encodings
Node.Root dummyRoot = new Node.Root(null, parent, true);
dummyRoot.setPageEncoding(pageEnc);
dummyRoot.setJspConfigPageEncoding(jspConfigPageEnc);
dummyRoot.setIsEncodingSpecifiedInProlog(isEncodingSpecifiedInProlog);
dummyRoot.setIsBomPresent(isBomPresent);
jspDocParser.current = dummyRoot;
if (parent == null) {
jspDocParser.addInclude(dummyRoot, jspDocParser.pageInfo.getIncludePrelude());
} else {
jspDocParser.isTop = false;
}
jspDocParser.isValidating = false;
// Parse the input
SAXParser saxParser = getSAXParser(false, jspDocParser);
InputSource source = JspUtil.getInputSource(path, jar, jspDocParser.ctxt);
try {
saxParser.parse(source, jspDocParser);
} catch (EnableDTDValidationException e) {
saxParser = getSAXParser(true, jspDocParser);
jspDocParser.isValidating = true;
try {
source.getByteStream().close();
} catch (IOException e2) {
// ignore
}
source = JspUtil.getInputSource(path, jar, jspDocParser.ctxt);
saxParser.parse(source, jspDocParser);
} finally {
try {
source.getByteStream().close();
} catch (IOException e) {
// ignore
}
}
if (parent == null) {
jspDocParser.addInclude(dummyRoot, jspDocParser.pageInfo.getIncludeCoda());
}
// Create Node.Nodes from dummy root
pageNodes = new Node.Nodes(dummyRoot);
} catch (IOException ioe) {
jspDocParser.err.jspError(ioe, "jsp.error.data.file.read", path);
} catch (SAXParseException e) {
jspDocParser.err.jspError(new Mark(jspDocParser.ctxt, path, e.getLineNumber(), e.getColumnNumber()), e,
e.getMessage());
} catch (Exception e) {
jspDocParser.err.jspError(e, "jsp.error.data.file.processing", path);
}
return pageNodes;
}
/*
* Processes the given list of included files.
*
* This is used to implement the include-prelude and include-coda subelements of the jsp-config element in web.xml
*/
private void addInclude(Node parent, Collection<String> files) throws SAXException {
if (files != null) {
for (String file : files) {
AttributesImpl attrs = new AttributesImpl();
attrs.addAttribute("", "file", "file", "CDATA", file);
// Create a dummy Include directive node
Node includeDir = new Node.IncludeDirective(attrs, null, // XXX
parent);
processIncludeDirective(file, includeDir);
}
}
}
@Override
public InputSource getExternalSubset(String name, String baseURI) throws SAXException, IOException {
return entityResolver.getExternalSubset(name, baseURI);
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return entityResolver.resolveEntity(publicId, systemId);
}
@Override
public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId)
throws SAXException, IOException {
// TODO URLs returned by the Jar abstraction may be of the form jar:jar:
// which is not a URL that can be resolved by the JRE. This should
// use the JarFactory to construct and return a valid InputSource.
return entityResolver.resolveEntity(name, publicId, baseURI, systemId);
}
/*
* Receives notification of the start of an element.
*
* This method assigns the given tag attributes to one of 3 buckets:
*
* - "xmlns" attributes that represent (standard or custom) tag libraries. - "xmlns" attributes that do not
* represent tag libraries. - all remaining attributes.
*
* For each "xmlns" attribute that represents a custom tag library, the corresponding TagLibraryInfo object is added
* to the set of custom tag libraries.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
AttributesImpl taglibAttrs = null;
AttributesImpl nonTaglibAttrs = null;
AttributesImpl nonTaglibXmlnsAttrs = null;
processChars();
checkPrefixes(uri, qName, attrs);
if (directivesOnly && !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) {
return;
}
// jsp:text must not have any subelements
if (current instanceof Node.JspText) {
throw new SAXParseException(Localizer.getMessage("jsp.error.text.has_subelement"), locator);
}
startMark = new Mark(ctxt, path, locator.getLineNumber(), locator.getColumnNumber());
/*
* Notice that due to a bug in the underlying SAX parser, the attributes must be enumerated in descending order.
*/
boolean isTaglib = false;
for (int i = attrs.getLength() - 1; i >= 0; i--) {
isTaglib = false;
String attrQName = attrs.getQName(i);
if (!attrQName.startsWith("xmlns")) {
if (nonTaglibAttrs == null) {
nonTaglibAttrs = new AttributesImpl();
}
nonTaglibAttrs.addAttribute(attrs.getURI(i), attrs.getLocalName(i), attrs.getQName(i), attrs.getType(i),
attrs.getValue(i));
} else {
if (attrQName.startsWith("xmlns:jsp")) {
isTaglib = true;
} else {
String attrUri = attrs.getValue(i);
// TaglibInfo for this uri already established in
// startPrefixMapping
isTaglib = pageInfo.hasTaglib(attrUri);
}
if (isTaglib) {
if (taglibAttrs == null) {
taglibAttrs = new AttributesImpl();
}
taglibAttrs.addAttribute(attrs.getURI(i), attrs.getLocalName(i), attrs.getQName(i),
attrs.getType(i), attrs.getValue(i));
} else {
if (nonTaglibXmlnsAttrs == null) {
nonTaglibXmlnsAttrs = new AttributesImpl();
}
nonTaglibXmlnsAttrs.addAttribute(attrs.getURI(i), attrs.getLocalName(i), attrs.getQName(i),
attrs.getType(i), attrs.getValue(i));
}
}
}
Node node = null;
if (tagDependentPending && JSP_URI.equals(uri) && localName.equals(BODY_ACTION)) {
tagDependentPending = false;
tagDependentNesting++;
current =
parseStandardAction(qName, localName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, startMark);
return;
}
if (tagDependentPending && JSP_URI.equals(uri) && localName.equals(ATTRIBUTE_ACTION)) {
current =
parseStandardAction(qName, localName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, startMark);
return;
}
if (tagDependentPending) {
tagDependentPending = false;
tagDependentNesting++;
}
if (tagDependentNesting > 0) {
node = new Node.UninterpretedTag(qName, localName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
startMark, current);
} else if (JSP_URI.equals(uri)) {
node = parseStandardAction(qName, localName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, startMark);
} else {
node = parseCustomAction(qName, localName, uri, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, startMark,
current);
if (node == null) {
node = new Node.UninterpretedTag(qName, localName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
startMark, current);
} else {
// custom action
String bodyType = getBodyType((Node.CustomTag) node);
if (scriptlessBodyNode == null && bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
scriptlessBodyNode = node;
} else if (TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType)) {
tagDependentPending = true;
}
}
}
current = node;
}
/*
* Receives notification of character data inside an element.
*
* The SAX does not call this method with all of the template text, but may invoke this method with chunks of it.
* This is a problem when we try to determine if the text contains only whitespaces, or when we are looking for an
* EL expression string. Therefore it is necessary to buffer and concatenate the chunks and process the concatenated
* text later (at beginTag and endTag)
*
* @param buf The characters
*
* @param offset The start position in the character array
*
* @param len The number of characters to use from the character array
*
* @throws SAXException
*/
@Override
public void characters(char[] buf, int offset, int len) {
if (charBuffer == null) {
charBuffer = new StringBuilder();
}
charBuffer.append(buf, offset, len);
}
private void processChars() throws SAXException {
if (charBuffer == null || directivesOnly) {
return;
}
/*
* JSP.6.1.1: All textual nodes that have only white space are to be dropped from the document, except for nodes
* in a jsp:text element, and any leading and trailing white-space-only textual nodes in a jsp:attribute whose
* 'trim' attribute is set to FALSE, which are to be kept verbatim. <p> JSP.6.2.3 defines white space
* characters.
*/
boolean isAllSpace = true;
if (!(current instanceof Node.JspText) && !(current instanceof Node.NamedAttribute)) {
for (int i = 0; i < charBuffer.length(); i++) {
char ch = charBuffer.charAt(i);
if (!(ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t')) {
isAllSpace = false;
break;
}
}
}
if (!isAllSpace && tagDependentPending) {
tagDependentPending = false;
tagDependentNesting++;
}
if (tagDependentNesting > 0 || pageInfo.isELIgnored() || current instanceof Node.ScriptingElement) {
if (charBuffer.length() > 0) {
@SuppressWarnings("unused")
Node unused = new Node.TemplateText(charBuffer.toString(), startMark, current);
}
startMark = new Mark(ctxt, path, locator.getLineNumber(), locator.getColumnNumber());
charBuffer = null;
return;
}
if ((current instanceof Node.JspText) || (current instanceof Node.NamedAttribute) || !isAllSpace) {
int line = startMark.getLineNumber();
int column = startMark.getColumnNumber();
CharArrayWriter ttext = new CharArrayWriter();
int lastCh = 0, elType = 0;
for (int i = 0; i < charBuffer.length(); i++) {
int ch = charBuffer.charAt(i);
if (ch == '\n') {
column = 1;
line++;
} else {
column++;
}
if ((lastCh == '$' || lastCh == '#') && ch == '{') {
elType = lastCh;
if (ttext.size() > 0) {
@SuppressWarnings("unused")
Node unused = new Node.TemplateText(ttext.toString(), startMark, current);
ttext.reset();
// We subtract two from the column number to
// account for the '[$,#]{' that we've already parsed
startMark = new Mark(ctxt, path, line, column - 2);
}
// following "${" || "#{" to first unquoted "}"
i++;
boolean singleQ = false;
boolean doubleQ = false;
lastCh = 0;
for (;; i++) {
if (i >= charBuffer.length()) {
throw new SAXParseException(
Localizer.getMessage("jsp.error.unterminated", (char) elType + "{"), locator);
}
ch = charBuffer.charAt(i);
if (ch == '\n') {
column = 1;
line++;
} else {
column++;
}
if (lastCh == '\\' && (singleQ || doubleQ)) {
ttext.write(ch);
lastCh = 0;
continue;
}
if (ch == '}') {
@SuppressWarnings("unused")
Node unused = new Node.ELExpression((char) elType, ttext.toString(), startMark, current);
ttext.reset();
startMark = new Mark(ctxt, path, line, column);
break;
}
if (ch == '"') {
doubleQ = !doubleQ;
} else if (ch == '\'') {
singleQ = !singleQ;
}
ttext.write(ch);
lastCh = ch;
}
} else if (lastCh == '\\' && (ch == '$' || ch == '#')) {
if (pageInfo.isELIgnored()) {
ttext.write('\\');
}
ttext.write(ch);
ch = 0; // Not start of EL anymore
} else {
if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
ttext.write(lastCh);
}
if (ch != '$' && ch != '#' && ch != '\\') {
ttext.write(ch);
}
}
lastCh = ch;
}
if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
ttext.write(lastCh);
}
if (ttext.size() > 0) {
@SuppressWarnings("unused")
Node unused = new Node.TemplateText(ttext.toString(), startMark, current);
}
}
startMark = new Mark(ctxt, path, locator.getLineNumber(), locator.getColumnNumber());
charBuffer = null;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
processChars();
if (directivesOnly && !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) {
return;
}
if (current instanceof Node.NamedAttribute) {
boolean isTrim = ((Node.NamedAttribute) current).isTrim();
Node.Nodes subElems = current.getBody();
for (int i = 0; subElems != null && i < subElems.size(); i++) {
Node subElem = subElems.getNode(i);
if (!(subElem instanceof Node.TemplateText)) {
continue;
}
// Ignore any whitespace (including spaces, carriage returns,
// line feeds, and tabs, that appear at the beginning and at
// the end of the body of the <jsp:attribute> action, if the
// action's 'trim' attribute is set to TRUE (default).
// In addition, any textual nodes in the <jsp:attribute> that
// have only white space are dropped from the document, with
// the exception of leading and trailing white-space-only
// textual nodes in a <jsp:attribute> whose 'trim' attribute
// is set to FALSE, which must be kept verbatim.
if (i == 0) {
if (isTrim) {
((Node.TemplateText) subElem).ltrim();
}
} else if (i == subElems.size() - 1) {
if (isTrim) {
((Node.TemplateText) subElem).rtrim();
}
} else {
if (((Node.TemplateText) subElem).isAllSpace()) {
subElems.remove(subElem);
}
}
}
} else if (current instanceof Node.ScriptingElement) {
checkScriptingBody((Node.ScriptingElement) current);
}
if (isTagDependent(current)) {
tagDependentNesting--;
}
if (scriptlessBodyNode != null && current.equals(scriptlessBodyNode)) {
scriptlessBodyNode = null;
}
if (current instanceof Node.CustomTag) {
String bodyType = getBodyType((Node.CustomTag) current);
if (TagInfo.BODY_CONTENT_EMPTY.equalsIgnoreCase(bodyType)) {
// Children - if any - must be JSP attributes
Node.Nodes children = current.getBody();
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Node child = children.getNode(i);
if (!(child instanceof Node.NamedAttribute)) {
throw new SAXParseException(
Localizer.getMessage("jasper.error.emptybodycontent.nonempty", current.qName),
locator);
}
}
}
}
}
if (current.getParent() != null) {
current = current.getParent();
}
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
/*
* See org.xml.sax.ext.LexicalHandler.
*/
@Override
public void comment(char[] buf, int offset, int len) throws SAXException {
processChars(); // Flush char buffer and remove white spaces
// ignore comments in the DTD
if (!inDTD) {
startMark = new Mark(ctxt, path, locator.getLineNumber(), locator.getColumnNumber());
@SuppressWarnings("unused")
Node unused = new Node.Comment(new String(buf, offset, len), startMark, current);
}
}
/*
* See org.xml.sax.ext.LexicalHandler.
*/
@Override
public void startCDATA() throws SAXException {
processChars(); // Flush char buffer and remove white spaces
startMark = new Mark(ctxt, path, locator.getLineNumber(), locator.getColumnNumber());
}
/*
* See org.xml.sax.ext.LexicalHandler.
*/
@Override
public void endCDATA() throws SAXException {
processChars(); // Flush char buffer and remove white spaces
}
/*
* See org.xml.sax.ext.LexicalHandler.
*/
@Override
public void startEntity(String name) throws SAXException {
// do nothing
}
/*
* See org.xml.sax.ext.LexicalHandler.
*/
@Override
public void endEntity(String name) throws SAXException {
// do nothing
}
/*
* See org.xml.sax.ext.LexicalHandler.
*/
@Override
public void startDTD(String name, String publicId, String systemId) throws SAXException {
if (!isValidating) {
fatalError(new EnableDTDValidationException("jsp.error.enable_dtd_validation", null));
}
inDTD = true;
}
/*
* See org.xml.sax.ext.LexicalHandler.
*/
@Override
public void endDTD() throws SAXException {
inDTD = false;
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
throw e;
}
@Override
public void error(SAXParseException e) throws SAXException {
throw e;
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
TagLibraryInfo taglibInfo;
if (directivesOnly && !(JSP_URI.equals(uri))) {
return;
}
try {
taglibInfo = getTaglibInfo(prefix, uri);
} catch (JasperException je) {
throw new SAXParseException(Localizer.getMessage("jsp.error.could.not.add.taglibraries"), locator, je);
}
if (taglibInfo != null) {
if (pageInfo.getTaglib(uri) == null) {
pageInfo.addTaglib(uri, taglibInfo);
}
pageInfo.pushPrefixMapping(prefix, uri);
} else {
pageInfo.pushPrefixMapping(prefix, null);
}
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
if (directivesOnly) {
String uri = pageInfo.getURI(prefix);
if (!JSP_URI.equals(uri)) {
return;
}
}
pageInfo.popPrefixMapping(prefix);
}
// *********************************************************************
// Private utility methods
private Node parseStandardAction(String qName, String localName, Attributes nonTaglibAttrs,
Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, Mark start) throws SAXException {
Node node = null;
if (localName.equals(ROOT_ACTION)) {
if (!(current instanceof Node.Root)) {
throw new SAXParseException(Localizer.getMessage("jsp.error.nested_jsproot"), locator);
}
node = new Node.JspRoot(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
if (isTop) {
pageInfo.setHasJspRoot(true);
}
} else if (localName.equals(PAGE_DIRECTIVE_ACTION)) {
if (isTagFile) {
throw new SAXParseException(Localizer.getMessage("jsp.error.action.istagfile", localName), locator);
}
node = new Node.PageDirective(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
String imports = nonTaglibAttrs.getValue("import");
// There can only be one 'import' attribute per page directive
if (imports != null) {
((Node.PageDirective) node).addImport(imports);
}
} else if (localName.equals(INCLUDE_DIRECTIVE_ACTION)) {
node = new Node.IncludeDirective(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
processIncludeDirective(nonTaglibAttrs.getValue("file"), node);
} else if (localName.equals(DECLARATION_ACTION)) {
if (scriptlessBodyNode != null) {
// We're nested inside a node whose body is
// declared to be scriptless
throw new SAXParseException(Localizer.getMessage("jsp.error.no.scriptlets", localName), locator);
}
node = new Node.Declaration(qName, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(SCRIPTLET_ACTION)) {
if (scriptlessBodyNode != null) {
// We're nested inside a node whose body is
// declared to be scriptless
throw new SAXParseException(Localizer.getMessage("jsp.error.no.scriptlets", localName), locator);
}
node = new Node.Scriptlet(qName, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(EXPRESSION_ACTION)) {
if (scriptlessBodyNode != null) {
// We're nested inside a node whose body is
// declared to be scriptless
throw new SAXParseException(Localizer.getMessage("jsp.error.no.scriptlets", localName), locator);
}
node = new Node.Expression(qName, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(USE_BEAN_ACTION)) {
node = new Node.UseBean(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(SET_PROPERTY_ACTION)) {
node = new Node.SetProperty(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(GET_PROPERTY_ACTION)) {
node = new Node.GetProperty(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(INCLUDE_ACTION)) {
node = new Node.IncludeAction(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(FORWARD_ACTION)) {
node = new Node.ForwardAction(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(PARAM_ACTION)) {
node = new Node.ParamAction(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(PARAMS_ACTION)) {
node = new Node.ParamsAction(qName, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(PLUGIN_ACTION)) {
node = new Node.PlugIn(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(TEXT_ACTION)) {
node = new Node.JspText(qName, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(BODY_ACTION)) {
node = new Node.JspBody(qName, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(ATTRIBUTE_ACTION)) {
node = new Node.NamedAttribute(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(OUTPUT_ACTION)) {
node = new Node.JspOutput(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(TAG_DIRECTIVE_ACTION)) {
if (!isTagFile) {
throw new SAXParseException(Localizer.getMessage("jsp.error.action.isnottagfile", localName), locator);
}
node = new Node.TagDirective(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
String imports = nonTaglibAttrs.getValue("import");
// There can only be one 'import' attribute per tag directive
if (imports != null) {
((Node.TagDirective) node).addImport(imports);
}
} else if (localName.equals(ATTRIBUTE_DIRECTIVE_ACTION)) {
if (!isTagFile) {
throw new SAXParseException(Localizer.getMessage("jsp.error.action.isnottagfile", localName), locator);
}
node = new Node.AttributeDirective(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(VARIABLE_DIRECTIVE_ACTION)) {
if (!isTagFile) {
throw new SAXParseException(Localizer.getMessage("jsp.error.action.isnottagfile", localName), locator);
}
node = new Node.VariableDirective(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(INVOKE_ACTION)) {
if (!isTagFile) {
throw new SAXParseException(Localizer.getMessage("jsp.error.action.isnottagfile", localName), locator);
}
node = new Node.InvokeAction(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(DOBODY_ACTION)) {
if (!isTagFile) {
throw new SAXParseException(Localizer.getMessage("jsp.error.action.isnottagfile", localName), locator);
}
node = new Node.DoBodyAction(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(ELEMENT_ACTION)) {
node = new Node.JspElement(qName, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else if (localName.equals(FALLBACK_ACTION)) {
node = new Node.FallBackAction(qName, nonTaglibXmlnsAttrs, taglibAttrs, start, current);
} else {
throw new SAXParseException(Localizer.getMessage("jsp.error.xml.badStandardAction", localName), locator);
}
return node;
}
/*
* Checks if the XML element with the given tag name is a custom action, and returns the corresponding Node object.
*/
private Node parseCustomAction(String qName, String localName, String uri, Attributes nonTaglibAttrs,
Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, Mark start, Node parent) throws SAXException {
// Check if this is a user-defined (custom) tag
TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
if (tagLibInfo == null) {
return null;
}
TagInfo tagInfo = tagLibInfo.getTag(localName);
TagFileInfo tagFileInfo = tagLibInfo.getTagFile(localName);
if (tagInfo == null && tagFileInfo == null) {
throw new SAXParseException(Localizer.getMessage("jsp.error.xml.bad_tag", localName, uri), locator);
}
Class<?> tagHandlerClass = null;
if (tagInfo != null) {
String handlerClassName = tagInfo.getTagClassName();
try {
tagHandlerClass = ctxt.getClassLoader().loadClass(handlerClassName);
} catch (Exception e) {
throw new SAXParseException(
Localizer.getMessage("jsp.error.loadclass.taghandler", handlerClassName, qName), locator, e);
}
}
String prefix = getPrefix(qName);
Node.CustomTag ret = null;
if (tagInfo != null) {
ret = new Node.CustomTag(qName, prefix, localName, uri, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
start, parent, tagInfo, tagHandlerClass);
} else {
ret = new Node.CustomTag(qName, prefix, localName, uri, nonTaglibAttrs, nonTaglibXmlnsAttrs, taglibAttrs,
start, parent, tagFileInfo);
}
return ret;
}
/*
* Creates the tag library associated with the given uri namespace, and returns it.
*
* @param prefix The prefix of the xmlns attribute
*
* @param uri The uri namespace (value of the xmlns attribute)
*
* @return The tag library associated with the given uri namespace
*/
private TagLibraryInfo getTaglibInfo(String prefix, String uri) throws JasperException {
TagLibraryInfo result = null;
if (uri.startsWith(URN_JSPTAGDIR)) {
// uri (of the form "urn:jsptagdir:path") references tag file dir
String tagdir = uri.substring(URN_JSPTAGDIR.length());
result = new ImplicitTagLibraryInfo(ctxt, parserController, pageInfo, prefix, tagdir, err);
} else {
// uri references TLD file
boolean isPlainUri = false;
if (uri.startsWith(URN_JSPTLD)) {
// uri is of the form "urn:jsptld:path"
uri = uri.substring(URN_JSPTLD.length());
} else {
isPlainUri = true;
}
TldResourcePath tldResourcePath = ctxt.getTldResourcePath(uri);
if (tldResourcePath != null || !isPlainUri) {
if (ctxt.getOptions().isCaching()) {
result = ctxt.getOptions().getCache().get(uri);
}
if (result == null) {
/*
* If the uri value is a plain uri, a translation error must not be generated if the uri is not
* found in the taglib map. Instead, any actions in the namespace defined by the uri value must be
* treated as uninterpreted.
*/
result = new TagLibraryInfoImpl(ctxt, parserController, pageInfo, prefix, uri, tldResourcePath,
err);
if (ctxt.getOptions().isCaching()) {
ctxt.getOptions().getCache().put(uri, result);
}
}
}
}
return result;
}
/*
* Ensures that the given body only contains nodes that are instances of TemplateText.
*
* This check is performed only for the body of a scripting (that is: declaration, scriptlet, or expression)
* element, after the end tag of a scripting element has been reached.
*/
private void checkScriptingBody(Node.ScriptingElement scriptingElem) throws SAXException {
Node.Nodes body = scriptingElem.getBody();
if (body != null) {
int size = body.size();
for (int i = 0; i < size; i++) {
Node n = body.getNode(i);
if (!(n instanceof Node.TemplateText)) {
String elemType = SCRIPTLET_ACTION;
if (scriptingElem instanceof Node.Declaration) {
elemType = DECLARATION_ACTION;
}
if (scriptingElem instanceof Node.Expression) {
elemType = EXPRESSION_ACTION;
}
String msg = Localizer.getMessage("jsp.error.parse.xml.scripting.invalid.body", elemType);
throw new SAXParseException(msg, locator);
}
}
}
}
/*
* Parses the given file included via an include directive.
*
* @param fname The path to the included resource, as specified by the 'file' attribute of the include directive
*
* @param parent The Node representing the include directive
*/
private void processIncludeDirective(String fname, Node parent) throws SAXException {
if (fname == null) {
return;
}
try {
parserController.parse(fname, parent, null);
} catch (FileNotFoundException fnfe) {
throw new SAXParseException(Localizer.getMessage("jsp.error.file.not.found", fname), locator, fnfe);
} catch (Exception e) {
throw new SAXParseException(e.getMessage(), locator, e);
}
}
/*
* Checks an element's given URI, qname, and attributes to see if any of them hijack the 'jsp' prefix, that is, bind
* it to a namespace other than http://java.sun.com/JSP/Page.
*
* @param uri The element's URI
*
* @param qName The element's qname
*
* @param attrs The element's attributes
*/
private void checkPrefixes(String uri, String qName, Attributes attrs) {
checkPrefix(uri, qName);
int len = attrs.getLength();
for (int i = 0; i < len; i++) {
checkPrefix(attrs.getURI(i), attrs.getQName(i));
}
}
/*
* Checks the given URI and qname to see if they hijack the 'jsp' prefix, which would be the case if qName contained
* the 'jsp' prefix and uri was different from http://java.sun.com/JSP/Page.
*
* @param uri The URI to check
*
* @param qName The qname to check
*/
private void checkPrefix(String uri, String qName) {
String prefix = getPrefix(qName);
if (prefix.length() > 0) {
pageInfo.addPrefix(prefix);
if ("jsp".equals(prefix) && !JSP_URI.equals(uri)) {
pageInfo.setIsJspPrefixHijacked(true);
}
}
}
private String getPrefix(String qName) {
int index = qName.indexOf(':');
if (index != -1) {
return qName.substring(0, index);
}
return "";
}
/*
* Gets SAXParser.
*
* @param validating Indicates whether the requested SAXParser should be validating
*
* @param jspDocParser The JSP document parser
*
* @return The SAXParser
*/
private static SAXParser getSAXParser(boolean validating, JspDocumentParser jspDocParser) throws Exception {
ClassLoader original;
Thread currentThread = Thread.currentThread();
if (Constants.IS_SECURITY_ENABLED) {
PrivilegedGetTccl pa = new PrivilegedGetTccl(currentThread);
original = AccessController.doPrivileged(pa);
} else {
original = currentThread.getContextClassLoader();
}
try {
if (Constants.IS_SECURITY_ENABLED) {
PrivilegedSetTccl pa = new PrivilegedSetTccl(currentThread, JspDocumentParser.class.getClassLoader());
AccessController.doPrivileged(pa);
} else {
currentThread.setContextClassLoader(JspDocumentParser.class.getClassLoader());
}
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
// Preserve xmlns attributes
factory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
factory.setValidating(validating);
if (validating) {
// Enable DTD validation
factory.setFeature("http://xml.org/sax/features/validation", true);
// Enable schema validation
factory.setFeature("http://apache.org/xml/features/validation/schema", true);
}
// Configure the parser
SAXParser saxParser = factory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY, jspDocParser);
xmlReader.setErrorHandler(jspDocParser);
return saxParser;
} finally {
if (Constants.IS_SECURITY_ENABLED) {
PrivilegedSetTccl pa = new PrivilegedSetTccl(currentThread, original);
AccessController.doPrivileged(pa);
} else {
currentThread.setContextClassLoader(original);
}
}
}
/*
* Exception indicating that a DOCTYPE declaration is present, but validation is turned off.
*/
private static class EnableDTDValidationException extends SAXParseException {
private static final long serialVersionUID = 1L;
EnableDTDValidationException(String message, Locator loc) {
super(message, loc);
}
@Override
public synchronized Throwable fillInStackTrace() {
// This class does not provide a stack trace
return this;
}
}
private static String getBodyType(Node.CustomTag custom) {
if (custom.getTagInfo() != null) {
return custom.getTagInfo().getBodyContent();
}
return custom.getTagFileInfo().getTagInfo().getBodyContent();
}
private boolean isTagDependent(Node n) {
if (n instanceof Node.CustomTag) {
String bodyType = getBodyType((Node.CustomTag) n);
return TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType);
}
return false;
}
}