001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.util; 018 019import java.nio.charset.Charset; 020import java.util.Base64; 021import javax.xml.bind.DatatypeConverter; 022 023import org.springframework.lang.UsesJava8; 024 025/** 026 * A simple utility class for Base64 encoding and decoding. 027 * 028 * <p>Adapts to either Java 8's {@link java.util.Base64} class or Apache Commons Codec's 029 * {@link org.apache.commons.codec.binary.Base64} class. With neither Java 8 nor Commons 030 * Codec present, {@link #encode}/{@link #decode} calls will throw an IllegalStateException. 031 * However, as of Spring 4.2, {@link #encodeToString} and {@link #decodeFromString} will 032 * nevertheless work since they can delegate to the JAXB DatatypeConverter as a fallback. 033 * However, this does not apply when using the "UrlSafe" methods for RFC 4648 "URL and 034 * Filename Safe Alphabet"; a delegate is required. 035 * 036 * <p><em>Note:</em> Apache Commons Codec does not add padding ({@code =}) when encoding 037 * with the URL and Filename Safe Alphabet. 038 * 039 * @author Juergen Hoeller 040 * @author Gary Russell 041 * @since 4.1 042 * @see java.util.Base64 043 * @see org.apache.commons.codec.binary.Base64 044 * @see javax.xml.bind.DatatypeConverter#printBase64Binary 045 * @see javax.xml.bind.DatatypeConverter#parseBase64Binary 046 */ 047public abstract class Base64Utils { 048 049 private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); 050 051 052 private static final Base64Delegate delegate; 053 054 static { 055 Base64Delegate delegateToUse = null; 056 // JDK 8's java.util.Base64 class present? 057 if (ClassUtils.isPresent("java.util.Base64", Base64Utils.class.getClassLoader())) { 058 delegateToUse = new JdkBase64Delegate(); 059 } 060 // Apache Commons Codec present on the classpath? 061 else if (ClassUtils.isPresent("org.apache.commons.codec.binary.Base64", Base64Utils.class.getClassLoader())) { 062 delegateToUse = new CommonsCodecBase64Delegate(); 063 } 064 delegate = delegateToUse; 065 } 066 067 /** 068 * Assert that Byte64 encoding between byte arrays is actually supported. 069 * @throws IllegalStateException if neither Java 8 nor Apache Commons Codec is present 070 */ 071 private static void assertDelegateAvailable() { 072 Assert.state(delegate != null, 073 "Neither Java 8 nor Apache Commons Codec found - Base64 encoding between byte arrays not supported"); 074 } 075 076 077 /** 078 * Base64-encode the given byte array. 079 * @param src the original byte array (may be {@code null}) 080 * @return the encoded byte array (or {@code null} if the input was {@code null}) 081 * @throws IllegalStateException if Base64 encoding between byte arrays is not 082 * supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime 083 */ 084 public static byte[] encode(byte[] src) { 085 assertDelegateAvailable(); 086 return delegate.encode(src); 087 } 088 089 /** 090 * Base64-decode the given byte array. 091 * @param src the encoded byte array (may be {@code null}) 092 * @return the original byte array (or {@code null} if the input was {@code null}) 093 * @throws IllegalStateException if Base64 encoding between byte arrays is not 094 * supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime 095 */ 096 public static byte[] decode(byte[] src) { 097 assertDelegateAvailable(); 098 return delegate.decode(src); 099 } 100 101 /** 102 * Base64-encode the given byte array using the RFC 4648 103 * "URL and Filename Safe Alphabet". 104 * @param src the original byte array (may be {@code null}) 105 * @return the encoded byte array (or {@code null} if the input was {@code null}) 106 * @throws IllegalStateException if Base64 encoding between byte arrays is not 107 * supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime 108 * @since 4.2.4 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 4648 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 * @since 4.2.4 123 */ 124 public static byte[] decodeUrlSafe(byte[] src) { 125 assertDelegateAvailable(); 126 return delegate.decodeUrlSafe(src); 127 } 128 129 /** 130 * Base64-encode the given byte array to a String. 131 * @param src the original byte array (may be {@code null}) 132 * @return the encoded byte array as a UTF-8 String 133 * (or {@code null} if the input was {@code null}) 134 */ 135 public static String encodeToString(byte[] src) { 136 if (src == null) { 137 return null; 138 } 139 if (src.length == 0) { 140 return ""; 141 } 142 143 if (delegate != null) { 144 // Full encoder available 145 return new String(delegate.encode(src), DEFAULT_CHARSET); 146 } 147 else { 148 // JAXB fallback for String case 149 return DatatypeConverter.printBase64Binary(src); 150 } 151 } 152 153 /** 154 * Base64-decode the given byte array from an UTF-8 String. 155 * @param src the encoded UTF-8 String (may be {@code null}) 156 * @return the original byte array (or {@code null} if the input was {@code null}) 157 */ 158 public static byte[] decodeFromString(String src) { 159 if (src == null) { 160 return null; 161 } 162 if (src.isEmpty()) { 163 return new byte[0]; 164 } 165 166 if (delegate != null) { 167 // Full encoder available 168 return delegate.decode(src.getBytes(DEFAULT_CHARSET)); 169 } 170 else { 171 // JAXB fallback for String case 172 return DatatypeConverter.parseBase64Binary(src); 173 } 174 } 175 176 /** 177 * Base64-encode the given byte array to a String using the RFC 4648 178 * "URL and Filename Safe Alphabet". 179 * @param src the original byte array (may be {@code null}) 180 * @return the encoded byte array as a UTF-8 String 181 * (or {@code null} if the input was {@code null}) 182 * @throws IllegalStateException if Base64 encoding between byte arrays is not 183 * supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime 184 */ 185 public static String encodeToUrlSafeString(byte[] src) { 186 assertDelegateAvailable(); 187 return new String(delegate.encodeUrlSafe(src), DEFAULT_CHARSET); 188 } 189 190 /** 191 * Base64-decode the given byte array from an UTF-8 String using the RFC 4648 192 * "URL and Filename Safe Alphabet". 193 * @param src the encoded UTF-8 String (may be {@code null}) 194 * @return the original byte array (or {@code null} if the input was {@code null}) 195 * @throws IllegalStateException if Base64 encoding between byte arrays is not 196 * supported, i.e. neither Java 8 nor Apache Commons Codec is present at runtime 197 */ 198 public static byte[] decodeFromUrlSafeString(String src) { 199 assertDelegateAvailable(); 200 return delegate.decodeUrlSafe(src.getBytes(DEFAULT_CHARSET)); 201 } 202 203 204 interface Base64Delegate { 205 206 byte[] encode(byte[] src); 207 208 byte[] decode(byte[] src); 209 210 byte[] encodeUrlSafe(byte[] src); 211 212 byte[] decodeUrlSafe(byte[] src); 213 } 214 215 216 @UsesJava8 217 static class JdkBase64Delegate implements Base64Delegate { 218 219 @Override 220 public byte[] encode(byte[] src) { 221 if (src == null || src.length == 0) { 222 return src; 223 } 224 return Base64.getEncoder().encode(src); 225 } 226 227 @Override 228 public byte[] decode(byte[] src) { 229 if (src == null || src.length == 0) { 230 return src; 231 } 232 return Base64.getDecoder().decode(src); 233 } 234 235 @Override 236 public byte[] encodeUrlSafe(byte[] src) { 237 if (src == null || src.length == 0) { 238 return src; 239 } 240 return Base64.getUrlEncoder().encode(src); 241 } 242 243 @Override 244 public byte[] decodeUrlSafe(byte[] src) { 245 if (src == null || src.length == 0) { 246 return src; 247 } 248 return Base64.getUrlDecoder().decode(src); 249 } 250 251 } 252 253 254 static class CommonsCodecBase64Delegate implements Base64Delegate { 255 256 private final org.apache.commons.codec.binary.Base64 base64 = 257 new org.apache.commons.codec.binary.Base64(); 258 259 private final org.apache.commons.codec.binary.Base64 base64UrlSafe = 260 new org.apache.commons.codec.binary.Base64(0, null, true); 261 262 @Override 263 public byte[] encode(byte[] src) { 264 return this.base64.encode(src); 265 } 266 267 @Override 268 public byte[] decode(byte[] src) { 269 return this.base64.decode(src); 270 } 271 272 @Override 273 public byte[] encodeUrlSafe(byte[] src) { 274 return this.base64UrlSafe.encode(src); 275 } 276 277 @Override 278 public byte[] decodeUrlSafe(byte[] src) { 279 return this.base64UrlSafe.decode(src); 280 } 281 282 } 283 284}