0001: package com.jidesoft.utils;
0002:
0003: /**
0004: * Encodes and decodes to and from Base64 notation.
0005: * <p/>
0006: * <p/>
0007: * Change Log:
0008: * </p>
0009: * <ul>
0010: * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
0011: * some convenience methods for reading and writing to and from files.</li>
0012: * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
0013: * with other encodings (like EBCDIC).</li>
0014: * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
0015: * encoded data was a single byte.</li>
0016: * <li>v2.0 - I got rid of methods that used booleans to set options.
0017: * Now everything is more consolidated and cleaner. The code now detects
0018: * when data that's being decoded is gzip-compressed and will decompress it
0019: * automatically. Generally things are cleaner. You'll probably have to
0020: * change some method calls that you were making to support the new
0021: * options format (<tt>int</tt>s that you "OR" together).</li>
0022: * <li>v1.5.1 - Fixed bug when decompressing and decoding to a
0023: * byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.
0024: * Added the ability to "suspend" encoding in the Output Stream so
0025: * you can turn on and off the encoding if you need to embed base64
0026: * data in an otherwise "normal" stream (like an XML file).</li>
0027: * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself.
0028: * This helps when using GZIP streams.
0029: * Added the ability to GZip-compress objects before encoding them.</li>
0030: * <li>v1.4 - Added helper methods to read/write files.</li>
0031: * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
0032: * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
0033: * where last buffer being read, if not completely full, was not returned.</li>
0034: * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
0035: * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
0036: * </ul>
0037: * <p/>
0038: * <p/>
0039: * I am placing this code in the Public Domain. Do with it as you will.
0040: * This software comes with no guarantees or warranties but with
0041: * plenty of well-wishing instead!
0042: * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
0043: * periodically to check for updates or to contribute improvements.
0044: * </p>
0045: *
0046: * @author Robert Harder
0047: * @author rob@iharder.net
0048: * @version 2.1
0049: */
0050: public class Base64 {
0051:
0052: /* ******** P U B L I C F I E L D S ******** */
0053:
0054: /**
0055: * No options specified. Value is zero.
0056: */
0057: public final static int NO_OPTIONS = 0;
0058:
0059: /**
0060: * Specify encoding.
0061: */
0062: public final static int ENCODE = 1;
0063:
0064: /**
0065: * Specify decoding.
0066: */
0067: public final static int DECODE = 0;
0068:
0069: /**
0070: * Specify that data should be gzip-compressed.
0071: */
0072: public final static int GZIP = 2;
0073:
0074: /**
0075: * Don't break lines when encoding (violates strict Base64 specification)
0076: */
0077: public final static int DONT_BREAK_LINES = 8;
0078:
0079: /* ******** P R I V A T E F I E L D S ******** */
0080:
0081: /**
0082: * Maximum line length (76) of Base64 output.
0083: */
0084: private final static int MAX_LINE_LENGTH = 76;
0085:
0086: /**
0087: * The equals sign (=) as a byte.
0088: */
0089: private final static byte EQUALS_SIGN = (byte) '=';
0090:
0091: /**
0092: * The new line character (\n) as a byte.
0093: */
0094: private final static byte NEW_LINE = (byte) '\n';
0095:
0096: /**
0097: * Preferred encoding.
0098: */
0099: private final static String PREFERRED_ENCODING = "UTF-8";
0100:
0101: /**
0102: * The 64 valid Base64 values.
0103: */
0104: private final static byte[] ALPHABET;
0105: private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */
0106: { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E',
0107: (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J',
0108: (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O',
0109: (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T',
0110: (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y',
0111: (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd',
0112: (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i',
0113: (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',
0114: (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's',
0115: (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x',
0116: (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2',
0117: (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
0118: (byte) '8', (byte) '9', (byte) '+', (byte) '/' };
0119:
0120: /** Determine which ALPHABET to use. */
0121: static {
0122: byte[] __bytes;
0123: try {
0124: __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
0125: .getBytes(PREFERRED_ENCODING);
0126: } // end try
0127: catch (java.io.UnsupportedEncodingException use) {
0128: __bytes = _NATIVE_ALPHABET; // Fall back to native encoding
0129: } // end catch
0130: ALPHABET = __bytes;
0131: } // end static
0132:
0133: /**
0134: * Translates a Base64 value to either its 6-bit reconstruction value
0135: * or a negative number indicating some other meaning.
0136: */
0137: private final static byte[] DECODABET = { -9, -9, -9, -9, -9, -9,
0138: -9, -9, -9, // Decimal 0 - 8
0139: -5, -5, // Whitespace: Tab and Linefeed
0140: -9, -9, // Decimal 11 - 12
0141: -5, // Whitespace: Carriage Return
0142: -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
0143: -9, -9, -9, -9, -9, // Decimal 27 - 31
0144: -5, // Whitespace: Space
0145: -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
0146: 62, // Plus sign at decimal 43
0147: -9, -9, -9, // Decimal 44 - 46
0148: 63, // Slash at decimal 47
0149: 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
0150: -9, -9, -9, // Decimal 58 - 60
0151: -1, // Equals sign at decimal 61
0152: -9, -9, -9, // Decimal 62 - 64
0153: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
0154: 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
0155: -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
0156: 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
0157: 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
0158: -9, -9, -9, -9 // Decimal 123 - 126
0159: /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
0160: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
0161: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
0162: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
0163: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
0164: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
0165: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
0166: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
0167: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
0168: -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
0169: };
0170:
0171: // I think I end up not using the BAD_ENCODING indicator.
0172: //private final static byte BAD_ENCODING = -9; // Indicates error in encoding
0173: private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
0174: private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
0175:
0176: /**
0177: * Defeats instantiation.
0178: */
0179: private Base64() {
0180: }
0181:
0182: /* ******** E N C O D I N G M E T H O D S ******** */
0183:
0184: /**
0185: * Encodes up to the first three bytes of array <var>threeBytes</var>
0186: * and returns a four-byte array in Base64 notation.
0187: * The actual number of significant bytes in your array is
0188: * given by <var>numSigBytes</var>.
0189: * The array <var>threeBytes</var> needs only be as big as
0190: * <var>numSigBytes</var>.
0191: * Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
0192: *
0193: * @param b4 A reusable byte array to reduce array instantiation
0194: * @param threeBytes the array to convert
0195: * @param numSigBytes the number of significant bytes in your array
0196: * @return four byte array in Base64 notation.
0197: * @since 1.5.1
0198: */
0199: private static byte[] encode3to4(byte[] b4, byte[] threeBytes,
0200: int numSigBytes) {
0201: encode3to4(threeBytes, 0, numSigBytes, b4, 0);
0202: return b4;
0203: } // end encode3to4
0204:
0205: /**
0206: * Encodes up to three bytes of the array <var>source</var>
0207: * and writes the resulting four Base64 bytes to <var>destination</var>.
0208: * The source and destination arrays can be manipulated
0209: * anywhere along their length by specifying
0210: * <var>srcOffset</var> and <var>destOffset</var>.
0211: * This method does not check to make sure your arrays
0212: * are large enough to accomodate <var>srcOffset</var> + 3 for
0213: * the <var>source</var> array or <var>destOffset</var> + 4 for
0214: * the <var>destination</var> array.
0215: * The actual number of significant bytes in your array is
0216: * given by <var>numSigBytes</var>.
0217: *
0218: * @param source the array to convert
0219: * @param srcOffset the index where conversion begins
0220: * @param numSigBytes the number of significant bytes in your array
0221: * @param destination the array to hold the conversion
0222: * @param destOffset the index where output will be put
0223: * @return the <var>destination</var> array
0224: * @since 1.3
0225: */
0226: private static byte[] encode3to4(byte[] source, int srcOffset,
0227: int numSigBytes, byte[] destination, int destOffset) {
0228: // 1 2 3
0229: // 01234567890123456789012345678901 Bit position
0230: // --------000000001111111122222222 Array position from threeBytes
0231: // --------| || || || | Six bit groups to index ALPHABET
0232: // >>18 >>12 >> 6 >> 0 Right shift necessary
0233: // 0x3f 0x3f 0x3f Additional AND
0234:
0235: // Create buffer with zero-padding if there are only one or two
0236: // significant bytes passed in the array.
0237: // We have to shift left 24 in order to flush out the 1's that appear
0238: // when Java treats a value as negative that is cast from a byte to an int.
0239: int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8)
0240: : 0)
0241: | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16)
0242: : 0)
0243: | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24)
0244: : 0);
0245:
0246: switch (numSigBytes) {
0247: case 3:
0248: destination[destOffset] = ALPHABET[(inBuff >>> 18)];
0249: destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
0250: destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
0251: destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
0252: return destination;
0253:
0254: case 2:
0255: destination[destOffset] = ALPHABET[(inBuff >>> 18)];
0256: destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
0257: destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
0258: destination[destOffset + 3] = EQUALS_SIGN;
0259: return destination;
0260:
0261: case 1:
0262: destination[destOffset] = ALPHABET[(inBuff >>> 18)];
0263: destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
0264: destination[destOffset + 2] = EQUALS_SIGN;
0265: destination[destOffset + 3] = EQUALS_SIGN;
0266: return destination;
0267:
0268: default:
0269: return destination;
0270: } // end switch
0271: } // end encode3to4
0272:
0273: /**
0274: * Serializes an object and returns the Base64-encoded
0275: * version of that serialized object. If the object
0276: * cannot be serialized or there is another error,
0277: * the method will return <tt>null</tt>.
0278: * The object is not GZip-compressed before being encoded.
0279: *
0280: * @param serializableObject The object to encode
0281: * @return The Base64-encoded object
0282: * @since 1.4
0283: */
0284: public static String encodeObject(
0285: java.io.Serializable serializableObject) {
0286: return encodeObject(serializableObject, NO_OPTIONS);
0287: } // end encodeObject
0288:
0289: /**
0290: * Serializes an object and returns the Base64-encoded
0291: * version of that serialized object. If the object
0292: * cannot be serialized or there is another error,
0293: * the method will return <tt>null</tt>.
0294: * <p/>
0295: * Valid options:<pre>
0296: * GZIP: gzip-compresses object before encoding it.
0297: * DONT_BREAK_LINES: don't break lines at 76 characters
0298: * <i>Note: Technically, this makes your encoding non-compliant.</i>
0299: * </pre>
0300: * <p/>
0301: * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
0302: * <p/>
0303: * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
0304: *
0305: * @param serializableObject The object to encode
0306: * @param options Specified options
0307: * @return The Base64-encoded object
0308: * @see Base64#GZIP
0309: * @see Base64#DONT_BREAK_LINES
0310: * @since 2.0
0311: */
0312: public static String encodeObject(
0313: java.io.Serializable serializableObject, int options) {
0314: // Streams
0315: java.io.ByteArrayOutputStream baos = null;
0316: java.io.OutputStream b64os = null;
0317: java.io.ObjectOutputStream oos = null;
0318: java.util.zip.GZIPOutputStream gzos = null;
0319:
0320: // Isolate options
0321: int gzip = (options & GZIP);
0322: int dontBreakLines = (options & DONT_BREAK_LINES);
0323:
0324: try {
0325: // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
0326: baos = new java.io.ByteArrayOutputStream();
0327: b64os = new Base64.OutputStream(baos, ENCODE
0328: | dontBreakLines);
0329:
0330: // GZip?
0331: if (gzip == GZIP) {
0332: gzos = new java.util.zip.GZIPOutputStream(b64os);
0333: oos = new java.io.ObjectOutputStream(gzos);
0334: } // end if: gzip
0335: else
0336: oos = new java.io.ObjectOutputStream(b64os);
0337:
0338: oos.writeObject(serializableObject);
0339: } // end try
0340: catch (java.io.IOException e) {
0341: e.printStackTrace();
0342: return null;
0343: } // end catch
0344: finally {
0345: try {
0346: oos.close();
0347: } catch (Exception e) {
0348: }
0349: try {
0350: gzos.close();
0351: } catch (Exception e) {
0352: }
0353: try {
0354: b64os.close();
0355: } catch (Exception e) {
0356: }
0357: try {
0358: baos.close();
0359: } catch (Exception e) {
0360: }
0361: } // end finally
0362:
0363: // Return value according to relevant encoding.
0364: try {
0365: return new String(baos.toByteArray(), PREFERRED_ENCODING);
0366: } // end try
0367: catch (java.io.UnsupportedEncodingException uue) {
0368: return new String(baos.toByteArray());
0369: } // end catch
0370:
0371: } // end encode
0372:
0373: /**
0374: * Encodes a byte array into Base64 notation.
0375: * Does not GZip-compress data.
0376: *
0377: * @param source The data to convert
0378: * @since 1.4
0379: */
0380: public static String encodeBytes(byte[] source) {
0381: return encodeBytes(source, 0, source.length, NO_OPTIONS);
0382: } // end encodeBytes
0383:
0384: /**
0385: * Encodes a byte array into Base64 notation.
0386: * <p/>
0387: * Valid options:<pre>
0388: * GZIP: gzip-compresses object before encoding it.
0389: * DONT_BREAK_LINES: don't break lines at 76 characters
0390: * <i>Note: Technically, this makes your encoding non-compliant.</i>
0391: * </pre>
0392: * <p/>
0393: * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
0394: * <p/>
0395: * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
0396: *
0397: * @param source The data to convert
0398: * @param options Specified options
0399: * @see Base64#GZIP
0400: * @see Base64#DONT_BREAK_LINES
0401: * @since 2.0
0402: */
0403: public static String encodeBytes(byte[] source, int options) {
0404: return encodeBytes(source, 0, source.length, options);
0405: } // end encodeBytes
0406:
0407: /**
0408: * Encodes a byte array into Base64 notation.
0409: * Does not GZip-compress data.
0410: *
0411: * @param source The data to convert
0412: * @param off Offset in array where conversion should begin
0413: * @param len Length of data to convert
0414: * @since 1.4
0415: */
0416: public static String encodeBytes(byte[] source, int off, int len) {
0417: return encodeBytes(source, off, len, NO_OPTIONS);
0418: } // end encodeBytes
0419:
0420: /**
0421: * Encodes a byte array into Base64 notation.
0422: * <p/>
0423: * Valid options:<pre>
0424: * GZIP: gzip-compresses object before encoding it.
0425: * DONT_BREAK_LINES: don't break lines at 76 characters
0426: * <i>Note: Technically, this makes your encoding non-compliant.</i>
0427: * </pre>
0428: * <p/>
0429: * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
0430: * <p/>
0431: * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
0432: *
0433: * @param source The data to convert
0434: * @param off Offset in array where conversion should begin
0435: * @param len Length of data to convert
0436: * @param options Specified options
0437: * @see Base64#GZIP
0438: * @see Base64#DONT_BREAK_LINES
0439: * @since 2.0
0440: */
0441: public static String encodeBytes(byte[] source, int off, int len,
0442: int options) {
0443: // Isolate options
0444: int dontBreakLines = (options & DONT_BREAK_LINES);
0445: int gzip = (options & GZIP);
0446:
0447: // Compress?
0448: if (gzip == GZIP) {
0449: java.io.ByteArrayOutputStream baos = null;
0450: java.util.zip.GZIPOutputStream gzos = null;
0451: Base64.OutputStream b64os = null;
0452:
0453: try {
0454: // GZip -> Base64 -> ByteArray
0455: baos = new java.io.ByteArrayOutputStream();
0456: b64os = new Base64.OutputStream(baos, ENCODE
0457: | dontBreakLines);
0458: gzos = new java.util.zip.GZIPOutputStream(b64os);
0459:
0460: gzos.write(source, off, len);
0461: gzos.close();
0462: } // end try
0463: catch (java.io.IOException e) {
0464: e.printStackTrace();
0465: return null;
0466: } // end catch
0467: finally {
0468: try {
0469: gzos.close();
0470: } catch (Exception e) {
0471: }
0472: try {
0473: b64os.close();
0474: } catch (Exception e) {
0475: }
0476: try {
0477: baos.close();
0478: } catch (Exception e) {
0479: }
0480: } // end finally
0481:
0482: // Return value according to relevant encoding.
0483: try {
0484: return new String(baos.toByteArray(),
0485: PREFERRED_ENCODING);
0486: } // end try
0487: catch (java.io.UnsupportedEncodingException uue) {
0488: return new String(baos.toByteArray());
0489: } // end catch
0490: } // end if: compress
0491:
0492: // Else, don't compress. Better not to use streams at all then.
0493: else {
0494: // Convert option to boolean in way that code likes it.
0495: boolean breakLines = dontBreakLines == 0;
0496:
0497: int len43 = len * 4 / 3;
0498: byte[] outBuff = new byte[(len43) // Main 4:3
0499: + ((len % 3) > 0 ? 4 : 0) // Account for padding
0500: + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines
0501: int d = 0;
0502: int e = 0;
0503: int len2 = len - 2;
0504: int lineLength = 0;
0505: for (; d < len2; d += 3, e += 4) {
0506: encode3to4(source, d + off, 3, outBuff, e);
0507:
0508: lineLength += 4;
0509: if (breakLines && lineLength == MAX_LINE_LENGTH) {
0510: outBuff[e + 4] = NEW_LINE;
0511: e++;
0512: lineLength = 0;
0513: } // end if: end of line
0514: } // en dfor: each piece of array
0515:
0516: if (d < len) {
0517: encode3to4(source, d + off, len - d, outBuff, e);
0518: e += 4;
0519: } // end if: some padding needed
0520:
0521: // Return value according to relevant encoding.
0522: try {
0523: return new String(outBuff, 0, e, PREFERRED_ENCODING);
0524: } // end try
0525: catch (java.io.UnsupportedEncodingException uue) {
0526: return new String(outBuff, 0, e);
0527: } // end catch
0528:
0529: } // end else: don't compress
0530:
0531: } // end encodeBytes
0532:
0533: /* ******** D E C O D I N G M E T H O D S ******** */
0534:
0535: /**
0536: * Decodes four bytes from array <var>source</var>
0537: * and writes the resulting bytes (up to three of them)
0538: * to <var>destination</var>.
0539: * The source and destination arrays can be manipulated
0540: * anywhere along their length by specifying
0541: * <var>srcOffset</var> and <var>destOffset</var>.
0542: * This method does not check to make sure your arrays
0543: * are large enough to accomodate <var>srcOffset</var> + 4 for
0544: * the <var>source</var> array or <var>destOffset</var> + 3 for
0545: * the <var>destination</var> array.
0546: * This method returns the actual number of bytes that
0547: * were converted from the Base64 encoding.
0548: *
0549: * @param source the array to convert
0550: * @param srcOffset the index where conversion begins
0551: * @param destination the array to hold the conversion
0552: * @param destOffset the index where output will be put
0553: * @return the number of decoded bytes converted
0554: * @since 1.3
0555: */
0556: private static int decode4to3(byte[] source, int srcOffset,
0557: byte[] destination, int destOffset) {
0558: // Example: Dk==
0559: if (source[srcOffset + 2] == EQUALS_SIGN) {
0560: // Two ways to do the same thing. Don't know which way I like best.
0561: //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
0562: // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
0563: int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
0564: | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
0565:
0566: destination[destOffset] = (byte) (outBuff >>> 16);
0567: return 1;
0568: }
0569:
0570: // Example: DkL=
0571: else if (source[srcOffset + 3] == EQUALS_SIGN) {
0572: // Two ways to do the same thing. Don't know which way I like best.
0573: //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
0574: // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
0575: // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
0576: int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
0577: | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
0578: | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
0579:
0580: destination[destOffset] = (byte) (outBuff >>> 16);
0581: destination[destOffset + 1] = (byte) (outBuff >>> 8);
0582: return 2;
0583: }
0584:
0585: // Example: DkLE
0586: else {
0587: try {
0588: // Two ways to do the same thing. Don't know which way I like best.
0589: //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
0590: // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
0591: // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
0592: // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
0593: int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
0594: | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
0595: | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
0596: | ((DECODABET[source[srcOffset + 3]] & 0xFF));
0597:
0598: destination[destOffset] = (byte) (outBuff >> 16);
0599: destination[destOffset + 1] = (byte) (outBuff >> 8);
0600: destination[destOffset + 2] = (byte) (outBuff);
0601:
0602: return 3;
0603: } catch (Exception e) {
0604: System.out.println("" + source[srcOffset] + ": "
0605: + (DECODABET[source[srcOffset]]));
0606: System.out.println("" + source[srcOffset + 1] + ": "
0607: + (DECODABET[source[srcOffset + 1]]));
0608: System.out.println("" + source[srcOffset + 2] + ": "
0609: + (DECODABET[source[srcOffset + 2]]));
0610: System.out.println("" + source[srcOffset + 3] + ": "
0611: + (DECODABET[source[srcOffset + 3]]));
0612: return -1;
0613: } //e nd catch
0614: }
0615: } // end decodeToBytes
0616:
0617: /**
0618: * Very low-level access to decoding ASCII characters in
0619: * the form of a byte array. Does not support automatically
0620: * gunzipping or any other "fancy" features.
0621: *
0622: * @param source The Base64 encoded data
0623: * @param off The offset of where to begin decoding
0624: * @param len The length of characters to decode
0625: * @return decoded data
0626: * @since 1.3
0627: */
0628: public static byte[] decode(byte[] source, int off, int len) {
0629: int len34 = len * 3 / 4;
0630: byte[] outBuff = new byte[len34]; // Upper limit on size of output
0631: int outBuffPosn = 0;
0632:
0633: byte[] b4 = new byte[4];
0634: int b4Posn = 0;
0635: int i = 0;
0636: byte sbiCrop = 0;
0637: byte sbiDecode = 0;
0638: for (i = off; i < off + len; i++) {
0639: sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
0640: sbiDecode = DECODABET[sbiCrop];
0641:
0642: if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better
0643: {
0644: if (sbiDecode >= EQUALS_SIGN_ENC) {
0645: b4[b4Posn++] = sbiCrop;
0646: if (b4Posn > 3) {
0647: outBuffPosn += decode4to3(b4, 0, outBuff,
0648: outBuffPosn);
0649: b4Posn = 0;
0650:
0651: // If that was the equals sign, break out of 'for' loop
0652: if (sbiCrop == EQUALS_SIGN)
0653: break;
0654: } // end if: quartet built
0655:
0656: } // end if: equals sign or better
0657:
0658: } // end if: white space, equals sign or better
0659: else {
0660: System.err.println("Bad Base64 input character at " + i
0661: + ": " + source[i] + "(decimal)");
0662: return null;
0663: } // end else:
0664: } // each input character
0665:
0666: byte[] out = new byte[outBuffPosn];
0667: System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
0668: return out;
0669: } // end decode
0670:
0671: /**
0672: * Decodes data from Base64 notation, automatically
0673: * detecting gzip-compressed data and decompressing it.
0674: *
0675: * @param s the string to decode
0676: * @return the decoded data
0677: * @since 1.4
0678: */
0679: public static byte[] decode(String s) {
0680: byte[] bytes;
0681: try {
0682: bytes = s.getBytes(PREFERRED_ENCODING);
0683: } // end try
0684: catch (java.io.UnsupportedEncodingException uee) {
0685: bytes = s.getBytes();
0686: } // end catch
0687: //</change>
0688:
0689: // Decode
0690: bytes = decode(bytes, 0, bytes.length);
0691:
0692: // Check to see if it's gzip-compressed
0693: // GZIP Magic Two-Byte Number: 0x8b1f (35615)
0694: if (bytes != null && bytes.length >= 4) {
0695:
0696: int head = ((int) bytes[0] & 0xff)
0697: | ((bytes[1] << 8) & 0xff00);
0698: if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
0699: java.io.ByteArrayInputStream bais = null;
0700: java.util.zip.GZIPInputStream gzis = null;
0701: java.io.ByteArrayOutputStream baos = null;
0702: byte[] buffer = new byte[2048];
0703: int length = 0;
0704:
0705: try {
0706: baos = new java.io.ByteArrayOutputStream();
0707: bais = new java.io.ByteArrayInputStream(bytes);
0708: gzis = new java.util.zip.GZIPInputStream(bais);
0709:
0710: while ((length = gzis.read(buffer)) >= 0) {
0711: baos.write(buffer, 0, length);
0712: } // end while: reading input
0713:
0714: // No error? Get new bytes.
0715: bytes = baos.toByteArray();
0716:
0717: } // end try
0718: catch (java.io.IOException e) {
0719: // Just return originally-decoded bytes
0720: } // end catch
0721: finally {
0722: try {
0723: baos.close();
0724: } catch (Exception e) {
0725: }
0726: try {
0727: gzis.close();
0728: } catch (Exception e) {
0729: }
0730: try {
0731: bais.close();
0732: } catch (Exception e) {
0733: }
0734: } // end finally
0735:
0736: } // end if: gzipped
0737: } // end if: bytes.length >= 2
0738:
0739: return bytes;
0740: } // end decode
0741:
0742: /**
0743: * Attempts to decode Base64 data and deserialize a Java
0744: * Object within. Returns <tt>null</tt> if there was an error.
0745: *
0746: * @param encodedObject The Base64 data to decode
0747: * @return The decoded and deserialized object
0748: * @since 1.5
0749: */
0750: public static Object decodeToObject(String encodedObject) {
0751: // Decode and gunzip if necessary
0752: byte[] objBytes = decode(encodedObject);
0753:
0754: java.io.ByteArrayInputStream bais = null;
0755: java.io.ObjectInputStream ois = null;
0756: Object obj = null;
0757:
0758: try {
0759: bais = new java.io.ByteArrayInputStream(objBytes);
0760: ois = new java.io.ObjectInputStream(bais);
0761:
0762: obj = ois.readObject();
0763: } // end try
0764: catch (java.io.IOException e) {
0765: e.printStackTrace();
0766: obj = null;
0767: } // end catch
0768: catch (java.lang.ClassNotFoundException e) {
0769: e.printStackTrace();
0770: obj = null;
0771: } // end catch
0772: finally {
0773: try {
0774: bais.close();
0775: } catch (Exception e) {
0776: }
0777: try {
0778: ois.close();
0779: } catch (Exception e) {
0780: }
0781: } // end finally
0782:
0783: return obj;
0784: } // end decodeObject
0785:
0786: /**
0787: * Convenience method for encoding data to a file.
0788: *
0789: * @param dataToEncode byte array of data to encode in base64 form
0790: * @param filename Filename for saving encoded data
0791: * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
0792: * @since 2.1
0793: */
0794: public static boolean encodeToFile(byte[] dataToEncode,
0795: String filename) {
0796: boolean success = false;
0797: Base64.OutputStream bos = null;
0798: try {
0799: bos = new Base64.OutputStream(new java.io.FileOutputStream(
0800: filename), Base64.ENCODE);
0801: bos.write(dataToEncode);
0802: success = true;
0803: } // end try
0804: catch (java.io.IOException e) {
0805:
0806: success = false;
0807: } // end catch: IOException
0808: finally {
0809: try {
0810: bos.close();
0811: } catch (Exception e) {
0812: }
0813: } // end finally
0814:
0815: return success;
0816: } // end encodeToFile
0817:
0818: /**
0819: * Convenience method for decoding data to a file.
0820: *
0821: * @param dataToDecode Base64-encoded data as a string
0822: * @param filename Filename for saving decoded data
0823: * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
0824: * @since 2.1
0825: */
0826: public static boolean decodeToFile(String dataToDecode,
0827: String filename) {
0828: boolean success = false;
0829: Base64.OutputStream bos = null;
0830: try {
0831: bos = new Base64.OutputStream(new java.io.FileOutputStream(
0832: filename), Base64.DECODE);
0833: bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
0834: success = true;
0835: } // end try
0836: catch (java.io.IOException e) {
0837: success = false;
0838: } // end catch: IOException
0839: finally {
0840: try {
0841: bos.close();
0842: } catch (Exception e) {
0843: }
0844: } // end finally
0845:
0846: return success;
0847: } // end decodeToFile
0848:
0849: /**
0850: * Convenience method for reading a base64-encoded
0851: * file and decoding it.
0852: *
0853: * @param filename Filename for reading encoded data
0854: * @return decoded byte array or null if unsuccessful
0855: * @since 2.1
0856: */
0857: public static byte[] decodeFromFile(String filename) {
0858: byte[] decodedData = null;
0859: Base64.InputStream bis = null;
0860: try {
0861: // Set up some useful variables
0862: java.io.File file = new java.io.File(filename);
0863: byte[] buffer = null;
0864: int length = 0;
0865: int numBytes = 0;
0866:
0867: // Check for size of file
0868: if (file.length() > Integer.MAX_VALUE) {
0869: System.err
0870: .println("File is too big for this convenience method ("
0871: + file.length() + " bytes).");
0872: return null;
0873: } // end if: file too big for int index
0874: buffer = new byte[(int) file.length()];
0875:
0876: // Open a stream
0877: bis = new Base64.InputStream(
0878: new java.io.BufferedInputStream(
0879: new java.io.FileInputStream(file)),
0880: Base64.DECODE);
0881:
0882: // Read until done
0883: while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
0884: length += numBytes;
0885:
0886: // Save in a variable to return
0887: decodedData = new byte[length];
0888: System.arraycopy(buffer, 0, decodedData, 0, length);
0889:
0890: } // end try
0891: catch (java.io.IOException e) {
0892: System.err.println("Error decoding from file " + filename);
0893: } // end catch: IOException
0894: finally {
0895: try {
0896: bis.close();
0897: } catch (Exception e) {
0898: }
0899: } // end finally
0900:
0901: return decodedData;
0902: } // end decodeFromFile
0903:
0904: /**
0905: * Convenience method for reading a binary file
0906: * and base64-encoding it.
0907: *
0908: * @param filename Filename for reading binary data
0909: * @return base64-encoded string or null if unsuccessful
0910: * @since 2.1
0911: */
0912: public static String encodeFromFile(String filename) {
0913: String encodedData = null;
0914: Base64.InputStream bis = null;
0915: try {
0916: // Set up some useful variables
0917: java.io.File file = new java.io.File(filename);
0918: byte[] buffer = new byte[(int) (file.length() * 1.4)];
0919: int length = 0;
0920: int numBytes = 0;
0921:
0922: // Open a stream
0923: bis = new Base64.InputStream(
0924: new java.io.BufferedInputStream(
0925: new java.io.FileInputStream(file)),
0926: Base64.ENCODE);
0927:
0928: // Read until done
0929: while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
0930: length += numBytes;
0931:
0932: // Save in a variable to return
0933: encodedData = new String(buffer, 0, length,
0934: Base64.PREFERRED_ENCODING);
0935:
0936: } // end try
0937: catch (java.io.IOException e) {
0938: System.err.println("Error encoding from file " + filename);
0939: } // end catch: IOException
0940: finally {
0941: try {
0942: bis.close();
0943: } catch (Exception e) {
0944: }
0945: } // end finally
0946:
0947: return encodedData;
0948: } // end encodeFromFile
0949:
0950: /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
0951:
0952: /**
0953: * A {@link Base64.InputStream} will read data from another
0954: * <tt>java.io.InputStream</tt>, given in the constructor,
0955: * and encode/decode to/from Base64 notation on the fly.
0956: *
0957: * @see Base64
0958: * @since 1.3
0959: */
0960: public static class InputStream extends java.io.FilterInputStream {
0961: private boolean encode; // Encoding or decoding
0962: private int position; // Current position in the buffer
0963: private byte[] buffer; // Small buffer holding converted data
0964: private int bufferLength; // Length of buffer (3 or 4)
0965: private int numSigBytes; // Number of meaningful bytes in the buffer
0966: private int lineLength;
0967: private boolean breakLines; // Break lines at less than 80 characters
0968:
0969: /**
0970: * Constructs a {@link Base64.InputStream} in DECODE mode.
0971: *
0972: * @param in the <tt>java.io.InputStream</tt> from which to read data.
0973: * @since 1.3
0974: */
0975: public InputStream(java.io.InputStream in) {
0976: this (in, DECODE);
0977: } // end constructor
0978:
0979: /**
0980: * Constructs a {@link Base64.InputStream} in
0981: * either ENCODE or DECODE mode.
0982: * <p/>
0983: * Valid options:<pre>
0984: * ENCODE or DECODE: Encode or Decode as data is read.
0985: * DONT_BREAK_LINES: don't break lines at 76 characters
0986: * (only meaningful when encoding)
0987: * <i>Note: Technically, this makes your encoding non-compliant.</i>
0988: * </pre>
0989: * <p/>
0990: * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
0991: *
0992: * @param in the <tt>java.io.InputStream</tt> from which to read data.
0993: * @param options Specified options
0994: * @see Base64#ENCODE
0995: * @see Base64#DECODE
0996: * @see Base64#DONT_BREAK_LINES
0997: * @since 2.0
0998: */
0999: public InputStream(java.io.InputStream in, int options) {
1000: super (in);
1001: this .breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
1002: this .encode = (options & ENCODE) == ENCODE;
1003: this .bufferLength = encode ? 4 : 3;
1004: this .buffer = new byte[bufferLength];
1005: this .position = -1;
1006: this .lineLength = 0;
1007: } // end constructor
1008:
1009: /**
1010: * Reads enough of the input stream to convert
1011: * to/from Base64 and returns the next byte.
1012: *
1013: * @return next byte
1014: * @since 1.3
1015: */
1016: @Override
1017: public int read() throws java.io.IOException {
1018: // Do we need to get data?
1019: if (position < 0) {
1020: if (encode) {
1021: byte[] b3 = new byte[3];
1022: int numBinaryBytes = 0;
1023: for (int i = 0; i < 3; i++) {
1024: try {
1025: int b = in.read();
1026:
1027: // If end of stream, b is -1.
1028: if (b >= 0) {
1029: b3[i] = (byte) b;
1030: numBinaryBytes++;
1031: } // end if: not end of stream
1032:
1033: } // end try: read
1034: catch (java.io.IOException e) {
1035: // Only a problem if we got no data at all.
1036: if (i == 0)
1037: throw e;
1038:
1039: } // end catch
1040: } // end for: each needed input byte
1041:
1042: if (numBinaryBytes > 0) {
1043: encode3to4(b3, 0, numBinaryBytes, buffer, 0);
1044: position = 0;
1045: numSigBytes = 4;
1046: } // end if: got data
1047: else {
1048: return -1;
1049: } // end else
1050: } // end if: encoding
1051:
1052: // Else decoding
1053: else {
1054: byte[] b4 = new byte[4];
1055: int i = 0;
1056: for (i = 0; i < 4; i++) {
1057: // Read four "meaningful" bytes:
1058: int b = 0;
1059: do {
1060: b = in.read();
1061: } while (b >= 0
1062: && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC);
1063:
1064: if (b < 0)
1065: break; // Reads a -1 if end of stream
1066:
1067: b4[i] = (byte) b;
1068: } // end for: each needed input byte
1069:
1070: if (i == 4) {
1071: numSigBytes = decode4to3(b4, 0, buffer, 0);
1072: position = 0;
1073: } // end if: got four characters
1074: else if (i == 0) {
1075: return -1;
1076: } // end else if: also padded correctly
1077: else {
1078: // Must have broken out from above.
1079: throw new java.io.IOException(
1080: "Improperly padded Base64 input.");
1081: } // end
1082:
1083: } // end else: decode
1084: } // end else: get data
1085:
1086: // Got data?
1087: if (position >= 0) {
1088: // End of relevant data?
1089: if ( /*!encode &&*/position >= numSigBytes)
1090: return -1;
1091:
1092: if (encode && breakLines
1093: && lineLength >= MAX_LINE_LENGTH) {
1094: lineLength = 0;
1095: return '\n';
1096: } // end if
1097: else {
1098: lineLength++; // This isn't important when decoding
1099: // but throwing an extra "if" seems
1100: // just as wasteful.
1101:
1102: int b = buffer[position++];
1103:
1104: if (position >= bufferLength)
1105: position = -1;
1106:
1107: return b & 0xFF; // This is how you "cast" a byte that's
1108: // intended to be unsigned.
1109: } // end else
1110: } // end if: position >= 0
1111:
1112: // Else error
1113: else {
1114: // When JDK1.4 is more accepted, use an assertion here.
1115: throw new java.io.IOException(
1116: "Error in Base64 code reading stream.");
1117: } // end else
1118: } // end read
1119:
1120: /**
1121: * Calls {@link #read()} repeatedly until the end of stream
1122: * is reached or <var>len</var> bytes are read.
1123: * Returns number of bytes read into array or -1 if
1124: * end of stream is encountered.
1125: *
1126: * @param dest array to hold values
1127: * @param off offset for array
1128: * @param len max number of bytes to read into array
1129: * @return bytes read into array or -1 if end of stream is encountered.
1130: * @since 1.3
1131: */
1132: @Override
1133: public int read(byte[] dest, int off, int len)
1134: throws java.io.IOException {
1135: int i;
1136: int b;
1137: for (i = 0; i < len; i++) {
1138: b = read();
1139:
1140: //if( b < 0 && i == 0 )
1141: // return -1;
1142:
1143: if (b >= 0)
1144: dest[off + i] = (byte) b;
1145: else if (i == 0)
1146: return -1;
1147: else
1148: break; // Out of 'for' loop
1149: } // end for: each byte read
1150: return i;
1151: } // end read
1152:
1153: } // end inner class InputStream
1154:
1155: /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
1156:
1157: /**
1158: * A {@link Base64.OutputStream} will write data to another
1159: * <tt>java.io.OutputStream</tt>, given in the constructor,
1160: * and encode/decode to/from Base64 notation on the fly.
1161: *
1162: * @see Base64
1163: * @since 1.3
1164: */
1165: public static class OutputStream extends java.io.FilterOutputStream {
1166: private boolean encode;
1167: private int position;
1168: private byte[] buffer;
1169: private int bufferLength;
1170: private int lineLength;
1171: private boolean breakLines;
1172: private byte[] b4; // Scratch used in a few places
1173: private boolean suspendEncoding;
1174:
1175: /**
1176: * Constructs a {@link Base64.OutputStream} in ENCODE mode.
1177: *
1178: * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1179: * @since 1.3
1180: */
1181: public OutputStream(java.io.OutputStream out) {
1182: this (out, ENCODE);
1183: } // end constructor
1184:
1185: /**
1186: * Constructs a {@link Base64.OutputStream} in
1187: * either ENCODE or DECODE mode.
1188: * <p/>
1189: * Valid options:<pre>
1190: * ENCODE or DECODE: Encode or Decode as data is read.
1191: * DONT_BREAK_LINES: don't break lines at 76 characters
1192: * (only meaningful when encoding)
1193: * <i>Note: Technically, this makes your encoding non-compliant.</i>
1194: * </pre>
1195: * <p/>
1196: * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
1197: *
1198: * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1199: * @param options Specified options.
1200: * @see Base64#ENCODE
1201: * @see Base64#DECODE
1202: * @see Base64#DONT_BREAK_LINES
1203: * @since 1.3
1204: */
1205: public OutputStream(java.io.OutputStream out, int options) {
1206: super (out);
1207: this .breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
1208: this .encode = (options & ENCODE) == ENCODE;
1209: this .bufferLength = encode ? 3 : 4;
1210: this .buffer = new byte[bufferLength];
1211: this .position = 0;
1212: this .lineLength = 0;
1213: this .suspendEncoding = false;
1214: this .b4 = new byte[4];
1215: } // end constructor
1216:
1217: /**
1218: * Writes the byte to the output stream after
1219: * converting to/from Base64 notation.
1220: * When encoding, bytes are buffered three
1221: * at a time before the output stream actually
1222: * gets a write() call.
1223: * When decoding, bytes are buffered four
1224: * at a time.
1225: *
1226: * @param theByte the byte to write
1227: * @since 1.3
1228: */
1229: @Override
1230: public void write(int theByte) throws java.io.IOException {
1231: // Encoding suspended?
1232: if (suspendEncoding) {
1233: super .out.write(theByte);
1234: return;
1235: } // end if: supsended
1236:
1237: // Encode?
1238: if (encode) {
1239: buffer[position++] = (byte) theByte;
1240: if (position >= bufferLength) // Enough to encode.
1241: {
1242: out.write(encode3to4(b4, buffer, bufferLength));
1243:
1244: lineLength += 4;
1245: if (breakLines && lineLength >= MAX_LINE_LENGTH) {
1246: out.write(NEW_LINE);
1247: lineLength = 0;
1248: } // end if: end of line
1249:
1250: position = 0;
1251: } // end if: enough to output
1252: } // end if: encoding
1253:
1254: // Else, Decoding
1255: else {
1256: // Meaningful Base64 character?
1257: if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) {
1258: buffer[position++] = (byte) theByte;
1259: if (position >= bufferLength) // Enough to output.
1260: {
1261: int len = Base64.decode4to3(buffer, 0, b4, 0);
1262: out.write(b4, 0, len);
1263: //out.write( Base64.decode4to3( buffer ) );
1264: position = 0;
1265: } // end if: enough to output
1266: } // end if: meaningful base64 character
1267: else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) {
1268: throw new java.io.IOException(
1269: "Invalid character in Base64 data.");
1270: } // end else: not white space either
1271: } // end else: decoding
1272: } // end write
1273:
1274: /**
1275: * Calls {@link #write(int)} repeatedly until <var>len</var>
1276: * bytes are written.
1277: *
1278: * @param theBytes array from which to read bytes
1279: * @param off offset for array
1280: * @param len max number of bytes to read into array
1281: * @since 1.3
1282: */
1283: @Override
1284: public void write(byte[] theBytes, int off, int len)
1285: throws java.io.IOException {
1286: // Encoding suspended?
1287: if (suspendEncoding) {
1288: super .out.write(theBytes, off, len);
1289: return;
1290: } // end if: supsended
1291:
1292: for (int i = 0; i < len; i++) {
1293: write(theBytes[off + i]);
1294: } // end for: each byte written
1295:
1296: } // end write
1297:
1298: /**
1299: * Method added by PHIL. [Thanks, PHIL. -Rob]
1300: * This pads the buffer without closing the stream.
1301: */
1302: public void flushBase64() throws java.io.IOException {
1303: if (position > 0) {
1304: if (encode) {
1305: out.write(encode3to4(b4, buffer, position));
1306: position = 0;
1307: } // end if: encoding
1308: else {
1309: throw new java.io.IOException(
1310: "Base64 input not properly padded.");
1311: } // end else: decoding
1312: } // end if: buffer partially full
1313:
1314: } // end flush
1315:
1316: /**
1317: * Flushes and closes (I think, in the superclass) the stream.
1318: *
1319: * @since 1.3
1320: */
1321: @Override
1322: public void close() throws java.io.IOException {
1323: // 1. Ensure that pending characters are written
1324: flushBase64();
1325:
1326: // 2. Actually close the stream
1327: // Base class both flushes and closes.
1328: super .close();
1329:
1330: buffer = null;
1331: out = null;
1332: } // end close
1333:
1334: /**
1335: * Suspends encoding of the stream.
1336: * May be helpful if you need to embed a piece of
1337: * base640-encoded data in a stream.
1338: *
1339: * @since 1.5.1
1340: */
1341: public void suspendEncoding() throws java.io.IOException {
1342: flushBase64();
1343: this .suspendEncoding = true;
1344: } // end suspendEncoding
1345:
1346: /**
1347: * Resumes encoding of the stream.
1348: * May be helpful if you need to embed a piece of
1349: * base640-encoded data in a stream.
1350: *
1351: * @since 1.5.1
1352: */
1353: public void resumeEncoding() {
1354: this .suspendEncoding = false;
1355: } // end resumeEncoding
1356:
1357: } // end inner class OutputStream
1358:
1359: } // end class Base64
|