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 rd...@apache.org on 2008/05/26 17:02:48 UTC

svn commit: r660206 - in /james/mime4j/trunk/src: main/java/org/apache/james/mime4j/message/TempFileBinaryBody.java main/java/org/apache/james/mime4j/util/CodecUtil.java test/java/org/apache/james/mime4j/util/QuotedPrintableEncodeTest.java

Author: rdonkin
Date: Mon May 26 08:02:46 2008
New Revision: 660206

URL: http://svn.apache.org/viewvc?rev=660206&view=rev
Log:
Added quoted printable encoding support for binaries. Completes fixes and test for MIME4J-36 (Wrong implementation of TempFileBinaryBody.writeTo) https://issues.apache.org/jira/browse/MIME4J-37

Added:
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableEncodeTest.java
Modified:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/TempFileBinaryBody.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/CodecUtil.java

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/TempFileBinaryBody.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/TempFileBinaryBody.java?rev=660206&r1=660205&r2=660206&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/TempFileBinaryBody.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/TempFileBinaryBody.java Mon May 26 08:02:46 2008
@@ -100,7 +100,8 @@
             CodecUtil.encodeBase64(inputStream, out);
             out.write(CodecUtil.CRLF_CRLF);
         } else if (MimeUtil.ENC_QUOTED_PRINTABLE.equals(transferEncoding)) {
-            IOUtils.copy(inputStream,out);
+            CodecUtil.encodeQuotedPrintableBinary(inputStream,out);
+            out.write(CodecUtil.CRLF_CRLF);
         } else {
             IOUtils.copy(inputStream,out);
         }

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=660206&r1=660205&r2=660206&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 May 26 08:02:46 2008
@@ -35,6 +35,128 @@
     
     private static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024;
     
+    private static final byte SPACE = 0x20;
+    private static final byte EQUALS = 0x3D;
+    private static final byte CR = 0x0D;
+    private static final byte LF = 0x0A;
+    private static final byte QUOTED_PRINTABLE_LAST_PLAIN = 0x7E;
+    private static final int QUOTED_PRINTABLE_MAX_LINE_LENGTH = 76;
+    private static final int QUOTED_PRINTABLE_OCTETS_PER_ESCAPE = 3;
+    private static final byte[] HEX_DIGITS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+        
+    /**
+     * Encodes the given stream using Quoted-Printable.
+     * This assumes that text is binary and therefore escapes
+     * all line endings.
+     * @param in not null
+     * @param out not null
+     * @throws IOException
+     */
+    public static void encodeQuotedPrintableBinary(final InputStream in, final OutputStream out) throws IOException {
+        
+        BinaryQuotedPrintableEncoder encoder = new BinaryQuotedPrintableEncoder(DEFAULT_ENCODING_BUFFER_SIZE);
+        encoder.encode(in, out);
+    }
+    
+    private static final class BinaryQuotedPrintableEncoder {
+        private final byte[] inBuffer;
+        private final byte[] outBuffer;
+        
+        private int nextSoftBreak;
+        private int inputIndex;
+        private int outputIndex;
+        private int inputLength;
+        private InputStream in;
+        private OutputStream out;
+        
+        
+        public BinaryQuotedPrintableEncoder(int bufferSize) {
+            inBuffer = new byte[bufferSize];
+            outBuffer = new byte[3*bufferSize];
+            inputLength = 0;
+            outputIndex = 0;
+            nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
+            in = null;
+            out = null;
+        }
+        
+        public void encode(final InputStream in, final OutputStream out) throws IOException {
+            this.in = in;
+            this.out = out;
+            nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
+            read();
+            while(inputLength > -1) {
+                while (inputIndex < inputLength) { 
+                    final byte next = inBuffer[inputIndex];
+                    encode(next);
+                    inputIndex++;
+                }
+                read();
+            }
+            flushOutput();
+        }
+
+        private void read() throws IOException {
+            inputLength = in.read(inBuffer);
+            inputIndex = 0;
+        }
+        
+        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);
+            } else {
+                plain(next);
+            }
+        }
+        
+        private void plain(byte next) throws IOException {
+            if (--nextSoftBreak <= 1) {
+                softBreak();
+            }
+            write(next);
+        }
+        
+        private void escape(byte next) throws IOException {
+            if (--nextSoftBreak <= QUOTED_PRINTABLE_OCTETS_PER_ESCAPE) {
+                softBreak();
+            }
+            write(EQUALS);
+            --nextSoftBreak;
+            write(HEX_DIGITS[next >> 4]);
+            --nextSoftBreak;
+            write(HEX_DIGITS[next % 0x10]);
+        }
+        
+        private void write(byte next) throws IOException {
+            outBuffer[outputIndex] = next;
+            if (outputIndex >= outBuffer.length) {
+                flushOutput();
+            } else {
+                outputIndex++;
+            }
+        }
+        
+        private void softBreak() throws IOException {
+            write(EQUALS);
+            write(CR);
+            write(LF);
+            nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH;
+        }
+        
+        private void flushOutput() throws IOException {
+            if (outputIndex < outBuffer.length) {
+                out.write(outBuffer, 0, outputIndex);
+            } else {
+                out.write(outBuffer);
+            }
+            outputIndex = 0;
+        }
+    }
+    
     /**
      * Encodes the given stream using Base64.
      * @param in not null

Added: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableEncodeTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableEncodeTest.java?rev=660206&view=auto
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableEncodeTest.java (added)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/QuotedPrintableEncodeTest.java Mon May 26 08:02:46 2008
@@ -0,0 +1,140 @@
+/*
+ * 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 QuotedPrintableEncodeTest 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)0x20);
+        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++] = '2';
+                expected[index++] = '0';
+            }
+            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("                 ");
+    }
+    
+    public void testLetterEncoding() throws Exception {
+        for (byte b=0;b<Byte.MAX_VALUE;b++) {
+            byte[] content = {b};
+            checkRoundtrip(content);
+        }
+    }
+    
+    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.encodeQuotedPrintableBinary(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(byte[] content, byte[] expected) throws Exception {
+        ByteArrayInputStream in = new ByteArrayInputStream(content);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        CodecUtil.encodeQuotedPrintableBinary(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: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org