[6785] | 1 | /**************************************************************** |
---|
| 2 | * Licensed to the Apache Software Foundation (ASF) under one * |
---|
| 3 | * or more contributor license agreements. See the NOTICE file * |
---|
| 4 | * distributed with this work for additional information * |
---|
| 5 | * regarding copyright ownership. The ASF licenses this file * |
---|
| 6 | * to you under the Apache License, Version 2.0 (the * |
---|
| 7 | * "License"); you may not use this file except in compliance * |
---|
| 8 | * with the License. You may obtain a copy of the License at * |
---|
| 9 | * * |
---|
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 * |
---|
| 11 | * * |
---|
| 12 | * Unless required by applicable law or agreed to in writing, * |
---|
| 13 | * software distributed under the License is distributed on an * |
---|
| 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * |
---|
| 15 | * KIND, either express or implied. See the License for the * |
---|
| 16 | * specific language governing permissions and limitations * |
---|
| 17 | * under the License. * |
---|
| 18 | ****************************************************************/ |
---|
| 19 | |
---|
| 20 | package org.apache.james.mime4j.codec; |
---|
| 21 | |
---|
| 22 | import java.io.FilterOutputStream; |
---|
| 23 | import java.io.IOException; |
---|
| 24 | import java.io.OutputStream; |
---|
| 25 | import java.util.HashSet; |
---|
| 26 | import java.util.Set; |
---|
| 27 | |
---|
| 28 | /** |
---|
| 29 | * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> |
---|
| 30 | * from RFC 2045 <cite>Multipurpose Internet Mail Extensions (MIME) Part One: |
---|
| 31 | * Format of Internet Message Bodies</cite> by Freed and Borenstein. |
---|
| 32 | * <p> |
---|
| 33 | * Code is based on Base64 and Base64OutputStream code from Commons-Codec 1.4. |
---|
| 34 | * |
---|
| 35 | * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> |
---|
| 36 | */ |
---|
| 37 | public class Base64OutputStream extends FilterOutputStream { |
---|
| 38 | |
---|
| 39 | // Default line length per RFC 2045 section 6.8. |
---|
| 40 | private static final int DEFAULT_LINE_LENGTH = 76; |
---|
| 41 | |
---|
| 42 | // CRLF line separator per RFC 2045 section 2.1. |
---|
| 43 | private static final byte[] CRLF_SEPARATOR = { '\r', '\n' }; |
---|
| 44 | |
---|
| 45 | // This array is a lookup table that translates 6-bit positive integer index |
---|
| 46 | // values into their "Base64 Alphabet" equivalents as specified in Table 1 |
---|
| 47 | // of RFC 2045. |
---|
| 48 | static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F', |
---|
| 49 | 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
---|
| 50 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', |
---|
| 51 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', |
---|
| 52 | 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', |
---|
| 53 | '6', '7', '8', '9', '+', '/' }; |
---|
| 54 | |
---|
| 55 | // Byte used to pad output. |
---|
| 56 | private static final byte BASE64_PAD = '='; |
---|
| 57 | |
---|
| 58 | // This set contains all base64 characters including the pad character. Used |
---|
| 59 | // solely to check if a line separator contains any of these characters. |
---|
| 60 | private static final Set<Byte> BASE64_CHARS = new HashSet<Byte>(); |
---|
| 61 | |
---|
| 62 | static { |
---|
| 63 | for (byte b : BASE64_TABLE) { |
---|
| 64 | BASE64_CHARS.add(b); |
---|
| 65 | } |
---|
| 66 | BASE64_CHARS.add(BASE64_PAD); |
---|
| 67 | } |
---|
| 68 | |
---|
| 69 | // Mask used to extract 6 bits |
---|
| 70 | private static final int MASK_6BITS = 0x3f; |
---|
| 71 | |
---|
| 72 | private static final int ENCODED_BUFFER_SIZE = 2048; |
---|
| 73 | |
---|
| 74 | private final byte[] singleByte = new byte[1]; |
---|
| 75 | |
---|
| 76 | private final int lineLength; |
---|
| 77 | private final byte[] lineSeparator; |
---|
| 78 | |
---|
| 79 | private boolean closed = false; |
---|
| 80 | |
---|
| 81 | private final byte[] encoded; |
---|
| 82 | private int position = 0; |
---|
| 83 | |
---|
| 84 | private int data = 0; |
---|
| 85 | private int modulus = 0; |
---|
| 86 | |
---|
| 87 | private int linePosition = 0; |
---|
| 88 | |
---|
| 89 | /** |
---|
| 90 | * Creates a <code>Base64OutputStream</code> that writes the encoded data |
---|
| 91 | * to the given output stream using the default line length (76) and line |
---|
| 92 | * separator (CRLF). |
---|
| 93 | * |
---|
| 94 | * @param out |
---|
| 95 | * underlying output stream. |
---|
| 96 | */ |
---|
| 97 | public Base64OutputStream(OutputStream out) { |
---|
| 98 | this(out, DEFAULT_LINE_LENGTH, CRLF_SEPARATOR); |
---|
| 99 | } |
---|
| 100 | |
---|
| 101 | /** |
---|
| 102 | * Creates a <code>Base64OutputStream</code> that writes the encoded data |
---|
| 103 | * to the given output stream using the given line length and the default |
---|
| 104 | * line separator (CRLF). |
---|
| 105 | * <p> |
---|
| 106 | * The given line length will be rounded up to the nearest multiple of 4. If |
---|
| 107 | * the line length is zero then the output will not be split into lines. |
---|
| 108 | * |
---|
| 109 | * @param out |
---|
| 110 | * underlying output stream. |
---|
| 111 | * @param lineLength |
---|
| 112 | * desired line length. |
---|
| 113 | */ |
---|
| 114 | public Base64OutputStream(OutputStream out, int lineLength) { |
---|
| 115 | this(out, lineLength, CRLF_SEPARATOR); |
---|
| 116 | } |
---|
| 117 | |
---|
| 118 | /** |
---|
| 119 | * Creates a <code>Base64OutputStream</code> that writes the encoded data |
---|
| 120 | * to the given output stream using the given line length and line |
---|
| 121 | * separator. |
---|
| 122 | * <p> |
---|
| 123 | * The given line length will be rounded up to the nearest multiple of 4. If |
---|
| 124 | * the line length is zero then the output will not be split into lines and |
---|
| 125 | * the line separator is ignored. |
---|
| 126 | * <p> |
---|
| 127 | * The line separator must not include characters from the BASE64 alphabet |
---|
| 128 | * (including the padding character <code>=</code>). |
---|
| 129 | * |
---|
| 130 | * @param out |
---|
| 131 | * underlying output stream. |
---|
| 132 | * @param lineLength |
---|
| 133 | * desired line length. |
---|
| 134 | * @param lineSeparator |
---|
| 135 | * line separator to use. |
---|
| 136 | */ |
---|
| 137 | public Base64OutputStream(OutputStream out, int lineLength, |
---|
| 138 | byte[] lineSeparator) { |
---|
| 139 | super(out); |
---|
| 140 | |
---|
| 141 | if (out == null) |
---|
| 142 | throw new IllegalArgumentException(); |
---|
| 143 | if (lineLength < 0) |
---|
| 144 | throw new IllegalArgumentException(); |
---|
| 145 | checkLineSeparator(lineSeparator); |
---|
| 146 | |
---|
| 147 | this.lineLength = lineLength; |
---|
| 148 | this.lineSeparator = new byte[lineSeparator.length]; |
---|
| 149 | System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, |
---|
| 150 | lineSeparator.length); |
---|
| 151 | |
---|
| 152 | this.encoded = new byte[ENCODED_BUFFER_SIZE]; |
---|
| 153 | } |
---|
| 154 | |
---|
| 155 | @Override |
---|
| 156 | public final void write(final int b) throws IOException { |
---|
| 157 | if (closed) |
---|
| 158 | throw new IOException("Base64OutputStream has been closed"); |
---|
| 159 | |
---|
| 160 | singleByte[0] = (byte) b; |
---|
| 161 | write0(singleByte, 0, 1); |
---|
| 162 | } |
---|
| 163 | |
---|
| 164 | @Override |
---|
| 165 | public final void write(final byte[] buffer) throws IOException { |
---|
| 166 | if (closed) |
---|
| 167 | throw new IOException("Base64OutputStream has been closed"); |
---|
| 168 | |
---|
| 169 | if (buffer == null) |
---|
| 170 | throw new NullPointerException(); |
---|
| 171 | |
---|
| 172 | if (buffer.length == 0) |
---|
| 173 | return; |
---|
| 174 | |
---|
| 175 | write0(buffer, 0, buffer.length); |
---|
| 176 | } |
---|
| 177 | |
---|
| 178 | @Override |
---|
| 179 | public final void write(final byte[] buffer, final int offset, |
---|
| 180 | final int length) throws IOException { |
---|
| 181 | if (closed) |
---|
| 182 | throw new IOException("Base64OutputStream has been closed"); |
---|
| 183 | |
---|
| 184 | if (buffer == null) |
---|
| 185 | throw new NullPointerException(); |
---|
| 186 | |
---|
| 187 | if (offset < 0 || length < 0 || offset + length > buffer.length) |
---|
| 188 | throw new IndexOutOfBoundsException(); |
---|
| 189 | |
---|
| 190 | if (length == 0) |
---|
| 191 | return; |
---|
| 192 | |
---|
| 193 | write0(buffer, offset, offset + length); |
---|
| 194 | } |
---|
| 195 | |
---|
| 196 | @Override |
---|
| 197 | public void flush() throws IOException { |
---|
| 198 | if (closed) |
---|
| 199 | throw new IOException("Base64OutputStream has been closed"); |
---|
| 200 | |
---|
| 201 | flush0(); |
---|
| 202 | } |
---|
| 203 | |
---|
| 204 | @Override |
---|
| 205 | public void close() throws IOException { |
---|
| 206 | if (closed) |
---|
| 207 | return; |
---|
| 208 | |
---|
| 209 | closed = true; |
---|
| 210 | close0(); |
---|
| 211 | } |
---|
| 212 | |
---|
| 213 | private void write0(final byte[] buffer, final int from, final int to) |
---|
| 214 | throws IOException { |
---|
| 215 | for (int i = from; i < to; i++) { |
---|
| 216 | data = (data << 8) | (buffer[i] & 0xff); |
---|
| 217 | |
---|
| 218 | if (++modulus == 3) { |
---|
| 219 | modulus = 0; |
---|
| 220 | |
---|
| 221 | // write line separator if necessary |
---|
| 222 | |
---|
| 223 | if (lineLength > 0 && linePosition >= lineLength) { |
---|
| 224 | // writeLineSeparator() inlined for performance reasons |
---|
| 225 | |
---|
| 226 | linePosition = 0; |
---|
| 227 | |
---|
| 228 | if (encoded.length - position < lineSeparator.length) |
---|
| 229 | flush0(); |
---|
| 230 | |
---|
| 231 | for (byte ls : lineSeparator) |
---|
| 232 | encoded[position++] = ls; |
---|
| 233 | } |
---|
| 234 | |
---|
| 235 | // encode data into 4 bytes |
---|
| 236 | |
---|
| 237 | if (encoded.length - position < 4) |
---|
| 238 | flush0(); |
---|
| 239 | |
---|
| 240 | encoded[position++] = BASE64_TABLE[(data >> 18) & MASK_6BITS]; |
---|
| 241 | encoded[position++] = BASE64_TABLE[(data >> 12) & MASK_6BITS]; |
---|
| 242 | encoded[position++] = BASE64_TABLE[(data >> 6) & MASK_6BITS]; |
---|
| 243 | encoded[position++] = BASE64_TABLE[data & MASK_6BITS]; |
---|
| 244 | |
---|
| 245 | linePosition += 4; |
---|
| 246 | } |
---|
| 247 | } |
---|
| 248 | } |
---|
| 249 | |
---|
| 250 | private void flush0() throws IOException { |
---|
| 251 | if (position > 0) { |
---|
| 252 | out.write(encoded, 0, position); |
---|
| 253 | position = 0; |
---|
| 254 | } |
---|
| 255 | } |
---|
| 256 | |
---|
| 257 | private void close0() throws IOException { |
---|
| 258 | if (modulus != 0) |
---|
| 259 | writePad(); |
---|
| 260 | |
---|
| 261 | // write line separator at the end of the encoded data |
---|
| 262 | |
---|
| 263 | if (lineLength > 0 && linePosition > 0) { |
---|
| 264 | writeLineSeparator(); |
---|
| 265 | } |
---|
| 266 | |
---|
| 267 | flush0(); |
---|
| 268 | } |
---|
| 269 | |
---|
| 270 | private void writePad() throws IOException { |
---|
| 271 | // write line separator if necessary |
---|
| 272 | |
---|
| 273 | if (lineLength > 0 && linePosition >= lineLength) { |
---|
| 274 | writeLineSeparator(); |
---|
| 275 | } |
---|
| 276 | |
---|
| 277 | // encode data into 4 bytes |
---|
| 278 | |
---|
| 279 | if (encoded.length - position < 4) |
---|
| 280 | flush0(); |
---|
| 281 | |
---|
| 282 | if (modulus == 1) { |
---|
| 283 | encoded[position++] = BASE64_TABLE[(data >> 2) & MASK_6BITS]; |
---|
| 284 | encoded[position++] = BASE64_TABLE[(data << 4) & MASK_6BITS]; |
---|
| 285 | encoded[position++] = BASE64_PAD; |
---|
| 286 | encoded[position++] = BASE64_PAD; |
---|
| 287 | } else { |
---|
| 288 | assert modulus == 2; |
---|
| 289 | encoded[position++] = BASE64_TABLE[(data >> 10) & MASK_6BITS]; |
---|
| 290 | encoded[position++] = BASE64_TABLE[(data >> 4) & MASK_6BITS]; |
---|
| 291 | encoded[position++] = BASE64_TABLE[(data << 2) & MASK_6BITS]; |
---|
| 292 | encoded[position++] = BASE64_PAD; |
---|
| 293 | } |
---|
| 294 | |
---|
| 295 | linePosition += 4; |
---|
| 296 | } |
---|
| 297 | |
---|
| 298 | private void writeLineSeparator() throws IOException { |
---|
| 299 | linePosition = 0; |
---|
| 300 | |
---|
| 301 | if (encoded.length - position < lineSeparator.length) |
---|
| 302 | flush0(); |
---|
| 303 | |
---|
| 304 | for (byte ls : lineSeparator) |
---|
| 305 | encoded[position++] = ls; |
---|
| 306 | } |
---|
| 307 | |
---|
| 308 | private void checkLineSeparator(byte[] lineSeparator) { |
---|
| 309 | if (lineSeparator.length > ENCODED_BUFFER_SIZE) |
---|
| 310 | throw new IllegalArgumentException("line separator length exceeds " |
---|
| 311 | + ENCODED_BUFFER_SIZE); |
---|
| 312 | |
---|
| 313 | for (byte b : lineSeparator) { |
---|
| 314 | if (BASE64_CHARS.contains(b)) { |
---|
| 315 | throw new IllegalArgumentException( |
---|
| 316 | "line separator must not contain base64 character '" |
---|
| 317 | + (char) (b & 0xff) + "'"); |
---|
| 318 | } |
---|
| 319 | } |
---|
| 320 | } |
---|
| 321 | } |
---|