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