This is an automated email from the ASF dual-hosted git repository. michaelo pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/10.1.x by this push: new 966ba70198 Add ContextNamingInfoListener 966ba70198 is described below commit 966ba701982d2779bed3246adc4e48b70971400b Author: Michael Osipov <micha...@apache.org> AuthorDate: Thu Jun 8 12:09:15 2023 +0200 Add ContextNamingInfoListener A listener which creates context naming information environment entries. --- .../catalina/core/ContextNamingInfoListener.java | 121 +++++++++++++++++++++ .../apache/catalina/core/LocalStrings.properties | 3 + .../core/TestContextNamingInfoListener.java | 119 ++++++++++++++++++++ webapps/docs/changelog.xml | 5 + webapps/docs/config/listeners.xml | 27 +++++ 5 files changed, 275 insertions(+) diff --git a/java/org/apache/catalina/core/ContextNamingInfoListener.java b/java/org/apache/catalina/core/ContextNamingInfoListener.java new file mode 100644 index 0000000000..cf0db7f8ed --- /dev/null +++ b/java/org/apache/catalina/core/ContextNamingInfoListener.java @@ -0,0 +1,121 @@ +/* + * 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.catalina.core; + +import org.apache.catalina.Context; +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleListener; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.descriptor.web.ContextEnvironment; +import org.apache.tomcat.util.res.StringManager; + +/** + * Implementation of {@code LifecycleListener} that will create context naming information + * environment entries. + * <p> + * This listener must only be nested within {@link Context} elements. + * <p> + * The following entries will be added to the initial context ({@code java:comp/env} implied): + * <ul> + * <li>Path: {@code context/path} from {@link Context#getPath()}</li> + * <li>Encoded Path: {@code context/encodedPath} from {@link Context#getEncodedPath()}</li> + * <li>Webapp Version: {@code context/webappVersion} from {@link Context#getWebappVersion()}</li> + * <li>Name: {@code context/name} from {@link Context#getName()}</li> + * <li>Base Name: {@code context/baseName} from {@link Context#getBaseName()}</li> + * <li>Display Name: {@code context/displayName} from {@link Context#getDisplayName()}</li> + * </ul> + * <p> + * See the <a href="https://tomcat.apache.org/tomcat-10.1-doc/config/context.html#Naming">Tomcat + * documentation</a> for more details on the values. + */ +public class ContextNamingInfoListener implements LifecycleListener { + + private static final String PATH_ENTRY_NAME = "context/path"; + private static final String ENCODED_PATH_ENTRY_NAME = "context/encodedPath"; + private static final String WEBAPP_VERSION_ENTRY_NAME = "context/webappVersion"; + private static final String NAME_ENTRY_NAME = "context/name"; + private static final String BASE_NAME_ENTRY_NAME = "context/baseName"; + private static final String DISPLAY_NAME_ENTRY_NAME = "context/displayName"; + + private static final Log log = LogFactory.getLog(ContextNamingInfoListener.class); + /** + * The string manager for this package. + */ + private static final StringManager sm = StringManager.getManager(ContextNamingInfoListener.class); + + private boolean emptyOnRoot = true; + + /** + * Sets whether for the root context {@code context/path} and {@code context/encodedPath} will + * contain {@code "/"} and {@code context/name} will contain {@code "ROOT"} with a version, if any. + * + * @param emptyOnRoot whether paths and name for root context shall be empty + */ + public void setEmptyOnRoot(boolean emptyOnRoot) { + this.emptyOnRoot = emptyOnRoot; + } + + /** + * Gets whether paths and name for the root context will be empty. + * + * @return indicator whether paths and name for the root context will be empty + */ + public boolean isEmptyOnRoot() { + return emptyOnRoot; + } + + @Override + public void lifecycleEvent(LifecycleEvent event) { + if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { + if (!(event.getLifecycle() instanceof Context)) { + log.warn(sm.getString("listener.notContext", event.getLifecycle().getClass().getSimpleName())); + return; + } + Context context = (Context) event.getLifecycle(); + String path = context.getPath(); + String encodedPath = context.getEncodedPath(); + String name = context.getName(); + + if (!emptyOnRoot && path.isEmpty()) { + path = encodedPath = "/"; + name = "ROOT" + name; + } + + addEnvEntry(context, PATH_ENTRY_NAME, path); + addEnvEntry(context, ENCODED_PATH_ENTRY_NAME, encodedPath); + addEnvEntry(context, WEBAPP_VERSION_ENTRY_NAME, context.getWebappVersion()); + addEnvEntry(context, NAME_ENTRY_NAME, name); + addEnvEntry(context, BASE_NAME_ENTRY_NAME, context.getBaseName()); + addEnvEntry(context, DISPLAY_NAME_ENTRY_NAME, context.getDisplayName()); + } + } + + private void addEnvEntry(Context context, String name, String value) { + ContextEnvironment ce = new ContextEnvironment(); + ce.setName(name); + ce.setOverride(true); + ce.setType("java.lang.String"); + ce.setValue(value); + if (log.isDebugEnabled()) { + log.info(sm.getString("contextNamingInfoListener.envEntry",name, value)); + } + context.getNamingResources().addEnvironment(ce); + } + +} diff --git a/java/org/apache/catalina/core/LocalStrings.properties b/java/org/apache/catalina/core/LocalStrings.properties index afc4812915..c5ab4a3506 100644 --- a/java/org/apache/catalina/core/LocalStrings.properties +++ b/java/org/apache/catalina/core/LocalStrings.properties @@ -124,6 +124,8 @@ containerBase.realm.stop=Error stopping old realm containerBase.threadedStartFailed=A child container failed during start containerBase.threadedStopFailed=A child container failed during stop +contextNamingInfoListener.envEntry=Adding context env entry [{0}] with value [{1}] + defaultInstanceManager.invalidAnnotation=Invalid [{0}] annotation defaultInstanceManager.invalidInjection=Invalid method resource injection annotation defaultInstanceManager.postConstructNotFound=Post construct method [{0}] for class [{1}] is declared in deployment descriptor but cannot be found @@ -145,6 +147,7 @@ jniLifecycleListener.missingPathOrName=One of libraryName or libraryPath must be jreLeakListener.classToInitializeFail=Failed to load class [{0}] during Tomcat start to prevent possible memory leaks. +listener.notContext=This listener must only be nested within Context elements, but is in [{0}]. listener.notServer=This listener must only be nested within Server elements, but is in [{0}]. naming.addEnvEntry=Adding environment entry [{0}] diff --git a/test/org/apache/catalina/core/TestContextNamingInfoListener.java b/test/org/apache/catalina/core/TestContextNamingInfoListener.java new file mode 100644 index 0000000000..875d0a574f --- /dev/null +++ b/test/org/apache/catalina/core/TestContextNamingInfoListener.java @@ -0,0 +1,119 @@ +/* + * 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.catalina.core; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleState; +import org.apache.catalina.deploy.NamingResourcesImpl; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.catalina.util.ContextName; +import org.apache.tomcat.util.descriptor.web.ContextEnvironment; + +@RunWith(Parameterized.class) +public class TestContextNamingInfoListener extends TomcatBaseTest { + + @Parameterized.Parameters(name = "{index}: contextPath[{0}], webappVersion[{1}], displayName[{2}], emptyOnRoot[{3}]") + public static Collection<Object[]> parameters() { + List<Object[]> parameterSets = new ArrayList<>(); + + parameterSets.add(new Object[] { "", "", null, Boolean.FALSE, "/", "/", "ROOT" }); + parameterSets.add(new Object[] { "", "42", null, Boolean.FALSE, "/", "/", "ROOT##42" }); + parameterSets.add(new Object[] { "", "", null, Boolean.TRUE, "", "", "" }); + parameterSets.add(new Object[] { "", "42", null, Boolean.TRUE, "", "", "##42" }); + for (Boolean b: Arrays.asList(Boolean.FALSE, Boolean.TRUE)) { + parameterSets.add(new Object[] { "/foo", "", null, b, "/foo", "/foo", "/foo" }); + parameterSets.add(new Object[] { "/foo", "", "My Foo Webapp", b, "/foo", "/foo", "/foo" }); + parameterSets.add(new Object[] { "/foo", "42", "My Foo Webapp", b, "/foo", "/foo", "/foo##42" }); + parameterSets.add(new Object[] { "/foo/bar", "", null, b, "/foo/bar", "/foo/bar", "/foo/bar" }); + parameterSets.add(new Object[] { "/foo/bar", "", "My Foobar Webapp", b, "/foo/bar", "/foo/bar", "/foo/bar" }); + parameterSets.add(new Object[] { "/foo/bar", "42", "My Foobar Webapp", b, "/foo/bar", "/foo/bar", "/foo/bar##42" }); + parameterSets.add(new Object[] { "/\u0444\u0443/\u0431\u0430\u0440", "", "\u041C\u043E\u0439 \u0424\u0443\u0431\u0430\u0440 \u0412\u0435\u0431\u0430\u043F\u043F", b, "/\u0444\u0443/\u0431\u0430\u0440", "/%D1%84%D1%83/%D0%B1%D0%B0%D1%80", "/\u0444\u0443/\u0431\u0430\u0440" }); + parameterSets.add(new Object[] { "/\u0444\u0443/\u0431\u0430\u0440", "42", "\u041C\u043E\u0439 \u0424\u0443\u0431\u0430\u0440 \u0412\u0435\u0431\u0430\u043F\u043F", b, "/\u0444\u0443/\u0431\u0430\u0440", "/%D1%84%D1%83/%D0%B1%D0%B0%D1%80", "/\u0444\u0443/\u0431\u0430\u0440##42" }); + } + + return parameterSets; + } + + @Parameter(0) + public String contextPath; + @Parameter(1) + public String webappVersion; + @Parameter(2) + public String displayName; + @Parameter(3) + public Boolean emptyOnRoot; + @Parameter(4) + public String expectedContextPath; + @Parameter(5) + public String expectedEncodedContextPath; + @Parameter(6) + public String expectedName; + + @Test + public void testListener() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + ContextName cn = new ContextName(contextPath, webappVersion); + Context ctx = tomcat.addContext(cn.getPath(), null); + ctx.setName(cn.getName()); + ctx.setWebappVersion(cn.getVersion()); + ctx.setDisplayName(displayName); + + // Enable JNDI - it is disabled by default + tomcat.enableNaming(); + + ContextNamingInfoListener listener = new ContextNamingInfoListener(); + listener.setEmptyOnRoot(emptyOnRoot); + + ctx.addLifecycleListener(listener); + + tomcat.start(); + + Assert.assertEquals(LifecycleState.STARTED, ctx.getState()); + + NamingResourcesImpl namingResources = ctx.getNamingResources(); + ContextEnvironment pathEnv = namingResources.findEnvironment("context/path"); + ContextEnvironment encodedPathEnv = namingResources.findEnvironment("context/encodedPath"); + ContextEnvironment webappVersionEnv = namingResources.findEnvironment("context/webappVersion"); + ContextEnvironment nameEnv = namingResources.findEnvironment("context/name"); + ContextEnvironment baseNameEnv = namingResources.findEnvironment("context/baseName"); + ContextEnvironment displayNameEnv = namingResources.findEnvironment("context/displayName"); + + Assert.assertEquals(expectedContextPath, pathEnv.getValue()); + Assert.assertEquals(expectedEncodedContextPath, encodedPathEnv.getValue()); + Assert.assertEquals(ctx.getWebappVersion(), webappVersionEnv.getValue()); + Assert.assertEquals(expectedName, nameEnv.getValue()); + Assert.assertEquals(ctx.getBaseName(), baseNameEnv.getValue()); + Assert.assertEquals(ctx.getDisplayName(), displayNameEnv.getValue()); + + tomcat.stop(); + } + +} diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index dda15a61fc..c6ea70d9ef 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -107,6 +107,11 @@ <section name="Tomcat 10.1.11 (schultz)" rtext="in development"> <subsection name="Catalina"> <changelog> + <add> + Add <code>org.apache.catalina.core.ContextNamingInfoListener</code>, + a listener which creates context naming information environment entries. + (michaelo) + </add> <fix> Fix an edge case where intra-web application symlinks would be followed if the web applications were deliberately crafted to allow it even when diff --git a/webapps/docs/config/listeners.xml b/webapps/docs/config/listeners.xml index 1495915373..9f0dd02eae 100644 --- a/webapps/docs/config/listeners.xml +++ b/webapps/docs/config/listeners.xml @@ -145,6 +145,33 @@ </subsection> + <subsection name="Context Naming Info Listener - org.apache.catalina.core.ContextNamingInfoListener"> + + <p>The <strong>Context Naming Info Listener</strong> adds the following + environment entries (<code>java:comp/env</code> implied) from the + <a href="context.html">Context</a>: <code>context/path</code>, + <code>context/encodedPath</code>, <code>context/webappVersion</code>, + <code>context/name</code>, <code>context/baseName</code>, + <code>context/displayName</code>.</p> + + <p>This listener must only be nested within + <a href="context.html">Context</a> elements.</p> + + <p>The following additional attributes are supported by the + <strong>Context Naming Info Listener</strong>:</p> + + <attributes> + <attribute name="emptyOnRoot" required="false"> + <p>Whether for the root context <code>context/path</code> and + <code>context/encodedPath</code> will contain <code>"/"</code> and + <code>context/name</code> will contain <code>"ROOT"</code> with a version, + if any.</p> + <p>The default value is <code>true</code>.</p> + </attribute> + </attributes> + + </subsection> + <subsection name="Global Resources Lifecycle Listener - org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"> <p>The <strong>Global Resources Lifecycle Listener</strong> initializes the --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org