All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.ws.commons.util.Base64 Maven / Gradle / Ivy

Go to download

This is a small collection of utility classes, that allow high performance XML processing based on SAX. Basically, it is assumed, that you are using an JAXP 1.1 compliant XML parser and nothing else. In particular, no dependency on the javax.xml.transform package is introduced.

There is a newer version: 1.0.1
Show newest version
/*
 * Copyright 1999,2005 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.ws.commons.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.UndeclaredThrowableException;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;


/** Performs Base64 encoding and/or decoding. This is an on-the-fly decoder: Unlike,
 * for example, the commons-codec classes, it doesn't depend on byte arrays. In
 * other words, it has an extremely low memory profile. This is well suited even
 * for very large byte streams.
 */
public class Base64 {
	/** An exception of this type is thrown, if the decoded
	 * character stream contains invalid input.
	 */
	public static class DecodingException extends IOException {
		private static final long serialVersionUID = 3257006574836135478L;
		DecodingException(String pMessage) { super(pMessage); }
	}

	/** An exception of this type is thrown by the {@link SAXEncoder},
	 * if writing to the target handler causes a SAX exception.
	 * This class is required, because the {@link IOException}
	 * allows no cause until Java 1.3.
	 */
	public static class SAXIOException extends IOException {
		private static final long serialVersionUID = 3258131345216451895L;
		final SAXException saxException;
		SAXIOException(SAXException e) {
			super();
			saxException = e;
		}
		/** Returns the encapsulated {@link SAXException}.
		 * @return An exception, which was thrown when invoking
		 * {@link ContentHandler#characters(char[], int, int)}.
		 */
		public SAXException getSAXException() { return saxException; }
	}


	/** Default line separator: \n
	 */
	public static final String LINE_SEPARATOR = "\n";

	/** Default size for line wrapping.
	 */
	public static final int LINE_SIZE = 76;

	/**
     * This array is a lookup table that translates 6-bit positive integer
     * index values into their "Base64 Alphabet" equivalents as specified 
     * in Table 1 of RFC 2045.
     */
    private static final char intToBase64[] = {
        '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', '+', '/'
    };

    /**
     * This array is a lookup table that translates unicode characters
     * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
     * into their 6-bit positive integer equivalents.  Characters that
     * are not in the Base64 alphabet but fall within the bounds of the
     * array are translated to -1.
     */
    private static final byte base64ToInt[] = {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
        55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
        5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
        24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
        35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    };

	/** An encoder is an object, which is able to encode byte array
	 * in blocks of three bytes. Any such block is converted into an
	 * array of four bytes.
	 */
	public static abstract class Encoder {
		private int num, numBytes;
		private final char[] charBuffer;
		private int charOffset;
		private final int wrapSize;
		private final int skipChars;
		private final String sep;
		private int lineChars = 0;
		/** Creates a new instance.
		 * @param pBuffer The encoders buffer. The encoder will
		 * write to the buffer as long as possible. If the
		 * buffer is full or the end of data is signaled, then
		 * the method {@link #writeBuffer(char[], int, int)}
		 * will be invoked.
		 * @param pWrapSize A nonzero value indicates, that a line
		 * wrap should be performed after the given number of
		 * characters. The value must be a multiple of 4. Zero
		 * indicates, that no line wrap should be performed.
		 * @param pSep The eol sequence being used to terminate
		 * a line in case of line wraps. May be null, in which
		 * case the default value {@link Base64#LINE_SEPARATOR}
		 * is being used.
		 */
		protected Encoder(char[] pBuffer, int pWrapSize, String pSep) {
			charBuffer = pBuffer;
			sep = pSep == null ? null : Base64.LINE_SEPARATOR;
			skipChars = pWrapSize == 0 ? 4 : 4 + sep.length();
			wrapSize = skipChars == 4 ? 0 : pWrapSize;
			if (wrapSize < 0  ||  wrapSize %4 > 0) {
				throw new IllegalArgumentException("Illegal argument for wrap size: " + pWrapSize
												   + "(Expected nonnegative multiple of 4)");
			}
			if (pBuffer.length < skipChars) {
				throw new IllegalArgumentException("The buffer must contain at least " + skipChars
												   + " characters, but has " + pBuffer.length);
			}
		}
		/** Called for writing the buffer contents to the target.
		 * @param pChars The buffer being written.
		 * @param pOffset Offset of first character being written.
		 * @param pLen Number of characters being written.
		 * @throws IOException Writing to the destination failed.
		 */
		protected abstract void writeBuffer(char[] pChars, int pOffset, int pLen) throws IOException;

		private void wrap() {
			for (int j = 0;  j < sep.length();  j++) {
				charBuffer[charOffset++] = sep.charAt(j);
			}
			lineChars = 0;
		}

		/** Encodes the given byte array.
		 * @param pBuffer Byte array being encoded.
		 * @param pOffset Offset of first byte being encoded.
		 * @param pLen Number of bytes being encoded.
		 * @throws IOException Invoking the {@link #writeBuffer(char[],int,int)} method
		 * for writing the encoded data failed.
		 */
		public void write(byte[] pBuffer, int pOffset, int pLen) throws IOException {
			for(int i = 0;  i < pLen;  i++) {
				int b = pBuffer[pOffset++];
				if (b < 0) { b += 256; }
				num = (num << 8) + b;
				if (++numBytes == 3) {
					charBuffer[charOffset++] = intToBase64[num >> 18];
					charBuffer[charOffset++] = intToBase64[(num >> 12) & 0x3f];
					charBuffer[charOffset++] = intToBase64[(num >> 6) & 0x3f];
					charBuffer[charOffset++] = intToBase64[num & 0x3f];
					if (wrapSize > 0) {
						lineChars += 4;
						if (lineChars >= wrapSize) {
							wrap();
						}
					}
					num = 0;
					numBytes = 0;
					if (charOffset + skipChars > charBuffer.length) {
						writeBuffer(charBuffer, 0, charOffset);
						charOffset = 0;
					}
				}
			}
		}
		/** Writes any currently buffered data to the destination.
		 * @throws IOException Invoking the {@link #writeBuffer(char[],int,int)}
		 * method for writing the encoded data failed.
		 */
		public void flush() throws IOException {
			if (numBytes > 0) {
				if (numBytes == 1) {
					charBuffer[charOffset++] = intToBase64[num >> 2];
					charBuffer[charOffset++] = intToBase64[(num << 4) & 0x3f];
					charBuffer[charOffset++] = '=';
					charBuffer[charOffset++] = '=';
				} else {
					charBuffer[charOffset++] = intToBase64[num >> 10];
					charBuffer[charOffset++] = intToBase64[(num >> 4) & 0x3f];
					charBuffer[charOffset++] = intToBase64[(num << 2) & 0x3f];
					charBuffer[charOffset++] = '=';
				}
				lineChars += 4;
				num = 0;
				numBytes = 0;
			}
			if (wrapSize > 0  &&  lineChars > 0) {
				wrap();
			}
			if (charOffset > 0) {
				writeBuffer(charBuffer, 0, charOffset);
				charOffset = 0;
			}
		}
	}

	/** An {@link OutputStream}, which is writing to the given
	 * {@link Encoder}.
	 */
	public static class EncoderOutputStream extends OutputStream {
		private final Encoder encoder;
		/** Creates a new instance, which is creating
		 * output using the given {@link Encoder}.
		 * @param pEncoder The base64 encoder being used.
		 */
		public EncoderOutputStream(Encoder pEncoder) {
			encoder = pEncoder;
		}
		private final byte[] oneByte = new byte[1];
		public void write(int b) throws IOException {
			oneByte[0] = (byte) b;
			encoder.write(oneByte, 0, 1);
		}
		public void write(byte[] pBuffer, int pOffset, int pLen) throws IOException {
			encoder.write(pBuffer, pOffset, pLen);
		}
		public void close() throws IOException {
			encoder.flush();
		}
	}

	/** Returns an {@link OutputStream}, that encodes its input in Base64
	 * and writes it to the given {@link Writer}. If the Base64 stream
	 * ends, then the output streams {@link OutputStream#close()} method
	 * must be invoked. Note, that this will not close the
	 * target {@link Writer}!
	 * @param pWriter Target writer.
	 * @return An output stream, encoding its input in Base64 and writing
	 * the output to the writer pWriter.
	 */
	public static OutputStream newEncoder(Writer pWriter) {
		return newEncoder(pWriter, LINE_SIZE, LINE_SEPARATOR);
	}

	/** Returns an {@link OutputStream}, that encodes its input in Base64
	 * and writes it to the given {@link Writer}. If the Base64 stream
	 * ends, then the output streams {@link OutputStream#close()} method
	 * must be invoked. Note, that this will not close the
	 * target {@link Writer}!
	 * @param pWriter Target writer.
	 * @param pLineSize Size of one line in characters, must be a multiple
	 * of four. Zero indicates, that no line wrapping should occur.
	 * @param pSeparator Line separator or null, in which case the default value
	 * {@link #LINE_SEPARATOR} is used.
	 * @return An output stream, encoding its input in Base64 and writing
	 * the output to the writer pWriter.
	 */
	public static OutputStream newEncoder(final Writer pWriter, int pLineSize, String pSeparator) {
		final Encoder encoder = new Encoder(new char[4096], pLineSize, pSeparator){
			protected void writeBuffer(char[] pBuffer, int pOffset, int pLen) throws IOException {
				pWriter.write(pBuffer, pOffset, pLen);
			}
		};
		return new EncoderOutputStream(encoder);
	}

	/** An {@link Encoder}, which is writing to a SAX content handler.
	 * This is typically used for embedding a base64 stream into an
	 * XML document.
	 */
	public static class SAXEncoder extends Encoder {
		private final ContentHandler handler;
		/** Creates a new instance.
		 * @param pBuffer The encoders buffer.
		 * @param pWrapSize A nonzero value indicates, that a line
		 * wrap should be performed after the given number of
		 * characters. The value must be a multiple of 4. Zero
		 * indicates, that no line wrap should be performed.
		 * @param pSep The eol sequence being used to terminate
		 * a line in case of line wraps. May be null, in which
		 * case the default value {@link Base64#LINE_SEPARATOR}
		 * is being used.
		 * @param pHandler The target handler.
		 */
		public SAXEncoder(char[] pBuffer, int pWrapSize, String pSep,
						  ContentHandler pHandler) {
			super(pBuffer, pWrapSize, pSep);
			handler = pHandler;
		}
		/** Writes to the content handler.
		 * @throws SAXIOException Writing to the content handler
		 * caused a SAXException.
		 */
		protected void writeBuffer(char[] pChars, int pOffset, int pLen) throws IOException {
			try {
				handler.characters(pChars, pOffset, pLen);
			} catch (SAXException e) {
				throw new SAXIOException(e);
			}
		}
	}

	/** Converts the given byte array into a base64 encoded character
	 * array.
	 * @param pBuffer The buffer being encoded.
	 * @param pOffset Offset in buffer, where to begin encoding.
	 * @param pLength Number of bytes being encoded.
	 * @return Character array of encoded bytes.
	 */
	public static String encode(byte[] pBuffer, int pOffset, int pLength) {
		return encode(pBuffer, pOffset, pLength, LINE_SIZE, LINE_SEPARATOR);
	}

	/** Converts the given byte array into a base64 encoded character
	 * array.
	 * @param pBuffer The buffer being encoded.
	 * @param pOffset Offset in buffer, where to begin encoding.
	 * @param pLength Number of bytes being encoded.
	 * @param pLineSize Size of one line in characters, must be a multiple
	 * of four. Zero indicates, that no line wrapping should occur.
	 * @param pSeparator Line separator or null, in which case the default value
	 * {@link #LINE_SEPARATOR} is used.
	 * @return Character array of encoded bytes.
	 */
	public static String encode(byte[] pBuffer, int pOffset, int pLength,
								int pLineSize, String pSeparator) {
		StringWriter sw = new StringWriter();
		OutputStream ostream = newEncoder(sw, pLineSize, pSeparator);
		try {
			ostream.write(pBuffer, pOffset, pLength);
			ostream.close();
		} catch (IOException e) {
			throw new UndeclaredThrowableException(e);
		}
		return sw.toString();
	}

	/** Converts the given byte array into a base64 encoded character
	 * array with the line size {@link #LINE_SIZE} and the separator
	 * {@link #LINE_SEPARATOR}.
	 * @param pBuffer The buffer being encoded.
	 * @return Character array of encoded bytes.
	 */
	public static String encode(byte[] pBuffer) {
		return encode(pBuffer, 0, pBuffer.length);
	}

	/** An encoder is an object, which is able to decode char arrays
	 * in blocks of four bytes. Any such block is converted into a
	 * array of three bytes.
	 */
	public static abstract class Decoder {
		private final byte[] byteBuffer;
		private int byteBufferOffset;
		private int num, numBytes;
		private int eofBytes;
		/** Creates a new instance.
		 * @param pBufLen The decoders buffer size. The decoder will
		 * store up to this number of decoded bytes before invoking
		 * {@link #writeBuffer(byte[],int,int)}.
		 */
		protected Decoder(int pBufLen) {
			byteBuffer = new byte[pBufLen];
		}
		/** Called for writing the decoded bytes to the destination.
		 * @param pBuffer The byte array being written.
		 * @param pOffset Offset of the first byte being written.
		 * @param pLen Number of bytes being written.
		 * @throws IOException Writing to the destination failed.
		 */
		protected abstract void writeBuffer(byte[] pBuffer, int pOffset, int pLen) throws IOException;
		/** Converts the Base64 encoded character array.
		 * @param pData The character array being decoded.
		 * @param pOffset Offset of first character being decoded.
		 * @param pLen Number of characters being decoded.
		 * @throws DecodingException Decoding failed.
		 * @throws IOException An invocation of the {@link #writeBuffer(byte[],int,int)}
		 * method failed.
		 */
		public void write(char[] pData, int pOffset, int pLen) throws IOException {
			for (int i = 0;  i < pLen;  i++) {
				char c = pData[pOffset++];
				if (Character.isWhitespace(c)) {
					continue;
				}
				if (c == '=') {
					++eofBytes;
					num = num << 6;
					switch(++numBytes) {
						case 1:
						case 2:
							throw new DecodingException("Unexpected end of stream character (=)");
						case 3:
							// Wait for the next '='
							break;
						case 4:
							byteBuffer[byteBufferOffset++] = (byte) (num >> 16);
							if (eofBytes == 1) {
								byteBuffer[byteBufferOffset++] = (byte) (num >> 8);
							}
							writeBuffer(byteBuffer, 0, byteBufferOffset);
							byteBufferOffset = 0;
							break;
						case 5:
							throw new DecodingException("Trailing garbage detected");
						default:
							throw new IllegalStateException("Invalid value for numBytes");
					}
				} else {
					if (eofBytes > 0) {
						throw new DecodingException("Base64 characters after end of stream character (=) detected.");
					}
					int result;
					if (c >= 0  &&  c < base64ToInt.length) {
						result = base64ToInt[c];
						if (result >= 0) {
							num = (num << 6) + result;
							if (++numBytes == 4) {
								byteBuffer[byteBufferOffset++] = (byte) (num >> 16);
								byteBuffer[byteBufferOffset++] = (byte) ((num >> 8) & 0xff);
								byteBuffer[byteBufferOffset++] = (byte) (num & 0xff);
								if (byteBufferOffset + 3 > byteBuffer.length) {
									writeBuffer(byteBuffer, 0, byteBufferOffset);
									byteBufferOffset = 0;
								}
								num = 0;
								numBytes = 0;
							}
							continue;
						}
				    }
					if (!Character.isWhitespace(c)) {
						throw new DecodingException("Invalid Base64 character: " + (int) c);
					}
				}
			}
		}
		/** Indicates, that no more data is being expected. Writes all currently
		 * buffered data to the destination by invoking {@link #writeBuffer(byte[],int,int)}.
		 * @throws DecodingException Decoding failed (Unexpected end of file).
		 * @throws IOException An invocation of the {@link #writeBuffer(byte[],int,int)} method failed.
		 */
		public void flush() throws IOException {
			if (numBytes != 0  &&  numBytes != 4) {
				throw new DecodingException("Unexpected end of file");
			}
			if (byteBufferOffset > 0) {
				writeBuffer(byteBuffer, 0, byteBufferOffset);
				byteBufferOffset = 0;
			}
		}
	}

	/** Returns a {@link Writer}, that decodes its Base64 encoded
	 * input and writes it to the given {@link OutputStream}.
	 * Note, that the writers {@link Writer#close()} method will
	 * not close the output stream pStream!
	 * @param pStream Target output stream.
	 * @return An output stream, encoding its input in Base64 and writing
	 * the output to the writer pWriter.
	 */
	public Writer newDecoder(final OutputStream pStream) {
		return new Writer(){
			private final Decoder decoder = new Decoder(1024){
				protected void writeBuffer(byte[] pBytes, int pOffset, int pLen) throws IOException {
					pStream.write(pBytes, pOffset, pLen);
				}
			};
			public void close() throws IOException {
				flush();
			}
			public void flush() throws IOException {
				decoder.flush();
				pStream.flush();
			}
			public void write(char[] cbuf, int off, int len) throws IOException {
				decoder.write(cbuf, off, len);
			}
		};
	}

	/** Converts the given base64 encoded character buffer into a byte array.
	 * @param pBuffer The character buffer being decoded.
	 * @param pOffset Offset of first character being decoded.
	 * @param pLength Number of characters being decoded.
	 * @return Converted byte array
	 * @throws DecodingException The input character stream contained invalid data.
	 */
	public static byte[] decode(char[] pBuffer, int pOffset, int pLength) throws DecodingException {
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
		Decoder d = new Decoder(1024){
			protected void writeBuffer(byte[] pBuf, int pOff, int pLen) throws IOException {
				baos.write(pBuf, pOff, pLen);
			}
		};
		try {
			d.write(pBuffer, pOffset, pLength);
			d.flush();
		} catch (DecodingException e) {
			throw e;
		} catch (IOException e) {
			throw new UndeclaredThrowableException(e);
		}
		return baos.toByteArray();
	}

	/** Converts the given base64 encoded character buffer into a byte array.
	 * @param pBuffer The character buffer being decoded.
	 * @return Converted byte array
	 * @throws DecodingException The input character stream contained invalid data.
	 */
	public static byte[] decode(char[] pBuffer) throws DecodingException {
		return decode(pBuffer, 0, pBuffer.length);
	}

	/** Converts the given base64 encoded String into a byte array.
	 * @param pBuffer The string being decoded.
	 * @return Converted byte array
	 * @throws DecodingException The input character stream contained invalid data.
	 */
	public static byte[] decode(String pBuffer) throws DecodingException {
		return decode(pBuffer.toCharArray());
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy