001: /*
002: * Created on 26 juin 2004
003: */
004: package de.nava.informa.utils;
005:
006: import java.io.IOException;
007: import java.io.FilterInputStream;
008: import java.io.*;
009:
010: /**
011: * A class to decode Base64 streams and strings.
012: * See RFC 1521 section 5.2 for details of the Base64 algorithm.
013: *
014: * This class can be used for decoding strings:
015: *
016: * String encoded = "d2VibWFzdGVyOnRyeTJndWVTUw";
017: * String decoded = Base64Decoder.decode(encoded);
018: *
019: * or for decoding streams:
020: *
021: * InputStream in = new Base64Decoder(System.in);
022: *
023: * @author Jason Hunter, Copyright 2000
024: * @version 1.0, 2000/06/11
025: */
026: class Base64Decoder extends FilterInputStream {
027:
028: private static final char[] chars = { 'A', 'B', 'C', 'D', 'E', 'F',
029: 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
030: 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
031: 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
032: 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1',
033: '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
034:
035: // A mapping between char values and six-bit integers
036: private static final int[] ints = new int[128];
037: static {
038: for (int i = 0; i < 64; i++) {
039: ints[chars[i]] = i;
040: }
041: }
042:
043: private int charCount;
044: private int carryOver;
045:
046: /**
047: * Constructs a new Base64 decoder that reads input from the given
048: * InputStream.
049: *
050: * @param in the input stream
051: */
052: public Base64Decoder(InputStream in) {
053: super (in);
054: }
055:
056: /**
057: * Returns the next decoded character from the stream, or -1 if
058: * end of stream was reached.
059: *
060: * @return the decoded character, or -1 if the end of the
061: * input stream is reached
062: * @exception IOException if an I/O error occurs
063: */
064: public int read() throws IOException {
065: // Read the next non-whitespace character
066: int x;
067: do {
068: x = in.read();
069: if (x == -1) {
070: return -1;
071: }
072: } while (Character.isWhitespace((char) x));
073: charCount++;
074:
075: // The '=' sign is just padding
076: if (x == '=') {
077: return -1; // effective end of stream
078: }
079:
080: // Convert from raw form to 6-bit form
081: x = ints[x];
082:
083: // Calculate which character we're decoding now
084: int mode = (charCount - 1) % 4;
085:
086: // First char save all six bits, go for another
087: if (mode == 0) {
088: carryOver = x & 63;
089: return read();
090: }
091: // Second char use previous six bits and first two new bits,
092: // save last four bits
093: else if (mode == 1) {
094: int decoded = ((carryOver << 2) + (x >> 4)) & 255;
095: carryOver = x & 15;
096: return decoded;
097: }
098: // Third char use previous four bits and first four new bits,
099: // save last two bits
100: else if (mode == 2) {
101: int decoded = ((carryOver << 4) + (x >> 2)) & 255;
102: carryOver = x & 3;
103: return decoded;
104: }
105: // Fourth char use previous two bits and all six new bits
106: else if (mode == 3) {
107: int decoded = ((carryOver << 6) + x) & 255;
108: return decoded;
109: }
110: return -1; // can't actually reach this line
111: }
112:
113: /**
114: * Reads decoded data into an array of bytes and returns the actual
115: * number of bytes read, or -1 if end of stream was reached.
116: *
117: * @param b the buffer into which the data is read
118: * @param off the start offset of the data
119: * @param len the maximum number of bytes to read
120: * @return the actual number of bytes read, or -1 if the end of the
121: * input stream is reached
122: * @exception IOException if an I/O error occurs
123: */
124: public int read(byte[] b, int off, int len) throws IOException {
125: // This could of course be optimized
126: int i;
127: for (i = 0; i < len; i++) {
128: int x = read();
129: if (x == -1 && i == 0) { // an immediate -1 returns -1
130: return -1;
131: } else if (x == -1) { // a later -1 returns the chars read so far
132: break;
133: }
134: b[off + i] = (byte) x;
135: }
136: return i;
137: }
138:
139: /**
140: * Returns the decoded form of the given encoded string.
141: *
142: * @param encoded the string to decode
143: * @return the decoded form of the encoded string
144: */
145: public static String decode(String encoded) {
146: byte[] bytes = null;
147: try {
148: bytes = encoded.getBytes("8859_1");
149: } catch (UnsupportedEncodingException ignored) {
150: }
151:
152: Base64Decoder bin = new Base64Decoder(new ByteArrayInputStream(
153: bytes));
154:
155: ByteArrayOutputStream out = new ByteArrayOutputStream(
156: (int) (bytes.length * 0.67));
157:
158: try {
159: byte[] buf = new byte[4 * 1024]; // 4K buffer
160: int bytesRead;
161: while ((bytesRead = bin.read(buf)) != -1) {
162: out.write(buf, 0, bytesRead);
163: }
164: out.close();
165:
166: return out.toString("8859_1");
167: } catch (IOException ignored) {
168: return null;
169: }
170: }
171:
172: /*
173: public static void main(String[] args) throws Exception {
174: if (args.length != 1) {
175: System.err.println("Usage: java Base64Decoder fileToDecode");
176: }
177:
178: Base64Decoder decoder = null;
179: try {
180: decoder = new Base64Decoder(
181: new BufferedInputStream(
182: new FileInputStream(args[0])));
183: byte[] buf = new byte[4 * 1024]; // 4K buffer
184: int bytesRead;
185: while ((bytesRead = decoder.read(buf)) != -1) {
186: System.out.write(buf, 0, bytesRead);
187: }
188: }
189: finally {
190: if (decoder != null) decoder.close();
191: }
192: }
193: */
194: }
|