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 * Called to throw {@link InvalidClassException} if an invalid 114 * class name is found during deserialization. Can be overridden, for example 115 * to log those class names. 116 * 117 * @param className name of the invalid class 118 * @throws InvalidClassException if the specified class is not allowed 119 */ 120 protected void invalidClassNameFound(final String className) throws InvalidClassException { 121 throw new InvalidClassException("Class name not accepted: " + className); 122 } 123 124 /** 125 * Reject the specified classes for deserialization, even if they 126 * are otherwise accepted. 127 * 128 * @param classes Classes to reject 129 * @return this object 130 */ 131 public ValidatingObjectInputStream reject(final Class<?>... classes) { 132 Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(rejectMatchers::add); 133 return this; 134 } 135 136 /** 137 * Reject class names where the supplied ClassNameMatcher matches for 138 * deserialization, even if they are otherwise accepted. 139 * 140 * @param m the matcher to use 141 * @return this object 142 */ 143 public ValidatingObjectInputStream reject(final ClassNameMatcher m) { 144 rejectMatchers.add(m); 145 return this; 146 } 147 148 /** 149 * Reject class names that match the supplied pattern for 150 * deserialization, even if they are otherwise accepted. 151 * 152 * @param pattern standard Java regexp 153 * @return this object 154 */ 155 public ValidatingObjectInputStream reject(final Pattern pattern) { 156 rejectMatchers.add(new RegexpClassNameMatcher(pattern)); 157 return this; 158 } 159 160 /** 161 * Reject the wildcard specified classes for deserialization, 162 * even if they are otherwise accepted. 163 * 164 * @param patterns Wildcard file name patterns as defined by 165 * {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch} 166 * @return this object 167 */ 168 public ValidatingObjectInputStream reject(final String... patterns) { 169 Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(rejectMatchers::add); 170 return this; 171 } 172 173 @Override 174 protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException { 175 validateClassName(osc.getName()); 176 return super.resolveClass(osc); 177 } 178 179 /** Check that the classname conforms to requirements. 180 * @param name The class name 181 * @throws InvalidClassException when a non-accepted class is encountered 182 */ 183 private void validateClassName(final String name) throws InvalidClassException { 184 // Reject has precedence over accept 185 for (final ClassNameMatcher m : rejectMatchers) { 186 if (m.matches(name)) { 187 invalidClassNameFound(name); 188 } 189 } 190 191 boolean ok = false; 192 for (final ClassNameMatcher m : acceptMatchers) { 193 if (m.matches(name)) { 194 ok = true; 195 break; 196 } 197 } 198 if (!ok) { 199 invalidClassNameFound(name); 200 } 201 } 202}