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