/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package fedora.server.journal.helpers; import java.io.IOException; import java.io.OutputStream; import java.util.regex.Pattern; import fedora.utilities.Base64; /** * Wraps an OutputStream with a Base64 decoder, so when you "write" to the * stream, you write Strings of Base64-encoded characters, but the OutputStream * receives decoded bytes. * <p> * Base64 encoding is defined in Internet RFC 3548, found at * http://tools.ietf.org/html/rfc3548 (among other places). * * @author Jim Blake */ public class DecodingBase64OutputStream { private final Pattern pattern = Pattern.compile("[^A-Za-z0-9+/=]*"); private final OutputStream stream; private String residual = ""; private boolean open = true; /** * @param stream * the destination for the decoded bytes. */ public DecodingBase64OutputStream(OutputStream stream) { this.stream = stream; } /** * Add Base64-encoded characters to be decoded. This is not a trivial * operation for two reasons: any characters that are not valid for * Base64-encoding must be ignored, and we can only decode groups of 4 * characters. So, when data is received, we remove any invalid characters * and then strip off any trailing characters that don't fit in the * 4-character groups. Those trailing characters will be prefixed to the * next set of data, and hopefully we will have none left over when the * writer is closed. * * @throws IllegalStateException * if called after close(). * @throws IOException * from the inner OutputStream. */ public void write(String data) throws IOException { if (!open) { throw new IllegalStateException("Stream has already been closed."); } String buffer = pattern.matcher(residual + data).replaceAll(""); int usableLength = buffer.length() - buffer.length() % 4; stream.write(Base64.decode((buffer.substring(0, usableLength)))); residual = buffer.substring(usableLength); } /** * Close the writer. If there are any residual characters at this point, the * data stream was not a valid Base64 encoding. * * @throws IOException * from the inner OutputStream. */ public void close() throws IOException { if (open) { if (residual.length() > 0) { throw new IOException("Base64 error - data is not properly" + "padded to 4-character groups."); } stream.close(); open = false; } } }