Return-Path: Delivered-To: apmail-james-mime4j-dev-archive@minotaur.apache.org Received: (qmail 87863 invoked from network); 27 Feb 2009 16:45:32 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 27 Feb 2009 16:45:32 -0000 Received: (qmail 99480 invoked by uid 500); 27 Feb 2009 16:45:32 -0000 Delivered-To: apmail-james-mime4j-dev-archive@james.apache.org Received: (qmail 99466 invoked by uid 500); 27 Feb 2009 16:45:32 -0000 Mailing-List: contact mime4j-dev-help@james.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: mime4j-dev@james.apache.org Delivered-To: mailing list mime4j-dev@james.apache.org Received: (qmail 99455 invoked by uid 99); 27 Feb 2009 16:45:32 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 27 Feb 2009 08:45:32 -0800 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 27 Feb 2009 16:45:26 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 259AA23889B2; Fri, 27 Feb 2009 16:45:05 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r748583 [1/2] - in /james/mime4j/trunk/src: main/java/org/apache/james/mime4j/field/ main/java/org/apache/james/mime4j/message/ main/java/org/apache/james/mime4j/parser/ main/java/org/apache/james/mime4j/util/ test/java/org/apache/james/mim... Date: Fri, 27 Feb 2009 16:45:03 -0000 To: mime4j-dev@james.apache.org From: mwiederkehr@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090227164505.259AA23889B2@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: mwiederkehr Date: Fri Feb 27 16:45:02 2009 New Revision: 748583 URL: http://svn.apache.org/viewvc?rev=748583&view=rev Log: Fix for MIME4J-118: MIME stream parser handles non-ASCII fields incorrectly Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Multipart.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/field/FieldsTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/EntityTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/ExampleMessagesRoundtripTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/MessageParserTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/MessageTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/MessageWriteToTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/MultipartFormTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/parser/MimeStreamParserTest.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/parser/TestHandler.java Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AbstractField.java Fri Feb 27 16:45:02 2009 @@ -24,6 +24,8 @@ import org.apache.james.mime4j.MimeException; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; import org.apache.james.mime4j.util.MimeUtil; /** @@ -31,23 +33,37 @@ */ public abstract class AbstractField implements Field { - private static final String FIELD_NAME_PATTERN = - "^([\\x21-\\x39\\x3b-\\x7e]+):"; - private static final Pattern fieldNamePattern = - Pattern.compile(FIELD_NAME_PATTERN); - + private static final Pattern FIELD_NAME_PATTERN = Pattern + .compile("^([\\x21-\\x39\\x3b-\\x7e]+):"); + private static final DefaultFieldParser parser = new DefaultFieldParser(); private final String name; private final String body; - private final String raw; + private final ByteSequence raw; - protected AbstractField(final String name, final String body, final String raw) { + protected AbstractField(final String name, final String body, final ByteSequence raw) { this.name = name; this.body = body; this.raw = raw; } - + + /** + * Parses the given byte sequence and returns an instance of the + * Field class. The type of the class returned depends on the + * field name; see {@link #parse(String)} for a table of field names and + * their corresponding classes. + * + * @param raw the bytes to parse. + * @return a Field instance. + * @throws MimeException if the raw string cannot be split into field name and body. + * @see #isValidField() + */ + public static Field parse(final ByteSequence raw) throws MimeException { + String rawStr = ContentUtil.decode(raw); + return parse(raw, rawStr); + } + /** * Parses the given string and returns an instance of the * Field class. The type of the class returned depends on @@ -65,69 +81,19 @@ * {@link UnstructuredField}Subject and others * * - * @param raw the string to parse. + * @param rawStr the string to parse. * @return a Field instance. * @throws MimeException if the raw string cannot be split into field name and body. * @see #isValidField() */ - public static Field parse(final String raw) throws MimeException { - - /* - * Unfold the field. - */ - final String unfolded = MimeUtil.unfold(raw); - - /* - * Split into name and value. - */ - final Matcher fieldMatcher = fieldNamePattern.matcher(unfolded); - if (!fieldMatcher.find()) { - throw new MimeException("Invalid field in string"); - } - final String name = fieldMatcher.group(1); - - String body = unfolded.substring(fieldMatcher.end()); - if (body.length() > 0 && body.charAt(0) == ' ') { - body = body.substring(1); - } - - return parser.parse(name, body, raw); - } - - /** - * Parses the given field name and field body strings and returns an - * instance of the Field class. The type of the class - * returned depends on the field name (see {@link #parse(String)}). - *

- * This method is convenient for creating or manipulating messages because - * contrary to {@link #parse(String)} it does not throw a - * {@link MimeException}. - *

- * Note that this method does not fold the header field; the specified field - * body should already have been folded into multiple lines prior to calling - * this method if folding is desired. - * - * @param name - * the field name. - * @param body - * the field body (a.k.a value). - * @return a Field instance. - */ - public static Field parse(String name, String body) { - if (body.length() > 0 && body.charAt(0) == ' ') { - body = body.substring(1); - } - - String raw = name + ": " + body; - - // Unfold body - body = MimeUtil.unfold(body); - - return parser.parse(name, body, raw); + public static Field parse(final String rawStr) throws MimeException { + ByteSequence raw = ContentUtil.encode(rawStr); + return parse(raw, rawStr); } /** * Gets the default parser used to parse fields. + * * @return the default field parser */ public static DefaultFieldParser getParser() { @@ -149,7 +115,7 @@ * * @return the original raw field string. */ - public String getRaw() { + public ByteSequence getRaw() { return raw; } @@ -227,11 +193,33 @@ return FieldName.TO.equalsIgnoreCase(name); } - /** - * @see #getRaw() - */ @Override public String toString() { - return raw; + return name + ": " + body; + } + + private static Field parse(final ByteSequence raw, final String rawStr) + throws MimeException { + /* + * Unfold the field. + */ + final String unfolded = MimeUtil.unfold(rawStr); + + /* + * Split into name and value. + */ + final Matcher fieldMatcher = FIELD_NAME_PATTERN.matcher(unfolded); + if (!fieldMatcher.find()) { + throw new MimeException("Invalid field in string"); + } + final String name = fieldMatcher.group(1); + + String body = unfolded.substring(fieldMatcher.end()); + if (body.length() > 0 && body.charAt(0) == ' ') { + body = body.substring(1); + } + + return parser.parse(name, body, raw); } + } Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/AddressListField.java Fri Feb 27 16:45:02 2009 @@ -24,6 +24,7 @@ import org.apache.james.mime4j.field.address.AddressList; import org.apache.james.mime4j.field.address.parser.ParseException; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; /** * Address list field such as To or Reply-To. @@ -36,7 +37,7 @@ private AddressList addressList; private ParseException parseException; - AddressListField(String name, String body, String raw) { + AddressListField(String name, String body, ByteSequence raw) { super(name, body, raw); } @@ -72,7 +73,7 @@ static final FieldParser PARSER = new FieldParser() { public Field parse(final String name, final String body, - final String raw) { + final ByteSequence raw) { return new AddressListField(name, body, raw); } }; Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentDispositionField.java Fri Feb 27 16:45:02 2009 @@ -33,6 +33,7 @@ import org.apache.james.mime4j.field.contentdisposition.parser.TokenMgrError; import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; /** * Represents a Content-Disposition field. @@ -76,7 +77,7 @@ private boolean readDateParsed; private Date readDate; - ContentDispositionField(String name, String body, String raw) { + ContentDispositionField(String name, String body, ByteSequence raw) { super(name, body, raw); } @@ -319,7 +320,7 @@ static final FieldParser PARSER = new FieldParser() { public Field parse(final String name, final String body, - final String raw) { + final ByteSequence raw) { return new ContentDispositionField(name, body, raw); } }; Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTransferEncodingField.java Fri Feb 27 16:45:02 2009 @@ -20,6 +20,7 @@ package org.apache.james.mime4j.field; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; import org.apache.james.mime4j.util.MimeUtil; /** @@ -28,7 +29,7 @@ public class ContentTransferEncodingField extends AbstractField { private String encoding; - ContentTransferEncodingField(String name, String body, String raw) { + ContentTransferEncodingField(String name, String body, ByteSequence raw) { super(name, body, raw); encoding = body.trim().toLowerCase(); } @@ -58,7 +59,7 @@ static final FieldParser PARSER = new FieldParser() { public Field parse(final String name, final String body, - final String raw) { + final ByteSequence raw) { return new ContentTransferEncodingField(name, body, raw); } }; Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/ContentTypeField.java Fri Feb 27 16:45:02 2009 @@ -31,6 +31,7 @@ import org.apache.james.mime4j.field.contenttype.parser.ParseException; import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; /** * Represents a Content-Type field. @@ -62,7 +63,7 @@ private Map parameters = new HashMap(); private ParseException parseException; - ContentTypeField(String name, String body, String raw) { + ContentTypeField(String name, String body, ByteSequence raw) { super(name, body, raw); } @@ -251,7 +252,7 @@ static final FieldParser PARSER = new FieldParser() { public Field parse(final String name, final String body, - final String raw) { + final ByteSequence raw) { return new ContentTypeField(name, body, raw); } }; Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DateTimeField.java Fri Feb 27 16:45:02 2009 @@ -28,6 +28,7 @@ import org.apache.james.mime4j.field.datetime.parser.ParseException; import org.apache.james.mime4j.field.datetime.parser.TokenMgrError; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; /** * Date-time field such as Date or Resent-Date. @@ -40,7 +41,7 @@ private Date date; private ParseException parseException; - DateTimeField(String name, String body, String raw) { + DateTimeField(String name, String body, ByteSequence raw) { super(name, body, raw); } @@ -82,7 +83,7 @@ static final FieldParser PARSER = new FieldParser() { public Field parse(final String name, final String body, - final String raw) { + final ByteSequence raw) { return new DateTimeField(name, body, raw); } }; Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/DelegatingFieldParser.java Fri Feb 27 16:45:02 2009 @@ -23,6 +23,7 @@ import java.util.Map; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; public class DelegatingFieldParser implements FieldParser { @@ -46,7 +47,7 @@ return field; } - public Field parse(final String name, final String body, final String raw) { + public Field parse(final String name, final String body, final ByteSequence raw) { final FieldParser parser = getParser(name); return parser.parse(name, body, raw); } Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/FieldParser.java Fri Feb 27 16:45:02 2009 @@ -20,9 +20,10 @@ package org.apache.james.mime4j.field; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; public interface FieldParser { - Field parse(final String name, final String body, final String raw); + Field parse(final String name, final String body, final ByteSequence raw); } Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/Fields.java Fri Feb 27 16:45:02 2009 @@ -31,6 +31,8 @@ import org.apache.james.mime4j.field.address.Address; import org.apache.james.mime4j.field.address.Mailbox; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; import org.apache.james.mime4j.util.MimeUtil; /** @@ -602,7 +604,8 @@ private static F parse(FieldParser parser, String fieldName, String fieldBody) { - String raw = MimeUtil.fold(fieldName + ": " + fieldBody, 0); + String rawStr = MimeUtil.fold(fieldName + ": " + fieldBody, 0); + ByteSequence raw = ContentUtil.encode(rawStr); Field field = parser.parse(fieldName, fieldBody, raw); Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxField.java Fri Feb 27 16:45:02 2009 @@ -26,6 +26,7 @@ import org.apache.james.mime4j.field.address.MailboxList; import org.apache.james.mime4j.field.address.parser.ParseException; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; /** * Mailbox field such as Sender or Resent-Sender. @@ -38,7 +39,7 @@ private Mailbox mailbox; private ParseException parseException; - MailboxField(final String name, final String body, final String raw) { + MailboxField(final String name, final String body, final ByteSequence raw) { super(name, body, raw); } @@ -77,7 +78,7 @@ static final FieldParser PARSER = new FieldParser() { public Field parse(final String name, final String body, - final String raw) { + final ByteSequence raw) { return new MailboxField(name, body, raw); } }; Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/MailboxListField.java Fri Feb 27 16:45:02 2009 @@ -25,6 +25,7 @@ import org.apache.james.mime4j.field.address.MailboxList; import org.apache.james.mime4j.field.address.parser.ParseException; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; /** * Mailbox-list field such as From or Resent-From. @@ -37,7 +38,7 @@ private MailboxList mailboxList; private ParseException parseException; - MailboxListField(final String name, final String body, final String raw) { + MailboxListField(final String name, final String body, final ByteSequence raw) { super(name, body, raw); } @@ -73,7 +74,7 @@ static final FieldParser PARSER = new FieldParser() { public Field parse(final String name, final String body, - final String raw) { + final ByteSequence raw) { return new MailboxListField(name, body, raw); } }; Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/field/UnstructuredField.java Fri Feb 27 16:45:02 2009 @@ -21,6 +21,7 @@ import org.apache.james.mime4j.codec.DecoderUtil; import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; /** * Simple unstructured field such as Subject. @@ -30,7 +31,7 @@ private String value; - UnstructuredField(String name, String body, String raw) { + UnstructuredField(String name, String body, ByteSequence raw) { super(name, body, raw); } @@ -51,7 +52,7 @@ static final FieldParser PARSER = new FieldParser() { public Field parse(final String name, final String body, - final String raw) { + final ByteSequence raw) { return new UnstructuredField(name, body, raw); } }; Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Message.java Fri Feb 27 16:45:02 2009 @@ -146,18 +146,16 @@ /** * Write the content to the given output stream using the - * {@link MessageWriter#STRICT_ERROR STRICT_ERROR} message writer. + * {@link MessageWriter#DEFAULT default} message writer. * * @param out * the output stream to write to. * @throws IOException * in case of an I/O error - * @throws MimeIOException - * in case of a MIME protocol violation * @see MessageWriter */ - public void writeTo(OutputStream out) throws IOException, MimeIOException { - MessageWriter.STRICT_ERROR.writeEntity(this, out); + public void writeTo(OutputStream out) throws IOException { + MessageWriter.DEFAULT.writeEntity(this, out); } /** Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageBuilder.java Fri Feb 27 16:45:02 2009 @@ -32,6 +32,8 @@ import org.apache.james.mime4j.parser.Field; import org.apache.james.mime4j.parser.MimeStreamParser; import org.apache.james.mime4j.storage.StorageProvider; +import org.apache.james.mime4j.util.ByteArrayBuffer; +import org.apache.james.mime4j.util.ByteSequence; import org.apache.james.mime4j.util.MimeUtil; /** @@ -183,12 +185,8 @@ */ public void epilogue(InputStream is) throws MimeException, IOException { expect(Multipart.class); - StringBuilder sb = new StringBuilder(128); - int b; - while ((b = is.read()) != -1) { - sb.append((char) b); - } - ((Multipart) stack.peek()).setEpilogue(sb.toString()); + ByteSequence bytes = loadStream(is); + ((Multipart) stack.peek()).setEpilogueRaw(bytes); } /** @@ -196,12 +194,8 @@ */ public void preamble(InputStream is) throws MimeException, IOException { expect(Multipart.class); - StringBuilder sb = new StringBuilder(128); - int b; - while ((b = is.read()) != -1) { - sb.append((char) b); - } - ((Multipart) stack.peek()).setPreamble(sb.toString()); + ByteSequence bytes = loadStream(is); + ((Multipart) stack.peek()).setPreambleRaw(bytes); } /** @@ -212,4 +206,15 @@ throw new UnsupportedOperationException("Not supported"); } + private static ByteSequence loadStream(InputStream in) throws IOException { + ByteArrayBuffer bab = new ByteArrayBuffer(64); + + int b; + while ((b = in.read()) != -1) { + bab.append(b); + } + + return bab; + } + } \ No newline at end of file Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/MessageWriter.java Fri Feb 27 16:45:02 2009 @@ -19,77 +19,36 @@ package org.apache.james.mime4j.message; -import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CodingErrorAction; -import org.apache.james.mime4j.MimeIOException; import org.apache.james.mime4j.codec.CodecUtil; import org.apache.james.mime4j.field.ContentTypeField; import org.apache.james.mime4j.field.FieldName; import org.apache.james.mime4j.parser.Field; -import org.apache.james.mime4j.util.CharsetUtil; +import org.apache.james.mime4j.util.ByteArrayBuffer; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; import org.apache.james.mime4j.util.MimeUtil; /** * Writes a message (or a part of a message) to an output stream. *

- * This class cannot be instantiated; instead the three static instances - * {@link #STRICT_ERROR}, {@link #STRICT_IGNORE} or {@link #LENIENT} implement - * different strategies for encoding header fields and preamble and epilogue of - * a multipart. + * This class cannot be instantiated; instead the static instance + * {@link #DEFAULT} implements the default strategy for writing a message. *

- * This class can also be subclassed to implement custom strategies for writing + * This class may be subclassed to implement custom strategies for writing * messages. */ public class MessageWriter { - private static final Charset LENIENT_FALLBACK_CHARSET = CharsetUtil.ISO_8859_1; - private static final String CRLF = CharsetUtil.CRLF; - private static final int WRITER_BUFFER_SIZE = 4096; + private static final byte[] CRLF = { '\r', '\n' }; + private static final byte[] DASHES = { '-', '-' }; /** - * A message writer that uses US-ASCII for encoding and throws - * {@link MimeIOException} if a non ASCII character is encountered. + * The default message writer. */ - public static final MessageWriter STRICT_ERROR = new MessageWriter(); - - /** - * A message writer that uses US-ASCII for encoding but ignores non ASCII - * characters. - */ - public static final MessageWriter STRICT_IGNORE = new MessageWriter() { - @Override - protected CharsetEncoder getCharsetEncoder(ContentTypeField contentType) { - return ignoreEncoder(CharsetUtil.DEFAULT_CHARSET); - } - }; - - /** - * A message writer that uses the charset of the Content-Type header for - * encoding. - */ - public static final MessageWriter LENIENT = new MessageWriter() { - @Override - protected CharsetEncoder getCharsetEncoder(ContentTypeField contentType) { - if (contentType == null) { - return ignoreEncoder(LENIENT_FALLBACK_CHARSET); - } else { - String charset = contentType.getCharset(); - if (charset != null) { - return ignoreEncoder(CharsetUtil.getCharset(charset)); - } else { - return ignoreEncoder(LENIENT_FALLBACK_CHARSET); - } - } - } - }; + public static final MessageWriter DEFAULT = new MessageWriter(); /** * Protected constructor prevents direct instantiation. @@ -107,11 +66,8 @@ * the OutputStream to write to. * @throws IOException * if an I/O error occurs. - * @throws MimeIOException - * in case of a MIME protocol violation */ - public void writeBody(Body body, OutputStream out) throws IOException, - MimeIOException { + public void writeBody(Body body, OutputStream out) throws IOException { if (body instanceof Message) { writeEntity((Message) body, out); } else if (body instanceof Multipart) { @@ -120,8 +76,6 @@ ((SingleBody) body).writeTo(out); } else throw new IllegalArgumentException("Unsupported body class"); - - out.flush(); } /** @@ -134,17 +88,13 @@ * the OutputStream to write to. * @throws IOException * if an I/O error occurs. - * @throws MimeIOException - * in case of a MIME protocol violation */ - public void writeEntity(Entity entity, OutputStream out) - throws IOException, MimeIOException { + public void writeEntity(Entity entity, OutputStream out) throws IOException { final Header header = entity.getHeader(); if (header == null) throw new IllegalArgumentException("Missing header"); writeHeader(header, out); - out.flush(); final Body body = entity.getBody(); if (body == null) @@ -171,42 +121,31 @@ * the OutputStream to write to. * @throws IOException * if an I/O error occurs. - * @throws MimeIOException - * in case of a MIME protocol violation */ public void writeMultipart(Multipart multipart, OutputStream out) - throws IOException, MimeIOException { + throws IOException { ContentTypeField contentType = getContentType(multipart); - String boundary = getBoundary(contentType); + ByteSequence boundary = getBoundary(contentType); - Writer writer = getWriter(contentType, out); + writeBytes(multipart.getPreambleRaw(), out); + out.write(CRLF); - try { - writer.write(multipart.getPreamble()); - writer.write(CRLF); - - for (BodyPart bodyPart : multipart.getBodyParts()) { - writer.write("--"); - writer.write(boundary); - writer.write(CRLF); - writer.flush(); - - writeEntity(bodyPart, out); - writer.write(CRLF); - } - - writer.write("--"); - writer.write(boundary); - writer.write("--"); - writer.write(CRLF); - - writer.write(multipart.getEpilogue()); - - writer.flush(); - } catch (CharacterCodingException e) { - throw new MimeIOException("Multipart violates RFC 822"); + for (BodyPart bodyPart : multipart.getBodyParts()) { + out.write(DASHES); + writeBytes(boundary, out); + out.write(CRLF); + + writeEntity(bodyPart, out); + out.write(CRLF); } + + out.write(DASHES); + writeBytes(boundary, out); + out.write(DASHES); + out.write(CRLF); + + writeBytes(multipart.getEpilogueRaw(), out); } /** @@ -219,37 +158,17 @@ * the OutputStream to write to. * @throws IOException * if an I/O error occurs. - * @throws MimeIOException - * in case of a MIME protocol violation */ - public void writeHeader(Header header, OutputStream out) - throws IOException, MimeIOException { - Writer writer = getWriter((ContentTypeField) header - .getField(FieldName.CONTENT_TYPE), out); - - try { - for (Field field : header) { - writer.write(field.getRaw()); - writer.write(CRLF); - } - - writer.write(CRLF); - writer.flush(); - } catch (CharacterCodingException e) { - throw new MimeIOException("Header violates RFC 822"); + public void writeHeader(Header header, OutputStream out) throws IOException { + for (Field field : header) { + writeBytes(field.getRaw(), out); + out.write(CRLF); } - } - CharsetEncoder getCharsetEncoder(ContentTypeField contentType) { - return CharsetUtil.DEFAULT_CHARSET.newEncoder(); + out.write(CRLF); } - CharsetEncoder ignoreEncoder(Charset charset) { - return charset.newEncoder().onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - } - - private OutputStream encodeStream(OutputStream out, String encoding, + protected OutputStream encodeStream(OutputStream out, String encoding, boolean binaryBody) throws IOException { if (MimeUtil.isBase64Encoding(encoding)) { return CodecUtil.wrapBase64(out); @@ -280,20 +199,23 @@ return contentType; } - private String getBoundary(ContentTypeField contentType) { + private ByteSequence getBoundary(ContentTypeField contentType) { String boundary = contentType.getBoundary(); if (boundary == null) throw new IllegalArgumentException( "Multipart boundary not specified"); - return boundary; + return ContentUtil.encode(boundary); } - private Writer getWriter(ContentTypeField contentType, OutputStream out) { - CharsetEncoder encoder = getCharsetEncoder(contentType); - - return new BufferedWriter(new OutputStreamWriter(out, encoder), - WRITER_BUFFER_SIZE); + private void writeBytes(ByteSequence byteSequence, OutputStream out) + throws IOException { + if (byteSequence instanceof ByteArrayBuffer) { + ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence; + out.write(bab.buffer(), 0, bab.length()); + } else { + out.write(byteSequence.toByteArray()); + } } } 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=748583&r1=748582&r2=748583&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 Fri Feb 27 16:45:02 2009 @@ -23,24 +23,37 @@ import java.util.LinkedList; import java.util.List; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; + /** - * Represents a MIME multipart body (see RFC 2045).A multipart body has a + * Represents a MIME multipart body (see 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 whatever characters appear before the - * first body part while the epilogue consists of whatever characters come - * after the last body part. + * epilogue. The preamble consists of whatever characters appear before the + * first body part while the epilogue consists of whatever characters come after + * the last body part. */ public class Multipart implements Body { - private String preamble = ""; - private String epilogue = ""; + private List bodyParts = new LinkedList(); private Entity parent = null; + + private ByteSequence preamble; + private transient String preambleStrCache; + private ByteSequence epilogue; + private transient String epilogueStrCache; + private String subType; /** * Creates a new empty Multipart instance. */ public Multipart(String subType) { + preamble = ByteSequence.EMPTY; + preambleStrCache = ""; + epilogue = ByteSequence.EMPTY; + epilogueStrCache = ""; + this.subType = subType; } @@ -64,45 +77,48 @@ */ public Multipart(Multipart other) { preamble = other.preamble; + preambleStrCache = other.preambleStrCache; epilogue = other.epilogue; - + epilogueStrCache = other.epilogueStrCache; + for (BodyPart otherBodyPart : other.bodyParts) { BodyPart bodyPartCopy = new BodyPart(otherBodyPart); addBodyPart(bodyPartCopy); } - + subType = other.subType; } /** - * Gets the multipart sub-type. E.g. alternative (the default) - * or parallel. See RFC 2045 for common sub-types and their - * meaning. + * Gets the multipart sub-type. E.g. alternative (the + * default) or parallel. See RFC 2045 for common sub-types + * and their meaning. * * @return the multipart sub-type. */ public String getSubType() { return subType; } - + /** - * Sets the multipart sub-type. E.g. alternative - * or parallel. See RFC 2045 for common sub-types and their + * Sets the multipart sub-type. E.g. alternative or + * parallel. See RFC 2045 for common sub-types and their * meaning. * - * @param subType the sub-type. + * @param subType + * the sub-type. */ public void setSubType(String subType) { this.subType = subType; } - + /** * @see org.apache.james.mime4j.message.Body#getParent() */ public Entity getParent() { return parent; } - + /** * @see org.apache.james.mime4j.message.Body#setParent(org.apache.james.mime4j.message.Entity) */ @@ -114,24 +130,6 @@ } /** - * Gets the epilogue. - * - * @return the epilogue. - */ - public String getEpilogue() { - return epilogue; - } - - /** - * Sets the epilogue. - * - * @param epilogue the epilogue. - */ - public void setEpilogue(String epilogue) { - this.epilogue = epilogue; - } - - /** * Returns the number of body parts. * * @return number of BodyPart objects. @@ -139,7 +137,7 @@ public int getCount() { return bodyParts.size(); } - + /** * Gets the list of body parts. The list is immutable. * @@ -148,11 +146,12 @@ public List getBodyParts() { return Collections.unmodifiableList(bodyParts); } - + /** * Sets the list of body parts. * - * @param bodyParts the new list of BodyPart objects. + * @param bodyParts + * the new list of BodyPart objects. */ public void setBodyParts(List bodyParts) { this.bodyParts = bodyParts; @@ -160,20 +159,21 @@ bodyPart.setParent(parent); } } - + /** * Adds a body part to the end of the list of body parts. * - * @param bodyPart the body part. + * @param bodyPart + * the body part. */ public void addBodyPart(BodyPart bodyPart) { if (bodyPart == null) throw new IllegalArgumentException(); - + bodyParts.add(bodyPart); bodyPart.setParent(parent); } - + /** * Inserts a body part at the specified position in the list of body parts. * @@ -188,11 +188,11 @@ public void addBodyPart(BodyPart bodyPart, int index) { if (bodyPart == null) throw new IllegalArgumentException(); - + bodyParts.add(index, bodyPart); bodyPart.setParent(parent); } - + /** * Removes the body part at the specified position in the list of body * parts. @@ -209,7 +209,7 @@ bodyPart.setParent(null); return bodyPart; } - + /** * Replaces the body part at the specified position in the list of body * parts with the specified body part. @@ -229,30 +229,79 @@ BodyPart replacedBodyPart = bodyParts.set(index, bodyPart); if (bodyPart == replacedBodyPart) - throw new IllegalArgumentException("Cannot replace body part with itself"); + throw new IllegalArgumentException( + "Cannot replace body part with itself"); bodyPart.setParent(parent); replacedBodyPart.setParent(null); return replacedBodyPart; } - + + // package private for now; might become public someday + ByteSequence getPreambleRaw() { + return preamble; + } + + void setPreambleRaw(ByteSequence preamble) { + this.preamble = preamble; + this.preambleStrCache = null; + } + /** * Gets the preamble. * * @return the preamble. */ public String getPreamble() { - return preamble; + if (preambleStrCache == null) { + preambleStrCache = ContentUtil.decode(preamble); + } + return preambleStrCache; } - + /** * Sets the preamble. * - * @param preamble the preamble. + * @param preamble + * the preamble. */ public void setPreamble(String preamble) { - this.preamble = preamble; + this.preamble = ContentUtil.encode(preamble); + this.preambleStrCache = preamble; + } + + // package private for now; might become public someday + ByteSequence getEpilogueRaw() { + return epilogue; + } + + void setEpilogueRaw(ByteSequence epilogue) { + this.epilogue = epilogue; + this.epilogueStrCache = null; + } + + /** + * Gets the epilogue. + * + * @return the epilogue. + */ + public String getEpilogue() { + if (epilogueStrCache == null) { + epilogueStrCache = ContentUtil.decode(epilogue); + } + return epilogueStrCache; + } + + /** + * Sets the epilogue. + * + * @param epilogue + * the epilogue. + */ + public void setEpilogue(String epilogue) { + this.epilogue = ContentUtil.encode(epilogue); + this.epilogueStrCache = epilogue; } /** Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/AbstractEntity.java Fri Feb 27 16:45:02 2009 @@ -33,7 +33,6 @@ import org.apache.james.mime4j.io.MaxHeaderLimitException; import org.apache.james.mime4j.io.MaxLineLimitException; import org.apache.james.mime4j.util.ByteArrayBuffer; -import org.apache.james.mime4j.util.CharArrayBuffer; import org.apache.james.mime4j.util.CharsetUtil; /** @@ -52,7 +51,6 @@ protected int state; private final ByteArrayBuffer linebuf; - private final CharArrayBuffer fieldbuf; private int lineCount; private Field field; @@ -92,7 +90,6 @@ this.config = config; this.body = newBodyDescriptor(parent); this.linebuf = new ByteArrayBuffer(64); - this.fieldbuf = new CharArrayBuffer(64); this.lineCount = 0; this.endOfHeader = false; this.headerCount = 0; @@ -125,13 +122,14 @@ protected abstract LineReaderInputStream getDataStream(); - private void fillFieldBuffer() throws IOException, MimeException { - if (endOfHeader) { - return; - } + private ByteArrayBuffer fillFieldBuffer() throws IOException, MimeException { + if (endOfHeader) + throw new IllegalStateException(); + int maxLineLen = config.getMaxLineLen(); LineReaderInputStream instream = getDataStream(); - fieldbuf.clear(); + ByteArrayBuffer fieldbuf = new ByteArrayBuffer(64); + for (;;) { // If there's still data stuck in the line buffer // copy it to the field buffer @@ -140,7 +138,7 @@ throw new MaxLineLimitException("Maximum line length limit exceeded"); } if (len > 0) { - fieldbuf.append(linebuf, 0, len); + fieldbuf.append(linebuf.buffer(), 0, len); } linebuf.clear(); if (instream.readLine(linebuf) == -1) { @@ -169,6 +167,8 @@ } } } + + return fieldbuf; } protected boolean parseField() throws MimeException, IOException { @@ -180,42 +180,37 @@ if (headerCount >= maxHeaderLimit) { throw new MaxHeaderLimitException("Maximum header limit exceeded"); } - - fillFieldBuffer(); + + ByteArrayBuffer fieldbuf = fillFieldBuffer(); headerCount++; - + // Strip away line delimiter int len = fieldbuf.length(); - if (len > 0 && fieldbuf.charAt(len - 1) == '\n') { + if (len > 0 && fieldbuf.byteAt(len - 1) == '\n') { len--; } - if (len > 0 && fieldbuf.charAt(len - 1) == '\r') { + if (len > 0 && fieldbuf.byteAt(len - 1) == '\r') { len--; } fieldbuf.setLength(len); boolean valid = true; - String fieldName = null; - String fieldValue = null; - - int pos = fieldbuf.indexOf(':'); + int pos = fieldbuf.indexOf((byte) ':'); if (pos <= 0) { monitor(Event.INALID_HEADER); valid = false; } else { - fieldName = fieldbuf.substring(0, pos); - for (int i = 0; i < fieldName.length(); i++) { - if (!fieldChars.get(fieldName.charAt(i))) { + for (int i = 0; i < pos; i++) { + if (!fieldChars.get(fieldbuf.byteAt(i) & 0xff)) { monitor(Event.INALID_HEADER); valid = false; break; } } - fieldValue = fieldbuf.substring(pos + 1, fieldbuf.length()); } if (valid) { - field = new RawField(fieldName, fieldValue, fieldbuf.toString()); + field = new RawField(fieldbuf, pos); body.addField(field); return true; } Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/Field.java Fri Feb 27 16:45:02 2009 @@ -19,6 +19,8 @@ package org.apache.james.mime4j.parser; +import org.apache.james.mime4j.util.ByteSequence; + /** * Abstract MIME field. */ @@ -39,10 +41,10 @@ String getBody(); /** - * Gets the original raw field string. + * Gets the original raw field bytes. * - * @return the original raw field string. + * @return the original raw field bytes. */ - String getRaw(); + ByteSequence getRaw(); } Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/MimeEntity.java Fri Feb 27 16:45:02 2009 @@ -32,6 +32,8 @@ import org.apache.james.mime4j.io.LineReaderInputStream; import org.apache.james.mime4j.io.LineReaderInputStreamAdaptor; import org.apache.james.mime4j.io.MimeBoundaryInputStream; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; import org.apache.james.mime4j.util.MimeUtil; public class MimeEntity extends AbstractEntity { @@ -94,12 +96,10 @@ throw new IllegalStateException("Invalid state: " + stateToString(state)); } skipHeader = true; - body.addField(new RawField( - "Content-Type", - contentType, - "Content-Type: " +contentType)); + ByteSequence raw = ContentUtil.encode("Content-Type: " + contentType); + body.addField(new RawField(raw, 12)); } - + @Override protected int getLineNumber() { if (lineSource == null) Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/parser/RawField.java Fri Feb 27 16:45:02 2009 @@ -19,37 +19,58 @@ package org.apache.james.mime4j.parser; +import org.apache.james.mime4j.util.ByteSequence; +import org.apache.james.mime4j.util.ContentUtil; + /** * The basic immutable MIME field. */ -public class RawField implements Field { +class RawField implements Field { + + private final ByteSequence raw; + private int colonIdx; + + private String name; + private String body; - private final String name; - private final String body; - private final String raw; - - public RawField(String name, String body, String raw) { - super(); - this.name = name; - this.body = body; + public RawField(ByteSequence raw, int colonIdx) { this.raw = raw; + this.colonIdx = colonIdx; } public String getName() { - return this.name; + if (name == null) { + name = parseName(); + } + + return name; } public String getBody() { - return this.body; + if (body == null) { + body = parseBody(); + } + + return body; } - public String getRaw() { - return this.raw; + public ByteSequence getRaw() { + return raw; } @Override public String toString() { - return this.raw; + return getName() + ':' + getBody(); + } + + private String parseName() { + return ContentUtil.decode(raw, 0, colonIdx); + } + + private String parseBody() { + int offset = colonIdx + 1; + int length = raw.length() - offset; + return ContentUtil.decode(raw, offset, length); } } Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteArrayBuffer.java Fri Feb 27 16:45:02 2009 @@ -19,10 +19,11 @@ package org.apache.james.mime4j.util; + /** * A resizable byte array. */ -public final class ByteArrayBuffer { +public final class ByteArrayBuffer implements ByteSequence { private byte[] buffer; private int len; @@ -35,6 +36,26 @@ this.buffer = new byte[capacity]; } + public ByteArrayBuffer(byte[] bytes, boolean dontCopy) { + this(bytes, bytes.length, dontCopy); + } + + public ByteArrayBuffer(byte[] bytes, int len, boolean dontCopy) { + if (bytes == null) + throw new IllegalArgumentException(); + if (len < 0 || len > bytes.length) + throw new IllegalArgumentException(); + + if (dontCopy) { + this.buffer = bytes; + } else { + this.buffer = new byte[len]; + System.arraycopy(bytes, 0, this.buffer, 0, len); + } + + this.len = len; + } + private void expand(int newlen) { byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)]; System.arraycopy(this.buffer, 0, newbuffer, 0, this.len); @@ -103,7 +124,10 @@ return b; } - public int byteAt(int i) { + public byte byteAt(int i) { + if (i < 0 || i >= this.len) + throw new IndexOutOfBoundsException(); + return this.buffer[i]; } @@ -118,7 +142,29 @@ public byte[] buffer() { return this.buffer; } - + + public int indexOf(byte b) { + return indexOf(b, 0, this.len); + } + + public int indexOf(byte b, int beginIndex, int endIndex) { + if (beginIndex < 0) { + beginIndex = 0; + } + if (endIndex > this.len) { + endIndex = this.len; + } + if (beginIndex > endIndex) { + return -1; + } + for (int i = beginIndex; i < endIndex; i++) { + if (this.buffer[i] == b) { + return i; + } + } + return -1; + } + public void setLength(int len) { if (len < 0 || len > this.buffer.length) { throw new IndexOutOfBoundsException(); @@ -138,5 +184,5 @@ public String toString() { return new String(toByteArray()); } - + } Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java?rev=748583&view=auto ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java (added) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ByteSequence.java Fri Feb 27 16:45:02 2009 @@ -0,0 +1,58 @@ +/**************************************************************** + * 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; + +/** + * An immutable sequence of bytes. + */ +public interface ByteSequence { + + /** + * An empty byte sequence. + */ + ByteSequence EMPTY = new EmptyByteSequence(); + + /** + * Returns the length of this byte sequence. + * + * @return the number of bytes in this sequence. + */ + int length(); + + /** + * Returns the byte value at the specified index. + * + * @param index + * the index of the byte value to be returned. + * @return the corresponding byte value + * @throws IndexOutOfBoundsException + * if index < 0 || index >= length(). + */ + byte byteAt(int index); + + /** + * Copies the contents of this byte sequence into a newly allocated byte + * array and returns that array. + * + * @return a byte array holding a copy of this byte sequence. + */ + byte[] toByteArray(); + +} Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java?rev=748583&view=auto ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java (added) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/ContentUtil.java Fri Feb 27 16:45:02 2009 @@ -0,0 +1,138 @@ +/**************************************************************** + * 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.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; + +/** + * Utility methods for converting textual content of a message. + */ +public class ContentUtil { + + private ContentUtil() { + } + + /** + * Encodes the specified string into an immutable sequence of bytes using + * the US-ASCII charset. + * + * @param string + * string to encode. + * @return encoded string as an immutable sequence of bytes. + */ + public static ByteSequence encode(String string) { + return encode(CharsetUtil.US_ASCII, string); + } + + /** + * Encodes the specified string into an immutable sequence of bytes using + * the specified charset. + * + * @param charset + * Java charset to be used for the conversion. + * @param string + * string to encode. + * @return encoded string as an immutable sequence of bytes. + */ + public static ByteSequence encode(Charset charset, String string) { + ByteBuffer encoded = charset.encode(CharBuffer.wrap(string)); + ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining()); + bab.append(encoded.array(), encoded.position(), encoded.remaining()); + return bab; + } + + /** + * Decodes the specified sequence of bytes into a string using the US-ASCII + * charset. + * + * @param byteSequence + * sequence of bytes to decode. + * @return decoded string. + */ + public static String decode(ByteSequence byteSequence) { + return decode(CharsetUtil.US_ASCII, byteSequence, 0, byteSequence + .length()); + } + + /** + * Decodes the specified sequence of bytes into a string using the specified + * charset. + * + * @param charset + * Java charset to be used for the conversion. + * @param byteSequence + * sequence of bytes to decode. + * @return decoded string. + */ + public static String decode(Charset charset, ByteSequence byteSequence) { + return decode(charset, byteSequence, 0, byteSequence.length()); + } + + /** + * Decodes a sub-sequence of the specified sequence of bytes into a string + * using the US-ASCII charset. + * + * @param byteSequence + * sequence of bytes to decode. + * @param offset + * offset into the byte sequence. + * @param length + * number of bytes. + * @return decoded string. + */ + public static String decode(ByteSequence byteSequence, int offset, + int length) { + return decode(CharsetUtil.US_ASCII, byteSequence, offset, length); + } + + /** + * Decodes a sub-sequence of the specified sequence of bytes into a string + * using the specified charset. + * + * @param charset + * Java charset to be used for the conversion. + * @param byteSequence + * sequence of bytes to decode. + * @param offset + * offset into the byte sequence. + * @param length + * number of bytes. + * @return decoded string. + */ + public static String decode(Charset charset, ByteSequence byteSequence, + int offset, int length) { + if (byteSequence instanceof ByteArrayBuffer) { + ByteArrayBuffer bab = (ByteArrayBuffer) byteSequence; + return decode(charset, bab.buffer(), offset, length); + } else { + byte[] bytes = byteSequence.toByteArray(); + return decode(charset, bytes, offset, length); + } + } + + private static String decode(Charset charset, byte[] buffer, int offset, + int length) { + return charset.decode(ByteBuffer.wrap(buffer, offset, length)) + .toString(); + } + +} Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java?rev=748583&view=auto ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java (added) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/util/EmptyByteSequence.java Fri Feb 27 16:45:02 2009 @@ -0,0 +1,36 @@ +/**************************************************************** + * 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; + +final class EmptyByteSequence implements ByteSequence { + private static final byte[] EMPTY_BYTES = {}; + + public int length() { + return 0; + } + + public byte byteAt(int index) { + throw new IndexOutOfBoundsException(); + } + + public byte[] toByteArray() { + return EMPTY_BYTES; + } +} \ No newline at end of file Modified: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java?rev=748583&r1=748582&r2=748583&view=diff ============================================================================== --- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java (original) +++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/descriptor/BaseTestForBodyDescriptors.java Fri Feb 27 16:45:02 2009 @@ -19,12 +19,11 @@ package org.apache.james.mime4j.descriptor; -import org.apache.james.mime4j.descriptor.BodyDescriptor; -import org.apache.james.mime4j.descriptor.MutableBodyDescriptor; -import org.apache.james.mime4j.field.AbstractField; - import junit.framework.TestCase; +import org.apache.james.mime4j.parser.Field; +import org.apache.james.mime4j.util.ByteSequence; + public abstract class BaseTestForBodyDescriptors extends TestCase { protected abstract MutableBodyDescriptor newBodyDescriptor(); @@ -35,7 +34,7 @@ MutableBodyDescriptor bd = null; bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type ", "text/plain; charset=ISO-8859-1; " + bd.addField(new TestField("Content-Type ", "text/plain; charset=ISO-8859-1; " + "boundary=foo; param1=value1; param2=value2; param3=value3")); assertEquals(3, bd.getContentTypeParameters().size()); assertEquals("value1", bd.getContentTypeParameters().get("param1")); @@ -43,7 +42,7 @@ assertEquals("value3", bd.getContentTypeParameters().get("param3")); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type ", "text/plain; param1=value1; param2=value2;" + bd.addField(new TestField("Content-Type ", "text/plain; param1=value1; param2=value2;" + " param3=value3")); assertEquals(3, bd.getContentTypeParameters().size()); assertEquals("value1", bd.getContentTypeParameters().get("param1")); @@ -51,7 +50,7 @@ assertEquals("value3", bd.getContentTypeParameters().get("param3")); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type ", "text/plain; " + bd.addField(new TestField("Content-Type ", "text/plain; " + "param1= \" value with\tspaces \" ; " + "param2=\"\\\"value4 with escaped \\\" \\\"\";")); assertEquals(2, bd.getContentTypeParameters().size()); @@ -63,7 +62,7 @@ * The parameter value should be \n\" */ bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type ", "text/plain; param=\"\\n\\\\\\\"\"")); + bd.addField(new TestField("Content-Type ", "text/plain; param=\"\\n\\\\\\\"\"")); assertEquals(1, bd.getContentTypeParameters().size()); assertEquals("\\n\\\"", bd.getContentTypeParameters().get("param")); } @@ -75,10 +74,10 @@ * Make sure that only the first Content-Type header added is used. */ bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type ", "text/plain; charset=ISO-8859-1")); + bd.addField(new TestField("Content-Type ", "text/plain; charset=ISO-8859-1")); assertEquals("text/plain", bd.getMimeType()); assertEquals("iso-8859-1", bd.getCharset()); - bd.addField(AbstractField.parse("Content-Type ", "text/html; charset=us-ascii")); + bd.addField(new TestField("Content-Type ", "text/html; charset=us-ascii")); assertEquals("text/plain", bd.getMimeType()); assertEquals("iso-8859-1", bd.getCharset()); } @@ -87,32 +86,32 @@ MutableBodyDescriptor bd = null; bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type ", "text/PLAIN")); + bd.addField(new TestField("Content-Type ", "text/PLAIN")); assertEquals("text/plain", bd.getMimeType()); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type ", "text/PLAIN;")); + bd.addField(new TestField("Content-Type ", "text/PLAIN;")); assertEquals("text/plain", bd.getMimeType()); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("content-type", " TeXt / html ")); + bd.addField(new TestField("content-type", " TeXt / html ")); assertEquals("text/html", bd.getMimeType()); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("CONTENT-TYPE", " x-app/yada ; param = yada")); + bd.addField(new TestField("CONTENT-TYPE", " x-app/yada ; param = yada")); assertEquals("x-app/yada", bd.getMimeType()); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("CONTENT-TYPE", " yada")); + bd.addField(new TestField("CONTENT-TYPE", " yada")); assertEquals("text/plain", bd.getMimeType()); /* * Make sure that only the first Content-Type header added is used. */ bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type ", "text/plain")); + bd.addField(new TestField("Content-Type ", "text/plain")); assertEquals("text/plain", bd.getMimeType()); - bd.addField(AbstractField.parse("Content-Type ", "text/html")); + bd.addField(new TestField("Content-Type ", "text/html")); assertEquals("text/plain", bd.getMimeType()); /* @@ -122,19 +121,19 @@ MutableBodyDescriptor parent = null; parent = newBodyDescriptor(); - parent.addField(AbstractField.parse("Content-Type", "mutlipart/alternative; boundary=foo")); + parent.addField(new TestField("Content-Type", "mutlipart/alternative; boundary=foo")); child = newBodyDescriptor(parent); assertEquals("text/plain", child.getMimeType()); - child.addField(AbstractField.parse("Content-Type", " child/type")); + child.addField(new TestField("Content-Type", " child/type")); assertEquals("child/type", child.getMimeType()); parent = newBodyDescriptor(); - parent.addField(AbstractField.parse("Content-Type", "multipart/digest; boundary=foo")); + parent.addField(new TestField("Content-Type", "multipart/digest; boundary=foo")); child = newBodyDescriptor(parent); assertEquals("message/rfc822", child.getMimeType()); - child.addField(AbstractField.parse("Content-Type", " child/type")); + child.addField(new TestField("Content-Type", " child/type")); assertEquals("child/type", child.getMimeType()); } @@ -147,39 +146,39 @@ */ bd = newBodyDescriptor(); assertEquals("us-ascii", bd.getCharset()); - bd.addField(AbstractField.parse("Content-Type ", "text/type; charset=ISO-8859-1")); + bd.addField(new TestField("Content-Type ", "text/type; charset=ISO-8859-1")); assertEquals("iso-8859-1", bd.getCharset()); bd = newBodyDescriptor(); assertEquals("us-ascii", bd.getCharset()); - bd.addField(AbstractField.parse("Content-Type ", "text/type")); + bd.addField(new TestField("Content-Type ", "text/type")); assertEquals("us-ascii", bd.getCharset()); /* * Test boundary. */ bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type", "text/html; boundary=yada yada")); + bd.addField(new TestField("Content-Type", "text/html; boundary=yada yada")); assertNull(bd.getBoundary()); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type", "multipart/yada; boundary=yada")); + bd.addField(new TestField("Content-Type", "multipart/yada; boundary=yada")); assertEquals("yada", bd.getBoundary()); /* * Test some weird parameters. */ bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type", "multipart/yada; boundary=yada yada")); + bd.addField(new TestField("Content-Type", "multipart/yada; boundary=yada yada")); assertEquals("yada", bd.getBoundary()); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type", "multipart/yada; boUNdarY= ya:*da; \tcharset\t = big5")); + bd.addField(new TestField("Content-Type", "multipart/yada; boUNdarY= ya:*da; \tcharset\t = big5")); assertEquals("ya:*da", bd.getBoundary()); assertEquals("big5", bd.getCharset()); bd = newBodyDescriptor(); - bd.addField(AbstractField.parse("Content-Type", "multipart/yada; boUNdarY= \"ya \\\"\\\"\tda \\\"\"; " + bd.addField(new TestField("Content-Type", "multipart/yada; boUNdarY= \"ya \\\"\\\"\tda \\\"\"; " + "\tcharset\t = \"\\\"hepp\\\" =us\t-ascii\"")); assertEquals("ya \"\"\tda \"", bd.getBoundary()); assertEquals("\"hepp\" =us\t-ascii", bd.getCharset()); @@ -192,23 +191,47 @@ bd = newBodyDescriptor(); assertEquals(-1, bd.getContentLength()); - bd.addField(AbstractField.parse("Content-Length", "9901")); + bd.addField(new TestField("Content-Length", "9901")); assertEquals(9901, bd.getContentLength()); // only the first content-length counts - bd.addField(AbstractField.parse("Content-Length", "1239901")); + bd.addField(new TestField("Content-Length", "1239901")); assertEquals(9901, bd.getContentLength()); } public void testDoDefaultToUsAsciiWhenUntyped() throws Exception { MutableBodyDescriptor descriptor = newBodyDescriptor(); - descriptor.addField(AbstractField.parse("To", "me@example.org")); + descriptor.addField(new TestField("To", "me@example.org")); assertEquals("us-ascii", descriptor.getCharset()); } public void testDoNotDefaultToUsAsciiForNonTextTypes() throws Exception { MutableBodyDescriptor descriptor = newBodyDescriptor(); - descriptor.addField(AbstractField.parse("Content-Type", "image/png; name=blob.png")); + descriptor.addField(new TestField("Content-Type", "image/png; name=blob.png")); assertNull(descriptor.getCharset()); } + + private static final class TestField implements Field { + + private final String name; + private final String body; + + public TestField(String name, String body){ + this.name = name; + this.body = body; + } + + public String getName() { + return name; + } + + public String getBody() { + return body; + } + + public ByteSequence getRaw() { + throw new UnsupportedOperationException(); + } + + } }