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