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 encode 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 encoding strings: 014 * <blockquote><pre> 015 * String unencoded = "webmaster:try2gueSS"; 016 * String encoded = Base64Encoder.encode(unencoded); 017 * </pre></blockquote> 018 * or for encoding streams: 019 * <blockquote><pre> 020 * OutputStream out = new Base64Encoder(System.out); 021 * </pre></blockquote> 022 * 023 * @author <b>Jason Hunter</b>, Copyright © 2000 024 * @version 1.2, 2002/11/01, added encode(byte[]) method to better handle 025 * binary data (thanks to Sean Graham) 026 * @version 1.1, 2000/11/17, fixed bug with sign bit for char values 027 * @version 1.0, 2000/06/11 028 */ 029public class Base64Encoder extends FilterOutputStream { 030 031 private static final char[] chars = { 032 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 033 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 034 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 035 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 036 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 037 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', 038 '8', '9', '+', '/' 039 }; 040 041 private int charCount; 042 private int carryOver; 043 044 /** 045 * Constructs a new Base64 encoder that writes output to the given 046 * OutputStream. 047 * 048 * @param out the output stream 049 */ 050 public Base64Encoder(OutputStream out) { 051 super(out); 052 } 053 054 /** 055 * Writes the given byte to the output stream in an encoded form. 056 * 057 * @exception IOException if an I/O error occurs 058 */ 059 public void write(int b) throws IOException { 060 // Take 24-bits from three octets, translate into four encoded chars 061 // Break lines at 76 chars 062 // If necessary, pad with 0 bits on the right at the end 063 // Use = signs as padding at the end to ensure encodedLength % 4 == 0 064 065 // Remove the sign bit, 066 // thanks to Christian Schweingruber <chrigu@lorraine.ch> 067 if (b < 0) { 068 b += 256; 069 } 070 071 // First byte use first six bits, save last two bits 072 if (charCount % 3 == 0) { 073 int lookup = b >> 2; 074 carryOver = b & 3; // last two bits 075 out.write(chars[lookup]); 076 } 077 // Second byte use previous two bits and first four new bits, 078 // save last four bits 079 else if (charCount % 3 == 1) { 080 int lookup = ((carryOver << 4) + (b >> 4)) & 63; 081 carryOver = b & 15; // last four bits 082 out.write(chars[lookup]); 083 } 084 // Third byte use previous four bits and first two new bits, 085 // then use last six new bits 086 else if (charCount % 3 == 2) { 087 int lookup = ((carryOver << 2) + (b >> 6)) & 63; 088 out.write(chars[lookup]); 089 lookup = b & 63; // last six bits 090 out.write(chars[lookup]); 091 carryOver = 0; 092 } 093 charCount++; 094 095 // Add newline every 76 output chars (that's 57 input chars) 096 if (charCount % 57 == 0) { 097 out.write('\n'); 098 } 099 } 100 101 /** 102 * Writes the given byte array to the output stream in an 103 * encoded form. 104 * 105 * @param b the data to be written 106 * @param off the start offset of the data 107 * @param len the length of the data 108 * @exception IOException if an I/O error occurs 109 */ 110 public void write(byte[] buf, int off, int len) throws IOException { 111 // This could of course be optimized 112 for (int i = 0; i < len; i++) { 113 write(buf[off + i]); 114 } 115 } 116 117 /** 118 * Closes the stream, this MUST be called to ensure proper padding is 119 * written to the end of the output stream. 120 * 121 * @exception IOException if an I/O error occurs 122 */ 123 public void close() throws IOException { 124 // Handle leftover bytes 125 if (charCount % 3 == 1) { // one leftover 126 int lookup = (carryOver << 4) & 63; 127 out.write(chars[lookup]); 128 out.write('='); 129 out.write('='); 130 } 131 else if (charCount % 3 == 2) { // two leftovers 132 int lookup = (carryOver << 2) & 63; 133 out.write(chars[lookup]); 134 out.write('='); 135 } 136 super.close(); 137 } 138 139 /** 140 * Returns the encoded form of the given unencoded string. The encoder 141 * uses the ISO-8859-1 (Latin-1) encoding to convert the string to bytes. 142 * For greater control over the encoding, encode the string to bytes 143 * yourself and use encode(byte[]). 144 * 145 * @param unencoded the string to encode 146 * @return the encoded form of the unencoded string 147 */ 148 public static String encode(String unencoded) { 149 byte[] bytes = null; 150 try { 151 bytes = unencoded.getBytes("8859_1"); 152 } 153 catch (UnsupportedEncodingException ignored) { } 154 return encode(bytes); 155 } 156 157 /** 158 * Returns the encoded form of the given unencoded string. 159 * 160 * @param unencoded the string to encode 161 * @return the encoded form of the unencoded string 162 */ 163 public static String encode(byte[] bytes) { 164 ByteArrayOutputStream out = 165 new ByteArrayOutputStream((int) (bytes.length * 1.37)); 166 Base64Encoder encodedOut = new Base64Encoder(out); 167 168 try { 169 encodedOut.write(bytes); 170 encodedOut.close(); 171 172 return out.toString("8859_1"); 173 } 174 catch (IOException ignored) { return null; } 175 } 176 177 public static void main(String[] args) throws Exception { 178 if (args.length != 1) { 179 System.err.println( 180 "Usage: java com.oreilly.servlet.Base64Encoder fileToEncode"); 181 return; 182 } 183 184 Base64Encoder encoder = null; 185 BufferedInputStream in = null; 186 try { 187 encoder = new Base64Encoder(System.out); 188 in = new BufferedInputStream(new FileInputStream(args[0])); 189 190 byte[] buf = new byte[4 * 1024]; // 4K buffer 191 int bytesRead; 192 while ((bytesRead = in.read(buf)) != -1) { 193 encoder.write(buf, 0, bytesRead); 194 } 195 } 196 finally { 197 if (in != null) in.close(); 198 if (encoder != null) encoder.close(); 199 } 200 } 201}