Author: bago Date: Sat Oct 4 08:40:26 2008 New Revision: 701655 URL: http://svn.apache.org/viewvc?rev=701655&view=rev Log: Add methods for removing and replacing header fields (MIME4J-80) Patch kindly provided by Markus Wiederkehr
Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java?rev=701655&r1=701654&r2=701655&view=diff ============================================================================== --- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java (original) +++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/Header.java Sat Oct 4 08:40:26 2008 @@ -1,206 +1,267 @@ -/**************************************************************** - * 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.message; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import org.apache.james.mime4j.MimeException; -import org.apache.james.mime4j.field.ContentTypeField; -import org.apache.james.mime4j.field.Field; -import org.apache.james.mime4j.parser.AbstractContentHandler; -import org.apache.james.mime4j.parser.MimeStreamParser; -import org.apache.james.mime4j.util.CharArrayBuffer; -import org.apache.james.mime4j.util.CharsetUtil; -import org.apache.james.mime4j.util.MessageUtils; - - -/** - * The header of an entity (see RFC 2045). - * - * - * @version $Id: Header.java,v 1.3 2004/10/04 15:36:44 ntherning Exp $ - */ -public class Header { - private List fields = new LinkedList(); - private HashMap fieldMap = new HashMap(); - - /** - * Creates a new empty <code>Header</code>. - */ - public Header() { - } - - /** - * Creates a new <code>Header</code> from the specified stream. - * - * @param is the stream to read the header from. - */ - public Header(InputStream is) throws MimeException, IOException { - final MimeStreamParser parser = new MimeStreamParser(); - parser.setContentHandler(new AbstractContentHandler() { - public void endHeader() { - parser.stop(); - } - public void field(String fieldData) throws MimeException { - addField(Field.parse(fieldData)); - } - }); - parser.parse(is); - } - - /** - * Adds a field to the end of the list of fields. - * - * @param field the field to add. - */ - public void addField(Field field) { - List values = (List) fieldMap.get(field.getName().toLowerCase()); - if (values == null) { - values = new LinkedList(); - fieldMap.put(field.getName().toLowerCase(), values); - } - values.add(field); - fields.add(field); - } - - /** - * Gets the fields of this header. The returned list will not be - * modifiable. - * - * @return the list of <code>Field</code> objects. - */ - public List getFields() { - return Collections.unmodifiableList(fields); - } - - /** - * Gets a <code>Field</code> given a field name. If there are multiple - * such fields defined in this header the first one will be returned. - * - * @param name the field name (e.g. From, Subject). - * @return the field or <code>null</code> if none found. - */ - public Field getField(String name) { - List l = (List) fieldMap.get(name.toLowerCase()); - if (l != null && !l.isEmpty()) { - return (Field) l.get(0); - } - return null; - } - - /** - * Gets all <code>Field</code>s having the specified field name. - * - * @param name the field name (e.g. From, Subject). - * @return the list of fields. - */ - public List getFields(final String name) { - final String lowerCaseName = name.toLowerCase(); - final List l = (List) fieldMap.get(lowerCaseName); - final List results; - if (l == null || l.isEmpty()) { - results = Collections.EMPTY_LIST; - } else { - results = Collections.unmodifiableList(l); - } - return results; - } - - /** - * Return Header Object as String representation. Each headerline is - * seperated by "\r\n" - * - * @return headers - */ - public String toString() { - CharArrayBuffer str = new CharArrayBuffer(128); - for (Iterator it = fields.iterator(); it.hasNext();) { - str.append(it.next().toString()); - str.append("\r\n"); - } - return str.toString(); - } - - - /** - * Write the Header to the given OutputStream. - * <p> - * Compatibility mode: - * <ul> - * <li> - * [EMAIL PROTECTED] MessageUtils#LENIENT}: use charset of the Content-Type header - * </li> - * <li> - * [EMAIL PROTECTED] MessageUtils#STRICT_ERROR}: use US-ASCII and throw [EMAIL PROTECTED] MimeException} - * if a non ASCII character is encountered - * </li> - * <li> - * [EMAIL PROTECTED] MessageUtils#STRICT_ERROR}: ignore non ASCII characters if encountered - * </li> - * </ul> - * @param out the OutputStream to write to - * @param mode compatibility mode: - * [EMAIL PROTECTED] MessageUtils#LENIENT}, [EMAIL PROTECTED] MessageUtils#STRICT_ERROR}, [EMAIL PROTECTED] MessageUtils#STRICT_IGNORE} - * - * @throws IOException if case of an I/O error - * @throws MimeException if case of a MIME protocol violation - */ - public void writeTo(final OutputStream out, int mode) throws IOException, MimeException { - Charset charset = null; - if (mode == MessageUtils.LENIENT) { - final ContentTypeField contentTypeField = ((ContentTypeField) getField(Field.CONTENT_TYPE)); - if (contentTypeField == null) { - charset = MessageUtils.DEFAULT_CHARSET; - } else { - final String contentTypeFieldCharset = contentTypeField.getCharset(); - if (contentTypeField != null && contentTypeFieldCharset != null) { - charset = CharsetUtil.getCharset(contentTypeFieldCharset); - } else { - charset = MessageUtils.ISO_8859_1; - } - } - } else { - charset = MessageUtils.DEFAULT_CHARSET; - } - BufferedWriter writer = new BufferedWriter( - new OutputStreamWriter(out, charset), 8192); - for (Iterator it = fields.iterator(); it.hasNext();) { - Field field = (Field) it.next(); - String fs = field.toString(); - if (mode == MessageUtils.STRICT_ERROR && !MessageUtils.isASCII(fs)) { - throw new MimeException("Header '" + fs + "' violates RFC 822"); - } - writer.write(fs); - writer.write(MessageUtils.CRLF); - } - writer.write(MessageUtils.CRLF); - writer.flush(); - } -} +/**************************************************************** + * 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.message; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.field.ContentTypeField; +import org.apache.james.mime4j.field.Field; +import org.apache.james.mime4j.parser.AbstractContentHandler; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.util.CharArrayBuffer; +import org.apache.james.mime4j.util.CharsetUtil; +import org.apache.james.mime4j.util.MessageUtils; + + +/** + * The header of an entity (see RFC 2045). + * + * + * @version $Id: Header.java,v 1.3 2004/10/04 15:36:44 ntherning Exp $ + */ +public class Header { + private List fields = new LinkedList(); + private Map/*<String, List<Field>>*/ fieldMap = new HashMap(); + + /** + * Creates a new empty <code>Header</code>. + */ + public Header() { + } + + /** + * Creates a new <code>Header</code> from the specified stream. + * + * @param is the stream to read the header from. + */ + public Header(InputStream is) throws MimeException, IOException { + final MimeStreamParser parser = new MimeStreamParser(); + parser.setContentHandler(new AbstractContentHandler() { + public void endHeader() { + parser.stop(); + } + public void field(String fieldData) throws MimeException { + addField(Field.parse(fieldData)); + } + }); + parser.parse(is); + } + + /** + * Adds a field to the end of the list of fields. + * + * @param field the field to add. + */ + public void addField(Field field) { + List values = (List) fieldMap.get(field.getName().toLowerCase()); + if (values == null) { + values = new LinkedList(); + fieldMap.put(field.getName().toLowerCase(), values); + } + values.add(field); + fields.add(field); + } + + /** + * Gets the fields of this header. The returned list will not be + * modifiable. + * + * @return the list of <code>Field</code> objects. + */ + public List getFields() { + return Collections.unmodifiableList(fields); + } + + /** + * Gets a <code>Field</code> given a field name. If there are multiple + * such fields defined in this header the first one will be returned. + * + * @param name the field name (e.g. From, Subject). + * @return the field or <code>null</code> if none found. + */ + public Field getField(String name) { + List l = (List) fieldMap.get(name.toLowerCase()); + if (l != null && !l.isEmpty()) { + return (Field) l.get(0); + } + return null; + } + + /** + * Gets all <code>Field</code>s having the specified field name. + * + * @param name the field name (e.g. From, Subject). + * @return the list of fields. + */ + public List getFields(final String name) { + final String lowerCaseName = name.toLowerCase(); + final List l = (List) fieldMap.get(lowerCaseName); + final List results; + if (l == null || l.isEmpty()) { + results = Collections.EMPTY_LIST; + } else { + results = Collections.unmodifiableList(l); + } + return results; + } + + /** + * Removes all <code>Field</code>s having the specified field name. + * + * @param name + * the field name (e.g. From, Subject). + * @return number of fields removed. + */ + public int removeFields(String name) { + final String lowerCaseName = name.toLowerCase(); + List removed = (List) fieldMap.remove(lowerCaseName); + if (removed == null || removed.isEmpty()) + return 0; + + for (Iterator iterator = fields.iterator(); iterator.hasNext();) { + Field field = (Field) iterator.next(); + if (field.getName().equalsIgnoreCase(name)) + iterator.remove(); + } + + return removed.size(); + } + + /** + * Sets or replaces a field. This method is useful for header fields such as + * Subject or Message-ID that should not occur more than once in a message. + * + * If this <code>Header</code> does not already contain a header field of + * the same name as the given field then it is added to the end of the list + * of fields (same behavior as [EMAIL PROTECTED] #addField(Field)}). Otherwise the + * first occurrence of a field with the same name is replaced by the given + * field and all further occurrences are removed. + * + * @param field the field to set. + */ + public void setField(Field field) { + final String lowerCaseName = field.getName().toLowerCase(); + List l = (List) fieldMap.get(lowerCaseName); + if (l == null || l.isEmpty()) { + addField(field); + return; + } + + l.clear(); + l.add(field); + + int firstOccurrence = -1; + int index = 0; + for (Iterator iterator = fields.iterator(); iterator.hasNext(); index++) { + Field f = (Field) iterator.next(); + if (f.getName().equalsIgnoreCase(field.getName())) { + iterator.remove(); + + if (firstOccurrence == -1) + firstOccurrence = index; + } + } + + fields.add(firstOccurrence, field); + } + + /** + * Return Header Object as String representation. Each headerline is + * seperated by "\r\n" + * + * @return headers + */ + public String toString() { + CharArrayBuffer str = new CharArrayBuffer(128); + for (Iterator it = fields.iterator(); it.hasNext();) { + str.append(it.next().toString()); + str.append("\r\n"); + } + return str.toString(); + } + + + /** + * Write the Header to the given OutputStream. + * <p> + * Compatibility mode: + * <ul> + * <li> + * [EMAIL PROTECTED] MessageUtils#LENIENT}: use charset of the Content-Type header + * </li> + * <li> + * [EMAIL PROTECTED] MessageUtils#STRICT_ERROR}: use US-ASCII and throw [EMAIL PROTECTED] MimeException} + * if a non ASCII character is encountered + * </li> + * <li> + * [EMAIL PROTECTED] MessageUtils#STRICT_ERROR}: ignore non ASCII characters if encountered + * </li> + * </ul> + * @param out the OutputStream to write to + * @param mode compatibility mode: + * [EMAIL PROTECTED] MessageUtils#LENIENT}, [EMAIL PROTECTED] MessageUtils#STRICT_ERROR}, [EMAIL PROTECTED] MessageUtils#STRICT_IGNORE} + * + * @throws IOException if case of an I/O error + * @throws MimeException if case of a MIME protocol violation + */ + public void writeTo(final OutputStream out, int mode) throws IOException, MimeException { + Charset charset = null; + if (mode == MessageUtils.LENIENT) { + final ContentTypeField contentTypeField = ((ContentTypeField) getField(Field.CONTENT_TYPE)); + if (contentTypeField == null) { + charset = MessageUtils.DEFAULT_CHARSET; + } else { + final String contentTypeFieldCharset = contentTypeField.getCharset(); + if (contentTypeField != null && contentTypeFieldCharset != null) { + charset = CharsetUtil.getCharset(contentTypeFieldCharset); + } else { + charset = MessageUtils.ISO_8859_1; + } + } + } else { + charset = MessageUtils.DEFAULT_CHARSET; + } + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(out, charset), 8192); + for (Iterator it = fields.iterator(); it.hasNext();) { + Field field = (Field) it.next(); + String fs = field.toString(); + if (mode == MessageUtils.STRICT_ERROR && !MessageUtils.isASCII(fs)) { + throw new MimeException("Header '" + fs + "' violates RFC 822"); + } + writer.write(fs); + writer.write(MessageUtils.CRLF); + } + writer.write(MessageUtils.CRLF); + writer.flush(); + } +} Modified: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java?rev=701655&r1=701654&r2=701655&view=diff ============================================================================== --- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java (original) +++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/HeaderTest.java Sat Oct 4 08:40:26 2008 @@ -104,4 +104,67 @@ "Content-type: text/plain; charset=ISO-8859-1\r\n\r\n", s); } + public void testRemoveFields() throws Exception { + Header header = new Header(); + header.addField(Field.parse("Received: from foo by bar for james")); + header.addField(Field.parse("Content-type: text/plain; charset=US-ASCII")); + header.addField(Field.parse("ReCeIvEd: from bar by foo for james")); + + assertEquals(3, header.getFields().size()); + assertEquals(2, header.getFields("received").size()); + assertEquals(1, header.getFields("Content-Type").size()); + + assertEquals(2, header.removeFields("rEcEiVeD")); + + assertEquals(1, header.getFields().size()); + assertEquals(0, header.getFields("received").size()); + assertEquals(1, header.getFields("Content-Type").size()); + + assertEquals("Content-type", ((Field) header.getFields().get(0)).getName()); + } + + public void testRemoveNonExistantField() throws Exception { + Header header = new Header(); + header.addField(Field.parse("Received: from foo by bar for james")); + header.addField(Field.parse("Content-type: text/plain; charset=US-ASCII")); + header.addField(Field.parse("ReCeIvEd: from bar by foo for james")); + + assertEquals(0, header.removeFields("noSuchField")); + + assertEquals(3, header.getFields().size()); + assertEquals(2, header.getFields("received").size()); + assertEquals(1, header.getFields("Content-Type").size()); + } + + public void testSetField() throws Exception { + Header header = new Header(); + header.addField(Field.parse("From: [EMAIL PROTECTED]")); + header.addField(Field.parse("Received: from foo by bar for james")); + header.addField(Field.parse("Content-type: text/plain; charset=US-ASCII")); + header.addField(Field.parse("ReCeIvEd: from bar by foo for james")); + + header.setField(Field.parse("received: from nobody by noone for james")); + + assertEquals(3, header.getFields().size()); + assertEquals(1, header.getFields("received").size()); + + assertEquals("From", ((Field) header.getFields().get(0)).getName()); + assertEquals("received", ((Field) header.getFields().get(1)).getName()); + assertEquals("Content-type", ((Field) header.getFields().get(2)).getName()); + } + + public void testSetNonExistantField() throws Exception { + Header header = new Header(); + header.addField(Field.parse("Received: from foo by bar for james")); + header.addField(Field.parse("Content-type: text/plain; charset=US-ASCII")); + header.addField(Field.parse("ReCeIvEd: from bar by foo for james")); + + header.setField(Field.parse("Message-ID: <[EMAIL PROTECTED]>")); + + assertEquals(4, header.getFields().size()); + assertEquals(1, header.getFields("message-id").size()); + + assertEquals("Message-ID", ((Field) header.getFields().get(3)).getName()); + } + } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]