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