#P apache-tomcat-8.0.18
Index: java/org/apache/catalina/session/LocalStrings.properties
===================================================================
--- java/org/apache/catalina/session/LocalStrings.properties	(revision 1732163)
+++ java/org/apache/catalina/session/LocalStrings.properties	(working copy)
@@ -32,6 +32,8 @@
 JDBCStore.commitSQLException=SQLException committing connection before closing
 managerBase.container.noop=Managers added to containers other than Contexts will never be used
 managerBase.createSession.ise=createSession: Too many active sessions
+managerBase.sessionAttributeNameFilter=Skipped session attribute named [{0}] because it did not match the name filter [{1}]
+managerBase.sessionAttributeValueClassNameFilter=Skipped session attribute named [{0}] because the value type [{1}] did not match the filter [{2}]
 managerBase.sessionTimeout=Invalid session timeout setting {0}
 standardManager.loading=Loading persisted sessions from {0}
 standardManager.loading.exception=Exception while loading persisted sessions
Index: java/org/apache/catalina/ha/session/ClusterManagerBase.java
===================================================================
--- java/org/apache/catalina/ha/session/ClusterManagerBase.java	(revision 1732163)
+++ java/org/apache/catalina/ha/session/ClusterManagerBase.java	(working copy)
@@ -197,6 +197,8 @@
         copy.setProcessExpiresFrequency(getProcessExpiresFrequency());
         copy.setNotifyListenersOnReplication(isNotifyListenersOnReplication());
         copy.setSessionAttributeFilter(getSessionAttributeFilter());
+        copy.setSessionAttributeValueClassNameFilter(getSessionAttributeValueClassNameFilter());
+        copy.setWarnOnSessionAttributeFilterFailure(getWarnOnSessionAttributeFilterFailure());
         copy.setSecureRandomClass(getSecureRandomClass());
         copy.setSecureRandomProvider(getSecureRandomProvider());
         copy.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
Index: .
===================================================================
--- .	(revision 1732163)
+++ .	(working copy)

Property changes on: .
___________________________________________________________________
Modified: svn:mergeinfo
## -0,0 +0,2 ##
   Merged /tomcat/tc8.0.x/trunk:r1726196
   Merged /tomcat/trunk:r1725263,1725405
Index: java/org/apache/catalina/util/CustomObjectInputStream.java
===================================================================
--- java/org/apache/catalina/util/CustomObjectInputStream.java	(revision 1732163)
+++ java/org/apache/catalina/util/CustomObjectInputStream.java	(working copy)
@@ -19,10 +19,19 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InvalidClassException;
 import java.io.ObjectInputStream;
 import java.io.ObjectStreamClass;
 import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
 
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.res.StringManager;
+
 /**
  * Custom subclass of <code>ObjectInputStream</code> that loads from the
  * class loader for this web application.  This allows classes defined only
@@ -34,15 +43,26 @@
 public final class CustomObjectInputStream
     extends ObjectInputStream {
 
+    private static final StringManager sm = StringManager.getManager("org.apache.catalina.util");
 
+    private static final WeakHashMap<ClassLoader, Set<String>> reportedClassCache =
+            new WeakHashMap<>();
+
     /**
      * The class loader we will use to resolve classes.
      */
     private final ClassLoader classLoader;
+    private final Set<String> reportedClasses;
+    private final Log log;
 
+    private final Pattern allowedClassNamePattern;
+    private final String allowedClassNameFilter;
+    private final boolean warnOnFailure;
 
+
     /**
-     * Construct a new instance of CustomObjectInputStream
+     * Construct a new instance of CustomObjectInputStream without any filtering
+     * of deserialized classes.
      *
      * @param stream The input stream we will read from
      * @param classLoader The class loader used to instantiate objects
@@ -53,8 +73,54 @@
                                    ClassLoader classLoader)
         throws IOException {
 
+        this(stream, classLoader, null, null, false);
+    }
+
+
+    /**
+     * Construct a new instance of CustomObjectInputStream with filtering of
+     * deserialized classes.
+     *
+     * @param stream The input stream we will read from
+     * @param classLoader The class loader used to instantiate objects
+     * @param log The logger to use to report any issues. It may only be null if
+     *            the filterMode does not require logging
+     * @param allowedClassNamePattern The regular expression to use to filter
+     *                                deserialized classes. The fully qualified
+     *                                class name must match this pattern for
+     *                                deserialization to be allowed if filtering
+     *                                is enabled.
+     * @param warnOnFailure Should any failures be logged?
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public CustomObjectInputStream(InputStream stream, ClassLoader classLoader,
+            Log log, Pattern allowedClassNamePattern, boolean warnOnFailure)
+            throws IOException {
         super(stream);
+        if (log == null && allowedClassNamePattern != null && warnOnFailure) {
+            throw new IllegalArgumentException(
+                    sm.getString("customObjectInputStream.logRequired"));
+        }
         this.classLoader = classLoader;
+        this.log = log;
+        this.allowedClassNamePattern = allowedClassNamePattern;
+        if (allowedClassNamePattern == null) {
+            this.allowedClassNameFilter = null;
+        } else {
+            this.allowedClassNameFilter = allowedClassNamePattern.toString();
+        }
+        this.warnOnFailure = warnOnFailure;
+
+        Set<String> reportedClasses;
+        synchronized (reportedClassCache) {
+            reportedClasses = reportedClassCache.get(classLoader);
+            if (reportedClasses == null) {
+                reportedClasses = Collections.newSetFromMap(new ConcurrentHashMap<String,Boolean>());
+                reportedClassCache.put(classLoader, reportedClasses);
+            }
+        }
+        this.reportedClasses = reportedClasses;
     }
 
 
@@ -70,8 +136,24 @@
     @Override
     public Class<?> resolveClass(ObjectStreamClass classDesc)
         throws ClassNotFoundException, IOException {
+
+        String name = classDesc.getName();
+        if (allowedClassNamePattern != null) {
+            boolean allowed = allowedClassNamePattern.matcher(name).matches();
+            if (!allowed) {
+                boolean doLog = warnOnFailure && reportedClasses.add(name);
+                String msg = sm.getString("customObjectInputStream.nomatch", name, allowedClassNameFilter);
+                if (doLog) {
+                    log.warn(msg);
+                } else if (log.isDebugEnabled()) {
+                    log.debug(msg);
+                }
+                throw new InvalidClassException(msg);
+            }
+        }
+
         try {
-            return Class.forName(classDesc.getName(), false, classLoader);
+            return Class.forName(name, false, classLoader);
         } catch (ClassNotFoundException e) {
             try {
                 // Try also the superclass because of primitive types
Index: java/org/apache/catalina/session/StoreBase.java
===================================================================
--- java/org/apache/catalina/session/StoreBase.java	(revision 1732163)
+++ java/org/apache/catalina/session/StoreBase.java	(working copy)
@@ -19,12 +19,16 @@
 
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
+import java.io.BufferedInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
 
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleState;
 import org.apache.catalina.Manager;
 import org.apache.catalina.Store;
+import org.apache.catalina.util.CustomObjectInputStream;
 import org.apache.catalina.util.LifecycleBase;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -112,6 +116,18 @@
         support.removePropertyChangeListener(listener);
     }
 
+    /**
+     * Get only those keys of sessions, that are saved in the Store and are to
+     * be expired.
+     *
+     * @return list of session keys, that are to be expired
+     * @throws IOException
+     *             if an input-/output error occurred
+     */
+    public String[] expiredKeys() throws IOException {
+        return keys();
+    }
+
     // --------------------------------------------------------- Protected Methods
 
     /**
@@ -128,7 +144,7 @@
         }
 
         try {
-            keys = keys();
+            keys = expiredKeys();
         } catch (IOException e) {
             manager.getContext().getLogger().error("Error getting keys", e);
             return;
@@ -183,7 +199,40 @@
         }
     }
 
+    // --------------------------------------------------------- Protected Methods
 
+    /**
+     * Create the object input stream to use to read a session from the store.
+     * Sub-classes <b>must</b> have set the thread context class loader before
+     * calling this method.
+     *
+     * @param is The input stream provided by the sub-class that will provide
+     *           the data for a session
+     *
+     * @return An appropriately configured ObjectInputStream from which the
+     *         session can be read.
+     *
+     * @throws IOException if a problem occurs creating the ObjectInputStream
+     */
+    protected ObjectInputStream getObjectInputStream(InputStream is) throws IOException {
+        BufferedInputStream bis = new BufferedInputStream(is);
+
+        CustomObjectInputStream ois;
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+        if (manager instanceof ManagerBase) {
+            ManagerBase managerBase = (ManagerBase) manager;
+            ois = new CustomObjectInputStream(bis, classLoader, manager.getContext().getLogger(),
+                    managerBase.getSessionAttributeValueClassNamePattern(),
+                    managerBase.getWarnOnSessionAttributeFilterFailure());
+        } else {
+            ois = new CustomObjectInputStream(bis, classLoader);
+        }
+
+        return ois;
+    }
+
+
     @Override
     protected void initInternal() {
         // NOOP
Index: java/org/apache/catalina/ha/session/mbeans-descriptors.xml
===================================================================
--- java/org/apache/catalina/ha/session/mbeans-descriptors.xml	(revision 1732163)
+++ java/org/apache/catalina/ha/session/mbeans-descriptors.xml	(working copy)
@@ -309,6 +309,14 @@
       is="true"
       description="All session messages before state transfer message creation are dropped."
       type="boolean"/>
+    <attribute
+      name="sessionAttributeValueClassNameFilter"
+      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
+      type="java.lang.String"/>
+    <attribute
+      name="warnOnSessionAttributeFilterFailure"
+      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
+      type="boolean"/>
     <operation
       name="expireSession"
       description="Expired the given session"
@@ -530,6 +538,14 @@
       name="secureRandomProvider"
       description="The secure random number generator provider name"
       type="java.lang.String"/>
+    <attribute
+      name="sessionAttributeValueClassNameFilter"
+      description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
+      type="java.lang.String"/>
+    <attribute
+      name="warnOnSessionAttributeFilterFailure"
+      description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
+      type="boolean"/>
     <operation
       name="expireSession"
       description="Expired the given session"
Index: java/org/apache/catalina/session/ManagerBase.java
===================================================================
--- java/org/apache/catalina/session/ManagerBase.java	(revision 1732163)
+++ java/org/apache/catalina/session/ManagerBase.java	(working copy)
@@ -32,10 +32,13 @@
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
 
 import org.apache.catalina.Container;
 import org.apache.catalina.Context;
 import org.apache.catalina.Engine;
+import org.apache.catalina.Globals;
 import org.apache.catalina.Lifecycle;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.Manager;
@@ -212,8 +215,106 @@
             new PropertyChangeSupport(this);
 
 
-    // ------------------------------------------------------------- Properties
+    private Pattern sessionAttributeValueClassNamePattern;
 
+    private boolean warnOnSessionAttributeFilterFailure;
+
+
+    // ------------------------------------------------------------ Constructors
+
+    public ManagerBase() {
+        if (Globals.IS_SECURITY_ENABLED) {
+            // Minimum set required for default distribution/persistence to work
+            // plus String
+            setSessionAttributeValueClassNameFilter(
+                    "java\\.lang\\.(?:Boolean|Integer|Long|Number|String)");
+            setWarnOnSessionAttributeFilterFailure(true);
+        }
+    }
+
+
+    // -------------------------------------------------------------- Properties
+
+    /**
+     * Obtain the regular expression used to filter session attribute based on
+     * the implementation class of the value. The regular expression is anchored
+     * and must match the fully qualified class name.
+     *
+     * @return The regular expression currently used to filter class names.
+     *         {@code null} means no filter is applied. If an empty string is
+     *         specified then no names will match the filter and all attributes
+     *         will be blocked.
+     */
+    public String getSessionAttributeValueClassNameFilter() {
+        if (sessionAttributeValueClassNamePattern == null) {
+            return null;
+        }
+        return sessionAttributeValueClassNamePattern.toString();
+    }
+
+
+    /**
+     * Provides {@link #getSessionAttributeValueClassNameFilter()} as a
+     * pre-compiled regular expression pattern.
+     *
+     * @return The pre-compiled pattern used to filter session attributes based
+     *         on the implementation class name of the value. {@code null} means
+     *         no filter is applied.
+     */
+    protected Pattern getSessionAttributeValueClassNamePattern() {
+        return sessionAttributeValueClassNamePattern;
+    }
+
+
+    /**
+     * Set the regular expression to use to filter classes used for session
+     * attributes. The regular expression is anchored and must match the fully
+     * qualified class name.
+     *
+     * @param sessionAttributeValueClassNameFilter The regular expression to use
+     *            to filter session attributes based on class name. Use {@code
+     *            null} if no filtering is required. If an empty string is
+     *           specified then no names will match the filter and all
+     *           attributes will be blocked.
+     *
+     * @throws PatternSyntaxException If the expression is not valid
+     */
+    public void setSessionAttributeValueClassNameFilter(String sessionAttributeValueClassNameFilter)
+            throws PatternSyntaxException {
+        if (sessionAttributeValueClassNameFilter == null ||
+                sessionAttributeValueClassNameFilter.length() == 0) {
+            sessionAttributeValueClassNamePattern = null;
+        }
+        sessionAttributeValueClassNamePattern =
+                Pattern.compile(sessionAttributeValueClassNameFilter);
+    }
+
+
+    /**
+     * Should a warn level log message be generated if a session attribute is
+     * not persisted / replicated / restored.
+     *
+     * @return {@code true} if a warn level log message should be generated
+     */
+    public boolean getWarnOnSessionAttributeFilterFailure() {
+        return warnOnSessionAttributeFilterFailure;
+    }
+
+
+    /**
+     * Configure whether or not a warn level log message should be generated if
+     * a session attribute is not persisted / replicated / restored.
+     *
+     * @param warnOnSessionAttributeFilterFailure {@code true} if the
+     *            warn level message should be generated
+     *
+     */
+    public void setWarnOnSessionAttributeFilterFailure(
+            boolean warnOnSessionAttributeFilterFailure) {
+        this.warnOnSessionAttributeFilterFailure = warnOnSessionAttributeFilterFailure;
+    }
+
+
     @Override
     @Deprecated
     public Container getContainer() {
Index: java/org/apache/catalina/session/StandardManager.java
===================================================================
--- java/org/apache/catalina/session/StandardManager.java	(revision 1732163)
+++ java/org/apache/catalina/session/StandardManager.java	(working copy)
@@ -192,8 +192,9 @@
      * @exception IOException if an input/output error occurs
      */
     protected void doLoad() throws ClassNotFoundException, IOException {
-        if (log.isDebugEnabled())
+        if (log.isDebugEnabled()) {
             log.debug("Start: Loading persisted sessions");
+        }
 
         // Initialize our internal data structures
         sessions.clear();
@@ -200,98 +201,67 @@
 
         // Open an input stream to the specified pathname, if any
         File file = file();
-        if (file == null)
+        if (file == null) {
             return;
-        if (log.isDebugEnabled())
+        }
+        if (log.isDebugEnabled()) {
             log.debug(sm.getString("standardManager.loading", pathname));
-        FileInputStream fis = null;
-        BufferedInputStream bis = null;
-        ObjectInputStream ois = null;
+        }
         Loader loader = null;
         ClassLoader classLoader = null;
-        try {
-            fis = new FileInputStream(file.getAbsolutePath());
-            bis = new BufferedInputStream(fis);
+        Log logger = null;
+        try (FileInputStream fis = new FileInputStream(file.getAbsolutePath());
+                BufferedInputStream bis = new BufferedInputStream(fis)) {
             Context c = getContext();
-            if (c != null)
-                loader = c.getLoader();
-            if (loader != null)
+            loader = c.getLoader();
+            logger = c.getLogger();
+            if (loader != null) {
                 classLoader = loader.getClassLoader();
-            if (classLoader != null) {
-                if (log.isDebugEnabled())
-                    log.debug("Creating custom object input stream for class loader ");
-                ois = new CustomObjectInputStream(bis, classLoader);
-            } else {
-                if (log.isDebugEnabled())
-                    log.debug("Creating standard object input stream");
-                ois = new ObjectInputStream(bis);
             }
-        } catch (FileNotFoundException e) {
-            if (log.isDebugEnabled())
-                log.debug("No persisted data file found");
-            return;
-        } catch (IOException e) {
-            log.error(sm.getString("standardManager.loading.ioe", e), e);
-            if (fis != null) {
-                try {
-                    fis.close();
-                } catch (IOException f) {
-                    // Ignore
-                }
+            if (classLoader == null) {
+                classLoader = getClass().getClassLoader();
             }
-            if (bis != null) {
-                try {
-                    bis.close();
-                } catch (IOException f) {
-                    // Ignore
-                }
-            }
-            throw e;
-        }
 
-        // Load the previously unloaded active sessions
-        synchronized (sessions) {
-            try {
-                Integer count = (Integer) ois.readObject();
-                int n = count.intValue();
-                if (log.isDebugEnabled())
-                    log.debug("Loading " + n + " persisted sessions");
-                for (int i = 0; i < n; i++) {
-                    StandardSession session = getNewSession();
-                    session.readObjectData(ois);
-                    session.setManager(this);
-                    sessions.put(session.getIdInternal(), session);
-                    session.activate();
-                    if (!session.isValidInternal()) {
-                        // If session is already invalid,
-                        // expire session to prevent memory leak.
-                        session.setValid(true);
-                        session.expire();
+            // Load the previously unloaded active sessions
+            synchronized (sessions) {
+                try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader, logger,
+                        getSessionAttributeValueClassNamePattern(),
+                        getWarnOnSessionAttributeFilterFailure())) {
+                    Integer count = (Integer) ois.readObject();
+                    int n = count.intValue();
+                    if (log.isDebugEnabled())
+                        log.debug("Loading " + n + " persisted sessions");
+                    for (int i = 0; i < n; i++) {
+                        StandardSession session = getNewSession();
+                        session.readObjectData(ois);
+                        session.setManager(this);
+                        sessions.put(session.getIdInternal(), session);
+                        session.activate();
+                        if (!session.isValidInternal()) {
+                            // If session is already invalid,
+                            // expire session to prevent memory leak.
+                            session.setValid(true);
+                            session.expire();
+                        }
+                        sessionCounter++;
                     }
-                    sessionCounter++;
+                } finally {
+                    // Delete the persistent storage file
+                    if (file.exists()) {
+                        file.delete();
+                    }
                 }
-            } catch (ClassNotFoundException e) {
-                log.error(sm.getString("standardManager.loading.cnfe", e), e);
-                throw e;
-            } catch (IOException e) {
-                log.error(sm.getString("standardManager.loading.ioe", e), e);
-                throw e;
-            } finally {
-                // Close the input stream
-                try {
-                    ois.close();
-                } catch (IOException f) {
-                    // ignored
-                }
-
-                // Delete the persistent storage file
-                if (file.exists() )
-                    file.delete();
             }
+        } catch (FileNotFoundException e) {
+            if (log.isDebugEnabled()) {
+                log.debug("No persisted data file found");
+            }
+            return;
         }
 
-        if (log.isDebugEnabled())
+        if (log.isDebugEnabled()) {
             log.debug("Finish: Loading persisted sessions");
+        }
     }
 
 
@@ -342,56 +312,26 @@
 
         // Open an output stream to the specified pathname, if any
         File file = file();
-        if (file == null)
+        if (file == null) {
             return;
-        if (log.isDebugEnabled())
+        }
+        if (log.isDebugEnabled()) {
             log.debug(sm.getString("standardManager.unloading", pathname));
-        FileOutputStream fos = null;
-        BufferedOutputStream bos = null;
-        ObjectOutputStream oos = null;
-        boolean error = false;
-        try {
-            fos = new FileOutputStream(file.getAbsolutePath());
-            bos = new BufferedOutputStream(fos);
-            oos = new ObjectOutputStream(bos);
-        } catch (IOException e) {
-            error = true;
-            log.error(sm.getString("standardManager.unloading.ioe", e), e);
-            throw e;
-        } finally {
-            if (error) {
-                if (oos != null) {
-                    try {
-                        oos.close();
-                    } catch (IOException ioe) {
-                        // Ignore
-                    }
-                }
-                if (bos != null) {
-                    try {
-                        bos.close();
-                    } catch (IOException ioe) {
-                        // Ignore
-                    }
-                }
-                if (fos != null) {
-                    try {
-                        fos.close();
-                    } catch (IOException ioe) {
-                        // Ignore
-                    }
-                }
-            }
         }
 
-        // Write the number of active sessions, followed by the details
+        // Keep a note of sessions that are expired
         ArrayList<StandardSession> list = new ArrayList<>();
-        synchronized (sessions) {
-            if (log.isDebugEnabled())
-                log.debug("Unloading " + sessions.size() + " sessions");
-            try {
-                // oos can't be null here
-                oos.writeObject(new Integer(sessions.size()));
+
+        try (FileOutputStream fos = new FileOutputStream(file.getAbsolutePath());
+                BufferedOutputStream bos = new BufferedOutputStream(fos);
+                ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+
+            synchronized (sessions) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Unloading " + sessions.size() + " sessions");
+                }
+                // Write the number of active sessions, followed by the details
+                oos.writeObject(Integer.valueOf(sessions.size()));
                 Iterator<Session> elements = sessions.values().iterator();
                 while (elements.hasNext()) {
                     StandardSession session =
@@ -400,31 +340,13 @@
                     session.passivate();
                     session.writeObjectData(oos);
                 }
-            } catch (IOException e) {
-                log.error(sm.getString("standardManager.unloading.ioe", e), e);
-                try {
-                    oos.close();
-                } catch (IOException f) {
-                    // Ignore
-                }
-                throw e;
             }
         }
 
-        // Flush and close the output stream
-        try {
-            oos.flush();
-        } finally {
-            try {
-                oos.close();
-            } catch (IOException f) {
-                // Ignore
-            }
-        }
-
         // Expire all the sessions we just wrote
-        if (log.isDebugEnabled())
+        if (log.isDebugEnabled()) {
             log.debug("Expiring " + list.size() + " persisted sessions");
+        }
         Iterator<StandardSession> expires = list.iterator();
         while (expires.hasNext()) {
             StandardSession session = expires.next();
@@ -437,9 +359,9 @@
             }
         }
 
-        if (log.isDebugEnabled())
+        if (log.isDebugEnabled()) {
             log.debug("Unloading complete");
-
+        }
     }
 
 
Index: java/org/apache/catalina/session/mbeans-descriptors.xml
===================================================================
--- java/org/apache/catalina/session/mbeans-descriptors.xml	(revision 1732163)
+++ java/org/apache/catalina/session/mbeans-descriptors.xml	(working copy)
@@ -133,6 +133,14 @@
                  type="int"
             writeable="false"/>
 
+    <attribute   name="sessionAttributeValueClassNameFilter"
+          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
+                 type="java.lang.String"/>
+
+    <attribute   name="warnOnSessionAttributeFilterFailure"
+          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
+                 type="boolean"/>
+
     <operation   name="backgroundProcess"
           description="Invalidate all sessions that have expired."
                impact="ACTION"
@@ -321,6 +329,14 @@
                  type="int"
             writeable="false"/>
 
+    <attribute   name="sessionAttributeValueClassNameFilter"
+          description="The regular expression used to filter session attributes based on the implementation class of the value. The regular expression is anchored and must match the fully qualified class name."
+                 type="java.lang.String"/>
+
+    <attribute   name="warnOnSessionAttributeFilterFailure"
+          description="Should a WARN level log message be generated if a session attribute fails to match sessionAttributeNameFilter or sessionAttributeClassNameFilter?"
+                 type="boolean"/>
+
     <operation   name="backgroundProcess"
           description="Invalidate all sessions that have expired."
                impact="ACTION"
Index: java/org/apache/catalina/util/LocalStrings.properties
===================================================================
--- java/org/apache/catalina/util/LocalStrings.properties	(revision 1732163)
+++ java/org/apache/catalina/util/LocalStrings.properties	(working copy)
@@ -17,6 +17,8 @@
 resourceSet.locked=No modifications are allowed to a locked ResourceSet
 hexUtil.bad=Bad hexadecimal digit
 hexUtil.odd=Odd number of hexadecimal digits
+customObjectInputStream.logRequired=A valid logger is required for class name filtering with logging
+customObjectInputStream.nomatch=The class [{0}] did not match the regular expression [{1}] for classes allowed to be deserialized
 #Default Messages Utilized by the ExtensionValidator
 extensionValidator.web-application-manifest=Web Application Manifest
 extensionValidator.extension-not-found-error=ExtensionValidator[{0}][{1}]: Required extension [{2}] not found.

