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