Skip to content

Commit cd523c7

Browse files
committed
Restored overridable getXStream() method in Spring Framework 3.2.x
Also setting the bean class loader on the XStream instance itself now. Issue: SPR-10421
1 parent 20ddd32 commit cd523c7

File tree

1 file changed

+60
-52
lines changed

1 file changed

+60
-52
lines changed

spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,28 @@
7575
/**
7676
* Implementation of the {@code Marshaller} interface for XStream.
7777
*
78-
* <p>By default, XStream does not require any further configuration,
79-
* though class aliases can be used to have more control over the behavior of XStream.
78+
* <p>By default, XStream does not require any further configuration and can (un)marshal
79+
* any class on the classpath. As such, it is <b>not recommended to use the
80+
* {@code XStreamMarshaller} to unmarshal XML from external sources</b> (i.e. the Web),
81+
* as this can result in <b>security vulnerabilities</b>. If you do use the
82+
* {@code XStreamMarshaller} to unmarshal external XML, set the
83+
* {@link #setConverters(ConverterMatcher[]) converters} and
84+
* {@link #setSupportedClasses(Class[]) supportedClasses} properties or override the
85+
* {@link #customizeXStream(XStream)} method to make sure it only accepts the classes
86+
* you want it to support.
8087
*
8188
* <p>Due to XStream's API, it is required to set the encoding used for writing to OutputStreams.
8289
* It defaults to {@code UTF-8}.
8390
*
8491
* <p><b>NOTE:</b> XStream is an XML serialization library, not a data binding library.
8592
* Therefore, it has limited namespace support. As such, it is rather unsuitable for
86-
* usage within Web services.
93+
* usage within Web Services.
94+
*
95+
* <p>This marshaller is compatible with XStream 1.3 and 1.4.
8796
*
8897
* @author Peter Meijer
8998
* @author Arjen Poutsma
9099
* @since 3.0
91-
* @see #setAliases
92-
* @see #setConverters
93-
* @see #setEncoding
94100
*/
95101
public class XStreamMarshaller extends AbstractMarshaller implements InitializingBean, BeanClassLoaderAware {
96102

@@ -106,15 +112,19 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
106112

107113
private String encoding = DEFAULT_ENCODING;
108114

109-
private Class[] supportedClasses;
115+
private Class<?>[] supportedClasses;
110116

111-
private ClassLoader classLoader;
117+
private ClassLoader beanClassLoader;
112118

113119

114120
/**
115-
* Returns the XStream instance used by this marshaller.
121+
* Return the XStream instance used by this marshaller.
122+
* <p><b>NOTE:</b> While this method can be overridden in Spring 3.x,
123+
* it wasn't originally meant to be. As of Spring 4.0, it will be
124+
* marked as final, with all of XStream 1.4's configurable strategies
125+
* to be exposed on XStreamMarshaller itself.
116126
*/
117-
public final XStream getXStream() {
127+
public XStream getXStream() {
118128
return this.xstream;
119129
}
120130

@@ -125,7 +135,7 @@ public final XStream getXStream() {
125135
* @see XStream#NO_REFERENCES
126136
*/
127137
public void setMode(int mode) {
128-
this.xstream.setMode(mode);
138+
getXStream().setMode(mode);
129139
}
130140

131141
/**
@@ -137,10 +147,10 @@ public void setMode(int mode) {
137147
public void setConverters(ConverterMatcher[] converters) {
138148
for (int i = 0; i < converters.length; i++) {
139149
if (converters[i] instanceof Converter) {
140-
this.xstream.registerConverter((Converter) converters[i], i);
150+
getXStream().registerConverter((Converter) converters[i], i);
141151
}
142152
else if (converters[i] instanceof SingleValueConverter) {
143-
this.xstream.registerConverter((SingleValueConverter) converters[i], i);
153+
getXStream().registerConverter((SingleValueConverter) converters[i], i);
144154
}
145155
else {
146156
throw new IllegalArgumentException("Invalid ConverterMatcher [" + converters[i] + "]");
@@ -156,7 +166,7 @@ else if (converters[i] instanceof SingleValueConverter) {
156166
public void setAliases(Map<String, ?> aliases) throws ClassNotFoundException {
157167
Map<String, Class<?>> classMap = toClassMap(aliases);
158168
for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
159-
this.xstream.alias(entry.getKey(), entry.getValue());
169+
getXStream().alias(entry.getKey(), entry.getValue());
160170
}
161171
}
162172

@@ -169,7 +179,7 @@ public void setAliases(Map<String, ?> aliases) throws ClassNotFoundException {
169179
public void setAliasesByType(Map<String, ?> aliases) throws ClassNotFoundException {
170180
Map<String, Class<?>> classMap = toClassMap(aliases);
171181
for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
172-
this.xstream.aliasType(entry.getKey(), entry.getValue());
182+
getXStream().aliasType(entry.getKey(), entry.getValue());
173183
}
174184
}
175185

@@ -178,16 +188,16 @@ private Map<String, Class<?>> toClassMap(Map<String, ?> map) throws ClassNotFoun
178188
for (Map.Entry<String, ?> entry : map.entrySet()) {
179189
String key = entry.getKey();
180190
Object value = entry.getValue();
181-
Class type;
191+
Class<?> type;
182192
if (value instanceof Class) {
183-
type = (Class) value;
193+
type = (Class<?>) value;
184194
}
185195
else if (value instanceof String) {
186-
String s = (String) value;
187-
type = ClassUtils.forName(s, classLoader);
196+
String className = (String) value;
197+
type = ClassUtils.forName(className, this.beanClassLoader);
188198
}
189199
else {
190-
throw new IllegalArgumentException("Unknown value [" + value + "], expected String or Class");
200+
throw new IllegalArgumentException("Unknown value [" + value + "] - expected String or Class");
191201
}
192202
result.put(key, type);
193203
}
@@ -205,9 +215,9 @@ public void setFieldAliases(Map<String, String> aliases) throws ClassNotFoundExc
205215
int idx = field.lastIndexOf('.');
206216
if (idx != -1) {
207217
String className = field.substring(0, idx);
208-
Class clazz = ClassUtils.forName(className, classLoader);
218+
Class<?> clazz = ClassUtils.forName(className, this.beanClassLoader);
209219
String fieldName = field.substring(idx + 1);
210-
this.xstream.aliasField(alias, clazz, fieldName);
220+
getXStream().aliasField(alias, clazz, fieldName);
211221
}
212222
else {
213223
throw new IllegalArgumentException("Field name [" + field + "] does not contain '.'");
@@ -219,9 +229,9 @@ public void setFieldAliases(Map<String, String> aliases) throws ClassNotFoundExc
219229
* Set types to use XML attributes for.
220230
* @see XStream#useAttributeFor(Class)
221231
*/
222-
public void setUseAttributeForTypes(Class[] types) {
223-
for (Class type : types) {
224-
this.xstream.useAttributeFor(type);
232+
public void setUseAttributeForTypes(Class<?>[] types) {
233+
for (Class<?> type : types) {
234+
getXStream().useAttributeFor(type);
225235
}
226236
}
227237

@@ -237,36 +247,33 @@ public void setUseAttributeFor(Map<?, ?> attributes) {
237247
for (Map.Entry<?, ?> entry : attributes.entrySet()) {
238248
if (entry.getKey() instanceof String) {
239249
if (entry.getValue() instanceof Class) {
240-
this.xstream.useAttributeFor((String) entry.getKey(), (Class) entry.getValue());
250+
getXStream().useAttributeFor((String) entry.getKey(), (Class) entry.getValue());
241251
}
242252
else {
243253
throw new IllegalArgumentException(
244-
"Invalid argument 'attributes'. 'useAttributesFor' property takes map of <String, Class>," +
245-
" when using a map key of type String");
254+
"'useAttributesFor' takes Map<String, Class> when using a map key of type String");
246255
}
247256
}
248257
else if (entry.getKey() instanceof Class) {
249258
Class<?> key = (Class<?>) entry.getKey();
250259
if (entry.getValue() instanceof String) {
251-
this.xstream.useAttributeFor(key, (String) entry.getValue());
260+
getXStream().useAttributeFor(key, (String) entry.getValue());
252261
}
253262
else if (entry.getValue() instanceof List) {
254-
List list = (List) entry.getValue();
255-
256-
for (Object o : list) {
257-
if (o instanceof String) {
258-
this.xstream.useAttributeFor(key, (String) o);
263+
List listValue = (List) entry.getValue();
264+
for (Object element : listValue) {
265+
if (element instanceof String) {
266+
getXStream().useAttributeFor(key, (String) element);
259267
}
260268
}
261269
}
262270
else {
263-
throw new IllegalArgumentException("Invalid argument 'attributes'. " +
264-
"'useAttributesFor' property takes either <Class, String> or <Class, List<String>> map," +
265-
" when using a map key of type Class");
271+
throw new IllegalArgumentException("'useAttributesFor' property takes either Map<Class, String> " +
272+
"or Map<Class, List<String>> when using a map key of type Class");
266273
}
267274
}
268275
else {
269-
throw new IllegalArgumentException("Invalid argument 'attributes. " +
276+
throw new IllegalArgumentException(
270277
"'useAttributesFor' property takes either a map key of type String or Class");
271278
}
272279
}
@@ -281,7 +288,7 @@ public void setImplicitCollections(Map<Class<?>, String> implicitCollections) {
281288
for (Map.Entry<Class<?>, String> entry : implicitCollections.entrySet()) {
282289
String[] collectionFields = StringUtils.commaDelimitedListToStringArray(entry.getValue());
283290
for (String collectionField : collectionFields) {
284-
this.xstream.addImplicitCollection(entry.getKey(), collectionField);
291+
getXStream().addImplicitCollection(entry.getKey(), collectionField);
285292
}
286293
}
287294
}
@@ -295,7 +302,7 @@ public void setOmittedFields(Map<Class<?>, String> omittedFields) {
295302
for (Map.Entry<Class<?>, String> entry : omittedFields.entrySet()) {
296303
String[] fields = StringUtils.commaDelimitedListToStringArray(entry.getValue());
297304
for (String field : fields) {
298-
this.xstream.omitField(entry.getKey(), field);
305+
getXStream().omitField(entry.getKey(), field);
299306
}
300307
}
301308
}
@@ -306,7 +313,7 @@ public void setOmittedFields(Map<Class<?>, String> omittedFields) {
306313
*/
307314
public void setAnnotatedClass(Class<?> annotatedClass) {
308315
Assert.notNull(annotatedClass, "'annotatedClass' must not be null");
309-
this.xstream.processAnnotations(annotatedClass);
316+
getXStream().processAnnotations(annotatedClass);
310317
}
311318

312319
/**
@@ -315,17 +322,17 @@ public void setAnnotatedClass(Class<?> annotatedClass) {
315322
*/
316323
public void setAnnotatedClasses(Class<?>[] annotatedClasses) {
317324
Assert.notEmpty(annotatedClasses, "'annotatedClasses' must not be empty");
318-
this.xstream.processAnnotations(annotatedClasses);
325+
getXStream().processAnnotations(annotatedClasses);
319326
}
320327

321328
/**
322-
* Set the autodetection mode of XStream.
323-
* <p><strong>Note</strong> that auto-detection implies that the XStream is configured while
329+
* Activate XStream's autodetection mode.
330+
* <p><b>Note</b>: Autodetection implies that the XStream instance is being configured while
324331
* it is processing the XML streams, and thus introduces a potential concurrency problem.
325332
* @see XStream#autodetectAnnotations(boolean)
326333
*/
327334
public void setAutodetectAnnotations(boolean autodetectAnnotations) {
328-
this.xstream.autodetectAnnotations(autodetectAnnotations);
335+
getXStream().autodetectAnnotations(autodetectAnnotations);
329336
}
330337

331338
/**
@@ -348,17 +355,18 @@ public void setEncoding(String encoding) {
348355
* <p>If this property is empty (the default), all classes are supported.
349356
* @see #supports(Class)
350357
*/
351-
public void setSupportedClasses(Class[] supportedClasses) {
358+
public void setSupportedClasses(Class<?>[] supportedClasses) {
352359
this.supportedClasses = supportedClasses;
353360
}
354361

355362
public void setBeanClassLoader(ClassLoader classLoader) {
356-
this.classLoader = classLoader;
363+
this.beanClassLoader = classLoader;
364+
getXStream().setClassLoader(classLoader);
357365
}
358366

359367

360368
public final void afterPropertiesSet() throws Exception {
361-
customizeXStream(this.xstream);
369+
customizeXStream(getXStream());
362370
}
363371

364372
/**
@@ -370,12 +378,12 @@ protected void customizeXStream(XStream xstream) {
370378
}
371379

372380

373-
public boolean supports(Class clazz) {
381+
public boolean supports(Class<?> clazz) {
374382
if (ObjectUtils.isEmpty(this.supportedClasses)) {
375383
return true;
376384
}
377385
else {
378-
for (Class supportedClass : this.supportedClasses) {
386+
for (Class<?> supportedClass : this.supportedClasses) {
379387
if (supportedClass.isAssignableFrom(clazz)) {
380388
return true;
381389
}
@@ -453,7 +461,7 @@ protected void marshalWriter(Object graph, Writer writer) throws XmlMappingExcep
453461
*/
454462
private void marshal(Object graph, HierarchicalStreamWriter streamWriter) {
455463
try {
456-
this.xstream.marshal(graph, streamWriter);
464+
getXStream().marshal(graph, streamWriter);
457465
}
458466
catch (Exception ex) {
459467
throw convertXStreamException(ex, true);
@@ -536,7 +544,7 @@ protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource
536544
*/
537545
private Object unmarshal(HierarchicalStreamReader streamReader) {
538546
try {
539-
return this.xstream.unmarshal(streamReader);
547+
return getXStream().unmarshal(streamReader);
540548
}
541549
catch (Exception ex) {
542550
throw convertXStreamException(ex, false);

0 commit comments

Comments
 (0)