/* * 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.net.URL; import java.util.Iterator; import java.util.List; import javax.servlet.jsp.tagext.TagAttributeInfo; import javax.servlet.jsp.tagext.TagFileInfo; import javax.servlet.jsp.tagext.TagInfo; import javax.servlet.jsp.tagext.TagLibraryInfo; import org.apache.jasper.JasperException; import org.apache.jasper.JspCompilationContext; import org.xml.sax.Attributes; import org.xml.sax.helpers.AttributesImpl; /** * This class implements a parser for a JSP page (non-xml view). JSP page * grammar is included here for reference. The token '#' that appears in the * production indicates the current input token location in the production. * * @author Kin-man Chung * @author Shawn Bayern * @author Mark Roth */ class Parser implements TagConstants { private ParserController parserController; private JspCompilationContext ctxt; private JspReader reader; private String currentFile; private Mark start; private ErrorDispatcher err; private int scriptlessCount; private boolean isTagFile; private boolean directivesOnly; private URL jarFileUrl; private PageInfo pageInfo; // Virtual body content types, to make parsing a little easier. // These are not accessible from outside the parser. private static final String JAVAX_BODY_CONTENT_PARAM = "JAVAX_BODY_CONTENT_PARAM"; private static final String JAVAX_BODY_CONTENT_PLUGIN = "JAVAX_BODY_CONTENT_PLUGIN"; private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT = "JAVAX_BODY_CONTENT_TEMPLATE_TEXT"; /** * The constructor */ private Parser(ParserController pc, JspReader reader, boolean isTagFile, boolean directivesOnly, URL jarFileUrl) { this.parserController = pc; this.ctxt = pc.getJspCompilationContext(); this.pageInfo = pc.getCompiler().getPageInfo(); this.err = pc.getCompiler().getErrorDispatcher(); this.reader = reader; this.currentFile = reader.mark().getFile(); this.scriptlessCount = 0; this.isTagFile = isTagFile; this.directivesOnly = directivesOnly; this.jarFileUrl = jarFileUrl; start = reader.mark(); } /** * The main entry for Parser * * @param pc * The ParseController, use for getting other objects in compiler * and for parsing included pages * @param reader * To read the page * @param parent * The parent node to this page, null for top level page * @return list of nodes representing the parsed page */ public static Node.Nodes parse(ParserController pc, JspReader reader, Node parent, boolean isTagFile, boolean directivesOnly, URL jarFileUrl, String pageEnc, String jspConfigPageEnc, boolean isDefaultPageEncoding, boolean isBomPresent) throws JasperException { Parser parser = new Parser(pc, reader, isTagFile, directivesOnly, jarFileUrl); Node.Root root = new Node.Root(reader.mark(), parent, false); root.setPageEncoding(pageEnc); root.setJspConfigPageEncoding(jspConfigPageEnc); root.setIsDefaultPageEncoding(isDefaultPageEncoding); root.setIsBomPresent(isBomPresent); // For the Top level page, add include-prelude and include-coda PageInfo pageInfo = pc.getCompiler().getPageInfo(); if (parent == null && !isTagFile) { parser.addInclude(root, pageInfo.getIncludePrelude()); } if (directivesOnly) { parser.parseFileDirectives(root); } else { while (reader.hasMoreInput()) { parser.parseElements(root); } } if (parent == null && !isTagFile) { parser.addInclude(root, pageInfo.getIncludeCoda()); } Node.Nodes page = new Node.Nodes(root); return page; } /** * Attributes ::= (S Attribute)* S? */ Attributes parseAttributes() throws JasperException { AttributesImpl attrs = new AttributesImpl(); reader.skipSpaces(); while (parseAttribute(attrs)) reader.skipSpaces(); return attrs; } /** * Parse Attributes for a reader, provided for external use */ public static Attributes parseAttributes(ParserController pc, JspReader reader) throws JasperException { Parser tmpParser = new Parser(pc, reader, false, false, null); return tmpParser.parseAttributes(); } /** * Attribute ::= Name S? Eq S? ( '"<%=' RTAttributeValueDouble | '"' * AttributeValueDouble | "'<%=" RTAttributeValueSingle | "'" * AttributeValueSingle } Note: JSP and XML spec does not allow while spaces * around Eq. It is added to be backward compatible with Tomcat, and with * other xml parsers. */ private boolean parseAttribute(AttributesImpl attrs) throws JasperException { // Get the qualified name String qName = parseName(); if (qName == null) return false; // Determine prefix and local name components String localName = qName; String uri = ""; int index = qName.indexOf(':'); if (index != -1) { String prefix = qName.substring(0, index); uri = pageInfo.getURI(prefix); if (uri == null) { err.jspError(reader.mark(), "jsp.error.attribute.invalidPrefix", prefix); } localName = qName.substring(index + 1); } reader.skipSpaces(); if (!reader.matches("=")) err.jspError(reader.mark(), "jsp.error.attribute.noequal"); reader.skipSpaces(); char quote = (char) reader.nextChar(); if (quote != '\'' && quote != '"') err.jspError(reader.mark(), "jsp.error.attribute.noquote"); String watchString = ""; if (reader.matches("<%=")) watchString = "%>"; watchString = watchString + quote; String attrValue = parseAttributeValue(watchString); attrs.addAttribute(uri, localName, qName, "CDATA", attrValue); return true; } /** * Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')* */ private String parseName() throws JasperException { char ch = (char) reader.peekChar(); if (Character.isLetter(ch) || ch == '_' || ch == ':') { StringBuffer buf = new StringBuffer(); buf.append(ch); reader.nextChar(); ch = (char) reader.peekChar(); while (Character.isLetter(ch) || Character.isDigit(ch) || ch == '.' || ch == '_' || ch == '-' || ch == ':') { buf.append(ch); reader.nextChar(); ch = (char) reader.peekChar(); } return buf.toString(); } return null; } /** * AttributeValueDouble ::= (QuotedChar - '"')* ('"' | <TRANSLATION_ERROR>) * RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"') * ('%>"' | TRANSLATION_ERROR) */ private String parseAttributeValue(String watch) throws JasperException { Mark start = reader.mark(); Mark stop = reader.skipUntilIgnoreEsc(watch); if (stop == null) { err.jspError(start, "jsp.error.attribute.unterminated", watch); } String ret = null; try { char quote = watch.charAt(watch.length() - 1); // If watch is longer than 1 character this is a scripting // expression and EL is always ignored boolean isElIgnored = pageInfo.isELIgnored() || watch.length() > 1; ret = AttributeParser.getUnquoted(reader.getText(start, stop), quote, isElIgnored, pageInfo.isDeferredSyntaxAllowedAsLiteral()); } catch (IllegalArgumentException iae) { err.jspError(start, iae.getMessage()); } if (watch.length() == 1) // quote return ret; // Put back delimiter '<%=' and '%>', since they are needed if the // attribute does not allow RTexpression. return "<%=" + ret + "%>"; } private String parseScriptText(String tx) { CharArrayWriter cw = new CharArrayWriter(); int size = tx.length(); int i = 0; while (i < size) { char ch = tx.charAt(i); if (i + 2 < size && ch == '%' && tx.charAt(i + 1) == '\\' && tx.charAt(i + 2) == '>') { cw.write('%'); cw.write('>'); i += 3; } else { cw.write(ch); ++i; } } cw.close(); return cw.toString(); } /* * Invokes parserController to parse the included page */ private void processIncludeDirective(String file, Node parent) throws JasperException { if (file == null) { return; } try { parserController.parse(file, parent, jarFileUrl); } catch (FileNotFoundException ex) { err.jspError(start, "jsp.error.file.not.found", file); } catch (Exception ex) { err.jspError(start, ex.getMessage()); } } /* * Parses a page directive with the following syntax: PageDirective ::= ( S * Attribute)* */ private void parsePageDirective(Node parent) throws JasperException { Attributes attrs = parseAttributes(); Node.PageDirective n = new Node.PageDirective(attrs, start, parent); /* * A page directive may contain multiple 'import' attributes, each of * which consists of a comma-separated list of package names. Store each * list with the node, where it is parsed. */ for (int i = 0; i < attrs.getLength(); i++) { if ("import".equals(attrs.getQName(i))) { n.addImport(attrs.getValue(i)); } } } /* * Parses an include directive with the following syntax: IncludeDirective * ::= ( S Attribute)* */ private void parseIncludeDirective(Node parent) throws JasperException { Attributes attrs = parseAttributes(); // Included file expanded here Node includeNode = new Node.IncludeDirective(attrs, start, parent); processIncludeDirective(attrs.getValue("file"), includeNode); } /** * Add a list of files. This is used for implementing include-prelude and * include-coda of jsp-config element in web.xml */ private void addInclude(Node parent, List files) throws JasperException { if (files != null) { Iterator iter = files.iterator(); while (iter.hasNext()) { String file = (String) iter.next(); AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute("", "file", "file", "CDATA", file); // Create a dummy Include directive node Node includeNode = new Node.IncludeDirective(attrs, reader .mark(), parent); processIncludeDirective(file, includeNode); } } } /* * Parses a taglib directive with the following syntax: Directive ::= ( S * Attribute)* */ private void parseTaglibDirective(Node parent) throws JasperException { Attributes attrs = parseAttributes(); String uri = attrs.getValue("uri"); String prefix = attrs.getValue("prefix"); if (prefix != null) { Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix); if (prevMark != null) { err.jspError(reader.mark(), "jsp.error.prefix.use_before_dcl", prefix, prevMark.getFile(), "" + prevMark.getLineNumber()); } if (uri != null) { String uriPrev = pageInfo.getURI(prefix); if (uriPrev != null && !uriPrev.equals(uri)) { err.jspError(reader.mark(), "jsp.error.prefix.refined", prefix, uri, uriPrev); } if (pageInfo.getTaglib(uri) == null) { TagLibraryInfoImpl impl = null; if (ctxt.getOptions().isCaching()) { impl = (TagLibraryInfoImpl) ctxt.getOptions() .getCache().get(uri); } if (impl == null) { String[] location = ctxt.getTldLocation(uri); impl = new TagLibraryInfoImpl(ctxt, parserController, pageInfo, prefix, uri, location, err, reader.mark()); if (ctxt.getOptions().isCaching()) { ctxt.getOptions().getCache().put(uri, impl); } } else { // Current compilation context needs location of cached // tag files for (TagFileInfo info : impl.getTagFiles()) { ctxt.setTagFileJarUrl(info.getPath(), ctxt.getTagFileJarUrl()); } } pageInfo.addTaglib(uri, impl); } pageInfo.addPrefixMapping(prefix, uri); } else { String tagdir = attrs.getValue("tagdir"); if (tagdir != null) { String urnTagdir = URN_JSPTAGDIR + tagdir; if (pageInfo.getTaglib(urnTagdir) == null) { pageInfo.addTaglib(urnTagdir, new ImplicitTagLibraryInfo(ctxt, parserController, pageInfo, prefix, tagdir, err)); } pageInfo.addPrefixMapping(prefix, urnTagdir); } } } new Node.TaglibDirective(attrs, start, parent); } /* * Parses a directive with the following syntax: Directive ::= S? ( 'page' * PageDirective | 'include' IncludeDirective | 'taglib' TagLibDirective) S? * '%>' * * TagDirective ::= S? ('tag' PageDirective | 'include' IncludeDirective | * 'taglib' TagLibDirective) | 'attribute AttributeDirective | 'variable * VariableDirective S? '%>' */ private void parseDirective(Node parent) throws JasperException { reader.skipSpaces(); String directive = null; if (reader.matches("page")) { directive = "<%@ page"; if (isTagFile) { err.jspError(reader.mark(), "jsp.error.directive.istagfile", directive); } parsePageDirective(parent); } else if (reader.matches("include")) { directive = "<%@ include"; parseIncludeDirective(parent); } else if (reader.matches("taglib")) { if (directivesOnly) { // No need to get the tagLibInfo objects. This alos suppresses // parsing of any tag files used in this tag file. return; } directive = "<%@ taglib"; parseTaglibDirective(parent); } else if (reader.matches("tag")) { directive = "<%@ tag"; if (!isTagFile) { err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", directive); } parseTagDirective(parent); } else if (reader.matches("attribute")) { directive = "<%@ attribute"; if (!isTagFile) { err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", directive); } parseAttributeDirective(parent); } else if (reader.matches("variable")) { directive = "<%@ variable"; if (!isTagFile) { err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", directive); } parseVariableDirective(parent); } else { err.jspError(reader.mark(), "jsp.error.invalid.directive"); } reader.skipSpaces(); if (!reader.matches("%>")) { err.jspError(start, "jsp.error.unterminated", directive); } } /* * Parses a directive with the following syntax: * * XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList S? ( '/>' | ( * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>' * S? ETag ) ) | <TRANSLATION_ERROR> * * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList S? ( '/>' | ( * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>' * S? ETag ) ) | ( 'attribute' AttributeDirectiveAttrList S? ( '/>' | ( '>' * S? ETag ) ) | ( 'variable' VariableDirectiveAttrList S? ( '/>' | ( '>' S? * ETag ) ) ) | <TRANSLATION_ERROR> */ private void parseXMLDirective(Node parent) throws JasperException { reader.skipSpaces(); String eTag = null; if (reader.matches("page")) { eTag = "jsp:directive.page"; if (isTagFile) { err.jspError(reader.mark(), "jsp.error.directive.istagfile", "<" + eTag); } parsePageDirective(parent); } else if (reader.matches("include")) { eTag = "jsp:directive.include"; parseIncludeDirective(parent); } else if (reader.matches("tag")) { eTag = "jsp:directive.tag"; if (!isTagFile) { err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", "<" + eTag); } parseTagDirective(parent); } else if (reader.matches("attribute")) { eTag = "jsp:directive.attribute"; if (!isTagFile) { err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", "<" + eTag); } parseAttributeDirective(parent); } else if (reader.matches("variable")) { eTag = "jsp:directive.variable"; if (!isTagFile) { err.jspError(reader.mark(), "jsp.error.directive.isnottagfile", "<" + eTag); } parseVariableDirective(parent); } else { err.jspError(reader.mark(), "jsp.error.invalid.directive"); } reader.skipSpaces(); if (reader.matches(">")) { reader.skipSpaces(); if (!reader.matchesETag(eTag)) { err.jspError(start, "jsp.error.unterminated", "<" + eTag); } } else if (!reader.matches("/>")) { err.jspError(start, "jsp.error.unterminated", "<" + eTag); } } /* * Parses a tag directive with the following syntax: PageDirective ::= ( S * Attribute)* */ private void parseTagDirective(Node parent) throws JasperException { Attributes attrs = parseAttributes(); Node.TagDirective n = new Node.TagDirective(attrs, start, parent); /* * A page directive may contain multiple 'import' attributes, each of * which consists of a comma-separated list of package names. Store each * list with the node, where it is parsed. */ for (int i = 0; i < attrs.getLength(); i++) { if ("import".equals(attrs.getQName(i))) { n.addImport(attrs.getValue(i)); } } } /* * Parses a attribute directive with the following syntax: * AttributeDirective ::= ( S Attribute)* */ private void parseAttributeDirective(Node parent) throws JasperException { Attributes attrs = parseAttributes(); Node.AttributeDirective n = new Node.AttributeDirective(attrs, start, parent); } /* * Parses a variable directive with the following syntax: PageDirective ::= ( * S Attribute)* */ private void parseVariableDirective(Node parent) throws JasperException { Attributes attrs = parseAttributes(); Node.VariableDirective n = new Node.VariableDirective(attrs, start, parent); } /* * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>' */ private void parseComment(Node parent) throws JasperException { start = reader.mark(); Mark stop = reader.skipUntil("--%>"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "<%--"); } new Node.Comment(reader.getText(start, stop), start, parent); } /* * DeclarationBody ::= (Char* - (char* '%>')) '%>' */ private void parseDeclaration(Node parent) throws JasperException { start = reader.mark(); Mark stop = reader.skipUntil("%>"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "<%!"); } new Node.Declaration(parseScriptText(reader.getText(start, stop)), start, parent); } /* * XMLDeclarationBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<')) * CDSect?)* ETag | <TRANSLATION_ERROR> CDSect ::= CDStart CData CDEnd * CDStart ::= '<![CDATA[' CData ::= (Char* - (Char* ']]>' Char*)) CDEnd * ::= ']]>' */ private void parseXMLDeclaration(Node parent) throws JasperException { reader.skipSpaces(); if (!reader.matches("/>")) { if (!reader.matches(">")) { err.jspError(start, "jsp.error.unterminated", "<jsp:declaration>"); } Mark stop; String text; while (true) { start = reader.mark(); stop = reader.skipUntil("<"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "<jsp:declaration>"); } text = parseScriptText(reader.getText(start, stop)); new Node.Declaration(text, start, parent); if (reader.matches("![CDATA[")) { start = reader.mark(); stop = reader.skipUntil("]]>"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "CDATA"); } text = parseScriptText(reader.getText(start, stop)); new Node.Declaration(text, start, parent); } else { break; } } if (!reader.matchesETagWithoutLessThan("jsp:declaration")) { err.jspError(start, "jsp.error.unterminated", "<jsp:declaration>"); } } } /* * ExpressionBody ::= (Char* - (char* '%>')) '%>' */ private void parseExpression(Node parent) throws JasperException { start = reader.mark(); Mark stop = reader.skipUntil("%>"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "<%="); } new Node.Expression(parseScriptText(reader.getText(start, stop)), start, parent); } /* * XMLExpressionBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<')) * CDSect?)* ETag ) | <TRANSLATION_ERROR> */ private void parseXMLExpression(Node parent) throws JasperException { reader.skipSpaces(); if (!reader.matches("/>")) { if (!reader.matches(">")) { err.jspError(start, "jsp.error.unterminated", "<jsp:expression>"); } Mark stop; String text; while (true) { start = reader.mark(); stop = reader.skipUntil("<"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "<jsp:expression>"); } text = parseScriptText(reader.getText(start, stop)); new Node.Expression(text, start, parent); if (reader.matches("![CDATA[")) { start = reader.mark(); stop = reader.skipUntil("]]>"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "CDATA"); } text = parseScriptText(reader.getText(start, stop)); new Node.Expression(text, start, parent); } else { break; } } if (!reader.matchesETagWithoutLessThan("jsp:expression")) { err.jspError(start, "jsp.error.unterminated", "<jsp:expression>"); } } } /* * ELExpressionBody (following "${" to first unquoted "}") // XXX add formal * production and confirm implementation against it, // once it's decided */ private void parseELExpression(Node parent, char type) throws JasperException { start = reader.mark(); Mark last = null; boolean singleQuoted = false, doubleQuoted = false; int currentChar; do { // XXX could move this logic to JspReader last = reader.mark(); // XXX somewhat wasteful currentChar = reader.nextChar(); while (currentChar == '\\' && (singleQuoted || doubleQuoted)) { // skip character following '\' within quotes reader.nextChar(); currentChar = reader.nextChar(); } if (currentChar == -1) err.jspError(start, "jsp.error.unterminated", type + "{"); if (currentChar == '"' && !singleQuoted) { doubleQuoted = !doubleQuoted; } else if (currentChar == '\'' && !doubleQuoted) { singleQuoted = !singleQuoted; } } while (currentChar != '}' || (singleQuoted || doubleQuoted)); new Node.ELExpression(type, reader.getText(start, last), start, parent); } /* * ScriptletBody ::= (Char* - (char* '%>')) '%>' */ private void parseScriptlet(Node parent) throws JasperException { start = reader.mark(); Mark stop = reader.skipUntil("%>"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "<%"); } new Node.Scriptlet(parseScriptText(reader.getText(start, stop)), start, parent); } /* * XMLScriptletBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<')) * CDSect?)* ETag ) | <TRANSLATION_ERROR> */ private void parseXMLScriptlet(Node parent) throws JasperException { reader.skipSpaces(); if (!reader.matches("/>")) { if (!reader.matches(">")) { err.jspError(start, "jsp.error.unterminated", "<jsp:scriptlet>"); } Mark stop; String text; while (true) { start = reader.mark(); stop = reader.skipUntil("<"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "<jsp:scriptlet>"); } text = parseScriptText(reader.getText(start, stop)); new Node.Scriptlet(text, start, parent); if (reader.matches("![CDATA[")) { start = reader.mark(); stop = reader.skipUntil("]]>"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "CDATA"); } text = parseScriptText(reader.getText(start, stop)); new Node.Scriptlet(text, start, parent); } else { break; } } if (!reader.matchesETagWithoutLessThan("jsp:scriptlet")) { err.jspError(start, "jsp.error.unterminated", "<jsp:scriptlet>"); } } } /** * Param ::= '<jsp:param' S Attributes S? EmptyBody S? */ private void parseParam(Node parent) throws JasperException { if (!reader.matches("<jsp:param")) { err.jspError(reader.mark(), "jsp.error.paramexpected"); } Attributes attrs = parseAttributes(); reader.skipSpaces(); Node paramActionNode = new Node.ParamAction(attrs, start, parent); parseEmptyBody(paramActionNode, "jsp:param"); reader.skipSpaces(); } /* * For Include: StdActionContent ::= Attributes ParamBody * * ParamBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' * (JspBodyParam | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? Param* ETag ) * * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute' * NamedAttributes ETag ) * * JspBodyParam ::= S? '>' Param* '</jsp:body>' */ private void parseInclude(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node includeNode = new Node.IncludeAction(attrs, start, parent); parseOptionalBody(includeNode, "jsp:include", JAVAX_BODY_CONTENT_PARAM); } /* * For Forward: StdActionContent ::= Attributes ParamBody */ private void parseForward(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node forwardNode = new Node.ForwardAction(attrs, start, parent); parseOptionalBody(forwardNode, "jsp:forward", JAVAX_BODY_CONTENT_PARAM); } private void parseInvoke(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node invokeNode = new Node.InvokeAction(attrs, start, parent); parseEmptyBody(invokeNode, "jsp:invoke"); } private void parseDoBody(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node doBodyNode = new Node.DoBodyAction(attrs, start, parent); parseEmptyBody(doBodyNode, "jsp:doBody"); } private void parseElement(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node elementNode = new Node.JspElement(attrs, start, parent); parseOptionalBody(elementNode, "jsp:element", TagInfo.BODY_CONTENT_JSP); } /* * For GetProperty: StdActionContent ::= Attributes EmptyBody */ private void parseGetProperty(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node getPropertyNode = new Node.GetProperty(attrs, start, parent); parseOptionalBody(getPropertyNode, "jsp:getProperty", TagInfo.BODY_CONTENT_EMPTY); } /* * For SetProperty: StdActionContent ::= Attributes EmptyBody */ private void parseSetProperty(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node setPropertyNode = new Node.SetProperty(attrs, start, parent); parseOptionalBody(setPropertyNode, "jsp:setProperty", TagInfo.BODY_CONTENT_EMPTY); } /* * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute' * NamedAttributes ETag ) */ private void parseEmptyBody(Node parent, String tag) throws JasperException { if (reader.matches("/>")) { // Done } else if (reader.matches(">")) { if (reader.matchesETag(tag)) { // Done } else if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) { // Parse the one or more named attribute nodes parseNamedAttributes(parent); if (!reader.matchesETag(tag)) { // Body not allowed err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only", "<" + tag); } } else { err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only", "<" + tag); } } else { err.jspError(reader.mark(), "jsp.error.unterminated", "<" + tag); } } /* * For UseBean: StdActionContent ::= Attributes OptionalBody */ private void parseUseBean(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node useBeanNode = new Node.UseBean(attrs, start, parent); parseOptionalBody(useBeanNode, "jsp:useBean", TagInfo.BODY_CONTENT_JSP); } /* * Parses OptionalBody, but also reused to parse bodies for plugin and param * since the syntax is identical (the only thing that differs substantially * is how to process the body, and thus we accept the body type as a * parameter). * * OptionalBody ::= EmptyBody | ActionBody * * ScriptlessOptionalBody ::= EmptyBody | ScriptlessActionBody * * TagDependentOptionalBody ::= EmptyBody | TagDependentActionBody * * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute' * NamedAttributes ETag ) * * ActionBody ::= JspAttributeAndBody | ( '>' Body ETag ) * * ScriptlessActionBody ::= JspAttributeAndBody | ( '>' ScriptlessBody ETag ) * * TagDependentActionBody ::= JspAttributeAndBody | ( '>' TagDependentBody * ETag ) * */ private void parseOptionalBody(Node parent, String tag, String bodyType) throws JasperException { if (reader.matches("/>")) { // EmptyBody return; } if (!reader.matches(">")) { err.jspError(reader.mark(), "jsp.error.unterminated", "<" + tag); } if (reader.matchesETag(tag)) { // EmptyBody return; } if (!parseJspAttributeAndBody(parent, tag, bodyType)) { // Must be ( '>' # Body ETag ) parseBody(parent, tag, bodyType); } } /** * Attempts to parse 'JspAttributeAndBody' production. Returns true if it * matched, or false if not. Assumes EmptyBody is okay as well. * * JspAttributeAndBody ::= ( '>' # S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' ( * JspBodyBody | <TRANSLATION_ERROR> ) S? ETag ) */ private boolean parseJspAttributeAndBody(Node parent, String tag, String bodyType) throws JasperException { boolean result = false; if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) { // May be an EmptyBody, depending on whether // There's a "<jsp:body" before the ETag // First, parse <jsp:attribute> elements: parseNamedAttributes(parent); result = true; } if (reader.matchesOptionalSpacesFollowedBy("<jsp:body")) { // ActionBody parseJspBody(parent, bodyType); reader.skipSpaces(); if (!reader.matchesETag(tag)) { err.jspError(reader.mark(), "jsp.error.unterminated", "<" + tag); } result = true; } else if (result && !reader.matchesETag(tag)) { // If we have <jsp:attribute> but something other than // <jsp:body> or the end tag, translation error. err.jspError(reader.mark(), "jsp.error.jspbody.required", "<" + tag); } return result; } /* * Params ::= `>' S? ( ( `<jsp:body>' ( ( S? Param+ S? `</jsp:body>' ) | * <TRANSLATION_ERROR> ) ) | Param+ ) '</jsp:params>' */ private void parseJspParams(Node parent) throws JasperException { Node jspParamsNode = new Node.ParamsAction(start, parent); parseOptionalBody(jspParamsNode, "jsp:params", JAVAX_BODY_CONTENT_PARAM); } /* * Fallback ::= '/>' | ( `>' S? `<jsp:body>' ( ( S? ( Char* - ( Char* `</jsp:body>' ) ) `</jsp:body>' * S? ) | <TRANSLATION_ERROR> ) `</jsp:fallback>' ) | ( '>' ( Char* - ( * Char* '</jsp:fallback>' ) ) '</jsp:fallback>' ) */ private void parseFallBack(Node parent) throws JasperException { Node fallBackNode = new Node.FallBackAction(start, parent); parseOptionalBody(fallBackNode, "jsp:fallback", JAVAX_BODY_CONTENT_TEMPLATE_TEXT); } /* * For Plugin: StdActionContent ::= Attributes PluginBody * * PluginBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' ( * JspBodyPluginTags | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? PluginTags * ETag ) * * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute' * NamedAttributes ETag ) * */ private void parsePlugin(Node parent) throws JasperException { Attributes attrs = parseAttributes(); reader.skipSpaces(); Node pluginNode = new Node.PlugIn(attrs, start, parent); parseOptionalBody(pluginNode, "jsp:plugin", JAVAX_BODY_CONTENT_PLUGIN); } /* * PluginTags ::= ( '<jsp:params' Params S? )? ( '<jsp:fallback' Fallback? * S? )? */ private void parsePluginTags(Node parent) throws JasperException { reader.skipSpaces(); if (reader.matches("<jsp:params")) { parseJspParams(parent); reader.skipSpaces(); } if (reader.matches("<jsp:fallback")) { parseFallBack(parent); reader.skipSpaces(); } } /* * StandardAction ::= 'include' StdActionContent | 'forward' * StdActionContent | 'invoke' StdActionContent | 'doBody' StdActionContent | * 'getProperty' StdActionContent | 'setProperty' StdActionContent | * 'useBean' StdActionContent | 'plugin' StdActionContent | 'element' * StdActionContent */ private void parseStandardAction(Node parent) throws JasperException { Mark start = reader.mark(); if (reader.matches(INCLUDE_ACTION)) { parseInclude(parent); } else if (reader.matches(FORWARD_ACTION)) { parseForward(parent); } else if (reader.matches(INVOKE_ACTION)) { if (!isTagFile) { err.jspError(reader.mark(), "jsp.error.action.isnottagfile", "<jsp:invoke"); } parseInvoke(parent); } else if (reader.matches(DOBODY_ACTION)) { if (!isTagFile) { err.jspError(reader.mark(), "jsp.error.action.isnottagfile", "<jsp:doBody"); } parseDoBody(parent); } else if (reader.matches(GET_PROPERTY_ACTION)) { parseGetProperty(parent); } else if (reader.matches(SET_PROPERTY_ACTION)) { parseSetProperty(parent); } else if (reader.matches(USE_BEAN_ACTION)) { parseUseBean(parent); } else if (reader.matches(PLUGIN_ACTION)) { parsePlugin(parent); } else if (reader.matches(ELEMENT_ACTION)) { parseElement(parent); } else if (reader.matches(ATTRIBUTE_ACTION)) { err.jspError(start, "jsp.error.namedAttribute.invalidUse"); } else if (reader.matches(BODY_ACTION)) { err.jspError(start, "jsp.error.jspbody.invalidUse"); } else if (reader.matches(FALLBACK_ACTION)) { err.jspError(start, "jsp.error.fallback.invalidUse"); } else if (reader.matches(PARAMS_ACTION)) { err.jspError(start, "jsp.error.params.invalidUse"); } else if (reader.matches(PARAM_ACTION)) { err.jspError(start, "jsp.error.param.invalidUse"); } else if (reader.matches(OUTPUT_ACTION)) { err.jspError(start, "jsp.error.jspoutput.invalidUse"); } else { err.jspError(start, "jsp.error.badStandardAction"); } } /* * # '<' CustomAction CustomActionBody * * CustomAction ::= TagPrefix ':' CustomActionName * * TagPrefix ::= Name * * CustomActionName ::= Name * * CustomActionBody ::= ( Attributes CustomActionEnd ) | <TRANSLATION_ERROR> * * Attributes ::= ( S Attribute )* S? * * CustomActionEnd ::= CustomActionTagDependent | CustomActionJSPContent | * CustomActionScriptlessContent * * CustomActionTagDependent ::= TagDependentOptionalBody * * CustomActionJSPContent ::= OptionalBody * * CustomActionScriptlessContent ::= ScriptlessOptionalBody */ private boolean parseCustomTag(Node parent) throws JasperException { if (reader.peekChar() != '<') { return false; } // Parse 'CustomAction' production (tag prefix and custom action name) reader.nextChar(); // skip '<' String tagName = reader.parseToken(false); int i = tagName.indexOf(':'); if (i == -1) { reader.reset(start); return false; } String prefix = tagName.substring(0, i); String shortTagName = tagName.substring(i + 1); // Check if this is a user-defined tag. String uri = pageInfo.getURI(prefix); if (uri == null) { reader.reset(start); // Remember the prefix for later error checking pageInfo.putNonCustomTagPrefix(prefix, reader.mark()); return false; } TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri); TagInfo tagInfo = tagLibInfo.getTag(shortTagName); TagFileInfo tagFileInfo = tagLibInfo.getTagFile(shortTagName); if (tagInfo == null && tagFileInfo == null) { err.jspError(start, "jsp.error.bad_tag", shortTagName, prefix); } Class tagHandlerClass = null; if (tagInfo != null) { // Must be a classic tag, load it here. // tag files will be loaded later, in TagFileProcessor String handlerClassName = tagInfo.getTagClassName(); try { tagHandlerClass = ctxt.getClassLoader().loadClass( handlerClassName); } catch (Exception e) { err.jspError(start, "jsp.error.loadclass.taghandler", handlerClassName, tagName); } } // Parse 'CustomActionBody' production: // At this point we are committed - if anything fails, we produce // a translation error. // Parse 'Attributes' production: Attributes attrs = parseAttributes(); reader.skipSpaces(); // Parse 'CustomActionEnd' production: if (reader.matches("/>")) { if (tagInfo != null) { new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs, start, parent, tagInfo, tagHandlerClass); } else { new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs, start, parent, tagFileInfo); } return true; } // Now we parse one of 'CustomActionTagDependent', // 'CustomActionJSPContent', or 'CustomActionScriptlessContent'. // depending on body-content in TLD. // Looking for a body, it still can be empty; but if there is a // a tag body, its syntax would be dependent on the type of // body content declared in the TLD. String bc; if (tagInfo != null) { bc = tagInfo.getBodyContent(); } else { bc = tagFileInfo.getTagInfo().getBodyContent(); } Node tagNode = null; if (tagInfo != null) { tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs, start, parent, tagInfo, tagHandlerClass); } else { tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs, start, parent, tagFileInfo); } parseOptionalBody(tagNode, tagName, bc); return true; } /* * Parse for a template text string until '<' or "${" or "#{" is encountered, * recognizing escape sequences "<\%", "\$", and "\#". */ private void parseTemplateText(Node parent) throws JasperException { if (!reader.hasMoreInput()) return; CharArrayWriter ttext = new CharArrayWriter(); // Output the first character int ch = reader.nextChar(); if (ch == '\\') { reader.pushChar(); } else { ttext.write(ch); } while (reader.hasMoreInput()) { int prev = ch; ch = reader.nextChar(); if (ch == '<') { reader.pushChar(); break; } else if ((ch == '$' || ch == '#') && !pageInfo.isELIgnored()) { if (!reader.hasMoreInput()) { ttext.write(ch); break; } if (reader.nextChar() == '{') { reader.pushChar(); reader.pushChar(); break; } ttext.write(ch); reader.pushChar(); continue; } else if (ch == '\\') { if (!reader.hasMoreInput()) { ttext.write('\\'); break; } char next = (char) reader.peekChar(); // Looking for \% or \$ or \# if ((prev == '<' && next == '%') || ((next == '$' || next == '#') && !pageInfo.isELIgnored())) { ch = reader.nextChar(); } } ttext.write(ch); } new Node.TemplateText(ttext.toString(), start, parent); } /* * XMLTemplateText ::= ( S? '/>' ) | ( S? '>' ( ( Char* - ( Char* ( '<' | * '${' ) ) ) ( '${' ELExpressionBody )? CDSect? )* ETag ) | * <TRANSLATION_ERROR> */ private void parseXMLTemplateText(Node parent) throws JasperException { reader.skipSpaces(); if (!reader.matches("/>")) { if (!reader.matches(">")) { err.jspError(start, "jsp.error.unterminated", "<jsp:text>"); } CharArrayWriter ttext = new CharArrayWriter(); while (reader.hasMoreInput()) { int ch = reader.nextChar(); if (ch == '<') { // Check for <![CDATA[ if (!reader.matches("![CDATA[")) { break; } start = reader.mark(); Mark stop = reader.skipUntil("]]>"); if (stop == null) { err.jspError(start, "jsp.error.unterminated", "CDATA"); } String text = reader.getText(start, stop); ttext.write(text, 0, text.length()); } else if (ch == '\\') { if (!reader.hasMoreInput()) { ttext.write('\\'); break; } ch = reader.nextChar(); if (ch != '$' && ch != '#') { ttext.write('\\'); } ttext.write(ch); } else if (ch == '$' || ch == '#') { if (!reader.hasMoreInput()) { ttext.write(ch); break; } if (reader.nextChar() != '{') { ttext.write(ch); reader.pushChar(); continue; } // Create a template text node new Node.TemplateText(ttext.toString(), start, parent); // Mark and parse the EL expression and create its node: start = reader.mark(); parseELExpression(parent, (char) ch); start = reader.mark(); ttext = new CharArrayWriter(); } else { ttext.write(ch); } } new Node.TemplateText(ttext.toString(), start, parent); if (!reader.hasMoreInput()) { err.jspError(start, "jsp.error.unterminated", "<jsp:text>"); } else if (!reader.matchesETagWithoutLessThan("jsp:text")) { err.jspError(start, "jsp.error.jsptext.badcontent"); } } } /* * AllBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.' * XMLDirectiveBody ) | ( '<%!' DeclarationBody ) | ( '<jsp:declaration' * XMLDeclarationBody ) | ( '<%=' ExpressionBody ) | ( '<jsp:expression' * XMLExpressionBody ) | ( '${' ELExpressionBody ) | ( '<%' ScriptletBody ) | ( '<jsp:scriptlet' * XMLScriptletBody ) | ( '<jsp:text' XMLTemplateText ) | ( '<jsp:' * StandardAction ) | ( '<' CustomAction CustomActionBody ) | TemplateText */ private void parseElements(Node parent) throws JasperException { if (scriptlessCount > 0) { // vc: ScriptlessBody // We must follow the ScriptlessBody production if one of // our parents is ScriptlessBody. parseElementsScriptless(parent); return; } start = reader.mark(); if (reader.matches("<%--")) { parseComment(parent); } else if (reader.matches("<%@")) { parseDirective(parent); } else if (reader.matches("<jsp:directive.")) { parseXMLDirective(parent); } else if (reader.matches("<%!")) { parseDeclaration(parent); } else if (reader.matches("<jsp:declaration")) { parseXMLDeclaration(parent); } else if (reader.matches("<%=")) { parseExpression(parent); } else if (reader.matches("<jsp:expression")) { parseXMLExpression(parent); } else if (reader.matches("<%")) { parseScriptlet(parent); } else if (reader.matches("<jsp:scriptlet")) { parseXMLScriptlet(parent); } else if (reader.matches("<jsp:text")) { parseXMLTemplateText(parent); } else if (!pageInfo.isELIgnored() && reader.matches("${")) { parseELExpression(parent, '$'); } else if (!pageInfo.isELIgnored() && !pageInfo.isDeferredSyntaxAllowedAsLiteral() && reader.matches("#{")) { parseELExpression(parent, '#'); } else if (reader.matches("<jsp:")) { parseStandardAction(parent); } else if (!parseCustomTag(parent)) { checkUnbalancedEndTag(); parseTemplateText(parent); } } /* * ScriptlessBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.' * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration' * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression' * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet' * <TRANSLATION_ERROR> ) | ( '<jsp:text' XMLTemplateText ) | ( '${' * ELExpressionBody ) | ( '<jsp:' StandardAction ) | ( '<' CustomAction * CustomActionBody ) | TemplateText */ private void parseElementsScriptless(Node parent) throws JasperException { // Keep track of how many scriptless nodes we've encountered // so we know whether our child nodes are forced scriptless scriptlessCount++; start = reader.mark(); if (reader.matches("<%--")) { parseComment(parent); } else if (reader.matches("<%@")) { parseDirective(parent); } else if (reader.matches("<jsp:directive.")) { parseXMLDirective(parent); } else if (reader.matches("<%!")) { err.jspError(reader.mark(), "jsp.error.no.scriptlets"); } else if (reader.matches("<jsp:declaration")) { err.jspError(reader.mark(), "jsp.error.no.scriptlets"); } else if (reader.matches("<%=")) { err.jspError(reader.mark(), "jsp.error.no.scriptlets"); } else if (reader.matches("<jsp:expression")) { err.jspError(reader.mark(), "jsp.error.no.scriptlets"); } else if (reader.matches("<%")) { err.jspError(reader.mark(), "jsp.error.no.scriptlets"); } else if (reader.matches("<jsp:scriptlet")) { err.jspError(reader.mark(), "jsp.error.no.scriptlets"); } else if (reader.matches("<jsp:text")) { parseXMLTemplateText(parent); } else if (!pageInfo.isELIgnored() && reader.matches("${")) { parseELExpression(parent, '$'); } else if (!pageInfo.isELIgnored() && !pageInfo.isDeferredSyntaxAllowedAsLiteral() && reader.matches("#{")) { parseELExpression(parent, '#'); } else if (reader.matches("<jsp:")) { parseStandardAction(parent); } else if (!parseCustomTag(parent)) { checkUnbalancedEndTag(); parseTemplateText(parent); } scriptlessCount--; } /* * TemplateTextBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.' * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration' * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression' * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet' * <TRANSLATION_ERROR> ) | ( '<jsp:text' <TRANSLATION_ERROR> ) | ( '${' * <TRANSLATION_ERROR> ) | ( '<jsp:' <TRANSLATION_ERROR> ) | TemplateText */ private void parseElementsTemplateText(Node parent) throws JasperException { start = reader.mark(); if (reader.matches("<%--")) { parseComment(parent); } else if (reader.matches("<%@")) { parseDirective(parent); } else if (reader.matches("<jsp:directive.")) { parseXMLDirective(parent); } else if (reader.matches("<%!")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Declarations"); } else if (reader.matches("<jsp:declaration")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Declarations"); } else if (reader.matches("<%=")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Expressions"); } else if (reader.matches("<jsp:expression")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Expressions"); } else if (reader.matches("<%")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Scriptlets"); } else if (reader.matches("<jsp:scriptlet")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Scriptlets"); } else if (reader.matches("<jsp:text")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "<jsp:text"); } else if (!pageInfo.isELIgnored() && reader.matches("${")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Expression language"); } else if (!pageInfo.isELIgnored() && !pageInfo.isDeferredSyntaxAllowedAsLiteral() && reader.matches("#{")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Expression language"); } else if (reader.matches("<jsp:")) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Standard actions"); } else if (parseCustomTag(parent)) { err.jspError(reader.mark(), "jsp.error.not.in.template", "Custom actions"); } else { checkUnbalancedEndTag(); parseTemplateText(parent); } } /* * Flag as error if an unbalanced end tag appears by itself. */ private void checkUnbalancedEndTag() throws JasperException { if (!reader.matches("</")) { return; } // Check for unbalanced standard actions if (reader.matches("jsp:")) { err.jspError(start, "jsp.error.unbalanced.endtag", "jsp:"); } // Check for unbalanced custom actions String tagName = reader.parseToken(false); int i = tagName.indexOf(':'); if (i == -1 || pageInfo.getURI(tagName.substring(0, i)) == null) { reader.reset(start); return; } err.jspError(start, "jsp.error.unbalanced.endtag", tagName); } /** * TagDependentBody := */ private void parseTagDependentBody(Node parent, String tag) throws JasperException { Mark bodyStart = reader.mark(); Mark bodyEnd = reader.skipUntilETag(tag); if (bodyEnd == null) { err.jspError(start, "jsp.error.unterminated", "<" + tag); } new Node.TemplateText(reader.getText(bodyStart, bodyEnd), bodyStart, parent); } /* * Parses jsp:body action. */ private void parseJspBody(Node parent, String bodyType) throws JasperException { Mark start = reader.mark(); Node bodyNode = new Node.JspBody(start, parent); reader.skipSpaces(); if (!reader.matches("/>")) { if (!reader.matches(">")) { err.jspError(start, "jsp.error.unterminated", "<jsp:body"); } parseBody(bodyNode, "jsp:body", bodyType); } } /* * Parse the body as JSP content. @param tag The name of the tag whose end * tag would terminate the body @param bodyType One of the TagInfo body * types */ private void parseBody(Node parent, String tag, String bodyType) throws JasperException { if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)) { parseTagDependentBody(parent, tag); } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)) { if (!reader.matchesETag(tag)) { err.jspError(start, "jasper.error.emptybodycontent.nonempty", tag); } } else if (bodyType == JAVAX_BODY_CONTENT_PLUGIN) { // (note the == since we won't recognize JAVAX_* // from outside this module). parsePluginTags(parent); if (!reader.matchesETag(tag)) { err.jspError(reader.mark(), "jsp.error.unterminated", "<" + tag); } } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP) || bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS) || (bodyType == JAVAX_BODY_CONTENT_PARAM) || (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT)) { while (reader.hasMoreInput()) { if (reader.matchesETag(tag)) { return; } // Check for nested jsp:body or jsp:attribute if (tag.equals("jsp:body") || tag.equals("jsp:attribute")) { if (reader.matches("<jsp:attribute")) { err.jspError(reader.mark(), "jsp.error.nested.jspattribute"); } else if (reader.matches("<jsp:body")) { err.jspError(reader.mark(), "jsp.error.nested.jspbody"); } } if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)) { parseElements(parent); } else if (bodyType .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) { parseElementsScriptless(parent); } else if (bodyType == JAVAX_BODY_CONTENT_PARAM) { // (note the == since we won't recognize JAVAX_* // from outside this module). reader.skipSpaces(); parseParam(parent); } else if (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) { parseElementsTemplateText(parent); } } err.jspError(start, "jsp.error.unterminated", "<" + tag); } else { err.jspError(start, "jasper.error.bad.bodycontent.type"); } } /* * Parses named attributes. */ private void parseNamedAttributes(Node parent) throws JasperException { do { Mark start = reader.mark(); Attributes attrs = parseAttributes(); Node.NamedAttribute namedAttributeNode = new Node.NamedAttribute( attrs, start, parent); reader.skipSpaces(); if (!reader.matches("/>")) { if (!reader.matches(">")) { err.jspError(start, "jsp.error.unterminated", "<jsp:attribute"); } if (namedAttributeNode.isTrim()) { reader.skipSpaces(); } parseBody(namedAttributeNode, "jsp:attribute", getAttributeBodyType(parent, attrs.getValue("name"))); if (namedAttributeNode.isTrim()) { Node.Nodes subElems = namedAttributeNode.getBody(); if (subElems != null) { Node lastNode = subElems.getNode(subElems.size() - 1); if (lastNode instanceof Node.TemplateText) { ((Node.TemplateText) lastNode).rtrim(); } } } } reader.skipSpaces(); } while (reader.matches("<jsp:attribute")); } /** * Determine the body type of <jsp:attribute> from the enclosing node */ private String getAttributeBodyType(Node n, String name) { if (n instanceof Node.CustomTag) { TagInfo tagInfo = ((Node.CustomTag) n).getTagInfo(); TagAttributeInfo[] tldAttrs = tagInfo.getAttributes(); for (int i = 0; i < tldAttrs.length; i++) { if (name.equals(tldAttrs[i].getName())) { if (tldAttrs[i].isFragment()) { return TagInfo.BODY_CONTENT_SCRIPTLESS; } if (tldAttrs[i].canBeRequestTime()) { return TagInfo.BODY_CONTENT_JSP; } } } if (tagInfo.hasDynamicAttributes()) { return TagInfo.BODY_CONTENT_JSP; } } else if (n instanceof Node.IncludeAction) { if ("page".equals(name)) { return TagInfo.BODY_CONTENT_JSP; } } else if (n instanceof Node.ForwardAction) { if ("page".equals(name)) { return TagInfo.BODY_CONTENT_JSP; } } else if (n instanceof Node.SetProperty) { if ("value".equals(name)) { return TagInfo.BODY_CONTENT_JSP; } } else if (n instanceof Node.UseBean) { if ("beanName".equals(name)) { return TagInfo.BODY_CONTENT_JSP; } } else if (n instanceof Node.PlugIn) { if ("width".equals(name) || "height".equals(name)) { return TagInfo.BODY_CONTENT_JSP; } } else if (n instanceof Node.ParamAction) { if ("value".equals(name)) { return TagInfo.BODY_CONTENT_JSP; } } else if (n instanceof Node.JspElement) { return TagInfo.BODY_CONTENT_JSP; } return JAVAX_BODY_CONTENT_TEMPLATE_TEXT; } private void parseFileDirectives(Node parent) throws JasperException { reader.setSingleFile(true); reader.skipUntil("<"); while (reader.hasMoreInput()) { start = reader.mark(); if (reader.matches("%--")) { // Comment reader.skipUntil("--%>"); } else if (reader.matches("%@")) { parseDirective(parent); } else if (reader.matches("jsp:directive.")) { parseXMLDirective(parent); } else if (reader.matches("%!")) { // Declaration reader.skipUntil("%>"); } else if (reader.matches("%=")) { // Expression reader.skipUntil("%>"); } else if (reader.matches("%")) { // Scriptlet reader.skipUntil("%>"); } reader.skipUntil("<"); } } }