001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 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 * http://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 * Contributors: 017 */ 018package org.nuxeo.ecm.automation.client.jaxrs.util; 019 020import java.io.UnsupportedEncodingException; 021import java.nio.charset.Charset; 022 023import org.apache.commons.io.IOUtils; 024 025/** 026 * Encodes and decodes to and from Base64 notation. 027 * <p> 028 * Change Log: 029 * </p> 030 * <ul> 031 * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added some convenience methods for reading 032 * and writing to and from files.</li> 033 * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems with other encodings (like 034 * EBCDIC).</li> 035 * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the encoded data was a single byte.</li> 036 * <li>v2.0 - I got rid of methods that used booleans to set options. Now everything is more consolidated and cleaner. 037 * The code now detects when data that's being decoded is gzip-compressed and will decompress it automatically. 038 * Generally things are cleaner. You'll probably have to change some method calls that you were making to support the 039 * new options format (<tt>int</tt>s that you "OR" together).</li> 040 * <li>v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using 041 * <tt>decode( String s, boolean gzipCompressed )</tt>. Added the ability to "suspend" encoding in the Output Stream so 042 * you can turn on and off the encoding if you need to embed base64 data in an otherwise "normal" stream (like an XML 043 * file).</li> 044 * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself. This helps when using GZIP streams. 045 * Added the ability to GZip-compress objects before encoding them.</li> 046 * <li>v1.4 - Added helper methods to read/write files.</li> 047 * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li> 048 * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream where last buffer being read, if 049 * not completely full, was not returned.</li> 050 * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li> 051 * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li> 052 * </ul> 053 * <p> 054 * I am placing this code in the Public Domain. Do with it as you will. This software comes with no guarantees or 055 * warranties but with plenty of well-wishing instead! Please visit <a 056 * href="http://iharder.net/base64">http://iharder.net/base64</a> periodically to check for updates or to contribute 057 * improvements. 058 * </p> 059 * 060 * @author Robert Harder 061 * @author rob@iharder.net 062 * @version 2.1 063 */ 064public class Base64 { 065 066 /* ******** P U B L I C F I E L D S ******** */ 067 068 /** No options specified. Value is zero. */ 069 public final static int NO_OPTIONS = 0; 070 071 /** Specify encoding. */ 072 public final static int ENCODE = 1; 073 074 /** Specify decoding. */ 075 public final static int DECODE = 0; 076 077 /** Specify that data should be gzip-compressed. */ 078 public final static int GZIP = 2; 079 080 /** Don't break lines when encoding (violates strict Base64 specification) */ 081 public final static int DONT_BREAK_LINES = 8; 082 083 /* ******** P R I V A T E F I E L D S ******** */ 084 085 /** Maximum line length (76) of Base64 output. */ 086 private final static int MAX_LINE_LENGTH = 76; 087 088 /** The equals sign (=) as a byte. */ 089 private final static byte EQUALS_SIGN = (byte) '='; 090 091 /** The new line character (\n) as a byte. */ 092 private final static byte NEW_LINE = (byte) '\n'; 093 094 /** Preferred encoding. */ 095 private final static String PREFERRED_ENCODING = "UTF-8"; 096 097 /** The 64 valid Base64 values. */ 098 private final static byte[] ALPHABET; 099 100 private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */ 101 { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', 102 (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', 103 (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', 104 (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', 105 (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', 106 (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', 107 (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', 108 (byte) '/' }; 109 110 /** Determine which ALPHABET to use. */ 111 static { 112 byte[] __bytes; 113 try { 114 __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(PREFERRED_ENCODING); 115 } // end try 116 catch (java.io.UnsupportedEncodingException use) { 117 __bytes = _NATIVE_ALPHABET; // Fall back to native encoding 118 } // end catch 119 ALPHABET = __bytes; 120 } // end static 121 122 /** 123 * Translates a Base64 value to either its 6-bit reconstruction value or a negative number indicating some other 124 * meaning. 125 **/ 126 private final static byte[] DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 127 -5, -5, // Whitespace: Tab and Linefeed 128 -9, -9, // Decimal 11 - 12 129 -5, // Whitespace: Carriage Return 130 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 131 -9, -9, -9, -9, -9, // Decimal 27 - 31 132 -5, // Whitespace: Space 133 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 134 62, // Plus sign at decimal 43 135 -9, -9, -9, // Decimal 44 - 46 136 63, // Slash at decimal 47 137 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine 138 -9, -9, -9, // Decimal 58 - 60 139 -1, // Equals sign at decimal 61 140 -9, -9, -9, // Decimal 62 - 64 141 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' 142 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' 143 -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 144 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' 145 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' 146 -9, -9, -9, -9 // Decimal 123 - 126 147 /* 148 * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 149 * 140 - 152 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // 150 * Decimal 166 - 178 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 151 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 152 * 205 - 217 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // 153 * Decimal 231 - 243 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 154 */ 155 }; 156 157 // I think I end up not using the BAD_ENCODING indicator. 158 // private final static byte BAD_ENCODING = -9; // Indicates error in encoding 159 private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding 160 161 private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding 162 163 /** Defeats instantiation. */ 164 private Base64() { 165 } 166 167 /* ******** E N C O D I N G M E T H O D S ******** */ 168 169 /** 170 * Encodes up to the first three bytes of array <var>threeBytes</var> and returns a four-byte array in Base64 171 * notation. The actual number of significant bytes in your array is given by <var>numSigBytes</var>. The array 172 * <var>threeBytes</var> needs only be as big as <var>numSigBytes</var>. Code can reuse a byte array by passing a 173 * four-byte array as <var>b4</var>. 174 * 175 * @param b4 A reusable byte array to reduce array instantiation 176 * @param threeBytes the array to convert 177 * @param numSigBytes the number of significant bytes in your array 178 * @return four byte array in Base64 notation. 179 * @since 1.5.1 180 */ 181 private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes) { 182 encode3to4(threeBytes, 0, numSigBytes, b4, 0); 183 return b4; 184 } // end encode3to4 185 186 /** 187 * Encodes up to three bytes of the array <var>source</var> and writes the resulting four Base64 bytes to 188 * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by 189 * specifying <var>srcOffset</var> and <var>destOffset</var>. This method does not check to make sure your arrays 190 * are large enough to accomodate <var>srcOffset</var> + 3 for the <var>source</var> array or <var>destOffset</var> 191 * + 4 for the <var>destination</var> array. The actual number of significant bytes in your array is given by 192 * <var>numSigBytes</var>. 193 * 194 * @param source the array to convert 195 * @param srcOffset the index where conversion begins 196 * @param numSigBytes the number of significant bytes in your array 197 * @param destination the array to hold the conversion 198 * @param destOffset the index where output will be put 199 * @return the <var>destination</var> array 200 * @since 1.3 201 */ 202 private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset) { 203 // 1 2 3 204 // 01234567890123456789012345678901 Bit position 205 // --------000000001111111122222222 Array position from threeBytes 206 // --------| || || || | Six bit groups to index ALPHABET 207 // >>18 >>12 >> 6 >> 0 Right shift necessary 208 // 0x3f 0x3f 0x3f Additional AND 209 210 // Create buffer with zero-padding if there are only one or two 211 // significant bytes passed in the array. 212 // We have to shift left 24 in order to flush out the 1's that appear 213 // when Java treats a value as negative that is cast from a byte to an int. 214 int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) 215 | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) 216 | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); 217 218 switch (numSigBytes) { 219 case 3: 220 destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 221 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 222 destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; 223 destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; 224 return destination; 225 226 case 2: 227 destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 228 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 229 destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; 230 destination[destOffset + 3] = EQUALS_SIGN; 231 return destination; 232 233 case 1: 234 destination[destOffset] = ALPHABET[(inBuff >>> 18)]; 235 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; 236 destination[destOffset + 2] = EQUALS_SIGN; 237 destination[destOffset + 3] = EQUALS_SIGN; 238 return destination; 239 240 default: 241 return destination; 242 } // end switch 243 } // end encode3to4 244 245 public static String encode(byte[] bytes) { 246 return encodeBytes(bytes); 247 } 248 249 public static String encode(String content) { 250 return encodeBytes(content.getBytes()); 251 } 252 253 public static String encode(String content, String charset) throws UnsupportedEncodingException { 254 return encodeBytes(content.getBytes(charset)); 255 } 256 257 public static String encode(String content, Charset charset) { 258 return encodeBytes(content.getBytes(charset)); 259 } 260 261 /** 262 * Serializes an object and returns the Base64-encoded version of that serialized object. If the object cannot be 263 * serialized or there is another error, the method will return <tt>null</tt>. The object is not GZip-compressed 264 * before being encoded. 265 * 266 * @param serializableObject The object to encode 267 * @return The Base64-encoded object 268 * @since 1.4 269 */ 270 public static String encodeObject(java.io.Serializable serializableObject) { 271 return encodeObject(serializableObject, NO_OPTIONS); 272 } // end encodeObject 273 274 /** 275 * Serializes an object and returns the Base64-encoded version of that serialized object. If the object cannot be 276 * serialized or there is another error, the method will return <tt>null</tt>. 277 * <p> 278 * Valid options: 279 * 280 * <pre> 281 * GZIP: gzip-compresses object before encoding it. 282 * DONT_BREAK_LINES: don't break lines at 76 characters 283 * <i>Note: Technically, this makes your encoding non-compliant.</i> 284 * </pre> 285 * <p> 286 * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or 287 * <p> 288 * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code> 289 * 290 * @param serializableObject The object to encode 291 * @param options Specified options 292 * @return The Base64-encoded object 293 * @see Base64#GZIP 294 * @see Base64#DONT_BREAK_LINES 295 * @since 2.0 296 */ 297 public static String encodeObject(java.io.Serializable serializableObject, int options) { 298 // Streams 299 java.io.ByteArrayOutputStream baos = null; 300 java.io.OutputStream b64os = null; 301 java.io.ObjectOutputStream oos = null; 302 java.util.zip.GZIPOutputStream gzos = null; 303 304 // Isolate options 305 int gzip = (options & GZIP); 306 int dontBreakLines = (options & DONT_BREAK_LINES); 307 308 try { 309 // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream 310 baos = new java.io.ByteArrayOutputStream(); 311 b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines); 312 313 // GZip? 314 if (gzip == GZIP) { 315 gzos = new java.util.zip.GZIPOutputStream(b64os); 316 oos = new java.io.ObjectOutputStream(gzos); 317 } // end if: gzip 318 else 319 oos = new java.io.ObjectOutputStream(b64os); 320 321 oos.writeObject(serializableObject); 322 } // end try 323 catch (java.io.IOException e) { 324 e.printStackTrace(); 325 return null; 326 } // end catch 327 finally { 328 IOUtils.closeQuietly(oos); 329 IOUtils.closeQuietly(gzos); 330 IOUtils.closeQuietly(b64os); 331 IOUtils.closeQuietly(baos); 332 } // end finally 333 334 // Return value according to relevant encoding. 335 try { 336 return new String(baos.toByteArray(), PREFERRED_ENCODING); 337 } // end try 338 catch (java.io.UnsupportedEncodingException uue) { 339 return new String(baos.toByteArray()); 340 } // end catch 341 342 } // end encode 343 344 /** 345 * Encodes a byte array into Base64 notation. Does not GZip-compress data. 346 * 347 * @param source The data to convert 348 * @since 1.4 349 */ 350 public static String encodeBytes(byte[] source) { 351 return encodeBytes(source, 0, source.length, NO_OPTIONS); 352 } // end encodeBytes 353 354 /** 355 * Encodes a byte array into Base64 notation. 356 * <p> 357 * Valid options: 358 * 359 * <pre> 360 * GZIP: gzip-compresses object before encoding it. 361 * DONT_BREAK_LINES: don't break lines at 76 characters 362 * <i>Note: Technically, this makes your encoding non-compliant.</i> 363 * </pre> 364 * <p> 365 * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or 366 * <p> 367 * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code> 368 * 369 * @param source The data to convert 370 * @param options Specified options 371 * @see Base64#GZIP 372 * @see Base64#DONT_BREAK_LINES 373 * @since 2.0 374 */ 375 public static String encodeBytes(byte[] source, int options) { 376 return encodeBytes(source, 0, source.length, options); 377 } // end encodeBytes 378 379 /** 380 * Encodes a byte array into Base64 notation. Does not GZip-compress data. 381 * 382 * @param source The data to convert 383 * @param off Offset in array where conversion should begin 384 * @param len Length of data to convert 385 * @since 1.4 386 */ 387 public static String encodeBytes(byte[] source, int off, int len) { 388 return encodeBytes(source, off, len, NO_OPTIONS); 389 } // end encodeBytes 390 391 /** 392 * Encodes a byte array into Base64 notation. 393 * <p> 394 * Valid options: 395 * 396 * <pre> 397 * GZIP: gzip-compresses object before encoding it. 398 * DONT_BREAK_LINES: don't break lines at 76 characters 399 * <i>Note: Technically, this makes your encoding non-compliant.</i> 400 * </pre> 401 * <p> 402 * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or 403 * <p> 404 * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code> 405 * 406 * @param source The data to convert 407 * @param off Offset in array where conversion should begin 408 * @param len Length of data to convert 409 * @param options Specified options 410 * @see Base64#GZIP 411 * @see Base64#DONT_BREAK_LINES 412 * @since 2.0 413 */ 414 public static String encodeBytes(byte[] source, int off, int len, int options) { 415 // Isolate options 416 int dontBreakLines = (options & DONT_BREAK_LINES); 417 int gzip = (options & GZIP); 418 419 // Compress? 420 if (gzip == GZIP) { 421 java.io.ByteArrayOutputStream baos = null; 422 java.util.zip.GZIPOutputStream gzos = null; 423 Base64.OutputStream b64os = null; 424 425 try { 426 // GZip -> Base64 -> ByteArray 427 baos = new java.io.ByteArrayOutputStream(); 428 b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines); 429 gzos = new java.util.zip.GZIPOutputStream(b64os); 430 431 gzos.write(source, off, len); 432 gzos.close(); 433 } // end try 434 catch (java.io.IOException e) { 435 e.printStackTrace(); 436 return null; 437 } // end catch 438 finally { 439 IOUtils.closeQuietly(gzos); 440 IOUtils.closeQuietly(b64os); 441 IOUtils.closeQuietly(baos); 442 } // end finally 443 444 // Return value according to relevant encoding. 445 try { 446 return new String(baos.toByteArray(), PREFERRED_ENCODING); 447 } // end try 448 catch (java.io.UnsupportedEncodingException uue) { 449 return new String(baos.toByteArray()); 450 } // end catch 451 } // end if: compress 452 453 // Else, don't compress. Better not to use streams at all then. 454 else { 455 // Convert option to boolean in way that code likes it. 456 boolean breakLines = dontBreakLines == 0; 457 458 int len43 = len * 4 / 3; 459 byte[] outBuff = new byte[(len43) // Main 4:3 460 + ((len % 3) > 0 ? 4 : 0) // Account for padding 461 + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines 462 int d = 0; 463 int e = 0; 464 int len2 = len - 2; 465 int lineLength = 0; 466 for (; d < len2; d += 3, e += 4) { 467 encode3to4(source, d + off, 3, outBuff, e); 468 469 lineLength += 4; 470 if (breakLines && lineLength == MAX_LINE_LENGTH) { 471 outBuff[e + 4] = NEW_LINE; 472 e++; 473 lineLength = 0; 474 } // end if: end of line 475 } // en dfor: each piece of array 476 477 if (d < len) { 478 encode3to4(source, d + off, len - d, outBuff, e); 479 e += 4; 480 } // end if: some padding needed 481 482 // Return value according to relevant encoding. 483 try { 484 return new String(outBuff, 0, e, PREFERRED_ENCODING); 485 } // end try 486 catch (java.io.UnsupportedEncodingException uue) { 487 return new String(outBuff, 0, e); 488 } // end catch 489 490 } // end else: don't compress 491 492 } // end encodeBytes 493 494 /* ******** D E C O D I N G M E T H O D S ******** */ 495 496 /** 497 * Decodes four bytes from array <var>source</var> and writes the resulting bytes (up to three of them) to 498 * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by 499 * specifying <var>srcOffset</var> and <var>destOffset</var>. This method does not check to make sure your arrays 500 * are large enough to accomodate <var>srcOffset</var> + 4 for the <var>source</var> array or <var>destOffset</var> 501 * + 3 for the <var>destination</var> array. This method returns the actual number of bytes that were converted from 502 * the Base64 encoding. 503 * 504 * @param source the array to convert 505 * @param srcOffset the index where conversion begins 506 * @param destination the array to hold the conversion 507 * @param destOffset the index where output will be put 508 * @return the number of decoded bytes converted 509 * @since 1.3 510 */ 511 private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) { 512 // Example: Dk== 513 if (source[srcOffset + 2] == EQUALS_SIGN) { 514 // Two ways to do the same thing. Don't know which way I like best. 515 // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 516 // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); 517 int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) 518 | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); 519 520 destination[destOffset] = (byte) (outBuff >>> 16); 521 return 1; 522 } 523 524 // Example: DkL= 525 else if (source[srcOffset + 3] == EQUALS_SIGN) { 526 // Two ways to do the same thing. Don't know which way I like best. 527 // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 528 // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 529 // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); 530 int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) 531 | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) 532 | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); 533 534 destination[destOffset] = (byte) (outBuff >>> 16); 535 destination[destOffset + 1] = (byte) (outBuff >>> 8); 536 return 2; 537 } 538 539 // Example: DkLE 540 else { 541 try { 542 // Two ways to do the same thing. Don't know which way I like best. 543 // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 544 // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 545 // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) 546 // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); 547 int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) 548 | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) 549 | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) 550 | ((DECODABET[source[srcOffset + 3]] & 0xFF)); 551 552 destination[destOffset] = (byte) (outBuff >> 16); 553 destination[destOffset + 1] = (byte) (outBuff >> 8); 554 destination[destOffset + 2] = (byte) (outBuff); 555 556 return 3; 557 } catch (ArrayIndexOutOfBoundsException e) { 558 System.out.println("" + source[srcOffset] + ": " + (DECODABET[source[srcOffset]])); 559 System.out.println("" + source[srcOffset + 1] + ": " + (DECODABET[source[srcOffset + 1]])); 560 System.out.println("" + source[srcOffset + 2] + ": " + (DECODABET[source[srcOffset + 2]])); 561 System.out.println("" + source[srcOffset + 3] + ": " + (DECODABET[source[srcOffset + 3]])); 562 return -1; 563 } // e nd catch 564 } 565 } // end decodeToBytes 566 567 /** 568 * Very low-level access to decoding ASCII characters in the form of a byte array. Does not support automatically 569 * gunzipping or any other "fancy" features. 570 * 571 * @param source The Base64 encoded data 572 * @param off The offset of where to begin decoding 573 * @param len The length of characters to decode 574 * @return decoded data 575 * @since 1.3 576 */ 577 public static byte[] decode(byte[] source, int off, int len) { 578 int len34 = len * 3 / 4; 579 byte[] outBuff = new byte[len34]; // Upper limit on size of output 580 int outBuffPosn = 0; 581 582 byte[] b4 = new byte[4]; 583 int b4Posn = 0; 584 int i = 0; 585 byte sbiCrop = 0; 586 byte sbiDecode = 0; 587 for (i = off; i < off + len; i++) { 588 sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits 589 sbiDecode = DECODABET[sbiCrop]; 590 591 if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better 592 { 593 if (sbiDecode >= EQUALS_SIGN_ENC) { 594 b4[b4Posn++] = sbiCrop; 595 if (b4Posn > 3) { 596 outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn); 597 b4Posn = 0; 598 599 // If that was the equals sign, break out of 'for' loop 600 if (sbiCrop == EQUALS_SIGN) 601 break; 602 } // end if: quartet built 603 604 } // end if: equals sign or better 605 606 } // end if: white space, equals sign or better 607 else { 608 System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)"); 609 return null; 610 } // end else: 611 } // each input character 612 613 byte[] out = new byte[outBuffPosn]; 614 System.arraycopy(outBuff, 0, out, 0, outBuffPosn); 615 return out; 616 } // end decode 617 618 /** 619 * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it. 620 * 621 * @param s the string to decode 622 * @return the decoded data 623 * @since 1.4 624 */ 625 public static byte[] decode(String s) { 626 byte[] bytes; 627 try { 628 bytes = s.getBytes(PREFERRED_ENCODING); 629 } // end try 630 catch (java.io.UnsupportedEncodingException uee) { 631 bytes = s.getBytes(); 632 } // end catch 633 // </change> 634 635 // Decode 636 bytes = decode(bytes, 0, bytes.length); 637 638 // Check to see if it's gzip-compressed 639 // GZIP Magic Two-Byte Number: 0x8b1f (35615) 640 if (bytes != null && bytes.length >= 4) { 641 642 int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); 643 if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { 644 java.io.ByteArrayInputStream bais = null; 645 java.util.zip.GZIPInputStream gzis = null; 646 java.io.ByteArrayOutputStream baos = null; 647 byte[] buffer = new byte[2048]; 648 int length = 0; 649 650 try { 651 baos = new java.io.ByteArrayOutputStream(); 652 bais = new java.io.ByteArrayInputStream(bytes); 653 gzis = new java.util.zip.GZIPInputStream(bais); 654 655 while ((length = gzis.read(buffer)) >= 0) { 656 baos.write(buffer, 0, length); 657 } // end while: reading input 658 659 // No error? Get new bytes. 660 bytes = baos.toByteArray(); 661 662 } // end try 663 catch (java.io.IOException e) { 664 // Just return originally-decoded bytes 665 } // end catch 666 finally { 667 IOUtils.closeQuietly(baos); 668 IOUtils.closeQuietly(gzis); 669 IOUtils.closeQuietly(bais); 670 } // end finally 671 672 } // end if: gzipped 673 } // end if: bytes.length >= 2 674 675 return bytes; 676 } // end decode 677 678 /** 679 * Attempts to decode Base64 data and deserialize a Java Object within. Returns <tt>null</tt> if there was an error. 680 * 681 * @param encodedObject The Base64 data to decode 682 * @return The decoded and deserialized object 683 * @since 1.5 684 */ 685 public static Object decodeToObject(String encodedObject) { 686 // Decode and gunzip if necessary 687 byte[] objBytes = decode(encodedObject); 688 689 java.io.ByteArrayInputStream bais = null; 690 java.io.ObjectInputStream ois = null; 691 Object obj = null; 692 693 try { 694 bais = new java.io.ByteArrayInputStream(objBytes); 695 ois = new java.io.ObjectInputStream(bais); 696 697 obj = ois.readObject(); 698 } // end try 699 catch (java.io.IOException e) { 700 e.printStackTrace(); 701 obj = null; 702 } // end catch 703 catch (java.lang.ClassNotFoundException e) { 704 e.printStackTrace(); 705 obj = null; 706 } // end catch 707 finally { 708 IOUtils.closeQuietly(bais); 709 IOUtils.closeQuietly(ois); 710 } // end finally 711 712 return obj; 713 } // end decodeObject 714 715 /** 716 * Convenience method for encoding data to a file. 717 * 718 * @param dataToEncode byte array of data to encode in base64 form 719 * @param filename Filename for saving encoded data 720 * @return <tt>true</tt> if successful, <tt>false</tt> otherwise 721 * @since 2.1 722 */ 723 public static boolean encodeToFile(byte[] dataToEncode, String filename) { 724 boolean success = false; 725 Base64.OutputStream bos = null; 726 try { 727 bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE); 728 bos.write(dataToEncode); 729 success = true; 730 } // end try 731 catch (java.io.IOException e) { 732 733 success = false; 734 } // end catch: IOException 735 finally { 736 IOUtils.closeQuietly(bos); 737 } // end finally 738 739 return success; 740 } // end encodeToFile 741 742 /** 743 * Convenience method for decoding data to a file. 744 * 745 * @param dataToDecode Base64-encoded data as a string 746 * @param filename Filename for saving decoded data 747 * @return <tt>true</tt> if successful, <tt>false</tt> otherwise 748 * @since 2.1 749 */ 750 public static boolean decodeToFile(String dataToDecode, String filename) { 751 boolean success = false; 752 Base64.OutputStream bos = null; 753 try { 754 bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE); 755 bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); 756 success = true; 757 } // end try 758 catch (java.io.IOException e) { 759 success = false; 760 } // end catch: IOException 761 finally { 762 IOUtils.closeQuietly(bos); 763 } // end finally 764 765 return success; 766 } // end decodeToFile 767 768 /** 769 * Convenience method for reading a base64-encoded file and decoding it. 770 * 771 * @param filename Filename for reading encoded data 772 * @return decoded byte array or null if unsuccessful 773 * @since 2.1 774 */ 775 public static byte[] decodeFromFile(String filename) { 776 byte[] decodedData = null; 777 Base64.InputStream bis = null; 778 try { 779 // Set up some useful variables 780 java.io.File file = new java.io.File(filename); 781 byte[] buffer = null; 782 int length = 0; 783 int numBytes = 0; 784 785 // Check for size of file 786 if (file.length() > Integer.MAX_VALUE) { 787 System.err.println("File is too big for this convenience method (" + file.length() + " bytes)."); 788 return null; 789 } // end if: file too big for int index 790 buffer = new byte[(int) file.length()]; 791 792 // Open a stream 793 bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), 794 Base64.DECODE); 795 796 // Read until done 797 while ((numBytes = bis.read(buffer, length, 4096)) >= 0) 798 length += numBytes; 799 800 // Save in a variable to return 801 decodedData = new byte[length]; 802 System.arraycopy(buffer, 0, decodedData, 0, length); 803 804 } // end try 805 catch (java.io.IOException e) { 806 System.err.println("Error decoding from file " + filename); 807 } // end catch: IOException 808 finally { 809 IOUtils.closeQuietly(bis); 810 } // end finally 811 812 return decodedData; 813 } // end decodeFromFile 814 815 /** 816 * Convenience method for reading a binary file and base64-encoding it. 817 * 818 * @param filename Filename for reading binary data 819 * @return base64-encoded string or null if unsuccessful 820 * @since 2.1 821 */ 822 public static String encodeFromFile(String filename) { 823 String encodedData = null; 824 Base64.InputStream bis = null; 825 try { 826 // Set up some useful variables 827 java.io.File file = new java.io.File(filename); 828 byte[] buffer = new byte[(int) (file.length() * 1.4)]; 829 int length = 0; 830 int numBytes = 0; 831 832 // Open a stream 833 bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), 834 Base64.ENCODE); 835 836 // Read until done 837 while ((numBytes = bis.read(buffer, length, 4096)) >= 0) 838 length += numBytes; 839 840 // Save in a variable to return 841 encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING); 842 843 } // end try 844 catch (java.io.IOException e) { 845 System.err.println("Error encoding from file " + filename); 846 } // end catch: IOException 847 finally { 848 IOUtils.closeQuietly(bis); 849 } // end finally 850 851 return encodedData; 852 } // end encodeFromFile 853 854 /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ 855 856 /** 857 * A {@link Base64.InputStream} will read data from another <tt>java.io.InputStream</tt>, given in the constructor, 858 * and encode/decode to/from Base64 notation on the fly. 859 * 860 * @see Base64 861 * @since 1.3 862 */ 863 public static class InputStream extends java.io.FilterInputStream { 864 private boolean encode; // Encoding or decoding 865 866 private int position; // Current position in the buffer 867 868 private byte[] buffer; // Small buffer holding converted data 869 870 private int bufferLength; // Length of buffer (3 or 4) 871 872 private int numSigBytes; // Number of meaningful bytes in the buffer 873 874 private int lineLength; 875 876 private boolean breakLines; // Break lines at less than 80 characters 877 878 /** 879 * Constructs a {@link Base64.InputStream} in DECODE mode. 880 * 881 * @param in the <tt>java.io.InputStream</tt> from which to read data. 882 * @since 1.3 883 */ 884 public InputStream(java.io.InputStream in) { 885 this(in, DECODE); 886 } // end constructor 887 888 /** 889 * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE mode. 890 * <p> 891 * Valid options: 892 * 893 * <pre> 894 * ENCODE or DECODE: Encode or Decode as data is read. 895 * DONT_BREAK_LINES: don't break lines at 76 characters 896 * (only meaningful when encoding) 897 * <i>Note: Technically, this makes your encoding non-compliant.</i> 898 * </pre> 899 * <p> 900 * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code> 901 * 902 * @param in the <tt>java.io.InputStream</tt> from which to read data. 903 * @param options Specified options 904 * @see Base64#ENCODE 905 * @see Base64#DECODE 906 * @see Base64#DONT_BREAK_LINES 907 * @since 2.0 908 */ 909 public InputStream(java.io.InputStream in, int options) { 910 super(in); 911 this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; 912 this.encode = (options & ENCODE) == ENCODE; 913 this.bufferLength = encode ? 4 : 3; 914 this.buffer = new byte[bufferLength]; 915 this.position = -1; 916 this.lineLength = 0; 917 } // end constructor 918 919 /** 920 * Reads enough of the input stream to convert to/from Base64 and returns the next byte. 921 * 922 * @return next byte 923 * @since 1.3 924 */ 925 public int read() throws java.io.IOException { 926 // Do we need to get data? 927 if (position < 0) { 928 if (encode) { 929 byte[] b3 = new byte[3]; 930 int numBinaryBytes = 0; 931 for (int i = 0; i < 3; i++) { 932 try { 933 int b = in.read(); 934 935 // If end of stream, b is -1. 936 if (b >= 0) { 937 b3[i] = (byte) b; 938 numBinaryBytes++; 939 } // end if: not end of stream 940 941 } // end try: read 942 catch (java.io.IOException e) { 943 // Only a problem if we got no data at all. 944 if (i == 0) 945 throw e; 946 947 } // end catch 948 } // end for: each needed input byte 949 950 if (numBinaryBytes > 0) { 951 encode3to4(b3, 0, numBinaryBytes, buffer, 0); 952 position = 0; 953 numSigBytes = 4; 954 } // end if: got data 955 else { 956 return -1; 957 } // end else 958 } // end if: encoding 959 960 // Else decoding 961 else { 962 byte[] b4 = new byte[4]; 963 int i = 0; 964 for (i = 0; i < 4; i++) { 965 // Read four "meaningful" bytes: 966 int b = 0; 967 do { 968 b = in.read(); 969 } while (b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC); 970 971 if (b < 0) 972 break; // Reads a -1 if end of stream 973 974 b4[i] = (byte) b; 975 } // end for: each needed input byte 976 977 if (i == 4) { 978 numSigBytes = decode4to3(b4, 0, buffer, 0); 979 position = 0; 980 } // end if: got four characters 981 else if (i == 0) { 982 return -1; 983 } // end else if: also padded correctly 984 else { 985 // Must have broken out from above. 986 throw new java.io.IOException("Improperly padded Base64 input."); 987 } // end 988 989 } // end else: decode 990 } // end else: get data 991 992 // Got data? 993 if (position >= 0) { 994 // End of relevant data? 995 if (/* !encode && */position >= numSigBytes) 996 return -1; 997 998 if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { 999 lineLength = 0; 1000 return '\n'; 1001 } // end if 1002 else { 1003 lineLength++; // This isn't important when decoding 1004 // but throwing an extra "if" seems 1005 // just as wasteful. 1006 1007 int b = buffer[position++]; 1008 1009 if (position >= bufferLength) 1010 position = -1; 1011 1012 return b & 0xFF; // This is how you "cast" a byte that's 1013 // intended to be unsigned. 1014 } // end else 1015 } // end if: position >= 0 1016 1017 // Else error 1018 else { 1019 // When JDK1.4 is more accepted, use an assertion here. 1020 throw new java.io.IOException("Error in Base64 code reading stream."); 1021 } // end else 1022 } // end read 1023 1024 /** 1025 * Calls {@link #read()} repeatedly until the end of stream is reached or <var>len</var> bytes are read. Returns 1026 * number of bytes read into array or -1 if end of stream is encountered. 1027 * 1028 * @param dest array to hold values 1029 * @param off offset for array 1030 * @param len max number of bytes to read into array 1031 * @return bytes read into array or -1 if end of stream is encountered. 1032 * @since 1.3 1033 */ 1034 public int read(byte[] dest, int off, int len) throws java.io.IOException { 1035 int i; 1036 int b; 1037 for (i = 0; i < len; i++) { 1038 b = read(); 1039 1040 // if( b < 0 && i == 0 ) 1041 // return -1; 1042 1043 if (b >= 0) 1044 dest[off + i] = (byte) b; 1045 else if (i == 0) 1046 return -1; 1047 else 1048 break; // Out of 'for' loop 1049 } // end for: each byte read 1050 return i; 1051 } // end read 1052 1053 } // end inner class InputStream 1054 1055 /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ 1056 1057 /** 1058 * A {@link Base64.OutputStream} will write data to another <tt>java.io.OutputStream</tt>, given in the constructor, 1059 * and encode/decode to/from Base64 notation on the fly. 1060 * 1061 * @see Base64 1062 * @since 1.3 1063 */ 1064 public static class OutputStream extends java.io.FilterOutputStream { 1065 private boolean encode; 1066 1067 private int position; 1068 1069 private byte[] buffer; 1070 1071 private int bufferLength; 1072 1073 private int lineLength; 1074 1075 private boolean breakLines; 1076 1077 private byte[] b4; // Scratch used in a few places 1078 1079 private boolean suspendEncoding; 1080 1081 /** 1082 * Constructs a {@link Base64.OutputStream} in ENCODE mode. 1083 * 1084 * @param out the <tt>java.io.OutputStream</tt> to which data will be written. 1085 * @since 1.3 1086 */ 1087 public OutputStream(java.io.OutputStream out) { 1088 this(out, ENCODE); 1089 } // end constructor 1090 1091 /** 1092 * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode. 1093 * <p> 1094 * Valid options: 1095 * 1096 * <pre> 1097 * ENCODE or DECODE: Encode or Decode as data is read. 1098 * DONT_BREAK_LINES: don't break lines at 76 characters 1099 * (only meaningful when encoding) 1100 * <i>Note: Technically, this makes your encoding non-compliant.</i> 1101 * </pre> 1102 * <p> 1103 * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code> 1104 * 1105 * @param out the <tt>java.io.OutputStream</tt> to which data will be written. 1106 * @param options Specified options. 1107 * @see Base64#ENCODE 1108 * @see Base64#DECODE 1109 * @see Base64#DONT_BREAK_LINES 1110 * @since 1.3 1111 */ 1112 public OutputStream(java.io.OutputStream out, int options) { 1113 super(out); 1114 this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; 1115 this.encode = (options & ENCODE) == ENCODE; 1116 this.bufferLength = encode ? 3 : 4; 1117 this.buffer = new byte[bufferLength]; 1118 this.position = 0; 1119 this.lineLength = 0; 1120 this.suspendEncoding = false; 1121 this.b4 = new byte[4]; 1122 } // end constructor 1123 1124 /** 1125 * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are 1126 * buffered three at a time before the output stream actually gets a write() call. When decoding, bytes are 1127 * buffered four at a time. 1128 * 1129 * @param theByte the byte to write 1130 * @since 1.3 1131 */ 1132 public void write(int theByte) throws java.io.IOException { 1133 // Encoding suspended? 1134 if (suspendEncoding) { 1135 super.out.write(theByte); 1136 return; 1137 } // end if: supsended 1138 1139 // Encode? 1140 if (encode) { 1141 buffer[position++] = (byte) theByte; 1142 if (position >= bufferLength) // Enough to encode. 1143 { 1144 out.write(encode3to4(b4, buffer, bufferLength)); 1145 1146 lineLength += 4; 1147 if (breakLines && lineLength >= MAX_LINE_LENGTH) { 1148 out.write(NEW_LINE); 1149 lineLength = 0; 1150 } // end if: end of line 1151 1152 position = 0; 1153 } // end if: enough to output 1154 } // end if: encoding 1155 1156 // Else, Decoding 1157 else { 1158 // Meaningful Base64 character? 1159 if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) { 1160 buffer[position++] = (byte) theByte; 1161 if (position >= bufferLength) // Enough to output. 1162 { 1163 int len = Base64.decode4to3(buffer, 0, b4, 0); 1164 out.write(b4, 0, len); 1165 // out.write( Base64.decode4to3( buffer ) ); 1166 position = 0; 1167 } // end if: enough to output 1168 } // end if: meaningful base64 character 1169 else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) { 1170 throw new java.io.IOException("Invalid character in Base64 data."); 1171 } // end else: not white space either 1172 } // end else: decoding 1173 } // end write 1174 1175 /** 1176 * Calls {@link #write(int)} repeatedly until <var>len</var> bytes are written. 1177 * 1178 * @param theBytes array from which to read bytes 1179 * @param off offset for array 1180 * @param len max number of bytes to read into array 1181 * @since 1.3 1182 */ 1183 public void write(byte[] theBytes, int off, int len) throws java.io.IOException { 1184 // Encoding suspended? 1185 if (suspendEncoding) { 1186 super.out.write(theBytes, off, len); 1187 return; 1188 } // end if: supsended 1189 1190 for (int i = 0; i < len; i++) { 1191 write(theBytes[off + i]); 1192 } // end for: each byte written 1193 1194 } // end write 1195 1196 /** 1197 * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream. 1198 */ 1199 public void flushBase64() throws java.io.IOException { 1200 if (position > 0) { 1201 if (encode) { 1202 out.write(encode3to4(b4, buffer, position)); 1203 position = 0; 1204 } // end if: encoding 1205 else { 1206 throw new java.io.IOException("Base64 input not properly padded."); 1207 } // end else: decoding 1208 } // end if: buffer partially full 1209 1210 } // end flush 1211 1212 /** 1213 * Flushes and closes (I think, in the superclass) the stream. 1214 * 1215 * @since 1.3 1216 */ 1217 public void close() throws java.io.IOException { 1218 // 1. Ensure that pending characters are written 1219 flushBase64(); 1220 1221 // 2. Actually close the stream 1222 // Base class both flushes and closes. 1223 super.close(); 1224 1225 buffer = null; 1226 out = null; 1227 } // end close 1228 1229 /** 1230 * Suspends encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a 1231 * stream. 1232 * 1233 * @since 1.5.1 1234 */ 1235 public void suspendEncoding() throws java.io.IOException { 1236 flushBase64(); 1237 this.suspendEncoding = true; 1238 } // end suspendEncoding 1239 1240 /** 1241 * Resumes encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a 1242 * stream. 1243 * 1244 * @since 1.5.1 1245 */ 1246 public void resumeEncoding() { 1247 this.suspendEncoding = false; 1248 } // end resumeEncoding 1249 1250 } // end inner class OutputStream 1251 1252} // end class Base64