001: /*
002: * Copyright © World Wide Web Consortium, (Massachusetts Institute of Technology,
003: * Institut National de Recherche en Informatique et en Automatique, Keio University).
004: * All Rights Reserved. http://www.w3.org/Consortium/Legal/
005: */
006: package org.w3c.tools.codec;
007:
008: import java.io.ByteArrayInputStream;
009: import java.io.ByteArrayOutputStream;
010: import java.io.FileInputStream;
011: import java.io.IOException;
012: import java.io.InputStream;
013: import java.io.OutputStream;
014: import java.io.UnsupportedEncodingException;
015:
016: /**
017: * Decode a BASE64 encoded input stream to some output stream.
018: * This class implements BASE64 decoding, as specified in the
019: * <a href="http://ds.internic.net/rfc/rfc1521.txt">MIME specification</a>.
020: * @see org.w3c.tools.codec.Base64Encoder
021: */
022: public class Base64Decoder {
023: private static final int BUFFER_SIZE = 1024;
024:
025: InputStream in = null;
026: OutputStream out = null;
027: boolean stringp = false;
028:
029: private void printHex(int x) {
030: int h = (x & 0xf0) >> 4;
031: int l = (x & 0x0f);
032: System.out.print((new Character((char) ((h > 9) ? 'A' + h - 10
033: : '0' + h))).toString()
034: + (new Character((char) ((l > 9) ? 'A' + l - 10
035: : '0' + l))).toString());
036: }
037:
038: private void printHex(byte buf[], int off, int len) {
039: while (off < len) {
040: printHex(buf[off++]);
041: System.out.print(" ");
042: }
043: System.out.println("");
044: }
045:
046: private void printHex(String s) {
047: byte bytes[];
048: try {
049: bytes = s.getBytes("ISO-8859-1");
050: } catch (UnsupportedEncodingException ex) {
051: throw new RuntimeException(this .getClass().getName()
052: + "[printHex] Unable to convert"
053: + "properly char to bytes");
054: }
055: printHex(bytes, 0, bytes.length);
056: }
057:
058: private final int get1(byte buf[], int off) {
059: return ((buf[off] & 0x3f) << 2) | ((buf[off + 1] & 0x30) >>> 4);
060: }
061:
062: private final int get2(byte buf[], int off) {
063: return ((buf[off + 1] & 0x0f) << 4)
064: | ((buf[off + 2] & 0x3c) >>> 2);
065: }
066:
067: private final int get3(byte buf[], int off) {
068: return ((buf[off + 2] & 0x03) << 6) | (buf[off + 3] & 0x3f);
069: }
070:
071: private final int check(int ch) {
072: if ((ch >= 'A') && (ch <= 'Z')) {
073: return ch - 'A';
074: } else if ((ch >= 'a') && (ch <= 'z')) {
075: return ch - 'a' + 26;
076: } else if ((ch >= '0') && (ch <= '9')) {
077: return ch - '0' + 52;
078: } else {
079: switch (ch) {
080: case '=':
081: return 65;
082: case '+':
083: return 62;
084: case '/':
085: return 63;
086: default:
087: return -1;
088: }
089: }
090: }
091:
092: /**
093: * Do the actual decoding.
094: * Process the input stream by decoding it and emiting the resulting bytes
095: * into the output stream.
096: * @exception IOException If the input or output stream accesses failed.
097: * @exception Base64FormatException If the input stream is not compliant
098: * with the BASE64 specification.
099: */
100: public void process() throws IOException, Base64FormatException {
101: byte buffer[] = new byte[BUFFER_SIZE];
102: byte chunk[] = new byte[4];
103: int got = -1;
104: int ready = 0;
105:
106: fill: while ((got = in.read(buffer)) > 0) {
107: int skiped = 0;
108: while (skiped < got) {
109: // Check for un-understood characters:
110: while (ready < 4) {
111: if (skiped >= got)
112: continue fill;
113: int ch = check(buffer[skiped++]);
114: if (ch >= 0)
115: chunk[ready++] = (byte) ch;
116: }
117: if (chunk[2] == 65) {
118: out.write(get1(chunk, 0));
119: return;
120: } else if (chunk[3] == 65) {
121: out.write(get1(chunk, 0));
122: out.write(get2(chunk, 0));
123: return;
124: } else {
125: out.write(get1(chunk, 0));
126: out.write(get2(chunk, 0));
127: out.write(get3(chunk, 0));
128: }
129: ready = 0;
130: }
131: }
132: if (ready != 0)
133: throw new Base64FormatException("Invalid length.");
134: out.flush();
135: }
136:
137: /**
138: * Do the decoding, and return a String.
139: * This methods should be called when the decoder is used in
140: * <em>String</em> mode. It decodes the input string to an output string
141: * that is returned.
142: * @exception RuntimeException If the object wasn't constructed to
143: * decode a String.
144: * @exception Base64FormatException If the input string is not compliant
145: * with the BASE64 specification.
146: */
147: public String processString() throws Base64FormatException {
148: if (!stringp)
149: throw new RuntimeException(this .getClass().getName()
150: + "[processString]" + "invalid call (not a String)");
151: try {
152: process();
153: } catch (IOException e) {
154: }
155: String s;
156: try {
157: s = ((ByteArrayOutputStream) out).toString("ISO-8859-1");
158: } catch (UnsupportedEncodingException ex) {
159: throw new RuntimeException(this .getClass().getName()
160: + "[processString] Unable to convert"
161: + "properly char to bytes");
162: }
163: return s;
164: }
165:
166: /**
167: * Create a decoder to decode a String.
168: * @param input The string to be decoded.
169: */
170: public Base64Decoder(String input) {
171: byte bytes[];
172: try {
173: bytes = input.getBytes("ISO-8859-1");
174: } catch (UnsupportedEncodingException ex) {
175: throw new RuntimeException(this .getClass().getName()
176: + "[Constructor] Unable to convert"
177: + "properly char to bytes");
178: }
179: this .stringp = true;
180: this .in = new ByteArrayInputStream(bytes);
181: this .out = new ByteArrayOutputStream();
182: }
183:
184: /**
185: * Create a decoder to decode a stream.
186: * @param in The input stream (to be decoded).
187: * @param out The output stream, to write decoded data to.
188: */
189: public Base64Decoder(InputStream in, OutputStream out) {
190: this .in = in;
191: this .out = out;
192: this .stringp = false;
193: }
194:
195: /**
196: * Test the decoder.
197: * Run it with one argument: the string to be decoded, it will print out
198: * the decoded value.
199: */
200: public static void main(String args[]) {
201: if (args.length == 1) {
202: try {
203: Base64Decoder b = new Base64Decoder(args[0]);
204: System.out.println("[" + b.processString() + "]");
205: } catch (Base64FormatException e) {
206: System.out.println("Invalid Base64 format !");
207: System.exit(1);
208: }
209: } else if ((args.length == 2) && (args[0].equals("-f"))) {
210: try {
211: FileInputStream in = new FileInputStream(args[1]);
212: Base64Decoder b = new Base64Decoder(in, System.out);
213: b.process();
214: } catch (Exception ex) {
215: System.out.println("error: " + ex.getMessage());
216: System.exit(1);
217: }
218: } else {
219: System.out.println("Base64Decoder [strong] [-f file]");
220: }
221: System.exit(0);
222: }
223: }
|