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]

Reply via email to