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
020 package com.mucommander.io.base64;
021
022 import java.io.IOException;
023 import java.io.OutputStream;
024
025
026 /**
027 * This <code>OuputStream</code> encodes supplied data to Base64 encoding and writes it to the underlying
028 * <code>OutputStream</code>.
029 *
030 * @see Base64Encoder
031 * @author Maxence Bernard, with the exception of the Base64 description which was found on the Web.
032 */
033 public class Base64OutputStream extends OutputStream {
034 /*
035 Base64 uses a 65 character subset of US-ASCII,
036 allowing 6 bits for each character so the character
037 "m" with a Base64 value of 38, when represented
038 in binary form, is 100110.
039
040 With a text string, let's say "men" is encoded this
041 is what happens :
042
043 The text string is converted into its US-ASCII value.
044
045 The character "m" has the decimal value of 109
046 The character "e" has the decimal value of 101
047 The character "n" has the decimal value of 110
048
049 When converted to binary the string looks like this :
050
051 m 01101101
052 e 01100101
053 n 01101110
054
055 These three "8-bits" are concatenated to make a
056 24 bit stream
057 011011010110010101101110
058
059 This 24 bit stream is then split up into 4 6-bit
060 sections
061 011011 010110 010101 101110
062
063 We now have 4 values. These binary values are
064 converted to decimal form
065 27 22 21 46
066
067 And the corresponding Base64 character are :
068 b W V u
069
070 The encoding is always on a three characters basis
071 (to have a set of 4 Base64 characters). To encode one
072 or two then, we use the special character "=" to pad
073 until 4 base64 characters is reached.
074
075 ex. encode "me"
076
077 01101101 01100101
078 0110110101100101
079 011011 010110 0101
080 111111 (AND to fill the missing bits)
081 011011 010110 010100
082 b W U
083 b W U = ("=" is the padding character)
084
085 so "bWU=" is the base64 equivalent.
086
087 encode "m"
088
089 01101101
090 011011 01
091 111111 (AND to fill the missing bits)
092 011011 010000
093 b Q = = (two paddings are added)
094
095 Finally, MIME specifies that lines are 76 characters wide maximum.
096
097 */
098
099 /** Base 64 translation table */
100 final static char BASE64_ENCODING_TABLE[] = {
101 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
102 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
103 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
104 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
105 };
106
107
108 /** Underlying OutputStream encoded data is sent to */
109 private OutputStream out;
110
111 /** Array used to accumulate the first 2 bytes of a 3-byte group */
112 private byte byteAcc[] = new byte[2];
113
114 /** Number of bytes accumulated to form a 3-byte group */
115 private int nbBytesWaiting;
116
117 /** Specifies whether line breaks should be inserted after 80 chars */
118 private boolean insertLineBreaks;
119
120 /** Current line length (to insert line return character after 80 chars)*/
121 private int lineLength;
122
123
124 /**
125 * Creates a new Base64OutputStream using the underlying OutputStream to write the base64-encoded data to.
126 *
127 * @param out the underlying OutputStream to write the base64-encoded data to
128 * @param insertLineBreaks if <code>true</code>, line breaks will be inserted after every 80 characters written
129 */
130 public Base64OutputStream(OutputStream out, boolean insertLineBreaks) {
131 this.out = out;
132 this.insertLineBreaks = insertLineBreaks;
133 }
134
135 /**
136 * Writes padding '=' characters to the underlying <code>OutputStream</code> if there currently is an
137 * unfinished 3-byte group. If it's not the case, then this method is a no-op.
138 *
139 * @throws IOException if the padding characters could not be written to the underlying OutputStream.
140 */
141 public void writePadding() throws IOException {
142 // No padding needed
143 if(nbBytesWaiting==0)
144 return;
145
146 // 1 padding '=' character
147 if (nbBytesWaiting==2) {
148 // 2 bytes left
149 out.write(BASE64_ENCODING_TABLE[(byte)((byteAcc[0] & 0xFC) >> 2)]);
150 out.write(BASE64_ENCODING_TABLE[(byte)(((byteAcc[0] & 0x03) << 4) | ((byteAcc[1] & 0xF0) >> 4))]);
151 out.write(BASE64_ENCODING_TABLE[(byte)((byteAcc[1] & 0x0F) << 2)]);
152 out.write('=');
153 }
154 // 2 padding '=' characters
155 else if (nbBytesWaiting==1) {
156 // 1 byte left
157 out.write(BASE64_ENCODING_TABLE[(byte)((byteAcc[0] & 0xFC) >> 2)]);
158 out.write(BASE64_ENCODING_TABLE[(byte)((byteAcc[0] & 0x03) << 4)]);
159 out.write('=');
160 out.write('=');
161 }
162
163 // Just in case this method is called again
164 nbBytesWaiting = 0;
165 }
166
167
168 /////////////////////////////////
169 // OutputStream implementation //
170 /////////////////////////////////
171
172 public void write(int i) throws IOException {
173 // We have a 3-byte group
174 if(nbBytesWaiting==2) {
175 // Write 3 bytes as 4 base64 characters
176 out.write(BASE64_ENCODING_TABLE[(byte)((byteAcc[0] & 0xFC) >> 2)]);
177 out.write(BASE64_ENCODING_TABLE[(byte)(((byteAcc[0] & 0x03) << 4) | ((byteAcc[1] & 0xF0) >> 4))]);
178 out.write(BASE64_ENCODING_TABLE[(byte)(((byteAcc[1] & 0x0F) << 2) | ((i & 0xC0) >> 6))]);
179 out.write(BASE64_ENCODING_TABLE[(byte)(i & 0x3F)]);
180
181 nbBytesWaiting = 0;
182
183 // Insert a line break after every 80 characters written
184 if (insertLineBreaks && (lineLength += 4) >= 76) {
185 out.write('\r');
186 out.write('\n');
187 lineLength = 0;
188 }
189 }
190 // Waiting for more bytes...
191 else {
192 byteAcc[nbBytesWaiting++] = (byte)i;
193 }
194 }
195
196 /**
197 * Writes padding if necessary and closes the underlying stream.
198 */
199 public void close() throws IOException {
200 writePadding();
201 out.close();
202 }
203 }