1 /*
2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */

25
26 package java.io;
27
28 import java.security.AccessController;
29 import java.security.PrivilegedAction;
30 import java.security.Security;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Objects;
34 import java.util.Optional;
35 import java.util.function.Function;
36
37 import jdk.internal.misc.SharedSecrets;
38 import jdk.internal.util.StaticProperty;
39
40 /**
41  * Filter classes, array lengths, and graph metrics during deserialization.
42  *
43  * <p><strong>Warning: Deserialization of untrusted data is inherently dangerous
44  * and should be avoided. Untrusted data should be carefully validated according to the
45  * "Serialization and Deserialization" section of the
46  * {@extLink secure_coding_guidelines_javase Secure Coding Guidelines for Java SE}.
47  * {@extLink serialization_filter_guide Serialization Filtering} describes best
48  * practices for defensive use of serial filters.
49  * </strong></p>
50  *
51  * If set on an {@link ObjectInputStream}, the {@link #checkInput checkInput(FilterInfo)}
52  * method is called to validate classes, the length of each array,
53  * the number of objects being read from the stream, the depth of the graph,
54  * and the total number of bytes read from the stream.
55  * <p>
56  * A filter can be set via {@link ObjectInputStream#setObjectInputFilter setObjectInputFilter}
57  * for an individual ObjectInputStream.
58  * A filter can be set via {@link Config#setSerialFilter(ObjectInputFilter) Config.setSerialFilter}
59  * to affect every {@code ObjectInputStream} that does not otherwise set a filter.
60  * <p>
61  * A filter determines whether the arguments are {@link Status#ALLOWED ALLOWED}
62  * or {@link Status#REJECTED REJECTED} and should return the appropriate status.
63  * If the filter cannot determine the status it should return
64  * {@link Status#UNDECIDED UNDECIDED}.
65  * Filters should be designed for the specific use case and expected types.
66  * A filter designed for a particular use may be passed a class that is outside
67  * of the scope of the filter. If the purpose of the filter is to black-list classes
68  * then it can reject a candidate class that matches and report UNDECIDED for others.
69  * A filter may be called with class equals {@code null}, {@code arrayLength} equal -1,
70  * the depth, number of references, and stream size and return a status
71  * that reflects only one or only some of the values.
72  * This allows a filter to specific about the choice it is reporting and
73  * to use other filters without forcing either allowed or rejected status.
74  *
75  * <p>
76  * Typically, a custom filter should check if a process-wide filter
77  * is configured and defer to it if so. For example,
78  * <pre>{@code
79  * ObjectInputFilter.Status checkInput(FilterInfo info) {
80  *     ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter();
81  *     if (serialFilter != null) {
82  *         ObjectInputFilter.Status status = serialFilter.checkInput(info);
83  *         if (status != ObjectInputFilter.Status.UNDECIDED) {
84  *             // The process-wide filter overrides this filter
85  *             return status;
86  *         }
87  *     }
88  *     if (info.serialClass() != null &&
89  *         Remote.class.isAssignableFrom(info.serialClass())) {
90  *         return Status.REJECTED;      // Do not allow Remote objects
91  *     }
92  *     return Status.UNDECIDED;
93  * }
94  *}</pre>
95  * <p>
96  * Unless otherwise noted, passing a {@code null} argument to a
97  * method in this interface and its nested classes will cause a
98  * {@link NullPointerException} to be thrown.
99  *
100  * @see ObjectInputStream#setObjectInputFilter(ObjectInputFilter)
101  * @since 9
102  */

103 @FunctionalInterface
104 public interface ObjectInputFilter {
105
106     /**
107      * Check the class, array length, number of object references, depth,
108      * stream size, and other available filtering information.
109      * Implementations of this method check the contents of the object graph being created
110      * during deserialization. The filter returns {@link Status#ALLOWED Status.ALLOWED},
111      * {@link Status#REJECTED Status.REJECTED}, or {@link Status#UNDECIDED Status.UNDECIDED}.
112      *
113      * @param filterInfo provides information about the current object being deserialized,
114      *             if any, and the status of the {@link ObjectInputStream}
115      * @return  {@link Status#ALLOWED Status.ALLOWED} if accepted,
116      *          {@link Status#REJECTED Status.REJECTED} if rejected,
117      *          {@link Status#UNDECIDED Status.UNDECIDED} if undecided.
118      */

119     Status checkInput(FilterInfo filterInfo);
120
121     /**
122      * FilterInfo provides access to information about the current object
123      * being deserialized and the status of the {@link ObjectInputStream}.
124      * @since 9
125      */

126     interface FilterInfo {
127         /**
128          * The class of an object being deserialized.
129          * For arrays, it is the array type.
130          * For example, the array class name of a 2 dimensional array of strings is
131          * "{@code [[Ljava.lang.String;}".
132          * To check the array's element type, iteratively use
133          * {@link Class#getComponentType() Class.getComponentType} while the result
134          * is an array and then check the class.
135          * The {@code serialClass is null} in the case where a new object is not being
136          * created and to give the filter a chance to check the depth, number of
137          * references to existing objects, and the stream size.
138          *
139          * @return class of an object being deserialized; may be null
140          */

141         Class<?> serialClass();
142
143         /**
144          * The number of array elements when deserializing an array of the class.
145          *
146          * @return the non-negative number of array elements when deserializing
147          * an array of the class, otherwise -1
148          */

149         long arrayLength();
150
151         /**
152          * The current depth.
153          * The depth starts at {@code 1} and increases for each nested object and
154          * decrements when each nested object returns.
155          *
156          * @return the current depth
157          */

158         long depth();
159
160         /**
161          * The current number of object references.
162          *
163          * @return the non-negative current number of object references
164          */

165         long references();
166
167         /**
168          * The current number of bytes consumed.
169          * @implSpec  {@code streamBytes} is implementation specific
170          * and may not be directly related to the object in the stream
171          * that caused the callback.
172          *
173          * @return the non-negative current number of bytes consumed
174          */

175         long streamBytes();
176     }
177
178     /**
179      * The status of a check on the class, array length, number of references,
180      * depth, and stream size.
181      *
182      * @since 9
183      */

184     enum Status {
185         /**
186          * The status is undecided, not allowed and not rejected.
187          */

188         UNDECIDED,
189         /**
190          * The status is allowed.
191          */

192         ALLOWED,
193         /**
194          * The status is rejected.
195          */

196         REJECTED;
197     }
198
199     /**
200      * A utility class to set and get the process-wide filter or create a filter
201      * from a pattern string. If a process-wide filter is set, it will be
202      * used for each {@link ObjectInputStream} that does not set its own filter.
203      * <p>
204      * When setting the filter, it should be stateless and idempotent,
205      * reporting the same result when passed the same arguments.
206      * <p>
207      * The filter is configured during the initialization of the {@code ObjectInputFilter.Config}
208      * class. For example, by calling {@link #getSerialFilter() Config.getSerialFilter}.
209      * If the system property {@code jdk.serialFilter} is defined, it is used
210      * to configure the filter.
211      * If the system property is not defined, and the {@link java.security.Security}
212      * property {@code jdk.serialFilter} is defined then it is used to configure the filter.
213      * Otherwise, the filter is not configured during initialization.
214      * The syntax for each property is the same as for the
215      * {@link #createFilter(String) createFilter} method.
216      * If a filter is not configured, it can be set with
217      * {@link #setSerialFilter(ObjectInputFilter) Config.setSerialFilter}.
218      *
219      * @since 9
220      */

221     final class Config {
222         /* No instances. */
223         private Config() {}
224
225         /**
226          * Lock object for process-wide filter.
227          */

228         private final static Object serialFilterLock = new Object();
229
230         /**
231          * Debug: Logger
232          */

233         private final static System.Logger configLog;
234
235         /**
236          * Logger for debugging.
237          */

238         static void filterLog(System.Logger.Level level, String msg, Object... args) {
239             if (configLog != null) {
240                 configLog.log(level, msg, args);
241             }
242         }
243
244         /**
245          * The name for the process-wide deserialization filter.
246          * Used as a system property and a java.security.Security property.
247          */

248         private final static String SERIAL_FILTER_PROPNAME = "jdk.serialFilter";
249
250         /**
251          * The process-wide filter; may be null.
252          * Lookup the filter in java.security.Security or
253          * the system property.
254          */

255         private final static ObjectInputFilter configuredFilter;
256
257         static {
258             configuredFilter = AccessController
259                     .doPrivileged((PrivilegedAction<ObjectInputFilter>) () -> {
260                         String props = StaticProperty.jdkSerialFilter();
261                         if (props == null) {
262                             props = Security.getProperty(SERIAL_FILTER_PROPNAME);
263                         }
264                         if (props != null) {
265                             System.Logger log =
266                                     System.getLogger("java.io.serialization");
267                             log.log(System.Logger.Level.INFO,
268                                     "Creating serialization filter from {0}", props);
269                             try {
270                                 return createFilter(props);
271                             } catch (RuntimeException re) {
272                                 log.log(System.Logger.Level.ERROR,
273                                         "Error configuring filter: {0}", re);
274                             }
275                         }
276                         return null;
277                     });
278             configLog = (configuredFilter != null) ? System.getLogger("java.io.serialization") : null;
279
280             // Setup shared secrets for RegistryImpl to use.
281             SharedSecrets.setJavaObjectInputFilterAccess(Config::createFilter2);
282         }
283
284         /**
285          * Current configured filter.
286          */

287         private static volatile ObjectInputFilter serialFilter = configuredFilter;
288
289         /**
290          * Returns the process-wide serialization filter or {@code nullif not configured.
291          *
292          * @return the process-wide serialization filter or {@code nullif not configured
293          */

294         public static ObjectInputFilter getSerialFilter() {
295             return serialFilter;
296         }
297
298         /**
299          * Set the process-wide filter if it has not already been configured or set.
300          *
301          * @param filter the serialization filter to set as the process-wide filter; not null
302          * @throws SecurityException if there is security manager and the
303          *       {@code SerializablePermission("serialFilter")} is not granted
304          * @throws IllegalStateException if the filter has already been set {@code non-null}
305          */

306         public static void setSerialFilter(ObjectInputFilter filter) {
307             Objects.requireNonNull(filter, "filter");
308             SecurityManager sm = System.getSecurityManager();
309             if (sm != null) {
310                 sm.checkPermission(ObjectStreamConstants.SERIAL_FILTER_PERMISSION);
311             }
312             synchronized (serialFilterLock) {
313                 if (serialFilter != null) {
314                     throw new IllegalStateException("Serial filter can only be set once");
315                 }
316                 serialFilter = filter;
317             }
318         }
319
320         /**
321          * Returns an ObjectInputFilter from a string of patterns.
322          * <p>
323          * Patterns are separated by ";" (semicolon). Whitespace is significant and
324          * is considered part of the pattern.
325          * If a pattern includes an equals assignment, "{@code =}" it sets a limit.
326          * If a limit appears more than once the last value is used.
327          * <ul>
328          *     <li>maxdepth={@code value} - the maximum depth of a graph</li>
329          *     <li>maxrefs={@code value}  - the maximum number of internal references</li>
330          *     <li>maxbytes={@code value} - the maximum number of bytes in the input stream</li>
331          *     <li>maxarray={@code value} - the maximum array length allowed</li>
332          * </ul>
333          * <p>
334          * Other patterns match or reject class or package name
335          * as returned from {@link Class#getName() Class.getName()} and
336          * if an optional module name is present
337          * {@link Module#getName() class.getModule().getName()}.
338          * Note that for arrays the element type is used in the pattern,
339          * not the array type.
340          * <ul>
341          * <li>If the pattern starts with "!", the class is rejected if the remaining pattern is matched;
342          *     otherwise the class is allowed if the pattern matches.
343          * <li>If the pattern contains "/", the non-empty prefix up to the "/" is the module name;
344          *     if the module name matches the module name of the class then
345          *     the remaining pattern is matched with the class name.
346          *     If there is no "/", the module name is not compared.
347          * <li>If the pattern ends with ".**" it matches any class in the package and all subpackages.
348          * <li>If the pattern ends with ".*" it matches any class in the package.
349          * <li>If the pattern ends with "*", it matches any class with the pattern as a prefix.
350          * <li>If the pattern is equal to the class name, it matches.
351          * <li>Otherwise, the pattern is not matched.
352          * </ul>
353          * <p>
354          * The resulting filter performs the limit checks and then
355          * tries to match the classif any. If any of the limits are exceeded,
356          * the filter returns {@link Status#REJECTED Status.REJECTED}.
357          * If the class is an array type, the class to be matched is the element type.
358          * Arrays of any number of dimensions are treated the same as the element type.
359          * For example, a pattern of "{@code !example.Foo}",
360          * rejects creation of any instance or array of {@code example.Foo}.
361          * The first pattern that matches, working from left to right, determines
362          * the {@link Status#ALLOWED Status.ALLOWED}
363          * or {@link Status#REJECTED Status.REJECTED} result.
364          * If the limits are not exceeded and no pattern matches the class,
365          * the result is {@link Status#UNDECIDED Status.UNDECIDED}.
366          *
367          * @param pattern the pattern string to parse; not null
368          * @return a filter to check a class being deserialized;
369          *          {@code nullif no patterns
370          * @throws IllegalArgumentException if the pattern string is illegal or
371          *         malformed and cannot be parsed.
372          *         In particular, if any of the following is true:
373          * <ul>
374          * <li>   if a limit is missing the name or the name is not one of
375          *        "maxdepth""maxrefs""maxbytes", or "maxarray"
376          * <li>   if the value of the limit can not be parsed by
377          *        {@link Long#parseLong Long.parseLong} or is negative
378          * <li>   if the pattern contains "/" and the module name is missing
379          *        or the remaining pattern is empty
380          * <li>   if the package is missing for ".*" and ".**"
381          * </ul>
382          */

383         public static ObjectInputFilter createFilter(String pattern) {
384             Objects.requireNonNull(pattern, "pattern");
385             return Global.createFilter(pattern, true);
386         }
387
388         /**
389          * Returns an ObjectInputFilter from a string of patterns that
390          * checks only the length for arrays, not the component type.
391          *
392          * @param pattern the pattern string to parse; not null
393          * @return a filter to check a class being deserialized;
394          *          {@code nullif no patterns
395          */

396         static ObjectInputFilter createFilter2(String pattern) {
397             Objects.requireNonNull(pattern, "pattern");
398             return Global.createFilter(pattern, false);
399         }
400
401         /**
402          * Implementation of ObjectInputFilter that performs the checks of
403          * the process-wide serialization filter. If configured, it will be
404          * used for all ObjectInputStreams that do not set their own filters.
405          *
406          */

407         final static class Global implements ObjectInputFilter {
408             /**
409              * The pattern used to create the filter.
410              */

411             private final String pattern;
412             /**
413              * The list of class filters.
414              */

415             private final List<Function<Class<?>, Status>> filters;
416             /**
417              * Maximum allowed bytes in the stream.
418              */

419             private long maxStreamBytes;
420             /**
421              * Maximum depth of the graph allowed.
422              */

423             private long maxDepth;
424             /**
425              * Maximum number of references in a graph.
426              */

427             private long maxReferences;
428             /**
429              * Maximum length of any array.
430              */

431             private long maxArrayLength;
432             /**
433              * True to check the component type for arrays.
434              */

435             private final boolean checkComponentType;
436
437             /**
438              * Returns an ObjectInputFilter from a string of patterns.
439              *
440              * @param pattern the pattern string to parse
441              * @param checkComponentType true if the filter should check
442              *                           the component type of arrays
443              * @return a filter to check a class being deserialized;
444              *          {@code nullif no patterns
445              * @throws IllegalArgumentException if the parameter is malformed
446              *                if the pattern is missing the name, the long value
447              *                is not a number or is negative.
448              */

449             static ObjectInputFilter createFilter(String pattern, boolean checkComponentType) {
450                 try {
451                     return new Global(pattern, checkComponentType);
452                 } catch (UnsupportedOperationException uoe) {
453                     // no non-empty patterns
454                     return null;
455                 }
456             }
457
458             /**
459              * Construct a new filter from the pattern String.
460              *
461              * @param pattern a pattern string of filters
462              * @param checkComponentType true if the filter should check
463              *                           the component type of arrays
464              * @throws IllegalArgumentException if the pattern is malformed
465              * @throws UnsupportedOperationException if there are no non-empty patterns
466              */

467             private Global(String pattern, boolean checkComponentType) {
468                 boolean hasLimits = false;
469                 this.pattern = pattern;
470                 this.checkComponentType = checkComponentType;
471
472                 maxArrayLength = Long.MAX_VALUE; // Default values are unlimited
473                 maxDepth = Long.MAX_VALUE;
474                 maxReferences = Long.MAX_VALUE;
475                 maxStreamBytes = Long.MAX_VALUE;
476
477                 String[] patterns = pattern.split(";");
478                 filters = new ArrayList<>(patterns.length);
479                 for (int i = 0; i < patterns.length; i++) {
480                     String p = patterns[i];
481                     int nameLen = p.length();
482                     if (nameLen == 0) {
483                         continue;
484                     }
485                     if (parseLimit(p)) {
486                         // If the pattern contained a limit setting, i.e. type=value
487                         hasLimits = true;
488                         continue;
489                     }
490                     boolean negate = p.charAt(0) == '!';
491                     int poffset = negate ? 1 : 0;
492
493                     // isolate module name, if any
494                     int slash = p.indexOf('/', poffset);
495                     if (slash == poffset) {
496                         throw new IllegalArgumentException("module name is missing in: \"" + pattern + "\"");
497                     }
498                     final String moduleName = (slash >= 0) ? p.substring(poffset, slash) : null;
499                     poffset = (slash >= 0) ? slash + 1 : poffset;
500
501                     final Function<Class<?>, Status> patternFilter;
502                     if (p.endsWith("*")) {
503                         // Wildcard cases
504                         if (p.endsWith(".*")) {
505                             // Pattern is a package name with a wildcard
506                             final String pkg = p.substring(poffset, nameLen - 2);
507                             if (pkg.isEmpty()) {
508                                 throw new IllegalArgumentException("package missing in: \"" + pattern + "\"");
509                             }
510                             if (negate) {
511                                 // A Function that fails if the class starts with the pattern, otherwise don't care
512                                 patternFilter = c -> matchesPackage(c, pkg) ? Status.REJECTED : Status.UNDECIDED;
513                             } else {
514                                 // A Function that succeeds if the class starts with the pattern, otherwise don't care
515                                 patternFilter = c -> matchesPackage(c, pkg) ? Status.ALLOWED : Status.UNDECIDED;
516                             }
517                         } else if (p.endsWith(".**")) {
518                             // Pattern is a package prefix with a double wildcard
519                             final String pkgs = p.substring(poffset, nameLen - 2);
520                             if (pkgs.length() < 2) {
521                                 throw new IllegalArgumentException("package missing in: \"" + pattern + "\"");
522                             }
523                             if (negate) {
524                                 // A Function that fails if the class starts with the pattern, otherwise don't care
525                                 patternFilter = c -> c.getName().startsWith(pkgs) ? Status.REJECTED : Status.UNDECIDED;
526                             } else {
527                                 // A Function that succeeds if the class starts with the pattern, otherwise don't care
528                                 patternFilter = c -> c.getName().startsWith(pkgs) ? Status.ALLOWED : Status.UNDECIDED;
529                             }
530                         } else {
531                             // Pattern is a classname (possibly empty) with a trailing wildcard
532                             final String className = p.substring(poffset, nameLen - 1);
533                             if (negate) {
534                                 // A Function that fails if the class starts with the pattern, otherwise don't care
535                                 patternFilter = c -> c.getName().startsWith(className) ? Status.REJECTED : Status.UNDECIDED;
536                             } else {
537                                 // A Function that succeeds if the class starts with the pattern, otherwise don't care
538                                 patternFilter = c -> c.getName().startsWith(className) ? Status.ALLOWED : Status.UNDECIDED;
539                             }
540                         }
541                     } else {
542                         final String name = p.substring(poffset);
543                         if (name.isEmpty()) {
544                             throw new IllegalArgumentException("class or package missing in: \"" + pattern + "\"");
545                         }
546                         // Pattern is a class name
547                         if (negate) {
548                             // A Function that fails if the class equals the pattern, otherwise don't care
549                             patternFilter = c -> c.getName().equals(name) ? Status.REJECTED : Status.UNDECIDED;
550                         } else {
551                             // A Function that succeeds if the class equals the pattern, otherwise don't care
552                             patternFilter = c -> c.getName().equals(name) ? Status.ALLOWED : Status.UNDECIDED;
553                         }
554                     }
555                     // If there is a moduleName, combine the module name check with the package/class check
556                     if (moduleName == null) {
557                         filters.add(patternFilter);
558                     } else {
559                         filters.add(c -> moduleName.equals(c.getModule().getName()) ? patternFilter.apply(c) : Status.UNDECIDED);
560                     }
561                 }
562                 if (filters.isEmpty() && !hasLimits) {
563                     throw new UnsupportedOperationException("no non-empty patterns");
564                 }
565             }
566
567             /**
568              * Parse out a limit for one of maxarray, maxdepth, maxbytes, maxreferences.
569              *
570              * @param pattern a string with a type name, '=' and a value
571              * @return {@code trueif a limit was parsed, else {@code false}
572              * @throws IllegalArgumentException if the pattern is missing
573              *                the name, the Long value is not a number or is negative.
574              */

575             private boolean parseLimit(String pattern) {
576                 int eqNdx = pattern.indexOf('=');
577                 if (eqNdx < 0) {
578                     // not a limit pattern
579                     return false;
580                 }
581                 String valueString = pattern.substring(eqNdx + 1);
582                 if (pattern.startsWith("maxdepth=")) {
583                     maxDepth = parseValue(valueString);
584                 } else if (pattern.startsWith("maxarray=")) {
585                     maxArrayLength = parseValue(valueString);
586                 } else if (pattern.startsWith("maxrefs=")) {
587                     maxReferences = parseValue(valueString);
588                 } else if (pattern.startsWith("maxbytes=")) {
589                     maxStreamBytes = parseValue(valueString);
590                 } else {
591                     throw new IllegalArgumentException("unknown limit: " + pattern.substring(0, eqNdx));
592                 }
593                 return true;
594             }
595
596             /**
597              * Parse the value of a limit and check that it is non-negative.
598              * @param string inputstring
599              * @return the parsed value
600              * @throws IllegalArgumentException if parsing the value fails or the value is negative
601              */

602             private static long parseValue(String string) throws IllegalArgumentException {
603                 // Parse a Long from after the '=' to the end
604                 long value = Long.parseLong(string);
605                 if (value < 0) {
606                     throw new IllegalArgumentException("negative limit: " + string);
607                 }
608                 return value;
609             }
610
611             /**
612              * {@inheritDoc}
613              */

614             @Override
615             public Status checkInput(FilterInfo filterInfo) {
616                 if (filterInfo.references() < 0
617                         || filterInfo.depth() < 0
618                         || filterInfo.streamBytes() < 0
619                         || filterInfo.references() > maxReferences
620                         || filterInfo.depth() > maxDepth
621                         || filterInfo.streamBytes() > maxStreamBytes) {
622                     return Status.REJECTED;
623                 }
624
625                 Class<?> clazz = filterInfo.serialClass();
626                 if (clazz != null) {
627                     if (clazz.isArray()) {
628                         if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > maxArrayLength) {
629                             // array length is too big
630                             return Status.REJECTED;
631                         }
632                         if (!checkComponentType) {
633                             // As revised; do not check the component type for arrays
634                             return Status.UNDECIDED;
635                         }
636                         do {
637                             // Arrays are decided based on the component type
638                             clazz = clazz.getComponentType();
639                         } while (clazz.isArray());
640                     }
641
642                     if (clazz.isPrimitive())  {
643                         // Primitive types are undecided; let someone else decide
644                         return Status.UNDECIDED;
645                     } else {
646                         // Find any filter that allowed or rejected the class
647                         final Class<?> cl = clazz;
648                         Optional<Status> status = filters.stream()
649                                 .map(f -> f.apply(cl))
650                                 .filter(p -> p != Status.UNDECIDED)
651                                 .findFirst();
652                         return status.orElse(Status.UNDECIDED);
653                     }
654                 }
655                 return Status.UNDECIDED;
656             }
657
658             /**
659              * Returns {@code trueif the class is in the package.
660              *
661              * @param c   a class
662              * @param pkg a package name
663              * @return {@code trueif the class is in the package,
664              * otherwise {@code false}
665              */

666             private static boolean matchesPackage(Class<?> c, String pkg) {
667                 return pkg.equals(c.getPackageName());
668             }
669
670             /**
671              * Returns the pattern used to create this filter.
672              * @return the pattern used to create this filter
673              */

674             @Override
675             public String toString() {
676                 return pattern;
677             }
678         }
679     }
680 }
681