//============================================================================== // Base64DecoderOutputStream.java //============================================================================== package tribble.io; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.IllegalArgumentException; import java.lang.IndexOutOfBoundsException; import java.lang.NullPointerException; import java.lang.String; import java.text.ParseException; import tribble.util.Base64Decoder; /******************************************************************************* * Base-64 decoding output stream. * *

* Provides an I/O stream that decodes base-64 characters into binary data. * Printable 8-bit characters that encode data as base-64 (a.k.a. radix-64) ASCII * characters are converted into 8-bit bytes (octets) as they are written to the * output stream. * *

* See {@link tribble.util.Base64Decoder} for more information about base-64 * decoding. * * *

*
Source code:
*
* http://david.tribble.com/src/java/tribble/io/Base64DecoderOutputStream.java *
*
Documentation:
*
* http://david.tribble.com/docs/tribble/io/Base64DecoderOutputStream.html *
*
* * * @version $Revision: 1.6 $ $Date: 2009/02/04 03:37:07 $ * @since 2005-04-08 * @author David R. Tribble (david@tribble.com) *

* Copyright ©2005-2009 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those designated by * by the United States Department of State as a terrorist, or terrorist * government or agency, to use and distribute this source code provided * that the original copyright notice remains present and unaltered. * * @see Base64DecoderInputStream * @see Base64EncoderOutputStream * @see tribble.util.Base64Decoder */ public class Base64DecoderOutputStream extends java.io.FilterOutputStream { static final String REV = "@(#)tribble/io/Base64DecoderOutputStream.java $Revision: 1.6 $ $Date: 2009/02/04 03:37:07 $\n"; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Static methods /*************************************************************************** * Test driver. * Read one or more input files and write them in base-64 output form. * *

* Usage *

* * java tribble.io.Base64DecoderOutputStream * [-o outfile] file... * * * @param args * Command line arguments. * * @throws IOException * Thrown if an I/O (write) error occurs. * * @since 1.1, 2005-04-04 */ public static void main(String[] args) throws IOException { Base64DecoderOutputStream out; String outFname = "-"; OutputStream os; int rc = 0; int i; // Parse command options for (i = 0; i < args.length && args[i].charAt(0) == '-'; i++) { if (args[i].equals("-o")) outFname = args[++i]; else if (args[i].equals("-")) break; else throw new IOException("Bad option: '" + args[i] + "'"); } // Check args if (i >= args.length) { // Show command usage System.out.println("Read input files and write them in base-64 " + "output form."); System.out.println(); System.out.println("usage: java " + Base64DecoderOutputStream.class.getName() + " [-o outfile] file..."); System.exit(255); } // Set up the output stream if (outFname.equals("-")) os = new BufferedOutputStream(System.out); else os = new BufferedOutputStream(new FileOutputStream(outFname)); out = new Base64DecoderOutputStream(os); // Read one or more input files for ( ; i < args.length; i++) { String inFname = null; try { InputStream in; // Read a named input file inFname = args[i]; if (inFname.equals("-")) in = System.in; else in = new FileInputStream(inFname); // Read and convert the input file from base-64 characters System.err.println(inFname); out.decode(in); // Clean up if (!inFname.equals("-")) in.close(); } catch (IOException ex) { System.err.println("Can't convert: " + inFname); System.err.println(ex.getMessage()); rc = 1; } } // Clean up out.finish(); out.flush(); if (!outFname.equals("-")) out.close(); System.exit(rc); } /*************************************************************************** * Read and convert the contents of an input stream. * This method is equivalent to the call: * {@link #decode(InputStream, OutputStream, int) decode}(in, out, 0). * * @param in * An input stream containing base-64 encoded character data to be converted * into binary 8-bit octet data. * * @param out * An output stream to which is written the decoded binary 8-bit octet data * bytes. * * @throws IOException * Thrown if an I/O (read or write) error occurs. * * @throws NullPointerException (unchecked) * Thrown if in or out is null. * * @see #decode(InputStream) decode() * * @since 1.6, 2009-02-03 */ public static void decode(InputStream in, OutputStream out) throws IOException { // Read and convert base-64 characters from the input stream, writing // the converted 8-bit octets to the output stream decode(in, out, 0); } /*************************************************************************** * Read and convert the contents of an input stream. * *

* The entire contents of an input stream are read as base-64 encoded * characters and converted into 8-bit octets, which are then written to an * output stream. * *

* Note that the output stream is finished and flushed after the converted * data is written, but is not closed, i.e., methods {@link #finish finish()} * and {@link #flush flush()} are called but {@link #close close()} is not. * * @param in * An input stream containing base-64 encoded character data to be converted * into binary 8-bit octet data. * * @param out * An output stream to which is written the decoded binary 8-bit octet data * bytes. * * @param bufLen * Specifies the size, in bytes, of the internal buffer to use for reading * data from the input stream. Specifying a value less than 4 causes a * default size of 64K (65,536) to be used. * * @throws IOException * Thrown if an I/O (read or write) error occurs. * * @throws NullPointerException (unchecked) * Thrown if in or out is null. * * @see #decode(InputStream, int) decode() * * @since 1.6, 2009-02-03 */ public static void decode(InputStream in, OutputStream out, int bufLen) throws IOException { Base64DecoderOutputStream dec; // Sanity check if (out == null) throw new NullPointerException(); // Read and convert base-64 characters from the input stream, writing // the converted 8-bit octets to the output stream dec = new Base64DecoderOutputStream(out); dec.decode(in, bufLen); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Variables /** Pending input queue, composed of groups of 8-bit octets. */ protected byte[] m_inBuf = new byte[4*16]; /** Output queue, composed of groups of 6-bit sextets. */ protected byte[] m_outBuf = new byte[3*16]; /** Pending input queue length. */ protected short m_inLen; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors /*************************************************************************** * Constructor. * * @param out * The underlying output stream for this output stream. * * @throws NullPointerException (unchecked) * Thrown if out is null. * * @since 1.1, 2005-04-04 */ public Base64DecoderOutputStream(OutputStream out) { // Establish the underlying output stream super(out); // Sanity check if (out == null) throw new NullPointerException(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Methods /*************************************************************************** * Complete all pending output to this output stream. * *

* All pending base-64 character output is completed. * * @throws IOException * Thrown if an I/O (write) error occurs. * * @throws NullPointerException (unchecked) * Thrown if the underlying output stream is closed. * * @see #flush * * @since 1.4, 2005-07-18 */ public void finish() throws IOException { // Sanity check if (this.out == null) throw new NullPointerException(); // Convert and write all pending base-64 characters to the output if (m_inLen > 0) { int ilen; // Append padding characters to the final partial 6-bit sextet group ilen = (m_inLen + 4-1)/4 * 4; while (m_inLen < ilen) m_inBuf[m_inLen++] = '='; // Convert and write all pending 6-bit sextets as 8-bit octets writePending(); } // Note: Do not flush or close the underlying output stream } /*************************************************************************** * Flush all pending output to this output stream. * *

* Any pending base-64 character output is written to the underlying output * stream, then the underlying output stream itself is flushed. * *

* Note that output characters may still remain pending after this operation * if the last bytes written to this stream do not constitute a complete * 24-bit octet group. Use the {@link #finish finish()} method to force * completion of the output stream. * * @throws IOException * Thrown if an I/O (write) error occurs. * * @throws NullPointerException (unchecked) * Thrown if the underlying output stream is closed. * * @see #finish * * @since 1.1, 2005-04-04 */ public void flush() throws IOException //overrides java.io.OutputStream { // Sanity check if (this.out == null) throw new NullPointerException(); // Convert and write pending base-64 characters to the output if (m_inLen > 0) { // Convert and write pending complete 6-bit sextets as 8-bit octets writePending(); } // Flush the underlying output stream this.out.flush(); } /*************************************************************************** * Flush and close this output stream. * *

* Any pending base-64 character output is completed, and the final padding * characters (if any) are written to the underlying output stream. * *

* Subsequent attempts to write to this output stream will cause exceptions * to be thrown. * * @throws IOException * Thrown if an I/O (write) error occurs. * * @see #finish * @see #flush * * @since 1.1, 2005-04-04 */ public void close() throws IOException //overrides java.io.OutputStream { // Sanity check if (this.out == null) return; // Flush all pending output to the underlying output stream finish(); flush(); // Clean up m_inBuf = null; m_outBuf = null; // Close and disassociate the underlying output stream this.out.close(); this.out = null; } /*************************************************************************** * Write a byte to this output stream. * *

* The 8-bit byte is converted into its corresponding base-64 character * encoding before being written to the underlying output stream. * * @param c * A character code. * Note that only the lower 8 bits are written to the underlying output * stream, and the upper 24 bits are ignored. * * @throws IOException * Thrown if an I/O (write) error occurs. * * @throws NullPointerException (unchecked) * Thrown if the underlying output stream is closed. * * @since 1.1, 2005-04-04 */ public void write(int c) throws IOException //overrides java.io.OutputStream { // Sanity check if (this.out == null) throw new NullPointerException(); // Ignore noise characters if (c < '+' || c > 'z') return; // Append the output byte to the output queue m_inBuf[m_inLen++] = (byte) c; if (m_inLen >= 4*16) { // Convert and write four 6-bit sextets as three 8-bit octets writePending(); } } /*************************************************************************** * Write a set of bytes to this output stream. * *

* The 8-bit bytes are converted into their corresponding base-64 character * encoding before being written to the underlying output stream. * * @param data * An array of bytes to write. * * @param off * Index of the first byte within data to write. * * @param len * Number of bytes from data to write. * * @throws IOException * Thrown if an I/O (write) error occurs. * * @throws NullPointerException (unchecked) * Thrown if the underlying output stream is closed. * * @throws IndexOutOfBoundsException (unchecked) * Thrown if off or len specify a negative offset or * length. * * @since 1.1, 2005-04-04 */ public void write(byte[] data, int off, int len) throws IOException //overrides java.io.OutputStream { // Sanity checks if (this.out == null) throw new NullPointerException(); if ((off | len | (off + len)) < 0) throw new IndexOutOfBoundsException(); if (data.length - (off + len) < 0) throw new IndexOutOfBoundsException(); // Append the output bytes to the output queue len += off; for (int i = off; i < len; i++) { int c; // Ignore noise characters c = data[i] & 0xFF; if (c < '+' || c > 'z') continue; // Append the character byte to the output queue m_inBuf[m_inLen++] = (byte) c; if (m_inLen >= 4*16) { // Convert and write four 6-bit sextets as three 8-bit octets writePending(); } } } /*************************************************************************** * Retrieve the underlying output stream for this output stream. * *

* Note that this method is not specified by the standard * {@link java.io.FilterOutputStream} library class, but is provided as a * convenient enhancement. * * @return * The underlying output stream for this stream, or null if it has been * closed. * * @since 1.1, 2005-04-04 */ public OutputStream getOutput() { // Get the output stream return this.out; } /*************************************************************************** * Read and convert the contents of an input stream. * This method is equivalent to the call: * {@link #decode(InputStream, int) decode}(in, 0). * * @param in * An input stream containing base-64 encoded character data to be converted * into binary 8-bit octet data. * * @throws IOException * Thrown if an I/O (read or write) error occurs. * * @throws NullPointerException (unchecked) * Thrown if in is null. * * @see #decode(InputStream, int) decode() * * @since 1.3, 2005-04-16 */ public void decode(InputStream in) throws IOException { // Read and convert base-64 characters from the input stream decode(in, 0); } /*************************************************************************** * Read and convert the contents of an input stream. * *

* The entire contents of an input stream are read as base-64 encoded * characters and converted into 8-bit octets, which are then written to this * output stream. * *

* Note that the output stream is finished and flushed after the converted * data is written, but is not closed, i.e., methods {@link #finish finish()} * and {@link #flush flush()} are called but {@link #close close()} is not. * * @param in * An input stream containing base-64 encoded character data to be converted * into binary 8-bit octet data. * * @param bufLen * Specifies the size, in bytes, of the internal buffer to use for reading * data from the input stream. Specifying a value less than 4 causes a * default size of 64K (65,536) to be used. * * @throws IOException * Thrown if an I/O (read or write) error occurs. * * @throws NullPointerException (unchecked) * Thrown if in is null. * * @since 1.3, 2005-04-16 */ public void decode(InputStream in, int bufLen) throws IOException { byte[] buf; // Sanity check if (in == null) throw new NullPointerException(); // Allocate an input buffer if (bufLen < 4) bufLen = 64*1024; buf = new byte[bufLen]; // Read and convert the base-64 input stream into octets for (;;) { int len; // Read and convert a block of base-64 input characters len = in.read(buf); if (len < 0) break; // Write the converted bytes to the output stream write(buf, 0, len); } // Clean up finish(); flush(); } /*************************************************************************** * Convert pending input data bytes into base-64 character bytes and write * them to the output stream. * * @throws IOException * Thrown if an I/O (write) error occurs, or if a base-64 character * conversion error occurs. * * @since 1.1, 2005-04-04 */ protected void writePending() throws IOException { int ilen; int n; // Convert complete groups of four 6-bit sextets into three 8-bit octets ilen = m_inLen/4 * 4; n = 0; if (ilen > 0) { try { // Convert complete groups of 6-bit sextets into 8-bit octets n = Base64Decoder.decodeBytes(m_inBuf, 0, ilen, m_outBuf, 0); // Write the converted sextets this.out.write(m_outBuf, 0, n); } catch (ParseException ex) { String msg; IOException ex2; // Base-64 conversion error, fail msg = ex.getMessage(); if (msg == null) msg = "Bad base-64 decoding"; ex2 = new IOException(msg); //ex2.initCause(ex); // JRE 1.4+ throw (ex2); } } // Save the unconverted characters of a partial 6-bit sextet group if (ilen > m_inLen && ilen > 4) { n = m_inLen; m_inLen = 0; while (n < ilen) m_inBuf[m_inLen++] = m_inBuf[n++]; } else m_inLen = 0; } } // End Base64DecoderOutputStream.java