/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. * * * This file incorporates work covered by the following copyright and * permission notice: * * Copyright 2004 The Apache Software Foundation * * Licensed 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.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import javax.el.FunctionMapper; import javax.servlet.jsp.tagext.FunctionInfo; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.PageData; import javax.servlet.jsp.tagext.TagAttributeInfo; import javax.servlet.jsp.tagext.TagData; import javax.servlet.jsp.tagext.TagExtraInfo; import javax.servlet.jsp.tagext.TagInfo; import javax.servlet.jsp.tagext.TagLibraryInfo; import javax.servlet.jsp.tagext.ValidationMessage; import org.apache.jasper.Constants; import org.apache.jasper.JasperException; import org.apache.jasper.JspCompilationContext; import org.apache.jasper.runtime.JspRuntimeLibrary; 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. * * @author Kin-man Chung * @author Jan Luehe * @author Shawn Bayern * @author Mark Roth */ class Validator { /** * A visitor to validate and extract page directive info */ static class DirectiveVisitor extends Node.Visitor { private PageInfo pageInfo; private 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("isThreadSafe"), 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("deferredSyntaxAllowedAsLiteral"), new JspUtil.ValidAttribute("trimDirectiveWhitespaces") }; private boolean pageEncodingSeen = false; /* * Constructor */ DirectiveVisitor(Compiler compiler) throws JasperException { this.pageInfo = compiler.getPageInfo(); this.err = compiler.getErrorDispatcher(); } 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; } 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, n); } 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 ("isThreadSafe".equals(attr)) { if (pageInfo.getIsThreadSafe() == null) { pageInfo.setIsThreadSafe(value, n, err); } else if (!pageInfo.getIsThreadSafe().equals(value)) { err.jspError(n, "jsp.error.page.conflict.isthreadsafe", pageInfo.getIsThreadSafe(), 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 ("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; /* * Report any encoding conflict, treating "UTF-16", * "UTF-16BE", and "UTF-16LE" as identical. */ comparePageEncodings(value, n); } 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.deferred", 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.trim", pageInfo.getTrimDirectiveWhitespaces(), value); } } } // Check for bad combinations if (pageInfo.getBuffer() == 0 && !pageInfo.isAutoFlush()) err.jspError(n, "jsp.error.page.badCombo"); // Error pages must not be self-referencing if (pageInfo.isErrorPage() && pageInfo.getErrorPage() != null) { String rootPath = pageInfo.getRootPath(); String errorPath = pageInfo.getErrorPage(); if (!errorPath.startsWith("/")) { // Error page location is relative to root path String baseRootPath = rootPath.substring(0, rootPath.lastIndexOf('/')); errorPath = baseRootPath + '/' + errorPath; } if (rootPath.equals(errorPath)) { err.jspError(n, "jsp.error.page.selfreferencing", rootPath); } } // Attributes for imports for this node have been processed by // the parsers, just add them to pageInfo. pageInfo.addImports(n.getImports()); } 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 ("pageEncoding".equals(attr)) { if (pageEncodingSeen) err.jspError(n, "jsp.error.tag.multi.pageencoding"); pageEncodingSeen = true; // Make sure that if tag file is preceded by BOM, the // BOM encoding matches the value of the pageEncoding // attribute of the tag directive if (n.getRoot().hasBom()) { String bom = n.getRoot().getPageEncoding(); // Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as // identical. if (value != null && !value.equalsIgnoreCase(bom) && (!value.toLowerCase().startsWith("utf-16") || !bom.toLowerCase().startsWith("utf-16"))) { err.jspError( n, "jsp.error.bom_tagdir_encoding_mismatch", bom, value); } } 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.deferred", 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.trim", pageInfo.getTrimDirectiveWhitespaces(), value); } } } // Attributes for imports for this node have been processed by // the parsers, just add them to pageInfo. pageInfo.addImports(n.getImports()); } 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 } 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 } /* * Ensures that the page encoding specified in the 'pageEncoding' * attribute of the page directive matches the page encodings specified * in other applicable authoritative page encoding sources. * * @param pageDirEnc The value of the pageEncoding attribute of the * page directive * @param pageDir The page directive node * * @throws JasperException if page encodings do not match */ private void comparePageEncodings(String pageDirEnc, Node.PageDirective pageDir) throws JasperException { Node.Root root = pageDir.getRoot(); String configEnc = root.getJspConfigPageEncoding(); /* * 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 && !pageDirEnc.equalsIgnoreCase(configEnc) && (!pageDirEnc.toLowerCase().startsWith("utf-16") || !configEnc.toLowerCase().startsWith("utf-16"))) { err.jspError(pageDir, "jsp.error.config_pagedir_encoding_mismatch", configEnc, pageDirEnc); } /* * 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()) { String pageEnc = root.getPageEncoding(); if (!pageDirEnc.equalsIgnoreCase(pageEnc) && (!pageDirEnc.toLowerCase().startsWith("utf-16") || !pageEnc.toLowerCase().startsWith("utf-16"))) { err.jspError(pageDir, "jsp.error.prolog_pagedir_encoding_mismatch", pageEnc, pageDirEnc); } } /* * Compare the 'pageEncoding' attribute of the page directive with * the encoding declared by BOM (if present). * Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as identical. */ if (root.hasBom()) { String pageEnc = root.getPageEncoding(); if (!pageDirEnc.equalsIgnoreCase(pageEnc) && (!pageDirEnc.toLowerCase().startsWith("utf-16") || !pageEnc.toLowerCase().startsWith("utf-16"))) { err.jspError(pageDir, "jsp.error.bom_pagedir_encoding_mismatch", pageEnc, pageDirEnc); } } } } /** * A visitor for validating nodes other than page directives */ static class ValidateVisitor extends Node.Visitor { private PageInfo pageInfo; private ErrorDispatcher err; private ClassLoader loader; private JspCompilationContext ctxt; 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[] plugInAttrs = { new JspUtil.ValidAttribute("type",true), new JspUtil.ValidAttribute("code", true), new JspUtil.ValidAttribute("codebase"), new JspUtil.ValidAttribute("align"), new JspUtil.ValidAttribute("archive"), new JspUtil.ValidAttribute("height", false), new JspUtil.ValidAttribute("hspace"), new JspUtil.ValidAttribute("jreversion"), new JspUtil.ValidAttribute("name"), new JspUtil.ValidAttribute("vspace"), new JspUtil.ValidAttribute("width", false), new JspUtil.ValidAttribute("nspluginurl"), new JspUtil.ValidAttribute("iepluginurl") }; 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") }; /* * Constructor */ ValidateVisitor(Compiler compiler) { this.pageInfo = compiler.getPageInfo(); this.err = compiler.getErrorDispatcher(); this.ctxt = compiler.getCompilationContext(); this.loader = ctxt.getClassLoader(); } 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")) { err.jspError(n, "jsp.error.jsproot.version.invalid", version); } visitBody(n); } public void visit(Node.IncludeDirective n) throws JasperException { JspUtil.checkAttributes("Include directive", n, includeDirectiveAttrs, err); visitBody(n); } 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"); } } 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("value", null, null, n.getAttributeValue("value"), n, false, null)); visitBody(n); } public void visit(Node.ParamsAction n) throws JasperException { // Make sure we've got at least one nested jsp:param Node.Nodes subElems = n.getBody(); if (subElems == null) { err.jspError(n, "jsp.error.params.emptyBody"); } visitBody(n); } public void visit(Node.IncludeAction n) throws JasperException { JspUtil.checkAttributes("Include action", n, includeActionAttrs, err); n.setPage(getJspAttribute("page", null, null, n.getAttributeValue("page"), n, false, null)); visitBody(n); }; public void visit(Node.ForwardAction n) throws JasperException { JspUtil.checkAttributes("Forward", n, forwardActionAttrs, err); n.setPage(getJspAttribute("page", null, null, n.getAttributeValue("page"), n, false, null)); visitBody(n); } public void visit(Node.GetProperty n) throws JasperException { JspUtil.checkAttributes("GetProperty", n, getPropertyAttrs, err); } 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("value", null, null, value, n, false, null)); 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); } 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", name); if ("session".equals(scope) && !pageInfo.isSession()) err.jspError(n, "jsp.error.usebean.noSession"); Node.JspAttribute jattr = getJspAttribute("beanName", null, null, n.getAttributeValue("beanName"), n, false, null); 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); } public void visit(Node.PlugIn n) throws JasperException { JspUtil.checkAttributes("Plugin", n, plugInAttrs, err); throwErrorIfExpression(n, "type", "jsp:plugin"); throwErrorIfExpression(n, "code", "jsp:plugin"); throwErrorIfExpression(n, "codebase", "jsp:plugin"); throwErrorIfExpression(n, "align", "jsp:plugin"); throwErrorIfExpression(n, "archive", "jsp:plugin"); throwErrorIfExpression(n, "hspace", "jsp:plugin"); throwErrorIfExpression(n, "jreversion", "jsp:plugin"); throwErrorIfExpression(n, "name", "jsp:plugin"); throwErrorIfExpression(n, "vspace", "jsp:plugin"); throwErrorIfExpression(n, "nspluginurl", "jsp:plugin"); throwErrorIfExpression(n, "iepluginurl", "jsp:plugin"); String type = n.getTextAttribute("type"); if (type == null) err.jspError(n, "jsp.error.plugin.notype"); if (!type.equals("bean") && !type.equals("applet")) err.jspError(n, "jsp.error.plugin.badtype"); if (n.getTextAttribute("code") == null) err.jspError(n, "jsp.error.plugin.nocode"); Node.JspAttribute width = getJspAttribute("width", null, null, n.getAttributeValue("width"), n, false, null); n.setWidth( width ); Node.JspAttribute height = getJspAttribute("height", null, null, n.getAttributeValue("height"), n, false, null); n.setHeight( height ); visitBody(n); } public void visit(Node.NamedAttribute n) throws JasperException { JspUtil.checkAttributes("Attribute", n, attributeAttrs, err); String omit = n.getAttributeValue("omit"); if (omit != null) { n.setOmit(getJspAttribute("omit", null, null, omit, n, false, null)); } visitBody(n); } public void visit(Node.JspBody n) throws JasperException { visitBody(n); } public void visit(Node.Declaration n) throws JasperException { if (pageInfo.isScriptingInvalid()) { err.jspError(n.getStart(), "jsp.error.no.scriptlets"); } } public void visit(Node.Expression n) throws JasperException { if (pageInfo.isScriptingInvalid()) { err.jspError(n.getStart(), "jsp.error.no.scriptlets"); } } public void visit(Node.Scriptlet n) throws JasperException { if (pageInfo.isScriptingInvalid()) { err.jspError(n.getStart(), "jsp.error.no.scriptlets"); } } public void visit(Node.ELExpression n) throws JasperException { if (pageInfo.isELIgnored()) { return; } String expressions = n.getText(); if (expressions.charAt(0) == '#') { if (pageInfo.isDeferredSyntaxAllowedAsLiteral()) { return; } // For tag files, if the Jsp version for tag library is 2.0 or // less, #{} is not an error if (ctxt.isTagFile()) { String versionString = ctxt.getTagInfo().getTagLibrary().getRequiredVersion(); Double version = Double.valueOf(versionString).doubleValue(); if (version < 2.1) { return; } } err.jspError(n.getStart(), "jsp.error.not.in.template", "#{...}"); } ELNode.Nodes el = ELParser.parse(expressions); validateFunctions(el, n); JspUtil.validateExpressions( n.getStart(), expressions, getFunctionMapper(el), err); n.setEL(el); } 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++) { jspAttrs[i] = getJspAttribute(attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), attrs.getValue(i), n, false, null); } n.setJspAttributes(jspAttrs); } visitBody(n); } 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().equals(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 (int i=0; i<tldAttrs.length; i++) { String attr = null; if (attrs != null) { attr = attrs.getValue(tldAttrs[i].getName()); if (attr == null) { attr = attrs.getValue(customActionUri, tldAttrs[i].getName()); } } Node.NamedAttribute na = n.getNamedAttributeNode(tldAttrs[i].getName()); if (tldAttrs[i].isRequired() && attr == null && na == null) { err.jspError(n, "jsp.error.missing_attribute", tldAttrs[i].getName(), n.getLocalName()); } if (attr != null && na != null) { err.jspError(n, "jsp.error.duplicate.name.jspattribute", tldAttrs[i].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<String, Object>(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); } public void visit(Node.JspElement n) throws JasperException { Attributes attrs = n.getAttributes(); if (attrs == null) { err.jspError(n, "jsp.error.jspelement.missing.name"); } 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(attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), attrs.getValue(i), n, false, null)); } else { if (jspAttrIndex<jspAttrSize) { jspAttrs[jspAttrIndex++] = getJspAttribute(attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), attrs.getValue(i), n, false, null); } } } 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, false); } n.setJspAttributes(jspAttrs); visitBody(n); } 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.doctypepulicsystem"); } if (omitXmlDecl != null) { pageInfo.setOmitXmlDecl(omitXmlDecl); } if (doctypeName != null) { pageInfo.setDoctypeName(doctypeName); } if (doctypeSystem != null) { pageInfo.setDoctypeSystem(doctypeSystem); } if (doctypePublic != null) { pageInfo.setDoctypePublic(doctypePublic); } } 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"); } } 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"); } } /* * Check that the setter method exists for the tag handler and is * consistent with the TLD entries. * * This can probably be done globally, when taglib directives are * processed. We do it here so checking is done only for the * attributes are actually used. */ private void checkSetter(Node.CustomTag n, TagAttributeInfo tldattr) throws JasperException { Class handler = n.getTagHandlerClass(); if (handler == null) { // Handler unknown. Maybe tag file? return; } String handlerName = handler.getName(); String property = tldattr.getName(); Method setter = null; try { setter = JspRuntimeLibrary.getWriteMethod(handler, property); } catch (Exception ex) { } if (setter == null) { err.jspError(n, "jsp.error.setter.none", handlerName, property); } Class setterType = setter.getParameterTypes()[0]; String typeName = setterType.getName(); if (tldattr.isDeferredValue()) { if (tldattr.canBeRequestTime()) { if (! "java.lang.Object".equals(typeName)) { err.jspError(n, "jsp.error.setter.notobject", handlerName, property); } return; } if (! "javax.el.ValueExpression".equals(typeName)) { err.jspError(n, "jsp.error.setter.notvalueexpression", handlerName, property); } return; } if (tldattr.isDeferredMethod()) { if (! "javax.el.MethodExpression".equals(typeName)) { err.jspError(n, "jsp.error.setter.notmethodexpression", handlerName, property); } return; } /* Temporarily removed, as it breaks some JSF applications. Should be able to refine the check. String tldType = tldattr.getTypeName(); if (tldType != null && ! tldType.equals(typeName)) { err.jspError(n, "jsp.error.setter.notequal", handlerName, property); } */ } /* * 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, Hashtable<String, Object> tagDataAttrs) throws JasperException { TagInfo tagInfo = n.getTagInfo(); if (tagInfo == null) { err.jspError(n, "jsp.error.missing.tagInfo", n.getQName()); } TagAttributeInfo[] tldAttrs = tagInfo.getAttributes(); Attributes attrs = n.getAttributes(); for (int i=0; attrs != null && i<attrs.getLength(); i++) { boolean found = false; 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).length() == 0 || attrs.getURI(i).equals(n.getURI()))) { checkSetter(n, tldAttrs[j]); if (tldAttrs[j].canBeRequestTime() || tldAttrs[j].isDeferredValue() || tldAttrs[j].isDeferredMethod()) { jspAttrs[i] = getJspAttribute(attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), attrs.getValue(i), n, false, tldAttrs[j]); ELNode.Nodes el = jspAttrs[i].getEL(); if (el != null) { if (el.hasDollarExpression()) { if (!tldAttrs[j].canBeRequestTime()) { err.jspError(n, "jsp.error.el.deferred.dollar", tldAttrs[j].getName()); } } else if (el.hasPoundExpression()) { boolean isLiteral = pageInfo.isDeferredSyntaxAllowedAsLiteral(); if (!tldAttrs[j].isDeferredValue() && !tldAttrs[j].isDeferredMethod()){ if (n.getJspVersion() >= 2.1 && !isLiteral){ err.jspError(n, "jsp.error.el.nondeferred.pound", tldAttrs[j].getName()); } else { isLiteral = true; } } if (isLiteral) { // #{} needs to be escaped before // sending it to EL interpreter jspAttrs[i].setValue(escapePound( jspAttrs[i].getValue())); } } else if (pageInfo.isDeferredSyntaxAllowedAsLiteral()){ // The literal is of the form \#{} jspAttrs[i].setValue(escapePound( jspAttrs[i].getValue())); } } } else { // Attribute does not accept any expressions. // Make sure its value does not contain any. String litAttr = getLiteral(n, attrs.getValue(i)); if (litAttr == null) { err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr", tldAttrs[j].getName()); } jspAttrs[i] = new Node.JspAttribute(attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), litAttr, false, null, false); } if (jspAttrs[i].isExpression()) { tagDataAttrs.put(attrs.getQName(i), TagData.REQUEST_TIME_VALUE); } else { tagDataAttrs.put(attrs.getQName(i), attrs.getValue(i)); } found = true; break; } } if (!found) { if (tagInfo.hasDynamicAttributes()) { jspAttrs[i] = getJspAttribute(attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), attrs.getValue(i), n, true, null); } 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, Hashtable<String,Object> tagDataAttrs) throws JasperException { TagInfo tagInfo = n.getTagInfo(); if (tagInfo == null) { err.jspError(n, "jsp.error.missing.tagInfo", n.getQName()); } 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 (int j=0; j<tldAttrs.length; j++) { /* * 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(tldAttrs[j].getName()) && (attrPrefix == null || attrPrefix.length() == 0 || attrPrefix.equals(n.getPrefix()))) { jspAttrs[start + i] = new Node.JspAttribute(na, 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, 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 subelements in the tree node, and if so, * constructs a JspAttribute out of a child NamedAttribute node. */ private Node.JspAttribute getJspAttribute(String qName, String uri, String localName, String value, Node n, boolean dynamic, TagAttributeInfo tagAttr) 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("%=")) { result = new Node.JspAttribute( qName, uri, localName, value.substring(2, value.length()-1), true, null, dynamic); } else if(!n.getRoot().isXmlSyntax() && value.startsWith("<%=")) { result = new Node.JspAttribute( qName, uri, localName, value.substring(3, value.length()-2), true, null, dynamic); } else { // 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) ELNode.Nodes el = ELParser.parse(value); if (el.hasPoundExpression() && tagAttr == null && !dynamic){ // Action attributes cannot have #{...}, unless // explicitly allowed if (pageInfo.isELIgnored() || pageInfo.isDeferredSyntaxAllowedAsLiteral()) { result = new Node.JspAttribute(qName, uri, localName, getLiteral(n, value), false, null, false); } else { err.jspError(n, "jsp.error.el.action.pound"); } } else if ((el.containsEL() && !pageInfo.isELIgnored()) || ((tagAttr !=null) && (( tagAttr.isDeferredValue() && !tagAttr.canBeRequestTime()) || tagAttr.isDeferredMethod() ) ) ) { validateFunctions(el, n); JspUtil.validateExpressions( n.getStart(), value, getFunctionMapper(el), this.err); if (tagAttr != null && tagAttr.isDeferredValue()) { String expectedType = tagAttr.getExpectedTypeName(); result = new Node.JspAttribute( qName, uri, localName, value, el, expectedType, null, null); } else if (tagAttr != null && tagAttr.isDeferredMethod()){ String methodSignature = tagAttr.getMethodSignature(); String returnType = getReturnType(methodSignature); // It is an error to specify a string literal // as a value for MethodExpression if the literal // cannot be coerced to the return type if (!el.containsEL()) { try { Class typeClass = JspUtil.toClass(returnType, loader); JspUtil.coerce(typeClass, value); } catch (ClassNotFoundException ex) { err.jspError(n, "jsp.error.el.method.type", qName, returnType); } catch (Exception ex) { err.jspError(n, "jsp.error.el.method.literal", qName); } } result = new Node.JspAttribute( qName, uri, localName, value, el, null, getReturnType(methodSignature), getParameters(methodSignature)); } else { result = new Node.JspAttribute( qName, uri, localName, value, false, el, dynamic); } } else { result = new Node.JspAttribute(qName, uri, localName, getLiteral(n, value), false, null, dynamic); } } } 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, dynamic); } } return result; } /* * Checks to see if the given attribute value represents a string * or an expression (runtime or EL). If it is a literal, also process * escape sequences as a side-effect. Note that the treatment of the * EL syntax "#{}" depends on the JSP version in the TLD. * * * @return null, if the attribute is an expression * otherwise, the literal string for the attribute */ private String getLiteral(Node n, String value) { if (n.getRoot().isXmlSyntax() && value.startsWith("%=")) { return null; } if (!n.getRoot().isXmlSyntax() && value.startsWith("<%=")) { return null; } if (pageInfo.isELIgnored()) { return value; } boolean poundExpressionIgnored = (n instanceof Node.CustomTag) && ((((Node.CustomTag)n).getJspVersion() < 2.1) || pageInfo.isDeferredSyntaxAllowedAsLiteral()); int size = value.length(); StringBuilder buf = new StringBuilder(size); char p = ' '; for (int i = 0; i < size; i++) { char c = value.charAt(i); if (p == '$' && c == '{') { return null; } if (p == '#' && c == '{' && !poundExpressionIgnored) { return null; } if (p == '\\') { if (c == '\\' || c == '$' || (c == '#' && !poundExpressionIgnored)) { // If "#{..}" is not recognized as an El expression, // then "\#" is not an escape sequence. buf.append(c); p = ' '; } else { buf.append(p).append(c); p = c; } } else { p = c; if (p != '\\') { buf.append(c); } } } return buf.toString(); } /* * 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 && getLiteral(n, n.getAttributes().getValue(attrName))==null) { err.jspError(n, "jsp.error.attribute.standard.non_rt_with_expr", attrName, actionName); } } /* * Since the EL engine reconginizes escape sequence for #{ }, literals * sent to it must be properly escaped, so "#{}" becomes "\#{}", and * "\#{}" becomes "\\\#{}" etc. */ private String escapePound(String value) { if (value.indexOf("#{") < 0) { return value; } StringBuilder buf = new StringBuilder(value.length() + 2); for (int i = 0; i < value.length(); i++) { if (value.charAt(i) == '#') { if (((i+1) < value.length()) && (value.charAt(i+1) == '{')){ if (((i-1) >= 0) && (value.charAt(i-1) == '\\')) { buf.append('\\'); } buf.append('\\'); } } buf.append(value.charAt(i)); } return buf.toString(); } private static class NamedAttributeVisitor extends Node.Visitor { private boolean hasDynamicContent; 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 { Node n; FVVisitor(Node n) { this.n = n; } 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) { // in EL 3.0, this can be a lambda expression call. return; /* err.jspError(n, "jsp.error.noFunctionPrefix", function); */ } 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 processSignature(ELNode.Function func) throws JasperException { FunctionInfo funcInfo = func.getFunctionInfo(); String signature = funcInfo.getFunctionSignature(); func.setMethodName(getMethod(signature)); func.setParameters(getParameters(signature)); } /** * Get the return type from the signature. */ private String getReturnType(String signature) throws JasperException { int start = signature.indexOf(' '); if (start < 0) { err.jspError("jsp.error.tld.invalid.signature", signature); } return signature.substring(0, start); } /** * Get the method name from the signature. */ private String getMethod(String signature) throws JasperException { int start = signature.indexOf(' '); if (start < 0) { err.jspError("jsp.error.tld.invalid.signature", signature); } int end = signature.indexOf('('); if (end < 0) { err.jspError("jsp.error.tld.invalid.signature", signature); } return signature.substring(start+1, end).trim(); } /** * Get the parameters types from the function signature. * @return An array of parameter class names */ private String[] getParameters(String signature) throws JasperException { ArrayList<String> params = new ArrayList<String>(); // 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.invalid.signature", signature); } lastArg = true; } String arg = signature.substring(start, p).trim(); if (!"".equals(arg)) { params.add(arg); } if (lastArg) { break; } start = p+1; } return params.toArray(new String[params.size()]); } private FunctionMapper getFunctionMapper(ELNode.Nodes el) throws JasperException { class ValidateFunctionMapper extends FunctionMapper { private HashMap<String, Method> fnmap = new HashMap<String, Method>(); public void mapFunction(String fnQName, Method method) { fnmap.put(fnQName, method); } public Method resolveFunction(String prefix, String localName) { return (Method) this.fnmap.get(prefix + ":" + localName); } } class MapperELVisitor extends ELNode.Visitor { ValidateFunctionMapper fmapper; MapperELVisitor(ValidateFunctionMapper fmapper) { this.fmapper = fmapper; } public void visit(ELNode.Function n) throws JasperException { if (n.getUri() == null) { // Can be a lambda expression call 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()); } // Make sure that this method is public if (! Modifier.isPublic(method.getModifiers())) { err.jspError("jsp.error.nonPublicFunction", c.getName() + '.' + method.getName()); } // Make sure that this method is static if (! Modifier.isStatic(method.getModifiers())) { err.jspError("jsp.error.nonStaticFunction", c.getName() + '.' + method.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 */ static class TagExtraInfoVisitor extends Node.Visitor { private ErrorDispatcher err; /* * Constructor */ TagExtraInfoVisitor(Compiler compiler) { this.err = compiler.getErrorDispatcher(); } public void visit(Node.CustomTag n) throws JasperException { TagInfo tagInfo = n.getTagInfo(); if (tagInfo == null) { err.jspError(n, "jsp.error.missing.tagInfo", n.getQName()); } 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 (int i=0; i<errors.length; i++) { errMsg.append("<p>"); if (errors[i].getId() != null) { errMsg.append(errors[i].getId()); errMsg.append(": "); } errMsg.append(errors[i].getMessage()); errMsg.append("</p>"); } err.jspError(n, errMsg.toString()); } visitBody(n); } } public static void validate(Compiler compiler, Node.Nodes page) throws JasperException { /* * Visit the page/tag directives first, as they are global to the page * and are position independent. */ page.visit(new DirectiveVisitor(compiler)); PageInfo pageInfo = compiler.getPageInfo(); JspCompilationContext ctxt = compiler.getCompilationContext(); JspConfig jspConfig = ctxt.getOptions().getJspConfig(); JspProperty jspProperty = jspConfig.findJspProperty(ctxt.getJspFile()); // If the buffer size is not specified in a page directive, use the // default buffer specified in jsp config property if (pageInfo.getBufferValue() == null && jspProperty.getBuffer() != null) { pageInfo.setBufferValue(jspProperty.getBuffer(), null, compiler.getErrorDispatcher()); } // Determine the default output content type String contentType = pageInfo.getContentType(); // If the contentType is not specified in a page directive, use the // contentType specified in a jsp config property if (contentType == null) { contentType = jspProperty.getDefaultContentType(); } if (contentType == null || contentType.indexOf("charset=") < 0) { boolean isXml = page.getRoot().isXmlSyntax(); String defaultType; if (contentType == null) { defaultType = isXml? "text/xml": "text/html"; } else { defaultType = contentType; } 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 (Iterator<TagLibraryInfo> iter = compiler.getPageInfo().getTaglibs().iterator(); iter.hasNext(); ) { TagLibraryInfo o = iter.next(); if (!(o instanceof TagLibraryInfoImpl)) continue; TagLibraryInfoImpl tli = (TagLibraryInfoImpl) o; 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())); errMsg.append("</h3>"); for (int i=0; i<errors.length; i++) { if (errors[i] != null) { errMsg.append("<p>"); errMsg.append(errors[i].getId()); errMsg.append(": "); errMsg.append(errors[i].getMessage()); errMsg.append("</p>"); } } } } if (errMsg != null) { errDisp.jspError(errMsg.toString()); } } }