001// Copyright (C) 1999-2002 by Jason Hunter <jhunter_AT_acm_DOT_org>. 002// All rights reserved. Use of this class is limited. 003// Please see the LICENSE for more information. 004 005package com.oreilly.servlet; 006 007import java.io.*; 008 009/** 010 * A class to decode Base64 streams and strings. 011 * See RFC 1521 section 5.2 for details of the Base64 algorithm. 012 * <p> 013 * This class can be used for decoding strings: 014 * <blockquote><pre> 015 * String encoded = "d2VibWFzdGVyOnRyeTJndWVTUw"; 016 * String decoded = Base64Decoder.decode(encoded); 017 * </pre></blockquote> 018 * or for decoding streams: 019 * <blockquote><pre> 020 * InputStream in = new Base64Decoder(System.in); 021 * </pre></blockquote> 022 * 023 * @author <b>Jason Hunter</b>, Copyright © 2000 024 * @version 1.1, 2002/11/01, added decodeToBytes() to better handle binary 025 * data (thanks to Sean Graham) 026 * @version 1.0, 2000/06/11 027 */ 028public class Base64Decoder extends FilterInputStream { 029 030 private static final char[] chars = { 031 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 032 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 033 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 034 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 035 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 036 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', 037 '8', '9', '+', '/' 038 }; 039 040 // A mapping between char values and six-bit integers 041 private static final int[] ints = new int[128]; 042 static { 043 for (int i = 0; i < 64; i++) { 044 ints[chars[i]] = i; 045 } 046 } 047 048 private int charCount; 049 private int carryOver; 050 051 /** 052 * Constructs a new Base64 decoder that reads input from the given 053 * InputStream. 054 * 055 * @param in the input stream 056 */ 057 public Base64Decoder(InputStream in) { 058 super(in); 059 } 060 061 /** 062 * Returns the next decoded character from the stream, or -1 if 063 * end of stream was reached. 064 * 065 * @return the decoded character, or -1 if the end of the 066 * input stream is reached 067 * @exception IOException if an I/O error occurs 068 */ 069 public int read() throws IOException { 070 // Read the next non-whitespace character 071 int x; 072 do { 073 x = in.read(); 074 if (x == -1) { 075 return -1; 076 } 077 } while (Character.isWhitespace((char)x)); 078 charCount++; 079 080 // The '=' sign is just padding 081 if (x == '=') { 082 return -1; // effective end of stream 083 } 084 085 // Convert from raw form to 6-bit form 086 x = ints[x]; 087 088 // Calculate which character we're decoding now 089 int mode = (charCount - 1) % 4; 090 091 // First char save all six bits, go for another 092 if (mode == 0) { 093 carryOver = x & 63; 094 return read(); 095 } 096 // Second char use previous six bits and first two new bits, 097 // save last four bits 098 else if (mode == 1) { 099 int decoded = ((carryOver << 2) + (x >> 4)) & 255; 100 carryOver = x & 15; 101 return decoded; 102 } 103 // Third char use previous four bits and first four new bits, 104 // save last two bits 105 else if (mode == 2) { 106 int decoded = ((carryOver << 4) + (x >> 2)) & 255; 107 carryOver = x & 3; 108 return decoded; 109 } 110 // Fourth char use previous two bits and all six new bits 111 else if (mode == 3) { 112 int decoded = ((carryOver << 6) + x) & 255; 113 return decoded; 114 } 115 return -1; // can't actually reach this line 116 } 117 118 /** 119 * Reads decoded data into an array of bytes and returns the actual 120 * number of bytes read, or -1 if end of stream was reached. 121 * 122 * @param buf the buffer into which the data is read 123 * @param off the start offset of the data 124 * @param len the maximum number of bytes to read 125 * @return the actual number of bytes read, or -1 if the end of the 126 * input stream is reached 127 * @exception IOException if an I/O error occurs 128 */ 129 public int read(byte[] buf, int off, int len) throws IOException { 130 if (buf.length < (len + off - 1)) { 131 throw new IOException("The input buffer is too small: " + len + 132 " bytes requested starting at offset " + off + " while the buffer " + 133 " is only " + buf.length + " bytes long."); 134 } 135 136 // This could of course be optimized 137 int i; 138 for (i = 0; i < len; i++) { 139 int x = read(); 140 if (x == -1 && i == 0) { // an immediate -1 returns -1 141 return -1; 142 } 143 else if (x == -1) { // a later -1 returns the chars read so far 144 break; 145 } 146 buf[off + i] = (byte) x; 147 } 148 return i; 149 } 150 151 /** 152 * Returns the decoded form of the given encoded string, as a String. 153 * Note that not all binary data can be represented as a String, so this 154 * method should only be used for encoded String data. Use decodeToBytes() 155 * otherwise. 156 * 157 * @param encoded the string to decode 158 * @return the decoded form of the encoded string 159 */ 160 public static String decode(String encoded) { 161 return new String(decodeToBytes(encoded)); 162 } 163 164 /** 165 * Returns the decoded form of the given encoded string, as bytes. 166 * 167 * @param encoded the string to decode 168 * @return the decoded form of the encoded string 169 */ 170 public static byte[] decodeToBytes(String encoded) { 171 byte[] bytes = null; 172 try { 173 bytes = encoded.getBytes("8859_1"); 174 } 175 catch (UnsupportedEncodingException ignored) { } 176 177 Base64Decoder in = new Base64Decoder( 178 new ByteArrayInputStream(bytes)); 179 180 ByteArrayOutputStream out = 181 new ByteArrayOutputStream((int) (bytes.length * 0.67)); 182 183 try { 184 byte[] buf = new byte[4 * 1024]; // 4K buffer 185 int bytesRead; 186 while ((bytesRead = in.read(buf)) != -1) { 187 out.write(buf, 0, bytesRead); 188 } 189 out.close(); 190 191 return out.toByteArray(); 192 } 193 catch (IOException ignored) { return null; } 194 } 195 196 public static void main(String[] args) throws Exception { 197 if (args.length != 1) { 198 System.err.println("Usage: java Base64Decoder fileToDecode"); 199 return; 200 } 201 202 Base64Decoder decoder = null; 203 try { 204 decoder = new Base64Decoder( 205 new BufferedInputStream( 206 new FileInputStream(args[0]))); 207 byte[] buf = new byte[4 * 1024]; // 4K buffer 208 int bytesRead; 209 while ((bytesRead = decoder.read(buf)) != -1) { 210 System.out.write(buf, 0, bytesRead); 211 } 212 } 213 finally { 214 if (decoder != null) decoder.close(); 215 } 216 } 217}