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

Bug#861681: unblock: tomcat8/8.5.14-1



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>");
+        }
     }
 }

Reply to: