001: /*
002: * Gruntspud
003: *
004: * Copyright (C) 2002 Brett Smith.
005: *
006: * Written by: Brett Smith <t_magicthize@users.sourceforge.net>
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Library General Public License
010: * as published by the Free Software Foundation; either version 2 of
011: * the License, or (at your option) any later version.
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU Library General Public License for more details.
016: *
017: * You should have received a copy of the GNU Library General Public
018: * License along with this program; if not, write to the Free Software
019: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
020: */
021:
022: package gruntspud;
023:
024: import java.io.BufferedInputStream;
025: import java.io.BufferedOutputStream;
026: import java.io.BufferedReader;
027: import java.io.BufferedWriter;
028: import java.io.ByteArrayOutputStream;
029: import java.io.CharArrayWriter;
030: import java.io.File;
031: import java.io.FileInputStream;
032: import java.io.FileOutputStream;
033: import java.io.FileReader;
034: import java.io.FileWriter;
035: import java.io.InputStream;
036: import java.io.OutputStream;
037: import java.io.Reader;
038: import java.io.Writer;
039:
040: /**
041: * Provides encoding of raw bytes to base64-encoded characters, and
042: * decoding of base64 characters to raw bytes.
043: *
044: * @author Kevin Kelley (kelley@ruralnet.net)
045: * @version 1.3
046: * @date 06 August 1998
047: * @modified 14 February 2000
048: * @modified 22 September 2000
049: */
050: public class Base64 {
051: //
052: // code characters for values 0..63
053: //
054: static private char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
055: .toCharArray();
056:
057: //
058: // lookup table for converting base64 characters to value in range 0..63
059: //
060: static private byte[] codes = new byte[256];
061:
062: static {
063: for (int i = 0; i < 256; i++) {
064: codes[i] = -1;
065:
066: }
067: for (int i = 'A'; i <= 'Z'; i++) {
068: codes[i] = (byte) (i - 'A');
069:
070: }
071: for (int i = 'a'; i <= 'z'; i++) {
072: codes[i] = (byte) ((26 + i) - 'a');
073:
074: }
075: for (int i = '0'; i <= '9'; i++) {
076: codes[i] = (byte) ((52 + i) - '0');
077:
078: }
079: codes['+'] = 62;
080: codes['/'] = 63;
081: }
082:
083: /**
084: * returns an array of base64-encoded characters to represent the
085: * passed data array.
086: *
087: * @param data the array of bytes to encode
088: * @return base64-coded character array.
089: */
090: static public char[] encode(byte[] data) {
091: char[] out = new char[((data.length + 2) / 3) * 4];
092:
093: //
094: // 3 bytes encode to 4 chars. Output is always an even
095: // multiple of 4 characters.
096: //
097: for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
098: boolean quad = false;
099: boolean trip = false;
100:
101: int val = (0xFF & (int) data[i]);
102: val <<= 8;
103:
104: if ((i + 1) < data.length) {
105: val |= (0xFF & (int) data[i + 1]);
106: trip = true;
107: }
108:
109: val <<= 8;
110:
111: if ((i + 2) < data.length) {
112: val |= (0xFF & (int) data[i + 2]);
113: quad = true;
114: }
115:
116: out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
117: val >>= 6;
118: out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)];
119: val >>= 6;
120: out[index + 1] = alphabet[val & 0x3F];
121: val >>= 6;
122: out[index + 0] = alphabet[val & 0x3F];
123: }
124:
125: return out;
126: }
127:
128: /**
129: * Decodes a BASE-64 encoded stream to recover the original
130: * data. White space before and after will be trimmed away,
131: * but no other manipulation of the input will be performed.
132: *
133: * As of version 1.2 this method will properly handle input
134: * containing junk characters (newlines and the like) rather
135: * than throwing an error. It does this by pre-parsing the
136: * input and generating from that a count of VALID input
137: * characters.
138: **/
139: static public byte[] decode(char[] data) {
140: // as our input could contain non-BASE64 data (newlines,
141: // whitespace of any sort, whatever) we must first adjust
142: // our count of USABLE data so that...
143: // (a) we don't misallocate the output array, and
144: // (b) think that we miscalculated our data length
145: // just because of extraneous throw-away junk
146: int tempLen = data.length;
147:
148: for (int ix = 0; ix < data.length; ix++) {
149: if ((data[ix] > 255) || (codes[data[ix]] < 0)) {
150: --tempLen; // ignore non-valid chars and padding
151: }
152: }
153:
154: // calculate required length:
155: // -- 3 bytes for every 4 valid base64 chars
156: // -- plus 2 bytes if there are 3 extra base64 chars,
157: // or plus 1 byte if there are 2 extra.
158: int len = (tempLen / 4) * 3;
159:
160: if ((tempLen % 4) == 3) {
161: len += 2;
162:
163: }
164: if ((tempLen % 4) == 2) {
165: len += 1;
166:
167: }
168: byte[] out = new byte[len];
169:
170: int shift = 0; // # of excess bits stored in accum
171: int accum = 0; // excess bits
172: int index = 0;
173:
174: // we now go through the entire array (NOT using the 'tempLen' value)
175: for (int ix = 0; ix < data.length; ix++) {
176: int value = (data[ix] > 255) ? (-1) : codes[data[ix]];
177:
178: if (value >= 0) { // skip over non-code
179: accum <<= 6; // bits shift up by 6 each time thru
180: shift += 6; // loop, with new bits being put in
181: accum |= value; // at the bottom.
182:
183: if (shift >= 8) { // whenever there are 8 or more shifted in,
184: shift -= 8; // write them out (from the top, leaving any
185: out[index++] = (byte) ((accum >> shift) & 0xff);
186: }
187: }
188:
189: // we will also have skipped processing a padding null byte ('=') here;
190: // these are used ONLY for padding to an even length and do not legally
191: // occur as encoded data. for this reason we can ignore the fact that
192: // no index++ operation occurs in that special case: the out[] array is
193: // initialized to all-zero bytes to start with and that works to our
194: // advantage in this combination.
195: }
196:
197: // if there is STILL something wrong we just have to throw up now!
198: if (index != out.length) {
199: throw new Error("Miscalculated data length (wrote " + index
200: + " instead of " + out.length + ")");
201: }
202:
203: return out;
204: }
205:
206: ///////////////////////////////////////////////////
207: // remainder (main method and helper functions) is
208: // for testing purposes only, feel free to clip it.
209: ///////////////////////////////////////////////////
210: public static void main(String[] args) {
211: boolean decode = false;
212:
213: if (args.length == 0) {
214: System.out
215: .println("usage: java Base64 [-d[ecode]] filename");
216: System.exit(0);
217: }
218:
219: for (int i = 0; i < args.length; i++) {
220: if ("-decode".equalsIgnoreCase(args[i])) {
221: decode = true;
222: } else if ("-d".equalsIgnoreCase(args[i])) {
223: decode = true;
224: }
225: }
226:
227: String filename = args[args.length - 1];
228: File file = new File(filename);
229:
230: if (!file.exists()) {
231: System.out.println("Error: file '" + filename
232: + "' doesn't exist!");
233: System.exit(0);
234: }
235:
236: if (decode) {
237: char[] encoded = readChars(file);
238: byte[] decoded = decode(encoded);
239: writeBytes(file, decoded);
240: } else {
241: byte[] decoded = readBytes(file);
242: char[] encoded = encode(decoded);
243: writeChars(file, encoded);
244: }
245: }
246:
247: private static byte[] readBytes(File file) {
248: ByteArrayOutputStream baos = new ByteArrayOutputStream();
249:
250: try {
251: InputStream fis = new FileInputStream(file);
252: InputStream is = new BufferedInputStream(fis);
253: int count = 0;
254: byte[] buf = new byte[16384];
255:
256: while ((count = is.read(buf)) != -1) {
257: if (count > 0) {
258: baos.write(buf, 0, count);
259: }
260: }
261:
262: is.close();
263: } catch (Exception e) {
264: e.printStackTrace();
265: }
266:
267: return baos.toByteArray();
268: }
269:
270: private static char[] readChars(File file) {
271: CharArrayWriter caw = new CharArrayWriter();
272:
273: try {
274: Reader fr = new FileReader(file);
275: Reader in = new BufferedReader(fr);
276: int count = 0;
277: char[] buf = new char[16384];
278:
279: while ((count = in.read(buf)) != -1) {
280: if (count > 0) {
281: caw.write(buf, 0, count);
282: }
283: }
284:
285: in.close();
286: } catch (Exception e) {
287: e.printStackTrace();
288: }
289:
290: return caw.toCharArray();
291: }
292:
293: private static void writeBytes(File file, byte[] data) {
294: try {
295: OutputStream fos = new FileOutputStream(file);
296: OutputStream os = new BufferedOutputStream(fos);
297: os.write(data);
298: os.close();
299: } catch (Exception e) {
300: e.printStackTrace();
301: }
302: }
303:
304: private static void writeChars(File file, char[] data) {
305: try {
306: Writer fos = new FileWriter(file);
307: Writer os = new BufferedWriter(fos);
308: os.write(data);
309: os.close();
310: } catch (Exception e) {
311: e.printStackTrace();
312: }
313: }
314:
315: ///////////////////////////////////////////////////
316: // end of test code.
317: ///////////////////////////////////////////////////
318: }
|