You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by mw...@apache.org on 2008/12/12 14:06:55 UTC
svn commit: r725992 - in /james/mime4j/trunk/src:
main/java/org/apache/james/mime4j/decoder/Base64OutputStream.java
test/java/org/apache/james/mime4j/decoder/Base64OutputStreamTest.java
Author: mwiederkehr
Date: Fri Dec 12 05:06:54 2008
New Revision: 725992
URL: http://svn.apache.org/viewvc?rev=725992&view=rev
Log:
Faster Base64OutputStream for MIME4J-71
Modified:
james/mime4j/trunk/src/main/java/org/apache/james/mime4j/decoder/Base64OutputStream.java
james/mime4j/trunk/src/test/java/org/apache/james/mime4j/decoder/Base64OutputStreamTest.java
Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/decoder/Base64OutputStream.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/decoder/Base64OutputStream.java?rev=725992&r1=725991&r2=725992&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/decoder/Base64OutputStream.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/decoder/Base64OutputStream.java Fri Dec 12 05:06:54 2008
@@ -22,382 +22,300 @@
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
+import java.util.HashSet;
+import java.util.Set;
/**
- * This class is based on Base64 and Base64OutputStream code from Commons-Codec 1.4
- *
- * Provides Base64 encoding and decoding as defined by RFC 2045.
- *
+ * 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>
- * 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>
- * @author Apache Software Foundation
- * @since 1.0-dev
- * @version $Id$
*/
public class Base64OutputStream extends FilterOutputStream {
- /**
- * Chunk size per RFC 2045 section 6.8.
- *
- * <p>
- * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
- * equal signs.
- * </p>
- *
- * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
- */
- static final int CHUNK_SIZE = 76;
-
- /**
- * Chunk separator per RFC 2045 section 2.1.
- *
- * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
- */
- static final byte[] CHUNK_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.
- *
- * Thanks to "commons" project in ws.apache.org for this code.
- * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
- */
- private static final byte[] intToBase64 = {
- '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', '+', '/'
- };
+ // Default line length per RFC 2045 section 6.8.
+ private static final int DEFAULT_LINE_LENGTH = 76;
- /**
- * Byte used to pad output.
- */
- private static final byte PAD = '=';
+ // 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 unicode characters
- * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
- * into their 6-bit positive integer equivalents. Characters that
- * are not in the Base64 alphabet but fall within the bounds of the
- * array are translated to -1.
- *
- * Thanks to "commons" project in ws.apache.org for this code.
- * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
- */
- private static final byte[] base64ToInt = {
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
- 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
- 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
- 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
- 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
- };
+ // 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.
+ private 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, used when encoding */
+ // Mask used to extract 6 bits
private static final int MASK_6BITS = 0x3f;
- // The static final fields above are used for the original static byte[] methods on Base64.
- // The private member fields below are used with the new streaming approach, which requires
- // some state be preserved between calls of encode() and decode().
+ private static final int ENCODED_BUFFER_SIZE = 2048;
+ private final byte[] singleByte = new byte[1];
- /**
- * Line length for encoding. Not used when decoding. A value of zero or less implies
- * no chunking of the base64 encoded data.
- */
private final int lineLength;
-
- /**
- * Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
- */
private final byte[] lineSeparator;
- /**
- * Convenience variable to help us determine when our buffer is going to run out of
- * room and needs resizing. <code>encodeSize = 4 + lineSeparator.length;</code>
- */
- private final int encodeSize;
-
- /**
- * Buffer for streaming.
- */
- private byte[] buf = new byte[1024];
-
- /**
- * Position where next character should be written in the buffer.
- */
- private int pos;
+ private boolean closed = false;
- /**
- * Variable tracks how many characters have been written to the current line.
- * Only used when encoding. We use it to make sure each encoded line never
- * goes beyond lineLength (if lineLength > 0).
- */
- private int currentLinePos;
+ private final byte[] encoded;
+ private int position = 0;
- /**
- * Writes to the buffer only occur after every 3 reads when encoding, an
- * every 4 reads when decoding. This variable helps track that.
- */
- private int modulus;
+ private int data = 0;
+ private int modulus = 0;
- /**
- * Boolean flag to indicate that this Base64OutputStream has been closed.
- * Once closed, this Base64 object becomes useless, and must be thrown away.
- */
- private boolean closed = false;
-
- /**
- * Place holder for the 3 bytes we're dealing with for our base64 logic.
- * Bitwise operations store and extract the base64 encoding or decoding from
- * this variable.
- */
- private int x;
+ private int linePosition = 0;
/**
- * Default constructor: lineLength is 76, and the lineSeparator is CRLF
- * when encoding, and all forms can be decoded.
+ * 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 os) {
- this(os, CHUNK_SIZE, CHUNK_SEPARATOR);
+ 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>
- * Consumer can use this constructor to choose a different lineLength
- * when encoding (lineSeparator is still CRLF). All forms of data can
- * be decoded.
- * </p><p>
- * Note: lineLengths that aren't multiples of 4 will still essentially
- * end up being multiples of 4 in the encoded data.
- * </p>
- *
- * @param lineLength each line of encoded data will be at most this long
- * (rounded up to nearest multiple of 4).
- * If lineLength <= 0, then the output will not be divided into lines (chunks).
- * Ignored when decoding.
+ * 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 os, int lineLength) {
- this(os, lineLength, CHUNK_SEPARATOR);
+ 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>
- * Consumer can use this constructor to choose a different lineLength
- * and lineSeparator when encoding. All forms of data can
- * be decoded.
- * </p><p>
- * Note: lineLengths that aren't multiples of 4 will still essentially
- * end up being multiples of 4 in the encoded data.
- * </p>
- * @param lineLength Each line of encoded data will be at most this long
- * (rounded up to nearest multiple of 4). Ignored when decoding.
- * If <= 0, then output will not be divided into lines (chunks).
- * @param lineSeparator Each line of encoded data will end with this
- * sequence of bytes.
- * If lineLength <= 0, then the lineSeparator is not used.
- * @throws IllegalArgumentException The provided lineSeparator included
- * some base64 characters. That's not going to work!
- */
- public Base64OutputStream(OutputStream os, int lineLength, byte[] lineSeparator) {
- super(os);
+ * 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);
- if (lineLength > 0) {
- this.encodeSize = 4 + lineSeparator.length;
- } else {
- this.encodeSize = 4;
- }
- if (containsBase64Byte(lineSeparator)) {
- String sep;
- try {
- sep = new String(lineSeparator, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- sep = new String(lineSeparator);
- }
- throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
- }
- }
+ System.arraycopy(lineSeparator, 0, this.lineSeparator, 0,
+ lineSeparator.length);
- /** Doubles our buffer. */
- private void resizeBuf() {
- byte[] b = new byte[buf.length * 2];
- System.arraycopy(buf, 0, b, 0, buf.length);
- buf = b;
+ this.encoded = new byte[ENCODED_BUFFER_SIZE];
}
- /**
- * Returns whether or not the <code>octet</code> is in the base 64 alphabet.
- *
- * @param octet
- * The value to test
- * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
- */
- public static boolean isBase64(byte octet) {
- return octet == PAD || (octet >= 0 && octet < base64ToInt.length && base64ToInt[octet] != -1);
- }
+ @Override
+ public final void write(final int b) throws IOException {
+ if (closed)
+ throw new IOException("Base64OutputStream has been closed");
- /*
- * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
- *
- * @param arrayOctet
- * byte array to test
- * @return <code>true</code> if any byte is a valid character in the Base64 alphabet; false herwise
- */
- private static boolean containsBase64Byte(byte[] arrayOctet) {
- for (byte octet : arrayOctet) {
- if (isBase64(octet)) {
- return true;
- }
- }
- return false;
+ singleByte[0] = (byte) b;
+ write0(singleByte, 0, 1);
}
- // Implementation of the Encoder Interface
+ @Override
+ public final void write(final byte[] buffer) throws IOException {
+ if (closed)
+ throw new IOException("Base64OutputStream has been closed");
- private final byte[] singleByte = new byte[1];
+ if (buffer == null)
+ throw new NullPointerException();
- /**
- * Writes the specified <code>byte</code> to this output stream.
- */
- @Override
- public void write(int i) throws IOException {
- singleByte[0] = (byte) i;
- write(singleByte, 0, 1);
+ if (buffer.length == 0)
+ return;
+
+ write0(buffer, 0, buffer.length);
}
-
- /**
- * Writes <code>len</code> bytes from the specified
- * <code>b</code> array starting at <code>offset</code> to
- * this output stream.
- *
- * @param b source byte array
- * @param offset where to start reading the bytes
- * @param len maximum number of bytes to write
- *
- * @throws IOException if an I/O error occurs.
- * @throws NullPointerException if the byte array parameter is null
- * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid
- */
@Override
- public void write(byte b[], int offset, int len) throws IOException {
- if (closed) {
- throw new IOException("Base64OutputStream has been closed");
- }
- if (b == null) {
+ 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();
- } else if (len < 0) {
- // len < 0 is how we're informed of EOF in the underlying data we're
- // encoding.
- closed = true;
- if (buf.length - pos < encodeSize) {
- resizeBuf();
- }
- switch (modulus) {
- case 1:
- buf[pos++] = intToBase64[(x >> 2) & MASK_6BITS];
- buf[pos++] = intToBase64[(x << 4) & MASK_6BITS];
- buf[pos++] = PAD;
- buf[pos++] = PAD;
- break;
-
- case 2:
- buf[pos++] = intToBase64[(x >> 10) & MASK_6BITS];
- buf[pos++] = intToBase64[(x >> 4) & MASK_6BITS];
- buf[pos++] = intToBase64[(x << 2) & MASK_6BITS];
- buf[pos++] = PAD;
- break;
- }
- if (lineLength > 0) {
- System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
- pos += lineSeparator.length;
- }
- } else if (offset < 0 || len < 0 || offset + len < 0) {
- throw new IndexOutOfBoundsException();
- } else if (offset > b.length || offset + len > b.length) {
+
+ if (offset < 0 || length < 0 || offset + length > buffer.length)
throw new IndexOutOfBoundsException();
- } else if (len > 0) {
- for (int i = 0; i < len; i++) {
- if (buf.length - pos < encodeSize) {
- resizeBuf();
- }
- modulus = (++modulus) % 3;
- int bc = b[offset++];
- if (bc < 0) { bc += 256; }
- x = (x << 8) + bc;
- if (0 == modulus) {
- buf[pos++] = intToBase64[(x >> 18) & MASK_6BITS];
- buf[pos++] = intToBase64[(x >> 12) & MASK_6BITS];
- buf[pos++] = intToBase64[(x >> 6) & MASK_6BITS];
- buf[pos++] = intToBase64[x & MASK_6BITS];
- currentLinePos += 4;
- if (lineLength > 0 && lineLength <= currentLinePos) {
- System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
- pos += lineSeparator.length;
- currentLinePos = 0;
- }
- }
- }
- }
- flushBuffer();
- }
-
- /**
- * Flushes this output stream and forces any buffered output bytes
- * to be written out to the stream. If propogate is true, the wrapped
- * stream will also be flushed.
- *
- * @param propogate boolean flag to indicate whether the wrapped
- * OutputStream should also be flushed.
- * @throws IOException if an I/O error occurs.
- */
- private void flushBuffer() throws IOException {
- if (pos > 0) {
- out.write(buf, 0, pos);
- // buf = null;
- pos = 0;
- }
+
+ if (length == 0)
+ return;
+
+ write0(buffer, offset, offset + length);
}
- /**
- * Flushes this output stream and forces any buffered output bytes
- * to be written out to the stream.
- *
- * @throws IOException if an I/O error occurs.
- */
@Override
public void flush() throws IOException {
- flushBuffer();
- out.flush();
+ if (closed)
+ throw new IOException("Base64OutputStream has been closed");
+
+ flush0();
}
-
- /**
- * Terminates the BASE64 coded content and flushes the internal buffers. This method does
- * NOT close the underlying output stream.
- */
+
@Override
public void close() throws IOException {
- if (closed) {
+ 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();
}
- try {
- // Notify encoder of EOF (-1).
- write(singleByte, 0, -1);
- flush();
- } finally {
- closed = true;
+ 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) + "'");
+ }
+ }
+ }
}
Modified: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/decoder/Base64OutputStreamTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/decoder/Base64OutputStreamTest.java?rev=725992&r1=725991&r2=725992&view=diff
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/decoder/Base64OutputStreamTest.java (original)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/decoder/Base64OutputStreamTest.java Fri Dec 12 05:06:54 2008
@@ -21,6 +21,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import junit.framework.TestCase;
@@ -62,6 +63,139 @@
bos.write('a');
assertEquals("VGhpcyBpcyB0aGUgcGxhaW4gdGV4dCBtZXNzYWdlIQ==\r\nyada", toString(bos.toByteArray()));
}
+
+ public void testNoLineSeparators() throws IOException {
+ assertEquals("", encodeNoLs(""));
+ assertEquals("YQ==", encodeNoLs("a"));
+ assertEquals("YWI=", encodeNoLs("ab"));
+ assertEquals("YWJj", encodeNoLs("abc"));
+ assertEquals("YWJjZA==", encodeNoLs("abcd"));
+ assertEquals("YWJjZGU=", encodeNoLs("abcde"));
+ assertEquals("YWJjZGVm", encodeNoLs("abcdef"));
+ assertEquals("YWJjZGVmZw==", encodeNoLs("abcdefg"));
+ assertEquals("YWJjZGVmZ2g=", encodeNoLs("abcdefgh"));
+ assertEquals("YWJjZGVmZ2hp", encodeNoLs("abcdefghi"));
+ assertEquals("DQoMCQ==", encodeNoLs("\r\n\f\t"));
+ assertEquals("LT0/VGhhdCdzIGEgdGVzdD89LQ==",
+ encodeNoLs("-=?That's a test?=-"));
+ }
+
+ public void testLineSeparators() throws IOException {
+ assertEquals("", encodeLs(""));
+ assertEquals("YQ==\r\n", encodeLs("a"));
+ assertEquals("YWJjZA==\r\n", encodeLs("abcd"));
+ assertEquals("YWJjZGVmZw==\r\n", encodeLs("abcdefg"));
+ assertEquals("YWJjZGVmZ2g=\r\n", encodeLs("abcdefgh"));
+ assertEquals("YWJjZGVmZ2hp\r\n", encodeLs("abcdefghi"));
+ assertEquals("YWJjZGVmZ2hp\r\nag==\r\n", encodeLs("abcdefghij"));
+ assertEquals("YWJjZGVmZ2hp\r\nams=\r\n", encodeLs("abcdefghijk"));
+ assertEquals("YWJjZGVmZ2hp\r\namts\r\n", encodeLs("abcdefghijkl"));
+ }
+
+ /**
+ * tests {@link OutputStream#write(int)}
+ */
+ public void testWriteInt() throws IOException {
+ byte[] bytes = fromString("123456789012345678901234567890123456789012"
+ + "3456789012345678901234567890123456789012345678901234567890"
+ + "123456789012345");
+
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ Base64OutputStream out = new Base64OutputStream(b);
+
+ for (byte element : bytes)
+ out.write(element);
+
+ out.close();
+
+ String actual = new String(b.toByteArray(), "US-ASCII");
+
+ String expected = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nz"
+ + "g5MDEyMzQ1Njc4OTAxMjM0NTY3\r\nODkwMTIzNDU2Nzg5MDEyMzQ1Njc4"
+ + "OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0\r\nNQ==\r"
+ + "\n";
+
+ assertEquals(expected, actual);
+ }
+
+ /**
+ * tests {@link OutputStream#write(byte[], int, int)} with various offsets
+ */
+ public void testWriteOffset() throws IOException {
+ byte[] bytes = fromString("123456789012345678901234567890123456789012"
+ + "3456789012345678901234567890123456789012345678901234567890"
+ + "123456789012345");
+
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ Base64OutputStream out = new Base64OutputStream(b);
+
+ for (int offset = 0; offset < bytes.length; offset += 2) {
+ int len = Math.min(2, bytes.length - offset);
+ out.write(bytes, offset, len);
+ }
+
+ out.close();
+
+ String actual = new String(b.toByteArray(), "US-ASCII");
+
+ String expected = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nz"
+ + "g5MDEyMzQ1Njc4OTAxMjM0NTY3\r\nODkwMTIzNDU2Nzg5MDEyMzQ1Njc4"
+ + "OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0\r\nNQ==\r"
+ + "\n";
+
+ assertEquals(expected, actual);
+ }
+
+ /**
+ * tests {@link OutputStream#flush()} while writing
+ */
+ public void testWriteFlush() throws IOException {
+ byte[] bytes = fromString("123456789012345678901234567890123456789012"
+ + "3456789012345678901234567890123456789012345678901234567890"
+ + "123456789012345");
+
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ Base64OutputStream out = new Base64OutputStream(b);
+
+ for (int offset = 0; offset < bytes.length; offset += 7) {
+ int len = Math.min(7, bytes.length - offset);
+ out.write(bytes, offset, len);
+ out.flush();
+ }
+
+ out.close();
+
+ String actual = new String(b.toByteArray(), "US-ASCII");
+
+ String expected = "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nz"
+ + "g5MDEyMzQ1Njc4OTAxMjM0NTY3\r\nODkwMTIzNDU2Nzg5MDEyMzQ1Njc4"
+ + "OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0\r\nNQ==\r"
+ + "\n";
+
+ assertEquals(expected, actual);
+ }
+
+ private String encodeNoLs(String str) throws IOException {
+ return encode(str, 0, new byte[] {});
+ }
+
+ private String encodeLs(String str) throws IOException {
+ return encode(str, 12, new byte[] { '\r', '\n' });
+ }
+
+ private String encode(String str, int lineLength, byte[] lineSeparator)
+ throws IOException {
+ byte[] bytes = fromString(str);
+
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ Base64OutputStream out = new Base64OutputStream(b, lineLength,
+ lineSeparator);
+
+ out.write(bytes);
+ out.close();
+
+ return toString(b.toByteArray());
+ }
private byte[] fromString(String s) {
try {
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org