9e6f2c
--- java/org/apache/catalina/servlets/DefaultServlet.java.orig	2017-10-13 09:41:05.734302404 -0400
9e6f2c
+++ java/org/apache/catalina/servlets/DefaultServlet.java	2017-10-13 09:42:53.515701311 -0400
9e6f2c
@@ -855,23 +855,6 @@
9e6f2c
             return;
9e6f2c
         }
9e6f2c
 
9e6f2c
-        // If the resource is not a collection, and the resource path
9e6f2c
-        // ends with "/" or "\", return NOT FOUND
9e6f2c
-        if (cacheEntry.context == null) {
9e6f2c
-            if (path.endsWith("/") || (path.endsWith("\\"))) {
9e6f2c
-                // Check if we're included so we can return the appropriate
9e6f2c
-                // missing resource name in the error
9e6f2c
-                String requestUri = (String) request.getAttribute(
9e6f2c
-                        RequestDispatcher.INCLUDE_REQUEST_URI);
9e6f2c
-                if (requestUri == null) {
9e6f2c
-                    requestUri = request.getRequestURI();
9e6f2c
-                }
9e6f2c
-                response.sendError(HttpServletResponse.SC_NOT_FOUND,
9e6f2c
-                                   requestUri);
9e6f2c
-                return;
9e6f2c
-            }
9e6f2c
-        }
9e6f2c
-
9e6f2c
         boolean isError = DispatcherType.ERROR == request.getDispatcherType();
9e6f2c
 
9e6f2c
         // Check if the conditions specified in the optional If headers are
9e6f2c
--- java/org/apache/naming/resources/FileDirContext.java.orig	2017-10-13 09:41:05.737302387 -0400
9e6f2c
+++ java/org/apache/naming/resources/FileDirContext.java	2017-10-13 09:42:53.516701306 -0400
9e6f2c
@@ -14,8 +14,6 @@
9e6f2c
  * See the License for the specific language governing permissions and
9e6f2c
  * limitations under the License.
9e6f2c
  */
9e6f2c
-
9e6f2c
-
9e6f2c
 package org.apache.naming.resources;
9e6f2c
 
9e6f2c
 import java.io.File;
9e6f2c
@@ -75,6 +73,8 @@
9e6f2c
 
9e6f2c
     /**
9e6f2c
      * Builds a file directory context using the given environment.
9e6f2c
+     *
9e6f2c
+     * @param env The environment with which to build the context
9e6f2c
      */
9e6f2c
     public FileDirContext(Hashtable<String,Object> env) {
9e6f2c
         super(env);
9e6f2c
@@ -95,6 +95,8 @@
9e6f2c
      */
9e6f2c
     protected String absoluteBase = null;
9e6f2c
 
9e6f2c
+    private String canonicalBase = null;
9e6f2c
+
9e6f2c
 
9e6f2c
     /**
9e6f2c
      * Allow linking.
9e6f2c
@@ -104,7 +106,6 @@
9e6f2c
 
9e6f2c
     // ------------------------------------------------------------- Properties
9e6f2c
 
9e6f2c
-
9e6f2c
     /**
9e6f2c
      * Set the document root.
9e6f2c
      *
9e6f2c
@@ -117,32 +118,41 @@
9e6f2c
      */
9e6f2c
     @Override
9e6f2c
     public void setDocBase(String docBase) {
9e6f2c
+        // Validate the format of the proposed document root
9e6f2c
+        if (docBase == null) {
9e6f2c
+            throw new IllegalArgumentException(sm.getString("resources.null"));
9e6f2c
+        }
9e6f2c
 
9e6f2c
-    // Validate the format of the proposed document root
9e6f2c
-    if (docBase == null)
9e6f2c
-        throw new IllegalArgumentException
9e6f2c
-        (sm.getString("resources.null"));
9e6f2c
-
9e6f2c
-    // Calculate a File object referencing this document base directory
9e6f2c
-    base = new File(docBase);
9e6f2c
+        // Calculate a File object referencing this document base directory
9e6f2c
+        base = new File(docBase);
9e6f2c
         try {
9e6f2c
             base = base.getCanonicalFile();
9e6f2c
         } catch (IOException e) {
9e6f2c
             // Ignore
9e6f2c
         }
9e6f2c
 
9e6f2c
-    // Validate that the document base is an existing directory
9e6f2c
-    if (!base.exists() || !base.isDirectory() || !base.canRead())
9e6f2c
-        throw new IllegalArgumentException
9e6f2c
-        (sm.getString("fileResources.base", docBase));
9e6f2c
-        this.absoluteBase = base.getAbsolutePath();
9e6f2c
-        super.setDocBase(docBase);
9e6f2c
+        // Validate that the document base is an existing directory
9e6f2c
+        if (!base.exists() || !base.isDirectory() || !base.canRead()) {
9e6f2c
+            throw new IllegalArgumentException(sm.getString("fileResources.base", docBase));
9e6f2c
+        }
9e6f2c
 
9e6f2c
+        this.absoluteBase = normalize(base.getAbsolutePath());
9e6f2c
+
9e6f2c
+        // absoluteBase also needs to be normalized. Using the canonical path is
9e6f2c
+        // the simplest way of doing this.
9e6f2c
+        try {
9e6f2c
+            this.canonicalBase = base.getCanonicalPath();
9e6f2c
+        } catch (IOException e) {
9e6f2c
+            throw new IllegalArgumentException(e);
9e6f2c
+        }
9e6f2c
+        super.setDocBase(docBase);
9e6f2c
     }
9e6f2c
 
9e6f2c
 
9e6f2c
     /**
9e6f2c
      * Set allow linking.
9e6f2c
+     *
9e6f2c
+     * @param allowLinking The new value for the attribute
9e6f2c
      */
9e6f2c
     public void setAllowLinking(boolean allowLinking) {
9e6f2c
         this.allowLinking = allowLinking;
9e6f2c
@@ -151,6 +161,8 @@
9e6f2c
 
9e6f2c
     /**
9e6f2c
      * Is linking allowed.
9e6f2c
+     *
9e6f2c
+     * @return {@code true} is linking is allowed, otherwise {@false}
9e6f2c
      */
9e6f2c
     public boolean getAllowLinking() {
9e6f2c
         return allowLinking;
9e6f2c
@@ -193,7 +205,7 @@
9e6f2c
     @Override
9e6f2c
     protected Object doLookup(String name) {
9e6f2c
         Object result = null;
9e6f2c
-        File file = file(name);
9e6f2c
+        File file = file(name, true);
9e6f2c
 
9e6f2c
         if (file == null)
9e6f2c
             return null;
9e6f2c
@@ -230,7 +242,7 @@
9e6f2c
     public void unbind(String name)
9e6f2c
         throws NamingException {
9e6f2c
 
9e6f2c
-        File file = file(name);
9e6f2c
+        File file = file(name, true);
9e6f2c
 
9e6f2c
         if (file == null)
9e6f2c
             throw new NameNotFoundException(
9e6f2c
@@ -255,22 +267,22 @@
9e6f2c
      * @exception NamingException if a naming exception is encountered
9e6f2c
      */
9e6f2c
     @Override
9e6f2c
-    public void rename(String oldName, String newName)
9e6f2c
-        throws NamingException {
9e6f2c
+    public void rename(String oldName, String newName) throws NamingException {
9e6f2c
 
9e6f2c
-        File file = file(oldName);
9e6f2c
+        File file = file(oldName, true);
9e6f2c
 
9e6f2c
-        if (file == null)
9e6f2c
-            throw new NameNotFoundException
9e6f2c
-                (sm.getString("resources.notFound", oldName));
9e6f2c
+        if (file == null) {
9e6f2c
+            throw new NameNotFoundException(sm.getString("resources.notFound", oldName));
9e6f2c
+        }
9e6f2c
 
9e6f2c
-        File newFile = new File(base, newName);
9e6f2c
+        File newFile = file(newName, false);
9e6f2c
+        if (newFile == null) {
9e6f2c
+            throw new NamingException(sm.getString("resources.renameFail", oldName, newName));
9e6f2c
+        }
9e6f2c
 
9e6f2c
         if (!file.renameTo(newFile)) {
9e6f2c
-            throw new NamingException(sm.getString("resources.renameFail",
9e6f2c
-                    oldName, newName));
9e6f2c
+            throw new NamingException(sm.getString("resources.renameFail", oldName, newName));
9e6f2c
         }
9e6f2c
-
9e6f2c
     }
9e6f2c
 
9e6f2c
 
9e6f2c
@@ -291,11 +303,11 @@
9e6f2c
     protected List<NamingEntry> doListBindings(String name)
9e6f2c
         throws NamingException {
9e6f2c
 
9e6f2c
-        File file = file(name);
9e6f2c
+        File file = file(name, true);
9e6f2c
 
9e6f2c
         if (file == null)
9e6f2c
             return null;
9e6f2c
-        
9e6f2c
+
9e6f2c
         return list(file);
9e6f2c
 
9e6f2c
     }
9e6f2c
@@ -395,7 +407,7 @@
9e6f2c
         throws NamingException {
9e6f2c
 
9e6f2c
         // Building attribute list
9e6f2c
-        File file = file(name);
9e6f2c
+        File file = file(name, true);
9e6f2c
 
9e6f2c
         if (file == null)
9e6f2c
             return null;
9e6f2c
@@ -463,12 +475,20 @@
9e6f2c
      * @exception NamingException if a naming exception is encountered
9e6f2c
      */
9e6f2c
     @Override
9e6f2c
-    public void bind(String name, Object obj, Attributes attrs)
9e6f2c
-        throws NamingException {
9e6f2c
+    public void bind(String name, Object obj, Attributes attrs) throws NamingException {
9e6f2c
 
9e6f2c
         // Note: No custom attributes allowed
9e6f2c
 
9e6f2c
-        File file = new File(base, name);
9e6f2c
+        // bind() is meant to create a file so ensure that the path doesn't end
9e6f2c
+        // in '/'
9e6f2c
+        if (name.endsWith("/")) {
9e6f2c
+            throw new NamingException(sm.getString("resources.bindFailed", name));
9e6f2c
+        }
9e6f2c
+
9e6f2c
+        File file = file(name, false);
9e6f2c
+        if (file == null) {
9e6f2c
+            throw new NamingException(sm.getString("resources.bindFailed", name));
9e6f2c
+        }
9e6f2c
         if (file.exists())
9e6f2c
             throw new NameAlreadyBoundException
9e6f2c
                 (sm.getString("resources.alreadyBound", name));
9e6f2c
@@ -503,7 +523,10 @@
9e6f2c
         // Note: No custom attributes allowed
9e6f2c
         // Check obj type
9e6f2c
 
9e6f2c
-        File file = new File(base, name);
9e6f2c
+        File file = file(name, false);
9e6f2c
+        if (file == null) {
9e6f2c
+            throw new NamingException(sm.getString("resources.bindFailed", name));
9e6f2c
+        }
9e6f2c
 
9e6f2c
         InputStream is = null;
9e6f2c
         if (obj instanceof Resource) {
9e6f2c
@@ -583,13 +606,14 @@
9e6f2c
     public DirContext createSubcontext(String name, Attributes attrs)
9e6f2c
         throws NamingException {
9e6f2c
 
9e6f2c
-        File file = new File(base, name);
9e6f2c
+        File file = file(name, false);
9e6f2c
+        if (file == null) {
9e6f2c
+            throw new NamingException(sm.getString("resources.bindFailed", name));
9e6f2c
+        }
9e6f2c
         if (file.exists())
9e6f2c
-            throw new NameAlreadyBoundException
9e6f2c
-                (sm.getString("resources.alreadyBound", name));
9e6f2c
+            throw new NameAlreadyBoundException(sm.getString("resources.alreadyBound", name));
9e6f2c
         if (!file.mkdir())
9e6f2c
-            throw new NamingException
9e6f2c
-                (sm.getString("resources.bindFailed", name));
9e6f2c
+            throw new NamingException(sm.getString("resources.bindFailed", name));
9e6f2c
         return (DirContext) lookup(name);
9e6f2c
 
9e6f2c
     }
9e6f2c
@@ -758,6 +782,7 @@
9e6f2c
 
9e6f2c
     }
9e6f2c
 
9e6f2c
+
9e6f2c
     /**
9e6f2c
      * Return a File object representing the specified normalized
9e6f2c
      * context-relative path if it exists and is readable.  Otherwise,
9e6f2c
@@ -766,51 +791,133 @@
9e6f2c
      * @param name Normalized context-relative path (with leading '/')
9e6f2c
      */
9e6f2c
     protected File file(String name) {
9e6f2c
+        return file(name, true);
9e6f2c
+    }
9e6f2c
+
9e6f2c
+
9e6f2c
+    /**
9e6f2c
+     * Return a File object representing the specified normalized
9e6f2c
+     * context-relative path if it exists and is readable.  Otherwise,
9e6f2c
+     * return null.
9e6f2c
+     *
9e6f2c
+     * @param name      Normalized context-relative path (with leading '/')
9e6f2c
+     * @param mustExist Must the specified resource exist?
9e6f2c
+     */
9e6f2c
+    protected File file(String name, boolean mustExist) {
9e6f2c
+        if (name.equals("/")) {
9e6f2c
+            name = "";
9e6f2c
+        }
9e6f2c
 
9e6f2c
         File file = new File(base, name);
9e6f2c
-        if (file.exists() && file.canRead()) {
9e6f2c
+        return validate(file, name, mustExist, absoluteBase, canonicalBase);
9e6f2c
+    }
9e6f2c
 
9e6f2c
-            if (allowLinking)
9e6f2c
-                return file;
9e6f2c
-            
9e6f2c
-            // Check that this file belongs to our root path
9e6f2c
-            String canPath = null;
9e6f2c
-            try {
9e6f2c
-                canPath = file.getCanonicalPath();
9e6f2c
-            } catch (IOException e) {
9e6f2c
-                // Ignore
9e6f2c
-            }
9e6f2c
-            if (canPath == null)
9e6f2c
-                return null;
9e6f2c
 
9e6f2c
-            // Check to see if going outside of the web application root
9e6f2c
-            if (!canPath.startsWith(absoluteBase)) {
9e6f2c
-                return null;
9e6f2c
-            }
9e6f2c
+    protected File validate(File file, String name, boolean mustExist, String absoluteBase,
9e6f2c
+            String canonicalBase) {
9e6f2c
 
9e6f2c
-            // Case sensitivity check - this is now always done
9e6f2c
-            String fileAbsPath = file.getAbsolutePath();
9e6f2c
-            if (fileAbsPath.endsWith("."))
9e6f2c
-                fileAbsPath = fileAbsPath + "/";
9e6f2c
-            String absPath = normalize(fileAbsPath);
9e6f2c
-            canPath = normalize(canPath);
9e6f2c
-            if ((absoluteBase.length() < absPath.length())
9e6f2c
-                && (absoluteBase.length() < canPath.length())) {
9e6f2c
-                absPath = absPath.substring(absoluteBase.length() + 1);
9e6f2c
-                if (absPath.equals(""))
9e6f2c
-                    absPath = "/";
9e6f2c
-                canPath = canPath.substring(absoluteBase.length() + 1);
9e6f2c
-                if (canPath.equals(""))
9e6f2c
-                    canPath = "/";
9e6f2c
-                if (!canPath.equals(absPath))
9e6f2c
-                    return null;
9e6f2c
-            }
9e6f2c
+        // If the requested names ends in '/', the Java File API will return a
9e6f2c
+        // matching file if one exists. This isn't what we want as it is not
9e6f2c
+        // consistent with the Servlet spec rules for request mapping.
9e6f2c
+        if (name.endsWith("/") && file.isFile()) {
9e6f2c
+            return null;
9e6f2c
+        }
9e6f2c
 
9e6f2c
-        } else {
9e6f2c
+        // If the file/dir must exist but the identified file/dir can't be read
9e6f2c
+        // then signal that the resource was not found
9e6f2c
+        if (mustExist && !file.canRead()) {
9e6f2c
+            return null;
9e6f2c
+        }
9e6f2c
+
9e6f2c
+        // If allow linking is enabled, files are not limited to being located
9e6f2c
+        // under the fileBase so all further checks are disabled.
9e6f2c
+        if (allowLinking) {
9e6f2c
+            return file;
9e6f2c
+        }
9e6f2c
+
9e6f2c
+        // Additional Windows specific checks to handle known problems with
9e6f2c
+        // File.getCanonicalPath()
9e6f2c
+        if (JrePlatform.IS_WINDOWS && isInvalidWindowsFilename(name)) {
9e6f2c
+            return null;
9e6f2c
+        }
9e6f2c
+
9e6f2c
+        // Check that this file is located under the web application root
9e6f2c
+        String canPath = null;
9e6f2c
+        try {
9e6f2c
+            canPath = file.getCanonicalPath();
9e6f2c
+        } catch (IOException e) {
9e6f2c
+            // Ignore
9e6f2c
+        }
9e6f2c
+        if (canPath == null || !canPath.startsWith(canonicalBase)) {
9e6f2c
+            return null;
9e6f2c
+        }
9e6f2c
+
9e6f2c
+        // Ensure that the file is not outside the fileBase. This should not be
9e6f2c
+        // possible for standard requests (the request is normalized early in
9e6f2c
+        // the request processing) but might be possible for some access via the
9e6f2c
+        // Servlet API (RequestDispatcher etc.) therefore these checks are
9e6f2c
+        // retained as an additional safety measure. absoluteBase has been
9e6f2c
+        // normalized so absPath needs to be normalized as well.
9e6f2c
+        String absPath = normalize(file.getAbsolutePath());
9e6f2c
+        if ((absoluteBase.length() > absPath.length())) {
9e6f2c
             return null;
9e6f2c
         }
9e6f2c
+
9e6f2c
+        // Remove the fileBase location from the start of the paths since that
9e6f2c
+        // was not part of the requested path and the remaining check only
9e6f2c
+        // applies to the request path
9e6f2c
+        absPath = absPath.substring(absoluteBase.length());
9e6f2c
+        canPath = canPath.substring(canonicalBase.length());
9e6f2c
+
9e6f2c
+        // Case sensitivity check
9e6f2c
+        // The normalized requested path should be an exact match the equivalent
9e6f2c
+        // canonical path. If it is not, possible reasons include:
9e6f2c
+        // - case differences on case insensitive file systems
9e6f2c
+        // - Windows removing a trailing ' ' or '.' from the file name
9e6f2c
+        //
9e6f2c
+        // In all cases, a mis-match here results in the resource not being
9e6f2c
+        // found
9e6f2c
+        //
9e6f2c
+        // absPath is normalized so canPath needs to be normalized as well
9e6f2c
+        // Can't normalize canPath earlier as canonicalBase is not normalized
9e6f2c
+        if (canPath.length() > 0) {
9e6f2c
+            canPath = normalize(canPath);
9e6f2c
+        }
9e6f2c
+        if (!canPath.equals(absPath)) {
9e6f2c
+            return null;
9e6f2c
+        }
9e6f2c
+
9e6f2c
         return file;
9e6f2c
+    }
9e6f2c
+
9e6f2c
 
9e6f2c
+    private boolean isInvalidWindowsFilename(String name) {
9e6f2c
+        final int len = name.length();
9e6f2c
+        if (len == 0) {
9e6f2c
+            return false;
9e6f2c
+        }
9e6f2c
+        // This consistently ~10 times faster than the equivalent regular
9e6f2c
+        // expression irrespective of input length.
9e6f2c
+        for (int i = 0; i < len; i++) {
9e6f2c
+            char c = name.charAt(i);
9e6f2c
+            if (c == '\"' || c == '<' || c == '>') {
9e6f2c
+                // These characters are disallowed in Windows file names and
9e6f2c
+                // there are known problems for file names with these characters
9e6f2c
+                // when using File#getCanonicalPath().
9e6f2c
+                // Note: There are additional characters that are disallowed in
9e6f2c
+                //       Windows file names but these are not known to cause
9e6f2c
+                //       problems when using File#getCanonicalPath().
9e6f2c
+                return true;
9e6f2c
+            }
9e6f2c
+        }
9e6f2c
+        // Windows does not allow file names to end in ' ' unless specific low
9e6f2c
+        // level APIs are used to create the files that bypass various checks.
9e6f2c
+        // File names that end in ' ' are known to cause problems when using
9e6f2c
+        // File#getCanonicalPath().
9e6f2c
+        if (name.charAt(len -1) == ' ') {
9e6f2c
+            return true;
9e6f2c
+        }
9e6f2c
+        return false;
9e6f2c
     }
9e6f2c
 
9e6f2c
 
9e6f2c
@@ -1054,10 +1161,10 @@
9e6f2c
             return super.getResourceType();
9e6f2c
         }
9e6f2c
 
9e6f2c
-        
9e6f2c
+
9e6f2c
         /**
9e6f2c
          * Get canonical path.
9e6f2c
-         * 
9e6f2c
+         *
9e6f2c
          * @return String the file's canonical path
9e6f2c
          */
9e6f2c
         @Override
9e6f2c
@@ -1071,10 +1178,6 @@
9e6f2c
             }
9e6f2c
             return canonicalPath;
9e6f2c
         }
9e6f2c
-        
9e6f2c
-
9e6f2c
     }
9e6f2c
-
9e6f2c
-
9e6f2c
 }
9e6f2c
 
9e6f2c
--- java/org/apache/naming/resources/VirtualDirContext.java.orig	2017-10-13 09:41:05.740302370 -0400
9e6f2c
+++ java/org/apache/naming/resources/VirtualDirContext.java	2017-10-13 09:42:53.517701300 -0400
9e6f2c
@@ -76,7 +76,8 @@
9e6f2c
      * be listed twice.
9e6f2c
      * 

9e6f2c
      *
9e6f2c
-     * @param path
9e6f2c
+     * @param path The set of file system paths and virtual paths to map them to
9e6f2c
+     *             in the required format
9e6f2c
      */
9e6f2c
     public void setExtraResourcePaths(String path) {
9e6f2c
         extraResourcePaths = path;
9e6f2c
@@ -106,13 +107,13 @@
9e6f2c
                     }
9e6f2c
                     path = resSpec.substring(0, idx);
9e6f2c
                 }
9e6f2c
-                String dir = resSpec.substring(idx + 1);
9e6f2c
+                File dir = new File(resSpec.substring(idx + 1));
9e6f2c
                 List<String> resourcePaths = mappedResourcePaths.get(path);
9e6f2c
                 if (resourcePaths == null) {
9e6f2c
                     resourcePaths = new ArrayList<String>();
9e6f2c
                     mappedResourcePaths.put(path, resourcePaths);
9e6f2c
                 }
9e6f2c
-                resourcePaths.add(dir);
9e6f2c
+                resourcePaths.add(dir.getAbsolutePath());
9e6f2c
             }
9e6f2c
         }
9e6f2c
         if (mappedResourcePaths.isEmpty()) {
9e6f2c
@@ -151,15 +152,17 @@
9e6f2c
                 String resourcesDir = dirList.get(0);
9e6f2c
                 if (name.equals(path)) {
9e6f2c
                     File f = new File(resourcesDir);
9e6f2c
-                    if (f.exists() && f.canRead()) {
9e6f2c
+                    f = validate(f, name, true, resourcesDir);
9e6f2c
+                    if (f != null) {
9e6f2c
                         return new FileResourceAttributes(f);
9e6f2c
                     }
9e6f2c
                 }
9e6f2c
                 path += "/";
9e6f2c
                 if (name.startsWith(path)) {
9e6f2c
                     String res = name.substring(path.length());
9e6f2c
-                    File f = new File(resourcesDir + "/" + res);
9e6f2c
-                    if (f.exists() && f.canRead()) {
9e6f2c
+                    File f = new File(resourcesDir, res);
9e6f2c
+                    f = validate(f, res, true, resourcesDir);
9e6f2c
+                    if (f != null) {
9e6f2c
                         return new FileResourceAttributes(f);
9e6f2c
                     }
9e6f2c
                 }
9e6f2c
@@ -168,9 +171,16 @@
9e6f2c
         throw initialException;
9e6f2c
     }
9e6f2c
 
9e6f2c
+
9e6f2c
     @Override
9e6f2c
     protected File file(String name) {
9e6f2c
-        File file = super.file(name);
9e6f2c
+        return file(name, true);
9e6f2c
+    }
9e6f2c
+
9e6f2c
+
9e6f2c
+    @Override
9e6f2c
+    protected File file(String name, boolean mustExist) {
9e6f2c
+        File file = super.file(name, true);
9e6f2c
         if (file != null || mappedResourcePaths == null) {
9e6f2c
             return file;
9e6f2c
         }
9e6f2c
@@ -185,7 +195,8 @@
9e6f2c
             if (name.equals(path)) {
9e6f2c
                 for (String resourcesDir : dirList) {
9e6f2c
                     file = new File(resourcesDir);
9e6f2c
-                    if (file.exists() && file.canRead()) {
9e6f2c
+                    file = validate(file, name, true, resourcesDir);
9e6f2c
+                    if (file != null) {
9e6f2c
                         return file;
9e6f2c
                     }
9e6f2c
                 }
9e6f2c
@@ -194,7 +205,8 @@
9e6f2c
                 String res = name.substring(path.length());
9e6f2c
                 for (String resourcesDir : dirList) {
9e6f2c
                     file = new File(resourcesDir, res);
9e6f2c
-                    if (file.exists() && file.canRead()) {
9e6f2c
+                    file = validate(file, res, true, resourcesDir);
9e6f2c
+                    if (file != null) {
9e6f2c
                         return file;
9e6f2c
                     }
9e6f2c
                 }
9e6f2c
@@ -229,7 +241,8 @@
9e6f2c
                     if (res != null) {
9e6f2c
                         for (String resourcesDir : dirList) {
9e6f2c
                             File f = new File(resourcesDir, res);
9e6f2c
-                            if (f.exists() && f.canRead() && f.isDirectory()) {
9e6f2c
+                            f = validate(f, res, true, resourcesDir);
9e6f2c
+                            if (f != null && f.isDirectory()) {
9e6f2c
                                 List<NamingEntry> virtEntries = super.list(f);
9e6f2c
                                 for (NamingEntry entry : virtEntries) {
9e6f2c
                                     // filter duplicate
9e6f2c
@@ -264,7 +277,8 @@
9e6f2c
             if (name.equals(path)) {
9e6f2c
                 for (String resourcesDir : dirList) {
9e6f2c
                     File f = new File(resourcesDir);
9e6f2c
-                    if (f.exists() && f.canRead()) {
9e6f2c
+                    f = validate(f, name, true, resourcesDir);
9e6f2c
+                    if (f != null) {
9e6f2c
                         if (f.isFile()) {
9e6f2c
                             return new FileResource(f);
9e6f2c
                         }
9e6f2c
@@ -279,8 +293,9 @@
9e6f2c
             if (name.startsWith(path)) {
9e6f2c
                 String res = name.substring(path.length());
9e6f2c
                 for (String resourcesDir : dirList) {
9e6f2c
-                    File f = new File(resourcesDir + "/" + res);
9e6f2c
-                    if (f.exists() && f.canRead()) {
9e6f2c
+                    File f = new File(resourcesDir, res);
9e6f2c
+                    f = validate(f, res, true, resourcesDir);
9e6f2c
+                    if (f != null) {
9e6f2c
                         if (f.isFile()) {
9e6f2c
                             return new FileResource(f);
9e6f2c
                         }
9e6f2c
@@ -304,4 +319,9 @@
9e6f2c
             return null;
9e6f2c
         }
9e6f2c
     }
9e6f2c
+
9e6f2c
+
9e6f2c
+    protected File validate(File file, String name, boolean mustExist, String absoluteBase) {
9e6f2c
+        return validate(file, name, mustExist, normalize(absoluteBase), absoluteBase);
9e6f2c
+    }
9e6f2c
 }
9e6f2c
--- webapps/docs/changelog.xml.orig	2017-10-13 09:15:35.996884086 -0400
9e6f2c
+++ webapps/docs/changelog.xml	2017-10-13 09:44:50.895046977 -0400
9e6f2c
@@ -64,6 +64,14 @@
9e6f2c
         <bug>61101</bug>: CORS filter should set Vary header in response.
9e6f2c
         Submitted by Rick Riemer. (remm)
9e6f2c
       </fix>
9e6f2c
+      <fix>
9e6f2c
+        Correct regression in 7.0.80 that broke WebDAV. (markt)
9e6f2c
+      </fix>
9e6f2c
+      <fix>
9e6f2c
+        <bug>61542</bug>: Fix CVE-2017-12617 and prevent JSPs from being
9e6f2c
+        uploaded via a specially crafted request when HTTP PUT was enabled.
9e6f2c
+        (markt)
9e6f2c
+      </fix>
9e6f2c
     </changelog>
9e6f2c
   </subsection>
9e6f2c
 </section>
9e6f2c
--- java/org/apache/naming/resources/JrePlatform.java.orig	2017-10-13 09:41:05.745302342 -0400
9e6f2c
+++ java/org/apache/naming/resources/JrePlatform.java	2017-10-13 09:42:53.516701306 -0400
9e6f2c
@@ -0,0 +1,59 @@
9e6f2c
+/*
9e6f2c
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
9e6f2c
+ *  contributor license agreements.  See the NOTICE file distributed with
9e6f2c
+ *  this work for additional information regarding copyright ownership.
9e6f2c
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
9e6f2c
+ *  (the "License"); you may not use this file except in compliance with
9e6f2c
+ *  the License.  You may obtain a copy of the License at
9e6f2c
+ *
9e6f2c
+ *      http://www.apache.org/licenses/LICENSE-2.0
9e6f2c
+ *
9e6f2c
+ *  Unless required by applicable law or agreed to in writing, software
9e6f2c
+ *  distributed under the License is distributed on an "AS IS" BASIS,
9e6f2c
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9e6f2c
+ *  See the License for the specific language governing permissions and
9e6f2c
+ *  limitations under the License.
9e6f2c
+ */
9e6f2c
+package org.apache.naming.resources;
9e6f2c
+
9e6f2c
+import java.security.AccessController;
9e6f2c
+import java.security.PrivilegedAction;
9e6f2c
+
9e6f2c
+public class JrePlatform {
9e6f2c
+
9e6f2c
+    private static final String OS_NAME_PROPERTY = "os.name";
9e6f2c
+    private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
9e6f2c
+
9e6f2c
+    static {
9e6f2c
+        /*
9e6f2c
+         * There are a few places where a) the behaviour of the Java API depends
9e6f2c
+         * on the underlying platform and b) those behavioural differences have
9e6f2c
+         * an impact on Tomcat.
9e6f2c
+         *
9e6f2c
+         * Tomcat therefore needs to be able to determine the platform it is
9e6f2c
+         * running on to account for those differences.
9e6f2c
+         *
9e6f2c
+         * In an ideal world this code would not exist.
9e6f2c
+         */
9e6f2c
+
9e6f2c
+        // This check is derived from the check in Apache Commons Lang
9e6f2c
+        String osName;
9e6f2c
+        if (System.getSecurityManager() == null) {
9e6f2c
+            osName = System.getProperty(OS_NAME_PROPERTY);
9e6f2c
+        } else {
9e6f2c
+            osName = AccessController.doPrivileged(
9e6f2c
+                    new PrivilegedAction<String>() {
9e6f2c
+
9e6f2c
+                    @Override
9e6f2c
+                    public String run() {
9e6f2c
+                        return System.getProperty(OS_NAME_PROPERTY);
9e6f2c
+                    }
9e6f2c
+                });
9e6f2c
+        }
9e6f2c
+
9e6f2c
+        IS_WINDOWS = osName.startsWith(OS_NAME_WINDOWS_PREFIX);
9e6f2c
+    }
9e6f2c
+
9e6f2c
+
9e6f2c
+    public static final boolean IS_WINDOWS;
9e6f2c
+}
9e6f2c
--- test/org/apache/naming/resources/TestFileDirContext.java.orig	2017-10-13 09:45:35.991795584 -0400
9e6f2c
+++ test/org/apache/naming/resources/TestFileDirContext.java	2017-10-13 09:42:53.517701300 -0400
9e6f2c
@@ -0,0 +1,46 @@
9e6f2c
+/*
9e6f2c
+ * Licensed to the Apache Software Foundation (ASF) under one or more
9e6f2c
+ * contributor license agreements.  See the NOTICE file distributed with
9e6f2c
+ * this work for additional information regarding copyright ownership.
9e6f2c
+ * The ASF licenses this file to You under the Apache License, Version 2.0
9e6f2c
+ * (the "License"); you may not use this file except in compliance with
9e6f2c
+ * the License.  You may obtain a copy of the License at
9e6f2c
+ *
9e6f2c
+ *      http://www.apache.org/licenses/LICENSE-2.0
9e6f2c
+ *
9e6f2c
+ * Unless required by applicable law or agreed to in writing, software
9e6f2c
+ * distributed under the License is distributed on an "AS IS" BASIS,
9e6f2c
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9e6f2c
+ * See the License for the specific language governing permissions and
9e6f2c
+ * limitations under the License.
9e6f2c
+ */
9e6f2c
+package org.apache.naming.resources;
9e6f2c
+
9e6f2c
+import java.io.File;
9e6f2c
+
9e6f2c
+import javax.servlet.http.HttpServletResponse;
9e6f2c
+
9e6f2c
+import org.junit.Assert;
9e6f2c
+import org.junit.Test;
9e6f2c
+
9e6f2c
+import org.apache.catalina.startup.Tomcat;
9e6f2c
+import org.apache.catalina.startup.TomcatBaseTest;
9e6f2c
+import org.apache.tomcat.util.buf.ByteChunk;
9e6f2c
+
9e6f2c
+public class TestFileDirContext extends TomcatBaseTest {
9e6f2c
+
9e6f2c
+    @Test
9e6f2c
+    public void testLookupResourceWithTrailingSlash() throws Exception {
9e6f2c
+        Tomcat tomcat = getTomcatInstance();
9e6f2c
+
9e6f2c
+        File appDir = new File("test/webapp-3.0");
9e6f2c
+        // app dir is relative to server home
9e6f2c
+        tomcat.addWebapp(null, "/test", appDir.getAbsolutePath());
9e6f2c
+
9e6f2c
+        tomcat.start();
9e6f2c
+
9e6f2c
+        int sc = getUrl("http://localhost:" + getPort() +
9e6f2c
+                "/test/index.html/", new ByteChunk(), null);
9e6f2c
+        Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, sc);
9e6f2c
+    }
9e6f2c
+}