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 null} if not configured.
291 *
292 * @return the process-wide serialization filter or {@code null} if 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 class, if 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 null} if 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 null} if 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 null} if 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 true} if 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 true} if the class is in the package.
660 *
661 * @param c a class
662 * @param pkg a package name
663 * @return {@code true} if 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