Skip to content

Commit 356eaef

Browse files
committed
Merge pull request #935 from garyrussell/SPR-13784
Base64Utils: URL/File Safe Alphabet Issue: SPR-13784
2 parents aaffc23 + 14fc6c2 commit 356eaef

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

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

2324
import org.springframework.lang.UsesJava8;
@@ -30,8 +31,14 @@
3031
* Codec present, {@link #encode}/{@link #decode} calls will throw an IllegalStateException.
3132
* However, as of Spring 4.2, {@link #encodeToString} and {@link #decodeFromString} will
3233
* nevertheless work since they can delegate to the JAXB DatatypeConverter as a fallback.
34+
* However, this does not apply when using the ...UrlSafe... methods for RFC 4648 "URL and
35+
* Filename Safe Alphabet"; a delegate is required.
36+
* <p>
37+
* <em>Note:</em> Apache Commons Codec does not add padding ({@code =}) when encoding with
38+
* the URL and Filename Safe Alphabet.
3339
*
3440
* @author Juergen Hoeller
41+
* @author Gary Russell
3542
* @since 4.1
3643
* @see java.util.Base64
3744
* @see org.apache.commons.codec.binary.Base64
@@ -92,6 +99,32 @@ public static byte[] decode(byte[] src) {
9299
return delegate.decode(src);
93100
}
94101

102+
/**
103+
* Base64-encode the given byte array using the RFC 4868
104+
* "URL and Filename Safe Alphabet".
105+
* @param src the original byte array (may be {@code null})
106+
* @return the encoded byte array (or {@code null} if the input was {@code null})
107+
* @throws IllegalStateException if Base64 encoding between byte arrays is not
108+
* supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime
109+
*/
110+
public static byte[] encodeUrlSafe(byte[] src) {
111+
assertDelegateAvailable();
112+
return delegate.encodeUrlSafe(src);
113+
}
114+
115+
/**
116+
* Base64-decode the given byte array using the RFC 4868
117+
* "URL and Filename Safe Alphabet".
118+
* @param src the encoded byte array (may be {@code null})
119+
* @return the original byte array (or {@code null} if the input was {@code null})
120+
* @throws IllegalStateException if Base64 encoding between byte arrays is not
121+
* supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime
122+
*/
123+
public static byte[] decodeUrlSafe(byte[] src) {
124+
assertDelegateAvailable();
125+
return delegate.decodeUrlSafe(src);
126+
}
127+
95128
/**
96129
* Base64-encode the given byte array to a String.
97130
* @param src the original byte array (may be {@code null})
@@ -139,45 +172,110 @@ public static byte[] decodeFromString(String src) {
139172
}
140173
}
141174

175+
/**
176+
* Base64-encode the given byte array to a String using the RFC 4868
177+
* "URL and Filename Safe Alphabet".
178+
* @param src the original byte array (may be {@code null})
179+
* @return the encoded byte array as a UTF-8 String
180+
* (or {@code null} if the input was {@code null})
181+
* @throws IllegalStateException if Base64 encoding between byte arrays is not
182+
* supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime
183+
*/
184+
public static String encodeToUrlSafeString(byte[] src) {
185+
assertDelegateAvailable();
186+
return new String(delegate.encodeUrlSafe(src), DEFAULT_CHARSET);
187+
}
188+
189+
/**
190+
* Base64-decode the given byte array from an UTF-8 String using the RFC 4868
191+
* "URL and Filename Safe Alphabet".
192+
* @param src the encoded UTF-8 String (may be {@code null})
193+
* @return the original byte array (or {@code null} if the input was {@code null})
194+
* @throws IllegalStateException if Base64 encoding between byte arrays is not
195+
* supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime
196+
*/
197+
public static byte[] decodeFromUrlSafeString(String src) {
198+
assertDelegateAvailable();
199+
return delegate.decodeUrlSafe(src.getBytes(DEFAULT_CHARSET));
200+
}
201+
142202

143203
interface Base64Delegate {
144204

145205
byte[] encode(byte[] src);
146206

147207
byte[] decode(byte[] src);
208+
209+
byte[] encodeUrlSafe(byte[] src);
210+
211+
byte[] decodeUrlSafe(byte[] src);
148212
}
149213

150214

151215
@UsesJava8
152216
static class JdkBase64Delegate implements Base64Delegate {
153217

218+
@Override
154219
public byte[] encode(byte[] src) {
155220
if (src == null || src.length == 0) {
156221
return src;
157222
}
158223
return Base64.getEncoder().encode(src);
159224
}
160225

226+
@Override
161227
public byte[] decode(byte[] src) {
162228
if (src == null || src.length == 0) {
163229
return src;
164230
}
165231
return Base64.getDecoder().decode(src);
166232
}
233+
234+
@Override
235+
public byte[] encodeUrlSafe(byte[] src) {
236+
if (src == null || src.length == 0) {
237+
return src;
238+
}
239+
return Base64.getUrlEncoder().encode(src);
240+
}
241+
242+
@Override
243+
public byte[] decodeUrlSafe(byte[] src) {
244+
if (src == null || src.length == 0) {
245+
return src;
246+
}
247+
return Base64.getUrlDecoder().decode(src);
248+
}
249+
167250
}
168251

169252

170253
static class CommonsCodecBase64Delegate implements Base64Delegate {
171254

172255
private final org.apache.commons.codec.binary.Base64 base64 = new org.apache.commons.codec.binary.Base64();
173256

257+
private final org.apache.commons.codec.binary.Base64 base64UrlSafe = new org.apache.commons.codec.binary.Base64(0, null, true);
258+
259+
@Override
174260
public byte[] encode(byte[] src) {
175261
return this.base64.encode(src);
176262
}
177263

264+
@Override
178265
public byte[] decode(byte[] src) {
179266
return this.base64.decode(src);
180267
}
268+
269+
@Override
270+
public byte[] encodeUrlSafe(byte[] src) {
271+
return this.base64UrlSafe.encode(src);
272+
}
273+
274+
@Override
275+
public byte[] decodeUrlSafe(byte[] src) {
276+
return this.base64UrlSafe.decode(src);
277+
}
278+
181279
}
182280

183281
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ public void encodeWithJdk8VsCommonsCodec() throws UnsupportedEncodingException {
5555
assertArrayEquals(jdkDelegate.encode(bytes), commonsDelegate.encode(bytes));
5656
assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes)));
5757
assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes)));
58+
59+
bytes = new byte[] { (byte) 0xfb, (byte) 0xf0 };
60+
assertArrayEquals("+/A=".getBytes(), jdkDelegate.encode(bytes));
61+
assertArrayEquals("+/A=".getBytes(), commonsDelegate.encode(bytes));
62+
assertArrayEquals(bytes, jdkDelegate.decode(jdkDelegate.encode(bytes)));
63+
assertArrayEquals(bytes, commonsDelegate.decode(commonsDelegate.encode(bytes)));
64+
65+
assertArrayEquals("-_A=".getBytes(), jdkDelegate.encodeUrlSafe(bytes));
66+
assertArrayEquals("-_A".getBytes(), commonsDelegate.encodeUrlSafe(bytes)); // no padding with commons and URL safe
67+
assertArrayEquals(bytes, jdkDelegate.decodeUrlSafe(jdkDelegate.encodeUrlSafe(bytes)));
68+
assertArrayEquals(bytes, commonsDelegate.decodeUrlSafe(commonsDelegate.encodeUrlSafe(bytes)));
5869
}
5970

6071
@Test
@@ -79,6 +90,17 @@ public void encodeToStringWithJdk8VsJaxb() throws UnsupportedEncodingException {
7990
assertEquals(Base64Utils.encodeToString(bytes), DatatypeConverter.printBase64Binary(bytes));
8091
assertArrayEquals(bytes, Base64Utils.decodeFromString(Base64Utils.encodeToString(bytes)));
8192
assertArrayEquals(bytes, DatatypeConverter.parseBase64Binary(DatatypeConverter.printBase64Binary(bytes)));
93+
94+
}
95+
96+
@Test
97+
public void encodeDecodeUrlSafe() {
98+
byte[] bytes = new byte[] { (byte) 0xfb, (byte) 0xf0 };
99+
assertArrayEquals("-_A=".getBytes(), Base64Utils.encodeUrlSafe(bytes));
100+
assertArrayEquals(bytes, Base64Utils.decodeUrlSafe(Base64Utils.encodeUrlSafe(bytes)));
101+
102+
assertEquals("-_A=", Base64Utils.encodeToUrlSafeString(bytes));
103+
assertArrayEquals(bytes, Base64Utils.decodeFromUrlSafeString(Base64Utils.encodeToUrlSafeString(bytes)));
82104
}
83105

84106
}

0 commit comments

Comments
 (0)