-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Thursday 29 November 2001 09:46 am, Richard Emberson wrote: > if sun.misc.BASE64Encoder is not in the classpath, then > this code fails during class loading, not during execution.
I wrote my own Base64 encoder/decoder Input/OutputStreams for a personal project a while back. They have not been tested thoroughly, and I don't have test cases at the moment, but they seem to work. I've attached their source files. The sources used to be GPL'd, but they were part of a project that never got released (an XML serialization/deserialization app call "Xerial", similar to JSX), so considering that I wrote them there shouldn't be any problems. I removed the GPL license from the tops of the files. Let met know if you are interested in using these as a replacement to Sun's Base64 encoder. Feel free to change the packages to org.apache.something if you do. I would love to be able to contribute something to this great project. - -- Furthermore, I believe bacon prevents hair loss. Peter Davis -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE8BoTB/kafCzGPRfMRAgm7AJ9kxz2BIn+lyodkvvtP6ATy1q5lWwCeP0kE 69OAfEhRMPe2mEWFz7g6KJU= =vFth -----END PGP SIGNATURE-----
/** * Copyright 2001 Peter Davis. **/ package cx.pdavis.xerial; import java.io.IOException; /** * Thrown when <tt>Base64InputStream</tt> tries to read input that is * not padded to a multiple of four ASCII sextets. * * @see Base64InputStream * @see <a href="http://www.faqs.org/rfcs/rfc1521.html">RFC 1521, * section 5.2"</a> **/ public class IllegalBase64PaddingException extends IOException { /** * Constructs a new IllegalBase64PaddingException with the given * detail message. * * @param msg the detail message **/ public IllegalBase64PaddingException(String msg) { super(msg); } }
/** * Copyright 2001 Peter Davis. **/ package cx.pdavis.xerial; import java.io.IOException; /** * Thrown when <tt>Base64InputStream</tt> tries to read a quadruplet * that contains illegal characters. Illegal characters are those not * in the ranges A-Z, a-z, 0-9, '+', or '/', an '=' character that is * not in the last two characters of the quadruplet, or an '=' * character not followed by another '=' character. * * @see Base64InputStream * @see <a href="http://www.faqs.org/rfcs/rfc1521.html">RFC 1521, * section 5.2"</a> **/ public class IllegalBase64QuadrupletException extends IOException { /** * Constructs a new IllegalBase64QuadrupletException with the * given detail message. * * @param msg the detail message **/ public IllegalBase64QuadrupletException(String quadruplet) { super(quadruplet); } }
/** * Copyright 2001 Peter Davis. **/ package cx.pdavis.xerial; import java.io.OutputStream; import java.io.Writer; import java.io.OutputStreamWriter; import java.io.IOException; /** * Encodes binary bytes into an ASCII compatible format, as specified * by <a href="http://www.faqs.org/rfcs/rfc1521.html">RFC 1521, * section 5.2</a>. This encoding is known as "Base64", because each * character in the encoded output represents one of 64 possible * values (6 bits). However, each character still ocupies an entire * byte (8 bits), which means that encoded data is consistantly about * 33% larger than unencoded data. * * <p>During encoding, each set of three byte octets (24 bits) is * converted into four sextets. Each sextet is then mapped to an * ASCII value in the range of A-Z, a-z, 0-9, '+', or '/'. If the * length of the input is not a multiple of three bytes, then the * output will be padded with one or two '=' characters so that the * length of the output is always a multiple of four sextets. * * <p>Users of this class should note that, in order to maintain full * compliance with RFC 1521, [EMAIL PROTECTED] #flush} should only be invoked * once per instance of <tt>Base64OutputStream</tt>. * * <p>Some implementations of Base64 encoders use ASCII characters * other than those in the ranges given above in an attempt to allow * encoded data to be compatible with the range of characters allowed * in URLEncoded data. The output of </tt>Base64OutputStream</tt> is * not compatible with those implementations. * * @see Base64InputStream * @see java.net.URLEncoder * @see <a href="http://www.faqs.org/rfcs/rfc1521.html">RFC 1521, * section 5.2"</a> **/ public class Base64OutputStream extends OutputStream { private final Writer out; private boolean closed; /** * Data written to this <tt>Base64OutputStream</tt> will be * encoded and written to the given <tt>OutputStream</tt>. * * @param os the OutputStream to which encoded data will be * written **/ public Base64OutputStream(OutputStream os) { this.out = new OutputStreamWriter(os); this.outBuffer = new char[outBufferLength = 1024]; } /** * Data written to this <tt>Base64OutputStream</tt> will be * encoded and written to the given <tt>Writer</tt>. * * @param w the Writer to which encoded data will be written **/ public Base64OutputStream(Writer w) { this.out = w; this.outBuffer = new char[outBufferLength = 1024]; } /** * Data written to this <tt>Base64OutputStream</tt> will be * encoded and written to the given <tt>OutputStream</tt>. * * @param os the OutputStream to which encoded data will be * written * @param bufferSize the size of the internal buffer (in input * bytes, not encoded sextets) **/ public Base64OutputStream(OutputStream os, int bufferSize) { this.out = new OutputStreamWriter(os); if(bufferSize == 0) { this.outBuffer = new char[outBufferLength = 4]; } else if(bufferSize > 0) { bufferSize *= 1.25f; this.outBuffer = new char[outBufferLength = bufferSize + (4 - (bufferSize % 4))]; } else { throw new IllegalArgumentException("bufferSize < 0"); } } /** * Data written to this <tt>Base64OutputStream</tt> will be * encoded and written to the given <tt>Writer</tt>. * * @param w the Writer to which encoded data will be written * @param bufferSize the size of the internal buffer (in input * bytes, not encoded sextets) **/ public Base64OutputStream(Writer w, int bufferSize) { this.out = w; if(bufferSize == 0) { this.outBuffer = new char[outBufferLength = 4]; } else if(bufferSize > 0) { bufferSize *= 1.25f; this.outBuffer = new char[outBufferLength = bufferSize + (4 - (bufferSize % 4))]; } else { throw new IllegalArgumentException("bufferSize < 0"); } } static final char[] alphabet = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; private static final String lineSep = System.getProperty("line.separator", "\n"); /** * Bytes are written in sets of 24 bits. This field stores those * bits until the full 24 is ready to be written. **/ private int buf; // = 0x00; /** * Counts the number of bytes stored in buf. Always one of 0, 1, * or 2. If there are 3 bytes stored in buf, they will be flushed * and mod will be set back to 0. **/ private int mod; // = 0; /** * Stores all written byte triplets/quadruplets until flushed for * efficiency. Length must be a multiple of 4. **/ private final char[] outBuffer; private final int outBufferLength; private int outBufferOffset; /** * According to RFC 1521, no more than 76 characters may be output * on a line. This field stores the number of characters that has * been written since the beginning of output or the last newline. * colMod will never be more than colWrap. **/ private int colMod; // = 0; /** * A newline will be output ever colWrap base64 characters (must * not be more than 76). Default is 64. **/ private static final int colWrap = 64; /** * Writes a single byte. * * @param b the byte to be written. Only the low-order 8 bits of * the <tt>int</tt> value are used. * * @exception IOException if thrown during writing to the * underlying stream, or if this stream has been closed **/ public synchronized void write(int b) throws IOException { if(closed) { throw new IOException("stream has been closed"); } b &= 0xFF; switch(mod++) { case 0: buf = b << 16; break; case 1: buf |= b << 8; break; case 2: buf |= b; flush24(mod); break; default: throw new Error("impossible bytesWritten % 3 = "+(mod - 1)); } } /** * Writes <tt>len</tt> bytes retrieved from the array <tt>b</tt> * starting from index <tt>offset</tt>. * * @param b the array containing the bytes to be written * @param offset no bytes in <tt>b</tt> before this index are * written * @param len the number of bytes to write * * @exception IOException if thrown during writing to the * underlying stream, or if this stream has been closed **/ public synchronized void write(byte[] b, int offset, int len) throws IOException { if(closed) { throw new IOException("stream has been closed"); } // for efficiency, len and max are absolute indexes instead of // relative ones len += offset; // flush any previously written single bytes int flushed = 0; for(; mod != 0 && offset < len; flushed++) { write(b[offset++]); } // max is always less than len int max = (max = len - flushed) - (max % 3); // will remain zero unless single bytes are // written later mod = 0; // write multiples of three bytes. This loop works just like // calling write(int) multiple times, except that mod is not // updated (since we always write 3 bytes) and there is no // synchronization penalty. while(offset < max) { buf = (b[offset++] & 0xFF) << 16; buf |= (b[offset++] & 0xFF) << 8; buf |= b[offset++] & 0xFF; flush24(3); } // write single bytes for any leftovers while(offset < len) { write(b[offset++]); } } /** * Encodes the the one, two, or three bytes stored in buf into * their four corresponding ASCII characters and puts them in * outBuffer. If outBuffer is full, it is flushed to the * underlying stream. If more than colWrap characters have been * written, a line separator is also written. * * @param mod the number of bytes currently stored in buf **/ private final void flush24(int mod) throws IOException { try { // REVISIT: should this be + 3? if(outBufferOffset + 4 >= outBufferLength) { flushOutBuffer(); } char[] outBuffer = this.outBuffer; int buf = this.buf; // divide 24 bits in buf into 4 sextets. If there are // less than 24 bits (mod < 3), d and c will be zero, but // this is handled later. int d = buf & 0x3F; buf >>>= 6; int c = buf & 0x3F; buf >>>= 6; int b = buf & 0x3F; buf >>>= 6; int a = buf & 0x3F; // lookup each switch(mod) { case 3: outBuffer[outBufferOffset++] = alphabet[a]; outBuffer[outBufferOffset++] = alphabet[b]; outBuffer[outBufferOffset++] = alphabet[c]; outBuffer[outBufferOffset++] = alphabet[d]; break; case 2: outBuffer[outBufferOffset++] = alphabet[a]; outBuffer[outBufferOffset++] = alphabet[b]; outBuffer[outBufferOffset++] = alphabet[c]; outBuffer[outBufferOffset++] = '='; break; case 1: outBuffer[outBufferOffset++] = alphabet[a]; outBuffer[outBufferOffset++] = alphabet[b]; outBuffer[outBufferOffset++] = '='; outBuffer[outBufferOffset++] = '='; break; case 0: // we shouldn't be here at all, but might as well // ignore it since there are no bytes to write return; // so that no newline is written; // execute through finally as well default: throw new Error("impossible bytesWritten % 3 = "+(mod - 1)); } if((colMod += 4) >= colWrap) { colMod = 0; int lineSepLength = lineSep.length(); for(int i=0, il=lineSepLength; i<il; i++) { if(outBufferOffset >= outBufferLength) { flushOutBuffer(); } outBuffer[outBufferOffset++] = lineSep.charAt(i); } } } finally { this.mod = 0; this.buf = 0; } } /** * This is where stuff stored in outBuffer is actually written to * out. **/ private final void flushOutBuffer() throws IOException { out.write(outBuffer, 0, outBufferOffset); outBufferOffset = 0; } /** * Flushes all buffered data to the underlying * <tt>OutputStream</tt> or <tt>Writer</tt>. If the length of the * buffered data is not a multiple of three bytes, the data will * be padded with '=' characters as necessary. The underlying * stream is also <tt>flush</tt>ed. * * <p>RFC 1521 specifies that padding with '=' characters is only * performed at the end of the data. Users should note that if * flush is forced to pad output data, non-compliant encoding may * result if this happens at any time other than at the end of the * data. * * <p>[EMAIL PROTECTED] Base64InputStream} is capable of reading data that * has been '=' padded at places other than at the end of the * input. However, to ensure compliant encoding, <tt>flush</tt> * should only be invoked once per instance of * <tt>Base64OutputStream</tt>. * * @see #close * * @exception IOException if thrown during writing to the * underlying stream, or if this stream has been closed **/ public synchronized void flush() throws IOException { if(closed) { throw new IOException("stream has been closed"); } if(mod != 0) { flush24(mod); } flushOutBuffer(); out.flush(); } /** * Flushes the stream and closes it to prevent further writing. * @see #flush * @exception IOException if the stream has already been closed **/ public synchronized void close() throws IOException { if(!closed) { try { flush(); } finally { closed = true; } } } public static void main(String[] args) throws IOException { OutputStream o = new Base64OutputStream(System.out, 1024); byte[] buf = new byte[1000]; int len; java.io.InputStream r = new java.io.FileInputStream(args[0]); while((len = r.read(buf, 0, 1000)) != -1) o.write(buf, 0, len); o.flush(); o.close(); System.out.println(); //for(int i=0; i<alphabet.length; i++) { // System.out.println(i+"="+alphabet[i]+","+((int)alphabet[i])); //} } }
/** * Copyright 2001 Peter Davis. **/ package cx.pdavis.xerial; import java.io.InputStream; import java.io.Reader; import java.io.InputStreamReader; import java.io.IOException; /** * Reads binary data encoded in <tt>base64</tt>. Each character in * the input is an ASCII value in the range A-Z, a-z, 0-9, '+', or '/' * that maps to one of 64 binary values (6 bits). The 24 combined * bits of four input characters are combined to form three binary * bytes. Input quadruplets may be padded with one or two '=' * characters in case the original data was not a multiple of three * bytes in length. See <a * href="http://www.faqs.org/rfcs/rfc1521.html">RFC 1521, section * 5.2"</a> for the full Base64 encoding specification. * * <p>As specified by RFC 1521, a <tt>Base64InputStream</tt> ignores * all whitespace in the input. Non-whitespace characters that are * not in one of the above ranges cause an * <tt>IllegalBase64QuadrupletException</tt>, since they are most * likely the result of corruption or a transmition failure. * <tt>Base64InputStream</tt>s do not support ignoring of illegal * characters as this is optional behavior according to RFC 1521. * * <p>Input that is not properly padded to a multiple of four sextets * with '=' characters will cause an * <tt>IllegalBase64PaddingException</tt> to be thrown. * * <p>In a slight deviation from RFC 1521, <tt>Base64InputStream</tt> * ignores '=' padding that does not occur at the end of the input. * See [EMAIL PROTECTED] Base64OutputStream#flush} for a description of why this * is sometimes necessary. This modification remains fully compatible * with compliant encoders. * * @see Base64OutputStream * @see IllegalBase64QuadrupletException * @see IllegalBase64PaddingException * @see <a href="http://www.faqs.org/rfcs/rfc1521.html">RFC 1521, * section 5.2"</a> **/ public class Base64InputStream extends InputStream { private final Reader in; private char[] buf; private byte[] byteBuf; private int byteBufOff; private int byteBufFilled; private boolean eof; private boolean closed; // Each index of reverseAlphabet is the integer value of one of // the valid input characters and is set to that character's // corresponding binary value. Characters that are illegal but // still in the range of 0 to 123 are mapped to '-1'. static final int[] reverseAlphabet = new int[((int)'z') + 1]; // new int[123]; static { for(int i=0; i<(((int)'z')+1); i++) reverseAlphabet[i] = -1; for(int i=0, il=Base64OutputStream.alphabet.length; i<il; i++) { // i < 64 reverseAlphabet[Base64OutputStream.alphabet[i]] = i; } } /** * Creates a new Base64InputStream that reads input from the given * InputStream. * * @param is the InputStream from which this stream will read data **/ public Base64InputStream(InputStream is) { this.in = new QuadrupletReader(new InputStreamReader(is), 1024); this.buf = new char[1024]; this.byteBuf = new byte[768]; } /** * Creates a new Base64InputStream that reads input from the given * Reader. * * @param is the Reader from which this stream will read data **/ public Base64InputStream(Reader r) { this.in = new QuadrupletReader(r, 1024); this.buf = new char[1024]; this.byteBuf = new byte[768]; } /** * Creates a new Base64InputStream that reads input from the given * InputStream. * * @param is the InputStream from which this stream will read data * @param bufferSize the size of the internal input buffer in * characters * @exception IllegalArgumentException if bufferSize is less than 0 **/ public Base64InputStream(InputStream is, int bufferSize) { if(bufferSize < 0) { throw new IllegalArgumentException("bufferSize < 0"); } else if(bufferSize < 8) { // buffer size <= 4 makes bogus shit happen bufferSize = 8; } else { bufferSize += 4 - (bufferSize % 4); } this.in = new QuadrupletReader(new InputStreamReader(is), bufferSize); this.buf = new char[bufferSize]; this.byteBuf = new byte[(int)(bufferSize * 0.75f)]; } /** * Creates a new Base64InputStream that reads input from the given * Reader. * * @param is the Reader from which this stream will read data * @param bufferSize the size of the internal input buffer in * characters * @exception IllegalArgumentException if bufferSize is less than 0 **/ public Base64InputStream(Reader r, int bufferSize) { if(bufferSize < 0) { throw new IllegalArgumentException("bufferSize < 0"); } else if(bufferSize < 8) { // buffer size <= 4 makes bogus shit happen bufferSize = 8; } else { bufferSize += 4 - (bufferSize % 4); } this.in = new QuadrupletReader(r, bufferSize); this.buf = new char[bufferSize]; this.byteBuf = new byte[(int)(bufferSize * 0.75f)]; } /** * Decodes and returns a single byte. If the end of the input has * been reached, -1 is returned. * * @exception IllegalBase64QuadrupletException if illegal * non-whitespace characters are found in the input * @exception IllegalBase64PaddingException if the end of input is * reached and determined to not be correctly padded * @exception IOException if thrown by the underlying reader, or * if this stream has been closed * * @return the byte read, or -1 if the end of input has been * reached **/ public synchronized int read() throws IOException { if(closed) { throw new IOException("stream has been closed"); } if(byteBufOff >= byteBufFilled) { byteBufOff = byteBufFilled = 0; fillBuffer(); } return eof ? -1 : byteBuf[byteBufOff++] & 0xFF; } /** * Attempts to fill the internal buffer with as much decoded data * as possible. **/ private final void fillBuffer() throws IOException { int read; if(eof = (read = in.read(buf)) == -1) { return; } if(read % 4 != 0) { throw new IllegalBase64PaddingException("input length must be multiple of 4"); } for(int i=0; i<read; i+=4) { int val1, val2, val3, val4; try { int num = 4; val1 = reverseAlphabet[buf[i]]; val2 = reverseAlphabet[buf[i + 1]]; val3 = reverseAlphabet[buf[i + 2]]; val4 = reverseAlphabet[buf[i + 3]]; if(val4 == -1 && buf[i + 3] == '=') { num = (val3 == -1 && buf[i + 2] == '=') ? 2 : 3; } else if(val1 == -1 || val2 == -1 || val3 == -1 || val4 == -1) { throw new IllegalBase64QuadrupletException(String.valueOf(buf, i, 4)); } // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 // | | | | | | \ \ / / / / | | | | / / / / / / / / // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 switch(num) { case 4: byteBuf[byteBufFilled++] = (byte)((val1 << 2) | (val2 >>> 4)); byteBuf[byteBufFilled++] = (byte)(((val2 & 0xF) << 4) | (val3 >>> 2)); byteBuf[byteBufFilled++] = (byte)(((val3 & 0x3) << 6) | val4); break; case 3: byteBuf[byteBufFilled++] = (byte)((val1 << 2) | (val2 >>> 4)); byteBuf[byteBufFilled++] = (byte)(((val2 & 0xF) << 4) | (val3 >>> 2)); break; case 2: byteBuf[byteBufFilled++] = (byte)((val1 << 2) | (val2 >>> 4)); break; default: throw new IllegalBase64QuadrupletException(String.valueOf(buf, i, 4)); } } catch(ArrayIndexOutOfBoundsException aioobe) { aioobe.printStackTrace(); throw new IllegalBase64QuadrupletException(String.valueOf(buf, i, 4)); } } } /** * Used to filter out whitespace and to always return a multiple * of four bytes. The no-arg read() method should never be used. **/ private static class QuadrupletReader extends Reader { private final char[] buf; private final char[] quad = new char[4]; private int leftovers; private final Reader in; public QuadrupletReader(Reader r, int bufSize) { this.in = r; this.buf = new char[bufSize]; } public int read(char[] buf, int off, int len) throws IOException { // len - off must be <= the bufSize given in the // constructor. Since this class is only used within // Base64InputStream, this isn't a problem. final char[] realBuf = this.buf; final char[] quad = this.quad; final int read = in.read(realBuf); int actual = off; char c; for(int i=0; i < read; i++) { if(!Character.isWhitespace(c = realBuf[i])) { quad[leftovers++] = c; if(leftovers == 4) { buf[actual++] = quad[0]; buf[actual++] = quad[1]; buf[actual++] = quad[2]; buf[actual++] = quad[3]; leftovers = 0; } } } if(read == -1) { // nothing read from in, but there might be // leftovers from previous invokations if(leftovers != 0) { for(int i=leftovers; i<4; i++) { buf[actual++] = quad[i]; } leftovers = 0; } else { return -1; } } return actual - off; } public void close() throws IOException { in.close(); } } public synchronized void close() throws IOException { closed = true; buf = null; byteBuf = null; } public static void main(String[] args) throws IOException { int val; InputStream is; if(args.length > 1) { is = new java.io.FileInputStream(args[1]); while((val = is.read()) != -1) { System.out.print(','); System.out.print(Integer.toString(val)); } System.out.println("\nActual Results:"); } is = new Base64InputStream(new java.io.FileInputStream(args[0]), 1); while((val = is.read()) != -1) { System.out.print(','); System.out.print(Integer.toString(val)); } } }
-- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>
