0001: package org.codehaus.aspectwerkz.util;
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.2 - 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.2
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:
0339: oos.writeObject(serializableObject);
0340: } // end try
0341: catch (java.io.IOException e) {
0342: e.printStackTrace();
0343: return null;
0344: } // end catch
0345: finally {
0346: try {
0347: oos.close();
0348: } catch (Exception e) {
0349: }
0350: try {
0351: gzos.close();
0352: } catch (Exception e) {
0353: }
0354: try {
0355: b64os.close();
0356: } catch (Exception e) {
0357: }
0358: try {
0359: baos.close();
0360: } catch (Exception e) {
0361: }
0362: } // end finally
0363:
0364: // Return value according to relevant encoding.
0365: try {
0366: return new String(baos.toByteArray(), PREFERRED_ENCODING);
0367: } // end try
0368: catch (java.io.UnsupportedEncodingException uue) {
0369: return new String(baos.toByteArray());
0370: } // end catch
0371:
0372: } // end encode
0373:
0374: /**
0375: * Encodes a byte array into Base64 notation.
0376: * Does not GZip-compress data.
0377: *
0378: * @param source The data to convert
0379: * @since 1.4
0380: */
0381: public static String encodeBytes(byte[] source) {
0382: return encodeBytes(source, 0, source.length, NO_OPTIONS);
0383: } // end encodeBytes
0384:
0385: /**
0386: * Encodes a byte array into Base64 notation.
0387: * <p/>
0388: * Valid options:<pre>
0389: * GZIP: gzip-compresses object before encoding it.
0390: * DONT_BREAK_LINES: don't break lines at 76 characters
0391: * <i>Note: Technically, this makes your encoding non-compliant.</i>
0392: * </pre>
0393: * <p/>
0394: * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
0395: * <p/>
0396: * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
0397: *
0398: * @param source The data to convert
0399: * @param options Specified options
0400: * @see Base64#GZIP
0401: * @see Base64#DONT_BREAK_LINES
0402: * @since 2.0
0403: */
0404: public static String encodeBytes(byte[] source, int options) {
0405: return encodeBytes(source, 0, source.length, options);
0406: } // end encodeBytes
0407:
0408: /**
0409: * Encodes a byte array into Base64 notation.
0410: * Does not GZip-compress data.
0411: *
0412: * @param source The data to convert
0413: * @param off Offset in array where conversion should begin
0414: * @param len Length of data to convert
0415: * @since 1.4
0416: */
0417: public static String encodeBytes(byte[] source, int off, int len) {
0418: return encodeBytes(source, off, len, NO_OPTIONS);
0419: } // end encodeBytes
0420:
0421: /**
0422: * Encodes a byte array into Base64 notation.
0423: * <p/>
0424: * Valid options:<pre>
0425: * GZIP: gzip-compresses object before encoding it.
0426: * DONT_BREAK_LINES: don't break lines at 76 characters
0427: * <i>Note: Technically, this makes your encoding non-compliant.</i>
0428: * </pre>
0429: * <p/>
0430: * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
0431: * <p/>
0432: * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
0433: *
0434: * @param source The data to convert
0435: * @param off Offset in array where conversion should begin
0436: * @param len Length of data to convert
0437: * @param options Specified options
0438: * @see Base64#GZIP
0439: * @see Base64#DONT_BREAK_LINES
0440: * @since 2.0
0441: */
0442: public static String encodeBytes(byte[] source, int off, int len,
0443: int options) {
0444: // Isolate options
0445: int dontBreakLines = (options & DONT_BREAK_LINES);
0446: int gzip = (options & GZIP);
0447:
0448: // Compress?
0449: if (gzip == GZIP) {
0450: java.io.ByteArrayOutputStream baos = null;
0451: java.util.zip.GZIPOutputStream gzos = null;
0452: Base64.OutputStream b64os = null;
0453:
0454: try {
0455: // GZip -> Base64 -> ByteArray
0456: baos = new java.io.ByteArrayOutputStream();
0457: b64os = new Base64.OutputStream(baos, ENCODE
0458: | dontBreakLines);
0459: gzos = new java.util.zip.GZIPOutputStream(b64os);
0460:
0461: gzos.write(source, off, len);
0462: gzos.close();
0463: } // end try
0464: catch (java.io.IOException e) {
0465: e.printStackTrace();
0466: return null;
0467: } // end catch
0468: finally {
0469: try {
0470: gzos.close();
0471: } catch (Exception e) {
0472: }
0473: try {
0474: b64os.close();
0475: } catch (Exception e) {
0476: }
0477: try {
0478: baos.close();
0479: } catch (Exception e) {
0480: }
0481: } // end finally
0482:
0483: // Return value according to relevant encoding.
0484: try {
0485: return new String(baos.toByteArray(),
0486: PREFERRED_ENCODING);
0487: } // end try
0488: catch (java.io.UnsupportedEncodingException uue) {
0489: return new String(baos.toByteArray());
0490: } // end catch
0491: } // end if: compress
0492:
0493: // Else, don't compress. Better not to use streams at all then.
0494: else {
0495: // Convert option to boolean in way that code likes it.
0496: boolean breakLines = dontBreakLines == 0;
0497:
0498: int len43 = len * 4 / 3;
0499: byte[] outBuff = new byte[(len43) // Main 4:3
0500: + ((len % 3) > 0 ? 4 : 0) // Account for padding
0501: + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines
0502: int d = 0;
0503: int e = 0;
0504: int len2 = len - 2;
0505: int lineLength = 0;
0506: for (; d < len2; d += 3, e += 4) {
0507: encode3to4(source, d + off, 3, outBuff, e);
0508:
0509: lineLength += 4;
0510: if (breakLines && lineLength == MAX_LINE_LENGTH) {
0511: outBuff[e + 4] = NEW_LINE;
0512: e++;
0513: lineLength = 0;
0514: } // end if: end of line
0515: } // en dfor: each piece of array
0516:
0517: if (d < len) {
0518: encode3to4(source, d + off, len - d, outBuff, e);
0519: e += 4;
0520: } // end if: some padding needed
0521:
0522: // Return value according to relevant encoding.
0523: try {
0524: return new String(outBuff, 0, e, PREFERRED_ENCODING);
0525: } // end try
0526: catch (java.io.UnsupportedEncodingException uue) {
0527: return new String(outBuff, 0, e);
0528: } // end catch
0529:
0530: } // end else: don't compress
0531:
0532: } // end encodeBytes
0533:
0534: /* ******** D E C O D I N G M E T H O D S ******** */
0535:
0536: /**
0537: * Decodes four bytes from array <var>source</var>
0538: * and writes the resulting bytes (up to three of them)
0539: * to <var>destination</var>.
0540: * The source and destination arrays can be manipulated
0541: * anywhere along their length by specifying
0542: * <var>srcOffset</var> and <var>destOffset</var>.
0543: * This method does not check to make sure your arrays
0544: * are large enough to accomodate <var>srcOffset</var> + 4 for
0545: * the <var>source</var> array or <var>destOffset</var> + 3 for
0546: * the <var>destination</var> array.
0547: * This method returns the actual number of bytes that
0548: * were converted from the Base64 encoding.
0549: *
0550: * @param source the array to convert
0551: * @param srcOffset the index where conversion begins
0552: * @param destination the array to hold the conversion
0553: * @param destOffset the index where output will be put
0554: * @return the number of decoded bytes converted
0555: * @since 1.3
0556: */
0557: private static int decode4to3(byte[] source, int srcOffset,
0558: byte[] destination, int destOffset) {
0559: // Example: Dk==
0560: if (source[srcOffset + 2] == EQUALS_SIGN) {
0561: // Two ways to do the same thing. Don't know which way I like best.
0562: //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
0563: // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
0564: int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
0565: | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
0566:
0567: destination[destOffset] = (byte) (outBuff >>> 16);
0568: return 1;
0569: }
0570:
0571: // Example: DkL=
0572: else if (source[srcOffset + 3] == EQUALS_SIGN) {
0573: // Two ways to do the same thing. Don't know which way I like best.
0574: //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
0575: // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
0576: // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
0577: int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
0578: | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
0579: | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
0580:
0581: destination[destOffset] = (byte) (outBuff >>> 16);
0582: destination[destOffset + 1] = (byte) (outBuff >>> 8);
0583: return 2;
0584: }
0585:
0586: // Example: DkLE
0587: else {
0588: try {
0589: // Two ways to do the same thing. Don't know which way I like best.
0590: //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
0591: // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
0592: // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
0593: // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
0594: int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
0595: | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
0596: | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
0597: | ((DECODABET[source[srcOffset + 3]] & 0xFF));
0598:
0599: destination[destOffset] = (byte) (outBuff >> 16);
0600: destination[destOffset + 1] = (byte) (outBuff >> 8);
0601: destination[destOffset + 2] = (byte) (outBuff);
0602:
0603: return 3;
0604: } catch (Exception e) {
0605: System.out.println("" + source[srcOffset] + ": "
0606: + (DECODABET[source[srcOffset]]));
0607: System.out.println("" + source[srcOffset + 1] + ": "
0608: + (DECODABET[source[srcOffset + 1]]));
0609: System.out.println("" + source[srcOffset + 2] + ": "
0610: + (DECODABET[source[srcOffset + 2]]));
0611: System.out.println("" + source[srcOffset + 3] + ": "
0612: + (DECODABET[source[srcOffset + 3]]));
0613: return -1;
0614: } //e nd catch
0615: }
0616: } // end decodeToBytes
0617:
0618: /**
0619: * Very low-level access to decoding ASCII characters in
0620: * the form of a byte array. Does not support automatically
0621: * gunzipping or any other "fancy" features.
0622: *
0623: * @param source The Base64 encoded data
0624: * @param off The offset of where to begin decoding
0625: * @param len The length of characters to decode
0626: * @return decoded data
0627: * @since 1.3
0628: */
0629: public static byte[] decode(byte[] source, int off, int len) {
0630: int len34 = len * 3 / 4;
0631: byte[] outBuff = new byte[len34]; // Upper limit on size of output
0632: int outBuffPosn = 0;
0633:
0634: byte[] b4 = new byte[4];
0635: int b4Posn = 0;
0636: int i = 0;
0637: byte sbiCrop = 0;
0638: byte sbiDecode = 0;
0639: for (i = off; i < off + len; i++) {
0640: sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
0641: sbiDecode = DECODABET[sbiCrop];
0642:
0643: if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better
0644: {
0645: if (sbiDecode >= EQUALS_SIGN_ENC) {
0646: b4[b4Posn++] = sbiCrop;
0647: if (b4Posn > 3) {
0648: outBuffPosn += decode4to3(b4, 0, outBuff,
0649: outBuffPosn);
0650: b4Posn = 0;
0651:
0652: // If that was the equals sign, break out of 'for' loop
0653: if (sbiCrop == EQUALS_SIGN) {
0654: break;
0655: }
0656: } // end if: quartet built
0657:
0658: } // end if: equals sign or better
0659:
0660: } // end if: white space, equals sign or better
0661: else {
0662: System.err.println("Bad Base64 input character at " + i
0663: + ": " + source[i] + "(decimal)");
0664: return null;
0665: } // end else:
0666: } // each input character
0667:
0668: byte[] out = new byte[outBuffPosn];
0669: System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
0670: return out;
0671: } // end decode
0672:
0673: /**
0674: * Decodes data from Base64 notation, automatically
0675: * detecting gzip-compressed data and decompressing it.
0676: *
0677: * @param s the string to decode
0678: * @return the decoded data
0679: * @since 1.4
0680: */
0681: public static byte[] decode(String s) {
0682: byte[] bytes;
0683: try {
0684: bytes = s.getBytes(PREFERRED_ENCODING);
0685: } // end try
0686: catch (java.io.UnsupportedEncodingException uee) {
0687: bytes = s.getBytes();
0688: } // end catch
0689: //</change>
0690:
0691: // Decode
0692: bytes = decode(bytes, 0, bytes.length);
0693:
0694: // Check to see if it's gzip-compressed
0695: // GZIP Magic Two-Byte Number: 0x8b1f (35615)
0696: if (bytes != null && bytes.length >= 4) {
0697:
0698: int head = ((int) bytes[0] & 0xff)
0699: | ((bytes[1] << 8) & 0xff00);
0700: if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
0701: java.io.ByteArrayInputStream bais = null;
0702: java.util.zip.GZIPInputStream gzis = null;
0703: java.io.ByteArrayOutputStream baos = null;
0704: byte[] buffer = new byte[2048];
0705: int length = 0;
0706:
0707: try {
0708: baos = new java.io.ByteArrayOutputStream();
0709: bais = new java.io.ByteArrayInputStream(bytes);
0710: gzis = new java.util.zip.GZIPInputStream(bais);
0711:
0712: while ((length = gzis.read(buffer)) >= 0) {
0713: baos.write(buffer, 0, length);
0714: } // end while: reading input
0715:
0716: // No error? Get new bytes.
0717: bytes = baos.toByteArray();
0718:
0719: } // end try
0720: catch (java.io.IOException e) {
0721: // Just return originally-decoded bytes
0722: } // end catch
0723: finally {
0724: try {
0725: baos.close();
0726: } catch (Exception e) {
0727: }
0728: try {
0729: gzis.close();
0730: } catch (Exception e) {
0731: }
0732: try {
0733: bais.close();
0734: } catch (Exception e) {
0735: }
0736: } // end finally
0737:
0738: } // end if: gzipped
0739: } // end if: bytes.length >= 2
0740:
0741: return bytes;
0742: } // end decode
0743:
0744: /**
0745: * Attempts to decode Base64 data and deserialize a Java
0746: * Object within. Returns <tt>null</tt> if there was an error.
0747: *
0748: * @param encodedObject The Base64 data to decode
0749: * @return The decoded and deserialized object
0750: * @since 1.5
0751: */
0752: public static Object decodeToObject(String encodedObject) {
0753: // Decode and gunzip if necessary
0754: byte[] objBytes = decode(encodedObject);
0755:
0756: java.io.ByteArrayInputStream bais = null;
0757: java.io.ObjectInputStream ois = null;
0758: Object obj = null;
0759:
0760: try {
0761: bais = new java.io.ByteArrayInputStream(objBytes);
0762: ois = new java.io.ObjectInputStream(bais);
0763:
0764: obj = ois.readObject();
0765: } // end try
0766: catch (java.io.IOException e) {
0767: e.printStackTrace();
0768: obj = null;
0769: } // end catch
0770: catch (java.lang.ClassNotFoundException e) {
0771: e.printStackTrace();
0772: obj = null;
0773: } // end catch
0774: finally {
0775: try {
0776: bais.close();
0777: } catch (Exception e) {
0778: }
0779: try {
0780: ois.close();
0781: } catch (Exception e) {
0782: }
0783: } // end finally
0784:
0785: return obj;
0786: } // end decodeObject
0787:
0788: /**
0789: * Convenience method for encoding data to a file.
0790: *
0791: * @param dataToEncode byte array of data to encode in base64 form
0792: * @param filename Filename for saving encoded data
0793: * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
0794: * @since 2.1
0795: */
0796: public static boolean encodeToFile(byte[] dataToEncode,
0797: String filename) {
0798: boolean success = false;
0799: Base64.OutputStream bos = null;
0800: try {
0801: bos = new Base64.OutputStream(new java.io.FileOutputStream(
0802: filename), Base64.ENCODE);
0803: bos.write(dataToEncode);
0804: success = true;
0805: } // end try
0806: catch (java.io.IOException e) {
0807:
0808: success = false;
0809: } // end catch: IOException
0810: finally {
0811: try {
0812: bos.close();
0813: } catch (Exception e) {
0814: }
0815: } // end finally
0816:
0817: return success;
0818: } // end encodeToFile
0819:
0820: /**
0821: * Convenience method for decoding data to a file.
0822: *
0823: * @param dataToDecode Base64-encoded data as a string
0824: * @param filename Filename for saving decoded data
0825: * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
0826: * @since 2.1
0827: */
0828: public static boolean decodeToFile(String dataToDecode,
0829: String filename) {
0830: boolean success = false;
0831: Base64.OutputStream bos = null;
0832: try {
0833: bos = new Base64.OutputStream(new java.io.FileOutputStream(
0834: filename), Base64.DECODE);
0835: bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
0836: success = true;
0837: } // end try
0838: catch (java.io.IOException e) {
0839: success = false;
0840: } // end catch: IOException
0841: finally {
0842: try {
0843: bos.close();
0844: } catch (Exception e) {
0845: }
0846: } // end finally
0847:
0848: return success;
0849: } // end decodeToFile
0850:
0851: /**
0852: * Convenience method for reading a base64-encoded
0853: * file and decoding it.
0854: *
0855: * @param filename Filename for reading encoded data
0856: * @return decoded byte array or null if unsuccessful
0857: * @since 2.1
0858: */
0859: public static byte[] decodeFromFile(String filename) {
0860: byte[] decodedData = null;
0861: Base64.InputStream bis = null;
0862: try {
0863: // Set up some useful variables
0864: java.io.File file = new java.io.File(filename);
0865: byte[] buffer = null;
0866: int length = 0;
0867: int numBytes = 0;
0868:
0869: // Check for size of file
0870: if (file.length() > Integer.MAX_VALUE) {
0871: System.err
0872: .println("File is too big for this convenience method ("
0873: + file.length() + " bytes).");
0874: return null;
0875: } // end if: file too big for int index
0876: buffer = new byte[(int) file.length()];
0877:
0878: // Open a stream
0879: bis = new Base64.InputStream(
0880: new java.io.BufferedInputStream(
0881: new java.io.FileInputStream(file)),
0882: Base64.DECODE);
0883:
0884: // Read until done
0885: while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
0886: length += numBytes;
0887: }
0888:
0889: // Save in a variable to return
0890: decodedData = new byte[length];
0891: System.arraycopy(buffer, 0, decodedData, 0, length);
0892:
0893: } // end try
0894: catch (java.io.IOException e) {
0895: System.err.println("Error decoding from file " + filename);
0896: } // end catch: IOException
0897: finally {
0898: try {
0899: bis.close();
0900: } catch (Exception e) {
0901: }
0902: } // end finally
0903:
0904: return decodedData;
0905: } // end decodeFromFile
0906:
0907: /**
0908: * Convenience method for reading a binary file
0909: * and base64-encoding it.
0910: *
0911: * @param filename Filename for reading binary data
0912: * @return base64-encoded string or null if unsuccessful
0913: * @since 2.1
0914: */
0915: public static String encodeFromFile(String filename) {
0916: String encodedData = null;
0917: Base64.InputStream bis = null;
0918: try {
0919: // Set up some useful variables
0920: java.io.File file = new java.io.File(filename);
0921: byte[] buffer = new byte[(int) (file.length() * 1.4)];
0922: int length = 0;
0923: int numBytes = 0;
0924:
0925: // Open a stream
0926: bis = new Base64.InputStream(
0927: new java.io.BufferedInputStream(
0928: new java.io.FileInputStream(file)),
0929: Base64.ENCODE);
0930:
0931: // Read until done
0932: while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
0933: length += numBytes;
0934: }
0935:
0936: // Save in a variable to return
0937: encodedData = new String(buffer, 0, length,
0938: Base64.PREFERRED_ENCODING);
0939:
0940: } // end try
0941: catch (java.io.IOException e) {
0942: System.err.println("Error encoding from file " + filename);
0943: } // end catch: IOException
0944: finally {
0945: try {
0946: bis.close();
0947: } catch (Exception e) {
0948: }
0949: } // end finally
0950:
0951: return encodedData;
0952: } // end encodeFromFile
0953:
0954: /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
0955:
0956: /**
0957: * A {@link Base64.InputStream} will read data from another
0958: * <tt>java.io.InputStream</tt>, given in the constructor,
0959: * and encode/decode to/from Base64 notation on the fly.
0960: *
0961: * @see Base64
0962: * @since 1.3
0963: */
0964: public static class InputStream extends java.io.FilterInputStream {
0965: private boolean encode; // Encoding or decoding
0966: private int position; // Current position in the buffer
0967: private byte[] buffer; // Small buffer holding converted data
0968: private int bufferLength; // Length of buffer (3 or 4)
0969: private int numSigBytes; // Number of meaningful bytes in the buffer
0970: private int lineLength;
0971: private boolean breakLines; // Break lines at less than 80 characters
0972:
0973: /**
0974: * Constructs a {@link Base64.InputStream} in DECODE mode.
0975: *
0976: * @param in the <tt>java.io.InputStream</tt> from which to read data.
0977: * @since 1.3
0978: */
0979: public InputStream(java.io.InputStream in) {
0980: this (in, DECODE);
0981: } // end constructor
0982:
0983: /**
0984: * Constructs a {@link Base64.InputStream} in
0985: * either ENCODE or DECODE mode.
0986: * <p/>
0987: * Valid options:<pre>
0988: * ENCODE or DECODE: Encode or Decode as data is read.
0989: * DONT_BREAK_LINES: don't break lines at 76 characters
0990: * (only meaningful when encoding)
0991: * <i>Note: Technically, this makes your encoding non-compliant.</i>
0992: * </pre>
0993: * <p/>
0994: * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
0995: *
0996: * @param in the <tt>java.io.InputStream</tt> from which to read data.
0997: * @param options Specified options
0998: * @see Base64#ENCODE
0999: * @see Base64#DECODE
1000: * @see Base64#DONT_BREAK_LINES
1001: * @since 2.0
1002: */
1003: public InputStream(java.io.InputStream in, int options) {
1004: super (in);
1005: this .breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
1006: this .encode = (options & ENCODE) == ENCODE;
1007: this .bufferLength = encode ? 4 : 3;
1008: this .buffer = new byte[bufferLength];
1009: this .position = -1;
1010: this .lineLength = 0;
1011: } // end constructor
1012:
1013: /**
1014: * Reads enough of the input stream to convert
1015: * to/from Base64 and returns the next byte.
1016: *
1017: * @return next byte
1018: * @since 1.3
1019: */
1020: public int read() throws java.io.IOException {
1021: // Do we need to get data?
1022: if (position < 0) {
1023: if (encode) {
1024: byte[] b3 = new byte[3];
1025: int numBinaryBytes = 0;
1026: for (int i = 0; i < 3; i++) {
1027: try {
1028: int b = in.read();
1029:
1030: // If end of stream, b is -1.
1031: if (b >= 0) {
1032: b3[i] = (byte) b;
1033: numBinaryBytes++;
1034: } // end if: not end of stream
1035:
1036: } // end try: read
1037: catch (java.io.IOException e) {
1038: // Only a problem if we got no data at all.
1039: if (i == 0) {
1040: throw e;
1041: }
1042:
1043: } // end catch
1044: } // end for: each needed input byte
1045:
1046: if (numBinaryBytes > 0) {
1047: encode3to4(b3, 0, numBinaryBytes, buffer, 0);
1048: position = 0;
1049: numSigBytes = 4;
1050: } // end if: got data
1051: else {
1052: return -1;
1053: } // end else
1054: } // end if: encoding
1055:
1056: // Else decoding
1057: else {
1058: byte[] b4 = new byte[4];
1059: int i = 0;
1060: for (i = 0; i < 4; i++) {
1061: // Read four "meaningful" bytes:
1062: int b = 0;
1063: do {
1064: b = in.read();
1065: } while (b >= 0
1066: && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC);
1067:
1068: if (b < 0) {
1069: break; // Reads a -1 if end of stream
1070: }
1071:
1072: b4[i] = (byte) b;
1073: } // end for: each needed input byte
1074:
1075: if (i == 4) {
1076: numSigBytes = decode4to3(b4, 0, buffer, 0);
1077: position = 0;
1078: } // end if: got four characters
1079: else if (i == 0) {
1080: return -1;
1081: } // end else if: also padded correctly
1082: else {
1083: // Must have broken out from above.
1084: throw new java.io.IOException(
1085: "Improperly padded Base64 input.");
1086: } // end
1087:
1088: } // end else: decode
1089: } // end else: get data
1090:
1091: // Got data?
1092: if (position >= 0) {
1093: // End of relevant data?
1094: if (/*!encode &&*/position >= numSigBytes) {
1095: return -1;
1096: }
1097:
1098: if (encode && breakLines
1099: && lineLength >= MAX_LINE_LENGTH) {
1100: lineLength = 0;
1101: return '\n';
1102: } // end if
1103: else {
1104: lineLength++; // This isn't important when decoding
1105: // but throwing an extra "if" seems
1106: // just as wasteful.
1107:
1108: int b = buffer[position++];
1109:
1110: if (position >= bufferLength) {
1111: position = -1;
1112: }
1113:
1114: return b & 0xFF; // This is how you "cast" a byte that's
1115: // intended to be unsigned.
1116: } // end else
1117: } // end if: position >= 0
1118:
1119: // Else error
1120: else {
1121: // When JDK1.4 is more accepted, use an assertion here.
1122: throw new java.io.IOException(
1123: "Error in Base64 code reading stream.");
1124: } // end else
1125: } // end read
1126:
1127: /**
1128: * Calls {@link #read()} repeatedly until the end of stream
1129: * is reached or <var>len</var> bytes are read.
1130: * Returns number of bytes read into array or -1 if
1131: * end of stream is encountered.
1132: *
1133: * @param dest array to hold values
1134: * @param off offset for array
1135: * @param len max number of bytes to read into array
1136: * @return bytes read into array or -1 if end of stream is encountered.
1137: * @since 1.3
1138: */
1139: public int read(byte[] dest, int off, int len)
1140: throws java.io.IOException {
1141: int i;
1142: int b;
1143: for (i = 0; i < len; i++) {
1144: b = read();
1145:
1146: //if( b < 0 && i == 0 )
1147: // return -1;
1148:
1149: if (b >= 0) {
1150: dest[off + i] = (byte) b;
1151: } else if (i == 0) {
1152: return -1;
1153: } else {
1154: break; // Out of 'for' loop
1155: }
1156: } // end for: each byte read
1157: return i;
1158: } // end read
1159:
1160: } // end inner class InputStream
1161:
1162: /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
1163:
1164: /**
1165: * A {@link Base64.OutputStream} will write data to another
1166: * <tt>java.io.OutputStream</tt>, given in the constructor,
1167: * and encode/decode to/from Base64 notation on the fly.
1168: *
1169: * @see Base64
1170: * @since 1.3
1171: */
1172: public static class OutputStream extends java.io.FilterOutputStream {
1173: private boolean encode;
1174: private int position;
1175: private byte[] buffer;
1176: private int bufferLength;
1177: private int lineLength;
1178: private boolean breakLines;
1179: private byte[] b4; // Scratch used in a few places
1180: private boolean suspendEncoding;
1181:
1182: /**
1183: * Constructs a {@link Base64.OutputStream} in ENCODE mode.
1184: *
1185: * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1186: * @since 1.3
1187: */
1188: public OutputStream(java.io.OutputStream out) {
1189: this (out, ENCODE);
1190: } // end constructor
1191:
1192: /**
1193: * Constructs a {@link Base64.OutputStream} in
1194: * either ENCODE or DECODE mode.
1195: * <p/>
1196: * Valid options:<pre>
1197: * ENCODE or DECODE: Encode or Decode as data is read.
1198: * DONT_BREAK_LINES: don't break lines at 76 characters
1199: * (only meaningful when encoding)
1200: * <i>Note: Technically, this makes your encoding non-compliant.</i>
1201: * </pre>
1202: * <p/>
1203: * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
1204: *
1205: * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1206: * @param options Specified options.
1207: * @see Base64#ENCODE
1208: * @see Base64#DECODE
1209: * @see Base64#DONT_BREAK_LINES
1210: * @since 1.3
1211: */
1212: public OutputStream(java.io.OutputStream out, int options) {
1213: super (out);
1214: this .breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
1215: this .encode = (options & ENCODE) == ENCODE;
1216: this .bufferLength = encode ? 3 : 4;
1217: this .buffer = new byte[bufferLength];
1218: this .position = 0;
1219: this .lineLength = 0;
1220: this .suspendEncoding = false;
1221: this .b4 = new byte[4];
1222: } // end constructor
1223:
1224: /**
1225: * Writes the byte to the output stream after
1226: * converting to/from Base64 notation.
1227: * When encoding, bytes are buffered three
1228: * at a time before the output stream actually
1229: * gets a write() call.
1230: * When decoding, bytes are buffered four
1231: * at a time.
1232: *
1233: * @param theByte the byte to write
1234: * @since 1.3
1235: */
1236: public void write(int theByte) throws java.io.IOException {
1237: // Encoding suspended?
1238: if (suspendEncoding) {
1239: super .out.write(theByte);
1240: return;
1241: } // end if: supsended
1242:
1243: // Encode?
1244: if (encode) {
1245: buffer[position++] = (byte) theByte;
1246: if (position >= bufferLength) // Enough to encode.
1247: {
1248: out.write(encode3to4(b4, buffer, bufferLength));
1249:
1250: lineLength += 4;
1251: if (breakLines && lineLength >= MAX_LINE_LENGTH) {
1252: out.write(NEW_LINE);
1253: lineLength = 0;
1254: } // end if: end of line
1255:
1256: position = 0;
1257: } // end if: enough to output
1258: } // end if: encoding
1259:
1260: // Else, Decoding
1261: else {
1262: // Meaningful Base64 character?
1263: if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) {
1264: buffer[position++] = (byte) theByte;
1265: if (position >= bufferLength) // Enough to output.
1266: {
1267: int len = Base64.decode4to3(buffer, 0, b4, 0);
1268: out.write(b4, 0, len);
1269: //out.write( Base64.decode4to3( buffer ) );
1270: position = 0;
1271: } // end if: enough to output
1272: } // end if: meaningful base64 character
1273: else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) {
1274: throw new java.io.IOException(
1275: "Invalid character in Base64 data.");
1276: } // end else: not white space either
1277: } // end else: decoding
1278: } // end write
1279:
1280: /**
1281: * Calls {@link #write(int)} repeatedly until <var>len</var>
1282: * bytes are written.
1283: *
1284: * @param theBytes array from which to read bytes
1285: * @param off offset for array
1286: * @param len max number of bytes to read into array
1287: * @since 1.3
1288: */
1289: public void write(byte[] theBytes, int off, int len)
1290: throws java.io.IOException {
1291: // Encoding suspended?
1292: if (suspendEncoding) {
1293: super .out.write(theBytes, off, len);
1294: return;
1295: } // end if: supsended
1296:
1297: for (int i = 0; i < len; i++) {
1298: write(theBytes[off + i]);
1299: } // end for: each byte written
1300:
1301: } // end write
1302:
1303: /**
1304: * Method added by PHIL. [Thanks, PHIL. -Rob]
1305: * This pads the buffer without closing the stream.
1306: */
1307: public void flushBase64() throws java.io.IOException {
1308: if (position > 0) {
1309: if (encode) {
1310: out.write(encode3to4(b4, buffer, position));
1311: position = 0;
1312: } // end if: encoding
1313: else {
1314: throw new java.io.IOException(
1315: "Base64 input not properly padded.");
1316: } // end else: decoding
1317: } // end if: buffer partially full
1318:
1319: } // end flush
1320:
1321: /**
1322: * Flushes and closes (I think, in the superclass) the stream.
1323: *
1324: * @since 1.3
1325: */
1326: public void close() throws java.io.IOException {
1327: // 1. Ensure that pending characters are written
1328: flushBase64();
1329:
1330: // 2. Actually close the stream
1331: // Base class both flushes and closes.
1332: super .close();
1333:
1334: buffer = null;
1335: out = null;
1336: } // end close
1337:
1338: /**
1339: * Suspends encoding of the stream.
1340: * May be helpful if you need to embed a piece of
1341: * base640-encoded data in a stream.
1342: *
1343: * @since 1.5.2
1344: */
1345: public void suspendEncoding() throws java.io.IOException {
1346: flushBase64();
1347: this .suspendEncoding = true;
1348: } // end suspendEncoding
1349:
1350: /**
1351: * Resumes encoding of the stream.
1352: * May be helpful if you need to embed a piece of
1353: * base640-encoded data in a stream.
1354: *
1355: * @since 1.5.2
1356: */
1357: public void resumeEncoding() {
1358: this .suspendEncoding = false;
1359: } // end resumeEncoding
1360:
1361: } // end inner class OutputStream
1362:
1363: } // end class Base64
|