/* * Copyright � World Wide Web Consortium, (Massachusetts Institute of Technology, * Institut National de Recherche en Informatique et en Automatique, Keio University). * All Rights Reserved. http://www.w3.org/Consortium/Legal/ */ package org.w3c.tools.codec; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; /** * Decode a BASE64 encoded input stream to some output stream. * This class implements BASE64 decoding, as specified in the * <a href="http://ds.internic.net/rfc/rfc1521.txt">MIME specification</a>. * @see org.w3c.tools.codec.Base64Encoder */ public class Base64Decoder { private static final int BUFFER_SIZE = 1024; InputStream in = null; OutputStream out = null; boolean stringp = false; private void printHex(int x) { int h = (x & 0xf0) >> 4; int l = (x & 0x0f); System.out.print( (new Character((char) ((h > 9) ? 'A' + h - 10 : '0' + h))) .toString() + (new Character((char) ((l > 9) ? 'A' + l - 10 : '0' + l))) .toString()); } private void printHex(byte buf[], int off, int len) { while (off < len) { printHex(buf[off++]); System.out.print(" "); } System.out.println(""); } private void printHex(String s) { byte bytes[]; try { bytes = s.getBytes("ISO-8859-1"); } catch (UnsupportedEncodingException ex) { throw new RuntimeException( this.getClass().getName() + "[printHex] Unable to convert" + "properly char to bytes"); } printHex(bytes, 0, bytes.length); } private final int get1(byte buf[], int off) { return ((buf[off] & 0x3f) << 2) | ((buf[off + 1] & 0x30) >>> 4); } private final int get2(byte buf[], int off) { return ((buf[off + 1] & 0x0f) << 4) | ((buf[off + 2] & 0x3c) >>> 2); } private final int get3(byte buf[], int off) { return ((buf[off + 2] & 0x03) << 6) | (buf[off + 3] & 0x3f); } private final int check(int ch) { if ((ch >= 'A') && (ch <= 'Z')) { return ch - 'A'; } else if ((ch >= 'a') && (ch <= 'z')) { return ch - 'a' + 26; } else if ((ch >= '0') && (ch <= '9')) { return ch - '0' + 52; } else { switch (ch) { case '=' : return 65; case '+' : return 62; case '/' : return 63; default : return -1; } } } /** * Do the actual decoding. * Process the input stream by decoding it and emiting the resulting bytes * into the output stream. * @exception IOException If the input or output stream accesses failed. * @exception Base64FormatException If the input stream is not compliant * with the BASE64 specification. */ public void process() throws IOException, Base64FormatException { byte buffer[] = new byte[BUFFER_SIZE]; byte chunk[] = new byte[4]; int got = -1; int ready = 0; fill : while ((got = in.read(buffer)) > 0) { int skiped = 0; while (skiped < got) { // Check for un-understood characters: while (ready < 4) { if (skiped >= got) { continue fill; } int ch = check(buffer[skiped++]); if (ch >= 0) { chunk[ready++] = (byte) ch; } } if (chunk[2] == 65) { out.write(get1(chunk, 0)); return; } else if (chunk[3] == 65) { out.write(get1(chunk, 0)); out.write(get2(chunk, 0)); return; } else { out.write(get1(chunk, 0)); out.write(get2(chunk, 0)); out.write(get3(chunk, 0)); } ready = 0; } } if (ready != 0) { throw new Base64FormatException("Invalid length."); } out.flush(); } /** * Do the decoding, and return a String. * This methods should be called when the decoder is used in * <em>String</em> mode. It decodes the input string to an output string * that is returned. * @exception RuntimeException If the object wasn't constructed to * decode a String. * @exception Base64FormatException If the input string is not compliant * with the BASE64 specification. */ public String processString() throws Base64FormatException { if (!stringp) { throw new RuntimeException( this.getClass().getName() + "[processString]" + "invalid call (not a String)"); } try { process(); } catch (IOException e) { } String s; try { s = ((ByteArrayOutputStream) out).toString("ISO-8859-1"); } catch (UnsupportedEncodingException ex) { throw new RuntimeException( this.getClass().getName() + "[processString] Unable to convert" + "properly char to bytes"); } return s; } /** * Create a decoder to decode a String. * @param input The string to be decoded. */ public Base64Decoder(String input) { byte bytes[]; try { bytes = input.getBytes("ISO-8859-1"); } catch (UnsupportedEncodingException ex) { throw new RuntimeException( this.getClass().getName() + "[Constructor] Unable to convert" + "properly char to bytes"); } this.stringp = true; this.in = new ByteArrayInputStream(bytes); this.out = new ByteArrayOutputStream(); } /** * Create a decoder to decode a stream. * @param in The input stream (to be decoded). * @param out The output stream, to write decoded data to. */ public Base64Decoder(InputStream in, OutputStream out) { this.in = in; this.out = out; this.stringp = false; } /** * Test the decoder. * Run it with one argument: the string to be decoded, it will print out * the decoded value. */ public static void main(String args[]) { if (args.length == 1) { try { Base64Decoder b = new Base64Decoder(args[0]); System.out.println("[" + b.processString() + "]"); } catch (Base64FormatException e) { System.out.println("Invalid Base64 format !"); System.exit(1); } } else if ((args.length == 2) && (args[0].equals("-f"))) { try { FileInputStream in = new FileInputStream(args[1]); Base64Decoder b = new Base64Decoder(in, System.out); b.process(); } catch (Exception ex) { System.out.println("error: " + ex.getMessage()); System.exit(1); } } else { System.out.println("Base64Decoder [strong] [-f file]"); } System.exit(0); } }