1/*
2 * Copyright (c) 1997, 2013, 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 */
27package com.sun.xml.internal.org.jvnet.mimepull;
28
29import 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
41final 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}
358