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