001    /*
002     * This file is part of muCommander, http://www.mucommander.com
003     * Copyright (C) 2002-2008 Maxence Bernard
004     *
005     * muCommander is free software; you can redistribute it and/or modify
006     * it under the terms of the GNU General Public License as published by
007     * the Free Software Foundation; either version 3 of the License, or
008     * (at your option) any later version.
009     *
010     * muCommander is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    
019    package com.mucommander.io.base64;
020    
021    import java.io.IOException;
022    import java.io.InputStream;
023    
024    /**
025     * <code>Base64InputStream</code> is an <code>InputStream</code> that decodes Base64-encoded data provided by
026     * an underlying <code>InputStream</code>. The underlying data must be valid base64-encoded data, if not,
027     * <code>IOException</code> will be thrown when illegal data is encountered.
028     *
029     * @see Base64Decoder
030     * @author Maxence Bernard
031     */
032    public class Base64InputStream extends InputStream {
033    
034        /** Underlying stream data is read from */
035        private InputStream in;
036    
037        /** Decoded bytes available for reading */
038        private int readBuffer[] = new int[3];
039    
040        /** Index of the next byte available for reading in the buffer */
041        private int readOffset;
042    
043        /** Number of bytes left for reading in the buffer */
044        private int bytesLeft;
045    
046        /** Buffer used temporarily for decoding */
047        private int decodeBuffer[] = new int[4];
048    
049        /** Decoding table */
050        private final static int BASE64_DECODING_TABLE[];
051        
052    
053        // Create the base 64 decoding table
054        static {
055            BASE64_DECODING_TABLE = new int[256];
056            int offset;
057            char c;
058    
059            for(c=0; c<256; c++)
060                BASE64_DECODING_TABLE[c] = -1;
061    
062            offset = 0;
063            for(c='A'; c<='Z'; c++)
064                BASE64_DECODING_TABLE[c] = offset++;
065    
066            for(c='a'; c<='z'; c++)
067                BASE64_DECODING_TABLE[c] = offset++;
068    
069            for(c='0'; c<='9'; c++)
070                BASE64_DECODING_TABLE[c] = offset++;
071    
072            BASE64_DECODING_TABLE['+'] = 62;
073            BASE64_DECODING_TABLE['/'] = 63;
074        }
075    
076    
077        /**
078         * Creates a new Base64InputStream that allows to decode Base64-encoded from the provided InputStream.
079         *
080         * @param in underlying InputStream the Base64-encoded data is read from
081         */
082        public Base64InputStream(InputStream in) {
083            this.in = in;
084        }
085    
086    
087        ////////////////////////////////
088        // InputStream implementation //
089        ////////////////////////////////
090    
091        public int read() throws IOException {
092            // Read buffer empty: read and decode a new base64-encoded 4-byte group
093            if(bytesLeft==0) {
094                int read;
095                int nbRead = 0;
096    
097                while(nbRead<4) {
098                    read = in.read();
099                    // EOF reached
100                    if(read==-1) {
101                        if(nbRead%4 != 0) {
102                            // Base64 encoded data must come in a multiple of 4 bytes, throw an IOException if the underlying stream ended prematurely
103                            throw new IOException("InputStream did not end on a multiple of 4 bytes");
104                        }
105    
106                        if(nbRead==0)
107                            return -1;
108                        else    // nbRead==4
109                            break;
110                    }
111    
112                    decodeBuffer[nbRead] = BASE64_DECODING_TABLE[read];
113    
114                    // Discard any character that's not a base64 character, without throwing an IOException.
115                    // In particular, '\r' and '\n' characters that are usually found in email attachments are simply ignored.
116                    if(decodeBuffer[nbRead]==-1 && read!='=') {
117                        continue;
118                    }
119    
120                    nbRead++;
121                }
122    
123                // Decode byte 0
124                readBuffer[bytesLeft++] = ((decodeBuffer[0]<<2)&0xFC | ((decodeBuffer[1]>>4)&0x03));
125    
126                // Test if the character is not padding ('=')
127                if(decodeBuffer[2]!=-1) {
128                    // Decode byte 1
129                    readBuffer[bytesLeft++] = (decodeBuffer[1]<<4)&0xF0 | ((decodeBuffer[2]>>2)&0x0F);
130    
131                    // Test if the character is not padding ('=')
132                    if(decodeBuffer[3]!=-1)
133                        // Decode byte 2
134                        readBuffer[bytesLeft++] = ((decodeBuffer[2]<<6)&0xC0) | (decodeBuffer[3]&0x3F);
135                }
136    
137                readOffset = 0;
138            }
139    
140            bytesLeft--;
141    
142            return readBuffer[readOffset++];
143        }
144    }