/******************************************************************************* * $Id$ * $Author$ * $Date$ * * Copyright 2002 - YAJUL Developers, Joshua Davis, Kent Vogel. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * ******************************************************************************/ /* * Created by IntelliJ IDEA. * User: josh * Date: Sep 23, 2002 * Time: 10:46:52 PM */ package org.yajul.io; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; /** * Provides decoding of BASE64 encoded data as an input stream filter. The * underlying input stream is expected to be encoded in BASE64 form. * * @author Joshua Davis */ public class Base64InputStream extends AbstractByteFilterInputStream { private static final int END_OF_INPUT = 65; /** * Keeps track of the state (byte number) in the input stream. * */ private int state; /** * The previous encoded and parsed byte. * */ private int previous; /** * Creates a new Base64 decoding input stream, using the input stream * (encoded stream). * * @param in The base 64 encoded input stream */ public Base64InputStream(InputStream in) { super(in); state = 0; previous = 0; } /** * Reads the next byte of data from this input stream. The value * byte is returned as an <code>int</code> in the range * <code>0</code> to <code>255</code>. If no byte is available * because the end of the stream has been reached, the value * <code>-1</code> is returned. This method blocks until input data * is available, the end of the stream is detected, or an exception * is thrown. * <p/> * This method * simply performs <code>in.read()</code> and returns the result. * * @return the next byte of data, or <code>-1</code> if the end of the * stream is reached. * @throws IOException if an I/O error occurs. * @see FilterInputStream#in */ @SuppressWarnings({"UnusedAssignment"}) public int read() throws IOException { int input = 0; int parsed = 0; int output = 0; if (state == -1) // EOF reached? return -1; // Just return -1 while ((input = super.readByte()) >= 0) { parsed = parseCharacter(input); // If the parsed character is valid BASE64 input, then // enter the state machine. Otherwise, keep reading. if (parsed >= 0 && parsed <= 64) { // A simple finite state machine based on the state, and the // current and previous *parsed* input. state++; switch (state) { case 1: // 1 byte of input, nothing to do. previous = parsed; break; case 2: // 2 bytes of input, output the first byte. output = get1((byte) previous, (byte) parsed); previous = parsed; return output; case 3: // 3 bytes parsed, output the second byte. output = get2((byte) previous, (byte) parsed); previous = parsed; return output; case 4: // 4 bytes parsed, output the third byte. output = get3((byte) previous, (byte) parsed); state = 0; // Go back to the initial state. return output; default: throw new IllegalStateException( "Unexpected state: " + state); } // switch } // if else if (parsed == END_OF_INPUT) { return lastByte(); } } // while return lastByte(); } @SuppressWarnings({"UnusedAssignment"}) private int lastByte() throws IOException { int output = -1; switch (state) { case 1: // 1 byte of encoded input... no output can be written! throw new Base64FormatException( "Base64 encoded input is not terminated properly!"); case 0: case 2: // 2 bytes of encoded input, the first decoded byte has been written. case 3: // 3 bytes of encoded input, the second decoded byte has been written. case 4: // 4 bytes parsed, third byte already written. output = -1; break; default: throw new IllegalStateException( "Unexpected state: " + state); } // switch // Set 'end of file' state. state = -1; return output; } /** * Returns the first decoded byte, given the first two bytes of * a set of BASE64 encoded input. * * @param byte1 The first encoded byte. * @param byte2 The second encoded byte. * @return The first decoded byte. */ private static int get1(byte byte1, byte byte2) { return ((byte1 & 0x3f) << 2) | ((byte2 & 0x30) >>> 4); } /** * Returns the second decoded byte, given the second and third bytes * of a aset of BASE64 encoded input. * * @param byte2 The second encoded byte. * @param byte3 The third encoded byte. * @return The second decoded byte. */ private static int get2(byte byte2, byte byte3) { return ((byte2 & 0x0f) << 4) | ((byte3 & 0x3c) >>> 2); } private static int get3(byte byte3, byte byte4) { return ((byte3 & 0x03) << 6) | (byte4 & 0x3f); } /** * Checks a character for correct BASE64 encoding. Returns -1 if the * character is not valid. Returns END_OF_INPUT if 'ch' is the end of * input character. * * @param ch the input character * @return int The binary value of the input character, or -1 if it is * not a valid BASE64 character. */ public static int parseCharacter(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 { switch (ch) { case'=': return END_OF_INPUT; case'+': return 62; case'/': return 63; default: // Invalid BASE64 character. return -1; } } } }