/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.commons.io.base64; import java.io.IOException; import java.io.InputStream; /** * <code>Base64InputStream</code> is an <code>InputStream</code> that decodes Base64-encoded data provided by * an underlying <code>InputStream</code>. The underlying data must be valid base64-encoded data with respect to * the character table in use. If not, an <code>IOException</code> will be thrown when illegal data is encountered. * * @see Base64Decoder * @author Maxence Bernard */ public class Base64InputStream extends InputStream { /** Underlying stream data is read from */ private InputStream in; /** The Base64 decoding table */ private final int[] decodingTable; /** The character used for padding */ private final byte paddingChar; /** Decoded bytes available for reading */ private int readBuffer[] = new int[3]; /** Index of the next byte available for reading in the buffer */ private int readOffset; /** Number of bytes left for reading in the buffer */ private int bytesLeft; /** Buffer used temporarily for decoding */ private int decodeBuffer[] = new int[4]; /** * Equivalent to calling {@link #Base64InputStream(java.io.InputStream, Base64Table)} with * a {@link Base64Table#STANDARD_TABLE} table. * * @param in underlying InputStream the Base64-encoded data is read from */ public Base64InputStream(InputStream in) { this(in, Base64Table.STANDARD_TABLE); } /** * Creates a new <code>Base64InputStream</code> that allows to decode data that has been Base64-encoded using the * given table, from the provided <code>InputStream</code>. * * @param in underlying InputStream the Base64-encoded data is read from * @param table the table to use for Base64 decoding */ public Base64InputStream(InputStream in, Base64Table table) { this.in = in; this.decodingTable = table.getDecodingTable(); this.paddingChar = table.getPaddingChar(); } //////////////////////////////// // InputStream implementation // //////////////////////////////// @Override public int read() throws IOException { // Read buffer empty: read and decode a new base64-encoded 4-byte group if(bytesLeft==0) { int read; int nbRead = 0; while(nbRead<4) { read = in.read(); // EOF reached if(read==-1) { if(nbRead%4 != 0) { // Base64 encoded data must come in a multiple of 4 bytes, throw an IOException if the underlying stream ended prematurely throw new IOException("InputStream did not end on a multiple of 4 bytes"); } if(nbRead==0) return -1; else // nbRead==4 break; } decodeBuffer[nbRead] = decodingTable[read]; // Discard any character that's not a base64 character, without throwing an IOException. // In particular, '\r' and '\n' characters that are usually found in email attachments are simply ignored. if(decodeBuffer[nbRead]==-1 && read!=paddingChar) { continue; } nbRead++; } // Decode byte 0 readBuffer[bytesLeft++] = ((decodeBuffer[0]<<2)&0xFC | ((decodeBuffer[1]>>4)&0x03)); // Test if the character is not a padding character if(decodeBuffer[2]!=-1) { // Decode byte 1 readBuffer[bytesLeft++] = (decodeBuffer[1]<<4)&0xF0 | ((decodeBuffer[2]>>2)&0x0F); // Test if the character is a padding character if(decodeBuffer[3]!=-1) // Decode byte 2 readBuffer[bytesLeft++] = ((decodeBuffer[2]<<6)&0xC0) | (decodeBuffer[3]&0x3F); } readOffset = 0; } bytesLeft--; return readBuffer[readOffset++]; } }