Author: rdonkin
Date: Mon Jul 21 13:03:38 2008
New Revision: 678541
URL: http://svn.apache.org/viewvc?rev=678541&view=rev
Log:
Improved encoder supports text and binary modes. More tests are needed but I
think the pay will be greater if these are added as integration tests.
Added:
james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableTextEncodeTest.java
Modified:
james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/CodecUtil.java
Modified:
james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/CodecUtil.java
URL:
http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/CodecUtil.java?rev=678541&r1=678540&r2=678541&view=diff
==============================================================================
---
james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/CodecUtil.java
(original)
+++
james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/CodecUtil.java
Mon Jul 21 13:03:38 2008
@@ -35,6 +35,7 @@
private static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024;
+ private static final byte TAB = 0x09;
private static final byte SPACE = 0x20;
private static final byte EQUALS = 0x3D;
private static final byte CR = 0x0D;
@@ -60,7 +61,7 @@
/**
* Encodes the given stream using Quoted-Printable.
- * This assumes that text is binary and therefore escapes
+ * This assumes that stream is binary and therefore escapes
* all line endings.
* @param in not null
* @param out not null
@@ -68,14 +69,18 @@
*/
public static void encodeQuotedPrintableBinary(final InputStream in, final
OutputStream out) throws IOException {
- BinaryQuotedPrintableEncoder encoder = new
BinaryQuotedPrintableEncoder(DEFAULT_ENCODING_BUFFER_SIZE);
+ QuotedPrintableEncoder encoder = new
QuotedPrintableEncoder(DEFAULT_ENCODING_BUFFER_SIZE, true);
encoder.encode(in, out);
}
- private static final class BinaryQuotedPrintableEncoder {
+ private static final class QuotedPrintableEncoder {
private final byte[] inBuffer;
private final byte[] outBuffer;
+ private final boolean binary;
+ private boolean pendingSpace;
+ private boolean pendingTab;
+ private boolean pendingCR;
private int nextSoftBreak;
private int inputIndex;
private int outputIndex;
@@ -84,7 +89,7 @@
private OutputStream out;
- public BinaryQuotedPrintableEncoder(int bufferSize) {
+ public QuotedPrintableEncoder(int bufferSize, boolean binary) {
inBuffer = new byte[bufferSize];
outBuffer = new byte[3*bufferSize];
inputLength = 0;
@@ -92,11 +97,18 @@
nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
in = null;
out = null;
+ this.binary = binary;
+ pendingSpace = false;
+ pendingTab = false;
+ pendingCR = false;
}
public void encode(final InputStream in, final OutputStream out)
throws IOException {
this.in = in;
this.out = out;
+ pendingSpace = false;
+ pendingTab = false;
+ pendingCR = false;
nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
read();
while(inputLength > -1) {
@@ -107,6 +119,7 @@
}
read();
}
+ writePending();
flushOutput();
}
@@ -115,15 +128,73 @@
inputIndex = 0;
}
+ private void writePending() throws IOException {
+ if (pendingSpace) {
+ plain(SPACE);
+ } else if (pendingTab) {
+ plain(TAB);
+ } else if (pendingCR) {
+ plain(CR);
+ }
+ clearPending();
+ }
+
+ private void clearPending() throws IOException {
+ pendingSpace = false;
+ pendingTab = false;
+ pendingCR = false;
+ }
+
private void encode(byte next) throws IOException {
- if (next <= SPACE) {
- escape(next);
- } else if (next > QUOTED_PRINTABLE_LAST_PLAIN) {
- escape(next);
- } else if (next == EQUALS) {
- escape(next);
+ if (next == LF) {
+ if (binary) {
+ writePending();
+ escape(next);
+ } else {
+ if (pendingCR) {
+ // Expect either space or tab pending
+ // but not both
+ if (pendingSpace) {
+ escape(SPACE);
+ } else if (pendingTab) {
+ escape(TAB);
+ }
+ lineBreak();
+ clearPending();
+ } else {
+ writePending();
+ plain(next);
+ }
+ }
+ } else if (next == CR) {
+ if (binary) {
+ escape(next);
+ } else {
+ pendingCR = true;
+ }
} else {
- plain(next);
+ writePending();
+ if (next == SPACE) {
+ if (binary) {
+ escape(next);
+ } else {
+ pendingSpace = true;
+ }
+ } else if (next == TAB) {
+ if (binary) {
+ escape(next);
+ } else {
+ pendingTab = true;
+ }
+ } else if (next < SPACE) {
+ escape(next);
+ } else if (next > QUOTED_PRINTABLE_LAST_PLAIN) {
+ escape(next);
+ } else if (next == EQUALS) {
+ escape(next);
+ } else {
+ plain(next);
+ }
}
}
@@ -157,6 +228,10 @@
private void softBreak() throws IOException {
write(EQUALS);
+ lineBreak();
+ }
+
+ private void lineBreak() throws IOException {
write(CR);
write(LF);
nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH;
@@ -173,6 +248,19 @@
}
/**
+ * Encodes the given stream using Quoted-Printable.
+ * This assumes that stream is text and therefore does not escape
+ * all line endings.
+ * @param in not null
+ * @param out not null
+ * @throws IOException
+ */
+ public static void encodeQuotedPrintable(final InputStream in, final
OutputStream out) throws IOException {
+ final QuotedPrintableEncoder encoder = new
QuotedPrintableEncoder(DEFAULT_ENCODING_BUFFER_SIZE, false);
+ encoder.encode(in, out);
+ }
+
+ /**
* Encodes the given stream using Base64.
* @param in not null
* @param out not null
Added:
james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableTextEncodeTest.java
URL:
http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableTextEncodeTest.java?rev=678541&view=auto
==============================================================================
---
james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableTextEncodeTest.java
(added)
+++
james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableTextEncodeTest.java
Mon Jul 21 13:03:38 2008
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+package org.apache.james.mime4j.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
+
+import junit.framework.TestCase;
+
+public class QuotedPrintableTextEncodeTest extends TestCase {
+
+ private static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testEscapedSoftBreak() throws Exception {
+ byte[] content = new byte[500];
+ Arrays.fill(content, (byte)0x18);
+ byte[] expected = new byte[1557];
+ int index = 0;
+ for (int l=0;l<20;l++) {
+ for (int i=0;i<25;i++) {
+ expected[index++] = '=';
+ expected[index++] = '1';
+ expected[index++] = '8';
+ }
+ if (l<19) {
+ expected[index++] = '=';
+ expected[index++] = '\r';
+ expected[index++] = '\n';
+ }
+ }
+ check(content, expected);
+ }
+
+ public void testPlainAsciiSoftBreak() throws Exception {
+ byte[] content = new byte[500];
+ Arrays.fill(content, (byte)0x29);
+ byte[] expected = new byte[518];
+ Arrays.fill(expected, (byte)0x29);
+ expected[75] = '=';
+ expected[76] = '\r';
+ expected[77] = '\n';
+ expected[153] = '=';
+ expected[154] = '\r';
+ expected[155] = '\n';
+ expected[231] = '=';
+ expected[232] = '\r';
+ expected[233] = '\n';
+ expected[309] = '=';
+ expected[310] = '\r';
+ expected[311] = '\n';
+ expected[387] = '=';
+ expected[388] = '\r';
+ expected[389] = '\n';
+ expected[465] = '=';
+ expected[466] = '\r';
+ expected[467] = '\n';
+ check(content, expected);
+ }
+
+ public void testPlainASCII() throws Exception {
+
checkRoundtrip("Thisisaverysimplemessage.Thisisaverysimplemessage.Thisisaverysimplemessage."
+
+
"Thisisaverysimplemessage.Thisisaverysimplemessage.Thisisaverysimplemessage." +
+
"Thisisaverysimplemessage.Thisisaverysimplemessage.Thisisaverysimplemessage." +
+
"Thisisaverysimplemessage.Thisisaverysimplemessage.Thisisaverysimplemessage.");
+ }
+
+ public void testEncodeSpace() throws Exception {
+ checkRoundtrip(" A");
+ }
+
+ public void testLetterEncoding() throws Exception {
+ for (byte b=0;b<Byte.MAX_VALUE;b++) {
+ byte[] content = {b};
+ // White space is only escaped when followed by CRLF
+ if (b != 32 && b != 9) {
+ checkRoundtrip(content);
+ }
+ }
+ }
+
+ public void testCRLFShouldResetLineCount() throws Exception {
+ StringBuffer buffer = new StringBuffer(4096);
+ for (int i=0;i<1000;i++) {
+ buffer.append("Hugo\r\n");
+ }
+ String longLine = buffer.toString();
+ check(longLine, longLine);
+ }
+
+ public void testDontEscapeLF() throws Exception {
+ check("Ready\nFor\n", "Ready\nFor\n");
+ }
+
+ public void testDontEscapeCR() throws Exception {
+ check("Ready\rFor\r", "Ready\rFor\r");
+ }
+
+ public void testEscapeSpaceAtLineEnd() throws Exception {
+ check(" \r\n", " =20\r\n");
+ }
+
+ public void testDontEscapeSpaceBeforeLineEnd() throws Exception {
+ check(" ", " ");
+ }
+
+ public void testDontEscapeTabsBeforeLineEnd() throws Exception {
+ check("\t\t\t\t", "\t\t\t\t");
+ }
+
+ public void testDontWhiteSpaceBeforeLineEnd() throws Exception {
+ check(" \t\t \t", " \t\t \t");
+ }
+
+ private void checkRoundtrip(String content) throws Exception {
+ checkRoundtrip(content, US_ASCII);
+ }
+
+ private void checkRoundtrip(String content, Charset charset) throws
Exception {
+ checkRoundtrip(charset.encode(content).array());
+ }
+
+ private void checkRoundtrip(byte[] content) throws Exception {
+ InputStream in = new ByteArrayInputStream(content);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ CodecUtil.encodeQuotedPrintable(in, out);
+ // read back through decoder
+ in = new QuotedPrintableInputStream(new
ByteArrayInputStream(out.toByteArray()));
+ out = new ByteArrayOutputStream();
+ IOUtils.copy(in, out);
+ assertEquals(content, out.toByteArray());
+ }
+
+ private void check(String content, String expected) throws Exception {
+ check(US_ASCII.encode(content).array(),
US_ASCII.encode(expected).array());
+ }
+
+
+ private void check(byte[] content, byte[] expected) throws Exception {
+ ByteArrayInputStream in = new ByteArrayInputStream(content);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ CodecUtil.encodeQuotedPrintable(in, out);
+ assertEquals(expected, out.toByteArray());
+ }
+
+ private void assertEquals(byte[] expected, byte[] actual) {
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < actual.length; i++) {
+ assertEquals("Mismatch@" + i, expected[i], actual[i]);
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]