[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#861681: marked as done (unblock: tomcat8/8.5.14-1)



Your message dated Mon, 08 May 2017 16:33:10 +0000
with message-id <E1d7law-0004hW-Da@respighi.debian.org>
and subject line unblock tomcat8
has caused the Debian Bug report #861681,
regarding unblock: tomcat8/8.5.14-1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
861681: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=861681
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock

Hi,

This is a pre-upload request to update tomcat8 to the version 8.5.14-1
in stretch. This version is mostly a bug fix release, it also integrates
the fixes for 3 vulnerabilities (CVE-2017-5647, CVE-2017-5650,
CVE-2017-5651).

The full changelog is available here:

http://tomcat.apache.org/tomcat-8.5-doc/changelog.html#Tomcat_8.5.14_(markt)

Backporting security fixes to the stable version of Tomcat has proven
to be a bit tedious in the past, so it would be appreciable to have
a version as fresh as possible in stretch to ease the maintenance work
on the long term.

Thank you,

Emmanuel Bourg
diff --git a/build.properties.default b/build.properties.default
index c5e67cdf..bfe9053d 100644
--- a/build.properties.default
+++ b/build.properties.default
@@ -25,7 +25,7 @@
 # ----- Version Control Flags -----
 version.major=8
 version.minor=5
-version.build=12
+version.build=14
 version.patch=0
 version.suffix=
 
diff --git a/debian/changelog b/debian/changelog
index 5cbb4174..18fcdb12 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+tomcat8 (8.5.14-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release
+    - Removed the CVE patches (fixed in this release)
+
+ -- Emmanuel Bourg <ebourg@apache.org>  Fri, 21 Apr 2017 08:38:28 +0200
+
 tomcat8 (8.5.12-1) unstable; urgency=medium
 
   * Team upload.
diff --git a/debian/patches/CVE-2017-5647.patch b/debian/patches/CVE-2017-5647.patch
deleted file mode 100644
index e746785c..00000000
--- a/debian/patches/CVE-2017-5647.patch
+++ /dev/null
@@ -1,241 +0,0 @@
-From: Markus Koschany <apo@debian.org>
-Date: Tue, 11 Apr 2017 22:18:52 +0200
-Subject: CVE-2017-5647
-
-Bug-Debian: https://bugs.debian.org/860068
-Origin: http://svn.apache.org/r1788932
----
- java/org/apache/coyote/AbstractProtocol.java       |  7 ++--
- java/org/apache/coyote/http11/Http11Processor.java | 12 ++++++-
- java/org/apache/tomcat/util/net/AprEndpoint.java   | 35 +++++++++++++------
- java/org/apache/tomcat/util/net/Nio2Endpoint.java  | 25 +++++++++-----
- java/org/apache/tomcat/util/net/NioEndpoint.java   | 26 +++++++++++----
- .../apache/tomcat/util/net/SendfileDataBase.java   |  6 ++--
- .../tomcat/util/net/SendfileKeepAliveState.java    | 39 ++++++++++++++++++++++
- 7 files changed, 116 insertions(+), 34 deletions(-)
- create mode 100644 java/org/apache/tomcat/util/net/SendfileKeepAliveState.java
-
---- a/java/org/apache/coyote/AbstractProtocol.java
-+++ b/java/org/apache/coyote/AbstractProtocol.java
-@@ -870,10 +870,9 @@
-                     wrapper.registerReadInterest();
-                 } else if (state == SocketState.SENDFILE) {
-                     // Sendfile in progress. If it fails, the socket will be
--                    // closed. If it works, the socket will be re-added to the
--                    // poller
--                    connections.remove(socket);
--                    release(processor);
-+                    // closed. If it works, the socket either be added to the
-+                    // poller (or equivalent) to await more data or processed
-+                    // if there are any pipe-lined requests remaining.
-                 } else if (state == SocketState.UPGRADED) {
-                     // Don't add sockets back to the poller if this was a
-                     // non-blocking write otherwise the poller may trigger
---- a/java/org/apache/coyote/http11/Http11Processor.java
-+++ b/java/org/apache/coyote/http11/Http11Processor.java
-@@ -58,6 +58,7 @@
- import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
- import org.apache.tomcat.util.net.SSLSupport;
- import org.apache.tomcat.util.net.SendfileDataBase;
-+import org.apache.tomcat.util.net.SendfileKeepAliveState;
- import org.apache.tomcat.util.net.SocketWrapperBase;
- import org.apache.tomcat.util.res.StringManager;
- 
-@@ -1601,7 +1602,16 @@
-         openSocket = keepAlive;
-         // Do sendfile as needed: add socket to sendfile and end
-         if (sendfileData != null && !getErrorState().isError()) {
--            sendfileData.keepAlive = keepAlive;
-+            if (keepAlive) {
-+                if (available(false) == 0) {
-+                    sendfileData.keepAliveState = SendfileKeepAliveState.OPEN;
-+                } else {
-+                    sendfileData.keepAliveState = SendfileKeepAliveState.PIPELINED;
-+                }
-+            } else {
-+                sendfileData.keepAliveState = SendfileKeepAliveState.NONE;
-+            }
-+
-             switch (socketWrapper.processSendfile(sendfileData)) {
-             case DONE:
-                 // If sendfile is complete, no need to break keep-alive loop
---- a/java/org/apache/tomcat/util/net/AprEndpoint.java
-+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
-@@ -2138,20 +2138,33 @@
-                             state.length -= nw;
-                             if (state.length == 0) {
-                                 remove(state);
--                                if (state.keepAlive) {
-+                                switch (state.keepAliveState) {
-+                                case NONE: {
-+                                    // Close the socket since this is
-+                                    // the end of the not keep-alive request.
-+                                    closeSocket(state.socket);
-+                                    break;
-+                                }
-+                                case PIPELINED: {
-                                     // Destroy file descriptor pool, which should close the file
-                                     Pool.destroy(state.fdpool);
--                                    Socket.timeoutSet(state.socket,
--                                            getSoTimeout() * 1000);
--                                    // If all done put the socket back in the
--                                    // poller for processing of further requests
--                                    getPoller().add(
--                                            state.socket, getKeepAliveTimeout(),
-+                                    Socket.timeoutSet(state.socket, getSoTimeout() * 1000);
-+                                    // Process the pipelined request data
-+                                    if (!processSocket(state.socket, SocketEvent.OPEN_READ)) {
-+                                        closeSocket(state.socket);
-+                                    }
-+                                    break;
-+                                }
-+                                case OPEN: {
-+                                    // Destroy file descriptor pool, which should close the file
-+                                    Pool.destroy(state.fdpool);
-+                                    Socket.timeoutSet(state.socket, getSoTimeout() * 1000);
-+                                    // Put the socket back in the poller for
-+                                    // processing of further requests
-+                                    getPoller().add(state.socket, getKeepAliveTimeout(),
-                                             Poll.APR_POLLIN);
--                                } else {
--                                    // Close the socket since this is
--                                    // the end of not keep-alive request.
--                                    closeSocket(state.socket);
-+                                    break;
-+                                }
-                                 }
-                             }
-                         }
---- a/java/org/apache/tomcat/util/net/Nio2Endpoint.java
-+++ b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
-@@ -536,17 +536,24 @@
-                         } catch (IOException e) {
-                             // Ignore
-                         }
--                        if (attachment.keepAlive) {
--                            if (!isInline()) {
-+                        if (isInline()) {
-+                            attachment.doneInline = true;
-+                        } else {
-+                            switch (attachment.keepAliveState) {
-+                            case NONE: {
-+                                getEndpoint().processSocket(Nio2SocketWrapper.this,
-+                                        SocketEvent.DISCONNECT, false);
-+                                break;
-+                            }
-+                            case PIPELINED: {
-+                                getEndpoint().processSocket(Nio2SocketWrapper.this,
-+                                        SocketEvent.OPEN_READ, true);
-+                                break;
-+                            }
-+                            case OPEN: {
-                                 awaitBytes();
--                            } else {
--                                attachment.doneInline = true;
-+                                break;
-                             }
--                        } else {
--                            if (!isInline()) {
--                                getEndpoint().processSocket(Nio2SocketWrapper.this, SocketEvent.DISCONNECT, false);
--                            } else {
--                                attachment.doneInline = true;
-                             }
-                         }
-                         return;
---- a/java/org/apache/tomcat/util/net/NioEndpoint.java
-+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
-@@ -924,16 +924,30 @@
-                     // responsible for registering the socket for the
-                     // appropriate event(s) if sendfile completes.
-                     if (!calledByProcessor) {
--                        if (sd.keepAlive) {
--                            if (log.isDebugEnabled()) {
--                                log.debug("Connection is keep alive, registering back for OP_READ");
--                            }
--                            reg(sk,socketWrapper,SelectionKey.OP_READ);
--                        } else {
-+                        switch (sd.keepAliveState) {
-+                        case NONE: {
-                             if (log.isDebugEnabled()) {
-                                 log.debug("Send file connection is being closed");
-                             }
-                             close(sc, sk);
-+                            break;
-+                        }
-+                        case PIPELINED: {
-+                            if (log.isDebugEnabled()) {
-+                                log.debug("Connection is keep alive, processing pipe-lined data");
-+                            }
-+                            if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {
-+                                close(sc, sk);
-+                            }
-+                            break;
-+                        }
-+                        case OPEN: {
-+                            if (log.isDebugEnabled()) {
-+                                log.debug("Connection is keep alive, registering back for OP_READ");
-+                            }
-+                            reg(sk,socketWrapper,SelectionKey.OP_READ);
-+                            break;
-+                        }
-                         }
-                     }
-                     return SendfileState.DONE;
---- a/java/org/apache/tomcat/util/net/SendfileDataBase.java
-+++ b/java/org/apache/tomcat/util/net/SendfileDataBase.java
-@@ -21,10 +21,10 @@
-     /**
-      * Is the current request being processed on a keep-alive connection? This
-      * determines if the socket is closed once the send file completes or if
--     * processing continues with the next request on the connection (or waiting
--     * for that next request to arrive).
-+     * processing continues with the next request on the connection or waiting
-+     * for that next request to arrive.
-      */
--    public boolean keepAlive;
-+    public SendfileKeepAliveState keepAliveState = SendfileKeepAliveState.NONE;
- 
-     /**
-      * The full path to the file that contains the data to be written to the
---- /dev/null
-+++ b/java/org/apache/tomcat/util/net/SendfileKeepAliveState.java
-@@ -0,0 +1,39 @@
-+/*
-+ *  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.tomcat.util.net;
-+
-+public enum SendfileKeepAliveState {
-+
-+    /**
-+     * Keep-alive is not in use. The socket can be closed when the response has
-+     * been written.
-+     */
-+    NONE,
-+
-+    /**
-+     * Keep-alive is in use and there is pipelined data in the input buffer to
-+     * be read as soon as the current response has been written.
-+     */
-+    PIPELINED,
-+
-+    /**
-+     * Keep-alive is in use. The socket should be added to the poller (or
-+     * equivalent) to await more data as soon as the current response has been
-+     * written.
-+     */
-+    OPEN
-+}
diff --git a/debian/patches/CVE-2017-5650.patch b/debian/patches/CVE-2017-5650.patch
deleted file mode 100644
index 74a88629..00000000
--- a/debian/patches/CVE-2017-5650.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From: Markus Koschany <apo@debian.org>
-Date: Wed, 12 Apr 2017 00:00:50 +0200
-Subject: CVE-2017-5650
-
-Bug-Debian: https://bugs.debian.org/860068
-Origin: http://svn.apache.org/r1788480
----
- java/org/apache/coyote/http2/Http2UpgradeHandler.java | 5 +++++
- 1 file changed, 5 insertions(+)
-
---- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
-+++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
-@@ -983,6 +983,11 @@
- 
-     private void close() {
-         connectionState.set(ConnectionState.CLOSED);
-+        for (Stream stream : streams.values()) {
-+            // The connection is closing. Close the associated streams as no
-+            // longer required.
-+            stream.receiveReset(Http2Error.CANCEL.getCode());
-+        }
-         try {
-             socketWrapper.close();
-         } catch (IOException ioe) {
diff --git a/debian/patches/CVE-2017-5651.patch b/debian/patches/CVE-2017-5651.patch
deleted file mode 100644
index e737f688..00000000
--- a/debian/patches/CVE-2017-5651.patch
+++ /dev/null
@@ -1,155 +0,0 @@
-From: Markus Koschany <apo@debian.org>
-Date: Wed, 12 Apr 2017 00:11:24 +0200
-Subject: CVE-2017-5651
-
-Bug-Debian: https://bugs.debian.org/860068
-Origin: http://svn.apache.org/r1788546
----
- java/org/apache/coyote/http11/Http11Processor.java | 52 ++++++++++------------
- 1 file changed, 24 insertions(+), 28 deletions(-)
-
---- a/java/org/apache/coyote/http11/Http11Processor.java
-+++ b/java/org/apache/coyote/http11/Http11Processor.java
-@@ -58,6 +58,7 @@
- import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
- import org.apache.tomcat.util.net.SSLSupport;
- import org.apache.tomcat.util.net.SendfileDataBase;
-+import org.apache.tomcat.util.net.SendfileState;
- import org.apache.tomcat.util.net.SendfileKeepAliveState;
- import org.apache.tomcat.util.net.SocketWrapperBase;
- import org.apache.tomcat.util.res.StringManager;
-@@ -659,9 +660,10 @@
-         openSocket = false;
-         readComplete = true;
-         boolean keptAlive = false;
-+        SendfileState sendfileState = SendfileState.DONE;
- 
--        while (!getErrorState().isError() && keepAlive && !isAsync() &&
--                upgradeToken == null && !endpoint.isPaused()) {
-+        while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
-+                sendfileState == SendfileState.DONE && !endpoint.isPaused()) {
- 
-             // Parsing the request header
-             try {
-@@ -850,9 +852,7 @@
- 
-             rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
- 
--            if (breakKeepAliveLoop(socketWrapper)) {
--                break;
--            }
-+            sendfileState = processSendfile(socketWrapper);
-         }
- 
-         rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
-@@ -864,7 +864,7 @@
-         } else if (isUpgrade()) {
-             return SocketState.UPGRADING;
-         } else {
--            if (sendfileData != null) {
-+            if (sendfileState == SendfileState.PENDING) {
-                 return SocketState.SENDFILE;
-             } else {
-                 if (openSocket) {
-@@ -940,7 +940,6 @@
-         http11 = true;
-         http09 = false;
-         contentDelimitation = false;
--        sendfileData = null;
- 
-         if (endpoint.isSSLEnabled()) {
-             request.scheme().setString("https");
-@@ -1147,15 +1146,14 @@
-         }
- 
-         // Sendfile support
--        boolean sendingWithSendfile = false;
-         if (endpoint.getUseSendfile()) {
--            sendingWithSendfile = prepareSendfile(outputFilters);
-+            prepareSendfile(outputFilters);
-         }
- 
-         // Check for compression
-         boolean isCompressable = false;
-         boolean useCompression = false;
--        if (entityBody && (compressionLevel > 0) && !sendingWithSendfile) {
-+        if (entityBody && (compressionLevel > 0) && sendfileData == null) {
-             isCompressable = isCompressable();
-             if (isCompressable) {
-                 useCompression = useCompression();
-@@ -1297,10 +1295,12 @@
-         return connection.equals(Constants.CLOSE);
-     }
- 
--    private boolean prepareSendfile(OutputFilter[] outputFilters) {
-+    private void prepareSendfile(OutputFilter[] outputFilters) {
-         String fileName = (String) request.getAttribute(
-                 org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
--        if (fileName != null) {
-+        if (fileName == null) {
-+            sendfileData = null;
-+        } else {
-             // No entity body sent here
-             outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
-             contentDelimitation = true;
-@@ -1309,9 +1309,7 @@
-             long end = ((Long) request.getAttribute(
-                     org.apache.coyote.Constants.SENDFILE_FILE_END_ATTR)).longValue();
-             sendfileData = socketWrapper.createSendfileData(fileName, pos, end - pos);
--            return true;
-         }
--        return false;
-     }
- 
-     /**
-@@ -1592,14 +1590,15 @@
- 
- 
-     /**
--     * Checks to see if the keep-alive loop should be broken, performing any
--     * processing (e.g. sendfile handling) that may have an impact on whether
--     * or not the keep-alive loop should be broken.
-      *
--     * @return true if the keep-alive loop should be broken
-+     * Trigger sendfile processing if required.
-+     *
-+     * @return The state of send file processing
-      */
--    private boolean breakKeepAliveLoop(SocketWrapperBase<?> socketWrapper) {
-+    private SendfileState processSendfile(SocketWrapperBase<?> socketWrapper) {
-         openSocket = keepAlive;
-+        // Done is equivalent to sendfile not being used
-+        SendfileState result = SendfileState.DONE;
-         // Do sendfile as needed: add socket to sendfile and end
-         if (sendfileData != null && !getErrorState().isError()) {
-             if (keepAlive) {
-@@ -1612,23 +1611,20 @@
-                 sendfileData.keepAliveState = SendfileKeepAliveState.NONE;
-             }
- 
--            switch (socketWrapper.processSendfile(sendfileData)) {
--            case DONE:
--                // If sendfile is complete, no need to break keep-alive loop
--                sendfileData = null;
--                return false;
--            case PENDING:
--                return true;
-+            result = socketWrapper.processSendfile(sendfileData);
-+            switch (result) {
-             case ERROR:
-                 // Write failed
-                 if (log.isDebugEnabled()) {
-                     log.debug(sm.getString("http11processor.sendfile.error"));
-                 }
-                 setErrorState(ErrorState.CLOSE_CONNECTION_NOW, null);
--                return true;
-+                //$FALL-THROUGH$
-+            default:
-+                sendfileData = null;
-             }
-         }
--        return false;
-+        return result;
-     }
- 
- 
diff --git a/debian/patches/series b/debian/patches/series
index 8aabbe86..1b369897 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -8,6 +8,3 @@
 0018-fix-manager-webapp.patch
 0019-add-distribution-to-error-page.patch
 0021-dont-test-unsupported-ciphers.patch
-CVE-2017-5647.patch
-CVE-2017-5650.patch
-CVE-2017-5651.patch
diff --git a/java/javax/el/BeanELResolver.java b/java/javax/el/BeanELResolver.java
index 27f3d58e..93728545 100644
--- a/java/javax/el/BeanELResolver.java
+++ b/java/javax/el/BeanELResolver.java
@@ -245,6 +245,7 @@ public class BeanELResolver extends ELResolver {
                                     this.type, pd));
                         }
                     }
+                    populateFromInterfaces(ifs);
                 }
             }
             Class<?> superclass = aClass.getSuperclass();
diff --git a/java/org/apache/catalina/Pipeline.java b/java/org/apache/catalina/Pipeline.java
index c5f19dda..8a21e760 100644
--- a/java/org/apache/catalina/Pipeline.java
+++ b/java/org/apache/catalina/Pipeline.java
@@ -18,6 +18,7 @@
 
 package org.apache.catalina;
 
+import java.util.Set;
 
 /**
  * <p>Interface describing a collection of Valves that should be executed
@@ -143,4 +144,14 @@ public interface Pipeline {
      */
     public void setContainer(Container container);
 
+
+    /**
+     * Identifies the Valves, if any, in this Pipeline that do not support
+     * async.
+     *
+     * @param result The Set to which the fully qualified class names of each
+     *               Valve in this Pipeline that does not support async will be
+     *               added
+     */
+    public void findNonAsyncValves(Set<String> result);
 }
diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java
index 52064d92..66574d6b 100644
--- a/java/org/apache/catalina/connector/CoyoteAdapter.java
+++ b/java/org/apache/catalina/connector/CoyoteAdapter.java
@@ -276,8 +276,9 @@ public class CoyoteAdapter implements Adapter {
                 if (req.getStartTime() != -1) {
                     time = System.currentTimeMillis() - req.getStartTime();
                 }
-                if (request.getMappingData().context != null) {
-                    request.getMappingData().context.logAccess(request, response, time, false);
+                Context context = request.getContext();
+                if (context != null) {
+                    context.logAccess(request, response, time, false);
                 } else {
                     log(req, res, time);
                 }
@@ -390,9 +391,17 @@ public class CoyoteAdapter implements Adapter {
             if (!async && postParseSuccess) {
                 // Log only if processing was invoked.
                 // If postParseRequest() failed, it has already logged it.
-                request.getMappingData().context.logAccess(request, response,
+                Context context = request.getContext();
+                // If the context is null, it is likely that the endpoint was
+                // shutdown, this connection closed and the request recycled in
+                // a different thread. That thread will have updated the access
+                // log so it is OK not to update the access log here in that
+                // case.
+                if (context != null) {
+                    context.logAccess(request, response,
                             System.currentTimeMillis() - req.getStartTime(), false);
                 }
+            }
 
             req.getRequestProcessor().setWorkerThreadName(null);
 
@@ -446,20 +455,17 @@ public class CoyoteAdapter implements Adapter {
             // Log at the lowest level available. logAccess() will be
             // automatically called on parent containers.
             boolean logged = false;
-            if (request.mappingData != null) {
-                if (request.mappingData.context != null) {
+            Context context = request.mappingData.context;
+            Host host = request.mappingData.host;
+            if (context != null) {
                 logged = true;
-                    request.mappingData.context.logAccess(
-                            request, response, time, true);
-                } else if (request.mappingData.host != null) {
+                context.logAccess(request, response, time, true);
+            } else if (host != null) {
                 logged = true;
-                    request.mappingData.host.logAccess(
-                            request, response, time, true);
-                }
+                host.logAccess(request, response, time, true);
             }
             if (!logged) {
-                connector.getService().getContainer().logAccess(
-                        request, response, time, true);
+                connector.getService().getContainer().logAccess(request, response, time, true);
             }
         } catch (Throwable t) {
             ExceptionUtils.handleThrowable(t);
@@ -973,24 +979,26 @@ public class CoyoteAdapter implements Adapter {
      * Look for SSL session ID if required. Only look for SSL Session ID if it
      * is the only tracking method enabled.
      *
-     * @param request The Servlet request obejct
+     * @param request The Servlet request object
      */
     protected void parseSessionSslId(Request request) {
         if (request.getRequestedSessionId() == null &&
                 SSL_ONLY.equals(request.getServletContext()
                         .getEffectiveSessionTrackingModes()) &&
                         request.connector.secure) {
-            request.setRequestedSessionId(
-                    request.getAttribute(SSLSupport.SESSION_ID_KEY).toString());
+            String sessionId = (String) request.getAttribute(SSLSupport.SESSION_ID_KEY);
+            if (sessionId != null) {
+                request.setRequestedSessionId(sessionId);
                 request.setRequestedSessionSSL(true);
             }
         }
+    }
 
 
     /**
      * Parse session id in URL.
      *
-     * @param request The Servlet request obejct
+     * @param request The Servlet request object
      */
     protected void parseSessionCookiesId(Request request) {
 
diff --git a/java/org/apache/catalina/connector/InputBuffer.java b/java/org/apache/catalina/connector/InputBuffer.java
index 7aee3f19..87097aed 100644
--- a/java/org/apache/catalina/connector/InputBuffer.java
+++ b/java/org/apache/catalina/connector/InputBuffer.java
@@ -34,6 +34,8 @@ import org.apache.catalina.security.SecurityUtil;
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.ContainerThreadMarker;
 import org.apache.coyote.Request;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.collections.SynchronizedStack;
@@ -56,6 +58,8 @@ public class InputBuffer extends Reader
      */
     protected static final StringManager sm = StringManager.getManager(InputBuffer.class);
 
+    private static final Log log = LogFactory.getLog(InputBuffer.class);
+
     public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
 
     // The buffer can be used for byte[] and char[] reading
@@ -271,7 +275,10 @@ public class InputBuffer extends Reader
 
     public boolean isReady() {
         if (coyoteRequest.getReadListener() == null) {
-            throw new IllegalStateException(sm.getString("inputBuffer.requiresNonBlocking"));
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("inputBuffer.requiresNonBlocking"));
+            }
+            return false;
         }
         if (isFinished()) {
             // If this is a non-container thread, need to trigger a read
diff --git a/java/org/apache/catalina/connector/LocalStrings.properties b/java/org/apache/catalina/connector/LocalStrings.properties
index 688f68f4..02923913 100644
--- a/java/org/apache/catalina/connector/LocalStrings.properties
+++ b/java/org/apache/catalina/connector/LocalStrings.properties
@@ -15,7 +15,7 @@
 coyoteAdapter.accesslogFail=Exception while attempting to add an entry to the access log
 coyoteAdapter.asyncDispatch=Exception while processing an asynchronous request
 coyoteAdapter.authenticate=Authenticated user [{0}] provided by connector
-coyoteAdapter.authorize=Authorizing user [{0}] using Tomcat's Realm
+coyoteAdapter.authorize=Authorizing user [{0}] using Tomcat''s Realm
 coyoteAdapter.checkRecycled.request=Encountered a non-recycled request and recycled it forcedly.
 coyoteAdapter.checkRecycled.response=Encountered a non-recycled response and recycled it forcedly.
 coyoteAdapter.debug=The variable [{0}] has value [{1}]
@@ -52,7 +52,9 @@ coyoteRequest.authenticate.ise=Cannot call authenticate() after the response has
 coyoteRequest.uploadLocationInvalid=The temporary upload location [{0}] is not valid
 coyoteRequest.sessionEndAccessFail=Exception triggered ending access to session while recycling request
 coyoteRequest.sendfileNotCanonical=Unable to determine canonical name of file [{0}] specified for use with sendfile
+coyoteRequest.filterAsyncSupportUnknown=Unable to determine if any filters do not support async processing
 coyoteRequest.maxPostSizeExceeded=The multi-part request contained parameter data (excluding uploaded files) that exceeded the limit for maxPostSize set on the associated connector
+coyoteRequest.noAsync=Unable to start async because the following classes in the processing chain do not support async [{0}]
 coyoteRequest.noMultipartConfig=Unable to process parts as no multi-part configuration has been provided
 
 coyoteResponse.getOutputStream.ise=getWriter() has already been called for this response
diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java
index 8d336a1f..7e8ea4b0 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -74,6 +74,7 @@ import org.apache.catalina.Realm;
 import org.apache.catalina.Session;
 import org.apache.catalina.TomcatPrincipal;
 import org.apache.catalina.Wrapper;
+import org.apache.catalina.core.ApplicationFilterChain;
 import org.apache.catalina.core.ApplicationMapping;
 import org.apache.catalina.core.ApplicationPart;
 import org.apache.catalina.core.ApplicationPushBuilder;
@@ -94,6 +95,7 @@ import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.buf.UDecoder;
 import org.apache.tomcat.util.http.CookieProcessor;
 import org.apache.tomcat.util.http.FastHttpDateFormat;
@@ -1643,7 +1645,11 @@ public class Request implements org.apache.catalina.servlet4preview.http.HttpSer
     public AsyncContext startAsync(ServletRequest request,
             ServletResponse response) {
         if (!isAsyncSupported()) {
-            throw new IllegalStateException(sm.getString("request.asyncNotSupported"));
+            IllegalStateException ise =
+                    new IllegalStateException(sm.getString("request.asyncNotSupported"));
+            log.warn(sm.getString("coyoteRequest.noAsync",
+                    StringUtils.join(getNonAsyncClassNames())), ise);
+            throw ise;
         }
 
         if (asyncContext == null) {
@@ -1657,6 +1663,31 @@ public class Request implements org.apache.catalina.servlet4preview.http.HttpSer
         return asyncContext;
     }
 
+
+    private Set<String> getNonAsyncClassNames() {
+        Set<String> result = new HashSet<>();
+
+        Wrapper wrapper = getWrapper();
+        if (!wrapper.isAsyncSupported()) {
+            result.add(wrapper.getServletClass());
+        }
+
+        FilterChain filterChain = getFilterChain();
+        if (filterChain instanceof ApplicationFilterChain) {
+            ((ApplicationFilterChain) filterChain).findNonAsyncFilters(result);
+        } else {
+            result.add(sm.getString("coyoteRequest.filterAsyncSupportUnknown"));
+        }
+
+        Container c = wrapper;
+        while (c != null) {
+            c.getPipeline().findNonAsyncValves(result);
+            c = c.getParent();
+        }
+
+        return result;
+    }
+
     @Override
     public boolean isAsyncStarted() {
         if (asyncContext == null) {
@@ -1932,7 +1963,7 @@ public class Request implements org.apache.catalina.servlet4preview.http.HttpSer
      * @return A builder to use to construct the push request
      */
     @Override
-    public PushBuilder getPushBuilder() {
+    public PushBuilder newPushBuilder() {
         AtomicBoolean result = new AtomicBoolean();
         coyoteRequest.action(ActionCode.IS_PUSH_SUPPORTED, result);
         if (result.get()) {
diff --git a/java/org/apache/catalina/connector/RequestFacade.java b/java/org/apache/catalina/connector/RequestFacade.java
index d11eddde..ca19d181 100644
--- a/java/org/apache/catalina/connector/RequestFacade.java
+++ b/java/org/apache/catalina/connector/RequestFacade.java
@@ -1135,7 +1135,7 @@ public class RequestFacade implements HttpServletRequest {
      * removed or replaced at any time until Servlet 4.0 becomes final.
      */
     @Override
-    public PushBuilder getPushBuilder() {
-        return request.getPushBuilder();
+    public PushBuilder newPushBuilder() {
+        return request.newPushBuilder();
     }
 }
diff --git a/java/org/apache/catalina/core/ApplicationContextFacade.java b/java/org/apache/catalina/core/ApplicationContextFacade.java
index 5af105c9..a8415d50 100644
--- a/java/org/apache/catalina/core/ApplicationContextFacade.java
+++ b/java/org/apache/catalina/core/ApplicationContextFacade.java
@@ -794,7 +794,7 @@ public class ApplicationContextFacade implements org.apache.catalina.servlet4pre
     @Override
     public void setSessionTimeout(int sessionTimeout) {
         if (SecurityUtil.isPackageProtectionEnabled()) {
-            doPrivileged("getSessionTimeout", new Object[] { Integer.valueOf(sessionTimeout) });
+            doPrivileged("setSessionTimeout", new Object[] { Integer.valueOf(sessionTimeout) });
         } else  {
             context.setSessionTimeout(sessionTimeout);
         }
diff --git a/java/org/apache/catalina/core/ApplicationFilterChain.java b/java/org/apache/catalina/core/ApplicationFilterChain.java
index 18e30bb1..f212c462 100644
--- a/java/org/apache/catalina/core/ApplicationFilterChain.java
+++ b/java/org/apache/catalina/core/ApplicationFilterChain.java
@@ -19,6 +19,7 @@ package org.apache.catalina.core;
 import java.io.IOException;
 import java.security.Principal;
 import java.security.PrivilegedActionException;
+import java.util.Set;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -43,7 +44,7 @@ import org.apache.tomcat.util.res.StringManager;
  *
  * @author Craig R. McClanahan
  */
-final class ApplicationFilterChain implements FilterChain {
+public final class ApplicationFilterChain implements FilterChain {
 
     // Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1
     private static final ThreadLocal<ServletRequest> lastServicedRequest;
@@ -326,4 +327,22 @@ final class ApplicationFilterChain implements FilterChain {
     void setServletSupportsAsync(boolean servletSupportsAsync) {
         this.servletSupportsAsync = servletSupportsAsync;
     }
+
+
+    /**
+     * Identifies the Filters, if any, in this FilterChain that do not support
+     * async.
+     *
+     * @param result The Set to which the fully qualified class names of each
+     *               Filter in this FilterChain that does not support async will
+     *               be added
+     */
+    public void findNonAsyncFilters(Set<String> result) {
+        for (int i = 0; i < n ; i++) {
+            ApplicationFilterConfig filter = filters[i];
+            if ("false".equalsIgnoreCase(filter.getFilterDef().getAsyncSupported())) {
+                result.add(filter.getFilterClass());
+            }
+        }
+    }
 }
diff --git a/java/org/apache/catalina/core/ApplicationHttpRequest.java b/java/org/apache/catalina/core/ApplicationHttpRequest.java
index cad7091e..84b42fa4 100644
--- a/java/org/apache/catalina/core/ApplicationHttpRequest.java
+++ b/java/org/apache/catalina/core/ApplicationHttpRequest.java
@@ -634,7 +634,7 @@ class ApplicationHttpRequest
 
 
     @Override
-    public PushBuilder getPushBuilder() {
+    public PushBuilder newPushBuilder() {
         return new ApplicationPushBuilder(this);
     }
 
diff --git a/java/org/apache/catalina/core/ApplicationMapping.java b/java/org/apache/catalina/core/ApplicationMapping.java
index dabf55e3..e939e67f 100644
--- a/java/org/apache/catalina/core/ApplicationMapping.java
+++ b/java/org/apache/catalina/core/ApplicationMapping.java
@@ -59,7 +59,13 @@ public class ApplicationMapping {
                                 "*" + path.substring(extIndex), mappingData.matchType, servletName);
                         break;
                     case PATH:
-                        mapping = new MappingImpl(mappingData.pathInfo.toString().substring(1),
+                        String matchValue;
+                        if (mappingData.pathInfo.isNull()) {
+                            matchValue = null;
+                        } else {
+                            matchValue = mappingData.pathInfo.toString().substring(1);
+                        }
+                        mapping = new MappingImpl(matchValue,
                                 mappingData.wrapperPath.toString() + "/*",
                                 mappingData.matchType, servletName);
                         break;
diff --git a/java/org/apache/catalina/core/ApplicationPushBuilder.java b/java/org/apache/catalina/core/ApplicationPushBuilder.java
index 6d907540..22f2bd6c 100644
--- a/java/org/apache/catalina/core/ApplicationPushBuilder.java
+++ b/java/org/apache/catalina/core/ApplicationPushBuilder.java
@@ -72,11 +72,8 @@ public class ApplicationPushBuilder implements PushBuilder {
     private final List<Cookie> cookies = new ArrayList<>();
     private String method = "GET";
     private String path;
-    private String eTag;
-    private String lastModified;
     private String queryString;
     private String sessionId;
-    private boolean conditional;
 
 
     public ApplicationPushBuilder(HttpServletRequest request) {
@@ -108,12 +105,8 @@ public class ApplicationPushBuilder implements PushBuilder {
 
         // Remove the headers
         headers.remove("if-match");
-        if (headers.remove("if-none-match") != null) {
-            conditional = true;
-        }
-        if (headers.remove("if-modified-since") != null) {
-            conditional = true;
-        }
+        headers.remove("if-none-match");
+        headers.remove("if-modified-since");
         headers.remove("if-unmodified-since");
         headers.remove("if-range");
         headers.remove("range");
@@ -228,32 +221,6 @@ public class ApplicationPushBuilder implements PushBuilder {
 
 
     @Override
-    public ApplicationPushBuilder eTag(String eTag) {
-        this.eTag = eTag;
-        return this;
-    }
-
-
-    @Override
-    public String getETag() {
-        return eTag;
-    }
-
-
-    @Override
-    public ApplicationPushBuilder lastModified(String lastModified) {
-        this.lastModified = lastModified;
-        return this;
-    }
-
-
-    @Override
-    public String getLastModified() {
-        return lastModified;
-    }
-
-
-    @Override
     public ApplicationPushBuilder queryString(String queryString) {
         this.queryString = queryString;
         return this;
@@ -280,19 +247,6 @@ public class ApplicationPushBuilder implements PushBuilder {
 
 
     @Override
-    public ApplicationPushBuilder conditional(boolean conditional) {
-        this.conditional = conditional;
-        return this;
-    }
-
-
-    @Override
-    public boolean isConditional() {
-        return conditional;
-    }
-
-
-    @Override
     public ApplicationPushBuilder addHeader(String name, String value) {
         List<String> values = headers.get(name);
         if (values == null) {
@@ -404,14 +358,6 @@ public class ApplicationPushBuilder implements PushBuilder {
             pushTarget.queryString().setString(pushQueryString + "&" +queryString);
         }
 
-        if (conditional) {
-            if (eTag != null) {
-                setHeader("if-none-match", eTag);
-            } else if (lastModified != null) {
-                setHeader("if-modified-since", lastModified);
-            }
-        }
-
         // Cookies
         setHeader("cookie", generateCookieHeader(cookies,
                 catalinaRequest.getContext().getCookieProcessor()));
@@ -421,8 +367,6 @@ public class ApplicationPushBuilder implements PushBuilder {
         // Reset for next call to this method
         pushTarget = null;
         path = null;
-        eTag = null;
-        lastModified = null;
         headers.remove("if-none-match");
         headers.remove("if-modified-since");
     }
diff --git a/java/org/apache/catalina/core/LocalStrings.properties b/java/org/apache/catalina/core/LocalStrings.properties
index b4b74506..2786bfd0 100644
--- a/java/org/apache/catalina/core/LocalStrings.properties
+++ b/java/org/apache/catalina/core/LocalStrings.properties
@@ -60,7 +60,7 @@ applicationPushBuilder.methodNotToken=HTTP methods must be tokens but [{0}] cont
 applicationPushBuilder.noCoyoteRequest=Unable to find the underlying Coyote request object (which is required to create a push request) from the request of type [{0}]
 
 applicationServletRegistration.setServletSecurity.iae=Null constraint specified for servlet [{0}] deployed to context with name [{1}]
-applicationServletRegistration.setServletSecurity.ise=Security constraints can't be added to servlet [{0}] deployed to context with name [{1}] as the context has already been initialised
+applicationServletRegistration.setServletSecurity.ise=Security constraints can''t be added to servlet [{0}] deployed to context with name [{1}] as the context has already been initialised
 applicationSessionCookieConfig.ise=Property {0} cannot be added to SessionCookieConfig for context {1} as the context has been initialised
 aprListener.aprInit=The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: {0}
 aprListener.aprInitDebug=The APR based Apache Tomcat Native library could not be found using names [{0}] on the java.library.path [{1}]. The errors reported were [{2}]
@@ -141,9 +141,9 @@ standardContext.listenerFail=One or more listeners failed to start. Full details
 standardContext.listenerStart=Exception sending context initialized event to listener instance of class {0}
 standardContext.listenerStop=Exception sending context destroyed event to listener instance of class {0}
 standardContext.loadOnStartup.loadException=Servlet [{1}] in web application [{0}] threw load() exception
-standardContext.loginConfig.errorPage=Form error page {0} must start with a ''/'
+standardContext.loginConfig.errorPage=Form error page {0} must start with a ''/''
 standardContext.loginConfig.errorWarning=WARNING: Form error page {0} must start with a ''/'' in Servlet 2.4
-standardContext.loginConfig.loginPage=Form login page {0} must start with a ''/'
+standardContext.loginConfig.loginPage=Form login page {0} must start with a ''/''
 standardContext.loginConfig.loginWarning=WARNING: Form login page {0} must start with a ''/'' in Servlet 2.4
 standardContext.loginConfig.required=LoginConfig cannot be null
 standardContext.manager=Configured a manager of class [{0}]
@@ -188,7 +188,7 @@ standardEngine.notHost=Child of an Engine must be a Host
 standardEngine.notParent=Engine cannot have a parent Container
 standardHost.asyncStateError=An asynchronous request was received for processing that was neither an async dispatch nor an error to process
 standardHost.clientAbort=Remote Client Aborted Request, IOException: {0}
-standardHost.invalidErrorReportValveClass=Couldn't load specified error report valve class: {0}
+standardHost.invalidErrorReportValveClass=Couldn''t load specified error report valve class: {0}
 standardHost.noContext=No Context configured to process this request
 standardHost.notContext=Child of a Host must be a Context
 standardHost.nullName=Host name is required
diff --git a/java/org/apache/catalina/core/LocalStrings_es.properties b/java/org/apache/catalina/core/LocalStrings_es.properties
index 9a5ef37b..077e6f18 100644
--- a/java/org/apache/catalina/core/LocalStrings_es.properties
+++ b/java/org/apache/catalina/core/LocalStrings_es.properties
@@ -88,9 +88,9 @@ standardContext.requestListener.requestInit = Una excepci\u00F3n durante el env\
 standardContext.isUnavailable = Esta aplicaci\u00F3n no est\u00E1 disponible en este momento
 standardContext.listenerStart = Excepci\u00F3n enviando evento inicializado de contexto a instancia de escuchador de clase {0}
 standardContext.listenerStop = Excepci\u00F3n enviando evento de contexto destru\u00EDdo a instancia de escuchador de clase {0}
-standardContext.loginConfig.errorPage = La P\u00E1gina de error de Formulario {0} debe de comenzar con ''/'
+standardContext.loginConfig.errorPage = La P\u00E1gina de error de Formulario {0} debe de comenzar con ''/''
 standardContext.loginConfig.errorWarning = AVISO\: La p\u00E1gina de error de Formulario {0} debe de comenzar con ''/'' en Servlet 2.4
-standardContext.loginConfig.loginPage = La p\u00E1gina de login de Formulario {0} debe de comenzar con ''/'
+standardContext.loginConfig.loginPage = La p\u00E1gina de login de Formulario {0} debe de comenzar con ''/''
 standardContext.loginConfig.loginWarning = AVISO\: La p\u00E1gina de login de Formulario {0} debe de comenzar con ''/'' en Servlet 2.4
 standardContext.loginConfig.required = LoginConfig no puede ser nula
 standardContext.manager = Configurado un gestor de la clase [{0}]
diff --git a/java/org/apache/catalina/core/LocalStrings_fr.properties b/java/org/apache/catalina/core/LocalStrings_fr.properties
index ed425712..c0d9ec18 100644
--- a/java/org/apache/catalina/core/LocalStrings_fr.properties
+++ b/java/org/apache/catalina/core/LocalStrings_fr.properties
@@ -30,7 +30,7 @@ naming.invalidEnvEntryValue=L''entr\u00e9e environnement {0} a une valeur invali
 naming.namingContextCreationFailed=La cr\u00e9ation du contexte de nommage (naming context) a \u00e9chou\u00e9 : {0}
 standardContext.applicationListener=Erreur lors de la configuration de la classe d''\u00e9coute de l''application (application listener) {0}
 standardContext.applicationSkipped=L''installation des \u00e9couteurs (listeners) de l''application a \u00e9t\u00e9 saut\u00e9e suite aux erreurs pr\u00e9c\u00e9dentes
-standardContext.errorPage.error=La position de la page d''erreur (ErrorPage) {0} doit commencer par un ''/'
+standardContext.errorPage.error=La position de la page d''erreur (ErrorPage) {0} doit commencer par un ''/''
 standardContext.errorPage.required=La page d''erreur (ErrorPage) ne peut \u00eatre nulle
 standardContext.errorPage.warning=ATTENTION: La position de la page d''erreur (ErrorPage) {0} doit commencer par un ''/'' dans l''API Servlet 2.4
 standardContext.filterMap.either=L''association de filtre (filter mapping) doit indiquer soit une <url-pattern> soit une <servlet-name>
diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
index 397749b3..fc82f5dc 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -115,6 +115,7 @@ import org.apache.tomcat.InstanceManagerBindings;
 import org.apache.tomcat.JarScanner;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.buf.UDecoder;
 import org.apache.tomcat.util.descriptor.XmlIdentifiers;
 import org.apache.tomcat.util.descriptor.web.ApplicationParameter;
@@ -1031,17 +1032,7 @@ public class StandardContext extends ContainerBase
 
     @Override
     public String getResourceOnlyServlets() {
-        StringBuilder result = new StringBuilder();
-        boolean first = true;
-        for (String servletName : resourceOnlyServlets) {
-            if (first) {
-                first = false;
-            } else {
-                result.append(',');
-            }
-            result.append(servletName);
-        }
-        return result.toString();
+        return StringUtils.join(resourceOnlyServlets);
     }
 
 
diff --git a/java/org/apache/catalina/core/StandardPipeline.java b/java/org/apache/catalina/core/StandardPipeline.java
index 040baed8..815890d2 100644
--- a/java/org/apache/catalina/core/StandardPipeline.java
+++ b/java/org/apache/catalina/core/StandardPipeline.java
@@ -20,6 +20,7 @@ package org.apache.catalina.core;
 
 
 import java.util.ArrayList;
+import java.util.Set;
 
 import javax.management.ObjectName;
 
@@ -117,9 +118,20 @@ public class StandardPipeline extends LifecycleBase
     }
 
 
-    // ------------------------------------------------------ Contained Methods
+    @Override
+    public void findNonAsyncValves(Set<String> result) {
+        Valve valve = (first!=null) ? first : basic;
+        while (valve != null) {
+            if (!valve.isAsyncSupported()) {
+                result.add(valve.getClass().getName());
+            }
+            valve = valve.getNext();
+        }
+    }
 
 
+    // ------------------------------------------------------ Contained Methods
+
     /**
      * Return the Container with which this Pipeline is associated.
      */
diff --git a/java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java b/java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java
index 78324772..48b7cf6f 100644
--- a/java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java
+++ b/java/org/apache/catalina/filters/HttpHeaderSecurityFilter.java
@@ -43,6 +43,7 @@ public class HttpHeaderSecurityFilter extends FilterBase {
     private boolean hstsEnabled = true;
     private int hstsMaxAgeSeconds = 0;
     private boolean hstsIncludeSubDomains = false;
+    private boolean hstsPreload = false;
     private String hstsHeaderValue;
 
     // Click-jacking protection
@@ -72,6 +73,9 @@ public class HttpHeaderSecurityFilter extends FilterBase {
         if (hstsIncludeSubDomains) {
             hstsValue.append(";includeSubDomains");
         }
+        if (hstsPreload) {
+            hstsValue.append(";preload");
+        }
         hstsHeaderValue = hstsValue.toString();
 
         // Anti click-jacking
@@ -169,19 +173,26 @@ public class HttpHeaderSecurityFilter extends FilterBase {
     }
 
 
+    public boolean isHstsPreload() {
+        return hstsPreload;
+    }
+
+
+    public void setHstsPreload(boolean hstsPreload) {
+        this.hstsPreload = hstsPreload;
+    }
+
 
     public boolean isAntiClickJackingEnabled() {
         return antiClickJackingEnabled;
     }
 
 
-
     public void setAntiClickJackingEnabled(boolean antiClickJackingEnabled) {
         this.antiClickJackingEnabled = antiClickJackingEnabled;
     }
 
 
-
     public String getAntiClickJackingOption() {
         return antiClickJackingOption.toString();
     }
diff --git a/java/org/apache/catalina/ha/deploy/LocalStrings.properties b/java/org/apache/catalina/ha/deploy/LocalStrings.properties
index 5c1374f1..e7b2dee8 100644
--- a/java/org/apache/catalina/ha/deploy/LocalStrings.properties
+++ b/java/org/apache/catalina/ha/deploy/LocalStrings.properties
@@ -20,7 +20,7 @@ farmWarDeployer.deployEnd=Deployment from [{0}] finished.
 farmWarDeployer.fileCopyFail=Unable to copy from [{0}] to [{1}]
 farmWarDeployer.hostOnly=FarmWarDeployer can only work as host cluster subelement!
 farmWarDeployer.hostParentEngine=FarmWarDeployer can only work if parent of [{0}] is an engine!
-farmWarDeployer.mbeanNameFail=Can't construct MBean object name for engine [{0}] and host [{1}]
+farmWarDeployer.mbeanNameFail=Can''t construct MBean object name for engine [{0}] and host [{1}]
 farmWarDeployer.alreadyDeployed=webapp [{0}] are already deployed.
 farmWarDeployer.modInstall=Installing webapp [{0}] from [{1}]
 farmWarDeployer.modRemoveFail=No removal
@@ -39,7 +39,7 @@ farmWarDeployer.sendEnd=Send cluster war deployment path [{0}], war [{1}] finish
 farmWarDeployer.sendFragment=Send cluster war fragment path [{0}], war [{1}] to [{2}]
 farmWarDeployer.sendStart=Send cluster war deployment path [{0}], war [{1}] started.
 farmWarDeployer.servicingDeploy=Application [{0}] is being serviced. Touch war file [{1}] again!
-farmWarDeployer.servicingUndeploy=Application [{0}] is being serviced and can't be removed from backup cluster node
+farmWarDeployer.servicingUndeploy=Application [{0}] is being serviced and can''t be removed from backup cluster node
 farmWarDeployer.started=Cluster FarmWarDeployer started.
 farmWarDeployer.stopped=Cluster FarmWarDeployer stopped.
 farmWarDeployer.undeployEnd=Undeployment from [{0}] finished.
diff --git a/java/org/apache/catalina/ha/session/LocalStrings.properties b/java/org/apache/catalina/ha/session/LocalStrings.properties
index 204a94ca..6aa7f3b5 100644
--- a/java/org/apache/catalina/ha/session/LocalStrings.properties
+++ b/java/org/apache/catalina/ha/session/LocalStrings.properties
@@ -62,7 +62,7 @@ deltaRequest.ssid.null=Session Id is null for setSessionId
 deltaSession.notifying=Notifying cluster of expiration primary={0} sessionId [{1}]
 deltaSession.readSession=readObject() loading session [{0}]
 deltaSession.writeSession=writeObject() storing session [{0}]
-jvmRoute.cannotFindSession=Can't find session [{0}]
+jvmRoute.cannotFindSession=Can''t find session [{0}]
 jvmRoute.changeSession=Changed session from [{0}] to [{1}]
 jvmRoute.failover=Detected a failover with different jvmRoute - orginal route: [{0}] new one: [{1}] at session id [{2}]
 jvmRoute.foundManager=Found Cluster Manager {0} at {1}
@@ -80,4 +80,4 @@ backupManager.noCluster=no cluster associated with this context: [{0}]
 backupManager.startUnable=Unable to start BackupManager: [{0}]
 backupManager.startFailed=Failed to start BackupManager: [{0}]
 backupManager.stopped=Manager [{0}] is stopping
-clusterSessionListener.noManager=Context manager doesn't exist:{0}
+clusterSessionListener.noManager=Context manager doesn''t exist:{0}
diff --git a/java/org/apache/catalina/ha/tcp/LocalStrings.properties b/java/org/apache/catalina/ha/tcp/LocalStrings.properties
index d4f368a2..df6accf3 100644
--- a/java/org/apache/catalina/ha/tcp/LocalStrings.properties
+++ b/java/org/apache/catalina/ha/tcp/LocalStrings.properties
@@ -24,7 +24,7 @@ ReplicationValve.nocluster=No cluster configured for this request.
 ReplicationValve.resetDeltaRequest=Cluster is standalone: reset Session Request Delta at context {0}
 ReplicationValve.send.failure=Unable to perform replication request.
 ReplicationValve.send.invalid.failure=Unable to send session [id={0}] invalid message over cluster.
-ReplicationValve.session.found=Context {0}: Found session {1} but it isn't a ClusterSession.
+ReplicationValve.session.found=Context {0}: Found session {1} but it isn''t a ClusterSession.
 ReplicationValve.session.indicator=Context {0}: Primarity of session {0} in request attribute {1} is {2}.
 ReplicationValve.session.invalid=Context {0}: Requested session {1} is invalid, removed or not replicated at this node.
 ReplicationValve.stats=Average request time= {0} ms for Cluster overhead time={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request={6} ms Cluster={7} ms).
diff --git a/java/org/apache/catalina/ha/tcp/LocalStrings_es.properties b/java/org/apache/catalina/ha/tcp/LocalStrings_es.properties
index f8d258d9..1cabdd5e 100644
--- a/java/org/apache/catalina/ha/tcp/LocalStrings_es.properties
+++ b/java/org/apache/catalina/ha/tcp/LocalStrings_es.properties
@@ -24,8 +24,7 @@ ReplicationValve.nocluster = No cluster configured for this request.
 ReplicationValve.resetDeltaRequest = Cluster is standalone\: reset Session Request Delta at context {0}
 ReplicationValve.send.failure = Unable to perform replication request.
 ReplicationValve.send.invalid.failure = Unable to send session [id\={0}] invalid message over cluster.
-ReplicationValve.session.found = Context {0}\: Found session {1} but it isn't a ClusterSession.
+ReplicationValve.session.found = Context {0}\: Found session {1} but it isn''t a ClusterSession.
 ReplicationValve.session.indicator = Context {0}\: Primarity of session {0} in request attribute {1} is {2}.
 ReplicationValve.session.invalid = Context {0}\: Requested session {1} is invalid, removed or not replicated at this node.
 ReplicationValve.stats = Average request time\= {0} ms for Cluster overhead time\={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request\={6} ms Cluster\={7} ms).
-
diff --git a/java/org/apache/catalina/manager/host/HostManagerServlet.java b/java/org/apache/catalina/manager/host/HostManagerServlet.java
index 267c58d0..605c1029 100644
--- a/java/org/apache/catalina/manager/host/HostManagerServlet.java
+++ b/java/org/apache/catalina/manager/host/HostManagerServlet.java
@@ -44,6 +44,7 @@ import org.apache.catalina.core.ContainerBase;
 import org.apache.catalina.core.StandardHost;
 import org.apache.catalina.startup.HostConfig;
 import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -519,15 +520,8 @@ public class HostManagerServlet
             Host host = (Host) hosts[i];
             String name = host.getName();
             String[] aliases = host.findAliases();
-            StringBuilder buf = new StringBuilder();
-            if (aliases.length > 0) {
-                buf.append(aliases[0]);
-                for (int j = 1; j < aliases.length; j++) {
-                    buf.append(',').append(aliases[j]);
-                }
-            }
             writer.println(smClient.getString("hostManagerServlet.listitem",
-                                        name, buf.toString()));
+                    name, StringUtils.join(aliases)));
         }
     }
 
diff --git a/java/org/apache/catalina/realm/LocalStrings.properties b/java/org/apache/catalina/realm/LocalStrings.properties
index 5b591ca5..7eb65844 100644
--- a/java/org/apache/catalina/realm/LocalStrings.properties
+++ b/java/org/apache/catalina/realm/LocalStrings.properties
@@ -29,7 +29,7 @@ jaasRealm.loginContextCreated=JAAS LoginContext created for username "{0}"
 jaasRealm.checkPrincipal=Checking Principal "{0}" [{1}]
 jaasRealm.userPrincipalSuccess=Principal "{0}" is a valid user class. We will use this as the user Principal.
 jaasRealm.userPrincipalFailure=No valid user Principal found
-jaasRealm.rolePrincipalAdd=Adding role Principal "{0}" to this user Principal's roles
+jaasRealm.rolePrincipalAdd=Adding role Principal "{0}" to this user Principal''s roles
 jaasRealm.rolePrincipalFailure=No valid role Principals found.
 jaasCallback.username=Returned username "{0}"
 jdbcRealm.authenticateFailure=Username {0} NOT successfully authenticated
diff --git a/java/org/apache/catalina/security/SecurityListener.java b/java/org/apache/catalina/security/SecurityListener.java
index feb056a3..782753f4 100644
--- a/java/org/apache/catalina/security/SecurityListener.java
+++ b/java/org/apache/catalina/security/SecurityListener.java
@@ -17,7 +17,6 @@
 package org.apache.catalina.security;
 
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Locale;
 import java.util.Set;
 
@@ -26,6 +25,7 @@ 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.buf.StringUtils;
 import org.apache.tomcat.util.res.StringManager;
 
 public class SecurityListener implements LifecycleListener {
@@ -97,18 +97,7 @@ public class SecurityListener implements LifecycleListener {
      * @return  A comma separated list of operating system user names.
      */
     public String getCheckedOsUsers() {
-        if (checkedOsUsers.size() == 0) {
-            return "";
-        }
-
-        StringBuilder result = new StringBuilder();
-        Iterator<String> iter = checkedOsUsers.iterator();
-        result.append(iter.next());
-        while (iter.hasNext()) {
-            result.append(',');
-            result.append(iter.next());
-        }
-        return result.toString();
+        return StringUtils.join(checkedOsUsers);
     }
 
 
diff --git a/java/org/apache/catalina/servlet4preview/http/HttpServletRequest.java b/java/org/apache/catalina/servlet4preview/http/HttpServletRequest.java
index b55ec06b..ebd1b063 100644
--- a/java/org/apache/catalina/servlet4preview/http/HttpServletRequest.java
+++ b/java/org/apache/catalina/servlet4preview/http/HttpServletRequest.java
@@ -37,5 +37,5 @@ public interface HttpServletRequest extends javax.servlet.http.HttpServletReques
      *
      * @since Servlet 4.0
      */
-    public PushBuilder getPushBuilder();
+    public PushBuilder newPushBuilder();
 }
diff --git a/java/org/apache/catalina/servlet4preview/http/HttpServletRequestWrapper.java b/java/org/apache/catalina/servlet4preview/http/HttpServletRequestWrapper.java
index 7904efa7..a5050432 100644
--- a/java/org/apache/catalina/servlet4preview/http/HttpServletRequestWrapper.java
+++ b/java/org/apache/catalina/servlet4preview/http/HttpServletRequestWrapper.java
@@ -56,12 +56,12 @@ public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletReq
      * {@inheritDoc}
      * <p>
      * The default behavior of this method is to return
-     * {@link HttpServletRequest#getPushBuilder()} on the wrapped request object.
+     * {@link HttpServletRequest#newPushBuilder()} on the wrapped request object.
      *
      * @since Servlet 4.0
      */
     @Override
-    public PushBuilder getPushBuilder() {
-        return this._getHttpServletRequest().getPushBuilder();
+    public PushBuilder newPushBuilder() {
+        return this._getHttpServletRequest().newPushBuilder();
     }
 }
diff --git a/java/org/apache/catalina/servlet4preview/http/PushBuilder.java b/java/org/apache/catalina/servlet4preview/http/PushBuilder.java
index c5ab5dbc..991d446d 100644
--- a/java/org/apache/catalina/servlet4preview/http/PushBuilder.java
+++ b/java/org/apache/catalina/servlet4preview/http/PushBuilder.java
@@ -34,9 +34,6 @@ import java.util.Set;
  * <li>The referer header will be set to
  *     {@link HttpServletRequest#getRequestURL()} plus, if present, the query
  *     string from {@link HttpServletRequest#getQueryString()}.
- * <li>If either of the headers {@code If-Modified-Since} or
- *     {@code If-None-Match} were present then {@link #isConditional()} will be
- *     set to {@code true}.
  * </ul>
  *
  * @since Servlet 4.0
@@ -87,17 +84,6 @@ public interface PushBuilder {
     PushBuilder sessionId(String sessionId);
 
     /**
-     * Sets if the request will be conditional. If {@code true} the values from
-     * {@link #getETag()} and {@link #getLastModified()} will be used to
-     * construct appropriate headers.
-     *
-     * @param conditional Should generated push requests be conditional
-     *
-     * @return This builder instance
-     */
-    PushBuilder conditional(boolean conditional);
-
-    /**
      * Sets a HTTP header on the request. Any existing headers of the same name
      * are first remove.
      *
@@ -143,28 +129,6 @@ public interface PushBuilder {
     PushBuilder path(String path);
 
     /**
-     * Sets the eTag to be used for conditional push requests. This will be
-     * set to {@code null} after a call to {@link #push()} so it must be
-     * explicitly set for every push request that requires it.
-     *
-     * @param eTag The eTag use for the push request
-     *
-     * @return This builder instance
-     */
-    PushBuilder eTag(String eTag);
-
-    /**
-     * Sets the last modified to be used for conditional push requests. This
-     * will be set to {@code null} after a call to {@link #push()} so it must be
-     * explicitly set for every push request that requires it.
-     *
-     * @param lastModified The last modified value to use for the push request
-     *
-     * @return This builder instance
-     */
-    PushBuilder lastModified(String lastModified);
-
-    /**
      * Generates the push request and sends it to the client unless pushes are
      * not available for some reason. After calling this method the following
      * fields are set to {@code null}:
@@ -205,14 +169,6 @@ public interface PushBuilder {
     String getSessionId();
 
     /**
-     * Will push requests generated by future calls to {@code push()} be
-     * conditional.
-     *
-     * @return {@code true} if push requests will be conditional
-     */
-    boolean isConditional();
-
-    /**
      * @return The current set of names of HTTP headers to be used the next time
      *         {@code push()} is called.
      */
@@ -237,21 +193,4 @@ public interface PushBuilder {
      * @return The path value that will be associated with the next push request
      */
     String getPath();
-
-    /**
-     * Obtain the eTag that will be used for the push request that will be
-     * generated by the next call to {@code push()}.
-     *
-     * @return The eTag value that will be associated with the next push request
-     */
-    String getETag();
-
-    /**
-     * Obtain the last modified that will be used for the push request that will
-     * be generated by the next call to {@code push()}.
-     *
-     * @return The last modified value that will be associated with the next
-     *         push request
-     */
-    String getLastModified();
 }
diff --git a/java/org/apache/catalina/startup/Catalina.java b/java/org/apache/catalina/startup/Catalina.java
index 22a8d348..2c7a55fd 100644
--- a/java/org/apache/catalina/startup/Catalina.java
+++ b/java/org/apache/catalina/startup/Catalina.java
@@ -125,6 +125,7 @@ public class Catalina {
 
     public Catalina() {
         setSecurityProtection();
+        ExceptionUtils.preload();
     }
 
 
diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java
index 012a17fd..1f6b84bd 100644
--- a/java/org/apache/catalina/startup/ContextConfig.java
+++ b/java/org/apache/catalina/startup/ContextConfig.java
@@ -142,7 +142,7 @@ public class ContextConfig implements LifecycleListener {
         // Load our mapping properties for the standard authenticators
         Properties props = new Properties();
         try (InputStream is = ContextConfig.class.getClassLoader().getResourceAsStream(
-                "org/apache/catalina/startup/Authenticators.properties");) {
+                "org/apache/catalina/startup/Authenticators.properties")) {
             if (is != null) {
                 props.load(is);
             }
diff --git a/java/org/apache/catalina/startup/LocalStrings.properties b/java/org/apache/catalina/startup/LocalStrings.properties
index bb2e3038..498faa5b 100644
--- a/java/org/apache/catalina/startup/LocalStrings.properties
+++ b/java/org/apache/catalina/startup/LocalStrings.properties
@@ -52,7 +52,7 @@ contextConfig.invalidSciHandlesTypes=Unable to load class [{0}] to check against
 contextConfig.jarFile=Unable to process Jar [{0}] for annotations
 contextConfig.jndiUrl=Unable to process JNDI URL [{0}] for annotations
 contextConfig.jndiUrlNotDirContextConn=The connection created for URL [{0}] was not a DirContextURLConnection
-contextConfig.jspFile.error=JSP file {0} must start with a ''/'
+contextConfig.jspFile.error=JSP file {0} must start with a ''/''
 contextConfig.jspFile.warning=WARNING: JSP file {0} must start with a ''/'' in Servlet 2.4
 contextConfig.missingRealm=No Realm has been configured to authenticate against
 contextConfig.processAnnotationsDir.debug=Scanning directory for class files with annotations [{0}]
diff --git a/java/org/apache/catalina/startup/LocalStrings_es.properties b/java/org/apache/catalina/startup/LocalStrings_es.properties
index 64907069..0fa6e4bc 100644
--- a/java/org/apache/catalina/startup/LocalStrings_es.properties
+++ b/java/org/apache/catalina/startup/LocalStrings_es.properties
@@ -44,7 +44,7 @@ contextConfig.invalidSci = No se pudo crear el ServletContentInitializer [{0}]
 contextConfig.invalidSciHandlesTypes = No puedo cargar la clase [{0}] para revisar contra la anotaci\u00F3n  @HandlesTypes de uno o m\u00E1s ServletContentInitializers.
 contextConfig.jndiUrl = No puedo procesar la URL JNDI [{0}] para las anotaciones
 contextConfig.jndiUrlNotDirContextConn = La conexi\u00F3n creada para la URL [{0}] no era una DirContextURLConnection
-contextConfig.jspFile.error = El archivo JSP {0} debe de comenzar con ''/'
+contextConfig.jspFile.error = El archivo JSP {0} debe de comenzar con ''/''
 contextConfig.jspFile.warning = AVISO\: El archivo JSP {0} debe de comenzar con ''/'' en Servlet 2.4
 contextConfig.missingRealm = Alg\u00FAn reino (realm) no ha sido configurado para realizar la autenticaci\u00F3n
 contextConfig.resourceJarFail = Hallado JAR fallido a los procesos en URL [{0}] para recursos est\u00E1ticos a ser incluidos en contexto con nombre [{0}]
diff --git a/java/org/apache/catalina/startup/Tomcat.java b/java/org/apache/catalina/startup/Tomcat.java
index 7285c527..fe57ba05 100644
--- a/java/org/apache/catalina/startup/Tomcat.java
+++ b/java/org/apache/catalina/startup/Tomcat.java
@@ -61,6 +61,7 @@ import org.apache.catalina.core.StandardService;
 import org.apache.catalina.core.StandardWrapper;
 import org.apache.catalina.realm.GenericPrincipal;
 import org.apache.catalina.realm.RealmBase;
+import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.buf.UriUtil;
 import org.apache.tomcat.util.descriptor.web.LoginConfig;
 
@@ -145,7 +146,7 @@ public class Tomcat {
     private final Map<String, Principal> userPrincipals = new HashMap<>();
 
     public Tomcat() {
-        // NOOP
+        ExceptionUtils.preload();
     }
 
     /**
diff --git a/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties b/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties
index 1626d99c..0103591d 100644
--- a/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties
+++ b/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties
@@ -42,7 +42,7 @@ tcpFailureDetector.already.disappeared=Verification complete. Member already dis
 tcpFailureDetector.member.disappeared=Verification complete. Member disappeared[{0}]
 tcpFailureDetector.still.alive=Verification complete. Member still alive[{0}]
 tcpFailureDetector.heartbeat.failed=Unable to perform heartbeat on the TcpFailureDetector.
-tcpFailureDetector.performBasicCheck.memberAdded=Member added, even though we weren't  notified:{0}
+tcpFailureDetector.performBasicCheck.memberAdded=Member added, even though we weren''t notified:{0}
 tcpFailureDetector.suspectMember.dead=Suspect member, confirmed dead.[{0}]
 tcpFailureDetector.suspectMember.alive=Suspect member, confirmed alive.[{0}]
 tcpFailureDetector.failureDetection.failed=Unable to perform failure detection check, assuming member down.
diff --git a/java/org/apache/catalina/users/MemoryGroup.java b/java/org/apache/catalina/users/MemoryGroup.java
index a1c2acc5..e3bdc5be 100644
--- a/java/org/apache/catalina/users/MemoryGroup.java
+++ b/java/org/apache/catalina/users/MemoryGroup.java
@@ -25,6 +25,8 @@ import java.util.Iterator;
 import org.apache.catalina.Role;
 import org.apache.catalina.User;
 import org.apache.catalina.UserDatabase;
+import org.apache.tomcat.util.buf.StringUtils;
+import org.apache.tomcat.util.buf.StringUtils.Function;
 
 
 /**
@@ -200,22 +202,12 @@ public class MemoryGroup extends AbstractGroup {
         synchronized (roles) {
             if (roles.size() > 0) {
                 sb.append(" roles=\"");
-                int n = 0;
-                Iterator<Role> values = roles.iterator();
-                while (values.hasNext()) {
-                    if (n > 0) {
-                        sb.append(',');
-                    }
-                    n++;
-                    sb.append((values.next()).getRolename());
-                }
+                StringUtils.join(roles, ',', new Function<Role>(){
+                    @Override public String apply(Role t) { return t.getRolename(); }}, sb);
                 sb.append("\"");
             }
         }
         sb.append("/>");
         return (sb.toString());
-
     }
-
-
 }
diff --git a/java/org/apache/catalina/users/MemoryUser.java b/java/org/apache/catalina/users/MemoryUser.java
index 744c8117..f4e30641 100644
--- a/java/org/apache/catalina/users/MemoryUser.java
+++ b/java/org/apache/catalina/users/MemoryUser.java
@@ -26,6 +26,8 @@ import org.apache.catalina.Group;
 import org.apache.catalina.Role;
 import org.apache.catalina.UserDatabase;
 import org.apache.catalina.util.RequestUtil;
+import org.apache.tomcat.util.buf.StringUtils;
+import org.apache.tomcat.util.buf.StringUtils.Function;
 
 /**
  * <p>Concrete implementation of {@link org.apache.catalina.User} for the
@@ -271,38 +273,30 @@ public class MemoryUser extends AbstractUser {
         synchronized (groups) {
             if (groups.size() > 0) {
                 sb.append(" groups=\"");
-                int n = 0;
-                Iterator<Group> values = groups.iterator();
-                while (values.hasNext()) {
-                    if (n > 0) {
-                        sb.append(',');
-                    }
-                    n++;
-                    sb.append(RequestUtil.filter(values.next().getGroupname()));
+                StringUtils.join(groups, ',', new Function<Group>() {
+                    @Override public String apply(Group t) {
+                        return RequestUtil.filter(t.getGroupname());
                     }
+                }, sb);
                 sb.append("\"");
             }
         }
         synchronized (roles) {
             if (roles.size() > 0) {
                 sb.append(" roles=\"");
-                int n = 0;
-                Iterator<Role> values = roles.iterator();
-                while (values.hasNext()) {
-                    if (n > 0) {
-                        sb.append(',');
-                    }
-                    n++;
-                    sb.append(RequestUtil.filter(values.next().getRolename()));
+                StringUtils.join(roles, ',', new Function<Role>() {
+                    @Override public String apply(Role t) {
+                        return RequestUtil.filter(t.getRolename());
                     }
+                }, sb);
                 sb.append("\"");
             }
         }
         sb.append("/>");
-        return (sb.toString());
-
+        return sb.toString();
     }
 
+
     /**
      * <p>Return a String representation of this user.</p>
      */
@@ -320,35 +314,25 @@ public class MemoryUser extends AbstractUser {
         synchronized (groups) {
             if (groups.size() > 0) {
                 sb.append(", groups=\"");
-                int n = 0;
-                Iterator<Group> values = groups.iterator();
-                while (values.hasNext()) {
-                    if (n > 0) {
-                        sb.append(',');
-                    }
-                    n++;
-                    sb.append(RequestUtil.filter(values.next().getGroupname()));
+                StringUtils.join(groups, ',', new Function<Group>() {
+                    @Override public String apply(Group t) {
+                        return RequestUtil.filter(t.getGroupname());
                     }
+                }, sb);
                 sb.append("\"");
             }
         }
         synchronized (roles) {
             if (roles.size() > 0) {
                 sb.append(", roles=\"");
-                int n = 0;
-                Iterator<Role> values = roles.iterator();
-                while (values.hasNext()) {
-                    if (n > 0) {
-                        sb.append(',');
-                    }
-                    n++;
-                    sb.append(RequestUtil.filter(values.next().getRolename()));
+                StringUtils.join(roles, ',', new Function<Role>() {
+                    @Override public String apply(Role t) {
+                        return RequestUtil.filter(t.getRolename());
                     }
+                }, sb);
                 sb.append("\"");
             }
         }
-        return (sb.toString());
+        return sb.toString();
     }
-
-
 }
diff --git a/java/org/apache/catalina/util/ExtensionValidator.java b/java/org/apache/catalina/util/ExtensionValidator.java
index f9da4cf7..2b358027 100644
--- a/java/org/apache/catalina/util/ExtensionValidator.java
+++ b/java/org/apache/catalina/util/ExtensionValidator.java
@@ -150,9 +150,8 @@ public final class ExtensionValidator {
             if (manifestResource.isFile()) {
                 // Primarily used for error reporting
                 String jarName = manifestResource.getURL().toExternalForm();
-                Manifest jmanifest = null;
-                try (InputStream is = manifestResource.getInputStream()) {
-                    jmanifest = new Manifest(is);
+                Manifest jmanifest = manifestResource.getManifest();
+                if (jmanifest != null) {
                     ManifestResource mre = new ManifestResource(jarName,
                             jmanifest, ManifestResource.APPLICATION);
                     appManifestResources.add(mre);
diff --git a/java/org/apache/catalina/webresources/JarWarResourceSet.java b/java/org/apache/catalina/webresources/JarWarResourceSet.java
index 7f16b63c..805d8ddb 100644
--- a/java/org/apache/catalina/webresources/JarWarResourceSet.java
+++ b/java/org/apache/catalina/webresources/JarWarResourceSet.java
@@ -107,11 +107,28 @@ public class JarWarResourceSet extends AbstractArchiveResourceSet {
 
                     try (JarInputStream jarIs = new JarInputStream(jarFileIs)) {
                         JarEntry entry = jarIs.getNextJarEntry();
+                        boolean hasMetaInf = false;
                         while (entry != null) {
+                            if (!hasMetaInf && entry.getName().startsWith("META-INF/")) {
+                                hasMetaInf = true;
+                            }
                             archiveEntries.put(entry.getName(), entry);
                             entry = jarIs.getNextJarEntry();
                         }
                         setManifest(jarIs.getManifest());
+                        // Hacks to work-around JarInputStream swallowing these
+                        // entries. The attributes for these entries will be
+                        // incomplete. Making the attributes available would
+                        // require (re-)reading the stream as a ZipInputStream
+                        // and creating JarEntry objects from the ZipEntries.
+                        if (hasMetaInf) {
+                            JarEntry metaInfDir = new JarEntry("META-INF/");
+                            archiveEntries.put(metaInfDir.getName(), metaInfDir);
+                        }
+                        if (jarIs.getManifest() != null) {
+                            JarEntry manifest = new JarEntry("META-INF/MANIFEST.MF");
+                            archiveEntries.put(manifest.getName(), manifest);
+                        }
                     }
                 } catch (IOException ioe) {
                     // Should never happen
diff --git a/java/org/apache/catalina/webresources/LocalStrings.properties b/java/org/apache/catalina/webresources/LocalStrings.properties
index 90dace1e..2f7921a0 100644
--- a/java/org/apache/catalina/webresources/LocalStrings.properties
+++ b/java/org/apache/catalina/webresources/LocalStrings.properties
@@ -25,7 +25,7 @@ cache.backgroundEvictFail=The background cache eviction process was unable to fr
 cache.objectMaxSizeTooBig=The value of [{0}]kB for objectMaxSize is larger than the limit of maxSize/20 so has been reduced to [{1}]kB
 cache.objectMaxSizeTooBigBytes=The value specified for the maximum object size to cache [{0}]kB is greater than Integer.MAX_VALUE bytes which is the maximum size that can be cached. The limit will be set to Integer.MAX_VALUE bytes.
 
-classpathUrlStreamHandler.notFound=Unable to load the resource [{0}] using the thread context class loader or the current class's class loader
+classpathUrlStreamHandler.notFound=Unable to load the resource [{0}] using the thread context class loader or the current class''s class loader
 
 dirResourceSet.manifestFail=Failed to read manifest from [{0}]
 dirResourceSet.notDirectory=The directory specified by base and internal path [{0}]{1}[{2}] does not exist.
diff --git a/java/org/apache/coyote/AbstractProcessorLight.java b/java/org/apache/coyote/AbstractProcessorLight.java
index c199dac3..a6acd1f3 100644
--- a/java/org/apache/coyote/AbstractProcessorLight.java
+++ b/java/org/apache/coyote/AbstractProcessorLight.java
@@ -62,8 +62,12 @@ public abstract class AbstractProcessorLight implements Processor {
             } else if (status == SocketEvent.OPEN_WRITE) {
                 // Extra write event likely after async, ignore
                 state = SocketState.LONG;
-            } else {
+            } else if (status == SocketEvent.OPEN_READ){
                 state = service(socketWrapper);
+            } else {
+                // Default to closing the socket if the SocketEvent passed in
+                // is not consistent with the current state of the Processor
+                state = SocketState.CLOSED;
             }
 
             if (state != SocketState.CLOSED && isAsync()) {
diff --git a/java/org/apache/coyote/AbstractProtocol.java b/java/org/apache/coyote/AbstractProtocol.java
index e69e984c..533812de 100644
--- a/java/org/apache/coyote/AbstractProtocol.java
+++ b/java/org/apache/coyote/AbstractProtocol.java
@@ -19,6 +19,7 @@ package org.apache.coyote;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -42,6 +43,8 @@ import org.apache.tomcat.util.collections.SynchronizedStack;
 import org.apache.tomcat.util.modeler.Registry;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler;
+import org.apache.tomcat.util.net.SSLHostConfig;
+import org.apache.tomcat.util.net.SSLHostConfigCertificate;
 import org.apache.tomcat.util.net.SocketEvent;
 import org.apache.tomcat.util.net.SocketWrapperBase;
 import org.apache.tomcat.util.res.StringManager;
@@ -74,6 +77,10 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
     protected ObjectName tpOname = null;
 
 
+    private Set<ObjectName> sslOnames = new HashSet<>();
+    private Set<ObjectName> sslCertOnames = new HashSet<>();
+
+
     /**
      * Unique ID for this connector. Only used if the connector is configured
      * to use a random port as the port will change if stop(), start() is
@@ -200,6 +207,40 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
         return asyncTimeout;
     }
 
+    /**
+     * Specifies whether the reason phrase will be sent in the response.
+     * By default a reason phrase will not be sent in the response.
+     *
+     * @deprecated This option will be removed in Tomcat 9. Reason phrase will
+     *             not be sent.
+     */
+    @Deprecated
+    private boolean sendReasonPhrase = false;
+    /**
+     * Returns whether the reason phrase will be sent in the response.
+     * By default a reason phrase will not be sent in the response.
+     *
+     * @return whether the reason phrase will be sent
+     * @deprecated This option will be removed in Tomcat 9. Reason phrase will
+     *             not be sent.
+     */
+    @Deprecated
+    public boolean getSendReasonPhrase() {
+        return sendReasonPhrase;
+    }
+    /**
+     * Specifies whether the reason phrase will be sent in the response.
+     * By default a reason phrase will not be sent in the response.
+     *
+     * @param sendReasonPhrase specifies whether the reason phrase will be sent
+     * @deprecated This option will be removed in Tomcat 9. Reason phrase will
+     *             not be sent.
+     */
+    @Deprecated
+    public void setSendReasonPhrase(boolean sendReasonPhrase) {
+        this.sendReasonPhrase = sendReasonPhrase;
+    }
+
 
     // ---------------------- Properties that are passed through to the EndPoint
 
@@ -536,10 +577,8 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
 
         if (this.domain != null) {
             try {
-                tpOname = new ObjectName(domain + ":" +
-                        "type=ThreadPool,name=" + getName());
-                Registry.getRegistry(null, null).registerComponent(endpoint,
-                        tpOname, null);
+                tpOname = new ObjectName(domain + ":type=ThreadPool,name=" + getName());
+                Registry.getRegistry(null, null).registerComponent(endpoint, tpOname, null);
             } catch (Exception e) {
                 getLog().error(sm.getString(
                         "abstractProtocolHandler.mbeanRegistrationFailed",
@@ -549,6 +588,22 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
                     ":type=GlobalRequestProcessor,name=" + getName());
             Registry.getRegistry(null, null).registerComponent(
                     getHandler().getGlobal(), rgOname, null );
+
+            for (SSLHostConfig sslHostConfig : getEndpoint().findSslHostConfigs()) {
+                ObjectName sslOname = new ObjectName(domain + ":type=SSLHostConfig,ThreadPool=" +
+                        getName() + ",name=" + sslHostConfig.getHostName());
+                Registry.getRegistry(null, null).registerComponent(sslHostConfig, sslOname, null);
+                sslOnames.add(sslOname);
+                for (SSLHostConfigCertificate sslHostConfigCert : sslHostConfig.getCertificates()) {
+                    ObjectName sslCertOname = new ObjectName(domain +
+                            ":type=SSLHostConfigCertificate,ThreadPool=" + getName() +
+                            ",Host=" + sslHostConfig.getHostName() +
+                            ",name=" + sslHostConfigCert.getType());
+                    Registry.getRegistry(null, null).registerComponent(
+                            sslHostConfigCert, sslCertOname, null);
+                    sslCertOnames.add(sslCertOname);
+                }
+            }
         }
 
         String endpointName = getName();
@@ -568,12 +623,12 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
     public void start() throws Exception {
         if (getLog().isInfoEnabled())
             getLog().info(sm.getString("abstractProtocolHandler.start",
-                    getNameInternal()));
+                    getName()));
         try {
             endpoint.start();
         } catch (Exception ex) {
             getLog().error(sm.getString("abstractProtocolHandler.startError",
-                    getNameInternal()), ex);
+                    getName()), ex);
             throw ex;
         }
 
@@ -668,11 +723,19 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
             }
         }
 
-        if (tpOname != null)
+        if (tpOname != null) {
             Registry.getRegistry(null, null).unregisterComponent(tpOname);
-        if (rgOname != null)
+        }
+        if (rgOname != null) {
             Registry.getRegistry(null, null).unregisterComponent(rgOname);
         }
+        for (ObjectName sslOname : sslOnames) {
+            Registry.getRegistry(null, null).unregisterComponent(sslOname);
+        }
+        for (ObjectName sslCertOname : sslCertOnames) {
+            Registry.getRegistry(null, null).unregisterComponent(sslCertOname);
+        }
+    }
 
 
     // ------------------------------------------- Connection handler base class
@@ -870,10 +933,9 @@ public abstract class AbstractProtocol<S> implements ProtocolHandler,
                     wrapper.registerReadInterest();
                 } else if (state == SocketState.SENDFILE) {
                     // Sendfile in progress. If it fails, the socket will be
-                    // closed. If it works, the socket will be re-added to the
-                    // poller
-                    connections.remove(socket);
-                    release(processor);
+                    // closed. If it works, the socket either be added to the
+                    // poller (or equivalent) to await more data or processed
+                    // if there are any pipe-lined requests remaining.
                 } else if (state == SocketState.UPGRADED) {
                     // Don't add sockets back to the poller if this was a
                     // non-blocking write otherwise the poller may trigger
diff --git a/java/org/apache/coyote/Constants.java b/java/org/apache/coyote/Constants.java
index dc48b5f6..cef86be3 100644
--- a/java/org/apache/coyote/Constants.java
+++ b/java/org/apache/coyote/Constants.java
@@ -49,6 +49,15 @@ public final class Constants {
 
 
     /**
+     * If true, custom HTTP status messages will be used in headers.
+     * @deprecated This option will be removed in Tomcat 9. Reason phrase will
+     *             not be sent.
+     */
+    @Deprecated
+    public static final boolean USE_CUSTOM_STATUS_MSG_IN_HEADER =
+            Boolean.getBoolean("org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER");
+
+    /**
      * The request attribute that is set to the value of {@code Boolean.TRUE}
      * if connector processing this request supports use of sendfile.
      */
diff --git a/java/org/apache/coyote/Response.java b/java/org/apache/coyote/Response.java
index 4cc44284..625315e5 100644
--- a/java/org/apache/coyote/Response.java
+++ b/java/org/apache/coyote/Response.java
@@ -25,6 +25,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.servlet.WriteListener;
 
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.http.MimeHeaders;
@@ -45,6 +47,8 @@ public final class Response {
 
     private static final StringManager sm = StringManager.getManager(Response.class);
 
+    private static final Log log = LogFactory.getLog(Response.class);
+
     // ----------------------------------------------------- Class Variables
 
     /**
@@ -632,7 +636,10 @@ public final class Response {
 
     public boolean isReady() {
         if (listener == null) {
-            throw new IllegalStateException(sm.getString("response.notNonBlocking"));
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("response.notNonBlocking"));
+            }
+            return false;
         }
         // Assume write is not possible
         boolean ready = false;
diff --git a/java/org/apache/coyote/ajp/AbstractAjpProtocol.java b/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
index 8eac1b77..b145a4c7 100644
--- a/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
+++ b/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
@@ -182,6 +182,7 @@ public abstract class AbstractAjpProtocol<S> extends AbstractProtocol<S> {
     }
 
 
+    @SuppressWarnings("deprecation")
     @Override
     protected Processor createProcessor() {
         AjpProcessor processor = new AjpProcessor(getPacketSize(), getEndpoint());
@@ -192,6 +193,7 @@ public abstract class AbstractAjpProtocol<S> extends AbstractProtocol<S> {
         processor.setRequiredSecret(requiredSecret);
         processor.setKeepAliveTimeout(getKeepAliveTimeout());
         processor.setClientCertProvider(getClientCertProvider());
+        processor.setSendReasonPhrase(getSendReasonPhrase());
         return processor;
     }
 
diff --git a/java/org/apache/coyote/ajp/AjpProcessor.java b/java/org/apache/coyote/ajp/AjpProcessor.java
index c1095c89..bfbfe2fc 100644
--- a/java/org/apache/coyote/ajp/AjpProcessor.java
+++ b/java/org/apache/coyote/ajp/AjpProcessor.java
@@ -40,6 +40,7 @@ import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.HexUtils;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.HttpMessages;
 import org.apache.tomcat.util.http.MimeHeaders;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
@@ -346,6 +347,13 @@ public class AjpProcessor extends AbstractProcessor {
         this.clientCertProvider = clientCertProvider;
     }
 
+    @Deprecated
+    private boolean sendReasonPhrase = false;
+    @Deprecated
+    void setSendReasonPhrase(boolean sendReasonPhrase) {
+        this.sendReasonPhrase = sendReasonPhrase;
+    }
+
 
     // --------------------------------------------------------- Public Methods
 
@@ -1008,6 +1016,7 @@ public class AjpProcessor extends AbstractProcessor {
      * When committing the response, we have to validate the set of headers, as
      * well as setup the response filters.
      */
+    @SuppressWarnings("deprecation")
     @Override
     protected final void prepareResponse() throws IOException {
 
@@ -1037,9 +1046,26 @@ public class AjpProcessor extends AbstractProcessor {
 
         // HTTP header contents
         responseMessage.appendInt(statusCode);
+        if (sendReasonPhrase) {
+            String message = null;
+            if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER &&
+                    HttpMessages.isSafeInHttpHeader(response.getMessage())) {
+                message = response.getMessage();
+            }
+            if (message == null) {
+                message = HttpMessages.getInstance(
+                        response.getLocale()).getMessage(response.getStatus());
+            }
+            if (message == null) {
+                // mod_jk + httpd 2.x fails with a null status message - bug 45026
+                message = Integer.toString(response.getStatus());
+            }
+            tmpMB.setString(message);
+        } else {
             // Reason phrase is optional but mod_jk + httpd 2.x fails with a null
             // reason phrase - bug 45026
             tmpMB.setString(Integer.toString(response.getStatus()));
+        }
         responseMessage.appendBytes(tmpMB);
 
         // Special headers
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
index 2c0161ac..6c7171e1 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
@@ -36,6 +36,7 @@ import org.apache.coyote.UpgradeToken;
 import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
 import org.apache.coyote.http11.upgrade.UpgradeProcessorExternal;
 import org.apache.coyote.http11.upgrade.UpgradeProcessorInternal;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.SSLHostConfig;
 import org.apache.tomcat.util.net.SocketWrapperBase;
@@ -142,20 +143,44 @@ public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
     }
 
 
-    private String compressableMimeType = "text/html,text/xml,text/plain,text/css,text/javascript,application/javascript";
-    private String[] compressableMimeTypes = null;
-    public String getCompressableMimeType() { return compressableMimeType; }
+    /**
+     * @return See {@link #getCompressibleMimeType()}
+     * @deprecated Use {@link #getCompressibleMimeType()}
+     */
+    @Deprecated
+    public String getCompressableMimeType() {
+        return getCompressibleMimeType();
+    }
+    /**
+     * @param valueS See {@link #setCompressibleMimeType(String)}
+     * @deprecated Use {@link #setCompressibleMimeType(String)}
+     */
+    @Deprecated
     public void setCompressableMimeType(String valueS) {
-        compressableMimeType = valueS;
-        compressableMimeTypes = null;
+        setCompressibleMimeType(valueS);
     }
+    /**
+     * @return See {@link #getCompressibleMimeTypes()}
+     * @deprecated Use {@link #getCompressibleMimeTypes()}
+     */
+    @Deprecated
     public String[] getCompressableMimeTypes() {
-        String[] result = compressableMimeTypes;
+        return getCompressibleMimeTypes();
+    }
+    private String compressibleMimeType = "text/html,text/xml,text/plain,text/css,text/javascript,application/javascript";
+    private String[] compressibleMimeTypes = null;
+    public String getCompressibleMimeType() { return compressibleMimeType; }
+    public void setCompressibleMimeType(String valueS) {
+        compressibleMimeType = valueS;
+        compressibleMimeTypes = null;
+    }
+    public String[] getCompressibleMimeTypes() {
+        String[] result = compressibleMimeTypes;
         if (result != null) {
             return result;
         }
         List<String> values = new ArrayList<>();
-        StringTokenizer tokens = new StringTokenizer(compressableMimeType, ",");
+        StringTokenizer tokens = new StringTokenizer(compressibleMimeType, ",");
         while (tokens.hasMoreTokens()) {
             String token = tokens.nextToken().trim();
             if (token.length() > 0) {
@@ -163,7 +188,7 @@ public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
             }
         }
         result = values.toArray(new String[values.size()]);
-        compressableMimeTypes = result;
+        compressibleMimeTypes = result;
         return result;
     }
 
@@ -274,17 +299,7 @@ public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
         // sync is unnecessary.
         List<String> copy = new ArrayList<>(allowedTrailerHeaders.size());
         copy.addAll(allowedTrailerHeaders);
-        StringBuilder result = new StringBuilder();
-        boolean first = true;
-        for (String header : copy) {
-            if (first) {
-                first = false;
-            } else {
-                result.append(',');
-            }
-            result.append(header);
-        }
-        return result.toString();
+        return StringUtils.join(copy);
     }
     public void addAllowedTrailerHeader(String header) {
         if (header != null) {
@@ -426,203 +441,353 @@ public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
     }
 
 
-    // TODO: All of these SSL setters can be removed once it is no longer
-    // necessary to support the old configuration attributes (Tomcat 10?).
+    // TODO: All of these SSL getters and setters can be removed once it is no
+    // longer necessary to support the old configuration attributes (Tomcat 10?)
 
+    public String getSslEnabledProtocols() {
+        registerDefaultSSLHostConfig();
+        return StringUtils.join(defaultSSLHostConfig.getEnabledProtocols());
+    }
     public void setSslEnabledProtocols(String enabledProtocols) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setProtocols(enabledProtocols);
     }
+    public String getSSLProtocol() {
+        registerDefaultSSLHostConfig();
+        return StringUtils.join(defaultSSLHostConfig.getEnabledProtocols());
+    }
     public void setSSLProtocol(String sslProtocol) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setProtocols(sslProtocol);
     }
 
 
+    public String getKeystoreFile() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateKeystoreFile();
+    }
     public void setKeystoreFile(String keystoreFile) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateKeystoreFile(keystoreFile);
     }
+    public String getSSLCertificateChainFile() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateChainFile();
+    }
     public void setSSLCertificateChainFile(String certificateChainFile) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateChainFile(certificateChainFile);
     }
+    public String getSSLCertificateFile() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateFile();
+    }
     public void setSSLCertificateFile(String certificateFile) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateFile(certificateFile);
     }
+    public String getSSLCertificateKeyFile() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateKeyFile();
+    }
     public void setSSLCertificateKeyFile(String certificateKeyFile) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateKeyFile(certificateKeyFile);
     }
 
 
+    public String getAlgorithm() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getKeyManagerAlgorithm();
+    }
     public void setAlgorithm(String keyManagerAlgorithm) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setKeyManagerAlgorithm(keyManagerAlgorithm);
     }
 
 
+    public String getClientAuth() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateVerification().toString();
+    }
     public void setClientAuth(String certificateVerification) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateVerification(certificateVerification);
     }
 
 
+    public String getSSLVerifyClient() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateVerification().toString();
+    }
     public void setSSLVerifyClient(String certificateVerification) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateVerification(certificateVerification);
     }
 
 
+    public int getTrustMaxCertLength(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateVerificationDepth();
+    }
     public void setTrustMaxCertLength(int certificateVerificationDepth){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateVerificationDepth(certificateVerificationDepth);
     }
+    public int getSSLVerifyDepth() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateVerificationDepth();
+    }
     public void setSSLVerifyDepth(int certificateVerificationDepth) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateVerificationDepth(certificateVerificationDepth);
     }
 
 
+    public String getUseServerCipherSuitesOrder() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getHonorCipherOrder();
+    }
     public void setUseServerCipherSuitesOrder(String honorCipherOrder) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setHonorCipherOrder(honorCipherOrder);
     }
+    public String getSSLHonorCipherOrder() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getHonorCipherOrder();
+    }
     public void setSSLHonorCipherOrder(String honorCipherOrder) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setHonorCipherOrder(honorCipherOrder);
     }
 
 
+    public String getCiphers() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCiphers();
+    }
     public void setCiphers(String ciphers) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCiphers(ciphers);
     }
+    public String getSSLCipherSuite() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCiphers();
+    }
     public void setSSLCipherSuite(String ciphers) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCiphers(ciphers);
     }
 
+
+    public String getKeystorePass() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateKeystorePassword();
+    }
     public void setKeystorePass(String certificateKeystorePassword) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateKeystorePassword(certificateKeystorePassword);
     }
 
+
+    public String getKeyPass() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateKeyPassword();
+    }
     public void setKeyPass(String certificateKeyPassword) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateKeyPassword(certificateKeyPassword);
     }
+    public String getSSLPassword() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateKeyPassword();
+    }
     public void setSSLPassword(String certificateKeyPassword) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateKeyPassword(certificateKeyPassword);
     }
 
 
+    public String getCrlFile(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateRevocationListFile();
+    }
     public void setCrlFile(String certificateRevocationListFile){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateRevocationListFile(certificateRevocationListFile);
     }
+    public String getSSLCARevocationFile() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateRevocationListFile();
+    }
     public void setSSLCARevocationFile(String certificateRevocationListFile) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateRevocationListFile(certificateRevocationListFile);
     }
+    public String getSSLCARevocationPath() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateRevocationListPath();
+    }
     public void setSSLCARevocationPath(String certificateRevocationListPath) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateRevocationListPath(certificateRevocationListPath);
     }
 
 
+    public String getKeystoreType() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateKeystoreType();
+    }
     public void setKeystoreType(String certificateKeystoreType) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateKeystoreType(certificateKeystoreType);
     }
 
 
+    public String getKeystoreProvider() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateKeystoreProvider();
+    }
     public void setKeystoreProvider(String certificateKeystoreProvider) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateKeystoreProvider(certificateKeystoreProvider);
     }
 
 
+    public String getKeyAlias() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCertificateKeyAlias();
+    }
     public void setKeyAlias(String certificateKeyAlias) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCertificateKeyAlias(certificateKeyAlias);
     }
 
 
+    public String getTruststoreAlgorithm(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getTruststoreAlgorithm();
+    }
     public void setTruststoreAlgorithm(String truststoreAlgorithm){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setTruststoreAlgorithm(truststoreAlgorithm);
     }
 
 
+    public String getTruststoreFile(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getTruststoreFile();
+    }
     public void setTruststoreFile(String truststoreFile){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setTruststoreFile(truststoreFile);
     }
 
 
+    public String getTruststorePass(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getTruststorePassword();
+    }
     public void setTruststorePass(String truststorePassword){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setTruststorePassword(truststorePassword);
     }
 
 
+    public String getTruststoreType(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getTruststoreType();
+    }
     public void setTruststoreType(String truststoreType){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setTruststoreType(truststoreType);
     }
 
 
+    public String getTruststoreProvider(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getTruststoreProvider();
+    }
     public void setTruststoreProvider(String truststoreProvider){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setTruststoreProvider(truststoreProvider);
     }
 
 
+    public String getSslProtocol() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getSslProtocol();
+    }
     public void setSslProtocol(String sslProtocol) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setSslProtocol(sslProtocol);
     }
 
 
+    public int getSessionCacheSize(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getSessionCacheSize();
+    }
     public void setSessionCacheSize(int sessionCacheSize){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setSessionCacheSize(sessionCacheSize);
     }
 
 
+    public int getSessionTimeout(){
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getSessionTimeout();
+    }
     public void setSessionTimeout(int sessionTimeout){
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setSessionTimeout(sessionTimeout);
     }
 
 
+    public String getSSLCACertificatePath() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCaCertificatePath();
+    }
     public void setSSLCACertificatePath(String caCertificatePath) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCaCertificatePath(caCertificatePath);
     }
 
 
+    public String getSSLCACertificateFile() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getCaCertificateFile();
+    }
     public void setSSLCACertificateFile(String caCertificateFile) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setCaCertificateFile(caCertificateFile);
     }
 
 
+    public boolean getSSLDisableCompression() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getDisableCompression();
+    }
     public void setSSLDisableCompression(boolean disableCompression) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setDisableCompression(disableCompression);
     }
 
 
+    public boolean getSSLDisableSessionTickets() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getDisableSessionTickets();
+    }
     public void setSSLDisableSessionTickets(boolean disableSessionTickets) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setDisableSessionTickets(disableSessionTickets);
     }
 
 
+    public String getTrustManagerClassName() {
+        registerDefaultSSLHostConfig();
+        return defaultSSLHostConfig.getTrustManagerClassName();
+    }
     public void setTrustManagerClassName(String trustManagerClassName) {
         registerDefaultSSLHostConfig();
         defaultSSLHostConfig.setTrustManagerClassName(trustManagerClassName);
@@ -631,11 +796,12 @@ public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
 
     // ------------------------------------------------------------- Common code
 
+    @SuppressWarnings("deprecation")
     @Override
     protected Processor createProcessor() {
         Http11Processor processor = new Http11Processor(getMaxHttpHeaderSize(), getEndpoint(),
                 getMaxTrailerSize(), allowedTrailerHeaders, getMaxExtensionSize(),
-                getMaxSwallowSize(), httpUpgradeProtocols);
+                getMaxSwallowSize(), httpUpgradeProtocols, getSendReasonPhrase());
         processor.setAdapter(getAdapter());
         processor.setMaxKeepAliveRequests(getMaxKeepAliveRequests());
         processor.setConnectionUploadTimeout(getConnectionUploadTimeout());
@@ -643,7 +809,7 @@ public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
         processor.setCompressionMinSize(getCompressionMinSize());
         processor.setCompression(getCompression());
         processor.setNoCompressionUserAgents(getNoCompressionUserAgents());
-        processor.setCompressableMimeTypes(getCompressableMimeTypes());
+        processor.setCompressibleMimeTypes(getCompressibleMimeTypes());
         processor.setRestrictedUserAgents(getRestrictedUserAgents());
         processor.setMaxSavePostSize(getMaxSavePostSize());
         processor.setServer(getServer());
diff --git a/java/org/apache/coyote/http11/Constants.java b/java/org/apache/coyote/http11/Constants.java
index cd8aae0f..7f0ce623 100644
--- a/java/org/apache/coyote/http11/Constants.java
+++ b/java/org/apache/coyote/http11/Constants.java
@@ -107,8 +107,14 @@ public final class Constants {
     public static final String KEEPALIVE = "keep-alive";
     public static final byte[] KEEPALIVE_BYTES = ByteChunk.convertToBytes(KEEPALIVE);
     public static final String CHUNKED = "chunked";
-    public static final byte[] ACK_BYTES =
+    /**
+     * @deprecated This option will be removed in Tomcat 9. Reason phrase will
+     *             not be sent.
+     */
+    @Deprecated
+    public static final byte[] ACK_BYTES_REASON =
             ByteChunk.convertToBytes("HTTP/1.1 100 Continue" + CRLF + CRLF);
+    public static final byte[] ACK_BYTES = ByteChunk.convertToBytes("HTTP/1.1 100 " + CRLF + CRLF);
     public static final String TRANSFERENCODING = "Transfer-Encoding";
     public static final byte[] _200_BYTES = ByteChunk.convertToBytes("200");
     public static final byte[] _400_BYTES = ByteChunk.convertToBytes("400");
diff --git a/java/org/apache/coyote/http11/Http11OutputBuffer.java b/java/org/apache/coyote/http11/Http11OutputBuffer.java
index 788927b8..1992df8a 100644
--- a/java/org/apache/coyote/http11/Http11OutputBuffer.java
+++ b/java/org/apache/coyote/http11/Http11OutputBuffer.java
@@ -27,6 +27,7 @@ import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.HttpMessages;
 import org.apache.tomcat.util.net.SocketWrapperBase;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -108,9 +109,14 @@ public class Http11OutputBuffer implements OutputBuffer {
     protected long byteCount = 0;
 
 
-    protected Http11OutputBuffer(Response response, int headerBufferSize) {
+    @Deprecated
+    private boolean sendReasonPhrase = false;
+
+
+    protected Http11OutputBuffer(Response response, int headerBufferSize, boolean sendReasonPhrase) {
 
         this.response = response;
+        this.sendReasonPhrase = sendReasonPhrase;
 
         headerBuffer = ByteBuffer.allocate(headerBufferSize);
 
@@ -121,6 +127,11 @@ public class Http11OutputBuffer implements OutputBuffer {
         responseFinished = false;
 
         outputStreamOutputBuffer = new SocketOutputBuffer();
+
+        if (sendReasonPhrase) {
+            // Cause loading of HttpMessages
+            HttpMessages.getInstance(response.getLocale()).getMessage(200);
+        }
     }
 
 
@@ -327,9 +338,14 @@ public class Http11OutputBuffer implements OutputBuffer {
     }
 
 
+    @SuppressWarnings("deprecation")
     public void sendAck() throws IOException {
         if (!response.isCommitted()) {
+            if (sendReasonPhrase) {
+                socketWrapper.write(isBlocking(), Constants.ACK_BYTES_REASON, 0, Constants.ACK_BYTES_REASON.length);
+            } else {
                 socketWrapper.write(isBlocking(), Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length);
+            }
             if (flushBuffer(true)) {
                 throw new IOException(sm.getString("iob.failedwrite.ack"));
             }
@@ -360,6 +376,7 @@ public class Http11OutputBuffer implements OutputBuffer {
     /**
      * Send the response status line.
      */
+    @SuppressWarnings("deprecation")
     public void sendStatus() {
         // Write protocol name
         write(Constants.HTTP_11_BYTES);
@@ -383,9 +400,24 @@ public class Http11OutputBuffer implements OutputBuffer {
 
         headerBuffer.put(Constants.SP);
 
+        if (sendReasonPhrase) {
+            // Write message
+            String message = null;
+            if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER &&
+                    HttpMessages.isSafeInHttpHeader(response.getMessage())) {
+                message = response.getMessage();
+            }
+            if (message == null) {
+                write(HttpMessages.getInstance(
+                        response.getLocale()).getMessage(status));
+            } else {
+                write(message);
+            }
+        } else {
             // The reason phrase is optional but the space before it is not. Skip
             // sending the reason phrase. Clients should ignore it (RFC 7230) and it
             // just wastes bytes.
+        }
 
         headerBuffer.put(Constants.CR).put(Constants.LF);
     }
@@ -475,6 +507,35 @@ public class Http11OutputBuffer implements OutputBuffer {
 
 
     /**
+     * This method will write the contents of the specified String to the
+     * output stream, without filtering. This method is meant to be used to
+     * write the response header.
+     *
+     * @param s data to be written
+     */
+    private void write(String s) {
+        if (s == null) {
+            return;
+        }
+
+        // From the Tomcat 3.3 HTTP/1.0 connector
+        int len = s.length();
+        checkLengthBeforeWrite(len);
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt (i);
+            // Note: This is clearly incorrect for many strings,
+            // but is the only consistent approach within the current
+            // servlet framework. It must suffice until servlet output
+            // streams properly encode their output.
+            if (((c <= 31) && (c != 9)) || c == 127 || c > 255) {
+                c = ' ';
+            }
+            headerBuffer.put((byte) c);
+        }
+    }
+
+
+    /**
      * This method will write the specified integer to the output stream. This
      * method is meant to be used to write the response header.
      *
diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java
index 5a1c7667..696e84ff 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -58,6 +58,8 @@ import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SendfileDataBase;
+import org.apache.tomcat.util.net.SendfileKeepAliveState;
+import org.apache.tomcat.util.net.SendfileState;
 import org.apache.tomcat.util.net.SocketWrapperBase;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -179,6 +181,8 @@ public class Http11Processor extends AbstractProcessor {
 
     /**
      * List of MIMES for which compression may be enabled.
+     * Note: This is not spelled correctly but can't be changed without breaking
+     *       compatibility
      */
     protected String[] compressableMimeTypes;
 
@@ -223,7 +227,7 @@ public class Http11Processor extends AbstractProcessor {
 
     public Http11Processor(int maxHttpHeaderSize, AbstractEndpoint<?> endpoint,int maxTrailerSize,
             Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize,
-            Map<String,UpgradeProtocol> httpUpgradeProtocols) {
+            Map<String,UpgradeProtocol> httpUpgradeProtocols, boolean sendReasonPhrase) {
 
         super(endpoint);
         userDataHelper = new UserDataHelper(log);
@@ -231,7 +235,7 @@ public class Http11Processor extends AbstractProcessor {
         inputBuffer = new Http11InputBuffer(request, maxHttpHeaderSize);
         request.setInputBuffer(inputBuffer);
 
-        outputBuffer = new Http11OutputBuffer(response, maxHttpHeaderSize);
+        outputBuffer = new Http11OutputBuffer(response, maxHttpHeaderSize, sendReasonPhrase);
         response.setOutputBuffer(outputBuffer);
 
         // Create and add the identity filters.
@@ -316,15 +320,27 @@ public class Http11Processor extends AbstractProcessor {
 
 
     /**
+     * @param compressibleMimeTypes See
+     *        {@link Http11Processor#setCompressibleMimeTypes(String[])}
+     * @deprecated Use
+     *             {@link Http11Processor#setCompressibleMimeTypes(String[])}
+     */
+    @Deprecated
+    public void setCompressableMimeTypes(String[] compressibleMimeTypes) {
+        setCompressibleMimeTypes(compressibleMimeTypes);
+    }
+
+
+    /**
      * Set compressible mime-type list (this method is best when used with
      * a large number of connectors, where it would be better to have all of
      * them referenced a single array).
      *
-     * @param compressableMimeTypes MIME types for which compression should be
+     * @param compressibleMimeTypes MIME types for which compression should be
      *                              enabled
      */
-    public void setCompressableMimeTypes(String[] compressableMimeTypes) {
-        this.compressableMimeTypes = compressableMimeTypes;
+    public void setCompressibleMimeTypes(String[] compressibleMimeTypes) {
+        this.compressableMimeTypes = compressibleMimeTypes;
     }
 
 
@@ -490,7 +506,7 @@ public class Http11Processor extends AbstractProcessor {
     /**
      * Check if the resource could be compressed, if the client supports it.
      */
-    private boolean isCompressable() {
+    private boolean isCompressible() {
 
         // Check if content is not already gzipped
         MessageBytes contentEncodingMB =
@@ -512,8 +528,7 @@ public class Http11Processor extends AbstractProcessor {
             || (contentLength > compressionMinSize)) {
             // Check for compatible MIME-TYPE
             if (compressableMimeTypes != null) {
-                return (startsWithStringArray(compressableMimeTypes,
-                                              response.getContentType()));
+                return (startsWithStringArray(compressableMimeTypes, response.getContentType()));
             }
         }
 
@@ -658,9 +673,10 @@ public class Http11Processor extends AbstractProcessor {
         openSocket = false;
         readComplete = true;
         boolean keptAlive = false;
+        SendfileState sendfileState = SendfileState.DONE;
 
-        while (!getErrorState().isError() && keepAlive && !isAsync() &&
-                upgradeToken == null && !endpoint.isPaused()) {
+        while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
+                sendfileState == SendfileState.DONE && !endpoint.isPaused()) {
 
             // Parsing the request header
             try {
@@ -849,9 +865,7 @@ public class Http11Processor extends AbstractProcessor {
 
             rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
 
-            if (breakKeepAliveLoop(socketWrapper)) {
-                break;
-            }
+            sendfileState = processSendfile(socketWrapper);
         }
 
         rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
@@ -863,7 +877,7 @@ public class Http11Processor extends AbstractProcessor {
         } else if (isUpgrade()) {
             return SocketState.UPGRADING;
         } else {
-            if (sendfileData != null) {
+            if (sendfileState == SendfileState.PENDING) {
                 return SocketState.SENDFILE;
             } else {
                 if (openSocket) {
@@ -939,7 +953,6 @@ public class Http11Processor extends AbstractProcessor {
         http11 = true;
         http09 = false;
         contentDelimitation = false;
-        sendfileData = null;
 
         if (endpoint.isSSLEnabled()) {
             request.scheme().setString("https");
@@ -1146,17 +1159,16 @@ public class Http11Processor extends AbstractProcessor {
         }
 
         // Sendfile support
-        boolean sendingWithSendfile = false;
         if (endpoint.getUseSendfile()) {
-            sendingWithSendfile = prepareSendfile(outputFilters);
+            prepareSendfile(outputFilters);
         }
 
         // Check for compression
-        boolean isCompressable = false;
+        boolean isCompressible = false;
         boolean useCompression = false;
-        if (entityBody && (compressionLevel > 0) && !sendingWithSendfile) {
-            isCompressable = isCompressable();
-            if (isCompressable) {
+        if (entityBody && (compressionLevel > 0) && sendfileData == null) {
+            isCompressible = isCompressible();
+            if (isCompressible) {
                 useCompression = useCompression();
             }
             // Change content-length to -1 to force chunking
@@ -1209,7 +1221,7 @@ public class Http11Processor extends AbstractProcessor {
             headers.setValue("Content-Encoding").setString("gzip");
         }
         // If it might be compressed, set the Vary header
-        if (isCompressable) {
+        if (isCompressible) {
             // Make Proxies happy via Vary (from mod_deflate)
             MessageBytes vary = headers.getValue("Vary");
             if (vary == null) {
@@ -1296,10 +1308,12 @@ public class Http11Processor extends AbstractProcessor {
         return connection.equals(Constants.CLOSE);
     }
 
-    private boolean prepareSendfile(OutputFilter[] outputFilters) {
+    private void prepareSendfile(OutputFilter[] outputFilters) {
         String fileName = (String) request.getAttribute(
                 org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
-        if (fileName != null) {
+        if (fileName == null) {
+            sendfileData = null;
+        } else {
             // No entity body sent here
             outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
             contentDelimitation = true;
@@ -1308,9 +1322,7 @@ public class Http11Processor extends AbstractProcessor {
             long end = ((Long) request.getAttribute(
                     org.apache.coyote.Constants.SENDFILE_FILE_END_ATTR)).longValue();
             sendfileData = socketWrapper.createSendfileData(fileName, pos, end - pos);
-            return true;
         }
-        return false;
     }
 
     /**
@@ -1591,34 +1603,39 @@ public class Http11Processor extends AbstractProcessor {
 
 
     /**
-     * Checks to see if the keep-alive loop should be broken, performing any
-     * processing (e.g. sendfile handling) that may have an impact on whether
-     * or not the keep-alive loop should be broken.
+     * Trigger sendfile processing if required.
      *
-     * @return true if the keep-alive loop should be broken
+     * @return The state of send file processing
      */
-    private boolean breakKeepAliveLoop(SocketWrapperBase<?> socketWrapper) {
+    private SendfileState processSendfile(SocketWrapperBase<?> socketWrapper) {
         openSocket = keepAlive;
+        // Done is equivalent to sendfile not being used
+        SendfileState result = SendfileState.DONE;
         // Do sendfile as needed: add socket to sendfile and end
         if (sendfileData != null && !getErrorState().isError()) {
-            sendfileData.keepAlive = keepAlive;
-            switch (socketWrapper.processSendfile(sendfileData)) {
-            case DONE:
-                // If sendfile is complete, no need to break keep-alive loop
-                sendfileData = null;
-                return false;
-            case PENDING:
-                return true;
+            if (keepAlive) {
+                if (available(false) == 0) {
+                    sendfileData.keepAliveState = SendfileKeepAliveState.OPEN;
+                } else {
+                    sendfileData.keepAliveState = SendfileKeepAliveState.PIPELINED;
+                }
+            } else {
+                sendfileData.keepAliveState = SendfileKeepAliveState.NONE;
+            }
+            result = socketWrapper.processSendfile(sendfileData);
+            switch (result) {
             case ERROR:
                 // Write failed
                 if (log.isDebugEnabled()) {
                     log.debug(sm.getString("http11processor.sendfile.error"));
                 }
                 setErrorState(ErrorState.CLOSE_CONNECTION_NOW, null);
-                return true;
+                //$FALL-THROUGH$
+            default:
+                sendfileData = null;
             }
         }
-        return false;
+        return result;
     }
 
 
diff --git a/java/org/apache/coyote/http2/ConnectionException.java b/java/org/apache/coyote/http2/ConnectionException.java
index 59573020..db754ac3 100644
--- a/java/org/apache/coyote/http2/ConnectionException.java
+++ b/java/org/apache/coyote/http2/ConnectionException.java
@@ -23,7 +23,12 @@ public class ConnectionException extends Http2Exception {
 
     private static final long serialVersionUID = 1L;
 
-    public ConnectionException(String msg, Http2Error error) {
+    ConnectionException(String msg, Http2Error error) {
         super(msg, error);
     }
+
+
+    ConnectionException(String msg, Http2Error error, Throwable cause) {
+        super(msg, error, cause);
+    }
 }
diff --git a/java/org/apache/coyote/http2/HPackHuffman.java b/java/org/apache/coyote/http2/HPackHuffman.java
index 54c6a310..637e690c 100644
--- a/java/org/apache/coyote/http2/HPackHuffman.java
+++ b/java/org/apache/coyote/http2/HPackHuffman.java
@@ -379,22 +379,27 @@ public class HPackHuffman {
         assert data.remaining() >= length;
         int treePos = 0;
         boolean eosBits = true;
+        int eosBitCount = 0;
         for (int i = 0; i < length; ++i) {
             byte b = data.get();
             int bitPos = 7;
             while (bitPos >= 0) {
                 int val = DECODING_TABLE[treePos];
                 if (((1 << bitPos) & b) == 0) {
-                    eosBits = false;
                     //bit not set, we want the lower part of the tree
                     if ((val & LOW_TERMINAL_BIT) == 0) {
                         treePos = val & LOW_MASK;
+                        eosBits = false;
+                        eosBitCount = 0;
                     } else {
                         target.append((char) (val & LOW_MASK));
                         treePos = 0;
                         eosBits = true;
                     }
                 } else {
+                    if (eosBits) {
+                        eosBitCount++;
+                    }
                     //bit not set, we want the lower part of the tree
                     if ((val & HIGH_TERMINAL_BIT) == 0) {
                         treePos = (val >> 16) & LOW_MASK;
@@ -407,6 +412,10 @@ public class HPackHuffman {
                 bitPos--;
             }
         }
+        if (eosBitCount > 7) {
+            throw new HpackException(sm.getString(
+                    "hpackhuffman.stringLiteralTooMuchPadding"));
+        }
         if (!eosBits) {
             throw new HpackException(sm.getString(
                     "hpackhuffman.huffmanEncodedHpackValueDidNotEndWithEOS"));
diff --git a/java/org/apache/coyote/http2/HpackDecoder.java b/java/org/apache/coyote/http2/HpackDecoder.java
index d75d6418..4a623425 100644
--- a/java/org/apache/coyote/http2/HpackDecoder.java
+++ b/java/org/apache/coyote/http2/HpackDecoder.java
@@ -57,9 +57,13 @@ public class HpackDecoder {
     private int currentMemorySize = 0;
 
     /**
-     * The maximum allowed memory size
+     * The maximum allowed memory size set by the container.
      */
-    private int maxMemorySize;
+    private int maxMemorySizeHard;
+    /**
+     * The maximum memory size currently in use. May be less than the hard limit.
+     */
+    private int maxMemorySizeSoft;
 
     private int maxHeaderCount = Constants.DEFAULT_MAX_HEADER_COUNT;
     private int maxHeaderSize = Constants.DEFAULT_MAX_HEADER_SIZE;
@@ -71,7 +75,8 @@ public class HpackDecoder {
     private final StringBuilder stringBuilder = new StringBuilder();
 
     public HpackDecoder(int maxMemorySize) {
-        this.maxMemorySize = maxMemorySize;
+        this.maxMemorySizeHard = maxMemorySize;
+        this.maxMemorySizeSoft = maxMemorySize;
         headerTable = new Hpack.HeaderField[DEFAULT_RING_BUFFER_SIZE];
     }
 
@@ -156,18 +161,24 @@ public class HpackDecoder {
     }
 
     private boolean handleMaxMemorySizeChange(ByteBuffer buffer, int originalPos) throws HpackException {
+        if (headerCount != 0) {
+            throw new HpackException(sm.getString("hpackdecoder.tableSizeUpdateNotAtStart"));
+        }
         buffer.position(buffer.position() - 1); //unget the byte
         int size = Hpack.decodeInteger(buffer, 5);
         if (size == -1) {
             buffer.position(originalPos);
             return false;
         }
-        maxMemorySize = size;
-        if (currentMemorySize > maxMemorySize) {
+        if (size > maxMemorySizeHard) {
+            throw new HpackException();
+        }
+        maxMemorySizeSoft = size;
+        if (currentMemorySize > maxMemorySizeSoft) {
             int newTableSlots = filledTableSlots;
             int tableLength = headerTable.length;
             int newSize = currentMemorySize;
-            while (newSize > maxMemorySize) {
+            while (newSize > maxMemorySizeSoft) {
                 int clearIndex = firstSlotPosition;
                 firstSlotPosition++;
                 if (firstSlotPosition == tableLength) {
@@ -284,7 +295,7 @@ public class HpackDecoder {
     }
 
     private void addEntryToHeaderTable(Hpack.HeaderField entry) {
-        if (entry.size > maxMemorySize) {
+        if (entry.size > maxMemorySizeSoft) {
             //it is to big to fit, so we just completely clear the table.
             while (filledTableSlots > 0) {
                 headerTable[firstSlotPosition] = null;
@@ -303,7 +314,7 @@ public class HpackDecoder {
         int index = (firstSlotPosition + filledTableSlots) % tableLength;
         headerTable[index] = entry;
         int newSize = currentMemorySize + entry.size;
-        while (newSize > maxMemorySize) {
+        while (newSize > maxMemorySizeSoft) {
             int clearIndex = firstSlotPosition;
             firstSlotPosition++;
             if (firstSlotPosition == tableLength) {
@@ -339,8 +350,10 @@ public class HpackDecoder {
          *
          * @param name  Header name
          * @param value Header value
+         * @throws HpackException If a header is received that is not compliant
+         *                        with the HTTP/2 specification
          */
-        void emitHeader(String name, String value);
+        void emitHeader(String name, String value) throws HpackException;
 
         /**
          * Are the headers pass to the recipient so far valid? The decoder needs
@@ -381,7 +394,7 @@ public class HpackDecoder {
     }
 
 
-    private void emitHeader(String name, String value) {
+    private void emitHeader(String name, String value) throws HpackException {
         // Header names are forced to lower case
         if ("cookie".equals(name)) {
             // Only count the cookie header once since HTTP/2 splits it into
@@ -447,7 +460,7 @@ public class HpackDecoder {
         return currentMemorySize;
     }
 
-    int getMaxMemorySize() {
-        return maxMemorySize;
+    int getMaxMemorySizeSoft() {
+        return maxMemorySizeSoft;
     }
 }
diff --git a/java/org/apache/coyote/http2/Http2Exception.java b/java/org/apache/coyote/http2/Http2Exception.java
index 65f7502f..5abaa9c2 100644
--- a/java/org/apache/coyote/http2/Http2Exception.java
+++ b/java/org/apache/coyote/http2/Http2Exception.java
@@ -23,13 +23,19 @@ public abstract class Http2Exception extends Exception {
     private final Http2Error error;
 
 
-    public Http2Exception(String msg, Http2Error error) {
+    Http2Exception(String msg, Http2Error error) {
         super(msg);
         this.error = error;
     }
 
 
-    public Http2Error getError() {
+    Http2Exception(String msg, Http2Error error, Throwable cause) {
+        super(msg, cause);
+        this.error = error;
+    }
+
+
+    Http2Error getError() {
         return error;
     }
 }
diff --git a/java/org/apache/coyote/http2/Http2Parser.java b/java/org/apache/coyote/http2/Http2Parser.java
index 54dd43b1..5fe61b9b 100644
--- a/java/org/apache/coyote/http2/Http2Parser.java
+++ b/java/org/apache/coyote/http2/Http2Parser.java
@@ -420,7 +420,7 @@ class Http2Parser {
             } catch (HpackException hpe) {
                 throw new ConnectionException(
                         sm.getString("http2Parser.processFrameHeaders.decodingFailed"),
-                        Http2Error.COMPRESSION_ERROR);
+                        Http2Error.COMPRESSION_ERROR, hpe);
             }
 
             // switches to write mode
diff --git a/java/org/apache/coyote/http2/Http2Protocol.java b/java/org/apache/coyote/http2/Http2Protocol.java
index 988b2d18..84b1d0fb 100644
--- a/java/org/apache/coyote/http2/Http2Protocol.java
+++ b/java/org/apache/coyote/http2/Http2Protocol.java
@@ -33,6 +33,7 @@ import org.apache.coyote.UpgradeProtocol;
 import org.apache.coyote.UpgradeToken;
 import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
 import org.apache.coyote.http11.upgrade.UpgradeProcessorInternal;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.net.SocketWrapperBase;
 
 public class Http2Protocol implements UpgradeProtocol {
@@ -68,7 +69,7 @@ public class Http2Protocol implements UpgradeProtocol {
     private int maxHeaderSize = Constants.DEFAULT_MAX_HEADER_SIZE;
     private int maxTrailerCount = Constants.DEFAULT_MAX_TRAILER_COUNT;
     private int maxTrailerSize = Constants.DEFAULT_MAX_TRAILER_SIZE;
-
+    private boolean initiatePingDisabled = false;
 
     @Override
     public String getHttpUpgradeName(boolean isSSLEnabled) {
@@ -113,6 +114,7 @@ public class Http2Protocol implements UpgradeProtocol {
         result.setMaxHeaderSize(getMaxHeaderSize());
         result.setMaxTrailerCount(getMaxTrailerCount());
         result.setMaxTrailerSize(getMaxTrailerSize());
+        result.setInitiatePingDisabled(initiatePingDisabled);
         return result;
     }
 
@@ -224,17 +226,7 @@ public class Http2Protocol implements UpgradeProtocol {
         // sync is unnecessary.
         List<String> copy = new ArrayList<>(allowedTrailerHeaders.size());
         copy.addAll(allowedTrailerHeaders);
-        StringBuilder result = new StringBuilder();
-        boolean first = true;
-        for (String header : copy) {
-            if (first) {
-                first = false;
-            } else {
-                result.append(',');
-            }
-            result.append(header);
-        }
-        return result.toString();
+        return StringUtils.join(copy);
     }
 
 
@@ -276,4 +268,9 @@ public class Http2Protocol implements UpgradeProtocol {
     public int getMaxTrailerSize() {
         return maxTrailerSize;
     }
+
+
+    public void setInitiatePingDisabled(boolean initiatePingDisabled) {
+        this.initiatePingDisabled = initiatePingDisabled;
+    }
 }
diff --git a/java/org/apache/coyote/http2/Http2UpgradeHandler.java b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
index 15c44443..097dfa89 100644
--- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
@@ -131,7 +131,6 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
 
     private final Map<Integer,Stream> streams = new HashMap<>();
     private final AtomicInteger activeRemoteStreamCount = new AtomicInteger(0);
-    private volatile int maxRemoteStreamId = 0;
     // Start at -1 so the 'add 2' logic in closeIdleStreams() works
     private volatile int maxActiveRemoteStreamId = -1;
     private volatile int maxProcessedStreamId;
@@ -171,7 +170,6 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
             Integer key = Integer.valueOf(1);
             Stream stream = new Stream(key, this, coyoteRequest);
             streams.put(key, stream);
-            maxRemoteStreamId = 1;
             maxActiveRemoteStreamId = 1;
             activeRemoteStreamCount.set(1);
             maxProcessedStreamId = 1;
@@ -353,7 +351,9 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
                     break;
                 }
 
+                if (connectionState.get() != ConnectionState.CLOSED) {
                     result = SocketState.UPGRADED;
+                }
                 break;
 
             case OPEN_WRITE:
@@ -539,6 +539,7 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
             while (state != State.COMPLETE) {
                 state = getHpackEncoder().encode(coyoteResponse.getMimeHeaders(), target);
                 target.flip();
+                if (state == State.COMPLETE || target.limit() > 0) {
                     ByteUtil.setThreeBytes(header, 0, target.limit());
                     if (first) {
                         first = false;
@@ -564,6 +565,12 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
                         handleAppInitiatedIOException(ioe);
                     }
                 }
+                if (state == State.UNDERFLOW && target.limit() == 0) {
+                    target = ByteBuffer.allocate(target.capacity() * 2);
+                } else {
+                    target.clear();
+                }
+            }
         }
     }
 
@@ -955,16 +962,10 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
                     sm.getString("upgradeHandler.stream.even", key), Http2Error.PROTOCOL_ERROR);
         }
 
-        if (streamId <= maxRemoteStreamId) {
-            throw new ConnectionException(sm.getString("upgradeHandler.stream.old", key,
-                    Integer.valueOf(maxRemoteStreamId)), Http2Error.PROTOCOL_ERROR);
-        }
-
         pruneClosedStreams();
 
         Stream result = new Stream(key, this);
         streams.put(key, result);
-        maxRemoteStreamId = streamId;
         return result;
     }
 
@@ -976,13 +977,17 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
 
         Stream result = new Stream(key, this, request);
         streams.put(key, result);
-        maxRemoteStreamId = streamId;
         return result;
     }
 
 
     private void close() {
         connectionState.set(ConnectionState.CLOSED);
+        for (Stream stream : streams.values()) {
+            // The connection is closing. Close the associated streams as no
+            // longer required.
+            stream.receiveReset(Http2Error.CANCEL.getCode());
+        }
         try {
             socketWrapper.close();
         } catch (IOException ioe) {
@@ -1259,6 +1264,11 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
     }
 
 
+    public void setInitiatePingDisabled(boolean initiatePingDisabled) {
+        pingManager.initiateDisabled = initiatePingDisabled;
+    }
+
+
     // ----------------------------------------------- Http2Parser.Input methods
 
     @Override
@@ -1329,6 +1339,7 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
     public ByteBuffer startRequestBodyFrame(int streamId, int payloadSize) throws Http2Exception {
         Stream stream = getStream(streamId, true);
         stream.checkState(FrameType.DATA);
+        stream.receivedData(payloadSize);
         return stream.getInputByteBuffer();
     }
 
@@ -1369,6 +1380,11 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
             if (stream == null) {
                 stream = createRemoteStream(streamId);
             }
+            if (streamId < maxActiveRemoteStreamId) {
+                throw new ConnectionException(sm.getString("upgradeHandler.stream.old",
+                        Integer.valueOf(streamId), Integer.valueOf(maxActiveRemoteStreamId)),
+                        Http2Error.PROTOCOL_ERROR);
+            }
             stream.checkState(FrameType.HEADERS);
             stream.receivedStartOfHeaders(headersEndStream);
             closeIdleStreams(streamId);
@@ -1404,6 +1420,10 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
     @Override
     public void reprioritise(int streamId, int parentStreamId,
             boolean exclusive, int weight) throws Http2Exception {
+        if (streamId == parentStreamId) {
+            throw new ConnectionException(sm.getString("upgradeHandler.dependency.invalid",
+                    getConnectionId(), Integer.valueOf(streamId)), Http2Error.PROTOCOL_ERROR);
+        }
         Stream stream = getStream(streamId, false);
         if (stream == null) {
             stream = createRemoteStream(streamId);
@@ -1497,6 +1517,7 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
             log.debug(sm.getString("upgradeHandler.goaway.debug", connectionId,
                     Integer.toString(lastStreamId), Long.toHexString(errorCode), debugData));
         }
+        close();
     }
 
 
@@ -1521,6 +1542,8 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
 
     private class PingManager {
 
+        protected boolean initiateDisabled = false;
+
         // 10 seconds
         private final long pingIntervalNano = 10000000000L;
 
@@ -1538,6 +1561,9 @@ public class Http2UpgradeHandler extends AbstractStream implements InternalHttpU
          * @throws IOException If an I/O issue prevents the ping from being sent
          */
         public void sendPing(boolean force) throws IOException {
+            if (initiateDisabled) {
+                return;
+            }
             long now = System.nanoTime();
             if (force || now - lastPingNanoTime > pingIntervalNano) {
                 lastPingNanoTime = now;
diff --git a/java/org/apache/coyote/http2/LocalStrings.properties b/java/org/apache/coyote/http2/LocalStrings.properties
index d1ad5c44..0728051e 100644
--- a/java/org/apache/coyote/http2/LocalStrings.properties
+++ b/java/org/apache/coyote/http2/LocalStrings.properties
@@ -35,10 +35,12 @@ hpack.integerEncodedOverTooManyOctets=HPACK variable length integer encoded over
 hpack.invalidCharacter=The Unicode character [{0}] at code point [{1}] cannot be encoded as it is outside the permitted range of 0 to 255.
 
 hpackdecoder.zeroNotValidHeaderTableIndex=Zero is not a valid header table index
+hpackdecoder.tableSizeUpdateNotAtStart=Any table size update must be sent at the start of a header block
 
 hpackEncoder.encodeHeader=Encoding header [{0}] with value [{1}]
 
 hpackhuffman.huffmanEncodedHpackValueDidNotEndWithEOS=Huffman encoded value in HPACK headers did not end with EOS padding
+hpackhuffman.stringLiteralTooMuchPadding=More than 7 bits of EOS padding were provided at the end of an Huffman encoded string literal
 
 http2Parser.headerLimitCount=Connection [{0}], Stream [{1}], Too many headers
 http2Parser.headerLimitSize=Connection [{0}], Stream [{1}], Total header size too big
@@ -71,7 +73,14 @@ http2Parser.swallow.debug=Connection [{0}], Stream [{1}], Swallowed [{2}] bytes
 pingManager.roundTripTime=Connection [{0}] Round trip time measured as [{1}]ns
 
 stream.closed=Connection [{0}], Stream [{1}], Unable to write to stream once it has been closed
+stream.header.case=Connection [{0}], Stream [{1}], HTTP header name [{2}] must be in lower case
+stream.header.connection=Connection [{0}], Stream [{1}], HTTP header [connection] is not permitted in an HTTP/2 request
+stream.header.contentLength=Connection [{0}], Stream [{1}], The content length header value [{2}] does not agree with the size of the data received [{3}]
 stream.header.debug=Connection [{0}], Stream [{1}], HTTP header [{2}], Value [{3}]
+stream.header.duplicate=Connection [{0}], Stream [{1}], received multiple [{3}] headers
+stream.header.noPath=Connection [{0}], Stream [{1}], The [:path] pseudo header was empty
+stream.header.required=Connection [{0}], Stream [{1}], One or more required headers was missing
+stream.header.te=Connection [{0}], Stream [{1}], HTTP header [te] is not permitted to have the value [{2}] in an HTTP/2 request
 stream.header.unexpectedPseudoHeader=Connection [{0}], Stream [{1}], Pseudo header [{2}] received after a regular header
 stream.header.unknownPseudoHeader=Connection [{0}], Stream [{1}], Unknown pseudo header [{2}] received
 stream.notWritable=Connection [{0}], Stream [{1}], This stream is not writable
@@ -101,6 +110,7 @@ upgradeHandler.allocate.debug=Connection [{0}], Stream [{1}], allocated [{2}] by
 upgradeHandler.allocate.left=Connection [{0}], Stream [{1}], [{2}] bytes unallocated - trying to allocate to children
 upgradeHandler.allocate.recipient=Connection [{0}], Stream [{1}], potential recipient [{2}] with weight [{3}]
 upgradeHandler.connectionError=Connection error
+upgradeHandler.dependency.invalid=Connection [{0}], Stream [{1}], Streams may not depend on themselves
 upgradeHandler.goaway.debug=Connection [{0}], Goaway, Last stream [{1}], Error code [{2}], Debug data [{3}]
 upgradeHandler.init=Connection [{0}], State [{1}]
 upgradeHandler.initialWindowSize.invalid=Connection [{0}], Illegal value of [{1}] ignored for initial window size
diff --git a/java/org/apache/coyote/http2/Stream.java b/java/org/apache/coyote/http2/Stream.java
index e18e4d04..2e3ff88e 100644
--- a/java/org/apache/coyote/http2/Stream.java
+++ b/java/org/apache/coyote/http2/Stream.java
@@ -22,6 +22,7 @@ import java.security.AccessController;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.Iterator;
+import java.util.Locale;
 
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.CloseNowException;
@@ -53,6 +54,7 @@ public class Stream extends AbstractStream implements HeaderEmitter {
     }
 
     private volatile int weight = Constants.DEFAULT_WEIGHT;
+    private volatile long contentLengthReceived = 0;
 
     private final Http2UpgradeHandler handler;
     private final StreamStateMachine state;
@@ -233,12 +235,30 @@ public class Stream extends AbstractStream implements HeaderEmitter {
 
 
     @Override
-    public void emitHeader(String name, String value) {
+    public final void emitHeader(String name, String value) throws HpackException {
         if (log.isDebugEnabled()) {
             log.debug(sm.getString("stream.header.debug", getConnectionId(), getIdentifier(),
                     name, value));
         }
 
+        // Header names must be lower case
+        if (!name.toLowerCase(Locale.US).equals(name)) {
+            throw new HpackException(sm.getString("stream.header.case",
+                    getConnectionId(), getIdentifier(), name));
+        }
+
+        if ("connection".equals(name)) {
+            throw new HpackException(sm.getString("stream.header.connection",
+                    getConnectionId(), getIdentifier()));
+        }
+
+        if ("te".equals(name)) {
+            if (!"trailers".equals(value)) {
+                throw new HpackException(sm.getString("stream.header.te",
+                        getConnectionId(), getIdentifier(), value));
+            }
+        }
+
         if (headerStateErrorMsg != null) {
             // Don't bother processing the header since the stream is going to
             // be reset anyway
@@ -260,28 +280,49 @@ public class Stream extends AbstractStream implements HeaderEmitter {
 
         switch(name) {
         case ":method": {
+            if (coyoteRequest.method().isNull()) {
                 coyoteRequest.method().setString(value);
+            } else {
+                throw new HpackException(sm.getString("stream.header.duplicate",
+                        getConnectionId(), getIdentifier(), ":method" ));
+            }
             break;
         }
         case ":scheme": {
+            if (coyoteRequest.scheme().isNull()) {
                 coyoteRequest.scheme().setString(value);
+            } else {
+                throw new HpackException(sm.getString("stream.header.duplicate",
+                        getConnectionId(), getIdentifier(), ":scheme" ));
+            }
             break;
         }
         case ":path": {
+            if (!coyoteRequest.requestURI().isNull()) {
+                throw new HpackException(sm.getString("stream.header.duplicate",
+                        getConnectionId(), getIdentifier(), ":path" ));
+            }
+            if (value.length() == 0) {
+                throw new HpackException(sm.getString("stream.header.noPath",
+                        getConnectionId(), getIdentifier()));
+            }
             int queryStart = value.indexOf('?');
             if (queryStart == -1) {
                 coyoteRequest.requestURI().setString(value);
-                coyoteRequest.decodedURI().setString(coyoteRequest.getURLDecoder().convert(value, false));
+                coyoteRequest.decodedURI().setString(
+                        coyoteRequest.getURLDecoder().convert(value, false));
             } else {
                 String uri = value.substring(0, queryStart);
                 String query = value.substring(queryStart + 1);
                 coyoteRequest.requestURI().setString(uri);
-                coyoteRequest.decodedURI().setString(coyoteRequest.getURLDecoder().convert(uri, false));
+                coyoteRequest.decodedURI().setString(
+                        coyoteRequest.getURLDecoder().convert(uri, false));
                 coyoteRequest.queryString().setString(query);
             }
             break;
         }
         case ":authority": {
+            if (coyoteRequest.serverName().isNull()) {
                 int i = value.lastIndexOf(':');
                 if (i > -1) {
                     coyoteRequest.serverName().setString(value.substring(0, i));
@@ -289,6 +330,10 @@ public class Stream extends AbstractStream implements HeaderEmitter {
                 } else {
                     coyoteRequest.serverName().setString(value);
                 }
+            } else {
+                throw new HpackException(sm.getString("stream.header.duplicate",
+                        getConnectionId(), getIdentifier(), ":authority" ));
+            }
             break;
         }
         case "cookie": {
@@ -331,7 +376,12 @@ public class Stream extends AbstractStream implements HeaderEmitter {
     }
 
 
-    final boolean receivedEndOfHeaders() {
+    final boolean receivedEndOfHeaders() throws ConnectionException {
+        if (coyoteRequest.method().isNull() || coyoteRequest.scheme().isNull() ||
+                coyoteRequest.requestURI().isNull()) {
+            throw new ConnectionException(sm.getString("stream.header.required",
+                    getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
+        }
         // Cookie headers need to be concatenated into a single header
         // See RFC 7540 8.1.2.5
         // Can only do this once the headers are fully received
@@ -411,11 +461,28 @@ public class Stream extends AbstractStream implements HeaderEmitter {
     }
 
 
-    void receivedEndOfStream() {
-        synchronized (inputBuffer) {
-            inputBuffer.notifyAll();
+    final void receivedData(int payloadSize) throws ConnectionException {
+        contentLengthReceived += payloadSize;
+        long contentLengthHeader = coyoteRequest.getContentLengthLong();
+        if (contentLengthHeader > -1 && contentLengthReceived > contentLengthHeader) {
+            throw new ConnectionException(sm.getString("stream.header.contentLength",
+                    getConnectionId(), getIdentifier(), Long.valueOf(contentLengthHeader),
+                    Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
+        }
+    }
+
+
+    final void receivedEndOfStream() throws ConnectionException {
+        long contentLengthHeader = coyoteRequest.getContentLengthLong();
+        if (contentLengthHeader > -1 && contentLengthReceived != contentLengthHeader) {
+            throw new ConnectionException(sm.getString("stream.header.contentLength",
+                    getConnectionId(), getIdentifier(), Long.valueOf(contentLengthHeader),
+                    Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
         }
         state.receivedEndOfStream();
+        if (inputBuffer != null) {
+            inputBuffer.notifyEof();
+        }
     }
 
 
@@ -749,8 +816,8 @@ public class Stream extends AbstractStream implements HeaderEmitter {
 
             // Ensure that only one thread accesses inBuffer at a time
             synchronized (inBuffer) {
-                boolean canRead = isActive() && !isInputFinished();
-                while (inBuffer.position() == 0 && canRead) {
+                boolean canRead = false;
+                while (inBuffer.position() == 0 && (canRead = isActive() && !isInputFinished())) {
                     // Need to block until some data is written
                     try {
                         if (log.isDebugEnabled()) {
@@ -806,8 +873,8 @@ public class Stream extends AbstractStream implements HeaderEmitter {
 
             // Ensure that only one thread accesses inBuffer at a time
             synchronized (inBuffer) {
-                boolean canRead = isActive() && !isInputFinished();
-                while (inBuffer.position() == 0 && canRead) {
+                boolean canRead = false;
+                while (inBuffer.position() == 0 && (canRead = isActive() && !isInputFinished())) {
                     // Need to block until some data is written
                     try {
                         if (log.isDebugEnabled()) {
@@ -937,5 +1004,13 @@ public class Stream extends AbstractStream implements HeaderEmitter {
                 }
             }
         }
+
+        private final void notifyEof() {
+            if (inBuffer != null) {
+                synchronized (inBuffer) {
+                    inBuffer.notifyAll();
+                }
+            }
+        }
     }
 }
diff --git a/java/org/apache/coyote/http2/StreamProcessor.java b/java/org/apache/coyote/http2/StreamProcessor.java
index 3ccfd8a6..344ad61c 100644
--- a/java/org/apache/coyote/http2/StreamProcessor.java
+++ b/java/org/apache/coyote/http2/StreamProcessor.java
@@ -134,7 +134,11 @@ class StreamProcessor extends AbstractProcessor {
     @Override
     protected final void setRequestBody(ByteChunk body) {
         stream.getInputBuffer().insertReplayedBody(body);
+        try {
             stream.receivedEndOfStream();
+        } catch (ConnectionException e) {
+            // Exception will not be thrown in this case
+        }
     }
 
 
diff --git a/java/org/apache/el/Messages.properties b/java/org/apache/el/Messages.properties
index f3d87089..e7b58c09 100644
--- a/java/org/apache/el/Messages.properties
+++ b/java/org/apache/el/Messages.properties
@@ -29,7 +29,7 @@ error.value.literal.write=ValueExpression is a literal and not writable: {0}
 
 # ExpressionFactoryImpl
 error.null=Expression cannot be null
-error.mixed=Expression cannot contain both '#{..}' and '${..}' : {0}
+error.mixed=Expression cannot contain both '#{...}' and '${...}' : {0}
 error.method=Not a valid MethodExpression : {0}
 error.method.nullParms=Parameter types cannot be null
 error.value.expectedType=Expected type cannot be null
diff --git a/java/org/apache/el/Messages_es.properties b/java/org/apache/el/Messages_es.properties
index 3f42d0f7..7a648f98 100644
--- a/java/org/apache/el/Messages_es.properties
+++ b/java/org/apache/el/Messages_es.properties
@@ -21,7 +21,7 @@ error.resolver.unhandled = ELResolver no manej\u00F3 el tipo\: {0} con propiedad
 error.resolver.unhandled.null = ELResolver no puede manejar un Objeto base nulo  con identificador de ''{0}''
 error.value.literal.write = ValueExpression es un literal y no un grabable\: {0}
 error.null = La expresi\u00F3n no puede ser nula
-error.mixed = La expresi\u00F3n no puede contenera la vez '\#{..}' y '${..}' \: {0}
+error.mixed = La expresi\u00F3n no puede contenera la vez ''\#{..}'' y ''${..}'' \: {0}
 error.method = No es una MethodExpression v\u00E1lida\: {0}
 error.method.nullParms = Los tipos de par\u00E1metro no pueden ser nulo
 error.value.expectedType = El tipo esperado no puede ser nulo
diff --git a/java/org/apache/el/util/ReflectionUtil.java b/java/org/apache/el/util/ReflectionUtil.java
index 0b0ff5b5..904aa8be 100644
--- a/java/org/apache/el/util/ReflectionUtil.java
+++ b/java/org/apache/el/util/ReflectionUtil.java
@@ -184,7 +184,7 @@ public class ReflectionUtil {
                         if (isAssignableFrom(paramTypes[j], varType)) {
                             assignableMatch++;
                         } else {
-                            if (paramValues == null) {
+                            if (paramValues == null || j >= paramValues.length) {
                                 noMatch = true;
                                 break;
                             } else {
@@ -203,7 +203,7 @@ public class ReflectionUtil {
                 } else if (isAssignableFrom(paramTypes[i], mParamTypes[i])) {
                     assignableMatch++;
                 } else {
-                    if (paramValues == null) {
+                    if (paramValues == null || i >= paramValues.length) {
                         noMatch = true;
                         break;
                     } else {
diff --git a/java/org/apache/jasper/compiler/Generator.java b/java/org/apache/jasper/compiler/Generator.java
index 6869fdf6..8112ebee 100644
--- a/java/org/apache/jasper/compiler/Generator.java
+++ b/java/org/apache/jasper/compiler/Generator.java
@@ -2640,9 +2640,15 @@ class Generator {
             declareScriptingVars(n, VariableInfo.AT_BEGIN);
             saveScriptingVars(n, VariableInfo.AT_BEGIN);
 
+            // Declare AT_END scripting variables
+            declareScriptingVars(n, VariableInfo.AT_END);
+
             String tagHandlerClassName = tagHandlerClass.getCanonicalName();
             writeNewInstance(tagHandlerVar, tagHandlerClassName);
 
+            out.printil("try {");
+            out.pushIndent();
+
             generateSetters(n, tagHandlerVar, handlerInfo, true);
 
             // Set the body
@@ -2682,13 +2688,19 @@ class Generator {
             // Synchronize AT_BEGIN scripting variables
             syncScriptingVars(n, VariableInfo.AT_BEGIN);
 
-            // Declare and synchronize AT_END scripting variables
-            declareScriptingVars(n, VariableInfo.AT_END);
+            // Synchronize AT_END scripting variables
             syncScriptingVars(n, VariableInfo.AT_END);
 
+            out.popIndent();
+            out.printil("} finally {");
+            out.pushIndent();
+
             // Resource injection
             writeDestroyInstance(tagHandlerVar);
 
+            out.popIndent();
+            out.printil("}");
+
             n.setEndJavaLine(out.getJavaLine());
         }
 
diff --git a/java/org/apache/jasper/resources/LocalStrings.properties b/java/org/apache/jasper/resources/LocalStrings.properties
index d5789cb5..14c635e0 100644
--- a/java/org/apache/jasper/resources/LocalStrings.properties
+++ b/java/org/apache/jasper/resources/LocalStrings.properties
@@ -26,31 +26,31 @@ jsp.message.parent_class_loader_is=Parent class loader is: {0}
 jsp.message.dont.modify.servlets=IMPORTANT: Do not modify the generated servlets
 jsp.error.unavailable=JSP has been marked unavailable
 jsp.error.usebean.duplicate=useBean: Duplicate bean name: {0}
-jsp.error.invalid.scope=Illegal value of \'scope\' attribute: {0} (must be one of \"page\", \"request\", \"session\", or \"application\")
+jsp.error.invalid.scope=Illegal value of ''scope'' attribute: {0} (must be one of \"page\", \"request\", \"session\", or \"application\")
 jsp.error.classname=Can't determine classname from .class file
 jsp.error.outputfolder=No output folder
 jsp.error.data.file.write=Error while writing data file
-jsp.error.page.conflict.contenttype=Page directive: illegal to have multiple occurrences of 'contentType' with different values (old: {0}, new: {1})
-jsp.error.page.conflict.session=Page directive: illegal to have multiple occurrences of 'session' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.contenttype=Page directive: illegal to have multiple occurrences of ''contentType'' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.session=Page directive: illegal to have multiple occurrences of ''session'' with different values (old: {0}, new: {1})
 jsp.error.page.invalid.session=Page directive: invalid value for session
-jsp.error.page.conflict.buffer=Page directive: illegal to have multiple occurrences of 'buffer' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.buffer=Page directive: illegal to have multiple occurrences of ''buffer'' with different values (old: {0}, new: {1})
 jsp.error.page.invalid.buffer=Page directive: invalid value for buffer
-jsp.error.page.conflict.autoflush=Page directive: illegal to have multiple occurrences of 'autoFlush' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.autoflush=Page directive: illegal to have multiple occurrences of ''autoFlush'' with different values (old: {0}, new: {1})
 jsp.error.page.invalid.import=Page directive: invalid value for import
-jsp.error.page.conflict.isthreadsafe=Page directive: illegal to have multiple occurrences of 'isThreadSafe' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.isthreadsafe=Page directive: illegal to have multiple occurrences of ''isThreadSafe'' with different values (old: {0}, new: {1})
 jsp.error.page.invalid.isthreadsafe=Page directive: invalid value for isThreadSafe
-jsp.error.page.conflict.info=Page directive: illegal to have multiple occurrences of 'info' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.info=Page directive: illegal to have multiple occurrences of ''info'' with different values (old: {0}, new: {1})
 jsp.error.page.invalid.info=Page directive: invalid value for info
-jsp.error.page.conflict.iserrorpage=Page directive: illegal to have multiple occurrences of 'isErrorPage' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.iserrorpage=Page directive: illegal to have multiple occurrences of ''isErrorPage'' with different values (old: {0}, new: {1})
 jsp.error.page.invalid.iserrorpage=Page directive: invalid value for isErrorPage
-jsp.error.page.conflict.errorpage=Page directive: illegal to have multiple occurrences of 'errorPage' with different values (old: {0}, new: {1})
-jsp.error.page.conflict.language=Page directive: illegal to have multiple occurrences of 'language' with different values (old: {0}, new: {1})
-jsp.error.tag.conflict.language=Tag directive: illegal to have multiple occurrences of 'language' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.errorpage=Page directive: illegal to have multiple occurrences of ''errorPage'' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.language=Page directive: illegal to have multiple occurrences of ''language'' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.language=Tag directive: illegal to have multiple occurrences of ''language'' with different values (old: {0}, new: {1})
 jsp.error.page.language.nonjava=Page directive: invalid language attribute
 jsp.error.tag.language.nonjava=Tag directive: invalid language attribute
-jsp.error.page.conflict.extends=Page directive: illegal to have multiple occurrences of 'extends' with different values (old: {0}, new: {1})
-jsp.error.page.conflict.iselignored=Page directive: illegal to have multiple occurrences of 'isELIgnored' with different values (old: {0}, new: {1})
-jsp.error.tag.conflict.iselignored=Tag directive: illegal to have multiple occurrences of 'isELIgnored' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.extends=Page directive: illegal to have multiple occurrences of ''extends'' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.iselignored=Page directive: illegal to have multiple occurrences of ''isELIgnored'' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.iselignored=Tag directive: illegal to have multiple occurrences of ''isELIgnored'' with different values (old: {0}, new: {1})
 jsp.error.page.invalid.iselignored=Page directive: invalid value for isELIgnored
 jsp.error.tag.invalid.iselignored=Tag directive: invalid value for isELIgnored
 jsp.error.page.multi.pageencoding=Page directive must not have multiple occurrences of pageencoding
@@ -131,7 +131,7 @@ jsp.warning.unknown.element.in.tagfile=Unknown element ({0}) in tag-file
 jsp.warning.unknown.element.in.attribute=Unknown element ({0}) in attribute
 jsp.warning.unknown.element.in.variable=Unknown element ({0}) in variable
 jsp.warning.unknown.element.in.validator=Unknown element ({0}) in validator
-jsp.warning.unknown.element.in.initParam=Unknown element ({0}) in validator's init-param
+jsp.warning.unknown.element.in.initParam=Unknown element ({0}) in validator''s init-param
 jsp.warning.unknown.element.in.function=Unknown element ({0}) in function
 jsp.error.teiclass.instantiation=Failed to load or instantiate TagExtraInfo class: {0}
 jsp.error.non_null_tei_and_var_subelems=Tag {0} has one or more variable subelements and a TagExtraInfo class that returns one or more VariableInfo
@@ -238,7 +238,7 @@ jsp.error.taglibDirective.both_uri_and_tagdir=Both \'uri\' and \'tagdir\' attrib
 jsp.error.invalid.tagdir=Tag file directory {0} does not start with \"/WEB-INF/tags\"
 #jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
 #Error while processing taglib jar file {0}: {1}
-jsp.error.needAlternateJavaEncoding=Default java encoding {0} is invalid on your java platform. An alternate can be specified via the 'javaEncoding' parameter of JspServlet.
+jsp.error.needAlternateJavaEncoding=Default java encoding {0} is invalid on your java platform. An alternate can be specified via the ''javaEncoding'' parameter of JspServlet.
 #Error when compiling, used for jsp line number error messages
 jsp.error.single.line.number=An error occurred at line: {0} in the jsp file: {1}
 jsp.error.java.line.number=An error occurred at line: [{0}] in the generated java file: [{1}]
@@ -249,7 +249,7 @@ jsp.error.jspbody.emptybody.only=The {0} tag can only have jsp:attribute in its
 jsp.error.no.scriptlets=Scripting elements ( &lt;%!, &lt;jsp:declaration, &lt;%=, &lt;jsp:expression, &lt;%, &lt;jsp:scriptlet ) are disallowed here.
 jsp.error.tld.fn.invalid.signature=Invalid syntax for function signature in TLD.  Tag Library: {0}, Function: {1}
 jsp.error.tld.fn.duplicate.name=Duplicate function name {0} in tag library {1}
-jsp.error.tld.fn.invalid.signature.parenexpected=Invalid syntax for function signature in TLD.  Parenthesis '(' expected.  Tag Library: {0}, Function: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected=Invalid syntax for function signature in TLD.  Parenthesis ''('' expected.  Tag Library: {0}, Function: {1}.
 jsp.error.tld.mandatory.element.missing=Mandatory TLD element {0} missing or empty in TLD {1}
 jsp.error.dynamic.attributes.not.implemented=The {0} tag declares that it accepts dynamic attributes but does not implement the required interface
 jsp.error.attribute.noequal=equal symbol expected
@@ -364,13 +364,13 @@ jsp.error.el.template.deferred=#{...} is not allowed in template text
 jsp.error.el.parse={0} : {1}
 jsp.error.page.invalid.deferredsyntaxallowedasliteral=Page directive: invalid value for deferredSyntaxAllowedAsLiteral
 jsp.error.tag.invalid.deferredsyntaxallowedasliteral=Tag directive: invalid value for deferredSyntaxAllowedAsLiteral
-jsp.error.page.conflict.deferredsyntaxallowedasliteral=Page directive: illegal to have multiple occurrences of 'deferredSyntaxAllowedAsLiteral' with different values (old: {0}, new: {1})
-jsp.error.tag.conflict.deferredsyntaxallowedasliteral=Tag directive: illegal to have multiple occurrences of 'deferredSyntaxAllowedAsLiteral' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.deferredsyntaxallowedasliteral=Page directive: illegal to have multiple occurrences of ''deferredSyntaxAllowedAsLiteral'' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.deferredsyntaxallowedasliteral=Tag directive: illegal to have multiple occurrences of ''deferredSyntaxAllowedAsLiteral'' with different values (old: {0}, new: {1})
 
 jsp.error.page.invalid.trimdirectivewhitespaces=Page directive: invalid value for trimDirectiveWhitespaces
 jsp.error.tag.invalid.trimdirectivewhitespaces=Tag directive: invalid value for trimDirectiveWhitespaces
-jsp.error.page.conflict.trimdirectivewhitespaces=Page directive: illegal to have multiple occurrences of 'trimDirectiveWhitespaces' with different values (old: {0}, new: {1})
-jsp.error.tag.conflict.trimdirectivewhitespaces=Tag directive: illegal to have multiple occurrences of 'trimDirectiveWhitespaces' with different values (old: {0}, new: {1})
+jsp.error.page.conflict.trimdirectivewhitespaces=Page directive: illegal to have multiple occurrences of ''trimDirectiveWhitespaces'' with different values (old: {0}, new: {1})
+jsp.error.tag.conflict.trimdirectivewhitespaces=Tag directive: illegal to have multiple occurrences of ''trimDirectiveWhitespaces'' with different values (old: {0}, new: {1})
 
 # JSP Servlet
 jsp.error.servlet.invalid.method=JSPs only permit GET POST or HEAD
diff --git a/java/org/apache/jasper/resources/LocalStrings_es.properties b/java/org/apache/jasper/resources/LocalStrings_es.properties
index baac6470..e02e82b9 100644
--- a/java/org/apache/jasper/resources/LocalStrings_es.properties
+++ b/java/org/apache/jasper/resources/LocalStrings_es.properties
@@ -25,30 +25,30 @@ jsp.message.parent_class_loader_is = El cargador de clases es\: {0}
 jsp.message.dont.modify.servlets = IMPORTANTE\: No modifique los servlets generados
 jsp.error.unavailable = JSP ha sido marcado como no disponible
 jsp.error.usebean.duplicate = useBean\: Nombre de bean duplicado\: {0}
-jsp.error.invalid.scope = Valor ilegal de atributo 'scope'\: {0} (debe de ser uno de "page", "request", "session", o "application")
+jsp.error.invalid.scope = Valor ilegal de atributo ''scope''\: {0} (debe de ser uno de "page", "request", "session", o "application")
 jsp.error.classname = No pude determinar el nombre de clase desde el fichero .class
 jsp.error.outputfolder = no hay carpeta de salida
 jsp.error.data.file.write = Error mientras escrib\u00EDa el archivo de datos
-jsp.error.page.conflict.contenttype = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'contentType' con valores distintos (viejo\: {0}, nuevo\: {1})
-jsp.error.page.conflict.session = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'session' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.contenttype = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''contentType'' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.session = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''session'' con valores distintos (viejo\: {0}, nuevo\: {1})
 jsp.error.page.invalid.session = Directiva Page\: valor incorrecto para session
-jsp.error.page.conflict.buffer = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'buffer'con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.buffer = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''buffer'' con valores distintos (viejo\: {0}, nuevo\: {1})
 jsp.error.page.invalid.buffer = Directiva Page\: valor incorrecto para b\u00FAfer
-jsp.error.page.conflict.autoflush = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'autoFlush' con valores distintos (viejo\: {0}, nuevo\: {1})
-jsp.error.page.conflict.isthreadsafe = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isThreadSafe' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.autoflush = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''autoFlush'' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.isthreadsafe = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''isThreadSafe'' con valores distintos (viejo\: {0}, nuevo\: {1})
 jsp.error.page.invalid.isthreadsafe = \=Directiva Page\: valor incorrecto para isThreadSafe
-jsp.error.page.conflict.info = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'info' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.info = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''info'' con valores distintos (viejo\: {0}, nuevo\: {1})
 jsp.error.page.invalid.info = \=Directiva Page\: valor incorrecto para info
-jsp.error.page.conflict.iserrorpage = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isErrorPage' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.iserrorpage = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''isErrorPage'' con valores distintos (viejo\: {0}, nuevo\: {1})
 jsp.error.page.invalid.iserrorpage = \=Directiva Page\: valor incorrecto para isErrorPage
-jsp.error.page.conflict.errorpage = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'errorPage' con valores distintos (viejo\: {0}, nuevo\: {1})
-jsp.error.page.conflict.language = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'language' con valores distintos (viejo\: {0}, nuevo\: {1})
-jsp.error.tag.conflict.language = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias de 'language' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.errorpage = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''errorPage'' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.language = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''language'' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.language = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias de ''language'' con valores distintos (viejo\: {0}, nuevo\: {1})
 jsp.error.page.language.nonjava = Directiva Page\: atributo language incorrecto
 jsp.error.tag.language.nonjava = Directiva Tag\: atributo language incorrecto
-jsp.error.page.conflict.extends = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'extends' con valores distintos (viejo\: {0}, nuevo\: {1})
-jsp.error.page.conflict.iselignored = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de 'isELIgnored' con valores distintos (viejo\: {0}, nuevo\: {1})
-jsp.error.tag.conflict.iselignored = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias de 'isELIgnored' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.extends = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''extends'' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.iselignored = Directiva Page\: es ilegal tener m\u00FAltiples ocurrencias de ''isELIgnored'' con valores distintos (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.iselignored = Directiva Tag\: es ilegal tener m\u00FAltiples ocurrencias de ''isELIgnored'' con valores distintos (viejo\: {0}, nuevo\: {1})
 jsp.error.page.invalid.iselignored = Directiva Page\: valor inv\u00E1lido para isELIgnored
 jsp.error.tag.invalid.iselignored = Directiva Tag\: valor incorrecto para isELIgnored
 jsp.error.page.multi.pageencoding = La directiva Page no debe de tener m\u00FAltiples ocurrencias de pageencoding
@@ -229,7 +229,7 @@ jsp.error.taglibDirective.both_uri_and_tagdir = Se han especificado ambos atribu
 jsp.error.invalid.tagdir = El directorio de archivo Tag {0} no comienza con "/WEB-INF/tags"
 #jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
 #Error while processing taglib jar file {0}: {1}
-jsp.error.needAlternateJavaEncoding = La codificaci\u00F3n java por defecto {0} es incorrecta en tu plataforma java. Se puede especificar una alternativa v\u00EDa par\u00E1metro 'javaEncoding' de JspServlet.
+jsp.error.needAlternateJavaEncoding = La codificaci\u00F3n java por defecto {0} es incorrecta en tu plataforma java. Se puede especificar una alternativa v\u00EDa par\u00E1metro ''javaEncoding'' de JspServlet.
 #Error when compiling, used for jsp line number error messages
 jsp.error.single.line.number = Ha tenido lugar un error en la l\u00EDnea\: {0} en el archivo jsp\: {1}
 jsp.error.java.line.number = Ha tenido lugar un error en la l\u00EDnea\: [{0}] en el fichero java generado: [{1}]
@@ -240,7 +240,7 @@ jsp.error.jspbody.emptybody.only = El tag {0} s\u00F3lo puede tener jsp\:attribu
 jsp.error.no.scriptlets = Los elementos de Scripting (&lt;%\!, &lt;jsp\:declaration, &lt;%\=, &lt;jsp\:expression, &lt;%, &lt;jsp\:scriptlet ) no est\u00E1n permitidos aqu\u00ED.
 jsp.error.tld.fn.invalid.signature = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}
 jsp.error.tld.fn.duplicate.name = Nombre duplicado de funci\u00F3n {0} en biblioteca de tag {1}
-jsp.error.tld.fn.invalid.signature.parenexpected = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Se esperaba Par\u00E9ntesis '('. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected = Sint\u00E1xis incorrecta para firma de funci\u00F3n en TLD. Se esperaba Par\u00E9ntesis ''(''. Biblioteca de Tag\: {0}, Funci\u00F3n\: {1}.
 jsp.error.tld.mandatory.element.missing = Falta o est\u00E1 vac\u00EDo elemento TLD obligatorio\: {0}
 jsp.error.dynamic.attributes.not.implemented = El tag {0} declara que acepta atributos din\u00E1micos pero no implementa la interfaz requerida
 jsp.error.attribute.noequal = se esperaba s\u00EDmbolo igual
@@ -349,12 +349,12 @@ jsp.error.el.template.deferred = \#{..} no est\u00E1 permitido en texto de plant
 jsp.error.el.parse = {0} \: {1}
 jsp.error.page.invalid.deferredsyntaxallowedasliteral = Directiva de p\u00E1gina\: valor inv\u00E1lido para deferredSyntaxAllowedAsLiteral
 jsp.error.tag.invalid.deferredsyntaxallowedasliteral = Directiva de marca\: valor inv\u00E1lido para deferredSyntaxAllowedAsLiteral
-jsp.error.page.conflict.deferredsyntaxallowedasliteral = Directiva de p\u00E1gina\: es ilegal tener m\u00FAltiples ocurrencias de 'deferredSyntaxAllowedAsLiteral' con diferentes valores (viejo\: {0}, nuevo\: {1})
-jsp.error.tag.conflict.deferredsyntaxallowedasliteral = Directiva de marca\: es ilegal tener m\u00FAltiples ocurrencias de 'deferredSyntaxAllowedAsLiteral' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.deferredsyntaxallowedasliteral = Directiva de p\u00E1gina\: es ilegal tener m\u00FAltiples ocurrencias de ''deferredSyntaxAllowedAsLiteral'' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.deferredsyntaxallowedasliteral = Directiva de marca\: es ilegal tener m\u00FAltiples ocurrencias de ''deferredSyntaxAllowedAsLiteral'' con diferentes valores (viejo\: {0}, nuevo\: {1})
 jsp.error.page.invalid.trimdirectivewhitespaces = Directiva de p\u00E1gina\: valor inv\u00E1lido para trimDirectiveWhitespaces
 jsp.error.tag.invalid.trimdirectivewhitespaces = Directiva de marca\: valor inv\u00E1lido para trimDirectiveWhitespaces
-jsp.error.page.conflict.trimdirectivewhitespaces = Directiva de p\u00E1gina\: es ilegal tener m\u00FAltiples ocurrencias de 'trimDirectivewhitespaces' con diferentes valores (viejo\: {0}, nuevo\: {1})
-jsp.error.tag.conflict.trimdirectivewhitespaces = Directiva de marca\: es ilegal tener m\u00FAltiples ocurrencias de 'trimDirectivewhitespaces' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.page.conflict.trimdirectivewhitespaces = Directiva de p\u00E1gina\: es ilegal tener m\u00FAltiples ocurrencias de ''trimDirectivewhitespaces'' con diferentes valores (viejo\: {0}, nuevo\: {1})
+jsp.error.tag.conflict.trimdirectivewhitespaces = Directiva de marca\: es ilegal tener m\u00FAltiples ocurrencias de ''trimDirectivewhitespaces'' con diferentes valores (viejo\: {0}, nuevo\: {1})
 jsp.warning.noJarScanner = Aviso\: No se ha puesto org.apache.tomcat.JarScanner en ServletContext. Volviendo a la implementaci\u00F3n por defecto de JarScanner.
 jsp.error.bug48498 = No puedo mostrar extracto de JSP. Probablemente debido a un error de analizador XML (ver error 48498 de Tomcat para detalles).
 jsp.error.duplicateqname = Se ha hallado un atributo con nombre cualificado duplicado [{0}]. Los nombres de atributos cuallificados deben de se \u00FAnicos dentro de un elemento.
diff --git a/java/org/apache/jasper/resources/LocalStrings_fr.properties b/java/org/apache/jasper/resources/LocalStrings_fr.properties
index 512b4d9e..49915bc8 100644
--- a/java/org/apache/jasper/resources/LocalStrings_fr.properties
+++ b/java/org/apache/jasper/resources/LocalStrings_fr.properties
@@ -151,7 +151,7 @@ jsp.error.no.more.content=Fin de contenu alors que l''\u00e9valution n''\u00e9ta
 jsp.error.parse.xml=Erreur d''\u00e9valuation XML sur le fichier {0}
 jsp.error.parse.xml.line=Erreur d''\u00e9valuation XML sur le fichier  {0}: (ligne {1}, col {2})
 jsp.error.parse.xml.scripting.invalid.body=Le corps de l''\u00e9l\u00e9ment {0} ne doit contenir aucun \u00e9l\u00e9ments XML
-jsp.error.internal.tldinit=Exception lors de l'initialisation de TldLocationsCache: {0}
+jsp.error.internal.tldinit=Exception lors de l''initialisation de TldLocationsCache: {0}
 jsp.error.internal.filenotfound=Erreur interne: Fichier {0} introuvable
 jsp.error.parse.xml.invalidPublicId=PUBLIC ID invalide: {0}
 jsp.error.unsupported.encoding=Encodage non support\u00e9: {0}
@@ -161,7 +161,7 @@ jsp.error.taglibDirective.missing.location=Ni l''uri' ni l''attribut 'tagdir' n'
 jsp.error.invalid.tagdir=Le r\u00e9pertoire du fichier Tag {0} ne commence pas par \"/WEB-INF/tags\"
 #jspx.error.templateDataNotInJspCdata=Erreur de validation: l''\u00e9l\u00e9ment &lt;{0}&gt; ne peut avoir de donn\u00e9es template. Les donn\u00e9es Template doivent \u00eatre encapsul\u00e9es \u00e0 l''int\u00e9rieur d''un \u00e9l\u00e9ment &lt;jsp:cdata&gt;. [JSP1.2 PFD section 5.1.9]\nDonn\u00e9e Template en erreur: {1}
 #Erreur lors du traitement du fichier jar de la taglib {0}: {1}
-jsp.error.needAlternateJavaEncoding=L''encodage java par d\u00e9faut {0} est incorrect sur votre environnement java. Une alternative peut \u00eatre indiqu\u00e9e via le param\u00eatre 'javaEncoding' de la JspServlet.
+jsp.error.needAlternateJavaEncoding=L''encodage java par d\u00e9faut {0} est incorrect sur votre environnement java. Une alternative peut \u00eatre indiqu\u00e9e via le param\u00eatre ''javaEncoding'' de la JspServlet.
 #Erreur lors de la compilation, utilis\u00e9 pour la ligne jsp des messages d''erreur
 jsp.error.single.line.number=Une erreur s''est produite \u00e0 la ligne: {0} dans le fichier jsp: {1}
 jsp.error.corresponding.servlet=Erreur de servlet g\u00e9n\u00e9r\u00e9e:\n
@@ -169,7 +169,7 @@ jsp.error.jspbody.required=Doit utiliser jsp:body pour indiqu\u00e9 le corps de
 jsp.error.jspbody.emptybody.only=Le tag {0} ne peut avoir que jsp:attribute dans son corps.
 jsp.error.no.scriptlets=Les \u00e9l\u00e9ments de Scripting ( <%!, <jsp:declaration, <%=, <jsp:expression, <%, <jsp:scriptlet ) ne sont pas autoris\u00e9s ici.
 jsp.error.tld.fn.invalid.signature=Synthaxe invalide pour la signature de fonction dans la TLD.  Librairie de Tag : {0}, Fonction: {1}
-jsp.error.tld.fn.invalid.signature.parenexpected=Synthaxe invalide pour la signature de fonction dans la TLD.  Parenth\u00e8se '(' attendue.  Librairie de Tag: {0}, Fonction: {1}.
+jsp.error.tld.fn.invalid.signature.parenexpected=Synthaxe invalide pour la signature de fonction dans la TLD.  Parenth\u00e8se ''('' attendue.  Librairie de Tag: {0}, Fonction: {1}.
 jsp.error.dynamic.attributes.not.implemented=Le tag {0} indique qu''il accepte des attributs dynamics mais n''impl\u00e9mente pas l''interface requise
 jsp.error.attribute.noequal=Symbole \u00e9gal (equal) attendu
 jsp.error.attribute.noquote=Symbole guillemet (quote) attendu
diff --git a/java/org/apache/jasper/resources/LocalStrings_ja.properties b/java/org/apache/jasper/resources/LocalStrings_ja.properties
index 1bc6aa00..2ad30b2d 100644
--- a/java/org/apache/jasper/resources/LocalStrings_ja.properties
+++ b/java/org/apache/jasper/resources/LocalStrings_ja.properties
@@ -25,28 +25,28 @@ jsp.message.parent_class_loader_is=\u89aa\u30af\u30e9\u30b9\u30ed\u30fc\u30c0: {
 jsp.message.dont.modify.servlets=\u91cd\u8981: \u751f\u6210\u3055\u308c\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u3092\u5909\u66f4\u3057\u3066\u306f\u3044\u3051\u307e\u305b\u3093
 jsp.error.unavailable=JSP\u306f\u5229\u7528\u4e0d\u53ef\u3068\u30de\u30fc\u30af\u3055\u308c\u3066\u3044\u307e\u3059
 jsp.error.usebean.duplicate=useBean: beanName\u5c5e\u6027\u304c\u91cd\u8907\u3057\u3066\u3044\u307e\u3059: {0}
-jsp.error.invalid.scope='scope'\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059: {0} (\"page\"\u3001\"request\"\u3001\"session\"\u53c8\u306f\"application\"\u306e\u3069\u308c\u304b\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093)
+jsp.error.invalid.scope=''scope''\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059: {0} (\"page\"\u3001\"request\"\u3001\"session\"\u53c8\u306f\"application\"\u306e\u3069\u308c\u304b\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093)
 jsp.error.classname=.class\u30d5\u30a1\u30a4\u30eb\u304b\u3089\u30af\u30e9\u30b9\u540d\u3092\u6c7a\u5b9a\u3067\u304d\u307e\u305b\u3093
 jsp.error.data.file.write=\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb\u3092\u66f8\u304d\u8fbc\u307f\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
-jsp.error.page.conflict.contenttype=page\u6307\u793a\u5b50: 'contentType'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
-jsp.error.page.conflict.session=page\u6307\u793a\u5b50: 'session'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.contenttype=page\u6307\u793a\u5b50: ''contentType''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.session=page\u6307\u793a\u5b50: ''session''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.page.invalid.session=page\u6307\u793a\u5b50: session\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
-jsp.error.page.conflict.buffer=page\u6307\u793a\u5b50: 'buffer'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.buffer=page\u6307\u793a\u5b50: ''buffer''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.page.invalid.buffer=page\u6307\u793a\u5b50: buffer\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
-jsp.error.page.conflict.autoflush=page\u6307\u793a\u5b50: 'autoFlush'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
-jsp.error.page.conflict.isthreadsafe=page\u6307\u793a\u5b50: 'isThreadSafe'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.autoflush=page\u6307\u793a\u5b50: ''autoFlush''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.isthreadsafe=page\u6307\u793a\u5b50: ''isThreadSafe''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.page.invalid.isthreadsafe=page\u6307\u793a\u5b50: isThreadSafe\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
-jsp.error.page.conflict.info=page\u6307\u793a\u5b50: 'info'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.info=page\u6307\u793a\u5b50: ''info''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.page.invalid.info=page\u6307\u793a\u5b50: info\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
-jsp.error.page.conflict.iserrorpage=page\u6307\u793a\u5b50: 'isErrorPage'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.iserrorpage=page\u6307\u793a\u5b50: ''isErrorPage''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.page.invalid.iserrorpage=page\u6307\u793a\u5b50: isErrorPage\u5c5e\u6027\u306e\u5024\u304c\u7121\u52b9\u3067\u3059
-jsp.error.page.conflict.errorpage=page\u6307\u793a\u5b50: 'errorPage'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.errorpage=page\u6307\u793a\u5b50: ''errorPage''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.page.conflict.language=page\u6307\u793a\u5b50: 'language'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
-jsp.error.tag.conflict.language=tag\u6307\u793a\u5b50: 'language'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.tag.conflict.language=tag\u6307\u793a\u5b50: ''language''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.page.language.nonjava=page\u6307\u793a\u5b50: \u7121\u52b9\u306alanguage\u5c5e\u6027\u3067\u3059
 jsp.error.tag.language.nonjava=tag\u6307\u793a\u5b50: \u7121\u52b9\u306alanguage\u5c5e\u6027\u3067\u3059
-jsp.error.page.conflict.extends=page\u6307\u793a\u5b50: 'extends'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
-jsp.error.page.conflict.iselignored=page\u6307\u793a\u5b50: 'isELIgnored'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.extends=page\u6307\u793a\u5b50: ''extends''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
+jsp.error.page.conflict.iselignored=page\u6307\u793a\u5b50: ''isELIgnored''\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.tag.conflict.iselignored=tag\u6307\u793a\u5b50: 'isELIgnored'\u3092\u7570\u306a\u308b\u5024\u3067\u8907\u6570\u56de\u6307\u5b9a\u3059\u308b\u306e\u306f\u7121\u52b9\u3067\u3059 (\u65e7: {0}, \u65b0: {1})
 jsp.error.page.invalid.iselignored=page\u6307\u793a\u5b50: isELIgnored\u306b\u7121\u52b9\u306a\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
 jsp.error.tag.invalid.iselignored=tag\u6307\u793a\u5b50: isELIgnored\u306b\u7121\u52b9\u306a\u5024\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
@@ -205,7 +205,7 @@ jsp.error.taglibDirective.both_uri_and_tagdir=\'uri\'\u5c5e\u6027 \u3068 \'tagdi
 jsp.error.invalid.tagdir=\u30bf\u30b0\u30d5\u30a1\u30a4\u30eb\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u304c\"/WEB-INF/tags\"\u3067\u59cb\u307e\u308a\u307e\u305b\u3093
 #jspx.error.templateDataNotInJspCdata=Validation Error: Element &lt;{0}&gt; cannot have template data. Template data must be encapsulated within a &lt;jsp:cdata&gt; element. [JSP1.2 PFD section 5.1.9]\nTemplate data in error: {1}
 #Error while processing taglib jar file {0}: {1}
-jsp.error.needAlternateJavaEncoding=\u30c7\u30d5\u30a9\u30eb\u30c8\u306eJava\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 {0} \u306f\u3042\u306a\u305f\u306e\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0\u3067\u306f\u7121\u52b9\u3067\u3059\u3002JspServlet\u306e 'javaEncoding' \u30d1\u30e9\u30e1\u30bf\u3067\u3001\u5225\u306e\u5024\u3092\u6307\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002
+jsp.error.needAlternateJavaEncoding=\u30c7\u30d5\u30a9\u30eb\u30c8\u306eJava\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 {0} \u306f\u3042\u306a\u305f\u306e\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0\u3067\u306f\u7121\u52b9\u3067\u3059\u3002JspServlet\u306e ''javaEncoding'' \u30d1\u30e9\u30e1\u30bf\u3067\u3001\u5225\u306e\u5024\u3092\u6307\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002
 #Error when compiling, used for jsp line number error messages
 jsp.error.single.line.number=JSP\u30d5\u30a1\u30a4\u30eb: {1} \u306e\u4e2d\u306e{0}\u884c\u76ee\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
 jsp.error.corresponding.servlet=\u751f\u6210\u3055\u308c\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u30a8\u30e9\u30fc\u3067\u3059:\n
@@ -214,7 +214,7 @@ jsp.error.jspbody.emptybody.only={0} \u30bf\u30b0\u306f\u3001\u305d\u306e\u30dc\
 jsp.error.no.scriptlets=\u30b9\u30af\u30ea\u30d7\u30c6\u30a3\u30f3\u30b0\u8981\u7d20 ( &lt;%!\u3001&lt;jsp:declaration\u3001&lt;%=\u3001&lt;jsp:expression\u3001&lt;%\u3001&lt;jsp:scriptlet ) \u306f\u3053\u3053\u3067\u306f\u8a31\u3055\u308c\u307e\u305b\u3093
 jsp.error.tld.fn.invalid.signature=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}
 jsp.error.tld.fn.duplicate.name=\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea {1} \u306e\u4e2d\u306e\u95a2\u6570\u540d {0} \u304c\u91cd\u8907\u3057\u3066\u3044\u307e\u3059
-jsp.error.tld.fn.invalid.signature.parenexpected=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u62ec\u5f27 '(' \u304c\u3042\u308a\u307e\u305b\u3093\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}\u3002
+jsp.error.tld.fn.invalid.signature.parenexpected=TLD\u306e\u4e2d\u306e\u95a2\u6570\u30b7\u30b0\u30cd\u30c1\u30e3\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306a\u69cb\u6587\u3067\u3059\u3002\u62ec\u5f27 ''('' \u304c\u3042\u308a\u307e\u305b\u3093\u3002\u30bf\u30b0\u30e9\u30a4\u30d6\u30e9\u30ea: {0}\u3001\u95a2\u6570: {1}\u3002
 jsp.error.tld.mandatory.element.missing=\u5fc5\u9808TLD\u8981\u7d20\u304c\u306a\u3044\u3001\u53c8\u306f\u7a7a\u3067\u3059: {0}
 jsp.error.dynamic.attributes.not.implemented={0} \u30bf\u30b0\u306f\u305d\u308c\u304cdynamic\u5c5e\u6027\u3092\u53d7\u3051\u4ed8\u3051\u308b\u3068\u5ba3\u8a00\u3057\u3066\u3044\u307e\u3059\u304c\u3001\u305d\u308c\u306b\u5fc5\u8981\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u5b9f\u88c5\u3057\u3066\u3044\u307e\u305b\u3093
 jsp.error.attribute.noequal=\u7b49\u53f7\u8a18\u53f7\u304c\u5fc5\u8981\u3067\u3059
diff --git a/java/org/apache/tomcat/buildutil/SignCode.java b/java/org/apache/tomcat/buildutil/SignCode.java
index e072ecbc..bde6bcd7 100644
--- a/java/org/apache/tomcat/buildutil/SignCode.java
+++ b/java/org/apache/tomcat/buildutil/SignCode.java
@@ -41,6 +41,7 @@ import javax.xml.soap.SOAPException;
 import javax.xml.soap.SOAPMessage;
 import javax.xml.soap.SOAPPart;
 
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.codec.binary.Base64;
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.DirectoryScanner;
@@ -62,7 +63,8 @@ public class SignCode extends Task {
 
     static {
         try {
-            SIGNING_SERVICE_URL = new URL("https://api.ws.symantec.com/webtrust/SigningService";);
+            SIGNING_SERVICE_URL = new URL(
+                    "https://api-appsec-cws.ws.symantec.com/webtrust/SigningService";);
         } catch (MalformedURLException e) {
             throw new IllegalArgumentException(e);
         }
@@ -77,9 +79,12 @@ public class SignCode extends Task {
     private String userName;
     private String password;
     private String partnerCode;
+    private String keyStore;
+    private String keyStorePassword;
     private String applicationName;
     private String applicationVersion;
     private String signingService;
+    private boolean debug;
 
     public void addFileset(FileSet fileset) {
         filesets.add(fileset);
@@ -101,6 +106,16 @@ public class SignCode extends Task {
     }
 
 
+    public void setKeyStore(String keyStore) {
+        this.keyStore = keyStore;
+    }
+
+
+    public void setKeyStorePassword(String keyStorePassword) {
+        this.keyStorePassword = keyStorePassword;
+    }
+
+
     public void setApplicationName(String applicationName) {
         this.applicationName = applicationName;
     }
@@ -116,6 +131,11 @@ public class SignCode extends Task {
     }
 
 
+    public void setDebug(String debug) {
+        this.debug = Boolean.parseBoolean(debug);
+    }
+
+
     @Override
     public void execute() throws BuildException {
 
@@ -135,6 +155,10 @@ public class SignCode extends Task {
             }
         }
 
+        // Set up the TLS client
+        System.setProperty("javax.net.ssl.keyStore", keyStore);
+        System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
+
         try {
             String signingSetID = makeSigningRequest(filesToSign);
             downloadSignedFiles(filesToSign, signingSetID);
@@ -172,7 +196,7 @@ public class SignCode extends Task {
 
         SOAPElement commaDelimitedFileNames =
                 requestSigningRequest.addChildElement("commaDelimitedFileNames", NS);
-        commaDelimitedFileNames.addTextNode(listToString(fileNames));
+        commaDelimitedFileNames.addTextNode(StringUtils.join(fileNames));
 
         SOAPElement application =
                 requestSigningRequest.addChildElement("application", NS);
@@ -185,6 +209,12 @@ public class SignCode extends Task {
         log("Sending singing request to server and waiting for response");
         SOAPMessage response = connection.call(message, SIGNING_SERVICE_URL);
 
+        if (debug) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream(2 * 1024);
+            response.writeTo(baos);
+            log(baos.toString("UTF-8"));
+        }
+
         log("Processing response");
         SOAPElement responseBody = response.getSOAPBody();
 
@@ -214,21 +244,6 @@ public class SignCode extends Task {
     }
 
 
-    private String listToString(List<String> list) {
-        StringBuilder sb = new StringBuilder(list.size() * 6);
-        boolean doneFirst = false;
-        for (String s : list) {
-            if (doneFirst) {
-                sb.append(',');
-            } else {
-                doneFirst = true;
-            }
-            sb.append(s);
-        }
-        return sb.toString();
-    }
-
-
     private void downloadSignedFiles(List<File> filesToSign, String id)
             throws SOAPException, IOException {
 
diff --git a/java/org/apache/tomcat/util/ExceptionUtils.java b/java/org/apache/tomcat/util/ExceptionUtils.java
index 97e357d8..3df395fa 100644
--- a/java/org/apache/tomcat/util/ExceptionUtils.java
+++ b/java/org/apache/tomcat/util/ExceptionUtils.java
@@ -57,4 +57,15 @@ public class ExceptionUtils {
         }
         return t;
     }
+
+
+    /**
+     * NO-OP method provided to enable simple pre-loading of this class. Since
+     * the class is used extensively in error handling, it is prudent to
+     * pre-load it to avoid any failure to load this class masking the true
+     * problem during error handling.
+     */
+    public static void preload() {
+        // NO-OP
+    }
 }
diff --git a/java/org/apache/tomcat/util/buf/StringUtils.java b/java/org/apache/tomcat/util/buf/StringUtils.java
new file mode 100644
index 00000000..33bb38b9
--- /dev/null
+++ b/java/org/apache/tomcat/util/buf/StringUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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.tomcat.util.buf;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Utility methods to build a separated list from a given set (not
+ * java.util.Set) of inputs and return that list as a string or append it to an
+ * existing StringBuilder.
+ */
+public final class StringUtils {
+
+    private static final String EMPTY_STRING = "";
+
+    private StringUtils() {
+        // Utility class
+    }
+
+
+    public static String join(String[] array) {
+        return join(Arrays.asList(array));
+    }
+
+
+    public static void join(String[] array, char separator, StringBuilder sb) {
+        join(Arrays.asList(array), separator, sb);
+    }
+
+
+    public static String join(Collection<String> collection) {
+        return join(collection, ',');
+    }
+
+
+    public static String join(Collection<String> collection, char separator) {
+        // Shortcut
+        if (collection.isEmpty()) {
+            return EMPTY_STRING;
+        }
+
+        StringBuilder result = new StringBuilder();
+        join(collection, separator, result);
+        return result.toString();
+    }
+
+
+    public static void join(Iterable<String> iterable, char separator, StringBuilder sb) {
+        join(iterable, separator,
+                new Function<String>() {@Override public String apply(String t) { return t; }}, sb);
+    }
+
+
+    public static <T> void join(T[] array, char separator, Function<T> function,
+            StringBuilder sb) {
+        join(Arrays.asList(array), separator, function, sb);
+    }
+
+
+    public static <T> void join(Iterable<T> iterable, char separator, Function<T> function,
+            StringBuilder sb) {
+        boolean first = true;
+        for (T value : iterable) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(separator);
+            }
+            sb.append(function.apply(value));
+        }
+    }
+
+
+    public interface Function<T> {
+        public String apply(T t);
+    }
+}
diff --git a/java/org/apache/tomcat/util/http/HttpMessages.java b/java/org/apache/tomcat/util/http/HttpMessages.java
new file mode 100644
index 00000000..1e4431db
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/HttpMessages.java
@@ -0,0 +1,150 @@
+/*
+ *  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.tomcat.util.http;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Handle (internationalized) HTTP messages.
+ *
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Jason Hunter [jch@eng.sun.com]
+ * @author Harish Prabandham
+ * @author costin@eng.sun.com
+ */
+public class HttpMessages {
+
+    private static final Map<Locale,HttpMessages> instances =
+            new ConcurrentHashMap<>();
+
+    private static final HttpMessages DEFAULT = new HttpMessages(
+            StringManager.getManager("org.apache.tomcat.util.http.res",
+                    Locale.getDefault()));
+
+
+    // XXX move message resources in this package
+    private final StringManager sm;
+
+    private String st_200 = null;
+    private String st_302 = null;
+    private String st_400 = null;
+    private String st_404 = null;
+    private String st_500 = null;
+
+    private HttpMessages(StringManager sm) {
+        this.sm = sm;
+    }
+
+
+    /**
+     * Get the status string associated with a status code. Common messages are
+     * cached.
+     *
+     * @param status The HTTP status code to retrieve the message for
+     *
+     * @return The HTTP status string that conforms to the requirements of the
+     *         HTTP specification
+     */
+    public String getMessage(int status) {
+        // method from Response.
+
+        // Does HTTP requires/allow international messages or
+        // are pre-defined? The user doesn't see them most of the time
+        switch( status ) {
+        case 200:
+            if(st_200 == null ) {
+                st_200 = sm.getString("sc.200");
+            }
+            return st_200;
+        case 302:
+            if(st_302 == null ) {
+                st_302 = sm.getString("sc.302");
+            }
+            return st_302;
+        case 400:
+            if(st_400 == null ) {
+                st_400 = sm.getString("sc.400");
+            }
+            return st_400;
+        case 404:
+            if(st_404 == null ) {
+                st_404 = sm.getString("sc.404");
+            }
+            return st_404;
+        case 500:
+            if (st_500 == null) {
+                st_500 = sm.getString("sc.500");
+            }
+            return st_500;
+        }
+        return sm.getString("sc."+ status);
+    }
+
+
+    public static HttpMessages getInstance(Locale locale) {
+        HttpMessages result = instances.get(locale);
+        if (result == null) {
+            StringManager sm = StringManager.getManager(
+                    "org.apache.tomcat.util.http.res", locale);
+            if (Locale.getDefault().equals(sm.getLocale())) {
+                result = DEFAULT;
+            } else {
+                result = new HttpMessages(sm);
+            }
+            instances.put(locale, result);
+        }
+        return result;
+    }
+
+
+    /**
+     * Is the provided message safe to use in an HTTP header. Safe messages must
+     * meet the requirements of RFC2616 - i.e. must consist only of TEXT.
+     *
+     * @param msg   The message to test
+     * @return      <code>true</code> if the message is safe to use in an HTTP
+     *              header else <code>false</code>
+     */
+    public static boolean isSafeInHttpHeader(String msg) {
+        // Nulls are fine. It is up to the calling code to address any NPE
+        // concerns
+        if (msg == null) {
+            return true;
+        }
+
+        // Reason-Phrase is defined as *<TEXT, excluding CR, LF>
+        // TEXT is defined as any OCTET except CTLs, but including LWS
+        // OCTET is defined as an 8-bit sequence of data
+        // CTL is defined as octets 0-31 and 127
+        // LWS, if we exclude CR LF pairs, is defined as SP or HT (32, 9)
+        final int len = msg.length();
+        for (int i = 0; i < len; i++) {
+            char c = msg.charAt(i);
+            if (32 <= c && c <= 126 || 128 <= c && c <= 255 || c == 9) {
+                continue;
+            }
+            return false;
+        }
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/java/org/apache/tomcat/util/http/LocalStrings.properties b/java/org/apache/tomcat/util/http/LocalStrings.properties
index e7893d25..4b91257c 100644
--- a/java/org/apache/tomcat/util/http/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/http/LocalStrings.properties
@@ -22,7 +22,7 @@ parameters.invalidChunk=Invalid chunk starting at byte [{0}] and ending at byte
 parameters.maxCountFail=More than the maximum number of request parameters (GET plus POST) for a single request ([{0}]) were detected. Any parameters beyond this limit have been ignored. To change this limit, set the maxParameterCount attribute on the Connector.
 parameters.maxCountFail.fallToDebug=\n Note: further occurrences of this error will be logged at DEBUG level.
 parameters.multipleDecodingFail=Character decoding failed. A total of [{0}] failures were detected but only the first was logged. Enable debug level logging for this logger to log all failures.
-parameters.noequal=Parameter starting at position [{0}] and ending at position [{1}] with a value of [{0}] was not followed by an '=' character
+parameters.noequal=Parameter starting at position [{0}] and ending at position [{1}] with a value of [{0}] was not followed by an ''='' character
 parameters.fallToDebug=\n Note: further occurrences of Parameter errors will be logged at DEBUG level.
 
 cookies.invalidCookieToken=Cookies: Invalid cookie. Value not a token or quoted value
diff --git a/java/org/apache/tomcat/util/http/Parameters.java b/java/org/apache/tomcat/util/http/Parameters.java
index 2dc6249a..72fb2589 100644
--- a/java/org/apache/tomcat/util/http/Parameters.java
+++ b/java/org/apache/tomcat/util/http/Parameters.java
@@ -31,6 +31,7 @@ import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.buf.UDecoder;
 import org.apache.tomcat.util.log.UserDataHelper;
 import org.apache.tomcat.util.res.StringManager;
@@ -513,10 +514,7 @@ public final class Parameters {
         StringBuilder sb = new StringBuilder();
         for (Map.Entry<String, ArrayList<String>> e : paramHashValues.entrySet()) {
             sb.append(e.getKey()).append('=');
-            ArrayList<String> values = e.getValue();
-            for (String value : values) {
-                sb.append(value).append(',');
-            }
+            StringUtils.join(e.getValue(), ',', sb);
             sb.append('\n');
         }
         return sb.toString();
diff --git a/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java b/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java
index faa6ac4a..a0e54f3a 100644
--- a/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java
+++ b/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java
@@ -143,14 +143,14 @@ public class Rfc6265CookieProcessor extends CookieProcessorBase {
         String domain = cookie.getDomain();
         if (domain != null && domain.length() > 0) {
             validateDomain(domain);
-            header.append(";domain=");
+            header.append("; Domain=");
             header.append(domain);
         }
 
         String path = cookie.getPath();
         if (path != null && path.length() > 0) {
             validatePath(path);
-            header.append(";path=");
+            header.append("; Path=");
             header.append(path);
         }
 
diff --git a/java/org/apache/tomcat/util/http/res/LocalStrings.properties b/java/org/apache/tomcat/util/http/res/LocalStrings.properties
new file mode 100644
index 00000000..0fe0bbc5
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/res/LocalStrings.properties
@@ -0,0 +1,78 @@
+# 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.
+
+# HttpMessages. The values in this file will be used in HTTP headers and as such
+# may only contain TEXT as defined by RFC 2616
+# All status codes registered with IANA can be found at
+# http://www.iana.org/assignments/http-status-codes/http-status-codes.xml
+# The list might be kept in sync with the one in
+# java/org/apache/catalina/valves/LocalStrings.properties
+sc.100=Continue
+sc.101=Switching Protocols
+sc.102=Processing
+sc.200=OK
+sc.201=Created
+sc.202=Accepted
+sc.203=Non-Authoritative Information
+sc.204=No Content
+sc.205=Reset Content
+sc.206=Partial Content
+sc.207=Multi-Status
+sc.208=Already Reported
+sc.226=IM Used
+sc.300=Multiple Choices
+sc.301=Moved Permanently
+sc.302=Found
+sc.303=See Other
+sc.304=Not Modified
+sc.305=Use Proxy
+sc.307=Temporary Redirect
+sc.308=Permanent Redirect
+sc.400=Bad Request
+sc.401=Unauthorized
+sc.402=Payment Required
+sc.403=Forbidden
+sc.404=Not Found
+sc.405=Method Not Allowed
+sc.406=Not Acceptable
+sc.407=Proxy Authentication Required
+sc.408=Request Timeout
+sc.409=Conflict
+sc.410=Gone
+sc.411=Length Required
+sc.412=Precondition Failed
+sc.413=Request Entity Too Large
+sc.414=Request-URI Too Long
+sc.415=Unsupported Media Type
+sc.416=Requested Range Not Satisfiable
+sc.417=Expectation Failed
+sc.422=Unprocessable Entity
+sc.423=Locked
+sc.424=Failed Dependency
+sc.426=Upgrade Required
+sc.428=Precondition Required
+sc.429=Too Many Requests
+sc.431=Request Header Fields Too Large
+sc.500=Internal Server Error
+sc.501=Not Implemented
+sc.502=Bad Gateway
+sc.503=Service Unavailable
+sc.504=Gateway Timeout
+sc.505=HTTP Version Not Supported
+sc.506=Variant Also Negotiates (Experimental)
+sc.507=Insufficient Storage
+sc.508=Loop Detected
+sc.510=Not Extended
+sc.511=Network Authentication Required
\ No newline at end of file
diff --git a/java/org/apache/tomcat/util/http/res/LocalStrings_es.properties b/java/org/apache/tomcat/util/http/res/LocalStrings_es.properties
new file mode 100644
index 00000000..30bb2e55
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/res/LocalStrings_es.properties
@@ -0,0 +1,61 @@
+# 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.
+# HttpMessages. The values in this file will be used in HTTP headers and as such
+# may only contain TEXT as defined by RFC 2616
+sc.100 = Continuar
+sc.101 = Cambiando Protocolos
+sc.200 = OK
+sc.201 = Creado
+sc.202 = Aceptado
+sc.203 = Informaci\u00F3n No-Autorizativa
+sc.204 = Sin Contenido
+sc.205 = Reponer Contenido
+sc.206 = Contenido Parcial
+sc.207 = Multi-Estado
+sc.300 = M\u00FAltiples Elecciones
+sc.301 = Movido permanentemente
+sc.302 = Movido temporalmente
+sc.303 = Mirar Otro
+sc.304 = No Modificado
+sc.305 = Usar Proxy
+sc.307 = Redirecci\u00F3n Temporal
+sc.400 = Petici\u00F3n incorrecta
+sc.401 = No Autorizado
+sc.402 = Pago requerido
+sc.403 = Prohibido
+sc.404 = No Encontrado
+sc.405 = M\u00E9todo No Permitido
+sc.406 = No Aceptable
+sc.407 = Autentificaci\u00F3n Proxy Requerida
+sc.408 = Request Caducada
+sc.409 = Conflicto
+sc.410 = Ido
+sc.411 = Longitud Requerida
+sc.412 = Precondici\u00F3n Fallada
+sc.413 = Entidad de Request Demasiado Grande
+sc.414 = Request-URI Demasiado Larga
+sc.415 = Tipo de Medio No Soportado
+sc.416 = El Rango Pedido No Ser Satisfecho
+sc.417 = Expectativa Fallada
+sc.422 = Entidad Improcesable
+sc.423 = Bloqueado
+sc.424 = Dependencia Fallida
+sc.500 = Error Interno del Servidor
+sc.501 = No Implementado
+sc.502 = Pasarela Incorrecta
+sc.503 = Servicio no Disponible
+sc.504 = Pasarela Caducada
+sc.505 = Versi\u00F3n de HTTP No Soportada
+sc.507 = Almacenaje Insuficiente
\ No newline at end of file
diff --git a/java/org/apache/tomcat/util/http/res/LocalStrings_fr.properties b/java/org/apache/tomcat/util/http/res/LocalStrings_fr.properties
new file mode 100644
index 00000000..8df5f107
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/res/LocalStrings_fr.properties
@@ -0,0 +1,61 @@
+# 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.
+
+# HttpMessages. The values in this file will be used in HTTP headers and as such
+# may only contain TEXT as defined by RFC 2616
+sc.100=Continuer
+sc.101=Changement de Protocols
+sc.200=OK
+sc.201=Cr\u00e9e
+sc.202=Accept\u00e9
+sc.203=Information Sans-Autorit\u00e9
+sc.204=Pas de Contenu
+sc.205=Remise \u00e0 Z\u00e9ro de Contenu
+sc.206=Contenu Partiel
+sc.207=Etat Multiple
+sc.300=Choix Multiples
+sc.301=D\u00e9plac\u00e9 de fa\u00e7on Permanente
+sc.302=D\u00e9plac\u00e9 Temporairement
+sc.303=Voir Autre
+sc.304=Non Modifi\u00e9
+sc.305=Utilisation de Relais
+sc.307=Redirection Temporaire
+sc.400=Mauvaise Requ\u00eate
+sc.401=Non-Autoris\u00e9
+sc.402=Paiement N\u00e9cessaire
+sc.403=Interdit
+sc.404=Introuvable
+sc.405=M\u00e9thode Non Autoris\u00e9e
+sc.406=Inacceptable
+sc.407=Authentification de Relais N\u00e9cessaire
+sc.408=D\u00e9passement de D\u00e9lais pour la Requ\u00eate
+sc.409=Conflit
+sc.410=Parti
+sc.411=Taille Demand\u00e9e
+sc.412=Echec de Pr\u00e9-condition
+sc.413=Entit\u00e9 de Requ\u00eate Trop Grande
+sc.414=URI de Requ\u00eate Trop Grande
+sc.415=Type de Support Non Support\u00e9
+sc.416=Etendue de Requ\u00eate Irr\u00e9alisable
+sc.417=Echec d'Attente
+sc.422=Entit\u00e9 Ing\u00e9rable
+sc.424=Echec de D\u00e9pendance
+sc.500=Erreur Interne de Servlet
+sc.501=Non Impl\u00e9ment\u00e9
+sc.502=Mauvaise Passerelle
+sc.503=Service Indisponible
+sc.504=D\u00e9passement de D\u00e9lais pour la Passerelle
+sc.505=Version HTTP Non Support\u00e9e
+sc.507=Stockage Insuffisant
\ No newline at end of file
diff --git a/java/org/apache/tomcat/util/modeler/ManagedBean.java b/java/org/apache/tomcat/util/modeler/ManagedBean.java
index f5613270..f4e8531a 100644
--- a/java/org/apache/tomcat/util/modeler/ManagedBean.java
+++ b/java/org/apache/tomcat/util/modeler/ManagedBean.java
@@ -38,6 +38,9 @@ import javax.management.ReflectionException;
 import javax.management.RuntimeOperationsException;
 import javax.management.ServiceNotFoundException;
 
+import org.apache.tomcat.util.buf.StringUtils;
+import org.apache.tomcat.util.buf.StringUtils.Function;
+
 
 /**
  * <p>Internal configuration information for a managed bean (MBean)
@@ -565,26 +568,17 @@ public class ManagedBean implements java.io.Serializable {
     private String createOperationKey(OperationInfo operation) {
         StringBuilder key = new StringBuilder(operation.getName());
         key.append('(');
-        for (ParameterInfo parameterInfo: operation.getSignature()) {
-            key.append(parameterInfo.getType());
-            // Note: A trailing ',' does not matter in this case
-            key.append(',');
-        }
+        StringUtils.join(operation.getSignature(), ',', new Function<ParameterInfo>() {
+            @Override public String apply(ParameterInfo t) { return t.getType(); }}, key);
         key.append(')');
-
         return key.toString();
     }
 
 
-    private String createOperationKey(String methodName,
-            String[] parameterTypes) {
+    private String createOperationKey(String methodName, String[] parameterTypes) {
         StringBuilder key = new StringBuilder(methodName);
         key.append('(');
-        for (String parameter: parameterTypes) {
-            key.append(parameter);
-            // Note: A trailing ',' does not matter in this case
-            key.append(',');
-        }
+        StringUtils.join(parameterTypes, ',', key);
         key.append(')');
 
         return key.toString();
diff --git a/java/org/apache/tomcat/util/net/AbstractEndpoint.java b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
index b0e677ed..bc461b66 100644
--- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
@@ -208,7 +208,7 @@ public abstract class AbstractEndpoint<S> {
             throw new IllegalArgumentException(sm.getString("endpoint.noSslHostName"));
         }
         sslHostConfig.setConfigType(getSslConfigType());
-        if (bindState != BindState.UNBOUND) {
+        if (bindState != BindState.UNBOUND && isSSLEnabled()) {
             try {
                 createSSLContext(sslHostConfig);
             } catch (Exception e) {
@@ -780,14 +780,13 @@ public abstract class AbstractEndpoint<S> {
      */
     protected void unlockAccept() {
         // Only try to unlock the acceptor if it is necessary
-        boolean unlockRequired = false;
+        int unlocksRequired = 0;
         for (Acceptor acceptor : acceptors) {
             if (acceptor.getState() == AcceptorState.RUNNING) {
-                unlockRequired = true;
-                break;
+                unlocksRequired++;
             }
         }
-        if (!unlockRequired) {
+        if (unlocksRequired == 0) {
             return;
         }
 
@@ -796,18 +795,17 @@ public abstract class AbstractEndpoint<S> {
         try {
             localAddress = getLocalAddress();
         } catch (IOException ioe) {
-            // TODO i18n
-            getLog().debug("Unable to determine local address for " + getName(), ioe);
+            getLog().debug(sm.getString("endpoint.debug.unlock.localFail", getName()), ioe);
         }
         if (localAddress == null) {
-            // TODO i18n
-            getLog().warn("Failed to unlock acceptor for " + getName() + " because the local address was not available.");
+            getLog().warn(sm.getString("endpoint.debug.unlock.localNone", getName()));
             return;
         }
 
         try {
             unlockAddress = getUnlockAddress(localAddress);
 
+            for (int i = 0; i < unlocksRequired; i++) {
                 try (java.net.Socket s = new java.net.Socket()) {
                     int stmo = 2 * 1000;
                     int utmo = 2 * 1000;
@@ -837,7 +835,8 @@ public abstract class AbstractEndpoint<S> {
                     if (getLog().isDebugEnabled()) {
                         getLog().debug("Socket unlock completed for:" + unlockAddress);
                     }
-
+                }
+            }
             // Wait for upto 1000ms acceptor threads to unlock
             long waitLeft = 1000;
             for (Acceptor acceptor : acceptors) {
@@ -847,10 +846,9 @@ public abstract class AbstractEndpoint<S> {
                     waitLeft -= 50;
                 }
             }
-            }
         } catch(Exception e) {
             if (getLog().isDebugEnabled()) {
-                getLog().debug(sm.getString("endpoint.debug.unlock", "" + getPort()), e);
+                getLog().debug(sm.getString("endpoint.debug.unlock.fail", "" + getPort()), e);
             }
         }
     }
diff --git a/java/org/apache/tomcat/util/net/AprEndpoint.java b/java/org/apache/tomcat/util/net/AprEndpoint.java
index d89ad975..5e5ba370 100644
--- a/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -1695,15 +1695,21 @@ public class AprEndpoint extends AbstractEndpoint<Long> implements SNICallBack {
                             pollerSpace[i] += rv;
                             connectionCount.addAndGet(-rv);
                             for (int n = 0; n < rv; n++) {
-                                long timeout = timeouts.remove(desc[n*2+1]);
-                                AprSocketWrapper wrapper = connections.get(
-                                        Long.valueOf(desc[n*2+1]));
                                 if (getLog().isDebugEnabled()) {
                                     log.debug(sm.getString(
                                             "endpoint.debug.pollerProcess",
                                             Long.valueOf(desc[n*2+1]),
                                             Long.valueOf(desc[n*2])));
                                 }
+                                long timeout = timeouts.remove(desc[n*2+1]);
+                                AprSocketWrapper wrapper = connections.get(
+                                        Long.valueOf(desc[n*2+1]));
+                                if (wrapper == null) {
+                                    // Socket was closed in another thread while still in
+                                    // the Poller but wasn't removed from the Poller before
+                                    // new data arrived.
+                                    continue;
+                                }
                                 wrapper.pollerFlags = wrapper.pollerFlags & ~((int) desc[n*2]);
                                 // Check for failed sockets and hand this socket off to a worker
                                 if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
@@ -2138,21 +2144,34 @@ public class AprEndpoint extends AbstractEndpoint<Long> implements SNICallBack {
                             state.length -= nw;
                             if (state.length == 0) {
                                 remove(state);
-                                if (state.keepAlive) {
+                                switch (state.keepAliveState) {
+                                case NONE: {
+                                    // Close the socket since this is
+                                    // the end of the not keep-alive request.
+                                    closeSocket(state.socket);
+                                    break;
+                                }
+                                case PIPELINED: {
                                     // Destroy file descriptor pool, which should close the file
                                     Pool.destroy(state.fdpool);
-                                    Socket.timeoutSet(state.socket,
-                                            getSoTimeout() * 1000);
-                                    // If all done put the socket back in the
-                                    // poller for processing of further requests
-                                    getPoller().add(
-                                            state.socket, getKeepAliveTimeout(),
-                                            Poll.APR_POLLIN);
-                                } else {
-                                    // Close the socket since this is
-                                    // the end of not keep-alive request.
+                                    Socket.timeoutSet(state.socket, getSoTimeout() * 1000);
+                                    // Process the pipelined request data
+                                    if (!processSocket(state.socket, SocketEvent.OPEN_READ)) {
                                         closeSocket(state.socket);
                                     }
+                                    break;
+                                }
+                                case OPEN: {
+                                    // Destroy file descriptor pool, which should close the file
+                                    Pool.destroy(state.fdpool);
+                                    Socket.timeoutSet(state.socket, getSoTimeout() * 1000);
+                                    // Put the socket back in the poller for
+                                    // processing of further requests
+                                    getPoller().add(state.socket, getKeepAliveTimeout(),
+                                            Poll.APR_POLLIN);
+                                    break;
+                                }
+                                }
                             }
                         }
                     } else if (rv < 0) {
diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties b/java/org/apache/tomcat/util/net/LocalStrings.properties
index 6acf4bbd..7b65b6f9 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings.properties
@@ -37,7 +37,9 @@ endpoint.debug.pollerRemoved=Removed [{0}] from poller
 endpoint.debug.socket=socket [{0}]
 endpoint.debug.socketCloseFail=Failed to close socket
 endpoint.debug.socketTimeout=Timing out [{0}]
-endpoint.debug.unlock=Caught exception trying to unlock accept on port {0}
+endpoint.debug.unlock.fail=Caught exception trying to unlock accept on port [{0}]
+endpoint.debug.unlock.localFail=Unable to determine local address for [{0}]
+endpoint.debug.unlock.localNone=Failed to unlock acceptor for [{0}] because the local address was not available
 endpoint.accept.fail=Socket accept failed
 endpoint.alpn.fail=Failed to configure endpoint for ALPN using {0}
 endpoint.alpn.negotiated=Negotiated [{0}] protocol using ALPN
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_es.properties b/java/org/apache/tomcat/util/net/LocalStrings_es.properties
index fa530ba1..0df35588 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_es.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_es.properties
@@ -15,7 +15,7 @@
 # net resources
 endpoint.err.handshake = Acuerdo fallido
 endpoint.err.unexpected = Error inesperado al procesar conector
-endpoint.debug.unlock = Excepci\u00F3n cogida intentando desbloquear aceptaci\u00F3n en puerto {0}
+endpoint.debug.unlock.fail = Excepci\u00F3n cogida intentando desbloquear aceptaci\u00F3n en puerto {0}
 endpoint.err.close = Excepci\u00F3n cogida intentando cerrar conector
 endpoint.init.bind = Ligado de conector fall\u00F3\: [{0}] {1}
 endpoint.init.listen = Escucha de conector fall\u00F3\: [{0}] {1}
diff --git a/java/org/apache/tomcat/util/net/Nio2Endpoint.java b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
index 73605e8c..a8231bf4 100644
--- a/java/org/apache/tomcat/util/net/Nio2Endpoint.java
+++ b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
@@ -536,17 +536,24 @@ public class Nio2Endpoint extends AbstractJsseEndpoint<Nio2Channel> {
                         } catch (IOException e) {
                             // Ignore
                         }
-                        if (attachment.keepAlive) {
-                            if (!isInline()) {
-                                awaitBytes();
-                            } else {
+                        if (isInline()) {
                             attachment.doneInline = true;
-                            }
                         } else {
-                            if (!isInline()) {
-                                getEndpoint().processSocket(Nio2SocketWrapper.this, SocketEvent.DISCONNECT, false);
-                            } else {
-                                attachment.doneInline = true;
+                            switch (attachment.keepAliveState) {
+                            case NONE: {
+                                getEndpoint().processSocket(Nio2SocketWrapper.this,
+                                        SocketEvent.DISCONNECT, false);
+                                break;
+                            }
+                            case PIPELINED: {
+                                getEndpoint().processSocket(Nio2SocketWrapper.this,
+                                        SocketEvent.OPEN_READ, true);
+                                break;
+                            }
+                            case OPEN: {
+                                awaitBytes();
+                                break;
+                            }
                             }
                         }
                         return;
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java
index 0439844d..b6d9bed6 100644
--- a/java/org/apache/tomcat/util/net/NioEndpoint.java
+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
@@ -924,17 +924,31 @@ public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
                     // responsible for registering the socket for the
                     // appropriate event(s) if sendfile completes.
                     if (!calledByProcessor) {
-                        if (sd.keepAlive) {
+                        switch (sd.keepAliveState) {
+                        case NONE: {
                             if (log.isDebugEnabled()) {
-                                log.debug("Connection is keep alive, registering back for OP_READ");
+                                log.debug("Send file connection is being closed");
                             }
-                            reg(sk,socketWrapper,SelectionKey.OP_READ);
-                        } else {
+                            close(sc, sk);
+                            break;
+                        }
+                        case PIPELINED: {
                             if (log.isDebugEnabled()) {
-                                log.debug("Send file connection is being closed");
+                                log.debug("Connection is keep alive, processing pipe-lined data");
                             }
+                            if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {
                                 close(sc, sk);
                             }
+                            break;
+                        }
+                        case OPEN: {
+                            if (log.isDebugEnabled()) {
+                                log.debug("Connection is keep alive, registering back for OP_READ");
+                            }
+                            reg(sk,socketWrapper,SelectionKey.OP_READ);
+                            break;
+                        }
+                        }
                     }
                     return SendfileState.DONE;
                 } else {
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java b/java/org/apache/tomcat/util/net/SSLHostConfig.java
index bec3adf7..76f02b2e 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -131,6 +131,10 @@ public class SSLHostConfig implements Serializable {
     }
 
 
+    // Expose in String form for JMX
+    public String getConfigType() {
+        return configType.name();
+    }
     public void setConfigType(Type configType) {
         this.configType = configType;
         if (configType == Type.EITHER) {
@@ -253,6 +257,10 @@ public class SSLHostConfig implements Serializable {
     // TODO: This certificate setter can be removed once it is no longer
     // necessary to support the old configuration attributes (Tomcat 10?).
 
+    public String getCertificateKeyPassword() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateKeyPassword();
+    }
     public void setCertificateKeyPassword(String certificateKeyPassword) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateKeyPassword(certificateKeyPassword);
@@ -446,30 +454,50 @@ public class SSLHostConfig implements Serializable {
     // TODO: These certificate setters can be removed once it is no longer
     // necessary to support the old configuration attributes (Tomcat 10?).
 
+    public String getCertificateKeyAlias() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateKeyAlias();
+    }
     public void setCertificateKeyAlias(String certificateKeyAlias) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateKeyAlias(certificateKeyAlias);
     }
 
 
+    public String getCertificateKeystoreFile() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateKeystoreFile();
+    }
     public void setCertificateKeystoreFile(String certificateKeystoreFile) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateKeystoreFile(certificateKeystoreFile);
     }
 
 
+    public String getCertificateKeystorePassword() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateKeystorePassword();
+    }
     public void setCertificateKeystorePassword(String certificateKeystorePassword) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateKeystorePassword(certificateKeystorePassword);
     }
 
 
+    public String getCertificateKeystoreProvider() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateKeystoreProvider();
+    }
     public void setCertificateKeystoreProvider(String certificateKeystoreProvider) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateKeystoreProvider(certificateKeystoreProvider);
     }
 
 
+    public String getCertificateKeystoreType() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateKeystoreType();
+    }
     public void setCertificateKeystoreType(String certificateKeystoreType) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateKeystoreType(certificateKeystoreType);
@@ -655,18 +683,30 @@ public class SSLHostConfig implements Serializable {
     // TODO: These certificate setters can be removed once it is no longer
     // necessary to support the old configuration attributes (Tomcat 10?).
 
+    public String getCertificateChainFile() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateChainFile();
+    }
     public void setCertificateChainFile(String certificateChainFile) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateChainFile(certificateChainFile);
     }
 
 
+    public String getCertificateFile() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateFile();
+    }
     public void setCertificateFile(String certificateFile) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateFile(certificateFile);
     }
 
 
+    public String getCertificateKeyFile() {
+        registerDefaultCertificate();
+        return defaultCertificate.getCertificateKeyFile();
+    }
     public void setCertificateKeyFile(String certificateKeyFile) {
         registerDefaultCertificate();
         defaultCertificate.setCertificateKeyFile(certificateKeyFile);
diff --git a/java/org/apache/tomcat/util/net/SendfileDataBase.java b/java/org/apache/tomcat/util/net/SendfileDataBase.java
index fc89b119..ca0ee3b2 100644
--- a/java/org/apache/tomcat/util/net/SendfileDataBase.java
+++ b/java/org/apache/tomcat/util/net/SendfileDataBase.java
@@ -21,10 +21,10 @@ public abstract class SendfileDataBase {
     /**
      * Is the current request being processed on a keep-alive connection? This
      * determines if the socket is closed once the send file completes or if
-     * processing continues with the next request on the connection (or waiting
-     * for that next request to arrive).
+     * processing continues with the next request on the connection or waiting
+     * for that next request to arrive.
      */
-    public boolean keepAlive;
+    public SendfileKeepAliveState keepAliveState = SendfileKeepAliveState.NONE;
 
     /**
      * The full path to the file that contains the data to be written to the
diff --git a/java/org/apache/tomcat/util/net/SendfileKeepAliveState.java b/java/org/apache/tomcat/util/net/SendfileKeepAliveState.java
new file mode 100644
index 00000000..b27a9f14
--- /dev/null
+++ b/java/org/apache/tomcat/util/net/SendfileKeepAliveState.java
@@ -0,0 +1,39 @@
+/*
+ *  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.tomcat.util.net;
+
+public enum SendfileKeepAliveState {
+
+    /**
+     * Keep-alive is not in use. The socket can be closed when the response has
+     * been written.
+     */
+    NONE,
+
+    /**
+     * Keep-alive is in use and there is pipelined data in the input buffer to
+     * be read as soon as the current response has been written.
+     */
+    PIPELINED,
+
+    /**
+     * Keep-alive is in use. The socket should be added to the poller (or
+     * equivalent) to await more data as soon as the current response has been
+     * written.
+     */
+    OPEN
+}
diff --git a/java/org/apache/tomcat/util/net/SocketWrapperBase.java b/java/org/apache/tomcat/util/net/SocketWrapperBase.java
index e0d25c28..5a3b6566 100644
--- a/java/org/apache/tomcat/util/net/SocketWrapperBase.java
+++ b/java/org/apache/tomcat/util/net/SocketWrapperBase.java
@@ -721,9 +721,9 @@ public abstract class SocketWrapperBase<E> {
 
     /**
      * Starts the sendfile process. It is expected that if the sendfile process
-     * does not complete during this call that the caller <b>will not</b> add
-     * the socket to the poller (or equivalent). That is the responsibility of
-     * this method.
+     * does not complete during this call and does not report an error, that the
+     * caller <b>will not</b> add the socket to the poller (or equivalent). That
+     * is the responsibility of this method.
      *
      * @param sendfileData Data representing the file to send
      *
diff --git a/java/org/apache/tomcat/util/net/openssl/ciphers/Cipher.java b/java/org/apache/tomcat/util/net/openssl/ciphers/Cipher.java
index 717b7156..5f38973c 100644
--- a/java/org/apache/tomcat/util/net/openssl/ciphers/Cipher.java
+++ b/java/org/apache/tomcat/util/net/openssl/ciphers/Cipher.java
@@ -2728,7 +2728,7 @@ public enum Cipher {
             Authentication.ECDH,
             Encryption.RC4,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.MEDIUM,
             false,
@@ -2796,7 +2796,7 @@ public enum Cipher {
             Authentication.ECDSA,
             Encryption.eNULL,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.STRONG_NONE,
             true,
@@ -2830,7 +2830,7 @@ public enum Cipher {
             Authentication.ECDSA,
             Encryption.TRIPLE_DES,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.MEDIUM,
             true,
@@ -2847,7 +2847,7 @@ public enum Cipher {
             Authentication.ECDSA,
             Encryption.AES128,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.HIGH,
             true,
@@ -2864,7 +2864,7 @@ public enum Cipher {
             Authentication.ECDSA,
             Encryption.AES256,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.HIGH,
             true,
@@ -2966,7 +2966,7 @@ public enum Cipher {
             Authentication.RSA,
             Encryption.eNULL,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.STRONG_NONE,
             true,
@@ -2983,7 +2983,7 @@ public enum Cipher {
             Authentication.RSA,
             Encryption.RC4,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.MEDIUM,
             false,
@@ -3000,7 +3000,7 @@ public enum Cipher {
             Authentication.RSA,
             Encryption.TRIPLE_DES,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.MEDIUM,
             true,
@@ -3017,7 +3017,7 @@ public enum Cipher {
             Authentication.RSA,
             Encryption.AES128,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.HIGH,
             true,
@@ -3034,7 +3034,7 @@ public enum Cipher {
             Authentication.RSA,
             Encryption.AES256,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.HIGH,
             true,
@@ -3051,7 +3051,7 @@ public enum Cipher {
             Authentication.aNULL,
             Encryption.eNULL,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.STRONG_NONE,
             true,
@@ -3068,7 +3068,7 @@ public enum Cipher {
             Authentication.aNULL,
             Encryption.RC4,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.MEDIUM,
             false,
@@ -3085,7 +3085,7 @@ public enum Cipher {
             Authentication.aNULL,
             Encryption.TRIPLE_DES,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.MEDIUM,
             true,
@@ -3102,7 +3102,7 @@ public enum Cipher {
             Authentication.aNULL,
             Encryption.AES128,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.HIGH,
             true,
@@ -3119,7 +3119,7 @@ public enum Cipher {
             Authentication.aNULL,
             Encryption.AES256,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.HIGH,
             true,
@@ -3564,7 +3564,7 @@ public enum Cipher {
             Authentication.PSK,
             Encryption.RC4,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.MEDIUM,
             false,
@@ -3581,7 +3581,7 @@ public enum Cipher {
             Authentication.PSK,
             Encryption.TRIPLE_DES,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.MEDIUM,
             true,
@@ -3598,7 +3598,7 @@ public enum Cipher {
             Authentication.PSK,
             Encryption.AES128,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.HIGH,
             true,
@@ -3615,7 +3615,7 @@ public enum Cipher {
             Authentication.PSK,
             Encryption.AES256,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.HIGH,
             true,
@@ -3664,7 +3664,7 @@ public enum Cipher {
             Authentication.PSK,
             Encryption.eNULL,
             MessageDigest.SHA1,
-            Protocol.SSLv3,
+            Protocol.TLSv1,
             false,
             EncryptionLevel.STRONG_NONE,
             true,
diff --git a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
index b6b993a7..846cd4ff 100644
--- a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
+++ b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
@@ -67,6 +67,7 @@ import javax.websocket.WebSocketContainer;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.InstanceManager;
+import org.apache.tomcat.util.buf.StringUtils;
 import org.apache.tomcat.util.codec.binary.Base64;
 import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
 import org.apache.tomcat.util.res.StringManager;
@@ -628,21 +629,13 @@ public class WsWebSocketContainer implements WebSocketContainer, BackgroundProce
 
 
     private static void addHeader(ByteBuffer result, String key, List<String> values) {
-        StringBuilder sb = new StringBuilder();
-
-        Iterator<String> iter = values.iterator();
-        if (!iter.hasNext()) {
+        if (values.isEmpty()) {
             return;
         }
-        sb.append(iter.next());
-        while (iter.hasNext()) {
-            sb.append(',');
-            sb.append(iter.next());
-        }
 
         result.put(key.getBytes(StandardCharsets.ISO_8859_1));
         result.put(": ".getBytes(StandardCharsets.ISO_8859_1));
-        result.put(sb.toString().getBytes(StandardCharsets.ISO_8859_1));
+        result.put(StringUtils.join(values).getBytes(StandardCharsets.ISO_8859_1));
         result.put(crlf);
     }
 
diff --git a/modules/jdbc-pool/doc/jdbc-pool.xml b/modules/jdbc-pool/doc/jdbc-pool.xml
index 3f4043c6..f675de4f 100644
--- a/modules/jdbc-pool/doc/jdbc-pool.xml
+++ b/modules/jdbc-pool/doc/jdbc-pool.xml
@@ -551,6 +551,13 @@
          The default value is <code>false</code>.
       </p>
     </attribute>
+    <attribute name="useStatementFacade" required="false">
+      <p>(boolean) Set this to true if you wish to wrap statements in order to
+         enable <code>equals()</code> and <code>hashCode()</code> methods to be
+         called on the closed statements if any statement proxy is set.
+         Default value is <code>true</code>.
+      </p>
+    </attribute>
 
   </attributes>
   </subsection>
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
index 856501da..8a69d699 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java
@@ -327,7 +327,10 @@ public class ConnectionPool {
                 next = next.getNext();
             }
         }
-
+        // setup statement proxy
+        if (getPoolProperties().getUseStatementFacade()) {
+            handler = new StatementFacade(handler);
+        }
         try {
             getProxyConstructor(con.getXAConnection() != null);
             //create the proxy
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java
index 945436c5..d5827f76 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java
@@ -124,6 +124,8 @@ public class DataSourceFactory implements ObjectFactory {
 
     protected static final String PROP_IGNOREEXCEPTIONONPRELOAD = "ignoreExceptionOnPreLoad";
 
+    protected static final String PROP_USESTATEMENTFACADE = "useStatementFacade";
+
     public static final int UNKNOWN_TRANSACTIONISOLATION = -1;
 
     public static final String OBJECT_NAME = "object_name";
@@ -179,7 +181,8 @@ public class DataSourceFactory implements ObjectFactory {
         PROP_USEDISPOSABLECONNECTIONFACADE,
         PROP_LOGVALIDATIONERRORS,
         PROP_PROPAGATEINTERRUPTSTATE,
-        PROP_IGNOREEXCEPTIONONPRELOAD
+        PROP_IGNOREEXCEPTIONONPRELOAD,
+        PROP_USESTATEMENTFACADE
     };
 
     // -------------------------------------------------- ObjectFactory Methods
@@ -527,7 +530,10 @@ public class DataSourceFactory implements ObjectFactory {
         if (value != null) {
             poolProperties.setIgnoreExceptionOnPreLoad(Boolean.parseBoolean(value));
         }
-
+        value = properties.getProperty(PROP_USESTATEMENTFACADE);
+        if (value != null) {
+            poolProperties.setUseStatementFacade(Boolean.parseBoolean(value));
+        }
         return poolProperties;
     }
 
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
index d3539ecc..a5407dfb 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java
@@ -1458,6 +1458,22 @@ public class DataSourceProxy implements PoolConfiguration {
         getPoolProperties().setIgnoreExceptionOnPreLoad(ignoreExceptionOnPreLoad);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean getUseStatementFacade() {
+        return getPoolProperties().getUseStatementFacade();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUseStatementFacade(boolean useStatementFacade) {
+        getPoolProperties().setUseStatementFacade(useStatementFacade);
+    }
+
     public void purge()  {
         try {
             createPool().purge();
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DisposableConnectionFacade.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DisposableConnectionFacade.java
index 3ad572c5..f6cea312 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DisposableConnectionFacade.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DisposableConnectionFacade.java
@@ -22,7 +22,7 @@ import java.sql.SQLException;
 
 /**
  * A DisposableConnectionFacade object is the top most interceptor that wraps an
- * object of type {@link PooledConnection}. The ProxyCutOffConnection intercepts
+ * object of type {@link PooledConnection}. The DisposableConnectionFacade intercepts
  * two methods:
  * <ul>
  *   <li>{@link java.sql.Connection#close()} - returns the connection to the
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java
index b8168fca..839d46fe 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java
@@ -894,4 +894,18 @@ public interface PoolConfiguration {
      */
     public boolean isIgnoreExceptionOnPreLoad();
 
+    /**
+     * Set this to true if you wish to wrap statements in order to enable equals() and hashCode()
+     * methods to be called on the closed statements if any statement proxy is set.
+     * @param useStatementFacade set to <code>true</code> to wrap statements
+     */
+    public void setUseStatementFacade(boolean useStatementFacade);
+
+    /**
+     * Returns <code>true</code> if this connection pool is configured to wrap statements in order
+     * to enable equals() and hashCode() methods to be called on the closed statements if any
+     * statement proxy is set.
+     * @return <code>true</code> if the statements are wrapped
+     */
+    public boolean getUseStatementFacade();
 }
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java
index 6a459ce1..96178e1d 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java
@@ -89,6 +89,7 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
     private volatile boolean logValidationErrors = false;
     private volatile boolean propagateInterruptState = false;
     private volatile boolean ignoreExceptionOnPreLoad = false;
+    private volatile boolean useStatementFacade = true;
 
     /**
      * {@inheritDoc}
@@ -1301,6 +1302,22 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
         this.ignoreExceptionOnPreLoad = ignoreExceptionOnPreLoad;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean getUseStatementFacade() {
+        return useStatementFacade;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUseStatementFacade(boolean useStatementFacade) {
+        this.useStatementFacade = useStatementFacade;
+    }
+
     @Override
     protected Object clone() throws CloneNotSupportedException {
         // TODO Auto-generated method stub
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/StatementFacade.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/StatementFacade.java
new file mode 100644
index 00000000..eac84838
--- /dev/null
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/StatementFacade.java
@@ -0,0 +1,153 @@
+/*
+ * 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.tomcat.jdbc.pool;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jdbc.pool.interceptor.AbstractCreateStatementInterceptor;
+
+public class StatementFacade extends AbstractCreateStatementInterceptor {
+
+    private static final Log logger = LogFactory.getLog(StatementFacade.class);
+
+    protected StatementFacade(JdbcInterceptor interceptor) {
+        setUseEquals(interceptor.isUseEquals());
+        setNext(interceptor);
+    }
+
+    @Override
+    public void closeInvoked() {
+        // nothing to do
+    }
+
+    /**
+     * Creates a statement interceptor to monitor query response times
+     */
+    @Override
+    public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
+        try {
+            String name = method.getName();
+            Constructor<?> constructor = null;
+            String sql = null;
+            if (compare(CREATE_STATEMENT, name)) {
+                // createStatement
+                constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
+            } else if (compare(PREPARE_STATEMENT, name)) {
+                // prepareStatement
+                constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
+                sql = (String)args[0];
+            } else if (compare(PREPARE_CALL, name)) {
+                // prepareCall
+                constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
+                sql = (String)args[0];
+            } else {
+                // do nothing
+                return statement;
+            }
+            return constructor.newInstance(new Object[] { new StatementProxy(statement,sql) });
+        } catch (Exception x) {
+            logger.warn("Unable to create statement proxy.", x);
+        }
+        return statement;
+    }
+
+    /**
+     * Class to measure query execute time.
+     */
+    protected class StatementProxy implements InvocationHandler {
+        protected boolean closed = false;
+        protected Object delegate;
+        protected final String query;
+        public StatementProxy(Object parent, String query) {
+            this.delegate = parent;
+            this.query = query;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            if (compare(TOSTRING_VAL,method)) {
+                return toString();
+            }
+            if (compare(EQUALS_VAL, method)) {
+                return Boolean.valueOf(
+                        this.equals(Proxy.getInvocationHandler(args[0])));
+            }
+            if (compare(HASHCODE_VAL, method)) {
+                return Integer.valueOf(this.hashCode());
+            }
+            if (compare(CLOSE_VAL, method)) {
+                if (delegate == null) return null;
+            }
+            if (compare(ISCLOSED_VAL, method)) {
+                if (delegate == null) return Boolean.TRUE;
+            }
+            if (delegate == null) throw new SQLException("Statement closed.");
+            Object result =  null;
+            try {
+                //invoke next
+                result =  method.invoke(delegate,args);
+            } catch (Throwable t) {
+                if (t instanceof InvocationTargetException && t.getCause() != null) {
+                    throw t.getCause();
+                } else {
+                    throw t;
+                }
+            }
+            //perform close cleanup
+            if (compare(CLOSE_VAL, method)) {
+                delegate = null;
+            }
+            return result;
+        }
+
+        @Override
+        public int hashCode() {
+            return System.identityHashCode(this);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return this==obj;
+        }
+
+        @Override
+        public String toString() {
+            StringBuffer buf = new StringBuffer(StatementProxy.class.getName());
+            buf.append("[Proxy=");
+            buf.append(hashCode());
+            buf.append("; Query=");
+            buf.append(query);
+            buf.append("; Delegate=");
+            buf.append(delegate);
+            buf.append("]");
+            return buf.toString();
+        }
+    }
+
+}
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java
index 11fc655d..521886e9 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractCreateStatementInterceptor.java
@@ -16,7 +16,10 @@
  */
 package org.apache.tomcat.jdbc.pool.interceptor;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 
 import org.apache.tomcat.jdbc.pool.ConnectionPool;
 import org.apache.tomcat.jdbc.pool.JdbcInterceptor;
@@ -46,6 +49,12 @@ public abstract class  AbstractCreateStatementInterceptor extends JdbcIntercepto
 
     protected static final String[] EXECUTE_TYPES = {EXECUTE, EXECUTE_QUERY, EXECUTE_UPDATE, EXECUTE_BATCH};
 
+    /**
+     * the constructors that are used to create statement proxies
+     */
+    protected static final Constructor<?>[] constructors =
+            new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
+
     public  AbstractCreateStatementInterceptor() {
         super();
     }
@@ -73,6 +82,25 @@ public abstract class  AbstractCreateStatementInterceptor extends JdbcIntercepto
     }
 
     /**
+     * Creates a constructor for a proxy class, if one doesn't already exist
+     *
+     * @param idx
+     *            - the index of the constructor
+     * @param clazz
+     *            - the interface that the proxy will implement
+     * @return - returns a constructor used to create new instances
+     * @throws NoSuchMethodException Constructor not found
+     */
+    protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
+        if (constructors[idx] == null) {
+            Class<?> proxyClass = Proxy.getProxyClass(AbstractCreateStatementInterceptor.class.getClassLoader(),
+                    new Class[] { clazz });
+            constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
+        }
+        return constructors[idx];
+    }
+
+    /**
      * This method will be invoked after a successful statement creation. This method can choose to return a wrapper
      * around the statement or return the statement itself.
      * If this method returns a wrapper then it should return a wrapper object that implements one of the following interfaces.
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractQueryReport.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractQueryReport.java
index 7d379c29..4c5b28be 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractQueryReport.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractQueryReport.java
@@ -21,7 +21,6 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
 import java.sql.CallableStatement;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
@@ -43,13 +42,6 @@ public abstract class AbstractQueryReport extends AbstractCreateStatementInterce
      */
     protected long threshold = 1000; //don't report queries less than this
 
-    /**
-     * the constructors that are used to create statement proxies
-     */
-    protected static final Constructor<?>[] constructors =
-        new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
-
-
     public AbstractQueryReport() {
         super();
     }
@@ -144,21 +136,6 @@ public abstract class AbstractQueryReport extends AbstractCreateStatementInterce
     }
 
     /**
-     * Creates a constructor for a proxy class, if one doesn't already exist
-     * @param idx - the index of the constructor
-     * @param clazz - the interface that the proxy will implement
-     * @return - returns a constructor used to create new instances
-     * @throws NoSuchMethodException Constructor not found
-     */
-    protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
-        if (constructors[idx]==null) {
-            Class<?> proxyClass = Proxy.getProxyClass(SlowQueryReport.class.getClassLoader(), new Class[] {clazz});
-            constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
-        }
-        return constructors[idx];
-    }
-
-    /**
      * Creates a statement interceptor to monitor query response times
      */
     @Override
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementDecoratorInterceptor.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementDecoratorInterceptor.java
index c8179d61..51ae9df0 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementDecoratorInterceptor.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/StatementDecoratorInterceptor.java
@@ -47,11 +47,6 @@ public class StatementDecoratorInterceptor extends AbstractCreateStatementInterc
     protected static final String[] RESULTSET_TYPES = {EXECUTE_QUERY, GET_GENERATED_KEYS, GET_RESULTSET};
 
     /**
-     * the constructors that are used to create statement proxies
-     */
-    protected static final Constructor<?>[] constructors = new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
-
-    /**
      * the constructor to create the resultSet proxies
      */
     protected static Constructor<?> resultSetConstructor = null;
@@ -61,25 +56,6 @@ public class StatementDecoratorInterceptor extends AbstractCreateStatementInterc
         // nothing to do
     }
 
-    /**
-     * Creates a constructor for a proxy class, if one doesn't already exist
-     *
-     * @param idx
-     *            - the index of the constructor
-     * @param clazz
-     *            - the interface that the proxy will implement
-     * @return - returns a constructor used to create new instances
-     * @throws NoSuchMethodException Constructor not found
-     */
-    protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
-        if (constructors[idx] == null) {
-            Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
-                    new Class[] { clazz });
-            constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
-        }
-        return constructors[idx];
-    }
-
     protected Constructor<?> getResultSetConstructor() throws NoSuchMethodException {
         if (resultSetConstructor == null) {
             Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java
index 0dfb6f72..5d443ebb 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java
@@ -919,6 +919,22 @@ public class ConnectionPool extends NotificationBroadcasterSupport implements Co
      * {@inheritDoc}
      */
     @Override
+    public boolean getUseStatementFacade() {
+        return getPoolProperties().getUseStatementFacade();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUseStatementFacade(boolean useStatementFacade) {
+        getPoolProperties().setUseStatementFacade(useStatementFacade);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void purge() {
         pool.purge();
 
diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml
index 335aaff0..7dfd1c3f 100644
--- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml
+++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml
@@ -330,6 +330,12 @@
                     is="true"
              writeable="false"/>
 
+    <attribute    name="useStatementFacade"
+           description="If true, connection pool is configured to wrap statements."
+                  type="java.lang.Boolean"
+                    is="false"
+             writeable="false"/>
+
     <attribute    name="borrowedCount"
            description="The total number of connections borrowed from this pool"
                   type="java.lang.Long"
diff --git a/res/findbugs/filter-false-positives.xml b/res/findbugs/filter-false-positives.xml
index 8fe71524..1c22623c 100644
--- a/res/findbugs/filter-false-positives.xml
+++ b/res/findbugs/filter-false-positives.xml
@@ -1061,6 +1061,12 @@
     <Bug code="DE" />
   </Match>
   <Match>
+    <!-- Concrete Map type not affected -->
+    <Class name="org.apache.catalina.util.TestParameterMap" />
+    <Method name="testEntrySetImmutabilityAfterLocked" />
+    <Bug pattern="DMI_ENTRY_SETS_MAY_REUSE_ENTRY_OBJECTS" />
+  </Match>
+  <Match>
     <!-- Code is deliberately unused -->
     <Or>
       <Class name="org.apache.catalina.webresources.AbstractTestFileResourceSet" />
diff --git a/res/maven/mvn.properties.default b/res/maven/mvn.properties.default
index d0260b11..b3c9332e 100644
--- a/res/maven/mvn.properties.default
+++ b/res/maven/mvn.properties.default
@@ -35,7 +35,7 @@ maven.asf.release.repo.url=https://repository.apache.org/service/local/staging/d
 maven.asf.release.repo.repositoryId=apache.releases
 
 # Release version info
-maven.asf.release.deploy.version=8.5.12
+maven.asf.release.deploy.version=8.5.14
 
 #Where do we load the libraries from
 tomcat.lib.path=../../output/build/lib
diff --git a/test/org/apache/catalina/core/TestApplicationMapping.java b/test/org/apache/catalina/core/TestApplicationMapping.java
index f6cff588..41dcd6bf 100644
--- a/test/org/apache/catalina/core/TestApplicationMapping.java
+++ b/test/org/apache/catalina/core/TestApplicationMapping.java
@@ -59,6 +59,16 @@ public class TestApplicationMapping extends TomcatBaseTest {
     }
 
     @Test
+    public void testContextNonRootMappingPathNone() throws Exception {
+        doTestMapping("/dummy", "/foo/bar/*", "/foo/bar", null, "PATH");
+    }
+
+    @Test
+    public void testContextNonRootMappingPathSeparatorOnly() throws Exception {
+        doTestMapping("/dummy", "/foo/bar/*", "/foo/bar/", "", "PATH");
+    }
+
+    @Test
     public void testContextNonRootMappingPath() throws Exception {
         doTestMapping("/dummy", "/foo/bar/*", "/foo/bar/foo2", "foo2", "PATH");
     }
diff --git a/test/org/apache/catalina/webresources/TestJarWarResourceSet.java b/test/org/apache/catalina/webresources/TestJarWarResourceSet.java
new file mode 100644
index 00000000..be5d98f8
--- /dev/null
+++ b/test/org/apache/catalina/webresources/TestJarWarResourceSet.java
@@ -0,0 +1,56 @@
+/*
+ * 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.webresources;
+
+import java.io.File;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.WebResource;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+
+public class TestJarWarResourceSet extends TomcatBaseTest {
+
+    @Before
+    public void register() {
+        TomcatURLStreamHandlerFactory.register();
+    }
+
+
+    @Test
+    public void testJarWarMetaInf() throws LifecycleException  {
+        Tomcat tomcat = getTomcatInstance();
+
+        File warFile = new File("test/webresources/war-url-connection.war");
+        Context ctx = tomcat.addContext("", warFile.getAbsolutePath());
+
+        tomcat.start();
+
+        StandardRoot root = (StandardRoot) ctx.getResources();
+
+        WebResource[] results = root.getClassLoaderResources("/META-INF");
+
+        Assert.assertNotNull(results);
+        Assert.assertEquals(1, results.length);
+        Assert.assertNotNull(results[0].getURL());
+    }
+}
diff --git a/test/org/apache/coyote/http11/filters/TesterOutputBuffer.java b/test/org/apache/coyote/http11/filters/TesterOutputBuffer.java
index 7a05c5da..fd12888a 100644
--- a/test/org/apache/coyote/http11/filters/TesterOutputBuffer.java
+++ b/test/org/apache/coyote/http11/filters/TesterOutputBuffer.java
@@ -38,7 +38,7 @@ public class TesterOutputBuffer extends Http11OutputBuffer {
 
 
     public TesterOutputBuffer(Response response, int headerBufferSize) {
-        super(response, headerBufferSize);
+        super(response, headerBufferSize, false);
         outputStreamOutputBuffer = new OutputStreamOutputBuffer();
     }
 
diff --git a/test/org/apache/coyote/http2/Http2TestBase.java b/test/org/apache/coyote/http2/Http2TestBase.java
index fa509baf..ca276c90 100644
--- a/test/org/apache/coyote/http2/Http2TestBase.java
+++ b/test/org/apache/coyote/http2/Http2TestBase.java
@@ -27,6 +27,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Random;
 
 import javax.net.SocketFactory;
 import javax.servlet.ServletException;
@@ -59,6 +60,8 @@ public abstract class Http2TestBase extends TomcatBaseTest {
     // response now included a date header
     protected static final String DEFAULT_DATE = "Wed, 11 Nov 2015 19:18:42 GMT";
 
+    private static final String HEADER_IGNORED = "x-ignore";
+
     static final String DEFAULT_CONNECTION_HEADER_VALUE = "Upgrade, HTTP2-Settings";
     private static final byte[] EMPTY_SETTINGS_FRAME =
         { 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 };
@@ -177,6 +180,7 @@ public abstract class Http2TestBase extends TomcatBaseTest {
             int streamId, String url) {
         List<Header> headers = new ArrayList<>(3);
         headers.add(new Header(":method", "GET"));
+        headers.add(new Header(":scheme", "http"));
         headers.add(new Header(":path", url));
         headers.add(new Header(":authority", "localhost:" + getPort()));
 
@@ -215,6 +219,7 @@ public abstract class Http2TestBase extends TomcatBaseTest {
             int streamId) {
         List<Header> headers = new ArrayList<>(3);
         headers.add(new Header(":method", "GET"));
+        headers.add(new Header(":scheme", "http"));
         headers.add(new Header(":path", "/simple"));
 
         buildSimpleGetRequestPart1(frameHeader, headersPayload, headers, streamId);
@@ -306,6 +311,7 @@ public abstract class Http2TestBase extends TomcatBaseTest {
             byte[] trailersFrameHeader, ByteBuffer trailersPayload, int streamId) {
         MimeHeaders headers = new MimeHeaders();
         headers.addValue(":method").setString("POST");
+        headers.addValue(":scheme").setString("http");
         headers.addValue(":path").setString("/simple");
         headers.addValue(":authority").setString("localhost:" + getPort());
         if (useExpectation) {
@@ -925,8 +931,13 @@ public abstract class Http2TestBase extends TomcatBaseTest {
             if ("date".equals(name)) {
                 value = DEFAULT_DATE;
             }
+            // Some header values vary so ignore them
+            if (HEADER_IGNORED.equals(name)) {
+                trace.append(lastStreamId + "-Header-[" + name + "]-[...]\n");
+            } else {
                 trace.append(lastStreamId + "-Header-[" + name + "]-[" + value + "]\n");
             }
+        }
 
 
         @Override
@@ -1139,6 +1150,26 @@ public abstract class Http2TestBase extends TomcatBaseTest {
     }
 
 
+    static class LargeHeaderServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            resp.setContentType("text/plain");
+            resp.setCharacterEncoding("UTF-8");
+            StringBuilder headerValue = new StringBuilder();
+            Random random = new Random();
+            while (headerValue.length() < 2048) {
+                headerValue.append(Long.toString(random.nextLong()));
+            }
+            resp.setHeader(HEADER_IGNORED, headerValue.toString());
+            resp.getWriter().print("OK");
+        }
+    }
+
+
     static class SettingValue {
         private final int setting;
         private final long value;
diff --git a/test/org/apache/coyote/http2/TestHpack.java b/test/org/apache/coyote/http2/TestHpack.java
index 5c4bca2a..7d199804 100644
--- a/test/org/apache/coyote/http2/TestHpack.java
+++ b/test/org/apache/coyote/http2/TestHpack.java
@@ -105,6 +105,25 @@ public class TestHpack {
         }
     }
 
+    @Test(expected=HpackException.class)
+    public void testExcessiveStringLiteralPadding() throws Exception {
+        MimeHeaders headers = new MimeHeaders();
+        headers.setValue("X-test").setString("foobar");
+        ByteBuffer output = ByteBuffer.allocate(512);
+        HpackEncoder encoder = new HpackEncoder();
+        encoder.encode(headers, output);
+        // Hack the output buffer to extend the EOS marker for the header value
+        // by another byte
+        output.array()[7] = (byte) -122;
+        output.put((byte) -1);
+        output.flip();
+        MimeHeaders headers2 = new MimeHeaders();
+        HpackDecoder decoder = new HpackDecoder();
+        decoder.setHeaderEmitter(new HeadersListener(headers2));
+        decoder.decode(output);
+    }
+
+
     private void doTestHeaderValueBug60451(String filename) throws HpackException {
         String headerName = "Content-Disposition";
         String headerValue = "attachment;filename=\"" + filename + "\"";
diff --git a/test/org/apache/coyote/http2/TestHttp2Limits.java b/test/org/apache/coyote/http2/TestHttp2Limits.java
index 9af34a37..817dcfb7 100644
--- a/test/org/apache/coyote/http2/TestHttp2Limits.java
+++ b/test/org/apache/coyote/http2/TestHttp2Limits.java
@@ -41,15 +41,15 @@ public class TestHttp2Limits extends Http2TestBase {
     @Test
     public void testHeaderLimits100x32() throws Exception {
         // Just within default maxHeaderCount
-        // Note request has 3 standard headers
-        doTestHeaderLimits(97, 32, 0);
+        // Note request has 4 standard headers
+        doTestHeaderLimits(96, 32, 0);
     }
 
 
     @Test
     public void testHeaderLimits101x32() throws Exception {
         // Just above default maxHeaderCount
-        doTestHeaderLimits(98, 32, 1);
+        doTestHeaderLimits(97, 32, 1);
     }
 
 
@@ -61,17 +61,17 @@ public class TestHttp2Limits extends Http2TestBase {
 
 
     @Test
-    public void testHeaderLimits8x1001() throws Exception {
+    public void testHeaderLimits8x1144() throws Exception {
         // Just within default maxHttpHeaderSize
-        // per header overhead plus standard 2 headers
-        doTestHeaderLimits(8, 1001, 0);
+        // per header overhead plus standard 3 headers
+        doTestHeaderLimits(7, 1144, 0);
     }
 
 
     @Test
-    public void testHeaderLimits8x1002() throws Exception {
+    public void testHeaderLimits8x1145() throws Exception {
         // Just above default maxHttpHeaderSize
-        doTestHeaderLimits(8, 1002, 1);
+        doTestHeaderLimits(7, 1145, 1);
     }
 
 
@@ -263,6 +263,7 @@ public class TestHttp2Limits extends Http2TestBase {
             String path) throws Exception {
         MimeHeaders headers = new MimeHeaders();
         headers.addValue(":method").setString("GET");
+        headers.addValue(":scheme").setString("http");
         headers.addValue(":path").setString(path);
         headers.addValue(":authority").setString("localhost:" + getPort());
         for (String[] customHeader : customHeaders) {
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_8_1.java b/test/org/apache/coyote/http2/TestHttp2Section_8_1.java
index 4b248f05..b44590b7 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_8_1.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_8_1.java
@@ -141,8 +141,9 @@ public class TestHttp2Section_8_1 extends Http2TestBase {
 
     @Test
     public void testUndefinedPseudoHeader() throws Exception {
-        List<Header> headers = new ArrayList<>(3);
+        List<Header> headers = new ArrayList<>(5);
         headers.add(new Header(":method", "GET"));
+        headers.add(new Header(":scheme", "http"));
         headers.add(new Header(":path", "/simple"));
         headers.add(new Header(":authority", "localhost:" + getPort()));
         headers.add(new Header(":foo", "bar"));
@@ -153,8 +154,9 @@ public class TestHttp2Section_8_1 extends Http2TestBase {
 
     @Test
     public void testInvalidPseudoHeader() throws Exception {
-        List<Header> headers = new ArrayList<>(3);
+        List<Header> headers = new ArrayList<>(5);
         headers.add(new Header(":method", "GET"));
+        headers.add(new Header(":scheme", "http"));
         headers.add(new Header(":path", "/simple"));
         headers.add(new Header(":authority", "localhost:" + getPort()));
         headers.add(new Header(":status", "200"));
@@ -170,8 +172,9 @@ public class TestHttp2Section_8_1 extends Http2TestBase {
 
         http2Connect();
 
-        List<Header> headers = new ArrayList<>(3);
+        List<Header> headers = new ArrayList<>(4);
         headers.add(new Header(":method", "GET"));
+        headers.add(new Header(":scheme", "http"));
         headers.add(new Header(":path", "/simple"));
         headers.add(new Header("x-test", "test"));
 
diff --git a/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java b/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
new file mode 100644
index 00000000..d12aba5f
--- /dev/null
+++ b/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
@@ -0,0 +1,71 @@
+/*
+ *  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.coyote.http2;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.Tomcat;
+
+public class TestHttp2UpgradeHandler extends Http2TestBase {
+
+    // https://bz.apache.org/bugzilla/show_bug.cgi?id=60970
+    @Test
+    public void testLargeHeader() throws Exception {
+        enableHttp2();
+
+        Tomcat tomcat = getTomcatInstance();
+
+        Context ctxt = tomcat.addContext("", null);
+        Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
+        ctxt.addServletMappingDecoded("/simple", "simple");
+        Tomcat.addServlet(ctxt, "large", new LargeHeaderServlet());
+        ctxt.addServletMappingDecoded("/large", "large");
+
+        tomcat.start();
+
+        openClientConnection();
+        doHttpUpgrade();
+        sendClientPreface();
+        validateHttp2InitialResponse();
+
+        byte[] frameHeader = new byte[9];
+        ByteBuffer headersPayload = ByteBuffer.allocate(128);
+        buildGetRequest(frameHeader, headersPayload, null, 3, "/large");
+        writeFrame(frameHeader, headersPayload);
+
+        // Headers
+        parser.readFrame(true);
+        parser.readFrame(true);
+        // Body
+        parser.readFrame(true);
+
+        Assert.assertEquals(
+                "3-HeadersStart\n" +
+                "3-Header-[:status]-[200]\n" +
+                "3-Header-[x-ignore]-[...]\n" +
+                "3-Header-[content-type]-[text/plain;charset=UTF-8]\n" +
+                "3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
+                "3-HeadersEnd\n" +
+                "3-Body-2\n" +
+                "3-EndOfStream\n", output.getTrace());
+    }
+
+}
diff --git a/test/org/apache/el/TestMethodExpressionImpl.java b/test/org/apache/el/TestMethodExpressionImpl.java
index ee71edcf..6a810da5 100644
--- a/test/org/apache/el/TestMethodExpressionImpl.java
+++ b/test/org/apache/el/TestMethodExpressionImpl.java
@@ -531,4 +531,11 @@ public class TestMethodExpressionImpl {
         assertEquals("aaa, bbb", r.toString());
     }
 
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testBug60844() {
+        MethodExpression me2 = factory.createMethodExpression(context,
+                "${beanC.sayHello}", null , new Class[]{ TesterBeanA.class, TesterBeanB.class});
+        me2.invoke(context, new Object[] { new Object() });
+    }
 }
diff --git a/test/org/apache/tomcat/util/buf/TestByteChunk.java b/test/org/apache/tomcat/util/buf/TestByteChunk.java
index d2f818ed..0838804a 100644
--- a/test/org/apache/tomcat/util/buf/TestByteChunk.java
+++ b/test/org/apache/tomcat/util/buf/TestByteChunk.java
@@ -32,7 +32,7 @@ public class TestByteChunk {
 
     @Test
     public void testConvertToBytes() throws UnsupportedEncodingException {
-        String string = "HTTP/1.1 100 Continue\r\n";
+        String string = "HTTP/1.1 100 \r\n\r\n";
         byte[] bytes = ByteChunk.convertToBytes(string);
         byte[] expected = string.getBytes("ISO-8859-1");
         assertTrue(Arrays.equals(bytes, expected));
diff --git a/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java b/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java
index 6c03e7f5..f8a1d582 100644
--- a/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java
+++ b/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java
@@ -187,7 +187,8 @@ public class TestCookieProcessorGeneration {
 
     @Test
     public void v1TestMaxAgeZero() {
-        doV1TestMaxAge(0, "foo=bar; Version=1; Max-Age=0", "foo=bar;Max-Age=0;Expires=Thu, 01-Jan-1970 00:00:10 GMT");
+        doV1TestMaxAge(0, "foo=bar; Version=1; Max-Age=0",
+                "foo=bar; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT");
     }
 
     @Test
@@ -198,13 +199,13 @@ public class TestCookieProcessorGeneration {
     @Test
     public void v1TestDomainValid01() {
         doV1TestDomain("example.com", "foo=bar; Version=1; Domain=example.com",
-                "foo=bar;domain=example.com");
+                "foo=bar; Domain=example.com");
     }
 
     @Test
     public void v1TestDomainValid02() {
         doV1TestDomain("exa-mple.com", "foo=bar; Version=1; Domain=exa-mple.com",
-                "foo=bar;domain=exa-mple.com");
+                "foo=bar; Domain=exa-mple.com");
     }
 
     @Test
@@ -245,7 +246,7 @@ public class TestCookieProcessorGeneration {
     @Test
     public void v1TestPathValid() {
         doV1TestPath("/example", "foo=bar; Version=1; Path=/example",
-                "foo=bar;path=/example");
+                "foo=bar; Path=/example");
     }
 
     @Test
diff --git a/test/org/apache/tomcat/util/net/openssl/ciphers/TestCipher.java b/test/org/apache/tomcat/util/net/openssl/ciphers/TestCipher.java
index 74dd1be2..b6e7ebcc 100644
--- a/test/org/apache/tomcat/util/net/openssl/ciphers/TestCipher.java
+++ b/test/org/apache/tomcat/util/net/openssl/ciphers/TestCipher.java
@@ -643,14 +643,14 @@ public class TestCipher {
                     "ECDHE-ECDSA-CAMELLIA256-SHA384+TLSv1.2",
                     "ECDHE-ECDSA-CHACHA20-POLY1305+TLSv1.2",
                     "ECDHE-PSK-3DES-EDE-CBC-SHA+SSLv3",
-                    "ECDHE-PSK-AES128-CBC-SHA+SSLv3",
+                    "ECDHE-PSK-AES128-CBC-SHA+TLSv1",
                     "ECDHE-PSK-AES128-CBC-SHA256+TLSv1",
-                    "ECDHE-PSK-AES256-CBC-SHA+SSLv3",
+                    "ECDHE-PSK-AES256-CBC-SHA+TLSv1",
                     "ECDHE-PSK-AES256-CBC-SHA384+TLSv1",
                     "ECDHE-PSK-CAMELLIA128-SHA256+TLSv1",
                     "ECDHE-PSK-CAMELLIA256-SHA384+TLSv1",
                     "ECDHE-PSK-CHACHA20-POLY1305+TLSv1.2",
-                    "ECDHE-PSK-NULL-SHA+SSLv3",
+                    "ECDHE-PSK-NULL-SHA+TLSv1",
                     "ECDHE-PSK-NULL-SHA256+TLSv1",
                     "ECDHE-PSK-NULL-SHA384+TLSv1",
                     "ECDHE-PSK-RC4-SHA+SSLv3",
diff --git a/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java b/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
index 3f257e56..62b203d6 100644
--- a/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
+++ b/test/org/apache/tomcat/util/net/openssl/ciphers/TestOpenSSLCipherConfigurationParser.java
@@ -306,7 +306,11 @@ public class TestOpenSSLCipherConfigurationParser {
 
     @Test
     public void testSSLv3() throws Exception {
-        testSpecification("SSLv3");
+        // In OpenSSL 1.1.0-dev, TLSv1 refers to those ciphers that require
+        // TLSv1 rather than being an alias for SSLv3
+        if (TesterOpenSSL.VERSION < 10100) {
+            testSpecification("SSLv3:TLSv1");
+        }
     }
 
 
diff --git a/test/org/apache/tomcat/util/net/openssl/ciphers/TesterOpenSSL.java b/test/org/apache/tomcat/util/net/openssl/ciphers/TesterOpenSSL.java
index fbbc4646..447c8538 100644
--- a/test/org/apache/tomcat/util/net/openssl/ciphers/TesterOpenSSL.java
+++ b/test/org/apache/tomcat/util/net/openssl/ciphers/TesterOpenSSL.java
@@ -20,8 +20,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.catalina.util.IOTools;
@@ -33,6 +35,8 @@ public class TesterOpenSSL {
 
     public static final Set<Cipher> OPENSSL_UNIMPLEMENTED_CIPHERS;
 
+    public static final Map<String,String> OPENSSL_RENAMED_CIPHERS;
+
     static {
         // Note: The following lists are intended to be aligned with the most
         //       recent release of each OpenSSL release branch. Running the unit
@@ -299,6 +303,29 @@ public class TesterOpenSSL {
             unimplemented.add(Cipher.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA);
         }
         OPENSSL_UNIMPLEMENTED_CIPHERS = Collections.unmodifiableSet(unimplemented);
+
+        Map<String,String> renamed = new HashMap<>();
+        renamed.put("ECDH-ECDSA-RC4-SHA+SSLv3", "ECDH-ECDSA-RC4-SHA+TLSv1");
+        renamed.put("ECDHE-ECDSA-NULL-SHA+SSLv3", "ECDHE-ECDSA-NULL-SHA+TLSv1");
+        renamed.put("ECDHE-ECDSA-DES-CBC3-SHA+SSLv3", "ECDHE-ECDSA-DES-CBC3-SHA+TLSv1");
+        renamed.put("ECDHE-ECDSA-AES128-SHA+SSLv3", "ECDHE-ECDSA-AES128-SHA+TLSv1");
+        renamed.put("ECDHE-ECDSA-AES256-SHA+SSLv3", "ECDHE-ECDSA-AES256-SHA+TLSv1");
+        renamed.put("ECDHE-RSA-NULL-SHA+SSLv3", "ECDHE-RSA-NULL-SHA+TLSv1");
+        renamed.put("ECDHE-RSA-RC4-SHA+SSLv3", "ECDHE-RSA-RC4-SHA+TLSv1");
+        renamed.put("ECDHE-RSA-DES-CBC3-SHA+SSLv3", "ECDHE-RSA-DES-CBC3-SHA+TLSv1");
+        renamed.put("ECDHE-RSA-AES128-SHA+SSLv3", "ECDHE-RSA-AES128-SHA+TLSv1");
+        renamed.put("ECDHE-RSA-AES256-SHA+SSLv3", "ECDHE-RSA-AES256-SHA+TLSv1");
+        renamed.put("AECDH-NULL-SHA+SSLv3", "AECDH-NULL-SHA+TLSv1");
+        renamed.put("AECDH-RC4-SHA+SSLv3", "AECDH-RC4-SHA+TLSv1");
+        renamed.put("AECDH-DES-CBC3-SHA+SSLv3", "AECDH-DES-CBC3-SHA+TLSv1");
+        renamed.put("AECDH-AES128-SHA+SSLv3", "AECDH-AES128-SHA+TLSv1");
+        renamed.put("AECDH-AES256-SHA+SSLv3", "AECDH-AES256-SHA+TLSv1");
+        renamed.put("ECDHE-PSK-RC4-SHA+SSLv3", "ECDHE-PSK-RC4-SHA+TLSv1");
+        renamed.put("ECDHE-PSK-3DES-EDE-CBC-SHA+SSLv3", "ECDHE-PSK-3DES-EDE-CBC-SHA+TLSv1");
+        renamed.put("ECDHE-PSK-AES128-CBC-SHA+SSLv3", "ECDHE-PSK-AES128-CBC-SHA+TLSv1");
+        renamed.put("ECDHE-PSK-AES256-CBC-SHA+SSLv3", "ECDHE-PSK-AES256-CBC-SHA+TLSv1");
+        renamed.put("ECDHE-PSK-NULL-SHA+SSLv3", "ECDHE-PSK-NULL-SHA+TLSv1");
+        OPENSSL_RENAMED_CIPHERS = Collections.unmodifiableMap(renamed);
     }
 
 
@@ -343,9 +370,11 @@ public class TesterOpenSSL {
             } else {
                 output.append(':');
             }
+            StringBuilder name = new StringBuilder();
+
             // Name is first part
             int i = cipher.indexOf(' ');
-            output.append(cipher.substring(0, i));
+            name.append(cipher.substring(0, i));
 
             // Advance i past the space
             while (Character.isWhitespace(cipher.charAt(i))) {
@@ -354,8 +383,15 @@ public class TesterOpenSSL {
 
             // Protocol is the second
             int j = cipher.indexOf(' ', i);
-            output.append('+');
-            output.append(cipher.substring(i, j));
+            name.append('+');
+            name.append(cipher.substring(i, j));
+
+            // More renames
+            if (OPENSSL_RENAMED_CIPHERS.containsKey(name.toString())) {
+                output.append(OPENSSL_RENAMED_CIPHERS.get(name.toString()));
+            } else {
+                output.append(name.toString());
+            }
         }
         return output.toString();
     }
diff --git a/test/webapp/WEB-INF/tags/bug42390.tag b/test/webapp/WEB-INF/tags/bug42390.tag
index 97619ed5..bf6a398d 100644
--- a/test/webapp/WEB-INF/tags/bug42390.tag
+++ b/test/webapp/WEB-INF/tags/bug42390.tag
@@ -14,5 +14,5 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 --%>
-<%@ variable name-given="X" scope="AT_BEGIN" %>
+<%@ variable name-given="X" scope="AT_END" %>
 <jsp:doBody/>
\ No newline at end of file
diff --git a/test/webapp/bug48nnn/bug48616b.jsp b/test/webapp/bug48nnn/bug48616b.jsp
index 31476ce6..715d8773 100644
--- a/test/webapp/bug48nnn/bug48616b.jsp
+++ b/test/webapp/bug48nnn/bug48616b.jsp
@@ -26,3 +26,6 @@
     <bugs:Bug48616b />
   </bugs:Bug46816a>
 </tags:bug42390>
+<%
+  out.println(X);
+%>
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index ac98485c..3312a4f1 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -44,7 +44,240 @@
   They eventually become mixed with the numbered issues. (I.e., numbered
   issues do not "pop up" wrt. others).
 -->
-<section name="Tomcat 8.5.12 (markt)">
+<section name="Tomcat 8.5.14 (markt)">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>59825</bug>: Log a message that lists the components in the
+        processing chain that do not support async processing when a call to
+        <code>ServletRequest.startAsync()</code> fails. (markt)
+      </fix>
+      <fix>
+        <bug>60926</bug>: Ensure
+        <code>o.a.c.core.ApplicationContextFacade#setSessionTimeout</code> will
+        invoke the correct method when running Tomcat with security manager.
+        (markt)
+      </fix>
+      <update>
+        Update the early access Servlet 4.0 API implementation to reflect the
+        change in method name from <code>getPushBuilder()</code> to
+        <code>newPushBuilder()</code>. (markt)
+      </update>
+      <fix>
+        Correct a regression in the X to comma refactoring that broke JMX
+        operations that take parameters. (markt)
+      </fix>
+      <fix>
+        Avoid a <code>NullPointerException</code> when reading attributes for a
+        running HTTP connector where TLS is not enabled. (markt)
+      </fix>
+      <fix>
+        <bug>60940</bug>: Improve the handling of the <code>META-INF/</code> and
+        <code>META-INF/MANIFEST.MF</code> entries for Jar files located in
+        <code>/WEB-INF/lib</code> when running a web application from a packed
+        WAR file. (markt)
+      </fix>
+      <fix>
+        Pre-load the <code>ExceptionUtils</code> class. Since the class is used
+        extensively in error handling, it is prudent to pre-load it to avoid any
+        failure to load this class masking the true problem during error
+        handling. (markt)
+      </fix>
+      <fix>
+        Avoid potential <code>NullPointerException</code>s related to access
+        logging during shutdown, some of which have been observed when running
+        the unit tests. (markt)
+      </fix>
+      <fix>
+        When there is no <code>javax.servlet.WriteListener</code> registered
+        then a call to <code>javax.servlet.ServletOutputStream#isReady</code>
+        will return <code>false</code> instead of throwing
+        <code>IllegalStateException</code>. (violetagg)
+      </fix>
+      <fix>
+        When there is no <code>javax.servlet.ReadListener</code> registered
+        then a call to <code>javax.servlet.ServletInputStream#isReady</code>
+        will return <code>false</code> instead of throwing
+        <code>IllegalStateException</code>. (violetagg)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        Align cipher configuration parsing with current OpenSSL master. (markt)
+      </fix>
+      <fix>
+        <bug>60970</bug>: Fix infinite loop if application tries to write a
+        large header to the response when using HTTP/2. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>60925</bug>: Improve the handling of access to properties defined
+        by interfaces when a <code>BeanELResolver</code> is used under a
+        <code>SecurityManager</code>. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="jdbc-pool">
+    <changelog>
+      <scode>
+        Refactor the creating a constructor for a proxy class to reduce
+        duplicate code. (kfujino)
+      </scode>
+      <fix>
+        In <code>StatementFacade</code>, the method call on the statements that
+        have been closed throw <code>SQLException</code> rather than
+        <code>NullPointerException</code>. (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        Correct comments about Java 8 in <code>Jre8Compat</code>.
+        Patch provided by fibbers via Github. (violetagg)
+      </fix>
+      <fix>
+        <bug>60932</bug>: Correctly escape single quotes when used in i18n
+        messages. Based on a patch by Michael Osipov. (markt)
+      </fix>
+      <fix>
+        Update the custom Ant task that integrates with the Symantec code
+        signing service to use the now mandatory 2-factor authentication.
+        (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.5.13 (markt)" rtext="2017-03-30">
+  <subsection name="Catalina">
+    <changelog>
+      <add>
+        <bug>54618</bug>: Add support to the
+        <code>HttpHeaderSecurityFilter</code> for the HSTS preload parameter.
+        (markt)
+      </add>
+      <fix>
+        <bug>60853</bug>: Expose the <code>SSLHostConfig</code> and
+        <code>SSLHostConfigCertificate</code> objects via JMX. (markt)
+      </fix>
+      <fix>
+        <bug>60876</bug>: Ensure that <code>Set-Cookie</code> headers generated
+        by the <code>Rfc6265CookieProcessor</code> are aligned with the
+        specification. Patch provided by Jim Griswold. (markt)
+      </fix>
+      <fix>
+        <bug>60882</bug>: Fix a <code>NullPointerException</code> when obtaining
+        a <code>RequestDispatcher</code> for a request that will not have any
+        pathInfo associated with it. This was a regression in the changes in
+        8.5.12 for the Servlet 4.0 API early preview changes. (markt)
+      </fix>
+      <update>
+        Align <code>PushBuilder</code> API with changes from Servlet expert
+        group. (markt)
+      </update>
+      <scode>
+        Refactor the various implementations of X to comma separated list to a
+        single utility class and update the code to use the new utility class.
+        (markt)
+      </scode>
+      <fix>
+        <bug>60911</bug>: Ensure NPE will not be thrown when looking for SSL
+        session ID. Based on a patch by Didier Gutacker. (violetagg)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <add>
+        <bug>60362</bug>: Add a new Connector configuration
+        <code>sendReasonPhrase</code>. When this attribute is set to
+        <code>true</code>, a reason phrase will be sent with the response.
+        By default a reason phrase will not be sent. This option is deprecated
+        and is not available in Tomcat 9. (violetagg)
+      </add>
+      <fix>
+        Fix HTTP/2 incorrect input unblocking on EOF. (remm)
+      </fix>
+      <fix>
+        Close the connection sooner if an event occurs for a current connection
+        that is not consistent with the current state of that connection.
+        (markt)
+      </fix>
+      <fix>
+        Speed up shutdown when using multiple acceptor threads by ensuring that
+        the code that unlocks the acceptor threads correctly handles the case
+        where there are multiple threads. (markt)
+      </fix>
+      <fix>
+        <bug>60852</bug>: Correctly spell compressible when used in
+        configuration attributes and internal code. Based on a patch by Michael
+        Osipov. (markt)
+      </fix>
+      <fix>
+        <bug>60900</bug>: Avoid a <code>NullPointerException</code> in the APR
+        Poller if a connection is closed at the same time as new data arrives on
+        that connection. (markt)
+      </fix>
+      <fix>
+        Improve HPACK specification compliance by fixing some test failures
+        reported by the h2spec tool written by Moto Ishizawa. (markt)
+      </fix>
+      <fix>
+        Improve HTTP/2 specification compliance by fixing some test failures
+        reported by the h2spec tool written by Moto Ishizawa. (markt)
+      </fix>
+      <fix>
+        <bug>60918</bug>: Fix sendfile processing error that could lead to
+        subsequent requests experiencing an <code>IllegalStateException</code>.
+        (markt)
+      </fix>
+      <fix>
+        Improve sendfile handling when requests are pipelined. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        Improve the error handling for simple tags to ensure that the tag is
+        released and destroyed once used. (remm, violetagg)
+      </fix>
+      <fix>
+        <bug>60844</bug>: Correctly handle the error when fewer parameter values
+        than required by the method are used to invoke an EL method expression.
+        Patch provided by Daniel Gray. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="jdbc-pool">
+    <changelog>
+      <fix>
+        <bug>60764</bug>: Implement <code>equals()</code> and
+        <code>hashCode()</code> in the <code>StatementFacade</code> in order to
+        enable these methods to be called on the closed statements if any
+        statement proxy is set. This behavior can be changed with
+        <code>useStatementFacade</code> attribute. (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        Refactor the build script and the NSIS installer script so that either
+        NSIS 2.x or NSIS 3.x can be used to build the installer. This is
+        primarily to re-enable building the installer on the Linux based CI
+        system where the combination of NSIS 3.x and wine leads to failed
+        installer builds. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.5.12 (markt)" rtext="2017-03-13">
   <subsection name="Catalina">
     <changelog>
       <fix>
@@ -180,12 +413,6 @@
         <strong>Connector</strong>. (markt)
       </fix>
       <fix>
-        <bug>60627</bug>: Modify the <code>Rfc6265CookieProcessor</code> so that
-        in addition to cookie headers that start with an explicit RFC 2109
-        <code>$Version=1</code>, cookies that start with <code>$Version=0</code>
-        are also parsed as RFC 2109 cookies. (markt)
-      </fix>
-      <fix>
         Include the value of <code>SslHostConfig.truststoreAlgorithm</code> when
         warning that the algorithm does not support the
         <code>certificateVerificationDepth</code> configuration option. (markt)
@@ -201,6 +428,12 @@
         (csutherl)
       </add>
       <fix>
+        <bug>60627</bug>: Modify the <code>Rfc6265CookieProcessor</code> so that
+        in addition to cookie headers that start with an explicit RFC 2109
+        <code>$Version=1</code>, cookies that start with <code>$Version=0</code>
+        are also parsed as RFC 2109 cookies. (markt)
+      </fix>
+      <fix>
         <bug>60716</bug>: Add a new JSSE specific attribute,
         <code>revocationEnabled</code>, to <code>SSLHostConfig</code> to permit
         JSSE provider revocation checks to be enabled when no
diff --git a/webapps/docs/config/ajp.xml b/webapps/docs/config/ajp.xml
index 160acf39..68c7620b 100644
--- a/webapps/docs/config/ajp.xml
+++ b/webapps/docs/config/ajp.xml
@@ -236,6 +236,14 @@
       The default value is <code>false</code>.</p>
     </attribute>
 
+    <attribute name="sendReasonPhrase" required="false">
+      <p>Set this attribute to <code>true</code> if you wish to have
+      a reason phrase in the response.
+      The default value is <code>false</code>.</p>
+      <p><strong>Note:</strong> This option is deprecated and will be removed
+      in Tomcat 9. The reason phrase will not be sent.</p>
+    </attribute>
+
     <attribute name="URIEncoding" required="false">
       <p>This specifies the character encoding used to decode the URI bytes,
       after %xx decoding the URL. If not specified, UTF-8 will be used unless
diff --git a/webapps/docs/config/filter.xml b/webapps/docs/config/filter.xml
index ef34436a..77d6537f 100644
--- a/webapps/docs/config/filter.xml
+++ b/webapps/docs/config/filter.xml
@@ -899,6 +899,13 @@ FINE: Request "/docs/config/manager.html" with response status "200"
         be used.</p>
       </attribute>
 
+      <attribute name="hstsPreload" required="false">
+        <p>Should the preload parameter be included in the HSTS header. If not
+        specified, the default value of <code>false</code> will be used. See
+        <a href="https://hstspreload.org/";>https://hstspreload.org</a> for
+        important information about this parameter.</p>
+      </attribute>
+
       <attribute name="antiClickJackingEnabled" required="false">
         <p>Should the anti click-jacking header (<code>X-Frame-Options</code>)
         be set on the response. Any anti click-jacking header already present
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index d726c0c1..076c6289 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -236,6 +236,14 @@
       The default value is <code>false</code>.</p>
     </attribute>
 
+    <attribute name="sendReasonPhrase" required="false">
+      <p>Set this attribute to <code>true</code> if you wish to have
+      a reason phrase in the response.
+      The default value is <code>false</code>.</p>
+      <p><strong>Note:</strong> This option is deprecated and will be removed
+      in Tomcat 9. The reason phrase will not be sent.</p>
+    </attribute>
+
     <attribute name="URIEncoding" required="false">
       <p>This specifies the character encoding used to decode the URI bytes,
       after %xx decoding the URL. If not specified, UTF-8 will be used unless
@@ -347,7 +355,7 @@
       provider will be used.</p>
     </attribute>
 
-    <attribute name="compressableMimeType" required="false">
+    <attribute name="compressibleMimeType" required="false">
       <p>The value is a comma separated list of MIME types for which HTTP
       compression may be used.
       The default value is
diff --git a/webapps/docs/config/systemprops.xml b/webapps/docs/config/systemprops.xml
index 38a2e097..3d93231f 100644
--- a/webapps/docs/config/systemprops.xml
+++ b/webapps/docs/config/systemprops.xml
@@ -553,6 +553,17 @@
 
   <properties>
 
+    <property
+    name="org.apache.coyote. USE_CUSTOM_STATUS_MSG_IN_HEADER"><p>If this is
+      <code>true</code>, custom HTTP status messages will be used within HTTP
+      headers. If a custom message is specified that is not valid for use in an
+      HTTP header (as defined by RFC2616) then the custom message will be
+      ignored and the default message used.</p>
+      <p>If not specified, the default value of <code>false</code> will be used.</p>
+      <p><strong>Note:</strong> This option is deprecated and will be removed
+      in Tomcat 9. The reason phrase will not be sent.</p>
+    </property>
+
     <property name="catalina.useNaming">
       <p>If this is <code>false</code> it will override the
       <code>useNaming</code> attribute for all <a href="context.html">
diff --git a/webapps/examples/WEB-INF/classes/http2/SimpleImagePush.java b/webapps/examples/WEB-INF/classes/http2/SimpleImagePush.java
index 6bb2dae8..c78819f5 100644
--- a/webapps/examples/WEB-INF/classes/http2/SimpleImagePush.java
+++ b/webapps/examples/WEB-INF/classes/http2/SimpleImagePush.java
@@ -34,13 +34,16 @@ public class SimpleImagePush extends HttpServlet {
     protected void doGet(HttpServletRequest req, HttpServletResponse resp)
             throws ServletException, IOException {
 
-        PushBuilder pb = ((org.apache.catalina.servlet4preview.http.HttpServletRequest)
-                req).getPushBuilder().path("servlets/images/code.gif");
-        pb.push();
-
         resp.setCharacterEncoding("UTF-8");
         resp.setContentType("text/html");
         PrintWriter pw = resp.getWriter();
+
+        PushBuilder pb = ((org.apache.catalina.servlet4preview.http.HttpServletRequest)
+                req).newPushBuilder();
+
+        if (pb != null) {
+            pb.path("servlets/images/code.gif");
+            pb.push();
             pw.println("<html>");
             pw.println("<body>");
             pw.println("<p>The following image was provided via a push request.</p>");
@@ -48,5 +51,12 @@ public class SimpleImagePush extends HttpServlet {
             pw.println("</body>");
             pw.println("</html>");
             pw.flush();
+        } else {
+            pw.println("<html>");
+            pw.println("<body>");
+            pw.println("<p>Server push requests are not supported by this protocol.</p>");
+            pw.println("</body>");
+            pw.println("</html>");
+        }
     }
 }

--- End Message ---
--- Begin Message ---
Unblocked tomcat8.

--- End Message ---

Reply to: