src/share/jaxws_classes/com/sun/xml/internal/org/jvnet/mimepull/BASE64DecoderStream.java

Tue, 09 Apr 2013 14:51:13 +0100

author
alanb
date
Tue, 09 Apr 2013 14:51:13 +0100
changeset 368
0989ad8c0860
parent 0
373ffda63c9a
permissions
-rw-r--r--

8010393: Update JAX-WS RI to 2.2.9-b12941
Reviewed-by: alanb, erikj
Contributed-by: miroslav.kos@oracle.com, martin.grebac@oracle.com

     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  */
    26 /* FROM mail.jar */
    27 package com.sun.xml.internal.org.jvnet.mimepull;
    29 import java.io.*;
    31 /**
    32  * This class implements a BASE64 Decoder. 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  */
    41 final class BASE64DecoderStream extends FilterInputStream {
    42     // buffer of decoded bytes for single byte reads
    43     private byte[] buffer = new byte[3];
    44     private int bufsize = 0;    // size of the cache
    45     private int index = 0;      // index into the cache
    47     // buffer for almost 8K of typical 76 chars + CRLF lines,
    48     // used by getByte method.  this buffer contains encoded bytes.
    49     private byte[] input_buffer = new byte[78*105];
    50     private int input_pos = 0;
    51     private int input_len = 0;;
    53     private boolean ignoreErrors = false;
    55     /**
    56      * Create a BASE64 decoder that decodes the specified input stream.
    57      * The System property <code>mail.mime.base64.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      *
    61      * @param in        the input stream
    62      */
    63     public BASE64DecoderStream(InputStream in) {
    64         super(in);
    65         // default to false
    66         ignoreErrors = PropUtil.getBooleanSystemProperty(
    67             "mail.mime.base64.ignoreerrors", false);
    68     }
    70     /**
    71      * Create a BASE64 decoder that decodes the specified input stream.
    72      *
    73      * @param in        the input stream
    74      * @param ignoreErrors      ignore errors in encoded data?
    75      */
    76     public BASE64DecoderStream(InputStream in, boolean ignoreErrors) {
    77         super(in);
    78         this.ignoreErrors = ignoreErrors;
    79     }
    81     /**
    82      * Read the next decoded byte from this input stream. The byte
    83      * is returned as an <code>int</code> in the range <code>0</code>
    84      * to <code>255</code>. If no byte is available because the end of
    85      * the stream has been reached, the value <code>-1</code> is returned.
    86      * This method blocks until input data is available, the end of the
    87      * stream is detected, or an exception is thrown.
    88      *
    89      * @return     next byte of data, or <code>-1</code> if the end of the
    90      *             stream is reached.
    91      * @exception  IOException  if an I/O error occurs.
    92      * @see        java.io.FilterInputStream#in
    93      */
    94     @Override
    95     public int read() throws IOException {
    96         if (index >= bufsize) {
    97             bufsize = decode(buffer, 0, buffer.length);
    98             if (bufsize <= 0) {
    99                 return -1;
   100             }
   101             index = 0; // reset index into buffer
   102         }
   103         return buffer[index++] & 0xff; // Zero off the MSB
   104     }
   106     /**
   107      * Reads up to <code>len</code> decoded bytes of data from this input stream
   108      * into an array of bytes. This method blocks until some input is
   109      * available.
   110      * <p>
   111      *
   112      * @param      buf   the buffer into which the data is read.
   113      * @param      off   the start offset of the data.
   114      * @param      len   the maximum number of bytes read.
   115      * @return     the total number of bytes read into the buffer, or
   116      *             <code>-1</code> if there is no more data because the end of
   117      *             the stream has been reached.
   118      * @exception  IOException  if an I/O error occurs.
   119      */
   120     @Override
   121     public int read(byte[] buf, int off, int len) throws IOException {
   122         // empty out single byte read buffer
   123         int off0 = off;
   124         while (index < bufsize && len > 0) {
   125             buf[off++] = buffer[index++];
   126             len--;
   127         }
   128         if (index >= bufsize) {
   129             bufsize = index = 0;
   130         }
   132         int bsize = (len / 3) * 3;      // round down to multiple of 3 bytes
   133         if (bsize > 0) {
   134             int size = decode(buf, off, bsize);
   135             off += size;
   136             len -= size;
   138             if (size != bsize) {        // hit EOF?
   139                 if (off == off0) {
   140                     return -1;
   141                 } else {
   142                     return off - off0;
   143                 }
   144             }
   145         }
   147         // finish up with a partial read if necessary
   148         for (; len > 0; len--) {
   149             int c = read();
   150             if (c == -1) {
   151                 break;
   152             }
   153             buf[off++] = (byte)c;
   154         }
   156         if (off == off0) {
   157             return -1;
   158         } else {
   159             return off - off0;
   160         }
   161     }
   163     /**
   164      * Skips over and discards n bytes of data from this stream.
   165      */
   166     @Override
   167     public long skip(long n) throws IOException {
   168         long skipped = 0;
   169         while (n-- > 0 && read() >= 0) {
   170             skipped++;
   171         }
   172         return skipped;
   173     }
   175     /**
   176      * Tests if this input stream supports marks. Currently this class
   177      * does not support marks
   178      */
   179     @Override
   180     public boolean markSupported() {
   181         return false; // Maybe later ..
   182     }
   184     /**
   185      * Returns the number of bytes that can be read from this input
   186      * stream without blocking. However, this figure is only
   187      * a close approximation in case the original encoded stream
   188      * contains embedded CRLFs; since the CRLFs are discarded, not decoded
   189      */
   190     @Override
   191     public int available() throws IOException {
   192          // This is only an estimate, since in.available()
   193          // might include CRLFs too ..
   194          return ((in.available() * 3)/4 + (bufsize-index));
   195     }
   197     /**
   198      * This character array provides the character to value map
   199      * based on RFC1521.
   200      */
   201     private final static char pem_array[] = {
   202         'A','B','C','D','E','F','G','H', // 0
   203         'I','J','K','L','M','N','O','P', // 1
   204         'Q','R','S','T','U','V','W','X', // 2
   205         'Y','Z','a','b','c','d','e','f', // 3
   206         'g','h','i','j','k','l','m','n', // 4
   207         'o','p','q','r','s','t','u','v', // 5
   208         'w','x','y','z','0','1','2','3', // 6
   209         '4','5','6','7','8','9','+','/'  // 7
   210     };
   212     private final static byte pem_convert_array[] = new byte[256];
   214     static {
   215         for (int i = 0; i < 255; i++) {
   216             pem_convert_array[i] = -1;
   217         }
   218         for (int i = 0; i < pem_array.length; i++) {
   219             pem_convert_array[pem_array[i]] = (byte)i;
   220         }
   221     }
   223     /**
   224      * The decoder algorithm.  Most of the complexity here is dealing
   225      * with error cases.  Returns the number of bytes decoded, which
   226      * may be zero.  Decoding is done by filling an int with 4 6-bit
   227      * values by shifting them in from the bottom and then extracting
   228      * 3 8-bit bytes from the int by shifting them out from the bottom.
   229      *
   230      * @param   outbuf  the buffer into which to put the decoded bytes
   231      * @param   pos     position in the buffer to start filling
   232      * @param   len     the number of bytes to fill
   233      * @return          the number of bytes filled, always a multiple
   234      *                  of three, and may be zero
   235      * @exception       IOException     if the data is incorrectly formatted
   236      */
   237     private int decode(byte[] outbuf, int pos, int len) throws IOException {
   238         int pos0 = pos;
   239         while (len >= 3) {
   240             /*
   241              * We need 4 valid base64 characters before we start decoding.
   242              * We skip anything that's not a valid base64 character (usually
   243              * just CRLF).
   244              */
   245             int got = 0;
   246             int val = 0;
   247             while (got < 4) {
   248                 int i = getByte();
   249                 if (i == -1 || i == -2) {
   250                     boolean atEOF;
   251                     if (i == -1) {
   252                         if (got == 0) {
   253                             return pos - pos0;
   254                         }
   255                         if (!ignoreErrors) {
   256                             throw new DecodingException(
   257                                 "BASE64Decoder: Error in encoded stream: " +
   258                                 "needed 4 valid base64 characters " +
   259                                 "but only got " + got + " before EOF" +
   260                                 recentChars());
   261                         }
   262                         atEOF = true;   // don't read any more
   263                     } else {    // i == -2
   264                         // found a padding character, we're at EOF
   265                         // XXX - should do something to make EOF "sticky"
   266                         if (got < 2 && !ignoreErrors) {
   267                             throw new DecodingException(
   268                                 "BASE64Decoder: Error in encoded stream: " +
   269                                 "needed at least 2 valid base64 characters," +
   270                                 " but only got " + got +
   271                                 " before padding character (=)" +
   272                                 recentChars());
   273                         }
   275                         // didn't get any characters before padding character?
   276                         if (got == 0) {
   277                             return pos - pos0;
   278                         }
   279                         atEOF = false;  // need to keep reading
   280                     }
   282                     // pad partial result with zeroes
   284                     // how many bytes will we produce on output?
   285                     // (got always < 4, so size always < 3)
   286                     int size = got - 1;
   287                     if (size == 0) {
   288                         size = 1;
   289                     }
   291                     // handle the one padding character we've seen
   292                     got++;
   293                     val <<= 6;
   295                     while (got < 4) {
   296                         if (!atEOF) {
   297                             // consume the rest of the padding characters,
   298                             // filling with zeroes
   299                             i = getByte();
   300                             if (i == -1) {
   301                                 if (!ignoreErrors) {
   302                                     throw new DecodingException(
   303                                         "BASE64Decoder: Error in encoded " +
   304                                         "stream: hit EOF while looking for " +
   305                                         "padding characters (=)" +
   306                                         recentChars());
   307                                 }
   308                             } else if (i != -2) {
   309                                 if (!ignoreErrors) {
   310                                     throw new DecodingException(
   311                                         "BASE64Decoder: Error in encoded " +
   312                                         "stream: found valid base64 " +
   313                                         "character after a padding character " +
   314                                         "(=)" + recentChars());
   315                                 }
   316                             }
   317                         }
   318                         val <<= 6;
   319                         got++;
   320                     }
   322                     // now pull out however many valid bytes we got
   323                     val >>= 8;          // always skip first one
   324                     if (size == 2) {
   325                         outbuf[pos + 1] = (byte)(val & 0xff);
   326                     }
   327                     val >>= 8;
   328                     outbuf[pos] = (byte)(val & 0xff);
   329                     // len -= size;     // not needed, return below
   330                     pos += size;
   331                     return pos - pos0;
   332                 } else {
   333                     // got a valid byte
   334                     val <<= 6;
   335                     got++;
   336                     val |= i;
   337                 }
   338             }
   340             // read 4 valid characters, now extract 3 bytes
   341             outbuf[pos + 2] = (byte)(val & 0xff);
   342             val >>= 8;
   343             outbuf[pos + 1] = (byte)(val & 0xff);
   344             val >>= 8;
   345             outbuf[pos] = (byte)(val & 0xff);
   346             len -= 3;
   347             pos += 3;
   348         }
   349         return pos - pos0;
   350     }
   352     /**
   353      * Read the next valid byte from the input stream.
   354      * Buffer lots of data from underlying stream in input_buffer,
   355      * for efficiency.
   356      *
   357      * @return  the next byte, -1 on EOF, or -2 if next byte is '='
   358      *          (padding at end of encoded data)
   359      */
   360     private int getByte() throws IOException {
   361         int c;
   362         do {
   363             if (input_pos >= input_len) {
   364                 try {
   365                     input_len = in.read(input_buffer);
   366                 } catch (EOFException ex) {
   367                     return -1;
   368                 }
   369                 if (input_len <= 0) {
   370                     return -1;
   371                 }
   372                 input_pos = 0;
   373             }
   374             // get the next byte in the buffer
   375             c = input_buffer[input_pos++] & 0xff;
   376             // is it a padding byte?
   377             if (c == '=') {
   378                 return -2;
   379             }
   380             // no, convert it
   381             c = pem_convert_array[c];
   382             // loop until we get a legitimate byte
   383         } while (c == -1);
   384         return c;
   385     }
   387     /**
   388      * Return the most recent characters, for use in an error message.
   389      */
   390     private String recentChars() {
   391         // reach into the input buffer and extract up to 10
   392         // recent characters, to help in debugging.
   393         StringBuilder errstr = new StringBuilder();
   394         int nc = input_pos > 10 ? 10 : input_pos;
   395         if (nc > 0) {
   396             errstr.append(", the ").append(nc).append(" most recent characters were: \"");
   397             for (int k = input_pos - nc; k < input_pos; k++) {
   398                 char c = (char)(input_buffer[k] & 0xff);
   399                 switch (c) {
   400                     case '\r':  errstr.append("\\r"); break;
   401                     case '\n':  errstr.append("\\n"); break;
   402                     case '\t':  errstr.append("\\t"); break;
   403                     default:
   404                         if (c >= ' ' && c < 0177) {
   405                         errstr.append(c);
   406                     } else {
   407                         errstr.append("\\").append((int)c);
   408                     }
   409                 }
   410             }
   411             errstr.append("\"");
   412         }
   413         return errstr.toString();
   414     }
   416     /**
   417      * Base64 decode a byte array.  No line breaks are allowed.
   418      * This method is suitable for short strings, such as those
   419      * in the IMAP AUTHENTICATE protocol, but not to decode the
   420      * entire content of a MIME part.
   421      *
   422      * NOTE: inbuf may only contain valid base64 characters.
   423      *       Whitespace is not ignored.
   424      */
   425     public static byte[] decode(byte[] inbuf) {
   426         int size = (inbuf.length / 4) * 3;
   427         if (size == 0) {
   428             return inbuf;
   429         }
   431         if (inbuf[inbuf.length - 1] == '=') {
   432             size--;
   433             if (inbuf[inbuf.length - 2] == '=') {
   434                 size--;
   435             }
   436         }
   437         byte[] outbuf = new byte[size];
   439         int inpos = 0, outpos = 0;
   440         size = inbuf.length;
   441         while (size > 0) {
   442             int val;
   443             int osize = 3;
   444             val = pem_convert_array[inbuf[inpos++] & 0xff];
   445             val <<= 6;
   446             val |= pem_convert_array[inbuf[inpos++] & 0xff];
   447             val <<= 6;
   448             if (inbuf[inpos] != '=') {
   449                 val |= pem_convert_array[inbuf[inpos++] & 0xff];
   450             } else {
   451                 osize--;
   452             }
   453             val <<= 6;
   454             if (inbuf[inpos] != '=') {
   455                 val |= pem_convert_array[inbuf[inpos++] & 0xff];
   456             } else {
   457                 osize--;
   458             }
   459             if (osize > 2) {
   460                 outbuf[outpos + 2] = (byte)(val & 0xff);
   461             }
   462             val >>= 8;
   463             if (osize > 1) {
   464                 outbuf[outpos + 1] = (byte)(val & 0xff);
   465             }
   466             val >>= 8;
   467             outbuf[outpos] = (byte)(val & 0xff);
   468             outpos += osize;
   469             size -= 4;
   470         }
   471         return outbuf;
   472     }
   474     /*** begin TEST program ***
   475     public static void main(String argv[]) throws Exception {
   476         FileInputStream infile = new FileInputStream(argv[0]);
   477         BASE64DecoderStream decoder = new BASE64DecoderStream(infile);
   478         int c;
   480         while ((c = decoder.read()) != -1)
   481             System.out.print((char)c);
   482         System.out.flush();
   483     }
   484     *** end TEST program ***/
   485 }

mercurial