Source code: com/anotherbigidea/util/Base64.java
1 /****************************************************************
2 * Copyright (c) 2001, David N. Main, All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the
6 * following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
16 *
17 * 3. The name of the author may not be used to endorse or
18 * promote products derived from this software without specific
19 * prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
31 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ****************************************************************/
34 package com.anotherbigidea.util;
35
36 import java.util.Hashtable;
37 import java.io.*;
38
39 /**
40 * Base64 encoding/decoding utilities
41 */
42 public class Base64
43 {
44 public static final char[] charset =
45 {
46 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
47 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
48 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
49 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
50 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
51 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
52 'w', 'x', 'y', 'z', '0', '1', '2', '3',
53 '4', '5', '6', '7', '8', '9', '+', '/'
54 };
55
56 public static final char paddingChar = '=';
57
58 protected static Hashtable charLookup = new Hashtable();
59
60 static //initialize the hashtable
61 {
62 for( int i = 0; i < charset.length; i++ )
63 {
64 charLookup.put( new Character( charset[i] ),
65 new Integer( i ) );
66 }
67 }
68
69 public static void decode( Reader in, OutputStream out )
70 throws Exception
71 {
72 char[] chars = new char[4];
73 int[] sixbit = new int[4];
74
75 //--Process the input stream in 4-character chunks
76 while( true )
77 {
78 int numread = 0;
79
80 while( numread < 4 )
81 {
82 int read = in.read();
83 if( read < 0 ) break; //end of input
84
85 char aChar = (char)read;
86
87 if( Character.isWhitespace( aChar ) ) continue; //skip w/s
88
89 chars[ numread++ ] = aChar;
90 }
91
92 if( numread == 0 ) return; //end of input
93
94 if( numread != 4 )
95 throw new Exception( "Incomplete character quartet at end of Base64 input" );
96
97 //--Convert chars to six-bit values
98 for( int i = 0; i < 4; i++ )
99 {
100 Integer value = (Integer)charLookup.get( new Character( chars[i] ) );
101
102 if( value == null )
103 {
104 if( chars[i] != '=' || i < 2 )
105 throw new Exception( "Invalid char ("
106 + chars[i] + ") in Base64 data" );
107
108 sixbit[i] = -1;
109 }
110 else
111 {
112 sixbit[i] = value.intValue();
113 }
114 }
115
116 //--Write first 6 bits and top 2 bits from second value
117 out.write( (sixbit[0] << 2) + (sixbit[1] >> 4) );
118 //System.out.println( (sixbit[0] << 2) + (sixbit[1] >> 4) );
119
120 //--Get bottom four bits of second value
121 int val = (sixbit[1] & 0xf) << 4;
122
123 if( sixbit[2] >= 0 ) //third value is valid
124 {
125 //--Add top four bits of third value
126 val += sixbit[2] >> 2;
127
128 out.write( val );
129 //System.out.println( val );
130
131 //--Get bottom two bits of third value
132 val = (sixbit[2] & 0x3) << 6;
133
134 if( sixbit[3] >= 0 ) //fourth value is valid
135 {
136 val += sixbit[3];
137
138 out.write( val );
139 //System.out.println( val );
140 }
141 }
142 }
143 }
144
145 public static byte[] decode( String base64 )
146 throws Exception //if base64 is invalid
147 {
148 ByteArrayOutputStream out = new ByteArrayOutputStream();
149 StringReader in = new StringReader( base64 );
150
151 decode( in, out );
152
153 in.close();
154 out.flush();
155 out.close();
156
157 return out.toByteArray();
158 }
159
160 public static void encode( InputStream in, Writer out )
161 throws IOException
162 {
163 int column = 0;
164
165 //--Process 3 bytes in each loop - writing 4 base64 chars to the output
166 while( true )
167 {
168 int byte1 = in.read();
169 int byte2 = in.read();
170 int byte3 = in.read();
171
172 if( byte1 < 0 ) return; //end-of-data
173
174 //--Wrap output at column 72
175 if( column >= 72 ) { column = 0; out.write( '\n' ); }
176
177 out.write( charset[ byte1 >> 2 ] ); //write top 6 bits of byte 1
178
179 int index = (byte1 & 0x3) << 4 ; //get bottom two bits of byte 1
180
181 if( byte2 < 0 ) //no more data
182 {
183 out.write( charset[ index ] );
184 out.write( paddingChar );
185 out.write( paddingChar );
186 return;
187 }
188
189 index += byte2 >> 4; //add the top 4 bits of byte 2
190 out.write( charset[ index ] );
191
192 index = ( byte2 & 0xf ) << 2; //get bottom 4 bits of byte 2
193
194 if( byte3 < 0 ) //more more data
195 {
196 out.write( charset[ index ] );
197 out.write( paddingChar );
198 return;
199 }
200
201 index += byte3 >> 6; //add top 2 bits of byte 3
202 out.write( charset[ index ] );
203
204 out.write( charset[ byte3 & 0x3f ] ); //write bottom 6 bits of byte 3
205
206 //--Advance column counter
207 column += 4;
208 }
209 }
210
211 public static String encode( byte[] data )
212 {
213 try
214 {
215 return encode( data, 0, data.length );
216 }
217 catch( ArrayIndexOutOfBoundsException aiobe )
218 {
219 return aiobe.toString();
220 }
221 }
222
223 public static String encode( byte[] data, int start, int length )
224 throws ArrayIndexOutOfBoundsException
225 {
226 StringWriter out = new StringWriter();
227 ByteArrayInputStream in = new ByteArrayInputStream( data, start, length );
228
229 try
230 {
231 encode( in, out );
232 in.close();
233 out.flush();
234 out.close();
235 }
236 catch( IOException ioe )
237 {
238 return ioe.toString();
239 }
240
241 return out.toString();
242 }
243
244 /**
245 * If args.length > 0 then encode binary on stdin to base64 on stdout, else
246 * decode base64 on stdin to binary on stdout
247 */
248 public static void main( String[] args ) throws Exception
249 {
250 if( args.length > 0 )
251 {
252 OutputStreamWriter out = new OutputStreamWriter( System.out );
253 encode( System.in, out );
254 out.flush();
255 return;
256 }
257
258 decode( new InputStreamReader( System.in ), System.out );
259 System.out.flush();
260 }
261 }