Skip to content

Commit a5349eb

Browse files
committed
Base64Utils falls back to JAXB DatatypeConverter for String-based encoding
Issue: SPR-12938
1 parent 5b47504 commit a5349eb

File tree

3 files changed

+81
-35
lines changed

3 files changed

+81
-35
lines changed

spring-core/src/main/java/org/springframework/util/Base64Utils.java

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,25 @@
1818

1919
import java.nio.charset.Charset;
2020
import java.util.Base64;
21+
import javax.xml.bind.DatatypeConverter;
2122

2223
import org.springframework.lang.UsesJava8;
2324

2425
/**
2526
* A simple utility class for Base64 encoding and decoding.
2627
*
27-
* <p>Adapts to either Java 8's {@link java.util.Base64} class or Apache
28-
* Commons Codec's {@link org.apache.commons.codec.binary.Base64} class.
29-
* With neither Java 8 nor Commons Codec present, encode/decode calls
30-
* will fail with an IllegalStateException.
28+
* <p>Adapts to either Java 8's {@link java.util.Base64} class or Apache Commons Codec's
29+
* {@link org.apache.commons.codec.binary.Base64} class. With neither Java 8 nor Commons
30+
* Codec present, {@link #encode}/{@link #decode} calls will throw an IllegalStateException.
31+
* However, as of Spring 4.2, {@link #encodeToString} and {@link #decodeFromString} will
32+
* nevertheless work since they can delegate to the JAXB DatatypeConverter as a fallback.
3133
*
3234
* @author Juergen Hoeller
3335
* @since 4.1
3436
* @see java.util.Base64
3537
* @see org.apache.commons.codec.binary.Base64
38+
* @see javax.xml.bind.DatatypeConverter#printBase64Binary
39+
* @see javax.xml.bind.DatatypeConverter#parseBase64Binary
3640
*/
3741
public abstract class Base64Utils {
3842

@@ -55,73 +59,84 @@ else if (ClassUtils.isPresent("org.apache.commons.codec.binary.Base64", Base64Ut
5559
}
5660

5761
/**
58-
* Assert that Byte64 encoding is actually supported.
62+
* Assert that Byte64 encoding between byte arrays is actually supported.
5963
* @throws IllegalStateException if neither Java 8 nor Apache Commons Codec is present
6064
*/
61-
private static void assertSupported() {
62-
Assert.state(delegate != null, "Neither Java 8 nor Apache Commons Codec found - Base64 encoding not supported");
65+
private static void assertDelegateAvailable() {
66+
Assert.state(delegate != null,
67+
"Neither Java 8 nor Apache Commons Codec found - Base64 encoding between byte arrays not supported");
6368
}
6469

6570

6671
/**
6772
* Base64-encode the given byte array.
6873
* @param src the original byte array (may be {@code null})
6974
* @return the encoded byte array (or {@code null} if the input was {@code null})
70-
* @throws IllegalStateException if Base64 encoding is not supported,
71-
* i.e. neither Java 8 nor Apache Commons Codec is present at runtime
75+
* @throws IllegalStateException if Base64 encoding between byte arrays is not
76+
* supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime
7277
*/
7378
public static byte[] encode(byte[] src) {
74-
assertSupported();
79+
assertDelegateAvailable();
7580
return delegate.encode(src);
7681
}
7782

83+
/**
84+
* Base64-decode the given byte array.
85+
* @param src the encoded byte array (may be {@code null})
86+
* @return the original byte array (or {@code null} if the input was {@code null})
87+
* @throws IllegalStateException if Base64 encoding between byte arrays is not
88+
* supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime
89+
*/
90+
public static byte[] decode(byte[] src) {
91+
assertDelegateAvailable();
92+
return delegate.decode(src);
93+
}
94+
7895
/**
7996
* Base64-encode the given byte array to a String.
8097
* @param src the original byte array (may be {@code null})
8198
* @return the encoded byte array as a UTF-8 String
8299
* (or {@code null} if the input was {@code null})
83-
* @throws IllegalStateException if Base64 encoding is not supported,
84-
* i.e. neither Java 8 nor Apache Commons Codec is present at runtime
85100
*/
86101
public static String encodeToString(byte[] src) {
87-
assertSupported();
88102
if (src == null) {
89103
return null;
90104
}
91105
if (src.length == 0) {
92106
return "";
93107
}
94-
return new String(delegate.encode(src), DEFAULT_CHARSET);
95-
}
96108

97-
/**
98-
* Base64-decode the given byte array.
99-
* @param src the encoded byte array (may be {@code null})
100-
* @return the original byte array (or {@code null} if the input was {@code null})
101-
* @throws IllegalStateException if Base64 encoding is not supported,
102-
* i.e. neither Java 8 nor Apache Commons Codec is present at runtime
103-
*/
104-
public static byte[] decode(byte[] src) {
105-
assertSupported();
106-
return delegate.decode(src);
109+
if (delegate != null) {
110+
// Full encoder available
111+
return new String(delegate.encode(src), DEFAULT_CHARSET);
112+
}
113+
else {
114+
// JAXB fallback for String case
115+
return DatatypeConverter.printBase64Binary(src);
116+
}
107117
}
108118

109119
/**
110120
* Base64-decode the given byte array from an UTF-8 String.
111121
* @param src the encoded UTF-8 String (may be {@code null})
112122
* @return the original byte array (or {@code null} if the input was {@code null})
113-
* @throws IllegalStateException if Base64 encoding is not supported,
114-
* i.e. neither Java 8 nor Apache Commons Codec is present at runtime
115123
*/
116124
public static byte[] decodeFromString(String src) {
117-
assertSupported();
118125
if (src == null) {
119126
return null;
120127
}
121128
if (src.length() == 0) {
122129
return new byte[0];
123130
}
124-
return delegate.decode(src.getBytes(DEFAULT_CHARSET));
131+
132+
if (delegate != null) {
133+
// Full encoder available
134+
return delegate.decode(src.getBytes(DEFAULT_CHARSET));
135+
}
136+
else {
137+
// JAXB fallback for String case
138+
return DatatypeConverter.parseBase64Binary(src);
139+
}
125140
}
126141

127142

spring-core/src/test/java/org/springframework/util/Base64UtilsTests.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,67 @@
1818

1919
import java.io.UnsupportedEncodingException;
2020

21+
import javax.xml.bind.DatatypeConverter;
22+
2123
import org.junit.Test;
2224

2325
import static org.junit.Assert.*;
2426

2527
/**
2628
* @author Juergen Hoeller
29+
* @since 4.2
2730
*/
2831
public class Base64UtilsTests {
2932

3033
@Test
31-
public void jdk8VsCommonsCodec() throws UnsupportedEncodingException {
34+
public void encodeWithJdk8VsCommonsCodec() throws UnsupportedEncodingException {
3235
Base64Utils.Base64Delegate jdkDelegate = new Base64Utils.JdkBase64Delegate();
3336
Base64Utils.Base64Delegate commonsDelegate = new Base64Utils.CommonsCodecBase64Delegate();
3437

3538
byte[] bytes = new byte[]
3639
{-0x4f, 0xa, -0x73, -0x4f, 0x64, -0x20, 0x75, 0x41, 0x5, -0x49, -0x57, -0x65, -0x19, 0x2e, 0x3f, -0x1b};
3740
assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes));
41+
assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes)));
42+
assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes)));
3843

3944
bytes = "Hello World".getBytes("UTF-8");
4045
assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes));
46+
assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes)));
47+
assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes)));
4148

4249
bytes = "Hello World\r\nSecond Line".getBytes("UTF-8");
4350
assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes));
51+
assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes)));
52+
assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes)));
4453

4554
bytes = "Hello World\r\nSecond Line\r\n".getBytes("UTF-8");
4655
assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes));
56+
assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes)));
57+
assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes)));
58+
}
59+
60+
@Test
61+
public void encodeToStringWithJdk8VsJaxb() throws UnsupportedEncodingException {
62+
byte[] bytes = new byte[]
63+
{-0x4f, 0xa, -0x73, -0x4f, 0x64, -0x20, 0x75, 0x41, 0x5, -0x49, -0x57, -0x65, -0x19, 0x2e, 0x3f, -0x1b};
64+
assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes));
65+
assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes)));
66+
assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes)));
67+
68+
bytes = "Hello World".getBytes("UTF-8");
69+
assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes));
70+
assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes)));
71+
assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes)));
72+
73+
bytes = "Hello World\r\nSecond Line".getBytes("UTF-8");
74+
assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes));
75+
assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes)));
76+
assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes)));
77+
78+
bytes = "Hello World\r\nSecond Line\r\n".getBytes("UTF-8");
79+
assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes));
80+
assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes)));
81+
assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes)));
4782
}
4883

4984
}

spring-web/src/main/java/org/springframework/http/converter/json/GsonBuilderUtils.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -52,10 +52,6 @@ public abstract class GsonBuilderUtils {
5252
* On Java 8, the standard {@link java.util.Base64} facility is used instead.
5353
*/
5454
public static GsonBuilder gsonBuilderWithBase64EncodedByteArrays() {
55-
// Assert that Base64 support is available, as long we're not on Java 8+
56-
Base64Utils.encode(null);
57-
58-
// Now, construct a pre-configured GsonBuilder...
5955
GsonBuilder builder = new GsonBuilder();
6056
builder.registerTypeHierarchyAdapter(byte[].class, new Base64TypeAdapter());
6157
return builder;

0 commit comments

Comments
 (0)