001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.io.serialization; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InvalidClassException; 024import java.io.ObjectInputStream; 025import java.io.ObjectStreamClass; 026import java.util.ArrayList; 027import java.util.List; 028import java.util.regex.Pattern; 029import java.util.stream.Stream; 030 031/** 032 * An {@link ObjectInputStream} that's restricted to deserialize 033 * a limited set of classes. 034 * 035 * <p> 036 * Various accept/reject methods allow for specifying which classes 037 * can be deserialized. 038 * </p> 039 * 040 * <p> 041 * Design inspired by <a 042 * href="http://www.ibm.com/developerworks/library/se-lookahead/">IBM 043 * DeveloperWorks Article</a>. 044 * </p> 045 */ 046public class ValidatingObjectInputStream extends ObjectInputStream { 047 private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>(); 048 private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>(); 049 050 /** 051 * Constructs an object to deserialize the specified input stream. 052 * At least one accept method needs to be called to specify which 053 * classes can be deserialized, as by default no classes are 054 * accepted. 055 * 056 * @param input an input stream 057 * @throws IOException if an I/O error occurs while reading stream header 058 */ 059 public ValidatingObjectInputStream(final InputStream input) throws IOException { 060 super(input); 061 } 062 063 /** 064 * Accept the specified classes for deserialization, unless they 065 * are otherwise rejected. 066 * 067 * @param classes Classes to accept 068 * @return this object 069 */ 070 public ValidatingObjectInputStream accept(final Class<?>... classes) { 071 Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(acceptMatchers::add); 072 return this; 073 } 074 075 /** 076 * Accept class names where the supplied ClassNameMatcher matches for 077 * deserialization, unless they are otherwise rejected. 078 * 079 * @param m the matcher to use 080 * @return this object 081 */ 082 public ValidatingObjectInputStream accept(final ClassNameMatcher m) { 083 acceptMatchers.add(m); 084 return this; 085 } 086 087 /** 088 * Accept class names that match the supplied pattern for 089 * deserialization, unless they are otherwise rejected. 090 * 091 * @param pattern standard Java regexp 092 * @return this object 093 */ 094 public ValidatingObjectInputStream accept(final Pattern pattern) { 095 acceptMatchers.add(new RegexpClassNameMatcher(pattern)); 096 return this; 097 } 098 099 /** 100 * Accept the wildcard specified classes for deserialization, 101 * unless they are otherwise rejected. 102 * 103 * @param patterns Wildcard file name patterns as defined by 104 * {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch} 105 * @return this object 106 */ 107 public ValidatingObjectInputStream accept(final String... patterns) { 108 Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(acceptMatchers::add); 109 return this; 110 } 111 112 /** 113 * Checks that the class name conforms to requirements. 114 * 115 * @param name The class name 116 * @throws InvalidClassException when a non-accepted class is encountered 117 */ 118 private void checkClassName(final String name) throws InvalidClassException { 119 // Reject has precedence over accept 120 for (final ClassNameMatcher m : rejectMatchers) { 121 if (m.matches(name)) { 122 invalidClassNameFound(name); 123 } 124 } 125 126 boolean ok = false; 127 for (final ClassNameMatcher m : acceptMatchers) { 128 if (m.matches(name)) { 129 ok = true; 130 break; 131 } 132 } 133 if (!ok) { 134 invalidClassNameFound(name); 135 } 136 } 137 138 /** 139 * Called to throw {@link InvalidClassException} if an invalid 140 * class name is found during deserialization. Can be overridden, for example 141 * to log those class names. 142 * 143 * @param className name of the invalid class 144 * @throws InvalidClassException if the specified class is not allowed 145 */ 146 protected void invalidClassNameFound(final String className) throws InvalidClassException { 147 throw new InvalidClassException("Class name not accepted: " + className); 148 } 149 150 /** 151 * Reject the specified classes for deserialization, even if they 152 * are otherwise accepted. 153 * 154 * @param classes Classes to reject 155 * @return this object 156 */ 157 public ValidatingObjectInputStream reject(final Class<?>... classes) { 158 Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(rejectMatchers::add); 159 return this; 160 } 161 162 /** 163 * Reject class names where the supplied ClassNameMatcher matches for 164 * deserialization, even if they are otherwise accepted. 165 * 166 * @param m the matcher to use 167 * @return this object 168 */ 169 public ValidatingObjectInputStream reject(final ClassNameMatcher m) { 170 rejectMatchers.add(m); 171 return this; 172 } 173 174 /** 175 * Reject class names that match the supplied pattern for 176 * deserialization, even if they are otherwise accepted. 177 * 178 * @param pattern standard Java regexp 179 * @return this object 180 */ 181 public ValidatingObjectInputStream reject(final Pattern pattern) { 182 rejectMatchers.add(new RegexpClassNameMatcher(pattern)); 183 return this; 184 } 185 186 /** 187 * Reject the wildcard specified classes for deserialization, 188 * even if they are otherwise accepted. 189 * 190 * @param patterns Wildcard file name patterns as defined by 191 * {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch} 192 * @return this object 193 */ 194 public ValidatingObjectInputStream reject(final String... patterns) { 195 Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(rejectMatchers::add); 196 return this; 197 } 198 199 @Override 200 protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException { 201 checkClassName(osc.getName()); 202 return super.resolveClass(osc); 203 } 204}