All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.wasp.compiler.Validator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022, 2022 Contributors to the Eclipse Foundation.
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 * 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.glassfish.wasp.compiler;

import static org.glassfish.wasp.compiler.JspUtil.checkAttributes;
import static org.glassfish.wasp.compiler.JspUtil.checkScope;

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 org.glassfish.wasp.JspCompilationContext;
import org.glassfish.wasp.WaspException;
import org.glassfish.wasp.runtime.JspRuntimeLibrary;
import org.xml.sax.Attributes;

import jakarta.el.FunctionMapper;
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;

/**
 * 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"),
                new JspUtil.ValidAttribute("errorOnELNotFound") };

        private boolean pageEncodingSeen = false;

        /*
         * Constructor
         */
        DirectiveVisitor(Compiler compiler) throws WaspException {
            this.pageInfo = compiler.getPageInfo();
            this.err = compiler.getErrorDispatcher();
        }

        @Override
        public void visit(Node.IncludeDirective n) throws WaspException {
            // 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 WaspException {
            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);
                    }
                } 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);
                    }
                }
            }

            // 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());
        }

        @Override
        public void visit(Node.TagDirective n) throws WaspException {
            // 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);
                    }
                } 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);
                    }
                }
            }

            // 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 WaspException {
            // 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 WaspException {
            // 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 WaspException if page encodings do not match
         */
        private void comparePageEncodings(String pageDirEnc, Node.PageDirective pageDir) throws WaspException {

            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();
        }

        @Override
        public void visit(Node.JspRoot n) throws WaspException {
            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);
        }

        @Override
        public void visit(Node.IncludeDirective n) throws WaspException {
            checkAttributes("Include directive", n, includeDirectiveAttrs, err);
            visitBody(n);
        }

        @Override
        public void visit(Node.TaglibDirective n) throws WaspException {
            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 WaspException {
            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);
        }

        @Override
        public void visit(Node.ParamsAction n) throws WaspException {
            // 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);
        }

        @Override
        public void visit(Node.IncludeAction n) throws WaspException {
            checkAttributes("Include action", n, includeActionAttrs, err);
            n.setPage(getJspAttribute("page", null, null, n.getAttributeValue("page"), n, false, null));
            visitBody(n);
        }

        @Override
        public void visit(Node.ForwardAction n) throws WaspException {
            checkAttributes("Forward", n, forwardActionAttrs, err);
            n.setPage(getJspAttribute("page", null, null, n.getAttributeValue("page"), n, false, null));
            visitBody(n);
        }

        @Override
        public void visit(Node.GetProperty n) throws WaspException {
            checkAttributes("GetProperty", n, getPropertyAttrs, err);
        }

        @Override
        public void visit(Node.SetProperty n) throws WaspException {
            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);
        }

        @Override
        public void visit(Node.UseBean useBean) throws WaspException {
            checkAttributes("UseBean", useBean, useBeanAttrs, err);

            String name = useBean.getTextAttribute("id");
            String scope = useBean.getTextAttribute("scope");
            checkScope(scope, useBean, err);
            String className = useBean.getTextAttribute("class");
            String type = useBean.getTextAttribute("type");
            BeanRepository beanInfo = pageInfo.getBeanRepository();

            if (className == null && type == null) {
                err.jspError(useBean, "jsp.error.usebean.missingType");
            }

            if (beanInfo.checkVariable(name)) {
                err.jspError(useBean, "jsp.error.usebean.duplicate", name);
            }

            if ("session".equals(scope) && !pageInfo.isSession()) {
                err.jspError(useBean, "jsp.error.usebean.noSession");
            }

            Node.JspAttribute jattr = getJspAttribute("beanName", null, null, useBean.getAttributeValue("beanName"), useBean, false, null);
            useBean.setBeanName(jattr);
            if (className != null && jattr != null) {
                err.jspError(useBean, "jsp.error.usebean.notBoth");
            }

            if (className == null) {
                className = type;
            }

            beanInfo.addBean(useBean, name, className, scope);

            visitBody(useBean);
        }

        @Override
        public void visit(Node.PlugIn plugIn) throws WaspException {
            checkAttributes("Plugin", plugIn, plugInAttrs, err);

            throwErrorIfExpression(plugIn, "type", "jsp:plugin");
            throwErrorIfExpression(plugIn, "code", "jsp:plugin");
            throwErrorIfExpression(plugIn, "codebase", "jsp:plugin");
            throwErrorIfExpression(plugIn, "align", "jsp:plugin");
            throwErrorIfExpression(plugIn, "archive", "jsp:plugin");
            throwErrorIfExpression(plugIn, "hspace", "jsp:plugin");
            throwErrorIfExpression(plugIn, "jreversion", "jsp:plugin");
            throwErrorIfExpression(plugIn, "name", "jsp:plugin");
            throwErrorIfExpression(plugIn, "vspace", "jsp:plugin");
            throwErrorIfExpression(plugIn, "nspluginurl", "jsp:plugin");
            throwErrorIfExpression(plugIn, "iepluginurl", "jsp:plugin");

            String type = plugIn.getTextAttribute("type");
            if (type == null) {
                err.jspError(plugIn, "jsp.error.plugin.notype");
            }
            if (!type.equals("bean") && !type.equals("applet")) {
                err.jspError(plugIn, "jsp.error.plugin.badtype");
            }
            if (plugIn.getTextAttribute("code") == null) {
                err.jspError(plugIn, "jsp.error.plugin.nocode");
            }

            Node.JspAttribute width = getJspAttribute("width", null, null, plugIn.getAttributeValue("width"), plugIn, false, null);
            plugIn.setWidth(width);

            Node.JspAttribute height = getJspAttribute("height", null, null, plugIn.getAttributeValue("height"), plugIn, false, null);
            plugIn.setHeight(height);

            visitBody(plugIn);
        }

        @Override
        public void visit(Node.NamedAttribute namedAttribute) throws WaspException {
            checkAttributes("Attribute", namedAttribute, attributeAttrs, err);
            String omit = namedAttribute.getAttributeValue("omit");
            if (omit != null) {
                namedAttribute.setOmit(getJspAttribute("omit", null, null, omit, namedAttribute, false, null));
            }
            visitBody(namedAttribute);
        }

        @Override
        public void visit(Node.JspBody pagesBody) throws WaspException {
            visitBody(pagesBody);
        }

        @Override
        public void visit(Node.Declaration declaration) throws WaspException {
            if (pageInfo.isScriptingInvalid()) {
                err.jspError(declaration.getStart(), "jsp.error.no.scriptlets");
            }
        }

        @Override
        public void visit(Node.Expression expression) throws WaspException {
            if (pageInfo.isScriptingInvalid()) {
                err.jspError(expression.getStart(), "jsp.error.no.scriptlets");
            }
        }

        @Override
        public void visit(Node.Scriptlet scriptlet) throws WaspException {
            if (pageInfo.isScriptingInvalid()) {
                err.jspError(scriptlet.getStart(), "jsp.error.no.scriptlets");
            }
        }

        @Override
        public void visit(Node.ELExpression expression) throws WaspException {
            if (pageInfo.isELIgnored()) {
                return;
            }

            String expressions = expression.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);
                    if (version < 2.1) {
                        return;
                    }
                }

                err.jspError(expression.getStart(), "jsp.error.not.in.template", "#{...}");
            }
            ELNode.Nodes el = ELParser.parse(expressions);
            validateFunctions(el, expression);
            JspUtil.validateExpressions(expression.getStart(), expressions, getFunctionMapper(el), err);
            expression.setEL(el);
        }

        @Override
        public void visit(Node.UninterpretedTag uninterpretedTag) throws WaspException {
            if (uninterpretedTag.getNamedAttributeNodes().size() != 0) {
                err.jspError(uninterpretedTag, "jsp.error.namedAttribute.invalidUse");
            }

            Attributes attrs = uninterpretedTag.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),
                                    uninterpretedTag, false, null);
                }
                uninterpretedTag.setJspAttributes(jspAttrs);
            }

            visitBody(uninterpretedTag);
        }

        @Override
        public void visit(Node.CustomTag customTag) throws WaspException {
            TagInfo tagInfo = customTag.getTagInfo();
            if (tagInfo == null) {
                err.jspError(customTag, "jsp.error.missing.tagInfo", customTag.getQName());
            }

            /*
             * The bodycontent of a SimpleTag cannot be JSP.
             */
            if (customTag.implementsSimpleTag() && tagInfo.getBodyContent().equals(TagInfo.BODY_CONTENT_JSP)) {
                err.jspError(customTag, "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() && !customTag.implementsDynamicAttributes()) {
                err.jspError(customTag, "jsp.error.dynamic.attributes.not.implemented", customTag.getQName());
            }

            /*
             * Make sure all required attributes are present, either as attributes or named attributes (). Also make
             * sure that the same attribute is not specified in both attributes or named attributes.
             */
            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
            String customActionUri = customTag.getURI();
            Attributes attrs = customTag.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 = customTag.getNamedAttributeNode(tldAttrs[i].getName());

                if (tldAttrs[i].isRequired() && attr == null && na == null) {
                    err.jspError(customTag, "jsp.error.missing_attribute", tldAttrs[i].getName(), customTag.getLocalName());
                }
                if (attr != null && na != null) {
                    err.jspError(customTag, "jsp.error.duplicate.name.jspattribute", tldAttrs[i].getName());
                }
            }

            Node.Nodes naNodes = customTag.getNamedAttributeNodes();
            int jspAttrsSize = naNodes.size() + attrsSize;
            Node.JspAttribute[] jspAttrs = null;
            if (jspAttrsSize > 0) {
                jspAttrs = new Node.JspAttribute[jspAttrsSize];
            }
            Hashtable tagDataAttrs = new Hashtable<>(attrsSize);

            checkXmlAttributes(customTag, jspAttrs, tagDataAttrs);
            checkNamedAttributes(customTag, 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", customTag.getQName());
            }

            customTag.setTagData(tagData);
            customTag.setJspAttributes(jspAttrs);

            visitBody(customTag);
        }

        @Override
        public void visit(Node.JspElement pagesElement) throws WaspException {
            Attributes attrs = pagesElement.getAttributes();
            if (attrs == null) {
                err.jspError(pagesElement, "jsp.error.jspelement.missing.name");
            }
            int xmlAttrLen = attrs.getLength();

            Node.Nodes namedAttrs = pagesElement.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))) {
                    pagesElement.setNameAttribute(getJspAttribute(attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), attrs.getValue(i), pagesElement, false, null));
                } else {
                    if (jspAttrIndex < jspAttrSize) {
                        jspAttrs[jspAttrIndex++] = getJspAttribute(attrs.getQName(i), attrs.getURI(i), attrs.getLocalName(i), attrs.getValue(i), pagesElement, false,
                                null);
                    }
                }
            }
            if (pagesElement.getNameAttribute() == null) {
                err.jspError(pagesElement, "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);
            }

            pagesElement.setJspAttributes(jspAttrs);

            visitBody(pagesElement);
        }

        @Override
        public void visit(Node.JspOutput pagesOutput) throws WaspException {
            checkAttributes("jsp:output", pagesOutput, jspOutputAttrs, err);

            if (pagesOutput.getBody() != null) {
                err.jspError(pagesOutput, "jsp.error.jspoutput.nonemptybody");
            }

            String omitXmlDecl = pagesOutput.getAttributeValue("omit-xml-declaration");
            String doctypeName = pagesOutput.getAttributeValue("doctype-root-element");
            String doctypePublic = pagesOutput.getAttributeValue("doctype-public");
            String doctypeSystem = pagesOutput.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(pagesOutput, "jsp.error.jspoutput.conflict", "omit-xml-declaration", omitXmlDeclOld, omitXmlDecl);
            }

            if (doctypeName != null && doctypeNameOld != null && !doctypeName.equals(doctypeNameOld)) {
                err.jspError(pagesOutput, "jsp.error.jspoutput.conflict", "doctype-root-element", doctypeNameOld, doctypeName);
            }

            if (doctypePublic != null && doctypePublicOld != null && !doctypePublic.equals(doctypePublicOld)) {
                err.jspError(pagesOutput, "jsp.error.jspoutput.conflict", "doctype-public", doctypePublicOld, doctypePublic);
            }

            if (doctypeSystem != null && doctypeSystemOld != null && !doctypeSystem.equals(doctypeSystemOld)) {
                err.jspError(pagesOutput, "jsp.error.jspoutput.conflict", "doctype-system", doctypeSystemOld, doctypeSystem);
            }

            if (doctypeName == null && doctypeSystem != null || doctypeName != null && doctypeSystem == null) {
                err.jspError(pagesOutput, "jsp.error.jspoutput.doctypenamesystem");
            }

            if (doctypePublic != null && doctypeSystem == null) {
                err.jspError(pagesOutput, "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);
            }
        }

        @Override
        public void visit(Node.InvokeAction invokeAction) throws WaspException {
            checkAttributes("Invoke", invokeAction, invokeAttrs, err);

            String scope = invokeAction.getTextAttribute("scope");
            checkScope(scope, invokeAction, err);

            String var = invokeAction.getTextAttribute("var");
            String varReader = invokeAction.getTextAttribute("varReader");
            if (scope != null && var == null && varReader == null) {
                err.jspError(invokeAction, "jsp.error.missing_var_or_varReader");
            }
            if (var != null && varReader != null) {
                err.jspError(invokeAction, "jsp.error.var_and_varReader");
            }
        }

        @Override
        public void visit(Node.DoBodyAction doBodyAction) throws WaspException {
            checkAttributes("DoBody", doBodyAction, doBodyAttrs, err);

            String scope = doBodyAction.getTextAttribute("scope");
            checkScope(scope, doBodyAction, err);

            String var = doBodyAction.getTextAttribute("var");
            String varReader = doBodyAction.getTextAttribute("varReader");
            if (scope != null && var == null && varReader == null) {
                err.jspError(doBodyAction, "jsp.error.missing_var_or_varReader");
            }
            if (var != null && varReader != null) {
                err.jspError(doBodyAction, "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 customTag, TagAttributeInfo tldattr) throws WaspException {
            Class handler = customTag.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(customTag, "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(customTag, "jsp.error.setter.notobject", handlerName, property);
                    }
                    return;
                }
                if (!"jakarta.el.ValueExpression".equals(typeName)) {
                    err.jspError(customTag, "jsp.error.setter.notvalueexpression", handlerName, property);
                }
                return;
            }
            if (tldattr.isDeferredMethod()) {
                if (!"jakarta.el.MethodExpression".equals(typeName)) {
                    err.jspError(customTag, "jsp.error.setter.notmethodexpression", handlerName, property);
                }
            }
            /*
             * 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:
         *
         * , 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:
         *
         * 
         *
         * 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 customTag, Node.JspAttribute[] jspAttrs, Hashtable tagDataAttrs) throws WaspException {
            TagInfo tagInfo = customTag.getTagInfo();
            if (tagInfo == null) {
                err.jspError(customTag, "jsp.error.missing.tagInfo", customTag.getQName());
            }

            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
            Attributes attrs = customTag.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(customTag.getURI()))) {

                        checkSetter(customTag, 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), customTag, false, tldAttrs[j]);
                            ELNode.Nodes el = jspAttrs[i].getEL();
                            if (el != null) {
                                if (el.hasDollarExpression()) {
                                    if (!tldAttrs[j].canBeRequestTime()) {
                                        err.jspError(customTag, "jsp.error.el.deferred.dollar", tldAttrs[j].getName());
                                    }
                                } else if (el.hasPoundExpression()) {
                                    boolean isLiteral = pageInfo.isDeferredSyntaxAllowedAsLiteral();
                                    if (!tldAttrs[j].isDeferredValue() && !tldAttrs[j].isDeferredMethod()) {
                                        if (customTag.getJspVersion() >= 2.1 && !isLiteral) {
                                            err.jspError(customTag, "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(customTag, attrs.getValue(i));
                            if (litAttr == null) {
                                err.jspError(customTag, "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), customTag, true, null);
                    } else {
                        err.jspError(customTag, "jsp.error.bad_attribute", attrs.getQName(i), customTag.getLocalName());
                    }
                }
            }
        }

        /*
         * Make sure the given custom action does not have any invalid named attributes
         */
        private void checkNamedAttributes(Node.CustomTag customTag, Node.JspAttribute[] jspAttrs, int start, Hashtable tagDataAttrs) throws WaspException {
            TagInfo tagInfo = customTag.getTagInfo();
            if (tagInfo == null) {
                err.jspError(customTag, "jsp.error.missing.tagInfo", customTag.getQName());
            }

            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
            Node.Nodes naNodes = customTag.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(customTag.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(customTag, "jsp.error.bad_attribute", na.getName(), customTag.getLocalName());
                    }
                }
            }
        }

        /**
         * Preprocess attributes that can be expressions. Expression delimiters are stripped.
         * 

* 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 WaspException { 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 node, String value) { if (node.getRoot().isXmlSyntax() && value.startsWith("%=")) { return null; } if (!node.getRoot().isXmlSyntax() && value.startsWith("<%=")) { return null; } if (pageInfo.isELIgnored()) { return value; } boolean poundExpressionIgnored = node instanceof Node.CustomTag && (((Node.CustomTag) node).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 node, String attrName, String actionName) throws WaspException { if (node.getAttributes() != null && node.getAttributes().getValue(attrName) != null && getLiteral(node, node.getAttributes().getValue(attrName)) == null) { err.jspError(node, "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) == '#') && (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; @Override public void doVisit(Node n) throws WaspException { if (!(n instanceof Node.JspText) && !(n instanceof Node.TemplateText)) { hasDynamicContent = true; } visitBody(n); } public boolean hasDynamicContent() { return hasDynamicContent; } } private String findUri(String prefix, Node node) { for (Node parentNode = node; parentNode != null; parentNode = parentNode.getParent()) { Attributes attrs = parentNode.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 WaspException { class FVVisitor extends ELNode.Visitor { Node n; FVVisitor(Node n) { this.n = n; } @Override public void visit(ELNode.Function func) throws WaspException { 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 function) throws WaspException { FunctionInfo funcInfo = function.getFunctionInfo(); String signature = funcInfo.getFunctionSignature(); function.setMethodName(getMethod(signature)); function.setParameters(getParameters(signature)); } /** * Get the return type from the signature. */ private String getReturnType(String signature) throws WaspException { 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 WaspException { 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 WaspException { ArrayList params = new ArrayList<>(); // Signature is of the form // S ( ',' )* )? ')' 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 WaspException { class ValidateFunctionMapper extends FunctionMapper { private HashMap fnmap = new HashMap<>(); public void mapFunction(String fnQName, Method method) { fnmap.put(fnQName, method); } @Override public Method resolveFunction(String prefix, String localName) { return this.fnmap.get(prefix + ":" + localName); } } class MapperELVisitor extends ELNode.Visitor { ValidateFunctionMapper fmapper; MapperELVisitor(ValidateFunctionMapper fmapper) { this.fmapper = fmapper; } @Override public void visit(ELNode.Function n) throws WaspException { 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(); } @Override public void visit(Node.CustomTag n) throws WaspException { 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("

"); errMsg.append(Localizer.getMessage("jsp.error.tei.invalid.attributes", n.getQName())); errMsg.append("

"); for (int i = 0; i < errors.length; i++) { errMsg.append("

"); if (errors[i].getId() != null) { errMsg.append(errors[i].getId()); errMsg.append(": "); } errMsg.append(errors[i].getMessage()); errMsg.append("

"); } err.jspError(n, errMsg.toString()); } visitBody(n); } } public static void validate(Compiler compiler, Node.Nodes page) throws WaspException { /* * 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 WaspException { StringBuilder errMsg = null; ErrorDispatcher errDisp = compiler.getErrorDispatcher(); for (Iterator 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("

"); errMsg.append(Localizer.getMessage("jsp.error.tlv.invalid.page", tli.getShortName())); errMsg.append("

"); for (int i = 0; i < errors.length; i++) { if (errors[i] != null) { errMsg.append("

"); errMsg.append(errors[i].getId()); errMsg.append(": "); errMsg.append(errors[i].getMessage()); errMsg.append("

"); } } } } if (errMsg != null) { errDisp.jspError(errMsg.toString()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy