*/
Set<String> addServletSecurity(ApplicationServletRegistration registration,
ServletSecurityElement servletSecurityElement);
+
+ /**
+ * Sets the (comma separated) list of Servlets that expect a resource to be
+ * present. Used to ensure that welcome files associated with Servlets that
+ * expect a resource to be present are not mapped when there is no resource.
+ */
+ public void setResourceOnlyServlets(String resourceOnlyServlets);
+
+ /**
+ * Obtains the list of Servlets that expect a resource to be present.
+ *
+ * @return A comma separated list of Servlet names as used in web.xml
+ */
+ public String getResourceOnlyServlets();
+
+ /**
+ * Checks the named Servlet to see if it expects a resource to be present.
+ *
+ * @param servletName Name of the Servlet (as per web.xml) to check
+ * @return <code>true</code> if the Servlet expects a resource,
+ * otherwise <code>false</code>
+ */
+ public boolean isResourceOnlyServlet(String servletName);
}
} else if (event.getType() == Wrapper.ADD_MAPPING_EVENT) {
// Handle dynamically adding wrappers
Wrapper wrapper = (Wrapper) event.getSource();
-
- String contextName = wrapper.getParent().getName();
+ Context context = (Context) wrapper.getParent();
+ String contextName = context.getName();
if ("/".equals(contextName)) {
contextName = "";
}
- String hostName = wrapper.getParent().getParent().getName();
-
+ String hostName = context.getParent().getName();
+ String wrapperName = wrapper.getName();
String mapping = (String) event.getData();
- boolean jspWildCard = ("jsp".equals(wrapper.getName())
+ boolean jspWildCard = ("jsp".equals(wrapperName)
&& mapping.endsWith("/*"));
mapper.addWrapper(hostName, contextName, mapping, wrapper,
- jspWildCard);
+ jspWildCard, context.isResourceOnlyServlet(wrapperName));
} else if (event.getType() == Wrapper.REMOVE_MAPPING_EVENT) {
// Handle dynamically removing wrappers
Wrapper wrapper = (Wrapper) event.getSource();
private void registerWrapper(Wrapper wrapper) {
String wrapperName = wrapper.getName();
- String contextName = wrapper.getParent().getName();
+ Context context = (Context) wrapper.getParent();
+ String contextName = context.getName();
if ("/".equals(contextName)) {
contextName = "";
}
- String hostName = wrapper.getParent().getParent().getName();
+ String hostName = context.getParent().getName();
String[] mappings = wrapper.findMappings();
boolean jspWildCard = (wrapperName.equals("jsp")
&& mapping.endsWith("/*"));
mapper.addWrapper(hostName, contextName, mapping, wrapper,
- jspWildCard);
+ jspWildCard,
+ context.isResourceOnlyServlet(wrapperName));
}
if(log.isDebugEnabled()) {
super();
pipeline.setBasic(new StandardContextValve());
broadcaster = new NotificationBroadcasterSupport();
-
+ // Set defaults
+ if (!Globals.STRICT_SERVLET_COMPLIANCE) {
+ // Strict servlet compliance requires all extension mapped servlets
+ // to be checked against welcome files
+ resourceOnlyServlets.add("jsp");
+ }
}
private JspConfigDescriptor jspConfigDescriptor =
new ApplicationJspConfigDescriptor();
+ private Set<String> resourceOnlyServlets = new HashSet<String>();
+
+
// ----------------------------------------------------- Context Properties
@Override
+ public String getResourceOnlyServlets() {
+ StringBuilder result = new StringBuilder();
+ boolean first = true;
+ for (String servletName : resourceOnlyServlets) {
+ if (!first) {
+ result.append(',');
+ }
+ result.append(servletName);
+ }
+ return result.toString();
+ }
+
+
+ @Override
+ public void setResourceOnlyServlets(String resourceOnlyServlets) {
+ this.resourceOnlyServlets.clear();
+ if (resourceOnlyServlets == null ||
+ resourceOnlyServlets.length() == 0) {
+ return;
+ }
+ String[] servletNames = resourceOnlyServlets.split(",");
+ for (String servletName : servletNames) {
+ this.resourceOnlyServlets.add(servletName);
+ }
+ }
+
+
+ @Override
+ public boolean isResourceOnlyServlet(String servletName) {
+ return resourceOnlyServlets.contains(servletName);
+ }
+
+
+ @Override
public int getEffectiveMajorVersion() {
return effectiveMajorVersion;
}
wrapper.addMapping(pattern);
// Update context mapper
- mapper.addWrapper(pattern, wrapper, jspWildCard);
+ mapper.addWrapper(pattern, wrapper, jspWildCard,
+ resourceOnlyServlets.contains(name));
fireContainerEvent("addServletMapping", pattern);
}
- /**
- * Add a new Wrapper to an existing Context.
- *
- * @param hostName Virtual host name this wrapper belongs to
- * @param contextPath Context path this wrapper belongs to
- * @param path Wrapper mapping
- * @param wrapper Wrapper object
- */
public void addWrapper(String hostName, String contextPath, String path,
- Object wrapper) {
- addWrapper(hostName, contextPath, path, wrapper, false);
- }
-
-
- public void addWrapper(String hostName, String contextPath, String path,
- Object wrapper, boolean jspWildCard) {
+ Object wrapper, boolean jspWildCard,
+ boolean resourceOnly) {
Host[] hosts = this.hosts;
int pos = find(hosts, hostName);
if (pos < 0) {
}
Context context = contexts[pos2];
if (context.name.equals(contextPath)) {
- addWrapper(context, path, wrapper, jspWildCard);
+ addWrapper(context, path, wrapper, jspWildCard, resourceOnly);
}
}
}
- /**
- * Add a wrapper to the context associated with this wrapper.
- *
- * @param path Wrapper mapping
- * @param wrapper The Wrapper object
- */
- public void addWrapper(String path, Object wrapper) {
- addWrapper(context, path, wrapper);
- }
-
-
- public void addWrapper(String path, Object wrapper, boolean jspWildCard) {
- addWrapper(context, path, wrapper, jspWildCard);
- }
-
-
- protected void addWrapper(Context context, String path, Object wrapper) {
- addWrapper(context, path, wrapper, false);
+ public void addWrapper(String path, Object wrapper, boolean jspWildCard,
+ boolean resourceOnly) {
+ addWrapper(context, path, wrapper, jspWildCard, resourceOnly);
}
* @param path Wrapper mapping
* @param wrapper The Wrapper object
* @param jspWildCard true if the wrapper corresponds to the JspServlet
+ * @param resourceOnly true if this wrapper always expects a physical
+ * resource to be present (such as a JSP)
* and the mapping path contains a wildcard; false otherwise
*/
protected void addWrapper(Context context, String path, Object wrapper,
- boolean jspWildCard) {
+ boolean jspWildCard, boolean resourceOnly) {
synchronized (context) {
Wrapper newWrapper = new Wrapper();
newWrapper.object = wrapper;
newWrapper.jspWildCard = jspWildCard;
+ newWrapper.resourceOnly = resourceOnly;
if (path.endsWith("/*")) {
// Wildcard wrapper
newWrapper.name = path.substring(0, path.length() - 2);
// Rule 3 -- Extension Match
Wrapper[] extensionWrappers = context.extensionWrappers;
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
- internalMapExtensionWrapper(extensionWrappers, path, mappingData);
+ internalMapExtensionWrapper(extensionWrappers, path, mappingData,
+ true);
}
// Rule 4 -- Welcome resources processing for servlets
// Swallow not found, since this is normal
}
if (file != null && !(file instanceof DirContext) ) {
- internalMapExtensionWrapper(extensionWrappers,
- path, mappingData);
+ internalMapExtensionWrapper(extensionWrappers, path,
+ mappingData, true);
if (mappingData.wrapper == null
&& context.defaultWrapper != null) {
mappingData.wrapper =
path.append(context.welcomeResources[i], 0,
context.welcomeResources[i].length());
path.setOffset(servletPath);
- internalMapExtensionWrapper(extensionWrappers,
- path, mappingData);
+ internalMapExtensionWrapper(extensionWrappers, path,
+ mappingData, false);
}
path.setOffset(servletPath);
/**
* Extension mappings.
+ *
+ * @param wrappers Set of wrappers to check for matches
+ * @param path Path to map
+ * @param mappingData Mapping data for result
+ * @param resourceExpected Is this mapping expecting to find a resource
*/
- private final void internalMapExtensionWrapper
- (Wrapper[] wrappers, CharChunk path, MappingData mappingData) {
+ private final void internalMapExtensionWrapper(Wrapper[] wrappers,
+ CharChunk path, MappingData mappingData, boolean resourceExpected) {
char[] buf = path.getBuffer();
int pathEnd = path.getEnd();
int servletPath = path.getOffset();
path.setOffset(period + 1);
path.setEnd(pathEnd);
int pos = find(wrappers, path);
- if ((pos != -1)
- && (path.equals(wrappers[pos].name))) {
+ if ((pos != -1) && (path.equals(wrappers[pos].name)) &&
+ (resourceExpected || !wrappers[pos].resourceOnly)) {
mappingData.wrapperPath.setChars
(buf, servletPath, pathEnd - servletPath);
mappingData.requestPath.setChars
public String path = null;
public boolean jspWildCard = false;
+ public boolean resourceOnly = false;
}
}
mapper.addContext("iowejoiejfoiew", "blah7", "/foo/bar/bla",
"context3", new String[0], null);
- mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/fo/*", "wrapper0");
- mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/", "wrapper1");
- mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blh", "wrapper2");
- mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "*.jsp", "wrapper3");
- mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blah/bou/*", "wrapper4");
- mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blah/bobou/*", "wrapper5");
- mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "*.htm", "wrapper6");
- mapper.addWrapper("iowejoiejfoiew", "/foo/bar/bla", "/bobou/*", "wrapper7");
+ mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/fo/*",
+ "wrapper0", false, false);
+ mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/",
+ "wrapper1", false, false);
+ mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blh",
+ "wrapper2", false, false);
+ mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "*.jsp",
+ "wrapper3", false, false);
+ mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blah/bou/*",
+ "wrapper4", false, false);
+ mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "/blah/bobou/*",
+ "wrapper5", false, false);
+ mapper.addWrapper("iowejoiejfoiew", "/foo/bar", "*.htm",
+ "wrapper6", false, false);
+ mapper.addWrapper("iowejoiejfoiew", "/foo/bar/bla", "/bobou/*",
+ "wrapper7", false, false);
}
--- /dev/null
+package org.apache.tomcat.util.http.mapper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+public class TestMapperWelcomeFiles extends TomcatBaseTest {
+
+ public void testWelcomeFileNotStrict() throws Exception {
+
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0");
+
+ StandardContext ctxt = (StandardContext) tomcat.addWebapp(null, "/test",
+ appDir.getAbsolutePath());
+ Tomcat.addServlet(ctxt, "Ok", new OkServlet());
+ ctxt.setReplaceWelcomeFiles(true);
+ ctxt.addWelcomeFile("index.jsp");
+ ctxt.addWelcomeFile("index.do");
+
+ tomcat.start();
+ ByteChunk bc = new ByteChunk();
+ int rc = getUrl("http://localhost:" + getPort() +
+ "/test/welcome-files", bc, new HashMap<String,List<String>>());
+ assertEquals(HttpServletResponse.SC_OK, rc);
+ assertTrue(bc.toString().contains("JSP"));
+
+ rc = getUrl("http://localhost:" + getPort() +
+ "/test/welcome-files/sub", bc,
+ new HashMap<String,List<String>>());
+ assertEquals(HttpServletResponse.SC_OK, rc);
+ assertTrue(bc.toString().contains("Servlet"));
+ }
+
+ public void testWelcomeFileStrict() throws Exception {
+
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0");
+
+ StandardContext ctxt = (StandardContext) tomcat.addWebapp(null, "/test",
+ appDir.getAbsolutePath());
+ Tomcat.addServlet(ctxt, "Ok", new OkServlet());
+ ctxt.setReplaceWelcomeFiles(true);
+ ctxt.addWelcomeFile("index.jsp");
+ ctxt.addWelcomeFile("index.do");
+
+ // Simulate STRICT_SERVLET_COMPLIANCE
+ ctxt.setResourceOnlyServlets("");
+
+ tomcat.start();
+ ByteChunk bc = new ByteChunk();
+ int rc = getUrl("http://localhost:" + getPort() +
+ "/test/welcome-files", bc, new HashMap<String,List<String>>());
+ assertEquals(HttpServletResponse.SC_OK, rc);
+ assertTrue(bc.toString().contains("JSP"));
+
+ rc = getUrl("http://localhost:" + getPort() +
+ "/test/welcome-files/sub", bc,
+ new HashMap<String,List<String>>());
+ assertEquals(HttpServletResponse.SC_NOT_FOUND, rc);
+ }
+
+ private static class OkServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ resp.setContentType("text/plain");
+ resp.getWriter().write("OK-Servlet");
+ }
+ }
+}
--- /dev/null
+<%--
+ 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.
+--%>
+<html>
+ <body>
+ <p>OK-JSP</p>
+ </body>
+</html>
\ No newline at end of file
on demand.</p>
</attribute>
+ <attribute name="resourceOnlyServlets" required="false">
+ <p>Comma separated list of Servlet names (as used in
+ <code>/WEB-INF/web.xml</code>) that expect a resource to be present.
+ Ensures that welcome files associated with Servlets that expect a
+ resource to be present (such as the JSP Servlet) are not used when there
+ is no resource present. This prevents issues caused by the clarification
+ of welcome file mapping in section 10.10 of the Servlet 3.0
+ specification. If the
+ <code>org.apache.catalina.STRICT_SERVLET_COMPLIANCE</code>
+ <a href="systemprops.html">system property</a> is set to
+ <code>true</code>, the default value of this attribute will be the empty
+ string, else the default value will be <code>jsp</code>.
+ </attribute>
+
<attribute name="sessionCookieDomain" required="false">
<p>The domain to be used for all session cookies created for this
context. If set, this overrides any domain set by the web application.
<li><code>org.apache.tomcat.util.http.ServerCookie.ALWAYS_ADD_EXPIRES</code>.</li>
<li><code>org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR</code>.</li>
<li><code>org.apache.tomcat.util.http.ServerCookie.STRICT_NAMING</code>.</li>
+ <li>The <code>resourceOnlyServlets</code> attribute of any
+ <a href="context.html">Context</a> element.</li>
<li>The <code>tldNamespaceAware</code> attribute of any
<a href="context.html">Context</a> element.</li>
<li>The <code>tldValidation</code> attribute of any