Commit 4ede89df authored by Emmanuel Bourg's avatar Emmanuel Bourg

Imported Upstream version 8.0.26

parent 60af523f
......@@ -64,9 +64,10 @@ source distribution, do the following:
into which you installed the JDK release.
(2) Install Apache Ant version 1.8.2 or later on your computer
(2) Install Apache Ant version 1.9.5 or later on your computer.
1. If Apache Ant version 1.8.2 or later is already installed on your computer, skip to (3).
1. If Apache Ant version 1.9.5 or later is already installed on your computer,
skip to (3).
2. Download a binary distribution of Ant from:
......@@ -343,8 +344,7 @@ For example:
It is possible to further limit such run to a number of selected test
methods by adding "test.entry.methods" property. The property specifies a
comma-separated list of test case methods. (This feature requires
Apache Ant 1.8.2 or later).
comma-separated list of test case methods.
For example:
......
......@@ -25,7 +25,7 @@
# ----- Version Control Flags -----
version.major=8
version.minor=0
version.build=24
version.build=26
version.patch=0
version.suffix=
......@@ -42,6 +42,12 @@ test.haltonfailure=false
# Activate AccessLog during testing
test.accesslog=false
# Number of parallel threads to use for testing. The recommended value is one
# thread per core.
# Note: Cobertura code coverage currently requires this to be set to 1. Setting
# a value above one will disable code coverage if enabled.
test.threads=1
# Note the Cobertura code coverage tool is GPLv2 licensed
test.cobertura=false
......@@ -208,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.7
checkstyle.version=6.8.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
......
......@@ -179,12 +179,9 @@
<property name="test.jvmarg.egd" value="" />
<!-- Location of OpenSSL binary (file name, not directory) -->
<!-- The OpenSSL tests cases be disabled by specifing an invalid path here -->
<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" />
......@@ -1422,7 +1419,8 @@
<junit printsummary="yes" fork="yes" dir="." showoutput="yes"
errorproperty="test.result.error"
failureproperty="test.result.failure"
haltonfailure="${test.haltonfailure}" >
haltonfailure="${test.haltonfailure}"
threads="${test.threads}" >
<jvmarg value="${test.jvmarg.egd}"/>
<jvmarg value="-Djava.library.path=${test.apr.loc}"/>
......@@ -1438,7 +1436,6 @@
<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}" />
......@@ -1472,15 +1469,39 @@
</sequential>
</macrodef>
<target name="cobertura-disabled" unless="${test.cobertura}">
<target name="cobertura-init">
<condition property="cobertura.enabled" value="true">
<and>
<istrue value="${test.cobertura}"/>
<equals arg1="1" arg2="${test.threads}"/>
</and>
</condition>
<condition property="cobertura.disabled" value="true">
<and>
<istrue value="${test.cobertura}"/>
<not>
<equals arg1="1" arg2="${test.threads}"/>
</not>
</and>
</condition>
</target>
<target name="cobertura-disabled" unless="${cobertura.enabled}"
depends="cobertura-init">
<!-- Define classpath used to run tests when Cobertura is turned off. -->
<path id="tomcat.test.run.classpath">
<path refid="tomcat.test.classpath" />
</path>
</target>
<target name="cobertura-instrument" depends="compile,download-cobertura,cobertura-disabled"
if="${test.cobertura}"
<target name="cobertura-disabled-log" if="${cobertura.disabled}"
depends="cobertura-init">
<echo message="Code coverage disabled because test.threads is greater than 1"/>
</target>
<target name="cobertura-instrument"
depends="compile,download-cobertura,cobertura-disabled,cobertura-disabled-log"
if="${cobertura.enabled}"
description="Adds Cobertura instrumentation to the compiled bytecode">
<path id="cobertura.classpath">
......@@ -1522,7 +1543,7 @@
</path>
</target>
<target name="cobertura-report" if="${test.cobertura}"
<target name="cobertura-report" if="${cobertura.enabled}"
depends="test-bio,test-nio,test-nio2,test-apr"
description="Creates report from gathered Cobertura results">
......
......@@ -136,7 +136,7 @@ xom-*.jar
# has been excluded by a broad file name pattern in the jarsToSkip list.
# The list of JARs to scan may be over-ridden at a Context level for individual
# scan types by configuring a JarScanner with a nested JarScanFilter.
tomcat.util.scan.StandardJarScanFilter.jarsToScan=log4j-core*.jar,log4j-taglib*.jar
tomcat.util.scan.StandardJarScanFilter.jarsToScan=log4j-core*.jar,log4j-taglib*.jar,log4javascript*.jar
# String cache configuration.
tomcat.util.buf.StringCache.byte.enabled=true
......
......@@ -42,7 +42,16 @@
<!-- parameters (default values are in square brackets): -->
<!-- -->
<!-- debug Debugging detail level for messages logged -->
<!-- by this servlet. [0] -->
<!-- by this servlet. Useful values range from 0 -->
<!-- to 5 where 0 means no logging and 5 means -->
<!-- maximum logging. Values of 10 or more mean -->
<!-- maximum logging plus debug info added to the -->
<!-- HTTP response. If an error occurs and debug is -->
<!-- 10 or more the standard error page mechanism -->
<!-- will be disabled and a response body with -->
<!-- debug information will be produced. Note that -->
<!-- any value of 10 or more has the same effect as -->
<!-- a value of 10. [0] -->
<!-- -->
<!-- fileEncoding Encoding to be used to read static resources -->
<!-- [platform default] -->
......
......@@ -247,6 +247,13 @@ public final class Globals {
"org.apache.catalina.parameter_parse_failed";
/**
* The reason that the parameter parsing failed.
*/
public static final String PARAMETER_PARSE_FAILED_REASON_ATTR =
"org.apache.catalina.parameter_parse_failed_reason";
/**
* The master flag which controls strict servlet specification
* compliance.
......
......@@ -185,7 +185,7 @@ public abstract class AbstractCatalinaTask extends BaseRedirectorHelperTask {
* @exception BuildException if an error occurs
*/
public void execute(String command, InputStream istream,
String contentType, int contentLength)
String contentType, long contentLength)
throws BuildException {
URLConnection conn = null;
......
......@@ -26,6 +26,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.regex.Pattern;
import org.apache.tools.ant.BuildException;
......@@ -38,6 +39,7 @@ import org.apache.tools.ant.BuildException;
* @since 4.1
*/
public class DeployTask extends AbstractCatalinaCommandTask {
private static final Pattern PROTOCOL_PATTERN = Pattern.compile("\\w{3,5}\\:");
// ------------------------------------------------------------- Properties
......@@ -138,32 +140,32 @@ public class DeployTask extends AbstractCatalinaCommandTask {
// Building an input stream on the WAR to upload, if any
BufferedInputStream stream = null;
String contentType = null;
int contentLength = -1;
long contentLength = -1;
if (war != null) {
if (!war.startsWith("file:")) {
if (PROTOCOL_PATTERN.matcher(war).lookingAt()) {
try {
URL url = new URL(war);
URLConnection conn = url.openConnection();
contentLength = conn.getContentLength();
contentLength = conn.getContentLengthLong();
stream = new BufferedInputStream
(conn.getInputStream(), 1024);
} catch (IOException e) {
throw new BuildException(e);
}
} else {
FileInputStream fsInput = null;
try {
FileInputStream fsInput = new FileInputStream(war);
long size = fsInput.getChannel().size();
if (size > Integer.MAX_VALUE)
throw new UnsupportedOperationException(
"DeployTask does not support WAR files " +
"greater than 2 Gb");
contentLength = (int) size;
fsInput = new FileInputStream(war);
contentLength = fsInput.getChannel().size();
stream = new BufferedInputStream(fsInput, 1024);
} catch (IOException e) {
if (fsInput != null) {
try {
fsInput.close();
} catch (IOException ioe) {
// Ignore
}
}
throw new BuildException(e);
}
}
......@@ -188,12 +190,20 @@ public class DeployTask extends AbstractCatalinaCommandTask {
sb.append("&tag=");
sb.append(URLEncoder.encode(tag, getCharset()));
}
execute(sb.toString(), stream, contentType, contentLength);
} catch (UnsupportedEncodingException e) {
throw new BuildException("Invalid 'charset' attribute: " + getCharset());
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException ioe) {
// Ignore
}
}
}
execute(sb.toString(), stream, contentType, contentLength);
}
......
......@@ -93,12 +93,8 @@ public class ValidatorTask extends BaseRedirectorHelperTask {
// SecurityManager assume that untrusted applications may be deployed.
Digester digester = DigesterFactory.newDigester(
true, true, null, Globals.IS_SECURITY_ENABLED);
try {
file = file.getCanonicalFile();
InputStream stream =
new BufferedInputStream(new FileInputStream(file));
InputSource is =
new InputSource(file.toURI().toURL().toExternalForm());
try (InputStream stream = new BufferedInputStream(new FileInputStream(file.getCanonicalFile()));) {
InputSource is = new InputSource(file.toURI().toURL().toExternalForm());
is.setByteStream(stream);
digester.parse(is);
handleOutput("web.xml validated");
......
......@@ -88,6 +88,7 @@ import org.apache.tomcat.util.buf.UDecoder;
import org.apache.tomcat.util.http.CookieProcessor;
import org.apache.tomcat.util.http.FastHttpDateFormat;
import org.apache.tomcat.util.http.Parameters;
import org.apache.tomcat.util.http.Parameters.FailReason;
import org.apache.tomcat.util.http.ServerCookie;
import org.apache.tomcat.util.http.ServerCookies;
import org.apache.tomcat.util.http.fileupload.FileItem;
......@@ -2703,6 +2704,7 @@ public class Request
}
if (!location.isDirectory()) {
parameters.setParseFailedReason(FailReason.MULTIPART_CONFIG_INVALID);
partsParseException = new IOException(
sm.getString("coyoteRequest.uploadLocationInvalid",
location));
......@@ -2715,6 +2717,7 @@ public class Request
try {
factory.setRepository(location.getCanonicalFile());
} catch (IOException ioe) {
parameters.setParseFailedReason(FailReason.IO_ERROR);
partsParseException = ioe;
return;
}
......@@ -2781,6 +2784,7 @@ public class Request
// Value separator
postSize++;
if (postSize > maxPostSize) {
parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
throw new IllegalStateException(sm.getString(
"coyoteRequest.maxPostSizeExceeded"));
}
......@@ -2791,19 +2795,23 @@ public class Request
success = true;
} catch (InvalidContentTypeException e) {
parameters.setParseFailedReason(FailReason.INVALID_CONTENT_TYPE);
partsParseException = new ServletException(e);
} catch (FileUploadBase.SizeException e) {
parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
checkSwallowInput();
partsParseException = new IllegalStateException(e);
} catch (FileUploadException e) {
parameters.setParseFailedReason(FailReason.IO_ERROR);
partsParseException = new IOException(e);
} catch (IllegalStateException e) {
// addParameters() will set parseFailedReason
checkSwallowInput();
partsParseException = e;
}
} finally {
if (partsParseException != null || !success) {
parameters.setParseFailed(true);
parameters.setParseFailedReason(FailReason.UNKNOWN);
}
}
}
......@@ -3082,6 +3090,7 @@ public class Request
sm.getString("coyoteRequest.postTooLarge"));
}
checkSwallowInput();
parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
return;
}
byte[] formData = null;
......@@ -3095,6 +3104,7 @@ public class Request
}
try {
if (readPostBody(formData, len) != len) {
parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
return;
}
} catch (IOException e) {
......@@ -3105,6 +3115,7 @@ public class Request
sm.getString("coyoteRequest.parseParameters"),
e);
}
parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
return;
}
parameters.processParameters(formData, 0, len);
......@@ -3113,8 +3124,19 @@ public class Request
byte[] formData = null;
try {
formData = readChunkedPostBody();
} catch (IllegalStateException ise) {
// chunkedPostTooLarge error
parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
Context context = getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.parseParameters"),
ise);
}
return;
} catch (IOException e) {
// Client disconnect or chunkedPostTooLarge error
// Client disconnect
parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
Context context = getContext();
if (context != null && context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
......@@ -3130,7 +3152,7 @@ public class Request
success = true;
} finally {
if (!success) {
parameters.setParseFailed(true);
parameters.setParseFailedReason(FailReason.UNKNOWN);
}
}
......@@ -3171,7 +3193,7 @@ public class Request
(body.getLength() + len) > connector.getMaxPostSize()) {
// Too much data
checkSwallowInput();
throw new IOException(
throw new IllegalStateException(
sm.getString("coyoteRequest.chunkedPostTooLarge"));
}
if (len > 0) {
......@@ -3343,6 +3365,18 @@ public class Request
return null;
}
@Override
public void set(Request request, String name, Object value) {
// NO-OP
}
});
specialAttributes.put(Globals.PARAMETER_PARSE_FAILED_REASON_ATTR,
new SpecialAttributeAdapter() {
@Override
public Object get(Request request, String name) {
return request.getCoyoteRequest().getParameters().getParseFailedReason();
}
@Override
public void set(Request request, String name, Object value) {
// NO-OP
......
......@@ -540,17 +540,18 @@ public class ApplicationContext
* in the correct form
*/
@Override
public URL getResource(String path)
throws MalformedURLException {
public URL getResource(String path) throws MalformedURLException {
if (path == null ||
!path.startsWith("/") && GET_RESOURCE_REQUIRE_SLASH)
throw new MalformedURLException(sm.getString(
"applicationContext.requestDispatcher.iae", path));
String validatedPath = validateResourcePath(path);
if (validatedPath == null) {
throw new MalformedURLException(
sm.getString("applicationContext.requestDispatcher.iae", path));
}
WebResourceRoot resources = context.getResources();
if (resources != null) {
return resources.getResource(path).getURL();
return resources.getResource(validatedPath).getURL();
}
return null;
......@@ -568,21 +569,42 @@ public class ApplicationContext
@Override
public InputStream getResourceAsStream(String path) {
if (path == null)
return (null);
String validatedPath = validateResourcePath(path);
if (!path.startsWith("/") && GET_RESOURCE_REQUIRE_SLASH)
if (validatedPath == null) {
return null;
}
WebResourceRoot resources = context.getResources();
if (resources != null) {
return resources.getResource(path).getInputStream();
return resources.getResource(validatedPath).getInputStream();
}
return null;
}
/*
* Returns null if the input path is not valid or a path that will be
* acceptable to resoucres.getResource().
*/
private String validateResourcePath(String path) {
if (path == null) {
return null;
}
if (!path.startsWith("/")) {
if (GET_RESOURCE_REQUIRE_SLASH) {
return null;
} else {
return "/" + path;
}
}
return path;
}
/**
* Return a Set containing the resource paths of resources member of the
* specified collection. Each path will be a String starting with
......@@ -782,17 +804,13 @@ public class ApplicationContext
return;
}
Object oldValue = null;
boolean replaced = false;
// Add or replace the specified attribute
// Check for read only attribute
if (readOnlyAttributes.containsKey(name))
return;
oldValue = attributes.get(name);
if (oldValue != null)
replaced = true;
attributes.put(name, value);
Object oldValue = attributes.put(name, value);
boolean replaced = oldValue != null;
// Notify interested application event listeners
Object listeners[] = context.getApplicationEventListeners();
......
......@@ -58,6 +58,15 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
protected static final StringManager sm =
StringManager.getManager(Constants.Package);
/* When a request uses a sequence of multiple start(); dispatch() with
* non-container threads it is possible for a previous dispatch() to
* interfere with a following start(). This lock prevents that from
* happening. It is a dedicated object as user code may lock on the
* AsyncContext so if container code also locks on that object deadlocks may
* occur.
*/
private final Object asyncContextLock = new Object();
private volatile ServletRequest servletRequest = null;
private volatile ServletResponse servletResponse = null;
private final List<AsyncListenerWrapper> listeners = new ArrayList<>();
......@@ -180,46 +189,48 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
@Override
public void dispatch(ServletContext context, String path) {
if (log.isDebugEnabled()) {
logDebug("dispatch ");
}
check();
if (dispatch != null) {
throw new IllegalStateException(
sm.getString("asyncContextImpl.dispatchingStarted"));
}
if (request.getAttribute(ASYNC_REQUEST_URI)==null) {
request.setAttribute(ASYNC_REQUEST_URI, request.getRequestURI());
request.setAttribute(ASYNC_CONTEXT_PATH, request.getContextPath());
request.setAttribute(ASYNC_SERVLET_PATH, request.getServletPath());
request.setAttribute(ASYNC_PATH_INFO, request.getPathInfo());
request.setAttribute(ASYNC_QUERY_STRING, request.getQueryString());
}
final RequestDispatcher requestDispatcher = context.getRequestDispatcher(path);
if (!(requestDispatcher instanceof AsyncDispatcher)) {
throw new UnsupportedOperationException(
sm.getString("asyncContextImpl.noAsyncDispatcher"));
}
final AsyncDispatcher applicationDispatcher =
(AsyncDispatcher) requestDispatcher;
final ServletRequest servletRequest = getRequest();
final ServletResponse servletResponse = getResponse();
Runnable run = new Runnable() {
@Override
public void run() {
request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCHED, null);
try {
applicationDispatcher.dispatch(servletRequest, servletResponse);
}catch (Exception x) {
//log.error("Async.dispatch",x);
throw new RuntimeException(x);
}
synchronized (asyncContextLock) {
if (log.isDebugEnabled()) {
logDebug("dispatch ");
}
check();
if (dispatch != null) {
throw new IllegalStateException(
sm.getString("asyncContextImpl.dispatchingStarted"));
}
if (request.getAttribute(ASYNC_REQUEST_URI)==null) {
request.setAttribute(ASYNC_REQUEST_URI, request.getRequestURI());
request.setAttribute(ASYNC_CONTEXT_PATH, request.getContextPath());
request.setAttribute(ASYNC_SERVLET_PATH, request.getServletPath());
request.setAttribute(ASYNC_PATH_INFO, request.getPathInfo());
request.setAttribute(ASYNC_QUERY_STRING, request.getQueryString());
}
};
final RequestDispatcher requestDispatcher = context.getRequestDispatcher(path);
if (!(requestDispatcher instanceof AsyncDispatcher)) {
throw new UnsupportedOperationException(
sm.getString("asyncContextImpl.noAsyncDispatcher"));
}
final AsyncDispatcher applicationDispatcher =
(AsyncDispatcher) requestDispatcher;
final ServletRequest servletRequest = getRequest();
final ServletResponse servletResponse = getResponse();
Runnable run = new Runnable() {
@Override
public void run() {
request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCHED, null);
try {
applicationDispatcher.dispatch(servletRequest, servletResponse);
}catch (Exception x) {
//log.error("Async.dispatch",x);
throw new RuntimeException(x);
}
}
};
this.dispatch = run;
this.request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCH, null);
clearServletRequestResponse();
this.dispatch = run;
this.request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCH, null);
clearServletRequestResponse();
}
}
@Override
......@@ -332,25 +343,27 @@ public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
public void setStarted(Context context, ServletRequest request,
ServletResponse response, boolean originalRequestResponse) {
this.request.getCoyoteRequest().action(
ActionCode.ASYNC_START, this);
synchronized (asyncContextLock) {
this.request.getCoyoteRequest().action(
ActionCode.ASYNC_START, this);
this.context = context;
this.servletRequest = request;
this.servletResponse = response;
this.hasOriginalRequestAndResponse = originalRequestResponse;
this.event = new AsyncEvent(this, request, response);
this.context = context;
this.servletRequest = request;
this.servletResponse = response;
this.hasOriginalRequestAndResponse = originalRequestResponse;
this.event = new AsyncEvent(this, request, response);
List<AsyncListenerWrapper> listenersCopy = new ArrayList<>();
listenersCopy.addAll(listeners);
listeners.clear();
for (AsyncListenerWrapper listener : listenersCopy) {
try {
listener.fireOnStartAsync(event);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.warn("onStartAsync() failed for listener of type [" +
listener.getClass().getName() + "]", t);
List<AsyncListenerWrapper> listenersCopy = new ArrayList<>();
listenersCopy.addAll(listeners);
listeners.clear();
for (AsyncListenerWrapper listener : listenersCopy) {
try {
listener.fireOnStartAsync(event);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.warn("onStartAsync() failed for listener of type [" +