View Javadoc
1   /*
2    * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  /* FROM mail.jar */
27  package com.sun.xml.internal.org.jvnet.mimepull;
28  
29  import java.io.*;
30  
31  /**
32   * This class implements a UUDecoder. It is implemented as
33   * a FilterInputStream, so one can just wrap this class around
34   * any input stream and read bytes from this filter. The decoding
35   * is done as the bytes are read out.
36   *
37   * @author John Mani
38   * @author Bill Shannon
39   */
40  
41  final class UUDecoderStream extends FilterInputStream {
42      private String name;
43      private int mode;
44  
45      private byte[] buffer = new byte[45]; // max decoded chars in a line = 45
46      private int bufsize = 0;    // size of the cache
47      private int index = 0;      // index into the cache
48      private boolean gotPrefix = false;
49      private boolean gotEnd = false;
50      private LineInputStream lin;
51      private boolean ignoreErrors;
52      private boolean ignoreMissingBeginEnd;
53      private String readAhead;
54  
55      /**
56       * Create a UUdecoder that decodes the specified input stream.
57       * The System property <code>mail.mime.uudecode.ignoreerrors</code>
58       * controls whether errors in the encoded data cause an exception
59       * or are ignored.  The default is false (errors cause exception).
60       * The System property <code>mail.mime.uudecode.ignoremissingbeginend</code>
61       * controls whether a missing begin or end line cause an exception
62       * or are ignored.  The default is false (errors cause exception).
63       * @param in        the input stream
64       */
65      public UUDecoderStream(InputStream in) {
66          super(in);
67          lin = new LineInputStream(in);
68          // default to false
69          ignoreErrors = PropUtil.getBooleanSystemProperty(
70              "mail.mime.uudecode.ignoreerrors", false);
71          // default to false
72          ignoreMissingBeginEnd = PropUtil.getBooleanSystemProperty(
73              "mail.mime.uudecode.ignoremissingbeginend", false);
74      }
75  
76      /**
77       * Create a UUdecoder that decodes the specified input stream.
78       * @param in                the input stream
79       * @param ignoreErrors      ignore errors?
80       * @param ignoreMissingBeginEnd     ignore missing begin or end?
81       */
82      public UUDecoderStream(InputStream in, boolean ignoreErrors,
83                                  boolean ignoreMissingBeginEnd) {
84          super(in);
85          lin = new LineInputStream(in);
86          this.ignoreErrors = ignoreErrors;
87          this.ignoreMissingBeginEnd = ignoreMissingBeginEnd;
88      }
89  
90      /**
91       * Read the next decoded byte from this input stream. The byte
92       * is returned as an <code>int</code> in the range <code>0</code>
93       * to <code>255</code>. If no byte is available because the end of
94       * the stream has been reached, the value <code>-1</code> is returned.
95       * This method blocks until input data is available, the end of the
96       * stream is detected, or an exception is thrown.
97       *
98       * @return     next byte of data, or <code>-1</code> if the end of
99       *             stream is reached.
100      * @exception  IOException  if an I/O error occurs.
101      * @see        java.io.FilterInputStream#in
102      */
103     @Override
104     public int read() throws IOException {
105         if (index >= bufsize) {
106             readPrefix();
107             if (!decode()) {
108                 return -1;
109             }
110             index = 0; // reset index into buffer
111         }
112         return buffer[index++] & 0xff; // return lower byte
113     }
114 
115     @Override
116     public int read(byte[] buf, int off, int len) throws IOException {
117         int i, c;
118         for (i = 0; i < len; i++) {
119             if ((c = read()) == -1) {
120                 if (i == 0) {// At end of stream, so we should
121                     i = -1; // return -1, NOT 0.
122                 }
123                 break;
124             }
125             buf[off+i] = (byte)c;
126         }
127         return i;
128     }
129 
130     @Override
131     public boolean markSupported() {
132         return false;
133     }
134 
135     @Override
136     public int available() throws IOException {
137          // This is only an estimate, since in.available()
138          // might include CRLFs too ..
139          return ((in.available() * 3)/4 + (bufsize-index));
140     }
141 
142     /**
143      * Get the "name" field from the prefix. This is meant to
144      * be the pathname of the decoded file
145      *
146      * @return     name of decoded file
147      * @exception  IOException  if an I/O error occurs.
148      */
149     public String getName() throws IOException {
150         readPrefix();
151         return name;
152     }
153 
154     /**
155      * Get the "mode" field from the prefix. This is the permission
156      * mode of the source file.
157      *
158      * @return     permission mode of source file
159      * @exception  IOException  if an I/O error occurs.
160      */
161     public int getMode() throws IOException {
162         readPrefix();
163         return mode;
164     }
165 
166     /**
167      * UUencoded streams start off with the line:
168      *  "begin <mode> <filename>"
169      * Search for this prefix and gobble it up.
170      */
171     private void readPrefix() throws IOException {
172         if (gotPrefix) {
173             return;
174         }
175 
176         mode = 0666;            // defaults, overridden below
177         name = "encoder.buf";   // same default used by encoder
178         String line;
179         for (;;) {
180             // read till we get the prefix: "begin MODE FILENAME"
181             line = lin.readLine(); // NOTE: readLine consumes CRLF pairs too
182             if (line == null) {
183                 if (!ignoreMissingBeginEnd) {
184                     throw new DecodingException("UUDecoder: Missing begin");
185                 }
186                 // at EOF, fake it
187                 gotPrefix = true;
188                 gotEnd = true;
189                 break;
190             }
191             if (line.regionMatches(false, 0, "begin", 0, 5)) {
192                 try {
193                     mode = Integer.parseInt(line.substring(6,9));
194                 } catch (NumberFormatException ex) {
195                     if (!ignoreErrors) {
196                         throw new DecodingException(
197                                 "UUDecoder: Error in mode: " + ex.toString());
198                     }
199                 }
200                 if (line.length() > 10) {
201                     name = line.substring(10);
202                 } else {
203                     if (!ignoreErrors) {
204                         throw new DecodingException(
205                                 "UUDecoder: Missing name: " + line);
206                     }
207                 }
208                 gotPrefix = true;
209                 break;
210             } else if (ignoreMissingBeginEnd && line.length() != 0) {
211                 int count = line.charAt(0);
212                 count = (count - ' ') & 0x3f;
213                 int need = ((count * 8)+5)/6;
214                 if (need == 0 || line.length() >= need + 1) {
215                     /*
216                      * Looks like a legitimate encoded line.
217                      * Pretend we saw the "begin" line and
218                      * save this line for later processing in
219                      * decode().
220                      */
221                     readAhead = line;
222                     gotPrefix = true;   // fake it
223                     break;
224                 }
225             }
226         }
227     }
228 
229     private boolean decode() throws IOException {
230 
231         if (gotEnd) {
232             return false;
233         }
234         bufsize = 0;
235         int count = 0;
236         String line;
237         for (;;) {
238             /*
239              * If we ignored a missing "begin", the first line
240              * will be saved in readAhead.
241              */
242             if (readAhead != null) {
243                 line = readAhead;
244                 readAhead = null;
245             } else {
246                 line = lin.readLine();
247             }
248 
249             /*
250              * Improperly encoded data sometimes omits the zero length
251              * line that starts with a space character, we detect the
252              * following "end" line here.
253              */
254             if (line == null) {
255                 if (!ignoreMissingBeginEnd) {
256                     throw new DecodingException(
257                                         "UUDecoder: Missing end at EOF");
258                 }
259                 gotEnd = true;
260                 return false;
261             }
262             if (line.equals("end")) {
263                 gotEnd = true;
264                 return false;
265             }
266             if (line.length() == 0) {
267                 continue;
268             }
269             count = line.charAt(0);
270             if (count < ' ') {
271                 if (!ignoreErrors) {
272                     throw new DecodingException(
273                                         "UUDecoder: Buffer format error");
274                 }
275                 continue;
276             }
277 
278             /*
279              * The first character in a line is the number of original (not
280              *  the encoded atoms) characters in the line. Note that all the
281              *  code below has to handle the <SPACE> character that indicates
282              *  end of encoded stream.
283              */
284             count = (count - ' ') & 0x3f;
285 
286             if (count == 0) {
287                 line = lin.readLine();
288                 if (line == null || !line.equals("end")) {
289                     if (!ignoreMissingBeginEnd) {
290                         throw new DecodingException(
291                                 "UUDecoder: Missing End after count 0 line");
292                     }
293                 }
294                 gotEnd = true;
295                 return false;
296             }
297 
298             int need = ((count * 8)+5)/6;
299 //System.out.println("count " + count + ", need " + need + ", len " + line.length());
300             if (line.length() < need + 1) {
301                 if (!ignoreErrors) {
302                     throw new DecodingException(
303                                         "UUDecoder: Short buffer error");
304                 }
305                 continue;
306             }
307 
308             // got a line we're committed to, break out and decode it
309             break;
310         }
311 
312         int i = 1;
313         byte a, b;
314         /*
315          * A correct uuencoder always encodes 3 characters at a time, even
316          * if there aren't 3 characters left.  But since some people out
317          * there have broken uuencoders we handle the case where they
318          * don't include these "unnecessary" characters.
319          */
320         while (bufsize < count) {
321             // continue decoding until we get 'count' decoded chars
322             a = (byte)((line.charAt(i++) - ' ') & 0x3f);
323             b = (byte)((line.charAt(i++) - ' ') & 0x3f);
324             buffer[bufsize++] = (byte)(((a << 2) & 0xfc) | ((b >>> 4) & 3));
325 
326             if (bufsize < count) {
327                 a = b;
328                 b = (byte)((line.charAt(i++) - ' ') & 0x3f);
329                 buffer[bufsize++] =
330                                 (byte)(((a << 4) & 0xf0) | ((b >>> 2) & 0xf));
331             }
332 
333             if (bufsize < count) {
334                 a = b;
335                 b = (byte)((line.charAt(i++) - ' ') & 0x3f);
336                 buffer[bufsize++] = (byte)(((a << 6) & 0xc0) | (b & 0x3f));
337             }
338         }
339         return true;
340     }
341 
342     /*** begin TEST program *****
343     public static void main(String argv[]) throws Exception {
344         FileInputStream infile = new FileInputStream(argv[0]);
345         UUDecoderStream decoder = new UUDecoderStream(infile);
346         int c;
347 
348         try {
349             while ((c = decoder.read()) != -1)
350                 System.out.write(c);
351             System.out.flush();
352         } catch (Exception e) {
353             e.printStackTrace();
354         }
355     }
356     **** end TEST program ****/
357 }