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.ByteArrayOutputStream; 023 import java.io.FilterInputStream; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.io.UnsupportedEncodingException; 027 028 /** 029 * An implementation of a FilterOutputStream that decodes the 030 * stream data in UU encoding format. This version does the 031 * decoding "on the fly" rather than decoding a single block of 032 * data. Since this version is intended for use by the MimeUtilty class, 033 * it also handles line breaks in the encoded data. 034 * 035 * @version $Rev: 467553 $ $Date: 2006-10-25 06:01:51 +0200 (Mi, 25. Okt 2006) $ 036 */ 037 public class UUDecoderStream extends FilterInputStream { 038 // maximum number of chars that can appear in a single line 039 protected static final int MAX_CHARS_PER_LINE = 45; 040 041 // our decoder for processing the data 042 protected UUEncoder decoder = new UUEncoder(); 043 044 // a buffer for one decoding unit's worth of data (45 bytes). 045 protected byte[] decodedChars; 046 // count of characters in the buffer 047 protected int decodedCount = 0; 048 // index of the next decoded character 049 protected int decodedIndex = 0; 050 051 // indicates whether we've already processed the "begin" prefix. 052 protected boolean beginRead = false; 053 054 055 public UUDecoderStream(InputStream in) { 056 super(in); 057 } 058 059 060 /** 061 * Test for the existance of decoded characters in our buffer 062 * of decoded data. 063 * 064 * @return True if we currently have buffered characters. 065 */ 066 private boolean dataAvailable() { 067 return decodedCount != 0; 068 } 069 070 /** 071 * Get the next buffered decoded character. 072 * 073 * @return The next decoded character in the buffer. 074 */ 075 private byte getBufferedChar() { 076 decodedCount--; 077 return decodedChars[decodedIndex++]; 078 } 079 080 /** 081 * Decode a requested number of bytes of data into a buffer. 082 * 083 * @return true if we were able to obtain more data, false otherwise. 084 */ 085 private boolean decodeStreamData() throws IOException { 086 decodedIndex = 0; 087 088 // fill up a data buffer with input data 089 return fillEncodedBuffer() != -1; 090 } 091 092 093 /** 094 * Retrieve a single byte from the decoded characters buffer. 095 * 096 * @return The decoded character or -1 if there was an EOF condition. 097 */ 098 private int getByte() throws IOException { 099 if (!dataAvailable()) { 100 if (!decodeStreamData()) { 101 return -1; 102 } 103 } 104 decodedCount--; 105 return decodedChars[decodedIndex++]; 106 } 107 108 private int getBytes(byte[] data, int offset, int length) throws IOException { 109 110 int readCharacters = 0; 111 while (length > 0) { 112 // need data? Try to get some 113 if (!dataAvailable()) { 114 // if we can't get this, return a count of how much we did get (which may be -1). 115 if (!decodeStreamData()) { 116 return readCharacters > 0 ? readCharacters : -1; 117 } 118 } 119 120 // now copy some of the data from the decoded buffer to the target buffer 121 int copyCount = Math.min(decodedCount, length); 122 System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount); 123 decodedIndex += copyCount; 124 decodedCount -= copyCount; 125 offset += copyCount; 126 length -= copyCount; 127 readCharacters += copyCount; 128 } 129 return readCharacters; 130 } 131 132 /** 133 * Verify that the first line of the buffer is a valid begin 134 * marker. 135 * 136 * @exception IOException 137 */ 138 private void checkBegin() throws IOException { 139 // we only do this the first time we're requested to read from the stream. 140 if (beginRead) { 141 return; 142 } 143 144 // we might have to skip over lines to reach the marker. If we hit the EOF without finding 145 // the begin, that's an error. 146 while (true) { 147 String line = readLine(); 148 if (line == null) { 149 throw new IOException("Missing UUEncode begin command"); 150 } 151 152 // is this our begin? 153 if (line.regionMatches(true, 0, "begin ", 0, 6)) { 154 // This is the droid we're looking for..... 155 beginRead = true; 156 return; 157 } 158 } 159 } 160 161 162 /** 163 * Read a line of data. Returns null if there is an EOF. 164 * 165 * @return The next line read from the stream. Returns null if we 166 * hit the end of the stream. 167 * @exception IOException 168 */ 169 protected String readLine() throws IOException { 170 decodedIndex = 0; 171 // get an accumulator for the data 172 StringBuffer buffer = new StringBuffer(); 173 174 // now process a character at a time. 175 int ch = in.read(); 176 while (ch != -1) { 177 // a naked new line completes the line. 178 if (ch == '\n') { 179 break; 180 } 181 // a carriage return by itself is ignored...we're going to assume that this is followed 182 // by a new line because we really don't have the capability of pushing this back . 183 else if (ch == '\r') { 184 ; 185 } 186 else { 187 // add this to our buffer 188 buffer.append((char)ch); 189 } 190 ch = in.read(); 191 } 192 193 // if we didn't get any data at all, return nothing 194 if (ch == -1 && buffer.length() == 0) { 195 return null; 196 } 197 // convert this into a string. 198 return buffer.toString(); 199 } 200 201 202 /** 203 * Fill our buffer of input characters for decoding from the 204 * stream. This will attempt read a full buffer, but will 205 * terminate on an EOF or read error. This will filter out 206 * non-Base64 encoding chars and will only return a valid 207 * multiple of 4 number of bytes. 208 * 209 * @return The count of characters read. 210 */ 211 private int fillEncodedBuffer() throws IOException 212 { 213 checkBegin(); 214 // reset the buffer position 215 decodedIndex = 0; 216 217 while (true) { 218 219 // we read these as character lines. We need to be looking for the "end" marker for the 220 // end of the data. 221 String line = readLine(); 222 223 // this should NOT be happening.... 224 if (line == null) { 225 throw new IOException("Missing end in UUEncoded data"); 226 } 227 228 // Is this the end marker? EOF baby, EOF! 229 if (line.equalsIgnoreCase("end")) { 230 // this indicates we got nuttin' more to do. 231 return -1; 232 } 233 234 ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_CHARS_PER_LINE); 235 236 byte [] lineBytes; 237 try { 238 lineBytes = line.getBytes("US-ASCII"); 239 } catch (UnsupportedEncodingException e) { 240 throw new IOException("Invalid UUEncoding"); 241 } 242 243 // decode this line 244 decodedCount = decoder.decode(lineBytes, 0, lineBytes.length, out); 245 246 // not just a zero-length line? 247 if (decodedCount != 0) { 248 // get the resulting byte array 249 decodedChars = out.toByteArray(); 250 return decodedCount; 251 } 252 } 253 } 254 255 256 // in order to function as a filter, these streams need to override the different 257 // read() signature. 258 259 public int read() throws IOException 260 { 261 return getByte(); 262 } 263 264 265 public int read(byte [] buffer, int offset, int length) throws IOException { 266 return getBytes(buffer, offset, length); 267 } 268 269 270 public boolean markSupported() { 271 return false; 272 } 273 274 275 public int available() throws IOException { 276 return ((in.available() / 4) * 3) + decodedCount; 277 } 278 } 279