Skip to content
Commits on Source (3)
......@@ -23,7 +23,7 @@
<parent>
<artifactId>apache-mime4j-project</artifactId>
<groupId>org.apache.james</groupId>
<version>0.8.1</version>
<version>0.8.2</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -23,7 +23,7 @@
<parent>
<artifactId>apache-mime4j-project</artifactId>
<groupId>org.apache.james</groupId>
<version>0.8.1</version>
<version>0.8.2</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -23,7 +23,7 @@
<parent>
<artifactId>apache-mime4j-project</artifactId>
<groupId>org.apache.james</groupId>
<version>0.8.1</version>
<version>0.8.2</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -539,12 +539,13 @@ public class EncoderUtil {
if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) {
return prefix + encodeB(bytes) + ENC_WORD_SUFFIX;
} else {
String part1 = text.substring(0, text.length() / 2);
int splitOffset = text.offsetByCodePoints(text.length() / 2, -1);
String part1 = text.substring(0, splitOffset);
byte[] bytes1 = encode(part1, charset);
String word1 = encodeB(prefix, part1, usedCharacters, charset,
bytes1);
String part2 = text.substring(text.length() / 2);
String part2 = text.substring(splitOffset);
byte[] bytes2 = encode(part2, charset);
String word2 = encodeB(prefix, part2, 0, charset, bytes2);
......
......@@ -26,6 +26,12 @@ import org.apache.james.mime4j.MimeException;
*/
public final class MimeConfig {
public static final MimeConfig PERMISSIVE = MimeConfig.custom()
.setMaxContentLen(100 * 1024 * 1024)
.setMaxHeaderCount(-1)
.setMaxHeaderLen(-1)
.setMaxLineLen(-1)
.build();
public static final MimeConfig DEFAULT = new Builder().build();
public static final MimeConfig STRICT = new Builder()
.setStrictParsing(true)
......
......@@ -141,6 +141,20 @@ public class EncoderUtilTest {
Assert.assertTrue(encodedSixtyOne.contains("?= =?US-ASCII?Q?"));
}
@Test
public void testEncodeEncodedWordSplitForUnicode() throws Exception {
StringBuilder sb = new StringBuilder("z");
for (int i = 0; i < 10; i++) {
// Append unicode character 𝕫 10 times.
sb.append("\uD835\uDD6b");
}
String expected = "=?UTF-8?B?evCdlavwnZWr8J2Vq/Cdlas=?= " +
"=?UTF-8?B?8J2Vq/CdlavwnZWr8J2Vq/CdlavwnZWr?=";
Assert.assertEquals(expected, EncoderUtil.encodeEncodedWord(sb.toString(),
Usage.TEXT_TOKEN, 10, null, Encoding.B));
}
@Test
public void testEncodeEncodedWord() throws Exception {
Assert.assertEquals("=?US-ASCII?Q??=", EncoderUtil.encodeEncodedWord("",
......
apache-mime4j (0.8.2-1) unstable; urgency=medium
* New ustream release
-- Emmanuel Bourg <ebourg@apache.org> Tue, 22 Jan 2019 22:49:10 +0100
apache-mime4j (0.8.1-1) unstable; urgency=medium
* New ustream release
......
......@@ -23,7 +23,7 @@
<parent>
<artifactId>apache-mime4j-project</artifactId>
<groupId>org.apache.james</groupId>
<version>0.8.1</version>
<version>0.8.2</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.InputStream;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.message.MultipartBuilder;
/**
* An interface to build instances of {@link Message} and other DOM elements either without
......@@ -39,10 +40,14 @@ public interface MessageBuilder {
Multipart newMultipart(Multipart source);
Multipart newMultipart(MultipartBuilder source);
Message newMessage();
Message newMessage(Message source);
Message newMessage(Message.Builder source);
Header parseHeader(InputStream source) throws MimeException, IOException;
Message parseMessage(InputStream source) throws MimeException, IOException;
......
......@@ -21,6 +21,8 @@ package org.apache.james.mime4j.dom;
import java.util.List;
import org.apache.james.mime4j.stream.NameValuePair;
/**
* A MIME multipart body (as defined in RFC 2045). A multipart body has a ordered list of
* body parts. The multipart body also has a preamble and epilogue. The preamble consists of
......@@ -139,4 +141,5 @@ public interface Multipart extends Body {
*/
void setEpilogue(String epilogue);
List<NameValuePair> getContentTypeParameters();
}
......@@ -42,9 +42,20 @@ public class DateTimeFieldLenientImpl extends AbstractField implements DateTimeF
"EEE, dd MMM yy HH:mm:ss ZZZZ",
"dd MMM yy HH:mm:ss ZZZZ",
"EEE, dd MMM yy HH:mm:ss.SSS 0000",
"EEE, dd MMM yy HH:mm:ss 0000",
"EEE, dd MMM yyyy HH:mm:ss ZZZZ",
"dd MMM yyyy HH:mm:ss ZZZZ",
"EEE, dd MMM yyyy HH:mm:ss.SSS 0000"};
"EEE, dd MMM yyyy HH:mm:ss.SSS 0000",
"EEE, dd MMM yyyy HH:mm:ss 0000",
"EEE, dd MMM yy HH:mm:ss X",
"dd MMM yy HH:mm:ss X",
"EEE, dd MMM yy HH:mm:ss.SSS X",
"EEE, dd MMM yy HH:mm:ss X",
"EEE, dd MMM yyyy HH:mm:ss X",
"dd MMM yyyy HH:mm:ss X",
"EEE, dd MMM yyyy HH:mm:ss.SSS X",
"EEE, dd MMM yyyy HH:mm:ss X",
};
private final List<String> datePatterns;
......
......@@ -19,6 +19,7 @@
package org.apache.james.mime4j.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
......@@ -40,6 +41,9 @@ import org.apache.james.mime4j.dom.field.ContentTypeField;
import org.apache.james.mime4j.dom.field.FieldName;
import org.apache.james.mime4j.dom.field.ParsedField;
import org.apache.james.mime4j.field.Fields;
import org.apache.james.mime4j.message.BodyPartBuilder;
import org.apache.james.mime4j.message.MultipartBuilder;
import org.apache.james.mime4j.message.SingleBodyBuilder;
import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.NameValuePair;
import org.apache.james.mime4j.util.MimeUtil;
......@@ -487,6 +491,10 @@ public abstract class AbstractEntityBuilder {
return this;
}
public AbstractEntityBuilder setBody(SingleBodyBuilder body) throws IOException {
return this.setBody(body.build());
}
/**
* Sets body of this message. Also sets the content type based on properties of
* the given {@link org.apache.james.mime4j.dom.Body}.
......@@ -546,6 +554,10 @@ public abstract class AbstractEntityBuilder {
return this;
}
public AbstractEntityBuilder setBody(Message.Builder message) {
return this.setBody(message.build());
}
/**
* Sets body of this message. Also sets the content type based on properties of
* the given {@link org.apache.james.mime4j.dom.Body}.
......@@ -556,14 +568,21 @@ public abstract class AbstractEntityBuilder {
public AbstractEntityBuilder setBody(Multipart multipart) {
this.body = multipart;
if (multipart != null) {
List<NameValuePair> parameters =
new ArrayList<NameValuePair>(multipart.getContentTypeParameters());
parameters.add(new NameValuePair("boundary", MimeUtil.createUniqueBoundary()));
setField(Fields.contentType("multipart/" + multipart.getSubType(),
new NameValuePair("boundary", MimeUtil.createUniqueBoundary())));
parameters.toArray(new NameValuePair[0])));
} else {
removeFields(FieldName.CONTENT_TYPE);
}
return this;
}
public AbstractEntityBuilder setBody(MultipartBuilder multipart) {
return this.setBody(multipart.build());
}
/**
* Returns message body.
*
......
......@@ -25,6 +25,7 @@ import java.util.List;
import org.apache.james.mime4j.dom.Entity;
import org.apache.james.mime4j.dom.Multipart;
import org.apache.james.mime4j.stream.NameValuePair;
/**
* Abstract MIME multipart body.
......@@ -35,12 +36,14 @@ public abstract class AbstractMultipart implements Multipart {
private Entity parent = null;
private String subType;
private final List<NameValuePair> contentTypeParameters;
/**
* Creates a new empty <code>Multipart</code> instance.
*/
public AbstractMultipart(String subType) {
public AbstractMultipart(String subType, List<NameValuePair> contentTypeParameters) {
this.subType = subType;
this.contentTypeParameters = contentTypeParameters;
}
/**
......@@ -222,6 +225,11 @@ public abstract class AbstractMultipart implements Multipart {
*/
public abstract void setEpilogue(String epilogue);
@Override
public List<NameValuePair> getContentTypeParameters() {
return contentTypeParameters;
}
/**
* Disposes of the BodyParts of this Multipart. Note that the dispose call
* does not get forwarded to the parent entity of this Multipart.
......
......@@ -21,6 +21,7 @@ package org.apache.james.mime4j.message;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.MimeIOException;
......@@ -43,6 +44,7 @@ import org.apache.james.mime4j.parser.MimeStreamParser;
import org.apache.james.mime4j.stream.BodyDescriptorBuilder;
import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.MimeConfig;
import org.apache.james.mime4j.stream.NameValuePair;
/**
* Default implementation of {@link MessageBuilder}.
......@@ -159,7 +161,7 @@ public class DefaultMessageBuilder implements MessageBuilder {
* {@link SingleBody}.
*/
public Multipart copy(Multipart other) {
MultipartImpl copy = new MultipartImpl(other.getSubType());
MultipartImpl copy = new MultipartImpl(other.getSubType(), other.getContentTypeParameters());
for (Entity otherBodyPart : other.getBodyParts()) {
copy.addBodyPart(copy(otherBodyPart));
}
......@@ -248,6 +250,10 @@ public class DefaultMessageBuilder implements MessageBuilder {
return new MultipartImpl(subType);
}
public Multipart newMultipart(final String subType, NameValuePair... contentTypeParameters) {
return new MultipartImpl(subType, Arrays.asList(contentTypeParameters));
}
public Multipart newMultipart(final Multipart source) {
return copy(source);
}
......@@ -324,4 +330,11 @@ public class DefaultMessageBuilder implements MessageBuilder {
return mif.messageImpl();
}
public Multipart newMultipart(MultipartBuilder source) {
return newMultipart(source.build());
}
public Message newMessage(Message.Builder source) {
return newMessage(source.build());
}
}
......@@ -19,6 +19,7 @@
package org.apache.james.mime4j.message;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
......@@ -48,6 +49,13 @@ public class DefaultMessageWriter implements MessageWriter {
private static final byte[] CRLF = { '\r', '\n' };
private static final byte[] DASHES = { '-', '-' };
public static byte[] asBytes(Message message) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DefaultMessageWriter writer = new DefaultMessageWriter();
writer.writeMessage(message, buffer);
return buffer.toByteArray();
}
/**
* Protected constructor prevents direct instantiation.
*/
......
......@@ -49,6 +49,7 @@ public class MultipartBuilder {
private String epilogue;
private BodyFactory bodyFactory;
private List<NameValuePair> parameters;
public static MultipartBuilder create(String subType) {
return new MultipartBuilder().setSubType(subType);
......@@ -64,6 +65,7 @@ public class MultipartBuilder {
private MultipartBuilder() {
this.bodyParts = new LinkedList<Entity>();
this.parameters = new LinkedList<NameValuePair>();
}
public MultipartBuilder use(final BodyFactory bodyFactory) {
......@@ -127,6 +129,10 @@ public class MultipartBuilder {
return this;
}
public MultipartBuilder addBodyPart(BodyPartBuilder bodyPart) {
return this.addBodyPart(bodyPart.build());
}
/**
* Inserts a body part at the specified position in the list of body parts.
*
......@@ -217,6 +223,11 @@ public class MultipartBuilder {
return this;
}
public MultipartBuilder addContentTypeParameter(NameValuePair parameter) {
this.parameters.add(parameter);
return this;
}
public MultipartBuilder addTextPart(String text, Charset charset) throws IOException {
Charset cs = charset != null ? charset : Charsets.ISO_8859_1;
TextBody body = bodyFactory != null ? bodyFactory.textBody(
......@@ -277,7 +288,7 @@ public class MultipartBuilder {
}
public Multipart build() {
MultipartImpl multipart = new MultipartImpl(subType);
MultipartImpl multipart = new MultipartImpl(subType, parameters);
for (Entity part : bodyParts) {
multipart.addBodyPart(part);
}
......
......@@ -19,7 +19,11 @@
package org.apache.james.mime4j.message;
import java.util.Collections;
import java.util.List;
import org.apache.james.mime4j.dom.Multipart;
import org.apache.james.mime4j.stream.NameValuePair;
import org.apache.james.mime4j.util.ByteSequence;
import org.apache.james.mime4j.util.ContentUtil;
......@@ -35,11 +39,15 @@ public class MultipartImpl extends AbstractMultipart {
private transient String epilogueStrCache;
private transient boolean epilogueComputed = false;
public MultipartImpl(String subType) {
this(subType, Collections.<NameValuePair>emptyList());
}
/**
* Creates a new empty <code>Multipart</code> instance.
*/
public MultipartImpl(String subType) {
super(subType);
public MultipartImpl(String subType, List<NameValuePair> parameters) {
super(subType, parameters);
preamble = null;
preambleStrCache = null;
preambleComputed = true;
......
/****************************************************************
* 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.dom;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import org.apache.james.mime4j.message.DefaultMessageBuilder;
import org.apache.james.mime4j.stream.MimeConfig;
import org.junit.Test;
public class LargeMessageParsingTest {
@Test
public void parsingALargeMessageWithPermissiveConfigShouldSucceed() throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(100 * 1024 * 1024);
// 32 * 1.000.000 = ~ 30,5 Mo of headers
for (int i = 0; i < 1000000; i++) {
outputStream.write(String.format("header: static important value\r\n", i, i).getBytes());
}
outputStream.write("\r\n".getBytes());
// 38 * 1.600.000 = ~ 58 Mo of body
for (int i = 0; i < 1600000; i++) {
outputStream.write(String.format("abcdeghijklmnopqrstuvwxyz0123456789\r\n", i, i).getBytes());
}
DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
messageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE);
messageBuilder.parseMessage(new ByteArrayInputStream(outputStream.toByteArray()));
}
@Test
public void parsingAMessageWithLongLinesWithPermissiveConfigShouldSucceed() throws Exception {
ByteArrayOutputStream longLineOutputStream = new ByteArrayOutputStream( 1024 * 1024);
ByteArrayOutputStream longHeaderOutputStream = new ByteArrayOutputStream( 1024 * 1024);
longHeaderOutputStream.write("header: ".getBytes());
// Each header is ~ 500 Ko
for (int i = 0; i < 50 * 1024; i++) {
longHeaderOutputStream.write("0123456789".getBytes());
}
longHeaderOutputStream.write("\r\n".getBytes());
// Each line is ~ 1Mo
for (int i = 0; i < 100 * 1024; i++) {
longLineOutputStream.write("0123456789".getBytes());
}
longLineOutputStream.write("\r\n".getBytes());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(100 * 1024 * 1024);
// 60 * 0.5 = ~ 30 Mo of headers
for (int i = 0; i < 60; i++) {
outputStream.write(longHeaderOutputStream.toByteArray());
}
outputStream.write("\r\n".getBytes());
// 60 * 1 = ~ 60 Mo of body
for (int i = 0; i < 60; i++) {
outputStream.write(longLineOutputStream.toByteArray());
}
DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
messageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE);
messageBuilder.parseMessage(new ByteArrayInputStream(outputStream.toByteArray()));
}
}
......@@ -81,4 +81,10 @@ public class LenientDateTimeFieldTest {
Assert.assertEquals(1175052759000L, f.getDate().getTime());
}
@Test
public void testDateWhenGeneralTimezone() throws Exception {
DateTimeField f = parse("Date: Fri, 05 Jan 2018 16:18:28 Z");
Assert.assertEquals(1515169108000L, f.getDate().getTime());
}
}
......@@ -20,6 +20,8 @@
package org.apache.james.mime4j.message;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
......@@ -263,7 +265,8 @@ public class AbstractEntityBuilderTest {
public void testSetMultipartBody() throws Exception {
AbstractEntityBuilder builder = new AbstractEntityBuilderImpl();
Multipart multipart = new MultipartImpl("stuff");
Multipart multipart = new MultipartImpl("stuff",
Collections.singletonList(new NameValuePair("report-type", "disposition-notification")));
builder.setBody(multipart);
Assert.assertSame(multipart, builder.getBody());
......@@ -273,6 +276,7 @@ public class AbstractEntityBuilderTest {
final ContentTypeField field = (ContentTypeField) builder.getField("Content-Type");
Assert.assertNotNull(field.getBoundary());
Assert.assertEquals("disposition-notification", field.getParameter("report-type"));
builder.setBody((Message) null);
Assert.assertFalse(builder.containsField("Content-Type"));
......