001 /** 002 * 003 * Copyright 2003-2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.mail.util; 019 020 import java.io.IOException; 021 import java.io.OutputStream; 022 import java.io.UnsupportedEncodingException; 023 024 public class UUEncoder implements Encoder { 025 026 // this is the maximum number of chars allowed per line, since we have to include a uuencoded length at 027 // the start of each line. 028 static private final int MAX_CHARS_PER_LINE = 45; 029 030 031 public UUEncoder() 032 { 033 } 034 035 /** 036 * encode the input data producing a UUEncoded output stream. 037 * 038 * @param data The array of byte data. 039 * @param off The starting offset within the data. 040 * @param length Length of the data to encode. 041 * @param out The output stream the encoded data is written to. 042 * 043 * @return the number of bytes produced. 044 */ 045 public int encode(byte[] data, int off, int length, OutputStream out) throws IOException 046 { 047 int byteCount = 0; 048 049 while (true) { 050 // keep writing complete lines until we've exhausted the data. 051 if (length > MAX_CHARS_PER_LINE) { 052 // encode another line and adjust the length and position 053 byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out); 054 length -= MAX_CHARS_PER_LINE; 055 off += MAX_CHARS_PER_LINE; 056 } 057 else { 058 // last line. Encode the partial and quit 059 byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out); 060 break; 061 } 062 } 063 return byteCount; 064 } 065 066 067 /** 068 * Encode a single line of data (less than or equal to 45 characters). 069 * 070 * @param data The array of byte data. 071 * @param off The starting offset within the data. 072 * @param length Length of the data to encode. 073 * @param out The output stream the encoded data is written to. 074 * 075 * @return The number of bytes written to the output stream. 076 * @exception IOException 077 */ 078 private int encodeLine(byte[] data, int offset, int length, OutputStream out) throws IOException { 079 // write out the number of characters encoded in this line. 080 out.write((byte)((length & 0x3F) + ' ')); 081 byte a; 082 byte b; 083 byte c; 084 085 // count the bytes written...we add 2, one for the length and 1 for the linend terminator. 086 int bytesWritten = 2; 087 088 for (int i = 0; i < length;) { 089 // set the padding defauls 090 b = 1; 091 c = 1; 092 // get the next 3 bytes (if we have them) 093 a = data[offset + i++]; 094 if (i < length) { 095 b = data[offset + i++]; 096 if (i < length) { 097 c = data[offset + i++]; 098 } 099 } 100 101 byte d1 = (byte)(((a >>> 2) & 0x3F) + ' '); 102 byte d2 = (byte)(((( a << 4) & 0x30) | ((b >>> 4) & 0x0F)) + ' '); 103 byte d3 = (byte)((((b << 2) & 0x3C) | ((c >>> 6) & 0x3)) + ' '); 104 byte d4 = (byte)((c & 0x3F) + ' '); 105 106 out.write(d1); 107 out.write(d2); 108 out.write(d3); 109 out.write(d4); 110 111 bytesWritten += 4; 112 } 113 114 // terminate with a linefeed alone 115 out.write('\n'); 116 117 return bytesWritten; 118 } 119 120 121 /** 122 * decode the uuencoded byte data writing it to the given output stream 123 * 124 * @param data The array of byte data to decode. 125 * @param off Starting offset within the array. 126 * @param length The length of data to encode. 127 * @param out The output stream used to return the decoded data. 128 * 129 * @return the number of bytes produced. 130 * @exception IOException 131 */ 132 public int decode(byte[] data, int off, int length, OutputStream out) throws IOException 133 { 134 int bytesWritten = 0; 135 136 while (length > 0) { 137 int lineOffset = off; 138 139 // scan forward looking for a EOL terminator for the next line of data. 140 while (length > 0 && data[off] != '\n') { 141 off++; 142 length--; 143 } 144 145 // go decode this line of data 146 bytesWritten += decodeLine(data, lineOffset, off - lineOffset, out); 147 148 // the offset was left pointing at the EOL character, so step over that one before 149 // scanning again. 150 off++; 151 length--; 152 } 153 return bytesWritten; 154 } 155 156 157 /** 158 * decode a single line of uuencoded byte data writing it to the given output stream 159 * 160 * @param data The array of byte data to decode. 161 * @param off Starting offset within the array. 162 * @param length The length of data to decode (length does NOT include the terminating new line). 163 * @param out The output stream used to return the decoded data. 164 * 165 * @return the number of bytes produced. 166 * @exception IOException 167 */ 168 private int decodeLine(byte[] data, int off, int length, OutputStream out) throws IOException { 169 int count = data[off++]; 170 171 // obtain and validate the count 172 if (count < ' ') { 173 throw new IOException("Invalid UUEncode line length"); 174 } 175 176 count = (count - ' ') & 0x3F; 177 178 // get the rounded count of characters that should have been used to encode this. The + 1 is for the 179 // length encoded at the beginning 180 int requiredLength = (((count * 8) + 5) / 6) + 1; 181 if (length < requiredLength) { 182 throw new IOException("UUEncoded data and length do not match"); 183 } 184 185 int bytesWritten = 0; 186 // now decode the bytes. 187 while (bytesWritten < count) { 188 // even one byte of data requires two bytes to encode, so we should have that. 189 byte a = (byte)((data[off++] - ' ') & 0x3F); 190 byte b = (byte)((data[off++] - ' ') & 0x3F); 191 byte c = 0; 192 byte d = 0; 193 194 // do the first byte 195 byte first = (byte)(((a << 2) & 0xFC) | ((b >>> 4) & 3)); 196 out.write(first); 197 bytesWritten++; 198 199 // still have more bytes to decode? do the second byte of the second. That requires 200 // a third byte from the data. 201 if (bytesWritten < count) { 202 c = (byte)((data[off++] - ' ') & 0x3F); 203 byte second = (byte)(((b << 4) & 0xF0) | ((c >>> 2) & 0x0F)); 204 out.write(second); 205 bytesWritten++; 206 207 // need the third one? 208 if (bytesWritten < count) { 209 d = (byte)((data[off++] - ' ') & 0x3F); 210 byte third = (byte)(((c << 6) & 0xC0) | (d & 0x3F)); 211 out.write(third); 212 bytesWritten++; 213 } 214 } 215 } 216 return bytesWritten; 217 } 218 219 220 /** 221 * decode the UUEncoded String data writing it to the given output stream. 222 * 223 * @param data The String data to decode. 224 * @param out The output stream to write the decoded data to. 225 * 226 * @return the number of bytes produced. 227 * @exception IOException 228 */ 229 public int decode(String data, OutputStream out) throws IOException 230 { 231 try { 232 // just get the byte data and decode. 233 byte[] bytes = data.getBytes("US-ASCII"); 234 return decode(bytes, 0, bytes.length, out); 235 } catch (UnsupportedEncodingException e) { 236 throw new IOException("Invalid UUEncoding"); 237 } 238 } 239 } 240 241