DatabindContext.java
package tools.jackson.databind;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;
import com.fasterxml.jackson.annotation.*;
import tools.jackson.core.util.InternCache;
import tools.jackson.databind.cfg.DatatypeFeature;
import tools.jackson.databind.cfg.DatatypeFeatures;
import tools.jackson.databind.cfg.HandlerInstantiator;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.introspect.Annotated;
import tools.jackson.databind.introspect.AnnotatedClass;
import tools.jackson.databind.introspect.ClassIntrospector;
import tools.jackson.databind.introspect.ObjectIdInfo;
import tools.jackson.databind.jsontype.PolymorphicTypeValidator;
import tools.jackson.databind.jsontype.PolymorphicTypeValidator.Validity;
import tools.jackson.databind.jsontype.impl.ApprovedBaseTypeValidator;
import tools.jackson.databind.jsontype.impl.SubTypeValidator;
import tools.jackson.databind.type.TypeFactory;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.databind.util.Converter;
/**
* Shared base class for {@link DeserializationContext} and
* {@link SerializationContext}, context objects passed through data-binding
* process. Designed so that some of the implementations can rely on shared
* aspects like access to secondary contextual objects like type factories
* or handler instantiators.
*/
public abstract class DatabindContext
{
/**
* Let's limit length of error messages, for cases where underlying data
* may be very large -- no point in spamming logs with megabytes of meaningless
* data.
*/
private final static int MAX_ERROR_STR_LEN = 500;
/*
/**********************************************************************
/* Generic config access
/**********************************************************************
*/
/**
* Accessor to currently active configuration (both per-request configs
* and per-mapper config).
*/
public abstract MapperConfig<?> getConfig();
/**
* Convenience method for accessing serialization view in use (if any); equivalent to:
*<pre>
* getConfig().getAnnotationIntrospector();
*</pre>
*/
public abstract AnnotationIntrospector getAnnotationIntrospector();
/*
/**********************************************************************
/* Access to specific config settings
/**********************************************************************
*/
/**
* Convenience method for checking whether specified Mapper
* feature is enabled or not.
* Shortcut for:
*<pre>
* getConfig().isEnabled(feature);
*</pre>
*/
public abstract boolean isEnabled(MapperFeature feature);
public final boolean isAnnotationProcessingEnabled() {
return isEnabled(MapperFeature.USE_ANNOTATIONS);
}
/**
* Method for checking whether specified datatype
* feature is enabled or not.
*
* @since 2.14
*/
public abstract boolean isEnabled(DatatypeFeature feature);
/**
* @since 2.15
*/
public abstract DatatypeFeatures getDatatypeFeatures();
/**
* Convenience method for accessing serialization view in use (if any); equivalent to:
*<pre>
* getConfig().canOverrideAccessModifiers();
*</pre>
*/
public abstract boolean canOverrideAccessModifiers();
/**
* Accessor for locating currently active view, if any;
* returns null if no view has been set.
*/
public abstract Class<?> getActiveView();
public abstract Locale getLocale();
public abstract TimeZone getTimeZone();
public abstract JsonFormat.Value getDefaultPropertyFormat(Class<?> baseType);
/*
/**********************************************************************
/* Generic attributes
/**********************************************************************
*/
/**
* Method for accessing attributes available in this context.
* Per-call attributes have highest precedence; attributes set
* via {@link ObjectReader} or {@link ObjectWriter} have lower
* precedence.
*
* @param key Key of the attribute to get
* @return Value of the attribute, if any; null otherwise
*/
public abstract Object getAttribute(Object key);
/**
* Method for setting per-call value of given attribute.
* This will override any previously defined value for the
* attribute within this context.
*
* @param key Key of the attribute to set
* @param value Value to set attribute to
*
* @return This context object, to allow chaining
*/
public abstract DatabindContext setAttribute(Object key, Object value);
/*
/**********************************************************************
/* Type instantiation/resolution
/**********************************************************************
*/
/**
* Convenience method for constructing {@link JavaType} for given JDK
* type (usually {@link java.lang.Class})
*/
public JavaType constructType(Type type) {
if (type == null) {
return null;
}
return getTypeFactory().constructType(type);
}
/**
* Convenience method for constructing subtypes, retaining generic
* type parameter (if any).
*<p>
* Note: since 2.11 handling has varied a bit across serialization, deserialization.
*/
public abstract JavaType constructSpecializedType(JavaType baseType, Class<?> subclass);
/**
* Lookup method called when code needs to resolve class name from input;
* usually simple lookup.
* Note that unlike {@link #resolveAndValidateSubType} this method DOES NOT
* validate subtype against configured {@link PolymorphicTypeValidator}: usually
* because such check has already been made.
*/
public JavaType resolveSubType(JavaType baseType, String subClassName)
{
// 30-Jan-2010, tatu: Most ids are basic class names; so let's first
// check if any generics info is added; and only then ask factory
// to do translation when necessary
if (subClassName.indexOf('<') > 0) {
// note: may want to try combining with specialization (esp for EnumMap)?
// 17-Aug-2017, tatu: As per [databind#1735] need to ensure assignment
// compatibility -- needed later anyway, and not doing so may open
// security issues.
JavaType t = getTypeFactory().constructFromCanonical(subClassName);
if (t.isTypeOrSubTypeOf(baseType.getRawClass())) {
return t;
}
} else {
Class<?> cls;
try {
cls = getTypeFactory().findClass(subClassName);
} catch (ClassNotFoundException e) { // let caller handle this problem
return null;
} catch (Exception e) {
throw invalidTypeIdException(baseType, subClassName, "problem: (%s) %s".formatted(
e.getClass().getName(),
ClassUtil.exceptionMessage(e)));
}
if (baseType.isTypeOrSuperTypeOf(cls)) {
return getTypeFactory().constructSpecializedType(baseType, cls);
}
}
throw invalidTypeIdException(baseType, subClassName, "Not a subtype");
}
/**
* Lookup method similar to {@link #resolveSubType} but one that also validates
* that resulting subtype is valid according to given {@link PolymorphicTypeValidator}.
*/
public JavaType resolveAndValidateSubType(JavaType baseType, String subClass,
PolymorphicTypeValidator ptv)
{
// Off-line the special case of generic (parameterized) type:
final int ltIndex = subClass.indexOf('<');
if (ltIndex > 0) {
return _resolveAndValidateGeneric(baseType, subClass, ptv, ltIndex);
}
PolymorphicTypeValidator.Validity vld = ptv.validateSubClassName(this, baseType, subClass);
if (vld == Validity.DENIED) {
return _throwSubtypeNameNotAllowed(baseType, subClass, ptv);
}
final Class<?> cls;
try {
cls = getTypeFactory().findClass(subClass);
} catch (ClassNotFoundException e) { // let caller handle this problem
return null;
} catch (Exception e) {
throw invalidTypeIdException(baseType, subClass, "problem: (%s) %s".formatted(
e.getClass().getName(),
ClassUtil.exceptionMessage(e)));
}
if (!baseType.isTypeOrSuperTypeOf(cls)) {
return _throwNotASubtype(baseType, subClass);
}
final JavaType subType = getTypeFactory().constructSpecializedType(baseType, cls);
// May skip check if type was allowed by subclass name already
if (vld == Validity.INDETERMINATE) {
vld = ptv.validateSubType(this, baseType, subType);
if (vld != Validity.ALLOWED) {
return _throwSubtypeClassNotAllowed(baseType, subClass, ptv);
}
}
// [CVE-2026-110879]: element type of an array type id is attacker-selected
// as well and needs validation like type parameters of generic type ids
// (approval of array type, by base type or by sub-type, does not extend to it)
if (subType.isArrayType()
&& !_configuredValidator(ptv).isArrayTypeAllowedExplicitly(this, baseType, subType)) {
_validateArrayElementType(baseType, subClass, baseType, subType, ptv);
}
return subType;
}
private JavaType _resolveAndValidateGeneric(JavaType baseType, String subClass,
PolymorphicTypeValidator ptv, int ltIndex)
{
// 24-Apr-2019, tatu: Not 100% sure if we should pass name with type parameters
// or not, but guessing it's more convenient not to have to worry about it so
// strip out
PolymorphicTypeValidator.Validity vld = ptv.validateSubClassName(this, baseType, subClass.substring(0, ltIndex));
if (vld == Validity.DENIED) {
return _throwSubtypeNameNotAllowed(baseType, subClass, ptv);
}
JavaType subType = getTypeFactory().constructFromCanonical(subClass);
if (!subType.isTypeOrSubTypeOf(baseType.getRawClass())) {
return _throwNotASubtype(baseType, subClass);
}
// Unless we were approved already by name, check that actual sub-class acceptable:
if (vld != Validity.ALLOWED) {
if (ptv.validateSubType(this, baseType, subType) != Validity.ALLOWED) {
return _throwSubtypeClassNotAllowed(baseType, subClass, ptv);
}
}
// [databind#5988]: even when the container class is approved (by name or
// by sub-type matcher), the attacker-supplied type parameters must still
// be validated -- otherwise a name-based allow-list for a safe container
// (e.g. "java.util.ArrayList") can be bypassed by smuggling a gadget
// class as a type argument (e.g. "java.util.ArrayList<EvilGadget>").
// [CVE-2026-110879]: ... and validated as if named as a direct type id,
// see `_validateContainedType()`
_validateContainedTypes(baseType, subClass, subType, ptv);
return subType;
}
// Approval of base type does not extend to contained types: need configured validator
private static PolymorphicTypeValidator _configuredValidator(PolymorphicTypeValidator ptv) {
if (ptv instanceof ApprovedBaseTypeValidator) {
return ((ApprovedBaseTypeValidator) ptv).getTypeParameterValidator();
}
return ptv;
}
// Declared type of array elements: element type of declared type if it is an
// array type; `Object` for other declared types (which can only be `Object`,
// `Serializable` or `Cloneable`, supertypes of all arrays); `null` (none) if
// no declared type
private static JavaType _declaredContentType(JavaType declaredType) {
if (declaredType == null) {
return null;
}
if (declaredType.isArrayType()) {
return declaredType.getContentType();
}
return TypeFactory.unknownType();
}
/**
* Helper for [databind#5988]: validate all type parameters of given type,
* recursively (for nested generics like {@code Map<String, List<Evil>>}
* or {@code List<Evil[]>}).
*
* @since 3.1.8
*/
private void _validateContainedTypes(JavaType baseType, String typeId, JavaType type,
PolymorphicTypeValidator ptv)
{
for (int i = 0, n = type.containedTypeCount(); i < n; ++i) {
_validateContainedType(baseType, typeId, null,
type.containedType(i), ptv);
}
}
/**
* Helper for validating a class named in a type id as type parameter (or as element
* type of an array type): it gets the same checks a class named directly as type id
* would get. Unless it is a safe type (see {@link #_isSafeTypeParameter}) or exactly
* its declared type, the {@link SubTypeValidator} deny list applies, and:
*<ul>
* <li>If assignable to polymorphic base type, checked as if a direct type id for it
* </li>
* <li>Otherwise checked by configured {@link PolymorphicTypeValidator} as a direct type
* id for its declared type (element type of declared array type for array
* elements; none -- validated as {@code Object} -- otherwise), starting with
* {@link PolymorphicTypeValidator#validateBaseType}: approval of polymorphic base
* type does not extend to it (except that approval of declared array type covers
* subtypes of its element type, other than for {@code Object[]})
* </li>
*</ul>
* Assignment compatibility with declared type parameters of the base type is
* NOT verified: classes named in the type id are the only attacker-selected
* ones (other types come from the declared base type or from class
* declarations), so validating them suffices to prevent instantiation of
* unapproved types.
*
* @param baseType Polymorphic base type
* @param declaredType Declared type of the contained type, if any ({@code null} if none)
* @param ptv Validator used for validating type id as subtype of {@code baseType}
*
* @since 3.1.8
*/
private void _validateContainedType(JavaType baseType, String typeId, JavaType declaredType,
JavaType param, PolymorphicTypeValidator ptv)
{
if (!_isSafeTypeParameter(param)
&& !((declaredType != null) && param.hasRawClass(declaredType.getRawClass()))) {
final PolymorphicTypeValidator vldr;
final JavaType vldBaseType;
Validity vld;
if (param.isTypeOrSubTypeOf(baseType.getRawClass())) {
// validity of base type itself already checked
vldr = ptv;
vldBaseType = baseType;
vld = Validity.INDETERMINATE;
} else if ((ptv instanceof ApprovedBaseTypeValidator)
&& (declaredType != null) && !ClassUtil.isArraySuperType(declaredType.getRawClass())
&& param.isTypeOrSubTypeOf(declaredType.getRawClass())) {
// Approval of base type (declared array type) covers subtypes of its
// element type (except for `Object[]`, `Serializable[]` and
// `Cloneable[]`, which would cover everything)
vldr = ptv;
vldBaseType = declaredType;
vld = Validity.ALLOWED;
} else {
vldr = _configuredValidator(ptv);
vldBaseType = (declaredType == null) ? TypeFactory.unknownType() : declaredType;
try {
vld = vldr.validateBaseType(this, vldBaseType);
} catch (InvalidDefinitionException e) {
// `DefaultBaseTypeLimitingValidator` reports unsafe base types (like
// `Object`) this way: for contained types, report as denial of the type
vld = Validity.DENIED;
}
}
if ((vld == Validity.INDETERMINATE)
&& _isApprovedSubType(vldr, vldBaseType, param)) {
vld = Validity.ALLOWED;
}
if (vld != Validity.ALLOWED) {
throw invalidTypeIdException(baseType, typeId,
"Configured `PolymorphicTypeValidator` (of type "
+ ClassUtil.classNameOf(vldr)
+ ") denied resolution of contained type "
+ ClassUtil.getTypeDescription(param));
}
// NOTE: `BeanDeserializerFactory` also checks deny list for types it creates
// bean deserializers for, but not all contained types get one (Map keys,
// for example, may be deserialized via single-String constructor)
// (same exception as `BeanDeserializerFactory` would throw)
if (SubTypeValidator.instance().isIllegalType(param.getRawClass())) {
reportBadDefinition(param, String.format(
"Illegal type (%s) to deserialize: prevented for security reasons",
param.getRawClass().getName()));
}
}
if (param.isArrayType()) {
_validateArrayElementType(baseType, typeId, declaredType, param, ptv);
} else {
_validateContainedTypes(baseType, typeId, param, ptv);
}
}
/**
* Helper for validating element type of an array type: like other contained types
* (including abstract types: they may still be deserializable, via
* {@code @JsonDeserialize(as=...)} or factory methods). Approval of the array
* type does not by itself cover the element type: only the validator can say
* it does (see {@link PolymorphicTypeValidator#isArrayTypeAllowedExplicitly},
* checked by the caller for array types named by type id). Inner dimensions
* and array type parameters always get here.
*
* @param declaredType Declared type of the array, if any ({@code null} if none)
*
* @since 3.1.8
*/
private void _validateArrayElementType(JavaType baseType, String typeId,
JavaType declaredType, JavaType arrayType,
PolymorphicTypeValidator ptv)
{
_validateContainedType(baseType, typeId, _declaredContentType(declaredType),
arrayType.getContentType(), ptv);
}
private boolean _isApprovedSubType(PolymorphicTypeValidator vldr,
JavaType baseType, JavaType subType)
{
Validity vld = vldr.validateSubClassName(this, baseType, subType.getRawClass().getName());
if (vld == Validity.INDETERMINATE) {
vld = vldr.validateSubType(this, baseType, subType);
}
return (vld == Validity.ALLOWED);
}
/**
* Types that may be used as type parameters without validation: ones that
* cannot be used as "gadget" types, since no attacker-controlled
* constructor or setter can be invoked.
*<p>
* {@code Object} is the canonical resolution of wildcards and unbound parameters;
* enum types are JVM-managed singletons resolved by name lookup;
* {@code String}, primitive types (including wrappers), {@code Number},
* {@code BigDecimal}, {@code BigInteger} and {@code UUID} are handled by Jackson
* as scalars (subtypes of {@code Number} other than these need type ids); and arrays
* and {@code java.util} container (Collection, Map, reference) types are JDK types
* whose contained types are validated separately.
*/
private static boolean _isSafeTypeParameter(JavaType param)
{
if (param.isJavaLangObject() || param.isEnumType() || param.isArrayType()) {
return true;
}
final Class<?> raw = param.getRawClass();
if ((param.isContainerType() || param.isReferenceType())
&& raw.getName().startsWith("java.util.")) {
return true;
}
return (raw == String.class) || (ClassUtil.primitiveType(raw) != null)
|| (raw == Number.class) || (raw == BigDecimal.class) || (raw == BigInteger.class) || (raw == UUID.class);
}
protected <T> T _throwNotASubtype(JavaType baseType, String subType) throws DatabindException {
throw invalidTypeIdException(baseType, subType, "Not a subtype");
}
protected <T> T _throwSubtypeNameNotAllowed(JavaType baseType, String subType,
PolymorphicTypeValidator ptv)
throws DatabindException
{
throw invalidTypeIdException(baseType, subType,
"Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution");
}
protected <T> T _throwSubtypeClassNotAllowed(JavaType baseType, String subType,
PolymorphicTypeValidator ptv)
throws DatabindException
{
throw invalidTypeIdException(baseType, subType,
"Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution");
}
/**
* Helper method for constructing exception to indicate that given type id
* could not be resolved to a valid subtype of specified base type.
* Most commonly called during polymorphic deserialization.
*<p>
* Note that most of the time this method should NOT be called directly: instead,
* method <code>handleUnknownTypeId()</code> should be called which will call this method
* if necessary.
*/
protected abstract DatabindException invalidTypeIdException(JavaType baseType, String typeId,
String extraDesc);
public abstract TypeFactory getTypeFactory();
/*
/**********************************************************************
/* Annotation, BeanDescription introspection
/**********************************************************************
*/
/**
* Convenience method for doing full "for serialization or deserialization"
* introspection of specified type; results may be cached for duration (lifespan)
* of this context as well.
*/
public final BeanDescription introspectBeanDescription(JavaType type) {
return introspectBeanDescription(type, introspectClassAnnotations(type));
}
public abstract BeanDescription introspectBeanDescription(JavaType type,
AnnotatedClass classDef);
public BeanDescription.Supplier lazyIntrospectBeanDescription(JavaType type) {
return new BeanDescription.LazySupplier(getConfig(), type) {
@Override
protected BeanDescription _construct(JavaType forType, AnnotatedClass ac) {
// System.out.println("lazyIntrospectBeanDescription.beanDesc("+forType+")");
return introspectBeanDescription(forType);
}
@Override
protected AnnotatedClass _introspect(JavaType forType) {
// System.out.println("lazyIntrospectBeanDescription.annotatedClass("+forType+")");
return introspectClassAnnotations(forType);
}
};
}
public AnnotatedClass introspectClassAnnotations(JavaType type) {
return classIntrospector().introspectClassAnnotations(type);
}
public AnnotatedClass introspectDirectClassAnnotations(JavaType type) {
return classIntrospector().introspectDirectClassAnnotations(type);
}
public AnnotatedClass introspectClassAnnotations(Class<?> rawType) {
return introspectClassAnnotations(constructType(rawType));
}
protected abstract ClassIntrospector classIntrospector();
/*
/**********************************************************************
/* Helper object construction
/**********************************************************************
*/
public ObjectIdGenerator<?> objectIdGeneratorInstance(Annotated annotated,
ObjectIdInfo objectIdInfo)
{
Class<?> implClass = objectIdInfo.getGeneratorType();
final MapperConfig<?> config = getConfig();
HandlerInstantiator hi = config.getHandlerInstantiator();
ObjectIdGenerator<?> gen = (hi == null) ? null : hi.objectIdGeneratorInstance(config, annotated, implClass);
if (gen == null) {
gen = (ObjectIdGenerator<?>) ClassUtil.createInstance(implClass,
config.canOverrideAccessModifiers());
}
return gen.forScope(objectIdInfo.getScope());
}
public ObjectIdResolver objectIdResolverInstance(Annotated annotated, ObjectIdInfo objectIdInfo)
{
Class<? extends ObjectIdResolver> implClass = objectIdInfo.getResolverType();
final MapperConfig<?> config = getConfig();
HandlerInstantiator hi = config.getHandlerInstantiator();
ObjectIdResolver resolver = (hi == null) ? null : hi.resolverIdGeneratorInstance(config, annotated, implClass);
if (resolver == null) {
resolver = ClassUtil.createInstance(implClass, config.canOverrideAccessModifiers());
}
return resolver;
}
/**
* Helper method to use to construct a {@link Converter}, given a definition
* that may be either actual converter instance, or Class for instantiating one.
*/
@SuppressWarnings("unchecked")
public Converter<Object,Object> converterInstance(Annotated annotated,
Object converterDef)
{
if (converterDef == null) {
return null;
}
if (converterDef instanceof Converter<?,?>) {
return (Converter<Object,Object>) converterDef;
}
if (!(converterDef instanceof Class)) {
throw new IllegalStateException("AnnotationIntrospector returned Converter definition of type "
+converterDef.getClass().getName()+"; expected type Converter or Class<Converter> instead");
}
Class<?> converterClass = (Class<?>)converterDef;
// there are some known "no class" markers to consider too:
if (converterClass == Converter.None.class || ClassUtil.isBogusClass(converterClass)) {
return null;
}
if (!Converter.class.isAssignableFrom(converterClass)) {
throw new IllegalStateException("AnnotationIntrospector returned Class "
+converterClass.getName()+"; expected Class<Converter>");
}
final MapperConfig<?> config = getConfig();
HandlerInstantiator hi = config.getHandlerInstantiator();
Converter<?,?> conv = (hi == null) ? null : hi.converterInstance(config, annotated, converterClass);
if (conv == null) {
conv = (Converter<?,?>) ClassUtil.createInstance(converterClass,
config.canOverrideAccessModifiers());
}
return (Converter<Object,Object>) conv;
}
/*
/**********************************************************************
/* Misc config access
/**********************************************************************
*/
public abstract PropertyName findRootName(JavaType rootType);
public abstract PropertyName findRootName(Class<?> rawRootType);
/*
/**********************************************************************
/* Misc helper methods
/**********************************************************************
*/
/**
* Helper method for applying default canonicalization method (currently
* {@code String.intern()}) for String, returning canonical instance.
*
* @since 3.2
*/
public String canonicalizeString(String str) {
if (str == null) {
return str;
}
return InternCache.instance.intern(str);
}
/*
/**********************************************************************
/* Error reporting
/**********************************************************************
*/
/**
* Helper method called to indicate a generic problem that stems from type
* definition(s), not input data, or input/output state; typically this
* means throwing a {@link tools.jackson.databind.exc.InvalidDefinitionException}.
*/
public abstract <T> T reportBadDefinition(JavaType type, String msg)
throws DatabindException;
public <T> T reportBadDefinition(Class<?> type, String msg)
throws DatabindException
{
return reportBadDefinition(constructType(type), msg);
}
public abstract <T> T reportBadTypeDefinition(BeanDescription bean,
String msg, Object... msgArgs)
throws DatabindException;
public <T> T reportBadTypeDefinition(BeanDescription.Supplier beanDescRef,
String msg, Object... msgArgs)
throws DatabindException {
return reportBadTypeDefinition(beanDescRef.get(), msg, msgArgs);
}
/*
/**********************************************************************
/* Helper methods
/**********************************************************************
*/
protected final String _format(String msg, Object... msgArgs) {
if (msgArgs.length > 0) {
return msg.formatted(msgArgs);
}
return msg;
}
protected final String _truncate(String desc) {
if (desc == null) {
return "";
}
if (desc.length() <= MAX_ERROR_STR_LEN) {
return desc;
}
return desc.substring(0, MAX_ERROR_STR_LEN) + "]...[" + desc.substring(desc.length() - MAX_ERROR_STR_LEN);
}
protected String _quotedString(String desc) {
if (desc == null) {
return "[N/A]";
}
// !!! should we quote it? (in case there are control chars, linefeeds)
return "\"" + _truncate(desc) + "\"";
}
protected String _colonConcat(String msgBase, String extra) {
if (extra == null) {
return msgBase;
}
return msgBase + ": " + extra;
}
protected String _desc(String desc) {
if (desc == null) {
return "[N/A]";
}
// !!! should we quote it? (in case there are control chars, linefeeds)
return _truncate(desc);
}
}