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