| /* |
| * 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.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import jakarta.el.ELException; |
| import jakarta.el.ExpressionFactory; |
| import jakarta.el.FunctionMapper; |
| import jakarta.servlet.jsp.JspFactory; |
| import jakarta.servlet.jsp.tagext.FunctionInfo; |
| import jakarta.servlet.jsp.tagext.PageData; |
| import jakarta.servlet.jsp.tagext.TagAttributeInfo; |
| import jakarta.servlet.jsp.tagext.TagData; |
| import jakarta.servlet.jsp.tagext.TagExtraInfo; |
| import jakarta.servlet.jsp.tagext.TagInfo; |
| import jakarta.servlet.jsp.tagext.TagLibraryInfo; |
| import jakarta.servlet.jsp.tagext.ValidationMessage; |
| |
| import org.apache.jasper.JasperException; |
| import org.apache.jasper.compiler.ELNode.Text; |
| import org.apache.jasper.el.ELContextImpl; |
| import org.apache.tomcat.util.security.Escape; |
| import org.xml.sax.Attributes; |
| |
| /** |
| * Performs validation on the page elements. Attributes are checked for mandatory presence, entry value validity, and |
| * consistency. As a side effect, some page global value (such as those from page directives) are stored, for later use. |
| */ |
| class Validator { |
| |
| /** |
| * A visitor to validate and extract page directive info |
| */ |
| private static class DirectiveVisitor extends Node.Visitor { |
| |
| private final PageInfo pageInfo; |
| |
| private final ErrorDispatcher err; |
| |
| private static final JspUtil.ValidAttribute[] pageDirectiveAttrs = { new JspUtil.ValidAttribute("language"), |
| new JspUtil.ValidAttribute("extends"), new JspUtil.ValidAttribute("import"), |
| new JspUtil.ValidAttribute("session"), new JspUtil.ValidAttribute("buffer"), |
| new JspUtil.ValidAttribute("autoFlush"), new JspUtil.ValidAttribute("info"), |
| new JspUtil.ValidAttribute("errorPage"), new JspUtil.ValidAttribute("isErrorPage"), |
| new JspUtil.ValidAttribute("contentType"), new JspUtil.ValidAttribute("pageEncoding"), |
| new JspUtil.ValidAttribute("isELIgnored"), new JspUtil.ValidAttribute("errorOnELNotFound"), |
| new JspUtil.ValidAttribute("deferredSyntaxAllowedAsLiteral"), |
| new JspUtil.ValidAttribute("trimDirectiveWhitespaces") }; |
| |
| private boolean pageEncodingSeen = false; |
| |
| /* |
| * Constructor |
| */ |
| DirectiveVisitor(Compiler compiler) { |
| this.pageInfo = compiler.getPageInfo(); |
| this.err = compiler.getErrorDispatcher(); |
| } |
| |
| @Override |
| public void visit(Node.IncludeDirective n) throws JasperException { |
| // Since pageDirectiveSeen flag only applies to the Current page |
| // save it here and restore it after the file is included. |
| boolean pageEncodingSeenSave = pageEncodingSeen; |
| pageEncodingSeen = false; |
| visitBody(n); |
| pageEncodingSeen = pageEncodingSeenSave; |
| } |
| |
| @Override |
| public void visit(Node.PageDirective n) throws JasperException { |
| |
| JspUtil.checkAttributes("Page directive", n, pageDirectiveAttrs, err); |
| |
| // JSP.2.10.1 |
| Attributes attrs = n.getAttributes(); |
| for (int i = 0; attrs != null && i < attrs.getLength(); i++) { |
| String attr = attrs.getQName(i); |
| String value = attrs.getValue(i); |
| |
| if ("language".equals(attr)) { |
| if (pageInfo.getLanguage(false) == null) { |
| pageInfo.setLanguage(value, n, err, true); |
| } else if (!pageInfo.getLanguage(false).equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.language", pageInfo.getLanguage(false), value); |
| } |
| } else if ("extends".equals(attr)) { |
| if (pageInfo.getExtends(false) == null) { |
| pageInfo.setExtends(value); |
| } else if (!pageInfo.getExtends(false).equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.extends", pageInfo.getExtends(false), value); |
| } |
| } else if ("contentType".equals(attr)) { |
| if (pageInfo.getContentType() == null) { |
| pageInfo.setContentType(value); |
| } else if (!pageInfo.getContentType().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.contenttype", pageInfo.getContentType(), value); |
| } |
| } else if ("session".equals(attr)) { |
| if (pageInfo.getSession() == null) { |
| pageInfo.setSession(value, n, err); |
| } else if (!pageInfo.getSession().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.session", pageInfo.getSession(), value); |
| } |
| } else if ("buffer".equals(attr)) { |
| if (pageInfo.getBufferValue() == null) { |
| pageInfo.setBufferValue(value, n, err); |
| } else if (!pageInfo.getBufferValue().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.buffer", pageInfo.getBufferValue(), value); |
| } |
| } else if ("autoFlush".equals(attr)) { |
| if (pageInfo.getAutoFlush() == null) { |
| pageInfo.setAutoFlush(value, n, err); |
| } else if (!pageInfo.getAutoFlush().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.autoflush", pageInfo.getAutoFlush(), value); |
| } |
| } else if ("isELIgnored".equals(attr)) { |
| if (pageInfo.getIsELIgnored() == null) { |
| pageInfo.setIsELIgnored(value, n, err, true); |
| } else if (!pageInfo.getIsELIgnored().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.iselignored", pageInfo.getIsELIgnored(), value); |
| } |
| } else if ("errorOnELNotFound".equals(attr)) { |
| if (pageInfo.getErrorOnELNotFound() == null) { |
| pageInfo.setErrorOnELNotFound(value, n, err, true); |
| } else if (!pageInfo.getErrorOnELNotFound().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.errorOnELNotFound", pageInfo.getErrorOnELNotFound(), |
| value); |
| } |
| } else if ("isErrorPage".equals(attr)) { |
| if (pageInfo.getIsErrorPage() == null) { |
| pageInfo.setIsErrorPage(value, n, err); |
| } else if (!pageInfo.getIsErrorPage().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.iserrorpage", pageInfo.getIsErrorPage(), value); |
| } |
| } else if ("errorPage".equals(attr)) { |
| if (pageInfo.getErrorPage() == null) { |
| pageInfo.setErrorPage(value); |
| } else if (!pageInfo.getErrorPage().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.errorpage", pageInfo.getErrorPage(), value); |
| } |
| } else if ("info".equals(attr)) { |
| if (pageInfo.getInfo() == null) { |
| pageInfo.setInfo(value); |
| } else if (!pageInfo.getInfo().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.info", pageInfo.getInfo(), value); |
| } |
| } else if ("pageEncoding".equals(attr)) { |
| if (pageEncodingSeen) { |
| err.jspError(n, "jsp.error.page.multi.pageencoding"); |
| } |
| // 'pageEncoding' can occur at most once per file |
| pageEncodingSeen = true; |
| String actual = comparePageEncodings(value, n); |
| n.getRoot().setPageEncoding(actual); |
| } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) { |
| if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) { |
| pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n, err, true); |
| } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.deferredsyntaxallowedasliteral", |
| pageInfo.getDeferredSyntaxAllowedAsLiteral(), value); |
| } |
| } else if ("trimDirectiveWhitespaces".equals(attr)) { |
| if (pageInfo.getTrimDirectiveWhitespaces() == null) { |
| pageInfo.setTrimDirectiveWhitespaces(value, n, err, true); |
| } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(value)) { |
| err.jspError(n, "jsp.error.page.conflict.trimdirectivewhitespaces", |
| pageInfo.getTrimDirectiveWhitespaces(), value); |
| } |
| } |
| } |
| |
| // Check for bad combinations |
| if (pageInfo.getBuffer() == 0 && !pageInfo.isAutoFlush()) { |
| err.jspError(n, "jsp.error.page.badCombo"); |
| } |
| |
| // Attributes for imports for this node have been processed by |
| // the parsers, just add them to pageInfo. |
| pageInfo.addImports(n.getImports()); |
| } |
| |
| @Override |
| public void visit(Node.TagDirective n) throws JasperException { |
| // Note: Most of the validation is done in TagFileProcessor |
| // when it created a TagInfo object from the |
| // tag file in which the directive appeared. |
| |
| // This method does additional processing to collect page info |
| |
| Attributes attrs = n.getAttributes(); |
| for (int i = 0; attrs != null && i < attrs.getLength(); i++) { |
| String attr = attrs.getQName(i); |
| String value = attrs.getValue(i); |
| |
| if ("language".equals(attr)) { |
| if (pageInfo.getLanguage(false) == null) { |
| pageInfo.setLanguage(value, n, err, false); |
| } else if (!pageInfo.getLanguage(false).equals(value)) { |
| err.jspError(n, "jsp.error.tag.conflict.language", pageInfo.getLanguage(false), value); |
| } |
| } else if ("isELIgnored".equals(attr)) { |
| if (pageInfo.getIsELIgnored() == null) { |
| pageInfo.setIsELIgnored(value, n, err, false); |
| } else if (!pageInfo.getIsELIgnored().equals(value)) { |
| err.jspError(n, "jsp.error.tag.conflict.iselignored", pageInfo.getIsELIgnored(), value); |
| } |
| } else if ("errorOnELNotFound".equals(attr)) { |
| if (pageInfo.getErrorOnELNotFound() == null) { |
| pageInfo.setErrorOnELNotFound(value, n, err, false); |
| } else if (!pageInfo.getErrorOnELNotFound().equals(value)) { |
| err.jspError(n, "jsp.error.tag.conflict.errorOnELNotFound", pageInfo.getErrorOnELNotFound(), |
| value); |
| } |
| } else if ("pageEncoding".equals(attr)) { |
| if (pageEncodingSeen) { |
| err.jspError(n, "jsp.error.tag.multi.pageencoding"); |
| } |
| pageEncodingSeen = true; |
| compareTagEncodings(value, n); |
| n.getRoot().setPageEncoding(value); |
| } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) { |
| if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) { |
| pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n, err, false); |
| } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral().equals(value)) { |
| err.jspError(n, "jsp.error.tag.conflict.deferredsyntaxallowedasliteral", |
| pageInfo.getDeferredSyntaxAllowedAsLiteral(), value); |
| } |
| } else if ("trimDirectiveWhitespaces".equals(attr)) { |
| if (pageInfo.getTrimDirectiveWhitespaces() == null) { |
| pageInfo.setTrimDirectiveWhitespaces(value, n, err, false); |
| } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(value)) { |
| err.jspError(n, "jsp.error.tag.conflict.trimdirectivewhitespaces", |
| pageInfo.getTrimDirectiveWhitespaces(), value); |
| } |
| } |
| } |
| |
| // Attributes for imports for this node have been processed by |
| // the parsers, just add them to pageInfo. |
| pageInfo.addImports(n.getImports()); |
| } |
| |
| @Override |
| public void visit(Node.AttributeDirective n) throws JasperException { |
| // Do nothing, since this attribute directive has already been |
| // validated by TagFileProcessor when it created a TagInfo object |
| // from the tag file in which the directive appeared |
| } |
| |
| @Override |
| public void visit(Node.VariableDirective n) throws JasperException { |
| // Do nothing, since this variable directive has already been |
| // validated by TagFileProcessor when it created a TagInfo object |
| // from the tag file in which the directive appeared |
| } |
| |
| /* |
| * Compares page encodings specified in various places, and throws exception in case of page encoding mismatch. |
| * |
| * @param pageDirEnc The value of the pageEncoding attribute of the page directive @param pageDir The page |
| * directive node |
| * |
| * @throws JasperException in case of page encoding mismatch |
| */ |
| private String comparePageEncodings(String thePageDirEnc, Node.PageDirective pageDir) throws JasperException { |
| |
| Node.Root root = pageDir.getRoot(); |
| String configEnc = root.getJspConfigPageEncoding(); |
| String pageDirEnc = thePageDirEnc.toUpperCase(Locale.ENGLISH); |
| |
| /* |
| * Compare the 'pageEncoding' attribute of the page directive with the encoding specified in the JSP config |
| * element whose URL pattern matches this page. Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as identical. |
| */ |
| if (configEnc != null) { |
| configEnc = configEnc.toUpperCase(Locale.ENGLISH); |
| if (!pageDirEnc.equals(configEnc) && |
| (!pageDirEnc.startsWith("UTF-16") || !configEnc.startsWith("UTF-16"))) { |
| err.jspError(pageDir, "jsp.error.config_pagedir_encoding_mismatch", configEnc, pageDirEnc); |
| } else { |
| return configEnc; |
| } |
| } |
| |
| /* |
| * Compare the 'pageEncoding' attribute of the page directive with the encoding specified in the XML prolog |
| * (only for XML syntax, and only if JSP document contains XML prolog with encoding declaration). Treat |
| * "UTF-16", "UTF-16BE", and "UTF-16LE" as identical. |
| */ |
| if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) { |
| String pageEnc = root.getPageEncoding().toUpperCase(Locale.ENGLISH); |
| if (!pageDirEnc.equals(pageEnc) && |
| (!pageDirEnc.startsWith("UTF-16") || !pageEnc.startsWith("UTF-16"))) { |
| err.jspError(pageDir, "jsp.error.prolog_pagedir_encoding_mismatch", pageEnc, pageDirEnc); |
| } else { |
| return pageEnc; |
| } |
| } |
| |
| return pageDirEnc; |
| } |
| |
| /* |
| * Compares page encodings specified in various places, and throws exception in case of page encoding mismatch. |
| * |
| * @param thePageDirEnc The value of the pageEncoding attribute of the page directive @param pageDir The page |
| * directive node |
| * |
| * @throws JasperException in case of page encoding mismatch |
| */ |
| private void compareTagEncodings(String thePageDirEnc, Node.TagDirective pageDir) throws JasperException { |
| |
| Node.Root root = pageDir.getRoot(); |
| String pageDirEnc = thePageDirEnc.toUpperCase(Locale.ENGLISH); |
| /* |
| * Compare the 'pageEncoding' attribute of the page directive with the encoding specified in the XML prolog |
| * (only for XML syntax, and only if JSP document contains XML prolog with encoding declaration). Treat |
| * "UTF-16", "UTF-16BE", and "UTF-16LE" as identical. |
| */ |
| if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) { |
| String pageEnc = root.getPageEncoding().toUpperCase(Locale.ENGLISH); |
| if (!pageDirEnc.equals(pageEnc) && |
| (!pageDirEnc.startsWith("UTF-16") || !pageEnc.startsWith("UTF-16"))) { |
| err.jspError(pageDir, "jsp.error.prolog_pagedir_encoding_mismatch", pageEnc, pageDirEnc); |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * A visitor for validating nodes other than page directives |
| */ |
| private static class ValidateVisitor extends Node.Visitor { |
| |
| // Pattern to extract a method name from a full method signature |
| private static final Pattern METHOD_NAME_PATTERN = |
| Pattern.compile(".*[ \t\n\r]+(.+?)[ \t\n\r]*\\(.*", Pattern.DOTALL); |
| |
| private final PageInfo pageInfo; |
| |
| private final ErrorDispatcher err; |
| |
| private final ClassLoader loader; |
| |
| private final StringBuilder buf = new StringBuilder(32); |
| |
| private static final JspUtil.ValidAttribute[] jspRootAttrs = |
| { new JspUtil.ValidAttribute("xsi:schemaLocation"), new JspUtil.ValidAttribute("version", true) }; |
| |
| private static final JspUtil.ValidAttribute[] includeDirectiveAttrs = |
| { new JspUtil.ValidAttribute("file", true) }; |
| |
| private static final JspUtil.ValidAttribute[] taglibDirectiveAttrs = { new JspUtil.ValidAttribute("uri"), |
| new JspUtil.ValidAttribute("tagdir"), new JspUtil.ValidAttribute("prefix", true) }; |
| |
| private static final JspUtil.ValidAttribute[] includeActionAttrs = |
| { new JspUtil.ValidAttribute("page", true), new JspUtil.ValidAttribute("flush") }; |
| |
| private static final JspUtil.ValidAttribute[] paramActionAttrs = |
| { new JspUtil.ValidAttribute("name", true), new JspUtil.ValidAttribute("value", true) }; |
| |
| private static final JspUtil.ValidAttribute[] forwardActionAttrs = { new JspUtil.ValidAttribute("page", true) }; |
| |
| private static final JspUtil.ValidAttribute[] getPropertyAttrs = |
| { new JspUtil.ValidAttribute("name", true), new JspUtil.ValidAttribute("property", true) }; |
| |
| private static final JspUtil.ValidAttribute[] setPropertyAttrs = |
| { new JspUtil.ValidAttribute("name", true), new JspUtil.ValidAttribute("property", true), |
| new JspUtil.ValidAttribute("value", false), new JspUtil.ValidAttribute("param") }; |
| |
| private static final JspUtil.ValidAttribute[] useBeanAttrs = { new JspUtil.ValidAttribute("id", true), |
| new JspUtil.ValidAttribute("scope"), new JspUtil.ValidAttribute("class"), |
| new JspUtil.ValidAttribute("type"), new JspUtil.ValidAttribute("beanName", false) }; |
| |
| private static final JspUtil.ValidAttribute[] attributeAttrs = { new JspUtil.ValidAttribute("name", true), |
| new JspUtil.ValidAttribute("trim"), new JspUtil.ValidAttribute("omit") }; |
| |
| private static final JspUtil.ValidAttribute[] invokeAttrs = |
| { new JspUtil.ValidAttribute("fragment", true), new JspUtil.ValidAttribute("var"), |
| new JspUtil.ValidAttribute("varReader"), new JspUtil.ValidAttribute("scope") }; |
| |
| private static final JspUtil.ValidAttribute[] doBodyAttrs = { new JspUtil.ValidAttribute("var"), |
| new JspUtil.ValidAttribute("varReader"), new JspUtil.ValidAttribute("scope") }; |
| |
| private static final JspUtil.ValidAttribute[] jspOutputAttrs = { |
| new JspUtil.ValidAttribute("omit-xml-declaration"), new JspUtil.ValidAttribute("doctype-root-element"), |
| new JspUtil.ValidAttribute("doctype-public"), new JspUtil.ValidAttribute("doctype-system") }; |
| |
| private final ExpressionFactory expressionFactory; |
| |
| /* |
| * Constructor |
| */ |
| ValidateVisitor(Compiler compiler) { |
| this.pageInfo = compiler.getPageInfo(); |
| this.err = compiler.getErrorDispatcher(); |
| this.loader = compiler.getCompilationContext().getClassLoader(); |
| // Get the cached EL expression factory for this context |
| expressionFactory = JspFactory.getDefaultFactory() |
| .getJspApplicationContext(compiler.getCompilationContext().getServletContext()) |
| .getExpressionFactory(); |
| } |
| |
| @Override |
| public void visit(Node.JspRoot n) throws JasperException { |
| JspUtil.checkAttributes("Jsp:root", n, jspRootAttrs, err); |
| String version = n.getTextAttribute("version"); |
| if (!version.equals("1.2") && !version.equals("2.0") && !version.equals("2.1") && !version.equals("2.2") && |
| !version.equals("2.3") && !version.equals("3.0") && !version.equals("3.1") && |
| !version.equals("4.0")) { |
| err.jspError(n, "jsp.error.jsproot.version.invalid", version); |
| } |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.IncludeDirective n) throws JasperException { |
| JspUtil.checkAttributes("Include directive", n, includeDirectiveAttrs, err); |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.TaglibDirective n) throws JasperException { |
| JspUtil.checkAttributes("Taglib directive", n, taglibDirectiveAttrs, err); |
| // Either 'uri' or 'tagdir' attribute must be specified |
| String uri = n.getAttributeValue("uri"); |
| String tagdir = n.getAttributeValue("tagdir"); |
| if (uri == null && tagdir == null) { |
| err.jspError(n, "jsp.error.taglibDirective.missing.location"); |
| } |
| if (uri != null && tagdir != null) { |
| err.jspError(n, "jsp.error.taglibDirective.both_uri_and_tagdir"); |
| } |
| } |
| |
| @Override |
| public void visit(Node.ParamAction n) throws JasperException { |
| JspUtil.checkAttributes("Param action", n, paramActionAttrs, err); |
| // make sure the value of the 'name' attribute is not a |
| // request-time expression |
| throwErrorIfExpression(n, "name", "jsp:param"); |
| n.setValue(getJspAttribute(null, "value", null, null, n.getAttributeValue("value"), n, null, false)); |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.IncludeAction n) throws JasperException { |
| JspUtil.checkAttributes("Include action", n, includeActionAttrs, err); |
| n.setPage(getJspAttribute(null, "page", null, null, n.getAttributeValue("page"), n, null, false)); |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.ForwardAction n) throws JasperException { |
| JspUtil.checkAttributes("Forward", n, forwardActionAttrs, err); |
| n.setPage(getJspAttribute(null, "page", null, null, n.getAttributeValue("page"), n, null, false)); |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.GetProperty n) throws JasperException { |
| JspUtil.checkAttributes("GetProperty", n, getPropertyAttrs, err); |
| } |
| |
| @Override |
| public void visit(Node.SetProperty n) throws JasperException { |
| JspUtil.checkAttributes("SetProperty", n, setPropertyAttrs, err); |
| String property = n.getTextAttribute("property"); |
| String param = n.getTextAttribute("param"); |
| String value = n.getAttributeValue("value"); |
| |
| n.setValue(getJspAttribute(null, "value", null, null, value, n, null, false)); |
| |
| boolean valueSpecified = n.getValue() != null; |
| |
| if ("*".equals(property)) { |
| if (param != null || valueSpecified) { |
| err.jspError(n, "jsp.error.setProperty.invalid"); |
| } |
| |
| } else if (param != null && valueSpecified) { |
| err.jspError(n, "jsp.error.setProperty.invalid"); |
| } |
| |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.UseBean n) throws JasperException { |
| JspUtil.checkAttributes("UseBean", n, useBeanAttrs, err); |
| |
| String name = n.getTextAttribute("id"); |
| String scope = n.getTextAttribute("scope"); |
| JspUtil.checkScope(scope, n, err); |
| String className = n.getTextAttribute("class"); |
| String type = n.getTextAttribute("type"); |
| BeanRepository beanInfo = pageInfo.getBeanRepository(); |
| |
| if (className == null && type == null) { |
| err.jspError(n, "jsp.error.usebean.missingType"); |
| } |
| |
| if (beanInfo.checkVariable(name)) { |
| err.jspError(n, "jsp.error.usebean.duplicate"); |
| } |
| |
| if ("session".equals(scope) && !pageInfo.isSession()) { |
| err.jspError(n, "jsp.error.usebean.noSession"); |
| } |
| |
| Node.JspAttribute jattr = |
| getJspAttribute(null, "beanName", null, null, n.getAttributeValue("beanName"), n, null, false); |
| n.setBeanName(jattr); |
| if (className != null && jattr != null) { |
| err.jspError(n, "jsp.error.usebean.notBoth"); |
| } |
| |
| if (className == null) { |
| className = type; |
| } |
| |
| beanInfo.addBean(n, name, className, scope); |
| |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.NamedAttribute n) throws JasperException { |
| JspUtil.checkAttributes("Attribute", n, attributeAttrs, err); |
| n.setOmit(getJspAttribute(null, "omit", null, null, n.getAttributeValue("omit"), n, null, false)); |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.JspBody n) throws JasperException { |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.Declaration n) throws JasperException { |
| if (pageInfo.isScriptingInvalid()) { |
| err.jspError(n.getStart(), "jsp.error.no.scriptlets"); |
| } |
| } |
| |
| @Override |
| public void visit(Node.Expression n) throws JasperException { |
| if (pageInfo.isScriptingInvalid()) { |
| err.jspError(n.getStart(), "jsp.error.no.scriptlets"); |
| } |
| } |
| |
| @Override |
| public void visit(Node.Scriptlet n) throws JasperException { |
| if (pageInfo.isScriptingInvalid()) { |
| err.jspError(n.getStart(), "jsp.error.no.scriptlets"); |
| } |
| } |
| |
| @Override |
| public void visit(Node.ELExpression n) throws JasperException { |
| // exit if we are ignoring EL all together |
| if (pageInfo.isELIgnored()) { |
| return; |
| } |
| |
| // JSP.2.2 - '#{' not allowed in template text |
| if (n.getType() == '#') { |
| if (!pageInfo.isDeferredSyntaxAllowedAsLiteral()) { |
| err.jspError(n, "jsp.error.el.template.deferred"); |
| } else { |
| return; |
| } |
| } |
| |
| // build expression |
| StringBuilder expr = this.getBuffer(); |
| expr.append(n.getType()).append('{').append(n.getText()).append('}'); |
| ELNode.Nodes el = ELParser.parse(expr.toString(), pageInfo.isDeferredSyntaxAllowedAsLiteral()); |
| |
| // validate/prepare expression |
| prepareExpression(el, n, expr.toString()); |
| |
| // store it |
| n.setEL(el); |
| } |
| |
| @Override |
| public void visit(Node.UninterpretedTag n) throws JasperException { |
| if (n.getNamedAttributeNodes().size() != 0) { |
| err.jspError(n, "jsp.error.namedAttribute.invalidUse"); |
| } |
| |
| Attributes attrs = n.getAttributes(); |
| if (attrs != null) { |
| int attrSize = attrs.getLength(); |
| Node.JspAttribute[] jspAttrs = new Node.JspAttribute[attrSize]; |
| for (int i = 0; i < attrSize; i++) { |
| // JSP.2.2 - '#{' not allowed in template text |
| String value = attrs.getValue(i); |
| if (!pageInfo.isDeferredSyntaxAllowedAsLiteral()) { |
| if (containsDeferredSyntax(value)) { |
| err.jspError(n, "jsp.error.el.template.deferred"); |
| } |
| } |
| jspAttrs[i] = getJspAttribute(null, attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), |
| value, n, null, false); |
| } |
| n.setJspAttributes(jspAttrs); |
| } |
| |
| visitBody(n); |
| } |
| |
| /* |
| * Look for a #{ sequence that isn't preceded by \. |
| */ |
| private boolean containsDeferredSyntax(String value) { |
| if (value == null) { |
| return false; |
| } |
| |
| int i = 0; |
| int len = value.length(); |
| boolean prevCharIsEscape = false; |
| while (i < value.length()) { |
| char c = value.charAt(i); |
| if (c == '#' && (i + 1) < len && value.charAt(i + 1) == '{' && !prevCharIsEscape) { |
| return true; |
| } else { |
| prevCharIsEscape = c == '\\'; |
| } |
| i++; |
| } |
| return false; |
| } |
| |
| @SuppressWarnings("null") // tagInfo can't be null after initial test |
| @Override |
| public void visit(Node.CustomTag n) throws JasperException { |
| |
| TagInfo tagInfo = n.getTagInfo(); |
| if (tagInfo == null) { |
| err.jspError(n, "jsp.error.missing.tagInfo", n.getQName()); |
| } |
| |
| /* |
| * The bodycontent of a SimpleTag cannot be JSP. |
| */ |
| if (n.implementsSimpleTag() && tagInfo.getBodyContent().equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)) { |
| err.jspError(n, "jsp.error.simpletag.badbodycontent", tagInfo.getTagClassName()); |
| } |
| |
| /* |
| * If the tag handler declares in the TLD that it supports dynamic attributes, it also must implement the |
| * DynamicAttributes interface. |
| */ |
| if (tagInfo.hasDynamicAttributes() && !n.implementsDynamicAttributes()) { |
| err.jspError(n, "jsp.error.dynamic.attributes.not.implemented", n.getQName()); |
| } |
| |
| /* |
| * Make sure all required attributes are present, either as attributes or named attributes |
| * (<jsp:attribute>). Also make sure that the same attribute is not specified in both attributes or named |
| * attributes. |
| */ |
| TagAttributeInfo[] tldAttrs = tagInfo.getAttributes(); |
| String customActionUri = n.getURI(); |
| Attributes attrs = n.getAttributes(); |
| int attrsSize = (attrs == null) ? 0 : attrs.getLength(); |
| for (TagAttributeInfo tldAttr : tldAttrs) { |
| String attr = null; |
| if (attrs != null) { |
| attr = attrs.getValue(tldAttr.getName()); |
| if (attr == null) { |
| attr = attrs.getValue(customActionUri, tldAttr.getName()); |
| } |
| } |
| Node.NamedAttribute na = n.getNamedAttributeNode(tldAttr.getName()); |
| |
| if (tldAttr.isRequired() && attr == null && na == null) { |
| err.jspError(n, "jsp.error.missing_attribute", tldAttr.getName(), n.getLocalName()); |
| } |
| if (attr != null && na != null) { |
| err.jspError(n, "jsp.error.duplicate.name.jspattribute", tldAttr.getName()); |
| } |
| } |
| |
| Node.Nodes naNodes = n.getNamedAttributeNodes(); |
| int jspAttrsSize = naNodes.size() + attrsSize; |
| Node.JspAttribute[] jspAttrs = null; |
| if (jspAttrsSize > 0) { |
| jspAttrs = new Node.JspAttribute[jspAttrsSize]; |
| } |
| Hashtable<String,Object> tagDataAttrs = new Hashtable<>(attrsSize); |
| |
| checkXmlAttributes(n, jspAttrs, tagDataAttrs); |
| checkNamedAttributes(n, jspAttrs, attrsSize, tagDataAttrs); |
| |
| TagData tagData = new TagData(tagDataAttrs); |
| |
| // JSP.C1: It is a (translation time) error for an action that |
| // has one or more variable subelements to have a TagExtraInfo |
| // class that returns a non-null object. |
| TagExtraInfo tei = tagInfo.getTagExtraInfo(); |
| if (tei != null && tei.getVariableInfo(tagData) != null && tei.getVariableInfo(tagData).length > 0 && |
| tagInfo.getTagVariableInfos().length > 0) { |
| err.jspError("jsp.error.non_null_tei_and_var_subelems", n.getQName()); |
| } |
| |
| n.setTagData(tagData); |
| n.setJspAttributes(jspAttrs); |
| |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.JspElement n) throws JasperException { |
| |
| Attributes attrs = n.getAttributes(); |
| if (attrs == null) { |
| err.jspError(n, "jsp.error.jspelement.missing.name"); |
| } |
| @SuppressWarnings("null") // Exception will have been thrown above |
| int xmlAttrLen = attrs.getLength(); |
| |
| Node.Nodes namedAttrs = n.getNamedAttributeNodes(); |
| |
| // XML-style 'name' attribute, which is mandatory, must not be |
| // included in JspAttribute array |
| int jspAttrSize = xmlAttrLen - 1 + namedAttrs.size(); |
| |
| Node.JspAttribute[] jspAttrs = new Node.JspAttribute[jspAttrSize]; |
| int jspAttrIndex = 0; |
| |
| // Process XML-style attributes |
| for (int i = 0; i < xmlAttrLen; i++) { |
| if ("name".equals(attrs.getLocalName(i))) { |
| n.setNameAttribute(getJspAttribute(null, attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), |
| attrs.getValue(i), n, null, false)); |
| } else { |
| if (jspAttrIndex < jspAttrSize) { |
| jspAttrs[jspAttrIndex++] = getJspAttribute(null, attrs.getQName(i), attrs.getURI(i), |
| attrs.getLocalName(i), attrs.getValue(i), n, null, false); |
| } |
| } |
| } |
| if (n.getNameAttribute() == null) { |
| err.jspError(n, "jsp.error.jspelement.missing.name"); |
| } |
| |
| // Process named attributes |
| for (int i = 0; i < namedAttrs.size(); i++) { |
| Node.NamedAttribute na = (Node.NamedAttribute) namedAttrs.getNode(i); |
| jspAttrs[jspAttrIndex++] = new Node.JspAttribute(na, null, false); |
| } |
| |
| n.setJspAttributes(jspAttrs); |
| |
| visitBody(n); |
| } |
| |
| @Override |
| public void visit(Node.JspOutput n) throws JasperException { |
| JspUtil.checkAttributes("jsp:output", n, jspOutputAttrs, err); |
| |
| if (n.getBody() != null) { |
| err.jspError(n, "jsp.error.jspoutput.nonemptybody"); |
| } |
| |
| String omitXmlDecl = n.getAttributeValue("omit-xml-declaration"); |
| String doctypeName = n.getAttributeValue("doctype-root-element"); |
| String doctypePublic = n.getAttributeValue("doctype-public"); |
| String doctypeSystem = n.getAttributeValue("doctype-system"); |
| |
| String omitXmlDeclOld = pageInfo.getOmitXmlDecl(); |
| String doctypeNameOld = pageInfo.getDoctypeName(); |
| String doctypePublicOld = pageInfo.getDoctypePublic(); |
| String doctypeSystemOld = pageInfo.getDoctypeSystem(); |
| |
| if (omitXmlDecl != null && omitXmlDeclOld != null && !omitXmlDecl.equals(omitXmlDeclOld)) { |
| err.jspError(n, "jsp.error.jspoutput.conflict", "omit-xml-declaration", omitXmlDeclOld, omitXmlDecl); |
| } |
| |
| if (doctypeName != null && doctypeNameOld != null && !doctypeName.equals(doctypeNameOld)) { |
| err.jspError(n, "jsp.error.jspoutput.conflict", "doctype-root-element", doctypeNameOld, doctypeName); |
| } |
| |
| if (doctypePublic != null && doctypePublicOld != null && !doctypePublic.equals(doctypePublicOld)) { |
| err.jspError(n, "jsp.error.jspoutput.conflict", "doctype-public", doctypePublicOld, doctypePublic); |
| } |
| |
| if (doctypeSystem != null && doctypeSystemOld != null && !doctypeSystem.equals(doctypeSystemOld)) { |
| err.jspError(n, "jsp.error.jspoutput.conflict", "doctype-system", doctypeSystemOld, doctypeSystem); |
| } |
| |
| if (doctypeName == null && doctypeSystem != null || doctypeName != null && doctypeSystem == null) { |
| err.jspError(n, "jsp.error.jspoutput.doctypenamesystem"); |
| } |
| |
| if (doctypePublic != null && doctypeSystem == null) { |
| err.jspError(n, "jsp.error.jspoutput.doctypepublicsystem"); |
| } |
| |
| if (omitXmlDecl != null) { |
| pageInfo.setOmitXmlDecl(omitXmlDecl); |
| } |
| if (doctypeName != null) { |
| pageInfo.setDoctypeName(doctypeName); |
| } |
| if (doctypeSystem != null) { |
| pageInfo.setDoctypeSystem(doctypeSystem); |
| } |
| if (doctypePublic != null) { |
| pageInfo.setDoctypePublic(doctypePublic); |
| } |
| } |
| |
| @Override |
| public void visit(Node.InvokeAction n) throws JasperException { |
| |
| JspUtil.checkAttributes("Invoke", n, invokeAttrs, err); |
| |
| String scope = n.getTextAttribute("scope"); |
| JspUtil.checkScope(scope, n, err); |
| |
| String var = n.getTextAttribute("var"); |
| String varReader = n.getTextAttribute("varReader"); |
| if (scope != null && var == null && varReader == null) { |
| err.jspError(n, "jsp.error.missing_var_or_varReader"); |
| } |
| if (var != null && varReader != null) { |
| err.jspError(n, "jsp.error.var_and_varReader"); |
| } |
| } |
| |
| @Override |
| public void visit(Node.DoBodyAction n) throws JasperException { |
| |
| JspUtil.checkAttributes("DoBody", n, doBodyAttrs, err); |
| |
| String scope = n.getTextAttribute("scope"); |
| JspUtil.checkScope(scope, n, err); |
| |
| String var = n.getTextAttribute("var"); |
| String varReader = n.getTextAttribute("varReader"); |
| if (scope != null && var == null && varReader == null) { |
| err.jspError(n, "jsp.error.missing_var_or_varReader"); |
| } |
| if (var != null && varReader != null) { |
| err.jspError(n, "jsp.error.var_and_varReader"); |
| } |
| } |
| |
| /* |
| * Make sure the given custom action does not have any invalid attributes. |
| * |
| * A custom action and its declared attributes always belong to the same namespace, which is identified by the |
| * prefix name of the custom tag invocation. For example, in this invocation: |
| * |
| * <my:test a="1" b="2" c="3"/>, the action |
| * |
| * "test" and its attributes "a", "b", and "c" all belong to the namespace identified by the prefix "my". The |
| * above invocation would be equivalent to: |
| * |
| * <my:test my:a="1" my:b="2" my:c="3"/> |
| * |
| * An action attribute may have a prefix different from that of the action invocation only if the underlying tag |
| * handler supports dynamic attributes, in which case the attribute with the different prefix is considered a |
| * dynamic attribute. |
| */ |
| private void checkXmlAttributes(Node.CustomTag n, Node.JspAttribute[] jspAttrs, Map<String,Object> tagDataAttrs) |
| throws JasperException { |
| |
| TagInfo tagInfo = n.getTagInfo(); |
| TagAttributeInfo[] tldAttrs = tagInfo.getAttributes(); |
| Attributes attrs = n.getAttributes(); |
| |
| for (int i = 0; attrs != null && i < attrs.getLength(); i++) { |
| boolean found = false; |
| |
| boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("%=")) || |
| (!n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("<%="))); |
| boolean elExpression = false; |
| boolean deferred = false; |
| double libraryVersion = Double.parseDouble(tagInfo.getTagLibrary().getRequiredVersion()); |
| boolean deferredSyntaxAllowedAsLiteral = |
| pageInfo.isDeferredSyntaxAllowedAsLiteral() || libraryVersion < 2.1; |
| |
| String xmlAttributeValue = attrs.getValue(i); |
| |
| ELNode.Nodes el = null; |
| if (!runtimeExpression && !pageInfo.isELIgnored()) { |
| el = ELParser.parse(xmlAttributeValue, deferredSyntaxAllowedAsLiteral); |
| Iterator<ELNode> nodes = el.iterator(); |
| while (nodes.hasNext()) { |
| ELNode node = nodes.next(); |
| if (node instanceof ELNode.Root) { |
| if (((ELNode.Root) node).getType() == '$') { |
| if (elExpression && deferred) { |
| err.jspError(n, "jsp.error.attribute.deferredmix"); |
| } |
| elExpression = true; |
| } else if (((ELNode.Root) node).getType() == '#') { |
| if (elExpression && !deferred) { |
| err.jspError(n, "jsp.error.attribute.deferredmix"); |
| } |
| elExpression = true; |
| deferred = true; |
| } |
| } |
| } |
| } |
| |
| boolean expression = runtimeExpression || elExpression; |
| |
| // When attribute is not an expression, |
| // contains its textual value with \$ and \# escaping removed. |
| String textAttributeValue; |
| if (!elExpression && el != null) { |
| // Should be a single Text node |
| Iterator<ELNode> it = el.iterator(); |
| if (it.hasNext()) { |
| textAttributeValue = ((ELNode.Text) it.next()).getText(); |
| } else { |
| textAttributeValue = ""; |
| } |
| } else { |
| textAttributeValue = xmlAttributeValue; |
| } |
| for (int j = 0; tldAttrs != null && j < tldAttrs.length; j++) { |
| if (attrs.getLocalName(i).equals(tldAttrs[j].getName()) && (attrs.getURI(i) == null || |
| attrs.getURI(i).isEmpty() || attrs.getURI(i).equals(n.getURI()))) { |
| |
| TagAttributeInfo tldAttr = tldAttrs[j]; |
| if (tldAttr.canBeRequestTime() || tldAttr.isDeferredMethod() || tldAttr.isDeferredValue()) { // JSP |
| // 2.1 |
| |
| if (!expression) { |
| |
| String expectedType = null; |
| if (tldAttr.isDeferredMethod()) { |
| // The String literal must be castable to what is declared as type |
| // for the attribute |
| String m = tldAttr.getMethodSignature(); |
| if (m != null) { |
| m = m.trim(); |
| int rti = m.indexOf(' '); |
| if (rti > 0) { |
| expectedType = m.substring(0, rti).trim(); |
| } |
| } else { |
| expectedType = "java.lang.Object"; |
| } |
| if ("void".equals(expectedType)) { |
| // Can't specify a literal for a |
| // deferred method with an expected type |
| // of void - JSP.2.3.4 |
| err.jspError(n, "jsp.error.literal_with_void", tldAttr.getName()); |
| } |
| } |
| if (tldAttr.isDeferredValue()) { |
| // The String literal must be castable to what is declared as type |
| // for the attribute |
| expectedType = tldAttr.getExpectedTypeName(); |
| } |
| if (expectedType != null) { |
| Class<?> expectedClass = String.class; |
| try { |
| expectedClass = JspUtil.toClass(expectedType, loader); |
| } catch (ClassNotFoundException e) { |
| err.jspError(n, "jsp.error.unknown_attribute_type", tldAttr.getName(), |
| expectedType); |
| } |
| // Check casting - not possible for all types |
| if (String.class.equals(expectedClass) || expectedClass == Long.TYPE || |
| expectedClass == Double.TYPE || expectedClass == Byte.TYPE || |
| expectedClass == Short.TYPE || expectedClass == Integer.TYPE || |
| expectedClass == Float.TYPE || |
| Number.class.isAssignableFrom(expectedClass) || |
| Character.class.equals(expectedClass) || Character.TYPE == expectedClass || |
| Boolean.class.equals(expectedClass) || Boolean.TYPE == expectedClass || |
| expectedClass.isEnum()) { |
| try { |
| expressionFactory.coerceToType(textAttributeValue, expectedClass); |
| } catch (Exception e) { |
| err.jspError(n, "jsp.error.coerce_to_type", tldAttr.getName(), expectedType, |
| textAttributeValue); |
| } |
| } |
| } |
| |
| jspAttrs[i] = new Node.JspAttribute(tldAttr, attrs.getQName(i), attrs.getURI(i), |
| attrs.getLocalName(i), textAttributeValue, false, null, false); |
| } else { |
| |
| if (deferred && !tldAttr.isDeferredMethod() && !tldAttr.isDeferredValue()) { |
| // No deferred expressions allowed for this attribute |
| err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr", tldAttr.getName()); |
| } |
| if (!deferred && !tldAttr.canBeRequestTime()) { |
| // Only deferred expressions are allowed for this attribute |
| err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr", tldAttr.getName()); |
| } |
| |
| // EL or Runtime expression |
| jspAttrs[i] = getJspAttribute(tldAttr, attrs.getQName(i), attrs.getURI(i), |
| attrs.getLocalName(i), xmlAttributeValue, n, el, false); |
| } |
| |
| } else { |
| // Attribute does not accept any expressions. |
| // Make sure its value does not contain any. |
| if (expression) { |
| err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr", tldAttr.getName()); |
| } |
| jspAttrs[i] = new Node.JspAttribute(tldAttr, attrs.getQName(i), attrs.getURI(i), |
| attrs.getLocalName(i), textAttributeValue, false, null, false); |
| } |
| if (expression) { |
| tagDataAttrs.put(attrs.getQName(i), TagData.REQUEST_TIME_VALUE); |
| } else { |
| tagDataAttrs.put(attrs.getQName(i), textAttributeValue); |
| } |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| if (tagInfo.hasDynamicAttributes()) { |
| jspAttrs[i] = getJspAttribute(null, attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), |
| xmlAttributeValue, n, el, true); |
| } else { |
| err.jspError(n, "jsp.error.bad_attribute", attrs.getQName(i), n.getLocalName()); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Make sure the given custom action does not have any invalid named attributes |
| */ |
| private void checkNamedAttributes(Node.CustomTag n, Node.JspAttribute[] jspAttrs, int start, |
| Map<String,Object> tagDataAttrs) throws JasperException { |
| |
| TagInfo tagInfo = n.getTagInfo(); |
| TagAttributeInfo[] tldAttrs = tagInfo.getAttributes(); |
| Node.Nodes naNodes = n.getNamedAttributeNodes(); |
| |
| for (int i = 0; i < naNodes.size(); i++) { |
| Node.NamedAttribute na = (Node.NamedAttribute) naNodes.getNode(i); |
| boolean found = false; |
| for (TagAttributeInfo tldAttr : tldAttrs) { |
| /* |
| * See above comment about namespace matches. For named attributes, we use the prefix instead of URI |
| * as the match criterion, because in the case of a JSP document, we'd have to keep track of which |
| * namespaces are in scope when parsing a named attribute, in order to determine the URI that the |
| * prefix of the named attribute's name matches to. |
| */ |
| String attrPrefix = na.getPrefix(); |
| if (na.getLocalName().equals(tldAttr.getName()) && |
| (attrPrefix == null || attrPrefix.isEmpty() || attrPrefix.equals(n.getPrefix()))) { |
| jspAttrs[start + i] = new Node.JspAttribute(na, tldAttr, false); |
| NamedAttributeVisitor nav = null; |
| if (na.getBody() != null) { |
| nav = new NamedAttributeVisitor(); |
| na.getBody().visit(nav); |
| } |
| if (nav != null && nav.hasDynamicContent()) { |
| tagDataAttrs.put(na.getName(), TagData.REQUEST_TIME_VALUE); |
| } else { |
| tagDataAttrs.put(na.getName(), na.getText()); |
| } |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| if (tagInfo.hasDynamicAttributes()) { |
| jspAttrs[start + i] = new Node.JspAttribute(na, null, true); |
| } else { |
| err.jspError(n, "jsp.error.bad_attribute", na.getName(), n.getLocalName()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Preprocess attributes that can be expressions. Expression delimiters are stripped. |
| * <p> |
| * If value is null, checks if there are any NamedAttribute sub elements in the tree node, and if so, constructs |
| * a JspAttribute out of a child NamedAttribute node. |
| * |
| * @param el EL expression, if already parsed by the caller (so that we can skip reparsing it) |
| */ |
| private Node.JspAttribute getJspAttribute(TagAttributeInfo tai, String qName, String uri, String localName, |
| String value, Node n, ELNode.Nodes el, boolean dynamic) throws JasperException { |
| |
| Node.JspAttribute result = null; |
| |
| // XXX Is it an error to see "%=foo%" in non-Xml page? |
| // (We won't see "<%=foo%> in xml page because '<' is not a |
| // valid attribute value in xml). |
| |
| if (value != null) { |
| if (n.getRoot().isXmlSyntax() && value.startsWith("%=") && value.length() >= 3) { |
| result = new Node.JspAttribute(tai, qName, uri, localName, value.substring(2, value.length() - 1), |
| true, null, dynamic); |
| } else if (!n.getRoot().isXmlSyntax() && value.startsWith("<%=") && value.length() >= 5) { |
| result = new Node.JspAttribute(tai, qName, uri, localName, value.substring(3, value.length() - 2), |
| true, null, dynamic); |
| } else { |
| if (!pageInfo.isELIgnored()) { |
| // The attribute can contain expressions but is not a |
| // scriptlet expression; thus, we want to run it through |
| // the expression interpreter |
| |
| // validate expression syntax if string contains |
| // expression(s) |
| if (el == null) { |
| el = ELParser.parse(value, pageInfo.isDeferredSyntaxAllowedAsLiteral()); |
| } |
| |
| if (el.containsEL()) { |
| validateFunctions(el, n); |
| } else { |
| // Get text with \$ and \# escaping removed. |
| // Should be a single Text node |
| Iterator<ELNode> it = el.iterator(); |
| if (it.hasNext()) { |
| value = ((ELNode.Text) it.next()).getText(); |
| } else { |
| value = ""; |
| } |
| el = null; |
| } |
| } |
| |
| if (n instanceof Node.UninterpretedTag && n.getRoot().isXmlSyntax()) { |
| // Attribute values of uninterpreted tags will have been |
| // XML un-escaped during parsing. Since these attributes |
| // are part of an uninterpreted tag the value needs to |
| // be re-escaped before being included in the output. |
| // The wrinkle is that the output of any EL must not be |
| // re-escaped as that must be output as is. |
| if (el != null) { |
| XmlEscapeNonELVisitor v = |
| new XmlEscapeNonELVisitor(pageInfo.isDeferredSyntaxAllowedAsLiteral()); |
| el.visit(v); |
| value = v.getText(); |
| } else { |
| value = Escape.xml(value); |
| } |
| } |
| |
| result = new Node.JspAttribute(tai, qName, uri, localName, value, false, el, dynamic); |
| |
| if (el != null) { |
| ELContextImpl ctx = new ELContextImpl(); |
| ctx.setFunctionMapper(getFunctionMapper(el)); |
| |
| try { |
| result.validateEL(this.pageInfo.getExpressionFactory(), ctx); |
| } catch (ELException e) { |
| this.err.jspError(n.getStart(), "jsp.error.invalid.expression", value, e.toString()); |
| } |
| } |
| } |
| } else { |
| // Value is null. Check for any NamedAttribute subnodes |
| // that might contain the value for this attribute. |
| // Otherwise, the attribute wasn't found so we return null. |
| |
| Node.NamedAttribute namedAttributeNode = n.getNamedAttributeNode(qName); |
| if (namedAttributeNode != null) { |
| result = new Node.JspAttribute(namedAttributeNode, tai, dynamic); |
| } |
| } |
| |
| return result; |
| } |
| |
| |
| private static class XmlEscapeNonELVisitor extends ELParser.TextBuilder { |
| |
| protected XmlEscapeNonELVisitor(boolean isDeferredSyntaxAllowedAsLiteral) { |
| super(isDeferredSyntaxAllowedAsLiteral); |
| } |
| |
| @Override |
| public void visit(Text n) throws JasperException { |
| output.append( |
| ELParser.escapeLiteralExpression(Escape.xml(n.getText()), isDeferredSyntaxAllowedAsLiteral)); |
| } |
| } |
| |
| |
| /* |
| * Return an empty StringBuilder [not thread-safe] |
| */ |
| private StringBuilder getBuffer() { |
| this.buf.setLength(0); |
| return this.buf; |
| } |
| |
| /* |
| * Checks to see if the given attribute value represents a runtime or EL expression. |
| */ |
| private boolean isExpression(Node n, String value, boolean checkDeferred) { |
| |
| boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && value.startsWith("%=")) || |
| (!n.getRoot().isXmlSyntax() && value.startsWith("<%="))); |
| boolean elExpression = false; |
| |
| if (!runtimeExpression && !pageInfo.isELIgnored()) { |
| Iterator<ELNode> nodes = ELParser.parse(value, pageInfo.isDeferredSyntaxAllowedAsLiteral()).iterator(); |
| while (nodes.hasNext()) { |
| ELNode node = nodes.next(); |
| if (node instanceof ELNode.Root) { |
| if (((ELNode.Root) node).getType() == '$') { |
| elExpression = true; |
| break; |
| } else if (checkDeferred && !pageInfo.isDeferredSyntaxAllowedAsLiteral() && |
| ((ELNode.Root) node).getType() == '#') { |
| elExpression = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| return runtimeExpression || elExpression; |
| |
| } |
| |
| /* |
| * Throws exception if the value of the attribute with the given name in the given node is given as an RT or EL |
| * expression, but the spec requires a static value. |
| */ |
| private void throwErrorIfExpression(Node n, String attrName, String actionName) throws JasperException { |
| if (n.getAttributes() != null && n.getAttributes().getValue(attrName) != null && |
| isExpression(n, n.getAttributes().getValue(attrName), true)) { |
| err.jspError(n, "jsp.error.attribute.standard.non_rt_with_expr", attrName, actionName); |
| } |
| } |
| |
| private static class NamedAttributeVisitor extends Node.Visitor { |
| private boolean hasDynamicContent; |
| |
| @Override |
| public void doVisit(Node n) throws JasperException { |
| if (!(n instanceof Node.JspText) && !(n instanceof Node.TemplateText)) { |
| hasDynamicContent = true; |
| } |
| visitBody(n); |
| } |
| |
| public boolean hasDynamicContent() { |
| return hasDynamicContent; |
| } |
| } |
| |
| private String findUri(String prefix, Node n) { |
| |
| for (Node p = n; p != null; p = p.getParent()) { |
| Attributes attrs = p.getTaglibAttributes(); |
| if (attrs == null) { |
| continue; |
| } |
| for (int i = 0; i < attrs.getLength(); i++) { |
| String name = attrs.getQName(i); |
| int k = name.indexOf(':'); |
| if (prefix == null && k < 0) { |
| // prefix not specified and a default ns found |
| return attrs.getValue(i); |
| } |
| if (prefix != null && k >= 0 && prefix.equals(name.substring(k + 1))) { |
| return attrs.getValue(i); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Validate functions in EL expressions |
| */ |
| private void validateFunctions(ELNode.Nodes el, Node n) throws JasperException { |
| |
| class FVVisitor extends ELNode.Visitor { |
| |
| private final Node n; |
| |
| FVVisitor(Node n) { |
| this.n = n; |
| } |
| |
| @Override |
| public void visit(ELNode.Function func) throws JasperException { |
| String prefix = func.getPrefix(); |
| String function = func.getName(); |
| String uri = null; |
| |
| if (n.getRoot().isXmlSyntax()) { |
| uri = findUri(prefix, n); |
| } else if (prefix != null) { |
| uri = pageInfo.getURI(prefix); |
| } |
| |
| if (uri == null) { |
| if (prefix == null) { |
| // This can occur when lambda expressions define |
| // functions and when functions are imported. No |
| // longer able to be sure this is an error. |
| return; |
| } else { |
| err.jspError(n, "jsp.error.attribute.invalidPrefix", prefix); |
| } |
| } |
| TagLibraryInfo taglib = pageInfo.getTaglib(uri); |
| FunctionInfo funcInfo = null; |
| if (taglib != null) { |
| funcInfo = taglib.getFunction(function); |
| } |
| if (funcInfo == null) { |
| err.jspError(n, "jsp.error.noFunction", function); |
| } |
| // Skip TLD function uniqueness check. Done by Schema ? |
| func.setUri(uri); |
| func.setFunctionInfo(funcInfo); |
| processSignature(func); |
| } |
| } |
| |
| el.visit(new FVVisitor(n)); |
| } |
| |
| private void prepareExpression(ELNode.Nodes el, Node n, String expr) throws JasperException { |
| validateFunctions(el, n); |
| |
| // test it out |
| ELContextImpl ctx = new ELContextImpl(); |
| ctx.setFunctionMapper(this.getFunctionMapper(el)); |
| ExpressionFactory ef = this.pageInfo.getExpressionFactory(); |
| try { |
| ef.createValueExpression(ctx, expr, Object.class); |
| } catch (ELException e) { |
| throw new JasperException(e); |
| } |
| } |
| |
| private void processSignature(ELNode.Function func) throws JasperException { |
| func.setMethodName(getMethod(func)); |
| func.setParameters(getParameters(func)); |
| } |
| |
| /** |
| * Get the method name from the signature. |
| */ |
| private String getMethod(ELNode.Function func) throws JasperException { |
| FunctionInfo funcInfo = func.getFunctionInfo(); |
| String signature = funcInfo.getFunctionSignature(); |
| |
| Matcher m = METHOD_NAME_PATTERN.matcher(signature); |
| if (!m.matches()) { |
| err.jspError("jsp.error.tld.fn.invalid.signature", func.getPrefix(), func.getName()); |
| } |
| |
| return m.group(1); |
| } |
| |
| /** |
| * Get the parameters types from the function signature. |
| * |
| * @return An array of parameter class names |
| */ |
| private String[] getParameters(ELNode.Function func) throws JasperException { |
| FunctionInfo funcInfo = func.getFunctionInfo(); |
| String signature = funcInfo.getFunctionSignature(); |
| List<String> params = new ArrayList<>(); |
| // Signature is of the form |
| // <return-type> S <method-name S? '(' |
| // < <arg-type> ( ',' <arg-type> )* )? ')' |
| int start = signature.indexOf('(') + 1; |
| boolean lastArg = false; |
| while (true) { |
| int p = signature.indexOf(',', start); |
| if (p < 0) { |
| p = signature.indexOf(')', start); |
| if (p < 0) { |
| err.jspError("jsp.error.tld.fn.invalid.signature", func.getPrefix(), func.getName()); |
| } |
| lastArg = true; |
| } |
| String arg = signature.substring(start, p).trim(); |
| if (!arg.isEmpty()) { |
| params.add(arg); |
| } |
| if (lastArg) { |
| break; |
| } |
| start = p + 1; |
| } |
| return params.toArray(new String[0]); |
| } |
| |
| private FunctionMapper getFunctionMapper(ELNode.Nodes el) throws JasperException { |
| |
| class ValidateFunctionMapper extends FunctionMapper { |
| |
| private final Map<String,Method> fnmap = new HashMap<>(); |
| |
| @Override |
| public void mapFunction(String prefix, String localName, Method method) { |
| fnmap.put(prefix + ":" + localName, method); |
| } |
| |
| @Override |
| public Method resolveFunction(String prefix, String localName) { |
| return this.fnmap.get(prefix + ":" + localName); |
| } |
| } |
| |
| class MapperELVisitor extends ELNode.Visitor { |
| private final ValidateFunctionMapper fmapper; |
| |
| MapperELVisitor(ValidateFunctionMapper fmapper) { |
| this.fmapper = fmapper; |
| } |
| |
| @SuppressWarnings("null") // c can't be null after catch block |
| @Override |
| public void visit(ELNode.Function n) throws JasperException { |
| |
| // Lambda / ImportHandler defined function |
| if (n.getFunctionInfo() == null) { |
| return; |
| } |
| |
| Class<?> c = null; |
| Method method = null; |
| try { |
| c = loader.loadClass(n.getFunctionInfo().getFunctionClass()); |
| } catch (ClassNotFoundException e) { |
| err.jspError("jsp.error.function.classnotfound", n.getFunctionInfo().getFunctionClass(), |
| n.getPrefix() + ':' + n.getName(), e.getMessage()); |
| } |
| String[] paramTypes = n.getParameters(); |
| int size = paramTypes.length; |
| Class<?>[] params = new Class[size]; |
| int i = 0; |
| try { |
| for (i = 0; i < size; i++) { |
| params[i] = JspUtil.toClass(paramTypes[i], loader); |
| } |
| method = c.getDeclaredMethod(n.getMethodName(), params); |
| } catch (ClassNotFoundException e) { |
| err.jspError("jsp.error.signature.classnotfound", paramTypes[i], |
| n.getPrefix() + ':' + n.getName(), e.getMessage()); |
| } catch (NoSuchMethodException e) { |
| err.jspError("jsp.error.noFunctionMethod", n.getMethodName(), n.getName(), c.getName()); |
| } |
| fmapper.mapFunction(n.getPrefix(), n.getName(), method); |
| } |
| } |
| |
| ValidateFunctionMapper fmapper = new ValidateFunctionMapper(); |
| el.visit(new MapperELVisitor(fmapper)); |
| return fmapper; |
| } |
| } // End of ValidateVisitor |
| |
| /** |
| * A visitor for validating TagExtraInfo classes of all tags |
| */ |
| private static class TagExtraInfoVisitor extends Node.Visitor { |
| |
| private final ErrorDispatcher err; |
| |
| /* |
| * Constructor |
| */ |
| TagExtraInfoVisitor(Compiler compiler) { |
| this.err = compiler.getErrorDispatcher(); |
| } |
| |
| @Override |
| public void visit(Node.CustomTag n) throws JasperException { |
| TagInfo tagInfo = n.getTagInfo(); |
| if (tagInfo == null) { |
| err.jspError(n, "jsp.error.missing.tagInfo", n.getQName()); |
| return; |
| } |
| |
| ValidationMessage[] errors = tagInfo.validate(n.getTagData()); |
| if (errors != null && errors.length != 0) { |
| StringBuilder errMsg = new StringBuilder(); |
| errMsg.append("<h3>"); |
| errMsg.append(Localizer.getMessage("jsp.error.tei.invalid.attributes", n.getQName())); |
| errMsg.append("</h3>"); |
| for (ValidationMessage error : errors) { |
| errMsg.append("<p>"); |
| if (error.getId() != null) { |
| errMsg.append(error.getId()); |
| errMsg.append(": "); |
| } |
| errMsg.append(error.getMessage()); |
| errMsg.append("</p>"); |
| } |
| |
| err.jspError(n, errMsg.toString()); |
| } |
| |
| visitBody(n); |
| } |
| } |
| |
| public static void validateDirectives(Compiler compiler, Node.Nodes page) throws JasperException { |
| page.visit(new DirectiveVisitor(compiler)); |
| } |
| |
| public static void validateExDirectives(Compiler compiler, Node.Nodes page) throws JasperException { |
| // Determine the default output content type |
| PageInfo pageInfo = compiler.getPageInfo(); |
| String contentType = pageInfo.getContentType(); |
| |
| if (contentType == null || !contentType.contains("charset=")) { |
| boolean isXml = page.getRoot().isXmlSyntax(); |
| String defaultType; |
| defaultType = Objects.requireNonNullElse(contentType, isXml ? "text/xml" : "text/html"); |
| |
| String charset = null; |
| if (isXml) { |
| charset = "UTF-8"; |
| } else { |
| if (!page.getRoot().isDefaultPageEncoding()) { |
| charset = page.getRoot().getPageEncoding(); |
| } |
| } |
| |
| if (charset != null) { |
| pageInfo.setContentType(defaultType + ";charset=" + charset); |
| } else { |
| pageInfo.setContentType(defaultType); |
| } |
| } |
| |
| /* |
| * Validate all other nodes. This validation step includes checking a custom tag's mandatory and optional |
| * attributes against information in the TLD (first validation step for custom tags according to JSP.10.5). |
| */ |
| page.visit(new ValidateVisitor(compiler)); |
| |
| /* |
| * Invoke TagLibraryValidator classes of all imported tags (second validation step for custom tags according to |
| * JSP.10.5). |
| */ |
| validateXmlView(new PageDataImpl(page, compiler), compiler); |
| |
| /* |
| * Invoke TagExtraInfo method isValid() for all imported tags (third validation step for custom tags according |
| * to JSP.10.5). |
| */ |
| page.visit(new TagExtraInfoVisitor(compiler)); |
| |
| } |
| |
| // ********************************************************************* |
| // Private (utility) methods |
| |
| /** |
| * Validate XML view against the TagLibraryValidator classes of all imported tag libraries. |
| */ |
| private static void validateXmlView(PageData xmlView, Compiler compiler) throws JasperException { |
| |
| StringBuilder errMsg = null; |
| ErrorDispatcher errDisp = compiler.getErrorDispatcher(); |
| |
| for (Object o : compiler.getPageInfo().getTaglibs()) { |
| |
| if (!(o instanceof TagLibraryInfoImpl tli)) { |
| continue; |
| } |
| |
| ValidationMessage[] errors = tli.validate(xmlView); |
| if ((errors != null) && (errors.length != 0)) { |
| if (errMsg == null) { |
| errMsg = new StringBuilder(); |
| } |
| errMsg.append("<h3>"); |
| errMsg.append(Localizer.getMessage("jsp.error.tlv.invalid.page", tli.getShortName(), |
| compiler.getPageInfo().getJspFile())); |
| errMsg.append("</h3>"); |
| for (ValidationMessage error : errors) { |
| if (error != null) { |
| errMsg.append("<p>"); |
| errMsg.append(error.getId()); |
| errMsg.append(": "); |
| errMsg.append(error.getMessage()); |
| errMsg.append("</p>"); |
| } |
| } |
| } |
| } |
| |
| if (errMsg != null) { |
| errDisp.jspError(errMsg.toString()); |
| } |
| } |
| } |