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/02/10 12:28:30 UTC

svn commit: r620259 - in /james/mime4j/trunk/src: main/java/org/apache/james/mime4j/message/ main/java/org/apache/james/mime4j/util/ test/java/org/apache/james/mime4j/message/ test/java/org/apache/james/mime4j/util/

Author: rdonkin
Date: Sun Feb 10 03:28:21 2008
New Revision: 620259

URL: http://svn.apache.org/viewvc?rev=620259&view=rev
Log:
MIM4J-34 Fix header charset handling. Applied revised and more flexible version of the patch. Contributed by Oleg Kalnichevski (https://issues.apache.org/jira/browse/MIME4J-34).

Added:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/MessageUtils.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/MessageUtilsTest.java
Modified:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java?rev=620259&r1=620258&r2=620259&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java Sun Feb 10 03:28:21 2008
@@ -24,6 +24,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -36,6 +37,7 @@
 import org.apache.james.mime4j.field.ContentTypeField;
 import org.apache.james.mime4j.field.Field;
 import org.apache.james.mime4j.util.CharsetUtil;
+import org.apache.james.mime4j.util.MessageUtils;
 
 
 /**
@@ -154,17 +156,53 @@
     
     
     /**
+     * Write the Header to the given OutputStream. 
+     * 
+     * @param out the OutputStream to write to
+     * @param mode compatibility mode
+     * 
+     * @throws IOException if case of an I/O error
+     * @throws MimeException if case of a MIME protocol violation
+     */
+    public void writeTo(final OutputStream out, int mode) throws IOException, MimeException {
+        Charset charset = null;
+        if (mode == MessageUtils.LENIENT) {
+            ContentTypeField cf = ((ContentTypeField) getField(Field.CONTENT_TYPE));
+            if (cf != null && cf.getCharset() != null) {
+                charset = CharsetUtil.getCharset(cf.getCharset());
+            } else {
+                charset = MessageUtils.ISO_8859_1;
+            }
+        } else {
+            charset = MessageUtils.DEFAULT_CHARSET;
+        }
+        BufferedWriter writer = new BufferedWriter(
+                new OutputStreamWriter(out, charset), 8192);
+        for (Iterator it = fields.iterator(); it.hasNext();) {
+            Field field = (Field) it.next();
+            String fs = field.toString();
+            if (mode == MessageUtils.STRICT_ERROR && !MessageUtils.isASCII(fs)) {
+                throw new MimeException("Header '" + fs + "' violates RFC 822");
+            }
+            writer.write(fs);
+            writer.write(MessageUtils.CRLF);
+        }
+        writer.write(MessageUtils.CRLF);
+        writer.flush();
+    }
+
+    /**
      * Write the Header to the given OutputStream
      * 
      * @param out the OutputStream to write to
      * @throws IOException
      */
-    public void writeTo(OutputStream out) throws IOException {
-        String charString = ((ContentTypeField) getField(Field.CONTENT_TYPE)).getCharset();
-        
-        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, CharsetUtil.getCharset(charString)),8192);
-        writer.write(toString()+ "\r\n");
-        writer.flush();
+    public void writeTo(final OutputStream out) throws IOException {
+        try {
+            writeTo(out, MessageUtils.LENIENT);
+        } catch (MimeException ex) {
+            throw new IOException(ex.getMessage());
+        }
     }
 
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java?rev=620259&r1=620258&r2=620259&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java Sun Feb 10 03:28:21 2008
@@ -23,14 +23,17 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 
+import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.field.ContentTypeField;
 import org.apache.james.mime4j.field.Field;
 import org.apache.james.mime4j.util.CharsetUtil;
+import org.apache.james.mime4j.util.MessageUtils;
 
 /**
  * Represents a MIME multipart body (see RFC 2045).A multipart body has a 
@@ -162,52 +165,73 @@
     }
 
     /**
+     * Write the Multipart to the given OutputStream. 
      * 
-     * @see org.apache.james.mime4j.message.Body#writeTo(java.io.OutputStream)
+     * @param out the OutputStream to write to
+     * @param mode compatibility mode
+     * 
+     * @throws IOException if case of an I/O error
+     * @throws MimeException if case of a MIME protocol violation
      */
-    public void writeTo(OutputStream out) throws IOException {
-        String boundary = getBoundary();
-        List bodyParts = getBodyParts();
+    public void writeTo(final OutputStream out, int mode) throws IOException, MimeException {
+        Entity e = getParent();
+        
+        ContentTypeField cField = (ContentTypeField) e.getHeader().getField(
+                Field.CONTENT_TYPE);
+        if (cField == null || cField.getBoundary() == null) {
+            throw new MimeException("Multipart boundary not specified");
+        }
+        String boundary = cField.getBoundary();
 
+        Charset charset = null;
+        if (mode == MessageUtils.LENIENT) {
+            if (cField != null && cField.getCharset() != null) {
+                charset = CharsetUtil.getCharset(cField.getCharset());
+            } else {
+                charset = MessageUtils.ISO_8859_1;
+            }
+        } else {
+            charset = MessageUtils.DEFAULT_CHARSET;
+        }
+        
         BufferedWriter writer = new BufferedWriter(
-                new OutputStreamWriter(out, CharsetUtil.getCharset(getCharset())),
-                8192);
+                new OutputStreamWriter(out, charset), 8192);
         
+        List bodyParts = getBodyParts();
+
         writer.write(getPreamble());
-        writer.write("\r\n");
+        writer.write(MessageUtils.CRLF);
 
         for (int i = 0; i < bodyParts.size(); i++) {
             writer.write("--");
             writer.write(boundary);
-            writer.write("\r\n");
+            writer.write(MessageUtils.CRLF);
             writer.flush();
             ((BodyPart) bodyParts.get(i)).writeTo(out);
-            writer.write("\r\n");
+            writer.write(MessageUtils.CRLF);
         }
 
         writer.write("--");
         writer.write(boundary);
-        writer.write("--\r\n");
+        writer.write("--");
+        writer.write(MessageUtils.CRLF);
         writer.write(getEpilogue());
-        writer.write("\r\n");
+        writer.write(MessageUtils.CRLF);
         writer.flush();
     }
 
     /**
-     * Return the boundory of the parent Entity
+     * Write the Header to the given OutputStream
      * 
-     * @return boundery
+     * @param out the OutputStream to write to
+     * @throws IOException
      */
-    private String getBoundary() {
-        Entity e = getParent();
-        ContentTypeField cField = (ContentTypeField) e.getHeader().getField(
-                Field.CONTENT_TYPE);
-        return cField.getBoundary();
-    }
-    
-    private String getCharset() {
-        Entity e = getParent();
-        String charString = ((ContentTypeField) e.getHeader().getField(Field.CONTENT_TYPE)).getCharset();
-        return charString;
+    public void writeTo(final OutputStream out) throws IOException {
+        try {
+            writeTo(out, MessageUtils.LENIENT);
+        } catch (MimeException ex) {
+            throw new IOException(ex.getMessage());
+        }
     }
+
 }

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/MessageUtils.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/MessageUtils.java?rev=620259&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/MessageUtils.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/MessageUtils.java Sun Feb 10 03:28:21 2008
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.nio.charset.Charset;
+
+
+/**
+ * Frequently used RFC 882 constants and utility methods.
+ * 
+ * @version $Id:$
+ */
+public final class MessageUtils {
+ 
+    public static final int STRICT_IGNORE     = 1;
+    public static final int STRICT_ERROR      = 2;
+    public static final int LENIENT           = 3;
+    
+    public static final Charset ASCII = CharsetUtil.getCharset("US-ASCII");
+
+    public static final Charset ISO_8859_1 = CharsetUtil.getCharset("ISO-8859-1");
+    
+    public static final Charset DEFAULT_CHARSET = ASCII;
+
+    public static final String CRLF = "\r\n";
+    
+    public static boolean isASCII(char ch) {
+        return ((int)ch & 0xFF80) == 0;
+    }
+    
+    public static boolean isASCII(final String s) {
+        if (s == null) {
+            throw new IllegalArgumentException("String may not be null");
+        }
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            if (!isASCII(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+}

Modified: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java?rev=620259&r1=620258&r2=620259&view=diff
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java (original)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java Sun Feb 10 03:28:21 2008
@@ -19,8 +19,11 @@
 
 package org.apache.james.mime4j.message;
 
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.field.Field;
 import org.apache.james.mime4j.message.Header;
+import org.apache.james.mime4j.util.MessageUtils;
 
 import junit.framework.TestCase;
 
@@ -41,4 +44,64 @@
         assertEquals("Headers equals", SUBJECT + "\r\n" + TO + "\r\n", header
                 .toString());
     }
+    
+    static final int SWISS_GERMAN_HELLO [] = {
+        0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
+    };
+        
+    private static String constructString(int [] unicodeChars) {
+        StringBuffer buffer = new StringBuffer();
+        if (unicodeChars != null) {
+            for (int i = 0; i < unicodeChars.length; i++) {
+                buffer.append((char)unicodeChars[i]); 
+            }
+        }
+        return buffer.toString();
+    }
+
+    public void testWriteInStrictMode() throws Exception {
+        String hello = constructString(SWISS_GERMAN_HELLO);
+        Header header = new Header();
+        header.addField(Field.parse("Hello: " + hello));
+        
+        Field field = header.getField("Hello");
+        assertNotNull(field);
+        assertEquals(hello, field.getBody());
+        
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        
+        header.writeTo(buffer, MessageUtils.STRICT_IGNORE);
+        String s = buffer.toString(MessageUtils.ASCII.name());
+        
+        assertEquals("Hello: Gr?ezi_z?m?\r\n\r\n", s);
+
+        buffer.reset();
+        
+        try {
+            header.writeTo(buffer, MessageUtils.STRICT_ERROR);
+            fail("MimeException should have been thrown");
+        } catch (MimeException expected) {
+        }
+    }
+    
+    public void testWriteInLenientMode() throws Exception {
+        String hello = constructString(SWISS_GERMAN_HELLO);
+        Header header = new Header();
+        header.addField(Field.parse("Hello: " + hello));
+        header.addField(Field.parse("Content-type: text/plain; charset=" + 
+                MessageUtils.ISO_8859_1.name()));
+        
+        Field field = header.getField("Hello");
+        assertNotNull(field);
+        assertEquals(hello, field.getBody());
+        
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        
+        header.writeTo(buffer, MessageUtils.LENIENT);
+        String s = buffer.toString(MessageUtils.ISO_8859_1.name());
+        
+        assertEquals("Hello: " + hello + "\r\n" +
+        		"Content-type: text/plain; charset=ISO-8859-1\r\n\r\n", s);
+    }
+    
 }

Added: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/MessageUtilsTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/MessageUtilsTest.java?rev=620259&view=auto
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/MessageUtilsTest.java (added)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/util/MessageUtilsTest.java Sun Feb 10 03:28:21 2008
@@ -0,0 +1,54 @@
+/*
+ * 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 junit.framework.TestCase;
+
+public class MessageUtilsTest extends TestCase {
+
+    public void testAllASCII() {
+        String s = "Like hello and stuff";
+        assertTrue(MessageUtils.isASCII(s));
+    }
+
+    static final int RUSSIAN_HELLO [] = {
+        0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438, 
+        0x432, 0x435, 0x442 
+    }; 
+    
+    static final int SWISS_GERMAN_HELLO [] = {
+        0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
+    };
+        
+    private static String constructString(int [] unicodeChars) {
+        StringBuffer buffer = new StringBuffer();
+        if (unicodeChars != null) {
+            for (int i = 0; i < unicodeChars.length; i++) {
+                buffer.append((char)unicodeChars[i]); 
+            }
+        }
+        return buffer.toString();
+    }
+
+    public void testNonASCII() {
+        String s = constructString(SWISS_GERMAN_HELLO);
+        assertFalse(MessageUtils.isASCII(s));
+        s = constructString(RUSSIAN_HELLO);
+        assertFalse(MessageUtils.isASCII(s));
+    }
+    
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org