TagLibraryInfoImpl.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.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.servlet.jsp.tagext.FunctionInfo;
import jakarta.servlet.jsp.tagext.PageData;
import jakarta.servlet.jsp.tagext.TagAttributeInfo;
import jakarta.servlet.jsp.tagext.TagExtraInfo;
import jakarta.servlet.jsp.tagext.TagFileInfo;
import jakarta.servlet.jsp.tagext.TagInfo;
import jakarta.servlet.jsp.tagext.TagLibraryInfo;
import jakarta.servlet.jsp.tagext.TagLibraryValidator;
import jakarta.servlet.jsp.tagext.TagVariableInfo;
import jakarta.servlet.jsp.tagext.ValidationMessage;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.apache.tomcat.Jar;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.descriptor.tld.TagFileXml;
import org.apache.tomcat.util.descriptor.tld.TagXml;
import org.apache.tomcat.util.descriptor.tld.TaglibXml;
import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
import org.apache.tomcat.util.descriptor.tld.ValidatorXml;
/**
* Implementation of the TagLibraryInfo class from the JSP spec.
*
* @author Anil K. Vijendran
* @author Mandar Raje
* @author Pierre Delisle
* @author Kin-man Chung
* @author Jan Luehe
*/
class TagLibraryInfoImpl extends TagLibraryInfo implements TagConstants {
private final JspCompilationContext ctxt;
private final PageInfo pi;
private final ErrorDispatcher err;
private final ParserController parserController;
private static void print(String name, String value, PrintWriter w) {
if (value != null) {
w.print(name + " = {\n\t");
w.print(value);
w.print("\n}\n");
}
}
@Override
public String toString() {
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
print("tlibversion", tlibversion, out);
print("jspversion", jspversion, out);
print("shortname", shortname, out);
print("urn", urn, out);
print("info", info, out);
print("uri", uri, out);
print("tagLibraryValidator", "" + tagLibraryValidator, out);
for (TagInfo tag : tags) {
out.println(tag.toString());
}
for (TagFileInfo tagFile : tagFiles) {
out.println(tagFile.toString());
}
for (FunctionInfo function : functions) {
out.println(function.toString());
}
return sw.toString();
}
TagLibraryInfoImpl(JspCompilationContext ctxt, ParserController pc,
PageInfo pi, String prefix, String uriIn,
TldResourcePath tldResourcePath, ErrorDispatcher err)
throws JasperException {
super(prefix, uriIn);
this.ctxt = ctxt;
this.parserController = pc;
this.pi = pi;
this.err = err;
if (tldResourcePath == null) {
// The URI points to the TLD itself or to a JAR file in which the TLD is stored
tldResourcePath = generateTldResourcePath(uri, ctxt);
}
try (Jar jar = tldResourcePath.openJar()) {
// Add the dependencies on the TLD to the referencing page
PageInfo pageInfo = ctxt.createCompiler().getPageInfo();
if (pageInfo != null) {
// If the TLD is in a JAR, that JAR may not be part of the web
// application
String path = tldResourcePath.getWebappPath();
if (path != null) {
// Add TLD (jar==null) / JAR (jar!=null) file to dependency list
// 2nd parameter is null since the path is always relative
// to the root of the web application even if we are
// processing a reference from a tag packaged in a JAR.
pageInfo.addDependant(path, ctxt.getLastModified(path, null));
}
if (jar != null) {
if (path == null) {
// JAR not in the web application so add it directly
URL jarUrl = jar.getJarFileURL();
long lastMod = -1;
URLConnection urlConn = null;
try {
urlConn = jarUrl.openConnection();
lastMod = urlConn.getLastModified();
} catch (IOException ioe) {
throw new JasperException(ioe);
} finally {
if (urlConn != null) {
try {
urlConn.getInputStream().close();
} catch (IOException e) {
// Ignore
}
}
}
pageInfo.addDependant(jarUrl.toExternalForm(),
Long.valueOf(lastMod));
}
// Add TLD within the JAR to the dependency list
String entryName = tldResourcePath.getEntryName();
try {
pageInfo.addDependant(jar.getURL(entryName),
Long.valueOf(jar.getLastModified(entryName)));
} catch (IOException ioe) {
throw new JasperException(ioe);
}
}
}
// Get the representation of the TLD
if (tldResourcePath.getUrl() == null) {
err.jspError("jsp.error.tld.missing", prefix, uri);
}
TaglibXml taglibXml =
ctxt.getOptions().getTldCache().getTaglibXml(tldResourcePath);
if (taglibXml == null) {
err.jspError("jsp.error.tld.missing", prefix, uri);
}
// Populate the TagLibraryInfo attributes
// Never null. jspError always throws an Exception
// Slightly convoluted so the @SuppressWarnings has minimal scope
@SuppressWarnings("null")
String v = taglibXml.getJspVersion();
this.jspversion = v;
this.tlibversion = taglibXml.getTlibVersion();
this.shortname = taglibXml.getShortName();
this.urn = taglibXml.getUri();
this.info = taglibXml.getInfo();
this.tagLibraryValidator = createValidator(taglibXml.getValidator());
List<TagInfo> tagInfos = new ArrayList<>();
for (TagXml tagXml : taglibXml.getTags()) {
tagInfos.add(createTagInfo(tagXml));
}
List<TagFileInfo> tagFileInfos = new ArrayList<>();
for (TagFileXml tagFileXml : taglibXml.getTagFiles()) {
tagFileInfos.add(createTagFileInfo(tagFileXml, jar));
}
Set<String> names = new HashSet<>();
List<FunctionInfo> functionInfos = taglibXml.getFunctions();
// TODO Move this validation to the parsing stage
for (FunctionInfo functionInfo : functionInfos) {
String name = functionInfo.getName();
if (!names.add(name)) {
err.jspError("jsp.error.tld.fn.duplicate.name", name, uri);
}
}
if (tlibversion == null) {
err.jspError("jsp.error.tld.mandatory.element.missing", "tlib-version", uri);
}
if (jspversion == null) {
err.jspError("jsp.error.tld.mandatory.element.missing", "jsp-version", uri);
}
this.tags = tagInfos.toArray(new TagInfo[0]);
this.tagFiles = tagFileInfos.toArray(new TagFileInfo[0]);
this.functions = functionInfos.toArray(new FunctionInfo[0]);
} catch (IOException ioe) {
throw new JasperException(ioe);
}
}
@Override
public TagLibraryInfo[] getTagLibraryInfos() {
Collection<TagLibraryInfo> coll = pi.getTaglibs();
return coll.toArray(new TagLibraryInfo[0]);
}
/*
* @param uri The uri of the TLD
* @param ctxt The compilation context
*
* @return the location of the TLD identified by the uri
*/
private TldResourcePath generateTldResourcePath(String uri,
JspCompilationContext ctxt) throws JasperException {
// TODO: this matches the current implementation but the URL logic looks fishy
// map URI to location per JSP 7.3.6.2
if (uri.indexOf(':') != -1) {
// abs_uri, this was not found in the taglibMap so raise an error
err.jspError("jsp.error.taglibDirective.absUriCannotBeResolved", uri);
} else if (uri.charAt(0) != '/') {
// noroot_rel_uri, resolve against the current JSP page
uri = ctxt.resolveRelativeUri(uri);
try {
// Can't use RequestUtils.normalize since that package is not
// available to Jasper.
uri = new URI(uri).normalize().toString();
if (uri.startsWith("../")) {
// Trying to go outside context root
err.jspError("jsp.error.taglibDirective.uriInvalid", uri);
}
} catch (URISyntaxException e) {
err.jspError("jsp.error.taglibDirective.uriInvalid", uri);
}
}
URL url = null;
try {
url = ctxt.getResource(uri);
if (url == null) {
throw new FileNotFoundException();
}
/*
* When the TLD cache is built for a TLD contained within a JAR within a WAR, the jar form of the URL is
* used for any nested JAR.
*/
if (url.getProtocol().equals("war") && uri.endsWith(".jar")) {
url = UriUtil.warToJar(url);
}
} catch (Exception ex) {
err.jspError("jsp.error.tld.unable_to_get_jar", uri, ex.toString());
}
if (uri.endsWith(".jar")) {
if (url == null) {
err.jspError("jsp.error.tld.missing_jar", uri);
}
return new TldResourcePath(url, uri, "META-INF/taglib.tld");
} else if (uri.startsWith("/WEB-INF/lib/") || uri.startsWith("/WEB-INF/classes/") ||
(uri.startsWith("/WEB-INF/tags/") && uri.endsWith(".tld")&& !uri.endsWith("implicit.tld"))) {
err.jspError("jsp.error.tld.invalid_tld_file", uri);
}
return new TldResourcePath(url, uri);
}
private TagInfo createTagInfo(TagXml tagXml) throws JasperException {
String teiClassName = tagXml.getTeiClass();
TagExtraInfo tei = null;
if (teiClassName != null && !teiClassName.isEmpty()) {
try {
Class<?> teiClass = ctxt.getClassLoader().loadClass(teiClassName);
tei = (TagExtraInfo) teiClass.getConstructor().newInstance();
} catch (Exception e) {
err.jspError(e, "jsp.error.teiclass.instantiation", teiClassName);
}
}
List<TagAttributeInfo> attributeInfos = tagXml.getAttributes();
List<TagVariableInfo> variableInfos = tagXml.getVariables();
return new TagInfo(tagXml.getName(),
tagXml.getTagClass(),
tagXml.getBodyContent(),
tagXml.getInfo(),
this,
tei,
attributeInfos.toArray(new TagAttributeInfo[0]),
tagXml.getDisplayName(),
tagXml.getSmallIcon(),
tagXml.getLargeIcon(),
variableInfos.toArray(new TagVariableInfo[0]),
tagXml.hasDynamicAttributes());
}
@SuppressWarnings("null") // Impossible for path to be null at warning
private TagFileInfo createTagFileInfo(TagFileXml tagFileXml, Jar jar) throws JasperException {
String name = tagFileXml.getName();
String path = tagFileXml.getPath();
if (path == null) {
// path is required
err.jspError("jsp.error.tagfile.missingPath");
} else if (!path.startsWith("/META-INF/tags") && !path.startsWith("/WEB-INF/tags")) {
err.jspError("jsp.error.tagfile.illegalPath", path);
}
if (jar == null && path.startsWith("/META-INF/tags")) {
// This is a tag file that was packaged in a JAR that has been
// unpacked into /WEB-INF/classes (probably by an IDE). Adjust the
// path accordingly.
path = "/WEB-INF/classes" + path;
}
TagInfo tagInfo =
TagFileProcessor.parseTagFileDirectives(parserController, name, path, jar, this);
return new TagFileInfo(name, path, tagInfo);
}
private TagLibraryValidator createValidator(ValidatorXml validatorXml) throws JasperException {
if (validatorXml == null) {
return null;
}
String validatorClass = validatorXml.getValidatorClass();
if (validatorClass == null || validatorClass.isEmpty()) {
return null;
}
Map<String, Object> initParams = new HashMap<>(validatorXml.getInitParams());
try {
Class<?> tlvClass = ctxt.getClassLoader().loadClass(validatorClass);
TagLibraryValidator tlv = (TagLibraryValidator) tlvClass.getConstructor().newInstance();
tlv.setInitParameters(initParams);
return tlv;
} catch (Exception e) {
err.jspError(e, "jsp.error.tlvclass.instantiation", validatorClass);
return null;
}
}
// *********************************************************************
// Until jakarta.servlet.jsp.tagext.TagLibraryInfo is fixed
/**
* The instance (if any) for the TagLibraryValidator class.
*
* @return The TagLibraryValidator instance, if any.
*/
public TagLibraryValidator getTagLibraryValidator() {
return tagLibraryValidator;
}
/**
* Translation-time validation of the XML document associated with the JSP
* page. This is a convenience method on the associated TagLibraryValidator
* class.
*
* @param thePage
* The JSP page object
* @return A string indicating whether the page is valid or not.
*/
public ValidationMessage[] validate(PageData thePage) {
TagLibraryValidator tlv = getTagLibraryValidator();
if (tlv == null) {
return null;
}
String uri = getURI();
if (uri.startsWith("/")) {
uri = URN_JSPTLD + uri;
}
return tlv.validate(getPrefixString(), uri, thePage);
}
private TagLibraryValidator tagLibraryValidator;
}