[Git][java-team/apache-mime4j][upstream] New upstream version 0.8.13

Tony Mancill (@tmancill) gitlab at salsa.debian.org
Sat Aug 16 05:09:29 BST 2025



Tony Mancill pushed to branch upstream at Debian Java Maintainers / apache-mime4j


Commits:
b8b38b36 by tony mancill at 2025-08-15T20:52:30-07:00
New upstream version 0.8.13
- - - - -


27 changed files:

- CHANGELOG.md
- assemble/pom.xml
- benchmark/pom.xml
- core/pom.xml
- core/src/main/java/org/apache/james/mime4j/io/MimeBoundaryInputStream.java
- core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java
- core/src/main/java/org/apache/james/mime4j/util/MimeParameterMapping.java
- core/src/main/javadoc/overview.html
- + core/src/test/java/org/apache/james/mime4j/parser/ExtractPartWithDifferentLengthsTest.java
- core/src/test/java/org/apache/james/mime4j/parser/MimeStreamParserTest.java
- core/src/test/java/org/apache/james/mime4j/parser/TestHandler.java
- core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java
- core/src/test/java/org/apache/james/mime4j/stream/RawFieldTest.java
- dom/pom.xml
- dom/src/main/java/org/apache/james/mime4j/dom/Disposable.java
- dom/src/main/java/org/apache/james/mime4j/field/DefaultFieldParser.java
- dom/src/main/java/org/apache/james/mime4j/field/LenientFieldParser.java
- dom/src/main/java/org/apache/james/mime4j/field/address/LenientAddressParser.java
- dom/src/main/jjtree/org/apache/james/mime4j/field/address/AddressListParser.jjt
- dom/src/test/java/org/apache/james/mime4j/field/LenientContentDispositionFieldTest.java
- dom/src/test/java/org/apache/james/mime4j/field/address/DefaultAddressBuilderTest.java
- dom/src/test/java/org/apache/james/mime4j/field/address/LenientAddressBuilderTest.java
- examples/pom.xml
- james-utils/pom.xml
- mbox/pom.xml
- pom.xml
- storage/pom.xml


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 
+## [0.8.13] - unreleased
+
+ - MIME4J-332 Improve handling of "sectioned" field names
+ - MIME4J-331 Add support for RFC-6532 Internationalized Email Headers, and test it. (#114)
+ - MIME4J-330 Fix MimeStreamParser bug: part body stream ends with CR
+
+## [0.8.12] - 2024-12-23
+
+New release tagged without changes.
+
 ## [0.8.11] - 2024-03-05
 
  - MIME4J-326 SILENT monitor for header decoding


=====================================
assemble/pom.xml
=====================================
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>apache-mime4j-project</artifactId>
         <groupId>org.apache.james</groupId>
-        <version>0.8.12</version>
+        <version>0.8.13</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 


=====================================
benchmark/pom.xml
=====================================
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>apache-mime4j-project</artifactId>
         <groupId>org.apache.james</groupId>
-        <version>0.8.12</version>
+        <version>0.8.13</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 


=====================================
core/pom.xml
=====================================
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>apache-mime4j-project</artifactId>
         <groupId>org.apache.james</groupId>
-        <version>0.8.12</version>
+        <version>0.8.13</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 


=====================================
core/src/main/java/org/apache/james/mime4j/io/MimeBoundaryInputStream.java
=====================================
@@ -245,14 +245,14 @@ public class MimeBoundaryInputStream extends LineReaderInputStream {
                     // Make sure the boundary is terminated with EOS
                     break;
                 } else {
-                    // or with a whitespace or '--' 
+                    // or with a whitespace or '--'
                     char ch = (char)(buffer.byteAt(pos));
                     if (CharsetUtil.isWhitespace(ch)) {
                         break;
                     }
                     if (ch == '-' && remaining > 1 && (char)(buffer.byteAt(pos+1)) == '-') {
                         break;
-                    } 
+                    }
                 }
             }
             off = i + boundary.length;
@@ -265,8 +265,14 @@ public class MimeBoundaryInputStream extends LineReaderInputStream {
             if (eof) {
                 limit = buffer.limit();
             } else {
-                limit = buffer.limit() - (boundary.length + 2);
-                                // [LF] [boundary] [CR][LF] minus one char
+                int position = buffer.limit() - (boundary.length + 3);    // 2nd position before boundary
+                if (position >= 0 && (char) buffer.byteAt(position) == '\r') {
+                    limit = buffer.limit() - (boundary.length + 3);
+                    // boundary [CR][LF] minus two chars
+                } else {
+                    limit = buffer.limit() - (boundary.length + 2);
+                    // boundary [LF] minus one char
+                }
             }
         }
         return bytesRead;


=====================================
core/src/main/java/org/apache/james/mime4j/stream/RawFieldParser.java
=====================================
@@ -323,6 +323,7 @@ public class RawFieldParser {
      */
     public void copyContent(final ByteSequence buf, final ParserCursor cursor, final BitSet delimiters,
             final StringBuilder dst) {
+        ByteArrayBuffer dstRaw = new ByteArrayBuffer(80);
         int pos = cursor.getPos();
         int indexFrom = cursor.getPos();
         int indexTo = cursor.getUpperBound();
@@ -333,10 +334,11 @@ public class RawFieldParser {
                 break;
             } else {
                 pos++;
-                dst.append(current);
+                dstRaw.append(current);
             }
         }
         cursor.updatePos(pos);
+        dst.append(ContentUtil.decode(StandardCharsets.UTF_8, dstRaw));
     }
 
     /**


=====================================
core/src/main/java/org/apache/james/mime4j/util/MimeParameterMapping.java
=====================================
@@ -19,33 +19,160 @@
 
 package org.apache.james.mime4j.util;
 
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
+import org.apache.james.mime4j.MimeException;
+
+/**
+ * This class tracks parameter mappings and tries to respect <a href="https://www.rfc-editor.org/rfc/rfc2231">rfc2231</a>
+ * and conform roughly to Thunderbird's behavior as described on <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=588781>bug 588781</a>.
+ * <p>
+ * See <a href="https://issues.apache.org/jira/browse/MIME4J-332">MIME4J-332</a>
+ * <p>
+ *     These are the behaviors:
+ * <ul>
+ *    <li>If there are multiple keys for standard parameters, use the first and ignore the rest</li>
+ *    <li>If there are multiple keys for extended parameters, use the first and ignore the rest</li>
+ *    <li>If there's an extended and standard key for a given field, prefer the extended</li>
+ *    <li>If there's an extended and a continuation, prefer the extended, recognizing that a continuation field may also include charset information</li>
+ * </ul>
+ */
 public class MimeParameterMapping {
 
+    /**
+     * What type of parameter name is it
+     */
+    enum PARAMETER_TYPE {
+        /**
+         * standard parameter name, e.g. "filename"
+         */
+        STANDARD,
+        /**
+         * extended parameter without continuation numbers, e.g. "filename*", typically a sign that charset is included
+         */
+        EXTENDED,
+        /**
+         * continuation parameter, e.g. "filename*0", used to extend field lengths beyond the initial length limit, may also
+         * include charset information like {@link PARAMETER_TYPE#EXTENDED}
+         */
+        CONTINUATION
+    }
+
+    //this could be more precise and require only a *\\d+\\Z
+    private static final Pattern STAR_AND_NUMBER = Pattern.compile("\\*\\d");
+
+    private final Set<String> parameterNames = new HashSet<>();
+    private final Map<String, String> standard = new HashMap<>();
+    private final Map<String, String> extended = new HashMap<>();
+    private final Map<String, String> continuation = new HashMap<>();
+
     private final Map<String, String> parameters = new HashMap<>();
-    /** Charset, taken from the first item added to {@link #parameters}. */
+    private boolean needToUpdate = true;
+
+    /** Charset, taken from the first item added via {@link #addParameter(String, String)}. */
     private String charset;
 
+    /**
+     *
+     * @return an Unmodifiable map of the parameters as calculated
+     * by the algorithm described in the class javadoc via {@link #get(String)}
+     */
     public Map<String, String> getParameters() {
-        return parameters;
+        if (needToUpdate) {
+            updateParameters();
+            needToUpdate = false;
+        }
+        return Collections.unmodifiableMap(parameters);
     }
 
+    private void updateParameters() {
+        parameters.clear();
+        for (String param : parameterNames) {
+            parameters.put(param, get(param));
+        }
+    }
+
+    /**
+     * Applies the algorithm described in the class javadoc and returns
+     * the decoded value from the best option.
+     *
+     * @param name field name (must be lowercased)
+     * @return field value or <code>null</code> if it doesn't exist
+     */
     public String get(String name) {
-        return parameters.get(name);
+        if (! parameterNames.contains(name)) {
+            return null;
+        }
+
+        if (extended.containsKey(name)) {
+            try {
+                return decodeParameterValue(extended.get(name));
+            } catch (DecodeException e) {
+                //ignore and try next
+            }
+        }
+        if (continuation.containsKey(name)) {
+            try {
+                return decodeParameterValue(continuation.get(name));
+            } catch (DecodeException e) {
+                //ignore and try standard
+            }
+        }
+        if (standard.containsKey(name)) {
+            try {
+                return decodeParameterValue(standard.get(name));
+            } catch (DecodeException e) {
+                //ignore and try without decoding
+            }
+        }
+        //couldn't decode anything. not clear what the "spec" says is the right behavior
+        //For now, back off in reverse order
+        if (standard.containsKey(name)) {
+            return standard.get(name);
+        }
+
+        if (continuation.containsKey(name)) {
+            return continuation.get(name);
+        }
+
+        if (extended.containsKey(name)) {
+            return extended.get(name);
+        }
+        //this should be unreachable
+        return null;
     }
 
-    public void addParameter(String name, String value) {
-        String key = removeSectionFromName(name).toLowerCase();
-        if (parameters.containsKey(key)) {
-            parameters.put(key, decodeParameterValue(parameters.get(key) + value));
-        } else {
-            parameters.put(key, decodeParameterValue(value));
+    public void addParameter(final String name, String value) {
+        ParameterTypePair parameterTypePair = getParameterTypePair(name);
+        parameterNames.add(parameterTypePair.fieldName);
+        needToUpdate = true;
+        switch (parameterTypePair.fieldType) {
+            case EXTENDED:
+                extended.putIfAbsent(parameterTypePair.fieldName, value);
+                break;
+            case CONTINUATION:
+                if (continuation.containsKey(parameterTypePair.fieldName)) {
+                    String newValue = continuation.get(parameterTypePair.fieldName) + value;
+                    continuation.put(parameterTypePair.fieldName, newValue);
+                } else {
+                    continuation.put(parameterTypePair.fieldName, value);
+                }
+                break;
+            case STANDARD:
+                standard.putIfAbsent(parameterTypePair.fieldName, value);
         }
     }
 
-    private String decodeParameterValue(String value) {
+    private String decodeParameterValue(String value) throws DecodeException {
         if (value == null) {
             return null;
         }
@@ -60,20 +187,65 @@ public class MimeParameterMapping {
         }
         charset = value.substring(0, charsetEnd);
         String fileName = value.substring(languageEnd + 1);
+        //check that the charset is valid
+        try {
+            Charset.forName(charset);
+        } catch (IllegalArgumentException e) {
+            return fileName;
+        }
         return urlDecode(fileName);
     }
 
-    private String urlDecode(String value) {
+    private String urlDecode(String value) throws DecodeException {
         try {
             return java.net.URLDecoder.decode(value, charset);
-        }
-        catch (Exception ignore) {
-            return value;
+        } catch (Exception e) {
+            throw new DecodeException(e);
         }
     }
 
-    private String removeSectionFromName(String parameterName) {
+    private ParameterTypePair getParameterTypePair(String parameterName) {
         int position = parameterName.indexOf('*');
-        return parameterName.substring(0, position < 0 ? parameterName.length() : position);
+        if (position < 0) {
+            return new ParameterTypePair(parameterName.toLowerCase(Locale.ROOT), PARAMETER_TYPE.STANDARD);
+        }
+        String fieldName = parameterName.substring(0, position);
+        fieldName = fieldName.toLowerCase(Locale.ROOT);
+
+        String starAndAfter = parameterName.substring(position);
+        if ("*".equals(starAndAfter)) {
+            return new ParameterTypePair(fieldName, PARAMETER_TYPE.EXTENDED);
+        }
+        Matcher m = STAR_AND_NUMBER.matcher(starAndAfter);
+        if (m.find()) {
+            return new ParameterTypePair(fieldName, PARAMETER_TYPE.CONTINUATION);
+        }
+        //something is weird, treat this as a STAR?
+        return new ParameterTypePair(fieldName, PARAMETER_TYPE.EXTENDED);
+    }
+
+    private static class ParameterTypePair {
+        private final String fieldName;
+        private final PARAMETER_TYPE fieldType;
+
+        public ParameterTypePair(String fieldName, PARAMETER_TYPE fieldType) {
+            this.fieldName = fieldName;
+            this.fieldType = fieldType;
+        }
+    }
+
+    private static class DecodeException extends MimeException {
+
+        public DecodeException(String message) {
+            super(message);
+        }
+
+        public DecodeException(Throwable cause) {
+            super(cause);
+        }
+
+        public DecodeException(String message, Throwable cause) {
+            super(message, cause);
+        }
     }
 }


=====================================
core/src/main/javadoc/overview.html
=====================================
@@ -34,7 +34,7 @@
       However, mime4j does include facilities to decode bodies and fields.</p>
     <p>The parser has been designed to be tolerant against messages violating the standards. It has
       been tested using a large corpus (>5000) of e-mail messages. As a benchmark the widely used
-      perl MIME::Tools parser has been used. mime4j and MIME:Tools rarely differ (<25 in those
+      perl MIME::Tools parser has been used. mime4j and MIME:Tools rarely differ (<25 in those
       5000). When they do (which only occurs for illegally formatted spam messages) we think mime4j
       does a better job.</p>
   </BODY>


=====================================
core/src/test/java/org/apache/james/mime4j/parser/ExtractPartWithDifferentLengthsTest.java
=====================================
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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.parser;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.mime4j.MimeException;
+import org.apache.james.mime4j.stream.BodyDescriptor;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+public class ExtractPartWithDifferentLengthsTest {
+    @Test
+    public void testExtractPartWithDifferentLengths() throws Exception {
+        StringBuilder partBuilder = new StringBuilder();
+        for (int i = 1; i <= 5000; i++) {
+            partBuilder.append(i % 80 == 0 ? "\n" : "A");
+            String part = partBuilder.toString();
+            String mimeMessage = createMimeMultipart(part);
+
+            String extracted = extractPart(mimeMessage);
+            Assert.assertEquals(part, extracted);
+        }
+    }
+
+    private String createMimeMultipart(String part) {
+        return "Content-type: multipart/mixed; boundary=QvEgqhjEnYxz\n"
+            + "\n"
+            + "--QvEgqhjEnYxz\n"
+            + "Content-Type: text/plain\n"
+            + "\n"
+            + part
+            + "\r\n"
+            + "--QvEgqhjEnYxz--\n";
+    }
+
+    private String extractPart(String mimeMessage) throws
+        MimeException, IOException {
+        String[] resultWrapper = new String[1];
+
+        MimeStreamParser parser = new MimeStreamParser();
+        parser.setContentHandler(new AbstractContentHandler() {
+            @Override
+            public void body(BodyDescriptor bd, InputStream is) throws IOException {
+                resultWrapper[0] = new String(IOUtils.toString(is,
+                    StandardCharsets.UTF_8).getBytes());
+            }
+        });
+        parser.parse(new ByteArrayInputStream(mimeMessage.getBytes()));
+        return resultWrapper[0];
+    }
+}


=====================================
core/src/test/java/org/apache/james/mime4j/parser/MimeStreamParserTest.java
=====================================
@@ -21,6 +21,7 @@ package org.apache.james.mime4j.parser;
 
 import org.apache.james.mime4j.stream.BodyDescriptor;
 import org.apache.james.mime4j.stream.Field;
+import org.apache.james.mime4j.stream.MimeConfig;
 import org.apache.james.mime4j.util.ByteSequence;
 import org.apache.james.mime4j.util.ContentUtil;
 import org.junit.Assert;
@@ -29,6 +30,7 @@ import org.junit.Test;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.Charset;
 import java.util.LinkedList;
 
 public class MimeStreamParserTest {
@@ -430,6 +432,55 @@ public class MimeStreamParserTest {
         Assert.assertEquals(expected, result);
     }
 
+    @Test
+    public void testRfc6531() throws Exception {
+        MimeStreamParser parser = new MimeStreamParser(MimeConfig.STRICT);
+        parser.setContentDecoding(true);
+        TestHandler handler = new TestHandler();
+        parser.setContentHandler(handler);
+
+        String msg = "Subject: Naïve Subject\r\n"
+                + "From: foo@ø.example\r\n"
+                + "To: ø@example.com\r\n"
+                + "Content-Type: text/plain; charset=utf-8\r\n"
+                + "\r\n"
+                + "This sentence ends with the letter x.\r\n";
+        String expected = "<message>\r\n"
+                + "<header>\r\n"
+                + "<field>\r\n"
+                + "Subject: Naïve Subject"
+                + "</field>\r\n"
+                + "<field>\r\n"
+                + "From: foo@ø.example"
+                + "</field>\r\n"
+                + "<field>\r\n"
+                + "To: ø@example.com"
+                + "</field>\r\n"
+                + "<field>\r\n"
+                + "Content-Type: text/plain; charset=utf-8"
+                + "</field>\r\n"
+                + "</header>\r\n"
+                + "<body>\r\n"
+                + "This sentence ends with the letter x.\r\n"
+                + "</body>\r\n"
+                + "</message>\r\n";
+
+        // Dot the ı's and check that the ø is present in the message
+        // as its UTF8 encoding, 0xC3 0xB8. If the test uses anything
+        // else, then passing the test doesn't imply correctness.
+        byte[] msgAsUtf8 = msg.getBytes(Charset.forName("utf8"));
+        int i = 0;
+        while(i+1 < msgAsUtf8.length &&
+              (msgAsUtf8[i] != 0xC3 || msgAsUtf8[i+1] != 0xB8))
+              i++;
+        Assert.assertTrue(i < msgAsUtf8.length);
+
+        parser.parse(new ByteArrayInputStream(msgAsUtf8));
+        String result = handler.sb.toString();
+
+        Assert.assertEquals(expected, result);
+    }
+
     protected String decode(ByteSequence byteSequence) {
         return ContentUtil.decode(byteSequence);
     }


=====================================
core/src/test/java/org/apache/james/mime4j/parser/TestHandler.java
=====================================
@@ -22,6 +22,7 @@ package org.apache.james.mime4j.parser;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.parser.ContentHandler;
 import org.apache.james.mime4j.stream.BodyDescriptor;
 import org.apache.james.mime4j.stream.Field;
@@ -94,7 +95,7 @@ class TestHandler implements ContentHandler {
         sb.append("<header>\r\n");
     }
     public void field(Field field) {
-        sb.append("<field>\r\n").append(escape(ContentUtil.decode(field.getRaw()))).append("</field>\r\n");
+        sb.append("<field>\r\n").append(escape(ContentUtil.decode(Charsets.UTF_8, field.getRaw()))).append("</field>\r\n");
     }
     public void endHeader() {
         sb.append("</header>\r\n");


=====================================
core/src/test/java/org/apache/james/mime4j/stream/RawFieldParserTest.java
=====================================
@@ -75,6 +75,25 @@ public class RawFieldParserTest {
         Assert.assertTrue(cursor.atEnd());
     }
 
+
+    @Test
+    public void testUtf8StringParsing() throws Exception {
+        String s = "grå \"rød\"";
+        ByteSequence raw = ContentUtil.encode(s);
+        ParserCursor cursor = new ParserCursor(0, 2 + s.length());
+
+        StringBuilder strbuf1 = new StringBuilder();
+        parser.copyContent(raw, cursor, RawFieldParser.INIT_BITSET(':'), strbuf1);
+        Assert.assertFalse(cursor.atEnd());
+        Assert.assertEquals("grå", strbuf1.toString());
+
+        parser.skipWhiteSpace(raw, cursor);
+
+        StringBuilder strbuf2 = new StringBuilder();
+        parser.copyQuotedContent(raw, cursor, strbuf2);
+        Assert.assertEquals("rød", strbuf2.toString());
+    }
+
     @Test
     public void testTokenParsingWithQuotedPairs() throws Exception {
         String s = "raw: \"\\\"some\\stuff\\\\\"";
@@ -228,6 +247,17 @@ public class RawFieldParserTest {
         }
     }
 
+    @Test
+    public void testLongString() throws Exception {
+        String body = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
+        String s = "raw: " + body;
+        ByteSequence raw = ContentUtil.encode(s);
+
+        RawField rawField = parser.parseField(raw);
+        Assert.assertEquals("raw", rawField.getName());
+        Assert.assertEquals(body, rawField.getBody());
+    }
+
     @Test
     public void testParsingInvalidSyntax2() throws Exception {
         String s = "raw    \t \t";


=====================================
core/src/test/java/org/apache/james/mime4j/stream/RawFieldTest.java
=====================================
@@ -38,6 +38,9 @@ public class RawFieldTest {
         Assert.assertEquals("raw", field.getName());
         Assert.assertEquals("stuff;  more stuff", field.getBody());
         Assert.assertEquals(s, field.toString());
+        raw = ContentUtil.encode("To: ø@ø.example");
+        field = new RawField(raw, 3, "To", null);
+        Assert.assertEquals("ø@ø.example", field.getBody());
     }
 
     @Test


=====================================
dom/pom.xml
=====================================
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>apache-mime4j-project</artifactId>
         <groupId>org.apache.james</groupId>
-        <version>0.8.12</version>
+        <version>0.8.13</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 


=====================================
dom/src/main/java/org/apache/james/mime4j/dom/Disposable.java
=====================================
@@ -20,7 +20,7 @@
 package org.apache.james.mime4j.dom;
 
 /**
- * A <tt>Disposable</tt> is an object that should be disposed of explicitly
+ * A <code>Disposable</code> is an object that should be disposed of explicitly
  * when it is no longer needed.
  *
  * The dispose method is invoked to release resources that the object is


=====================================
dom/src/main/java/org/apache/james/mime4j/field/DefaultFieldParser.java
=====================================
@@ -83,7 +83,8 @@ public class DefaultFieldParser extends DelegatingFieldParser {
      * Parses the given string and returns an instance of the {@link ParsedField} class. 
      * The type of the class returned depends on the field name:
      * </p>
-     * <table summary="Field names and corresponding classes">
+     * <table>
+     *   <caption>Field names and corresponding classes</caption>
      *   <tr><th>Class returned</th><th>Field names</th></tr>
      *   <tr><td>{@link ContentTypeField}</td><td>Content-Type</td></tr>
      *   <tr><td>{@link ContentLengthField}</td><td>Content-Length</td></tr>


=====================================
dom/src/main/java/org/apache/james/mime4j/field/LenientFieldParser.java
=====================================
@@ -103,7 +103,8 @@ public class LenientFieldParser extends DelegatingFieldParser {
      * Parses the given string and returns an instance of the {@link ParsedField} class. 
      * The type of the class returned depends on the field name:
      * </p>
-     * <table summary="Field names and corresponding classes">
+     * <table>
+     *   <caption>Field names and corresponding classes</caption>
      *   <tr><th>Class returned</th><th>Field names</th></tr>
      *   <tr><td>{@link ContentTypeField}</td><td>Content-Type</td></tr>
      *   <tr><td>{@link ContentLengthField}</td><td>Content-Length</td></tr>


=====================================
dom/src/main/java/org/apache/james/mime4j/field/address/LenientAddressParser.java
=====================================
@@ -217,7 +217,7 @@ public class LenientAddressParser implements AddressParser {
 
     public Mailbox parseMailbox(final CharSequence text) {
         ByteSequence raw = ContentUtil.encode(text);
-        ParserCursor cursor = new ParserCursor(0, text.length());
+        ParserCursor cursor = new ParserCursor(0, raw.length());
         return parseMailbox(raw, cursor, null);
     }
 


=====================================
dom/src/main/jjtree/org/apache/james/mime4j/field/address/AddressListParser.jjt
=====================================
@@ -243,7 +243,9 @@ TOKEN :
 {
 	< #ALPHA: ["a" - "z", "A" - "Z"] >
 |	< #DIGIT: ["0" - "9"] >
+|	< UTF8NONASCII: ["\u0080" - "\uFFFF"] >
 |	< #ATEXT: ( <ALPHA> | <DIGIT>
+			  | <UTF8NONASCII>
 			  | "!" | "#" | "$" | "%"
 			  | "&" | "'" | "*" | "+"
 			  | "-" | "/" | "=" | "?"


=====================================
dom/src/test/java/org/apache/james/mime4j/field/LenientContentDispositionFieldTest.java
=====================================
@@ -19,6 +19,8 @@
 
 package org.apache.james.mime4j.field;
 
+import static org.junit.Assert.assertEquals;
+
 import java.util.Date;
 
 import org.apache.james.mime4j.MimeException;
@@ -231,10 +233,65 @@ public class LenientContentDispositionFieldTest {
     public void testBadEncodingFilename() throws MimeException {
          ContentDispositionField f = parse("Content-Disposition: attachment; \n" +
                  "        filename*=utf-8''4%P001!.DOC;\n" +
-                 "        filename=\"4%P001!.DOC\"");
+                 "        filename=\"4%P002!.DOC\"");
 
-         Assert.assertEquals(f.getDispositionType(), "attachment");
-         Assert.assertEquals(f.getFilename(), "4%P001!.DOC4%P001!.DOC");
+         Assert.assertEquals("attachment", f.getDispositionType());
+         Assert.assertEquals("4%P002!.DOC", f.getFilename());
     }
 
+    @Test
+    public void testDuplicateFields() throws MimeException {
+        //test that the first is taken and that concatenation is not applied
+        ContentDispositionField f = parse("Content-Disposition: attachment; \n" +
+                "filename=\"foo\";\n" +
+                "filename=\"bar2.rtf\";\n" +
+                "filename=\"bar3.rtf\"");
+        assertEquals("foo", f.getFilename());
+    }
+
+    @Test
+    public void testDuplicateFieldsPreferExtended() throws MimeException {
+        //test that extended field names are preferred to standard field names
+        ContentDispositionField f = parse("Content-Disposition: attachment; \n" +
+                "filename*=\"foo\";\"");
+        assertEquals("foo", f.getFilename());
+
+        f = parse("Content-Disposition: attachment;\n" +
+                "filename*=\"UTF-8''%D0%BC%2E%78%6C%73%78\";\n" +
+                "filename=\"bar2.rtf\";");
+        assertEquals("м.xlsx", f.getFilename());
+
+        f = parse("Content-Disposition: attachment; \n" +
+                "filename*=\"foo\";\n" +
+                "filename=\"bar2.rtf\";\n" +
+                "filename=\"bar3.rtf\"");
+        assertEquals("foo", f.getFilename());
+
+        f = parse("Content-Disposition: attachment; \n" +
+                "filename=\"bar2.rtf\";\n" +
+                "filename*=\"foo\";\n" +
+                "filename=\"bar3.rtf\"");
+        assertEquals("foo", f.getFilename());
+    }
+
+    @Test
+    public void testStandardAndContinuationFields() throws MimeException {
+        ContentDispositionField f = parse("Content-Disposition: attachment; \n" +
+                "filename*0=\"foo\";\n" +
+                "filename=\"bar2.rtf\";\n" +
+                "filename*1=\"bar3.rtf\"");
+        assertEquals("foobar3.rtf", f.getFilename());
+
+        f = parse("Content-Disposition: attachment; \n" +
+                "filename=\"bar1.rtf\";\n" +
+                "filename*0=\"foo\";\n" +
+                "filename*1=\"bar3.rtf\"");
+        assertEquals("foobar3.rtf", f.getFilename());
+
+        f = parse("Content-Disposition: attachment; \n" +
+                "filename*0=\"foo\";\n" +
+                "filename*1=\"bar2.rtf\";\n" +
+                "filename=\"bar3.rtf\"");
+        assertEquals("foobar2.rtf", f.getFilename());
+    }
 }


=====================================
dom/src/test/java/org/apache/james/mime4j/field/address/DefaultAddressBuilderTest.java
=====================================
@@ -67,6 +67,10 @@ public class DefaultAddressBuilderTest {
         Assert.assertEquals("Hans M\374ller", mailbox5.getName());
         Assert.assertEquals("hans.mueller at acme.org", mailbox5.getAddress());
 
+        // UTF8 should be allowed in atoms too now
+        Mailbox mailbox6 = parser.parseMailbox(
+                "<dr.müller at dr-müller-lüdenscheid.de>");
+        Assert.assertEquals("dr.müller at dr-müller-lüdenscheid.de", mailbox6.getAddress());
     }
 
     @Test


=====================================
dom/src/test/java/org/apache/james/mime4j/field/address/LenientAddressBuilderTest.java
=====================================
@@ -231,6 +231,11 @@ public class LenientAddressBuilderTest {
                 "\"Hans M\374ller\" <hans.mueller at acme.org>");
         Assert.assertEquals("Hans M\374ller", mailbox5.getName());
         Assert.assertEquals("hans.mueller at acme.org", mailbox5.getAddress());
+
+        // UTF8 should be allowed in atoms too now
+        Mailbox mailbox6 = parser.parseMailbox(
+                "<dr.müller at dr-müller-lüdenscheid.de>");
+        Assert.assertEquals("dr.müller at dr-müller-lüdenscheid.de", mailbox6.getAddress());
     }
 
     @Test


=====================================
examples/pom.xml
=====================================
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>apache-mime4j-project</artifactId>
         <groupId>org.apache.james</groupId>
-        <version>0.8.12</version>
+        <version>0.8.13</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 


=====================================
james-utils/pom.xml
=====================================
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.james</groupId>
         <artifactId>apache-mime4j-project</artifactId>
-        <version>0.8.12</version>
+        <version>0.8.13</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 


=====================================
mbox/pom.xml
=====================================
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.james</groupId>
         <artifactId>apache-mime4j-project</artifactId>
-        <version>0.8.12</version>
+        <version>0.8.13</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 


=====================================
pom.xml
=====================================
@@ -23,13 +23,13 @@
     <parent>
         <groupId>org.apache</groupId>
         <artifactId>apache</artifactId>
-        <version>21</version>
+        <version>35</version>
         <relativePath />
     </parent>
 
     <groupId>org.apache.james</groupId>
     <artifactId>apache-mime4j-project</artifactId>
-    <version>0.8.12</version>
+    <version>0.8.13</version>
     <packaging>pom</packaging>
 
     <name>Apache James :: Mime4j :: Project</name>
@@ -52,7 +52,7 @@
         <connection>scm:git:http://git-wip-us.apache.org/repos/asf/james-mime4j.git</connection>
         <developerConnection>scm:git:ssh://git@github.com/apache/james-mime4j.git</developerConnection>
         <url>https://git-wip-us.apache.org/repos/asf/james-mime4j.git</url>
-      <tag>apache-mime4j-project-0.8.12</tag>
+      <tag>apache-mime4j-project-0.8.13</tag>
   </scm>
     <issueManagement>
         <url>http://issues.apache.org/jira/browse/MIME4J</url>
@@ -69,11 +69,11 @@
         <james-skin.version>1.8</james-skin.version>
 
         <target.jdk>1.8</target.jdk>
-        <commons-logging.version>1.2</commons-logging.version>
-        <log4j.version>2.19.0</log4j.version>
+        <commons-logging.version>1.3.5</commons-logging.version>
+        <log4j.version>2.25.0</log4j.version>
         <junit.version>4.13.2</junit.version>
-        <mockito.version>3.12.4</mockito.version>
-        <commons-io.version>2.17.0</commons-io.version>
+        <mockito.version>4.11.0</mockito.version>
+        <commons-io.version>2.19.0</commons-io.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
 
@@ -128,7 +128,7 @@
             <dependency>
                 <groupId>org.assertj</groupId>
                 <artifactId>assertj-core</artifactId>
-                <version>3.21.0</version>
+                <version>3.27.3</version>
                 <scope>test</scope>
             </dependency>
             <dependency>
@@ -187,7 +187,7 @@
             </plugin>
             <plugin>
                 <artifactId>maven-jar-plugin</artifactId>
-                <version>3.1.2</version>
+                <version>3.4.2</version>
                 <executions>
                     <execution>
                         <id>jar</id>
@@ -203,13 +203,13 @@
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
-                <version>5.1.1</version>
+                <version>5.1.9</version>
                 <extensions>true</extensions>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.8.1</version>
+                <version>3.14.0</version>
                 <configuration>
                     <optimize>true</optimize>
                     <source>${target.jdk}</source>
@@ -241,7 +241,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
-                <version>3.3.0</version>
+                <version>3.11.2</version>
                 <reportSets>
                     <reportSet>
                         <id>aggregate</id>


=====================================
storage/pom.xml
=====================================
@@ -23,7 +23,7 @@
     <parent>
         <artifactId>apache-mime4j-project</artifactId>
         <groupId>org.apache.james</groupId>
-        <version>0.8.12</version>
+        <version>0.8.13</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 



View it on GitLab: https://salsa.debian.org/java-team/apache-mime4j/-/commit/b8b38b366658996aadc04cd9f783bbfa3da3e197

-- 
View it on GitLab: https://salsa.debian.org/java-team/apache-mime4j/-/commit/b8b38b366658996aadc04cd9f783bbfa3da3e197
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20250816/6355da90/attachment.htm>


More information about the pkg-java-commits mailing list