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 obj = null; 673 } // end catch 674 catch (java.lang.ClassNotFoundException e) { 675 e.printStackTrace(); 676 obj = null; 677 } // end catch 678 finally { 679 IOUtils.closeQuietly(bais); 680 IOUtils.closeQuietly(ois); 681 } // end finally 682 683 return obj; 684 } // end decodeObject 685 686 /** 687 * Convenience method for encoding data to a file. 688 * 689 * @param dataToEncode byte array of data to encode in base64 form 690 * @param filename Filename for saving encoded data 691 * @return <tt>true</tt> if successful, <tt>false</tt> otherwise 692 * @since 2.1 693 */ 694 public static boolean encodeToFile(byte[] dataToEncode, String filename) { 695 boolean success = false; 696 Base64.OutputStream bos = null; 697 try { 698 bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE); 699 bos.write(dataToEncode); 700 success = true; 701 } // end try 702 catch (java.io.IOException e) { 703 704 success = false; 705 } // end catch: IOException 706 finally { 707 IOUtils.closeQuietly(bos); 708 } // end finally 709 710 return success; 711 } // end encodeToFile 712 713 /** 714 * Convenience method for decoding data to a file. 715 * 716 * @param dataToDecode Base64-encoded data as a string 717 * @param filename Filename for saving decoded data 718 * @return <tt>true</tt> if successful, <tt>false</tt> otherwise 719 * @since 2.1 720 */ 721 public static boolean decodeToFile(String dataToDecode, String filename) { 722 boolean success = false; 723 Base64.OutputStream bos = null; 724 try { 725 bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE); 726 bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); 727 success = true; 728 } // end try 729 catch (java.io.IOException e) { 730 success = false; 731 } // end catch: IOException 732 finally { 733 IOUtils.closeQuietly(bos); 734 } // end finally 735 736 return success; 737 } // end decodeToFile 738 739 /** 740 * Convenience method for reading a base64-encoded file and decoding it. 741 * 742 * @param filename Filename for reading encoded data 743 * @return decoded byte array or null if unsuccessful 744 * @since 2.1 745 */ 746 public static byte[] decodeFromFile(String filename) { 747 byte[] decodedData = null; 748 Base64.InputStream bis = null; 749 try { 750 // Set up some useful variables 751 java.io.File file = new java.io.File(filename); 752 byte[] buffer = null; 753 int length = 0; 754 int numBytes = 0; 755 756 // Check for size of file 757 if (file.length() > Integer.MAX_VALUE) { 758 // System.err.println("File is too big for this convenience method (" + file.length() + " bytes)."); 759 return null; 760 } // end if: file too big for int index 761 buffer = new byte[(int) file.length()]; 762 763 // Open a stream 764 bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), 765 Base64.DECODE); 766 767 // Read until done 768 while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { 769 length += numBytes; 770 } 771 772 // Save in a variable to return 773 decodedData = new byte[length]; 774 System.arraycopy(buffer, 0, decodedData, 0, length); 775 776 } // end try 777 catch (java.io.IOException e) { 778 // System.err.println("Error decoding from file " + filename); 779 } // end catch: IOException 780 finally { 781 IOUtils.closeQuietly(bis); 782 } // end finally 783 784 return decodedData; 785 } // end decodeFromFile 786 787 /** 788 * Convenience method for reading a binary file and base64-encoding it. 789 * 790 * @param filename Filename for reading binary data 791 * @return base64-encoded string or null if unsuccessful 792 * @since 2.1 793 */ 794 public static String encodeFromFile(String filename) { 795 String encodedData = null; 796 Base64.InputStream bis = null; 797 try { 798 // Set up some useful variables 799 java.io.File file = new java.io.File(filename); 800 byte[] buffer = new byte[(int) (file.length() * 1.4)]; 801 int length = 0; 802 int numBytes = 0; 803 804 // Open a stream 805 bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), 806 Base64.ENCODE); 807 808 // Read until done 809 while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { 810 length += numBytes; 811 } 812 813 // Save in a variable to return 814 encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING); 815 816 } // end try 817 catch (java.io.IOException e) { 818 // System.err.println("Error encoding from file " + filename); 819 } // end catch: IOException 820 finally { 821 IOUtils.closeQuietly(bis); 822 } // end finally 823 824 return encodedData; 825 } // end encodeFromFile 826 827 /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ 828 829 /** 830 * A {@link Base64.InputStream} will read data from another <tt>java.io.InputStream</tt>, given in the constructor, 831 * and encode/decode to/from Base64 notation on the fly. 832 * 833 * @see Base64 834 * @since 1.3 835 */ 836 public static class InputStream extends java.io.FilterInputStream { 837 private boolean encode; // Encoding or decoding 838 839 private int position; // Current position in the buffer 840 841 private byte[] buffer; // Small buffer holding converted data 842 843 private int bufferLength; // Length of buffer (3 or 4) 844 845 private int numSigBytes; // Number of meaningful bytes in the buffer 846 847 private int lineLength; 848 849 private boolean breakLines; // Break lines at less than 80 characters 850 851 /** 852 * Constructs a {@link Base64.InputStream} in DECODE mode. 853 * 854 * @param in the <tt>java.io.InputStream</tt> from which to read data. 855 * @since 1.3 856 */ 857 public InputStream(java.io.InputStream in) { 858 this(in, DECODE); 859 } // end constructor 860 861 /** 862 * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE mode. 863 * <p> 864 * Valid options: 865 * 866 * <pre> 867 * ENCODE or DECODE: Encode or Decode as data is read. 868 * DONT_BREAK_LINES: don't break lines at 76 characters 869 * (only meaningful when encoding) 870 * <i>Note: Technically, this makes your encoding non-compliant.</i> 871 * </pre> 872 * <p> 873 * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code> 874 * 875 * @param in the <tt>java.io.InputStream</tt> from which to read data. 876 * @param options Specified options 877 * @see Base64#ENCODE 878 * @see Base64#DECODE 879 * @see Base64#DONT_BREAK_LINES 880 * @since 2.0 881 */ 882 public InputStream(java.io.InputStream in, int options) { 883 super(in); 884 breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; 885 encode = (options & ENCODE) == ENCODE; 886 bufferLength = encode ? 4 : 3; 887 buffer = new byte[bufferLength]; 888 position = -1; 889 lineLength = 0; 890 } // end constructor 891 892 /** 893 * Reads enough of the input stream to convert to/from Base64 and returns the next byte. 894 * 895 * @return next byte 896 * @since 1.3 897 */ 898 @Override 899 public int read() throws java.io.IOException { 900 // Do we need to get data? 901 if (position < 0) { 902 if (encode) { 903 byte[] b3 = new byte[3]; 904 int numBinaryBytes = 0; 905 for (int i = 0; i < 3; i++) { 906 try { 907 int b = in.read(); 908 909 // If end of stream, b is -1. 910 if (b >= 0) { 911 b3[i] = (byte) b; 912 numBinaryBytes++; 913 } // end if: not end of stream 914 915 } // end try: read 916 catch (java.io.IOException e) { 917 // Only a problem if we got no data at all. 918 if (i == 0) { 919 throw e; 920 } 921 922 } // end catch 923 } // end for: each needed input byte 924 925 if (numBinaryBytes > 0) { 926 encode3to4(b3, 0, numBinaryBytes, buffer, 0); 927 position = 0; 928 numSigBytes = 4; 929 } // end if: got data 930 else { 931 return -1; 932 } // end else 933 } // end if: encoding 934 935 // Else decoding 936 else { 937 byte[] b4 = new byte[4]; 938 int i = 0; 939 for (i = 0; i < 4; i++) { 940 // Read four "meaningful" bytes: 941 int b = 0; 942 do { 943 b = in.read(); 944 } while (b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC); 945 946 if (b < 0) { 947 break; // Reads a -1 if end of stream 948 } 949 950 b4[i] = (byte) b; 951 } // end for: each needed input byte 952 953 if (i == 4) { 954 numSigBytes = decode4to3(b4, 0, buffer, 0); 955 position = 0; 956 } // end if: got four characters 957 else if (i == 0) { 958 return -1; 959 } // end else if: also padded correctly 960 else { 961 // Must have broken out from above. 962 throw new java.io.IOException("Improperly padded Base64 input."); 963 } // end 964 965 } // end else: decode 966 } // end else: get data 967 968 // Got data? 969 if (position >= 0) { 970 // End of relevant data? 971 if (/* !encode && */position >= numSigBytes) { 972 return -1; 973 } 974 975 if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { 976 lineLength = 0; 977 return '\n'; 978 } // end if 979 else { 980 lineLength++; // This isn't important when decoding 981 // but throwing an extra "if" seems 982 // just as wasteful. 983 984 int b = buffer[position++]; 985 986 if (position >= bufferLength) { 987 position = -1; 988 } 989 990 return b & 0xFF; // This is how you "cast" a byte that's 991 // intended to be unsigned. 992 } // end else 993 } // end if: position >= 0 994 995 // Else error 996 else { 997 // When JDK1.4 is more accepted, use an assertion here. 998 throw new java.io.IOException("Error in Base64 code reading stream."); 999 } // end else 1000 } // end read 1001 1002 /** 1003 * Calls {@link #read()} repeatedly until the end of stream is reached or <var>len</var> bytes are read. Returns 1004 * number of bytes read into array or -1 if end of stream is encountered. 1005 * 1006 * @param dest array to hold values 1007 * @param off offset for array 1008 * @param len max number of bytes to read into array 1009 * @return bytes read into array or -1 if end of stream is encountered. 1010 * @since 1.3 1011 */ 1012 @Override 1013 public int read(byte[] dest, int off, int len) throws java.io.IOException { 1014 int i; 1015 int b; 1016 for (i = 0; i < len; i++) { 1017 b = read(); 1018 1019 // if( b < 0 && i == 0 ) 1020 // return -1; 1021 1022 if (b >= 0) { 1023 dest[off + i] = (byte) b; 1024 } else if (i == 0) { 1025 return -1; 1026 } else { 1027 break; // Out of 'for' loop 1028 } 1029 } // end for: each byte read 1030 return i; 1031 } // end read 1032 1033 } // end inner class InputStream 1034 1035 /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ 1036 1037 /** 1038 * A {@link Base64.OutputStream} will write data to another <tt>java.io.OutputStream</tt>, given in the constructor, 1039 * and encode/decode to/from Base64 notation on the fly. 1040 * 1041 * @see Base64 1042 * @since 1.3 1043 */ 1044 public static class OutputStream extends java.io.FilterOutputStream { 1045 private boolean encode; 1046 1047 private int position; 1048 1049 private byte[] buffer; 1050 1051 private int bufferLength; 1052 1053 private int lineLength; 1054 1055 private boolean breakLines; 1056 1057 private byte[] b4; // Scratch used in a few places 1058 1059 private boolean suspendEncoding; 1060 1061 /** 1062 * Constructs a {@link Base64.OutputStream} in ENCODE mode. 1063 * 1064 * @param out the <tt>java.io.OutputStream</tt> to which data will be written. 1065 * @since 1.3 1066 */ 1067 public OutputStream(java.io.OutputStream out) { 1068 this(out, ENCODE); 1069 } // end constructor 1070 1071 /** 1072 * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode. 1073 * <p> 1074 * Valid options: 1075 * 1076 * <pre> 1077 * ENCODE or DECODE: Encode or Decode as data is read. 1078 * DONT_BREAK_LINES: don't break lines at 76 characters 1079 * (only meaningful when encoding) 1080 * <i>Note: Technically, this makes your encoding non-compliant.</i> 1081 * </pre> 1082 * <p> 1083 * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code> 1084 * 1085 * @param out the <tt>java.io.OutputStream</tt> to which data will be written. 1086 * @param options Specified options. 1087 * @see Base64#ENCODE 1088 * @see Base64#DECODE 1089 * @see Base64#DONT_BREAK_LINES 1090 * @since 1.3 1091 */ 1092 public OutputStream(java.io.OutputStream out, int options) { 1093 super(out); 1094 breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; 1095 encode = (options & ENCODE) == ENCODE; 1096 bufferLength = encode ? 3 : 4; 1097 buffer = new byte[bufferLength]; 1098 position = 0; 1099 lineLength = 0; 1100 suspendEncoding = false; 1101 b4 = new byte[4]; 1102 } // end constructor 1103 1104 /** 1105 * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are 1106 * buffered three at a time before the output stream actually gets a write() call. When decoding, bytes are 1107 * buffered four at a time. 1108 * 1109 * @param theByte the byte to write 1110 * @since 1.3 1111 */ 1112 @Override 1113 public void write(int theByte) throws java.io.IOException { 1114 // Encoding suspended? 1115 if (suspendEncoding) { 1116 super.out.write(theByte); 1117 return; 1118 } // end if: supsended 1119 1120 // Encode? 1121 if (encode) { 1122 buffer[position++] = (byte) theByte; 1123 if (position >= bufferLength) // Enough to encode. 1124 { 1125 out.write(encode3to4(b4, buffer, bufferLength)); 1126 1127 lineLength += 4; 1128 if (breakLines && lineLength >= MAX_LINE_LENGTH) { 1129 out.write(NEW_LINE); 1130 lineLength = 0; 1131 } // end if: end of line 1132 1133 position = 0; 1134 } // end if: enough to output 1135 } // end if: encoding 1136 1137 // Else, Decoding 1138 else { 1139 // Meaningful Base64 character? 1140 if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) { 1141 buffer[position++] = (byte) theByte; 1142 if (position >= bufferLength) // Enough to output. 1143 { 1144 int len = Base64.decode4to3(buffer, 0, b4, 0); 1145 out.write(b4, 0, len); 1146 // out.write( Base64.decode4to3( buffer ) ); 1147 position = 0; 1148 } // end if: enough to output 1149 } // end if: meaningful base64 character 1150 else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) { 1151 throw new java.io.IOException("Invalid character in Base64 data."); 1152 } // end else: not white space either 1153 } // end else: decoding 1154 } // end write 1155 1156 /** 1157 * Calls {@link #write(int)} repeatedly until <var>len</var> bytes are written. 1158 * 1159 * @param theBytes array from which to read bytes 1160 * @param off offset for array 1161 * @param len max number of bytes to read into array 1162 * @since 1.3 1163 */ 1164 @Override 1165 public void write(byte[] theBytes, int off, int len) throws java.io.IOException { 1166 // Encoding suspended? 1167 if (suspendEncoding) { 1168 super.out.write(theBytes, off, len); 1169 return; 1170 } // end if: supsended 1171 1172 for (int i = 0; i < len; i++) { 1173 write(theBytes[off + i]); 1174 } // end for: each byte written 1175 1176 } // end write 1177 1178 /** 1179 * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream. 1180 */ 1181 public void flushBase64() throws java.io.IOException { 1182 if (position > 0) { 1183 if (encode) { 1184 out.write(encode3to4(b4, buffer, position)); 1185 position = 0; 1186 } // end if: encoding 1187 else { 1188 throw new java.io.IOException("Base64 input not properly padded."); 1189 } // end else: decoding 1190 } // end if: buffer partially full 1191 1192 } // end flush 1193 1194 /** 1195 * Flushes and closes (I think, in the superclass) the stream. 1196 * 1197 * @since 1.3 1198 */ 1199 @Override 1200 public void close() throws java.io.IOException { 1201 // 1. Ensure that pending characters are written 1202 flushBase64(); 1203 1204 // 2. Actually close the stream 1205 // Base class both flushes and closes. 1206 super.close(); 1207 1208 buffer = null; 1209 out = null; 1210 } // end close 1211 1212 /** 1213 * Suspends encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a 1214 * stream. 1215 * 1216 * @since 1.5.1 1217 */ 1218 public void suspendEncoding() throws java.io.IOException { 1219 flushBase64(); 1220 suspendEncoding = true; 1221 } // end suspendEncoding 1222 1223 /** 1224 * Resumes encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a 1225 * stream. 1226 * 1227 * @since 1.5.1 1228 */ 1229 public void resumeEncoding() { 1230 suspendEncoding = false; 1231 } // end resumeEncoding 1232 1233 } // end inner class OutputStream 1234 1235} // end class Base64