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) ;
}
}