Commit 67517d26 authored by Emmanuel Bourg's avatar Emmanuel Bourg

Imported Upstream version 8.0.21

parent 9e8f2cb6
......@@ -245,7 +245,7 @@ You can build them by using the following commands:
A full release includes the Windows installer which requires a Windows
environment to be available to create it. If not building in a Windows
environment, the build scripts assume that WINE is available. If this is not
environment, the build scripts assume that Wine is available. If this is not
the case, the skip.installer property may be set to skip the creation of the
Windows installer.
......@@ -401,6 +401,20 @@ For example:
Apache License v1.1. See http://cobertura.sf.net for details. Using it
during Tomcat build is optional and is off by default.
6. The performance tests are written to run reasonably powerful machines (such
as a developer may use day to day) assuming no other resource hungry
processes are running. These assumptions are not always true (e.g. on CI
systems running ina virtual machine) so the performance tests may be
disabled by using the following property:
test.excludePerformance=true
7. Some tests include checks that the access log valve entries are as expected.
These checks include timings. On slower / loaded systems these checks will
often fail. The checks may be relaxed by using the following property:
test.relaxTiming=true
(8) Source code checks
......
......@@ -28,16 +28,7 @@
<typedef resource="org/apache/catalina/ant/catalina.tasks">
<classpath>
<fileset file="${catalina.home}/bin/tomcat-juli.jar"/>
<fileset file="${catalina.home}/lib/tomcat-api.jar"/>
<fileset file="${catalina.home}/lib/tomcat-util.jar"/>
<fileset file="${catalina.home}/lib/tomcat-util-scan.jar"/>
<fileset file="${catalina.home}/lib/jasper.jar"/>
<fileset file="${catalina.home}/lib/jasper-el.jar"/>
<fileset file="${catalina.home}/lib/el-api.jar"/>
<fileset file="${catalina.home}/lib/jsp-api.jar"/>
<fileset file="${catalina.home}/lib/servlet-api.jar"/>
<fileset file="${catalina.home}/lib/catalina-ant.jar"/>
<fileset file="${catalina.home}/lib/tomcat-coyote.jar"/>
<fileset dir="${catalina.home}/lib" includes="*.jar"/>
</classpath>
</typedef>
<typedef resource="org/apache/catalina/ant/jmx/jmxaccessor.tasks">
......
......@@ -25,7 +25,7 @@
# ----- Version Control Flags -----
version.major=8
version.minor=0
version.build=18
version.build=21
version.patch=0
version.suffix=
......@@ -137,13 +137,14 @@ wsdl4j-lib.jar=${wsdl4j-lib.home}/wsdl4j-${wsdl4j-lib.version}.jar
# ----- Eclipse JDT, version 4.4 or later -----#
# See https://wiki.apache.org/tomcat/JDTCoreBatchCompiler before updating
jdt.version=4.4
jdt.release=R-4.4-201406061215
jdt.version=4.4.2
jdt.release=R-4.4.2-201502041700
jdt.home=${base.path}/ecj-${jdt.version}
jdt.jar=${jdt.home}/ecj-${jdt.version}.jar
# The download will be moved to the archive area eventually. We are taking care of that in advance.
jdt.loc.1=http://archive.eclipse.org/eclipse/downloads/drops4/${jdt.release}/ecj-${jdt.version}.jar
jdt.loc.2=http://download.eclipse.org/eclipse/downloads/drops4/${jdt.release}/ecj-${jdt.version}.jar
# ---- NPN support
npn.version=8.1.2.v20120308
npn.home=${base.path}/npn-${npn.version}
......@@ -151,7 +152,7 @@ npn.jar=${npn.home}/npn-${npn.version}.jar
npn.loc=http://repo1.maven.org/maven2/org/eclipse/jetty/npn/npn-api/${npn.version}/npn-api-${npn.version}.jar
# ----- Tomcat native library -----
tomcat-native.version=1.1.32
tomcat-native.version=1.1.33
tomcat-native.home=${base.path}/tomcat-native-${tomcat-native.version}
tomcat-native.tar.gz=${tomcat-native.home}/tomcat-native.tar.gz
tomcat-native.loc.1=${base-tomcat.loc.1}/tomcat-connectors/native/${tomcat-native.version}/source/tomcat-native-${tomcat-native.version}-src.tar.gz
......@@ -213,7 +214,7 @@ objenesis.loc=https://objenesis.googlecode.com/files/objenesis-${objenesis.versi
objenesis.jar=${objenesis.home}/objenesis-${objenesis.version}.jar
# ----- Checkstyle, version 6.0 or later -----
checkstyle.version=6.2
checkstyle.version=6.4.1
checkstyle.home=${base.path}/checkstyle-${checkstyle.version}
checkstyle.loc=${base-sf.loc}/checkstyle/checkstyle/${checkstyle.version}/checkstyle-${checkstyle.version}-all.jar
checkstyle.jar=${checkstyle.home}/checkstyle-${checkstyle.version}-all.jar
......
......@@ -168,6 +168,7 @@
<!-- Tests To Run -->
<property name="test.name" value="**/Test*.java"/>
<property name="test.formatter" value="-Dorg.apache.juli.formatter=java.util.logging.SimpleFormatter"/>
<property name="test.relaxTiming" value="false"/>
<!-- Cobertura code coverage settings -->
<property name="cobertura.out" value="${tomcat.output}/coverage"/>
......@@ -179,9 +180,13 @@
<available file="/dev/urandom" property="test.jvmarg.egd" value="-Djava.security.egd=file:/dev/./urandom"/>
<property name="test.jvmarg.egd" value="" />
<!-- Location of OpenSSL binary -->
<!-- Location of OpenSSL binary (file name, not directory) -->
<property name="test.openssl.path" value="" />
<!-- Accepted version of OpenSSL. The output of "openssl version"
must start with "OpenSSL " plus value of this property. -->
<property name="test.openssl.version" value="1.0.2" />
<!-- Include .gitignore in src distributions. -->
<!-- .git and .gitignore are in defaultexcludes since Ant 1.8.2 -->
<defaultexcludes add="**/.git" />
......@@ -268,6 +273,7 @@
<include name="**/*.MF"/>
<include name="**/*.notice"/>
<include name="**/*.nsi"/>
<include name="**/*.pem"/>
<include name="**/*.pl"/>
<include name="**/*.policy"/>
<include name="**/*.pom"/>
......@@ -1340,7 +1346,36 @@
<target name="test" description="Runs the JUnit test cases"
depends="test-bio,test-nio,test-nio2,test-apr,cobertura-report" >
<fail if="test.result.error" message='Some tests completed with an Error. See ${tomcat.build}/logs for details, search for "FAILED".' />
<fileset id="test.result.skippedtests" dir="${test.reports}" includes="*.txt">
<not>
<contains text="Skipped: 0" />
</not>
</fileset>
<fileset id="test.result.failedtests" dir="${test.reports}" includes="*.txt">
<not>
<contains text="Failures: 0, Errors: 0" />
</not>
</fileset>
<concat>
<header>Testsuites with skipped tests:${line.separator}</header>
<string>${toString:test.result.skippedtests}</string>
<filterchain>
<tokenfilter delimOutput="${line.separator}">
<stringtokenizer delims=";"/>
</tokenfilter>
</filterchain>
</concat>
<concat>
<header>Testsuites with failed tests:${line.separator}</header>
<string>${toString:test.result.failedtests}</string>
<filterchain>
<tokenfilter delimOutput="${line.separator}">
<stringtokenizer delims=";"/>
</tokenfilter>
</filterchain>
</concat>
<fail if="test.result.error" message='Some tests completed with an Error. See ${tomcat.build}/logs for details, search for "ERROR".' />
<fail if="test.result.failure" message='Some tests completed with a Failure. See ${tomcat.build}/logs for details, search for "FAILED".' />
</target>
......@@ -1419,6 +1454,8 @@
<sysproperty key="tomcat.test.accesslog" value="${test.accesslog}" />
<sysproperty key="tomcat.test.reports" value="${test.reports}" />
<sysproperty key="tomcat.test.openssl.path" value="${test.openssl.path}" />
<sysproperty key="tomcat.test.openssl.version" value="${test.openssl.version}" />
<sysproperty key="tomcat.test.relaxTiming" value="${test.relaxTiming}" />
<!-- File for Cobertura to write coverage results to -->
<sysproperty key="net.sourceforge.cobertura.datafile" file="${cobertura.datafile}" />
......@@ -1442,7 +1479,9 @@
<!-- Exclude the tests known to fail -->
<exclude name="org/apache/catalina/tribes/test/**" />
<!-- Exclude the OpenSSL tests unless OpenSSL is available -->
<exclude name="org/apache/tomcat/util/net/jsse/openssl/**" unless="${test.openssl.exists}"/>
<exclude name="org/apache/tomcat/util/net/jsse/openssl/**" unless="${test.openssl.exists}" />
<!-- Exclude performance tests. E.g. on systems with slow/inconsistent timing -->
<exclude name="**/*Performance.java" if="${test.excludePerformance}" />
</fileset>
</batchtest>
</junit>
......
......@@ -121,7 +121,7 @@ commons-beanutils*.jar,commons-codec*.jar,commons-collections*.jar,\
commons-dbcp*.jar,commons-digester*.jar,commons-fileupload*.jar,\
commons-httpclient*.jar,commons-io*.jar,commons-lang*.jar,commons-logging*.jar,\
commons-math*.jar,commons-pool*.jar,\
jstl.jar,\
jstl.jar,taglibs-standard-spec-*.jar,\
geronimo-spec-jaxrpc*.jar,wsdl4j*.jar,\
ant.jar,ant-junit*.jar,aspectj*.jar,jmx.jar,h2*.jar,hibernate*.jar,httpclient*.jar,\
jmx-tools.jar,jta*.jar,log4j*.jar,mail*.jar,slf4j*.jar,\
......
......@@ -19,7 +19,6 @@ package javax.el;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
......@@ -52,7 +51,7 @@ public class ImportHandler {
String className = name.substring(0, lastPeriod);
String fieldOrMethodName = name.substring(lastPeriod + 1);
Class<?> clazz = findClass(className);
Class<?> clazz = findClass(className, true);
if (clazz == null) {
throw new ELException(Util.message(
......@@ -124,18 +123,10 @@ public class ImportHandler {
public void importPackage(String name) {
// Import ambiguity is handled at resolution, not at import
Package p = Package.getPackage(name);
if (p == null) {
// Either the package does not exist or no class has been loaded
// from that package. Check if the package exists.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String path = name.replace('.', '/');
URL url = cl.getResource(path);
if (url == null) {
throw new ELException(Util.message(
null, "importHandler.invalidPackage", name));
}
}
// Whether the package exists is not checked,
// a) for sake of performance when used in JSPs (BZ 57142),
// b) java.lang.Package.getPackage(name) is not reliable (BZ 57574),
// c) such check is not required by specification.
packageNames.add(name);
}
......@@ -149,13 +140,17 @@ public class ImportHandler {
Class<?> result = clazzes.get(name);
if (result != null) {
return result;
if (NotFound.class.equals(result)) {
return null;
} else {
return result;
}
}
// Search the class imports
String className = classNames.get(name);
if (className != null) {
Class<?> clazz = findClass(className);
Class<?> clazz = findClass(className, true);
if (clazz != null) {
clazzes.put(className, clazz);
return clazz;
......@@ -166,7 +161,7 @@ public class ImportHandler {
// (which correctly triggers an error)
for (String p : packageNames) {
className = p + '.' + name;
Class<?> clazz = findClass(className);
Class<?> clazz = findClass(className, false);
if (clazz != null) {
if (result != null) {
throw new ELException(Util.message(null,
......@@ -176,7 +171,11 @@ public class ImportHandler {
result = clazz;
}
}
if (result != null) {
if (result == null) {
// Cache NotFound results to save repeated calls to findClass()
// which is relatively slow
clazzes.put(name, NotFound.class);
} else {
clazzes.put(name, result);
}
......@@ -189,7 +188,7 @@ public class ImportHandler {
}
private Class<?> findClass(String name) {
private Class<?> findClass(String name, boolean throwException) {
Class<?> clazz;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
......@@ -202,10 +201,22 @@ public class ImportHandler {
int modifiers = clazz.getModifiers();
if (!Modifier.isPublic(modifiers) || Modifier.isAbstract(modifiers) ||
Modifier.isInterface(modifiers)) {
throw new ELException(Util.message(
null, "importHandler.invalidClass", name));
if (throwException) {
throw new ELException(Util.message(
null, "importHandler.invalidClass", name));
} else {
return null;
}
}
return clazz;
}
/*
* Marker class used because null values are not permitted in a
* ConcurrentHashMap.
*/
private static class NotFound {
}
}
......@@ -37,7 +37,6 @@ importHandler.classNotFound=The class [{0}] could not be imported as it could no
importHandler.invalidClass=The class [{0}] must be public, non-abstract and not an interface
importHandler.invalidClassName=Name of class to import [{0}] must include a package
importHandler.invalidClassNameForStatic=The class [{0}] specified for static import [{1}] is not valid
importHandler.invalidPackage=The package [{0}] could not be found
importHandler.invalidStaticName=Name of static method or field to import [{0}] must include a class
importHandler.staticNotFound=The static import [{0}] could not be found in class [{1}] for import [{2}]
......
......@@ -25,6 +25,7 @@ import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javax.servlet.DispatcherType;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
......@@ -236,10 +237,13 @@ public abstract class HttpServlet extends GenericServlet {
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
NoBodyResponse response = new NoBodyResponse(resp);
doGet(req, response);
response.setContentLength();
if (DispatcherType.INCLUDE.equals(req.getDispatcherType())) {
doGet(req, resp);
} else {
NoBodyResponse response = new NoBodyResponse(resp);
doGet(req, response);
response.setContentLength();
}
}
......
......@@ -475,7 +475,7 @@ http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:token">
<xsd:pattern value="[\p{L}-[\p{Cc}\p{Z}]]+" />
<xsd:pattern value="[!-~-[\(\)&#60;&#62;@,;:&#34;/\[\]?=\{\}\\\p{Z}]]+"/>
</xsd:restriction>
</xsd:simpleType>
......
......@@ -75,6 +75,15 @@ public interface Realm {
public void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Return the Principal associated with the specified username, if there
* is one; otherwise return <code>null</code>.
*
* @param username Username of the Principal to look up
*/
public Principal authenticate(String username);
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return <code>null</code>.
......
......@@ -407,6 +407,12 @@ public interface WebResourceRoot extends Lifecycle {
*/
List<URL> getBaseUrls();
/**
* Implementations may cache some information to improve performance. This
* method triggers the clean-up of those resources.
*/
void gc();
static enum ResourceSetType {
PRE,
RESOURCE_JAR,
......
......@@ -145,4 +145,10 @@ public interface WebResourceSet extends Lifecycle {
* read-only, otherwise <code>false</code>
*/
boolean isReadOnly();
/**
* Implementations may cache some information to improve performance. This
* method triggers the clean-up of those resources.
*/
void gc();
}
......@@ -40,6 +40,7 @@ import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.util.SessionIdGeneratorBase;
import org.apache.catalina.util.StandardSessionIdGenerator;
import org.apache.catalina.valves.ValveBase;
......@@ -681,6 +682,83 @@ public abstract class AuthenticatorBase extends ValveBase
HttpServletResponse response) throws IOException;
/**
* Check to see if the user has already been authenticated earlier in the
* processing chain or if there is enough information available to
* authenticate the user without requiring further user interaction.
*
* @param request The current request
* @param response The current response
* @param useSSO Should information available from SSO be used to attempt
* to authenticate the current user?
*
* @return <code>true</code> if the user was authenticated via the cache,
* otherwise <code>false</code>
*/
protected boolean checkForCachedAuthentication(Request request,
HttpServletResponse response, boolean useSSO) {
// Has the user already been authenticated?
Principal principal = request.getUserPrincipal();
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authenticator.check.found", principal.getName()));
}
// Associate the session with any existing SSO session. Even if
// useSSO is false, this will ensure coordinated session
// invalidation at log out.
if (ssoId != null) {
associate(ssoId, request.getSessionInternal(true));
}
return true;
}
// Is there an SSO session against which we can try to reauthenticate?
if (useSSO && ssoId != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authenticator.check.sso", ssoId));
}
/* Try to reauthenticate using data cached by SSO. If this fails,
either the original SSO logon was of DIGEST or SSL (which
we can't reauthenticate ourselves because there is no
cached username and password), or the realm denied
the user's reauthentication for some reason.
In either case we have to prompt the user for a logon */
if (reauthenticateFromSSO(ssoId, request)) {
return true;
}
}
// Has the Connector provided a pre-authenticated Principal that now
// needs to be authorized?
if (request.getCoyoteRequest().getRemoteUserNeedsAuthorization()) {
String username = request.getCoyoteRequest().getRemoteUser().toString();
if (username != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authenticator.check.authorize", username));
}
Principal authorized = context.getRealm().authenticate(username);
if (authorized == null) {
// Realm doesn't recognise user. Create a user with no roles
// from the authenticated user name
if (log.isDebugEnabled()) {
log.debug(sm.getString("authenticator.check.authorizeFail", username));
}
authorized = new GenericPrincipal(username, null, null);
}
String authType = request.getAuthType();
if (authType == null || authType.length() == 0) {
authType = getAuthMethod();
}
register(request, response, authorized, authType, username, null);
return true;
}
}
return false;
}
/**
* Attempts reauthentication to the <code>Realm</code> using
* the credentials included in argument <code>entry</code>.
......
......@@ -63,35 +63,8 @@ public class BasicAuthenticator extends AuthenticatorBase {
public boolean authenticate(Request request, HttpServletResponse response)
throws IOException {
// Have we already authenticated someone?
Principal principal = request.getUserPrincipal();
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
if (log.isDebugEnabled()) {
log.debug("Already authenticated '" + principal.getName() + "'");
}
// Associate the session with any existing SSO session
if (ssoId != null) {
associate(ssoId, request.getSessionInternal(true));
}
return (true);
}
// Is there an SSO session against which we can try to reauthenticate?
if (ssoId != null) {
if (log.isDebugEnabled()) {
log.debug("SSO Id " + ssoId + " set; attempting " +
"reauthentication");
}
/* Try to reauthenticate using data cached by SSO. If this fails,
either the original SSO logon was of DIGEST or SSL (which
we can't reauthenticate ourselves because there is no
cached username and password), or the realm denied
the user's reauthentication for some reason.
In either case we have to prompt the user for a logon */
if (reauthenticateFromSSO(ssoId, request)) {
return true;
}
if (checkForCachedAuthentication(request, response, true)) {
return true;
}
// Validate any credentials already included with this request
......@@ -108,7 +81,7 @@ public class BasicAuthenticator extends AuthenticatorBase {
String username = credentials.getUsername();
String password = credentials.getPassword();
principal = context.getRealm().authenticate(username, password);
Principal principal = context.getRealm().authenticate(username, password);
if (principal != null) {
register(request, response, principal,
HttpServletRequest.BASIC_AUTH, username, password);
......
......@@ -197,48 +197,20 @@ public class DigestAuthenticator extends AuthenticatorBase {
public boolean authenticate(Request request, HttpServletResponse response)
throws IOException {
// Have we already authenticated someone?
Principal principal = request.getUserPrincipal();
//String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
if (log.isDebugEnabled()) {
log.debug("Already authenticated '" + principal.getName() + "'");
}
// Associate the session with any existing SSO session in order
// to get coordinated session invalidation at logout
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (ssoId != null) {
associate(ssoId, request.getSessionInternal(true));
}
return (true);
}
// NOTE: We don't try to reauthenticate using any existing SSO session,
// because that will only work if the original authentication was
// BASIC or FORM, which are less secure than the DIGEST auth-type
// specified for this webapp
//
// Uncomment below to allow previous FORM or BASIC authentications
// Change to true below to allow previous FORM or BASIC authentications
// to authenticate users for this webapp
// TODO make this a configurable attribute (in SingleSignOn??)
/*
// Is there an SSO session against which we can try to reauthenticate?
if (ssoId != null) {
if (log.isDebugEnabled())
log.debug("SSO Id " + ssoId + " set; attempting " +
"reauthentication");
// Try to reauthenticate using data cached by SSO. If this fails,
// either the original SSO logon was of DIGEST or SSL (which
// we can't reauthenticate ourselves because there is no
// cached username and password), or the realm denied
// the user's reauthentication for some reason.
// In either case we have to prompt the user for a logon
if (reauthenticateFromSSO(ssoId, request))
return true;
if (checkForCachedAuthentication(request, response, false)) {
return true;
}
*/
// Validate any credentials already included with this request
Principal principal = null;
String authorization = request.getHeader("authorization");
DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
getKey(), nonces, isValidateUri());
......@@ -637,10 +609,10 @@ public class DigestAuthenticator extends AuthenticatorBase {
}
private static class NonceInfo {
private volatile long timestamp;
private volatile boolean seen[];
private volatile int offset;
private volatile int count = 0;
private final long timestamp;
private final boolean seen[];
private final int offset;
private int count = 0;
public NonceInfo(long currentTime, int seenWindowSize) {
this.timestamp = currentTime;
......
......@@ -125,40 +125,13 @@ public class FormAuthenticator
public boolean authenticate(Request request, HttpServletResponse response)
throws IOException {
// References to objects we will need later
Session session = null;
// Have we already authenticated someone?
Principal principal = request.getUserPrincipal();
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
if (log.isDebugEnabled()) {
log.debug("Already authenticated '" +
principal.getName() + "'");
}
// Associate the session with any existing SSO session
if (ssoId != null) {
associate(ssoId, request.getSessionInternal(true));
}
if (checkForCachedAuthentication(request, response, true)) {