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 }