package org.w3c.tools.codec; // Base64Decoder.java // $Id: Base64Decoder.java,v 1.6 2000/08/16 21:37:48 ylafon Exp $ // (c) COPYRIGHT MIT and INRIA, 1996. // Please first read the full copyright statement in file COPYRIGHT.html 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.PrintStream; 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>. * Extended to suppress padding and use Url encoding by D. Burt, Cablelabs Inc, Jun 2008 * @see org.w3c.tools.codec.Base64Encoder */ public class Base64UrlDecoder { private static final int BUFFER_SIZE = 1024 ; InputStream in = null ; OutputStream out = null ; boolean stringp = false ; //If true, use Base64URL encoding vs Base64 encoding as defined in RFC 3548 boolean encodeUrl = false; //If true, imply trailing padding boolean dropPadding = 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 { int val = 0; switch (ch) { case '=': return 65 ; case '+': val = (encodeUrl) ? -1 : 62; return val ; case '/': val = (encodeUrl) ? -1 : 63; return val ; case '-': val = (encodeUrl) ? 62 : -1; return val ; case '_': val = (encodeUrl) ? 63 : -1; return val ; 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 (dropPadding) { switch (ready) { case 0: break; case 1: out.write(get1(chunk, 0)) ; break; case 2: out.write(get1(chunk, 0)) ; out.write(get2(chunk, 0)) ; break; default: throw new Base64FormatException ("Invalid length.") ; } } else { 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 Base64UrlDecoder (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 Base64UrlDecoder (InputStream in, OutputStream out) { this.in = in ; this.out = out ; this.stringp = false ; } //Sets public void setEncodeUrl(boolean flag) { encodeUrl = flag; } public void setDropPadding(boolean flag) { dropPadding = flag; } //Gets public boolean isEncodeUrl() { return encodeUrl; } public boolean isDropPadding() { return dropPadding; } /** * 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[]) { boolean fileForm = false; boolean padding = false; boolean url = false; String target = null; if ( args.length == 0 ) { System.out.println("Base64UrlDecoder[-f[pu] file | [-pu] string"); System.exit(1); } else if (args.length == 1) { target = args[0]; } else if (args.length == 2) { target = args[1]; if (args[0].contains("f")) { fileForm = true; } if (args[0].contains("p")) { padding = true; } if (args[0].contains("u")) { url = true; } } if (fileForm) { try { FileInputStream in = new FileInputStream(target) ; Base64UrlDecoder b = new Base64UrlDecoder (in, System.out); if (padding) b.setDropPadding(true); if (url) b.setEncodeUrl(true); b.process(); } catch (Exception ex) { System.out.println("error: " + ex.getMessage()); System.exit(1) ; } } else { try { Base64UrlDecoder b = new Base64UrlDecoder (target) ; if (padding) b.setDropPadding(true); if (url) b.setEncodeUrl(true); System.out.println ("["+b.processString()+"]") ; } catch (Base64FormatException e) { System.out.println ("Invalid Base64 format !") ; System.exit (1) ; } } System.exit (0) ; } }