Mega Code Archive

 
Categories / Java / Network Protocol
 

Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One

/****************************************************************  * Licensed to the Apache Software Foundation (ASF) under one   *  * or more contributor license agreements.  See the NOTICE file *  * distributed with this work for additional information        *  * regarding copyright ownership.  The ASF licenses this file   *  * to you under the Apache License, Version 2.0 (the            *  * "License"); you may not use this file except in compliance   *  * with the License.  You may obtain a copy of the License at   *  *                                                              *  *   http://www.apache.org/licenses/LICENSE-2.0                 *  *                                                              *  * Unless required by applicable law or agreed to in writing,   *  * software distributed under the License is distributed on an  *  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *  * KIND, either express or implied.  See the License for the    *  * specific language governing permissions and limitations      *  * under the License.                                           *  ****************************************************************/ import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; import java.util.Set; /**  * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite>  * from RFC 2045 <cite>Multipurpose Internet Mail Extensions (MIME) Part One:  * Format of Internet Message Bodies</cite> by Freed and Borenstein.  * <p>  * Code is based on Base64 and Base64OutputStream code from Commons-Codec 1.4.  *   * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>  */ public class Base64OutputStream extends FilterOutputStream {     // Default line length per RFC 2045 section 6.8.     private static final int DEFAULT_LINE_LENGTH = 76;     // CRLF line separator per RFC 2045 section 2.1.     private static final byte[] CRLF_SEPARATOR = { '\r', '\n' };     // This array is a lookup table that translates 6-bit positive integer index     // values into their "Base64 Alphabet" equivalents as specified in Table 1     // of RFC 2045.     static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F',             'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',             'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',             'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',             't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',             '6', '7', '8', '9', '+', '/' };     // Byte used to pad output.     private static final byte BASE64_PAD = '=';     // This set contains all base64 characters including the pad character. Used     // solely to check if a line separator contains any of these characters.     private static final Set<Byte> BASE64_CHARS = new HashSet<Byte>();     static {         for (byte b : BASE64_TABLE) {             BASE64_CHARS.add(b);         }         BASE64_CHARS.add(BASE64_PAD);     }     // Mask used to extract 6 bits     private static final int MASK_6BITS = 0x3f;     private static final int ENCODED_BUFFER_SIZE = 2048;     private final byte[] singleByte = new byte[1];     private final int lineLength;     private final byte[] lineSeparator;     private boolean closed = false;     private final byte[] encoded;     private int position = 0;     private int data = 0;     private int modulus = 0;     private int linePosition = 0;     /**      * Creates a <code>Base64OutputStream</code> that writes the encoded data      * to the given output stream using the default line length (76) and line      * separator (CRLF).      *       * @param out      *            underlying output stream.      */     public Base64OutputStream(OutputStream out) {         this(out, DEFAULT_LINE_LENGTH, CRLF_SEPARATOR);     }     /**      * Creates a <code>Base64OutputStream</code> that writes the encoded data      * to the given output stream using the given line length and the default      * line separator (CRLF).      * <p>      * The given line length will be rounded up to the nearest multiple of 4. If      * the line length is zero then the output will not be split into lines.      *       * @param out      *            underlying output stream.      * @param lineLength      *            desired line length.      */     public Base64OutputStream(OutputStream out, int lineLength) {         this(out, lineLength, CRLF_SEPARATOR);     }     /**      * Creates a <code>Base64OutputStream</code> that writes the encoded data      * to the given output stream using the given line length and line      * separator.      * <p>      * The given line length will be rounded up to the nearest multiple of 4. If      * the line length is zero then the output will not be split into lines and      * the line separator is ignored.      * <p>      * The line separator must not include characters from the BASE64 alphabet      * (including the padding character <code>=</code>).      *       * @param out      *            underlying output stream.      * @param lineLength      *            desired line length.      * @param lineSeparator      *            line separator to use.      */     public Base64OutputStream(OutputStream out, int lineLength,             byte[] lineSeparator) {         super(out);         if (out == null)             throw new IllegalArgumentException();         if (lineLength < 0)             throw new IllegalArgumentException();         checkLineSeparator(lineSeparator);         this.lineLength = lineLength;         this.lineSeparator = new byte[lineSeparator.length];         System.arraycopy(lineSeparator, 0, this.lineSeparator, 0,                 lineSeparator.length);         this.encoded = new byte[ENCODED_BUFFER_SIZE];     }     @Override     public final void write(final int b) throws IOException {         if (closed)             throw new IOException("Base64OutputStream has been closed");         singleByte[0] = (byte) b;         write0(singleByte, 0, 1);     }     @Override     public final void write(final byte[] buffer) throws IOException {         if (closed)             throw new IOException("Base64OutputStream has been closed");         if (buffer == null)             throw new NullPointerException();         if (buffer.length == 0)             return;         write0(buffer, 0, buffer.length);     }     @Override     public final void write(final byte[] buffer, final int offset,             final int length) throws IOException {         if (closed)             throw new IOException("Base64OutputStream has been closed");         if (buffer == null)             throw new NullPointerException();         if (offset < 0 || length < 0 || offset + length > buffer.length)             throw new IndexOutOfBoundsException();         if (length == 0)             return;         write0(buffer, offset, offset + length);     }     @Override     public void flush() throws IOException {         if (closed)             throw new IOException("Base64OutputStream has been closed");         flush0();     }     @Override     public void close() throws IOException {         if (closed)             return;         closed = true;         close0();     }     private void write0(final byte[] buffer, final int from, final int to)             throws IOException {         for (int i = from; i < to; i++) {             data = (data << 8) | (buffer[i] & 0xff);             if (++modulus == 3) {                 modulus = 0;                 // write line separator if necessary                 if (lineLength > 0 && linePosition >= lineLength) {                     // writeLineSeparator() inlined for performance reasons                     linePosition = 0;                     if (encoded.length - position < lineSeparator.length)                         flush0();                     for (byte ls : lineSeparator)                         encoded[position++] = ls;                 }                 // encode data into 4 bytes                 if (encoded.length - position < 4)                     flush0();                 encoded[position++] = BASE64_TABLE[(data >> 18) & MASK_6BITS];                 encoded[position++] = BASE64_TABLE[(data >> 12) & MASK_6BITS];                 encoded[position++] = BASE64_TABLE[(data >> 6) & MASK_6BITS];                 encoded[position++] = BASE64_TABLE[data & MASK_6BITS];                 linePosition += 4;             }         }     }     private void flush0() throws IOException {         if (position > 0) {             out.write(encoded, 0, position);             position = 0;         }     }     private void close0() throws IOException {         if (modulus != 0)             writePad();         // write line separator at the end of the encoded data         if (lineLength > 0 && linePosition > 0) {             writeLineSeparator();         }         flush0();     }     private void writePad() throws IOException {         // write line separator if necessary         if (lineLength > 0 && linePosition >= lineLength) {             writeLineSeparator();         }         // encode data into 4 bytes         if (encoded.length - position < 4)             flush0();         if (modulus == 1) {             encoded[position++] = BASE64_TABLE[(data >> 2) & MASK_6BITS];             encoded[position++] = BASE64_TABLE[(data << 4) & MASK_6BITS];             encoded[position++] = BASE64_PAD;             encoded[position++] = BASE64_PAD;         } else {             assert modulus == 2;             encoded[position++] = BASE64_TABLE[(data >> 10) & MASK_6BITS];             encoded[position++] = BASE64_TABLE[(data >> 4) & MASK_6BITS];             encoded[position++] = BASE64_TABLE[(data << 2) & MASK_6BITS];             encoded[position++] = BASE64_PAD;         }         linePosition += 4;     }     private void writeLineSeparator() throws IOException {         linePosition = 0;         if (encoded.length - position < lineSeparator.length)             flush0();         for (byte ls : lineSeparator)             encoded[position++] = ls;     }     private void checkLineSeparator(byte[] lineSeparator) {         if (lineSeparator.length > ENCODED_BUFFER_SIZE)             throw new IllegalArgumentException("line separator length exceeds "                     + ENCODED_BUFFER_SIZE);         for (byte b : lineSeparator) {             if (BASE64_CHARS.contains(b)) {                 throw new IllegalArgumentException(                         "line separator must not contain base64 character '"                                 + (char) (b & 0xff) + "'");             }         }     } }