1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-12-25 00:58:50 -05:00

Upgrade to mime4j. "0.7" branch as of

http://svn.apache.org/repos/asf/james/mime4j/trunk@1058339
This commit is contained in:
Jesse Vincent 2011-01-19 21:32:09 +00:00
parent f5bb836087
commit 8329a0287b
225 changed files with 26744 additions and 8494 deletions

View File

@ -13,11 +13,12 @@ import com.fsck.k9.K9;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.Utility;
import org.apache.james.mime4j.codec.EncoderUtil;
import org.apache.james.mime4j.field.address.AddressList;
import org.apache.james.mime4j.field.address.Mailbox;
import org.apache.james.mime4j.field.address.MailboxList;
import org.apache.james.mime4j.field.address.NamedMailbox;
import org.apache.james.mime4j.field.address.parser.ParseException;
import org.apache.james.mime4j.dom.address.AddressList;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.field.DefaultFieldParser;
import org.apache.james.mime4j.field.address.parser.AddressBuilder;
import org.apache.james.mime4j.MimeException;
import java.util.ArrayList;
import java.util.List;
@ -137,21 +138,21 @@ public class Address
}
try
{
MailboxList parsedList = AddressList.parse(addressList).flatten();
MailboxList parsedList = (MailboxList) AddressBuilder.parseAddressList(addressList).flatten();
for (int i = 0, count = parsedList.size(); i < count; i++)
{
org.apache.james.mime4j.field.address.Address address = parsedList.get(i);
if (address instanceof NamedMailbox)
{
NamedMailbox namedMailbox = (NamedMailbox)address;
addresses.add(new Address(namedMailbox.getLocalPart() + "@"
+ namedMailbox.getDomain(), namedMailbox.getName()));
}
else if (address instanceof Mailbox)
org.apache.james.mime4j.dom.address.Address address = parsedList.get(i);
if (address instanceof Mailbox)
{
Mailbox mailbox = (Mailbox)address;
if (mailbox.getName() != null )
{
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain(), mailbox.getName()));
} else
{
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain()));
}
}
else
{
Log.e(K9.LOG_TAG, "Unknown address type from Mime4J: "
@ -160,7 +161,7 @@ public class Address
}
}
catch (ParseException pe)
catch (MimeException pe)
{
}
return addresses.toArray(EMPTY_ADDRESS_ARRAY);

View File

@ -8,8 +8,8 @@ import com.fsck.k9.mail.MessagingException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.apache.james.mime4j.decoder.Base64InputStream;
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
import org.apache.james.mime4j.util.CharsetUtil;

View File

@ -4,12 +4,16 @@ package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.store.UnavailableStorageException;
import org.apache.james.mime4j.BodyDescriptor;
import org.apache.james.mime4j.ContentHandler;
import org.apache.james.mime4j.EOLConvertingInputStream;
import org.apache.james.mime4j.MimeStreamParser;
import org.apache.james.mime4j.field.DateTimeField;
import org.apache.james.mime4j.field.Field;
import org.apache.james.mime4j.stream.BodyDescriptor;
import org.apache.james.mime4j.stream.RawField;
import org.apache.james.mime4j.parser.ContentHandler;
import org.apache.james.mime4j.io.EOLConvertingInputStream;
import org.apache.james.mime4j.parser.MimeStreamParser;
import org.apache.james.mime4j.dom.field.DateTimeField;
import org.apache.james.mime4j.dom.field.Field;
import org.apache.james.mime4j.field.DefaultFieldParser;
import org.apache.james.mime4j.MimeException;
import java.io.*;
import java.text.SimpleDateFormat;
@ -74,7 +78,12 @@ public class MimeMessage extends Message
MimeStreamParser parser = new MimeStreamParser();
parser.setContentHandler(new MimeMessageBuilder());
parser.parse(new EOLConvertingInputStream(in));
try {
parser.parse(new EOLConvertingInputStream(in));
} catch (MimeException me) {
throw new Error(me);
}
}
@Override
@ -84,7 +93,7 @@ public class MimeMessage extends Message
{
try
{
DateTimeField field = (DateTimeField)Field.parse("Date: "
DateTimeField field = (DateTimeField)DefaultFieldParser.parse("Date: "
+ MimeUtility.unfoldAndDecode(getFirstHeader("Date")));
mSentDate = field.getDate();
}
@ -565,6 +574,18 @@ public class MimeMessage extends Message
expect(Part.class);
}
public void field(RawField field)
{
try {
Field parsedField = DefaultFieldParser.parse(field.getRaw(), null);
((Part)stack.peek()).addHeader(parsedField.getName(), field.getBody().trim());
} catch (MessagingException me) {
throw new Error(me);
} catch (MimeException me) {
throw new Error(me);
}
}
public void field(String fieldData)
{
expect(Part.class);

View File

@ -5,8 +5,8 @@ import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.*;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.decoder.Base64InputStream;
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
import java.io.IOException;
import java.io.InputStream;

View File

@ -1,113 +0,0 @@
/****************************************************************
* 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;
import java.io.IOException;
import java.io.InputStream;
/**
* Abstract <code>ContentHandler</code> with default implementations of all
* the methods of the <code>ContentHandler</code> interface.
*
* The default is to todo nothing.
*
*
* @version $Id: AbstractContentHandler.java,v 1.3 2004/10/02 12:41:10 ntherning Exp $
*/
public abstract class AbstractContentHandler implements ContentHandler {
/**
* @see org.apache.james.mime4j.ContentHandler#endMultipart()
*/
public void endMultipart() {
}
/**
* @see org.apache.james.mime4j.ContentHandler#startMultipart(org.apache.james.mime4j.BodyDescriptor)
*/
public void startMultipart(BodyDescriptor bd) {
}
/**
* @see org.apache.james.mime4j.ContentHandler#body(org.apache.james.mime4j.BodyDescriptor, java.io.InputStream)
*/
public void body(BodyDescriptor bd, InputStream is) throws IOException {
}
/**
* @see org.apache.james.mime4j.ContentHandler#endBodyPart()
*/
public void endBodyPart() {
}
/**
* @see org.apache.james.mime4j.ContentHandler#endHeader()
*/
public void endHeader() {
}
/**
* @see org.apache.james.mime4j.ContentHandler#endMessage()
*/
public void endMessage() {
}
/**
* @see org.apache.james.mime4j.ContentHandler#epilogue(java.io.InputStream)
*/
public void epilogue(InputStream is) throws IOException {
}
/**
* @see org.apache.james.mime4j.ContentHandler#field(java.lang.String)
*/
public void field(String fieldData) {
}
/**
* @see org.apache.james.mime4j.ContentHandler#preamble(java.io.InputStream)
*/
public void preamble(InputStream is) throws IOException {
}
/**
* @see org.apache.james.mime4j.ContentHandler#startBodyPart()
*/
public void startBodyPart() {
}
/**
* @see org.apache.james.mime4j.ContentHandler#startHeader()
*/
public void startHeader() {
}
/**
* @see org.apache.james.mime4j.ContentHandler#startMessage()
*/
public void startMessage() {
}
/**
* @see org.apache.james.mime4j.ContentHandler#raw(java.io.InputStream)
*/
public void raw(InputStream is) throws IOException {
}
}

View File

@ -1,129 +0,0 @@
/****************************************************************
* 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;
import java.io.InputStream;
import java.io.IOException;
/**
* InputStream that shields its underlying input stream from
* being closed.
*
*
* @version $Id: CloseShieldInputStream.java,v 1.2 2004/10/02 12:41:10 ntherning Exp $
*/
public class CloseShieldInputStream extends InputStream {
/**
* Underlying InputStream
*/
private InputStream is;
public CloseShieldInputStream(InputStream is) {
this.is = is;
}
public InputStream getUnderlyingStream() {
return is;
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
checkIfClosed();
return is.read();
}
/**
* @see java.io.InputStream#available()
*/
public int available() throws IOException {
checkIfClosed();
return is.available();
}
/**
* Set the underlying InputStream to null
*/
public void close() throws IOException {
is = null;
}
/**
* @see java.io.FilterInputStream#reset()
*/
public synchronized void reset() throws IOException {
checkIfClosed();
is.reset();
}
/**
* @see java.io.FilterInputStream#markSupported()
*/
public boolean markSupported() {
if (is == null)
return false;
return is.markSupported();
}
/**
* @see java.io.FilterInputStream#mark(int)
*/
public synchronized void mark(int readlimit) {
if (is != null)
is.mark(readlimit);
}
/**
* @see java.io.FilterInputStream#skip(long)
*/
public long skip(long n) throws IOException {
checkIfClosed();
return is.skip(n);
}
/**
* @see java.io.FilterInputStream#read(byte[])
*/
public int read(byte b[]) throws IOException {
checkIfClosed();
return is.read(b);
}
/**
* @see java.io.FilterInputStream#read(byte[], int, int)
*/
public int read(byte b[], int off, int len) throws IOException {
checkIfClosed();
return is.read(b, off, len);
}
/**
* Check if the underlying InputStream is null. If so throw an Exception
*
* @throws IOException if the underlying InputStream is null
*/
private void checkIfClosed() throws IOException {
if (is == null)
throw new IOException("Stream is closed");
}
}

View File

@ -1,184 +0,0 @@
/****************************************************************
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
/**
* Stream that constrains itself to a single MIME body part.
* After the stream ends (i.e. read() returns -1) {@link #hasMoreParts()}
* can be used to determine if a final boundary has been seen or not.
* If {@link #parentEOF()} is <code>true</code> an unexpected end of stream
* has been detected in the parent stream.
*
*
*
* @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $
*/
public class MimeBoundaryInputStream extends InputStream {
private PushbackInputStream s = null;
private byte[] boundary = null;
private boolean first = true;
private boolean eof = false;
private boolean parenteof = false;
private boolean moreParts = true;
/**
* Creates a new MimeBoundaryInputStream.
* @param s The underlying stream.
* @param boundary Boundary string (not including leading hyphens).
*/
public MimeBoundaryInputStream(InputStream s, String boundary)
throws IOException {
this.s = new PushbackInputStream(s, boundary.length() + 4);
boundary = "--" + boundary;
this.boundary = new byte[boundary.length()];
for (int i = 0; i < this.boundary.length; i++) {
this.boundary[i] = (byte) boundary.charAt(i);
}
/*
* By reading one byte we will update moreParts to be as expected
* before any bytes have been read.
*/
int b = read();
if (b != -1) {
this.s.unread(b);
}
}
/**
* Closes the underlying stream.
*
* @throws IOException on I/O errors.
*/
public void close() throws IOException {
s.close();
}
/**
* Determines if the underlying stream has more parts (this stream has
* not seen an end boundary).
*
* @return <code>true</code> if there are more parts in the underlying
* stream, <code>false</code> otherwise.
*/
public boolean hasMoreParts() {
return moreParts;
}
/**
* Determines if the parent stream has reached EOF
*
* @return <code>true</code> if EOF has been reached for the parent stream,
* <code>false</code> otherwise.
*/
public boolean parentEOF() {
return parenteof;
}
/**
* Consumes all unread bytes of this stream. After a call to this method
* this stream will have reached EOF.
*
* @throws IOException on I/O errors.
*/
public void consume() throws IOException {
while (read() != -1) {
}
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
if (eof) {
return -1;
}
if (first) {
first = false;
if (matchBoundary()) {
return -1;
}
}
int b1 = s.read();
int b2 = s.read();
if (b1 == '\r' && b2 == '\n') {
if (matchBoundary()) {
return -1;
}
}
if (b2 != -1) {
s.unread(b2);
}
parenteof = b1 == -1;
eof = parenteof;
return b1;
}
private boolean matchBoundary() throws IOException {
for (int i = 0; i < boundary.length; i++) {
int b = s.read();
if (b != boundary[i]) {
if (b != -1) {
s.unread(b);
}
for (int j = i - 1; j >= 0; j--) {
s.unread(boundary[j]);
}
return false;
}
}
/*
* We have a match. Is it an end boundary?
*/
int prev = s.read();
int curr = s.read();
moreParts = !(prev == '-' && curr == '-');
do {
if (curr == '\n' && prev == '\r') {
break;
}
prev = curr;
} while ((curr = s.read()) != -1);
if (curr == -1) {
moreParts = false;
parenteof = true;
}
eof = true;
return true;
}
}

View File

@ -0,0 +1,65 @@
/****************************************************************
* 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;
/**
* MIME processing exception.
* <p>
* A <code>MimeException</code> may be thrown by a {@link org.apache.james.mime4j.parser.ContentHandler} to
* indicate that it has failed to process a message event and that no further
* events should be generated.
* <p>
* <code>MimeException</code> also gets thrown by the parser to indicate MIME
* protocol errors, e.g. if a message boundary is too long or a header field
* cannot be parsed.
*/
public class MimeException extends Exception {
private static final long serialVersionUID = 8352821278714188542L;
/**
* Constructs a new MIME exception with the specified detail message.
*
* @param message detail message
*/
public MimeException(String message) {
super(message);
}
/**
* Constructs a MIME exception with the specified cause.
*
* @param cause cause of the exception
*/
public MimeException(Throwable cause) {
super(cause);
}
/**
* Constructs a MIME exception with the specified detail message and cause.
*
* @param message detail message
* @param cause cause of the exception
*/
public MimeException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -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;
import java.io.IOException;
/**
* A wrapper class based on {@link IOException} for MIME protocol exceptions.
* <p>
* This exception is used to signal a <code>MimeException</code> in methods
* that only permit <code>IOException</code> to be thrown.
* <p>
* The cause of a <code>MimeIOException</code> is always a
* <code>MimeException</code> therefore.
*/
public class MimeIOException extends IOException {
private static final long serialVersionUID = 5393613459533735409L;
/**
* Constructs an IO exception based on {@link MimeException}.
*
* @param cause the cause.
*/
public MimeIOException(MimeException cause) {
super(cause == null ? null : cause.getMessage());
initCause(cause);
}
/**
* Returns the <code>MimeException</code> that caused this
* <code>MimeIOException</code>.
*
* @return the cause of this <code>MimeIOException</code>.
*/
@Override
public MimeException getCause() {
return (MimeException) super.getCause();
}
}

View File

@ -1,320 +0,0 @@
/****************************************************************
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.util.BitSet;
import java.util.LinkedList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.james.mime4j.decoder.Base64InputStream;
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
/**
* <p>
* Parses MIME (or RFC822) message streams of bytes or characters and reports
* parsing events to a <code>ContentHandler</code> instance.
* </p>
* <p>
* Typical usage:<br/>
* <pre>
* ContentHandler handler = new MyHandler();
* MimeStreamParser parser = new MimeStreamParser();
* parser.setContentHandler(handler);
* parser.parse(new BufferedInputStream(new FileInputStream("mime.msg")));
* </pre>
* <strong>NOTE:</strong> All lines must end with CRLF
* (<code>\r\n</code>). If you are unsure of the line endings in your stream
* you should wrap it in a {@link org.apache.james.mime4j.EOLConvertingInputStream} instance.
*
*
* @version $Id: MimeStreamParser.java,v 1.8 2005/02/11 10:12:02 ntherning Exp $
*/
public class MimeStreamParser {
private static final Log log = LogFactory.getLog(MimeStreamParser.class);
private static BitSet fieldChars = null;
private RootInputStream rootStream = null;
private LinkedList bodyDescriptors = new LinkedList();
private ContentHandler handler = null;
private boolean raw = false;
static {
fieldChars = new BitSet();
for (int i = 0x21; i <= 0x39; i++) {
fieldChars.set(i);
}
for (int i = 0x3b; i <= 0x7e; i++) {
fieldChars.set(i);
}
}
/**
* Creates a new <code>MimeStreamParser</code> instance.
*/
public MimeStreamParser() {
}
/**
* Parses a stream of bytes containing a MIME message.
*
* @param is the stream to parse.
* @throws IOException on I/O errors.
*/
public void parse(InputStream is) throws IOException {
rootStream = new RootInputStream(is);
parseMessage(rootStream);
}
/**
* Determines if this parser is currently in raw mode.
*
* @return <code>true</code> if in raw mode, <code>false</code>
* otherwise.
* @see #setRaw(boolean)
*/
public boolean isRaw() {
return raw;
}
/**
* Enables or disables raw mode. In raw mode all future entities
* (messages or body parts) in the stream will be reported to the
* {@link ContentHandler#raw(InputStream)} handler method only.
* The stream will contain the entire unparsed entity contents
* including header fields and whatever is in the body.
*
* @param raw <code>true</code> enables raw mode, <code>false</code>
* disables it.
*/
public void setRaw(boolean raw) {
this.raw = raw;
}
/**
* Finishes the parsing and stops reading lines.
* NOTE: No more lines will be parsed but the parser
* will still call
* {@link ContentHandler#endMultipart()},
* {@link ContentHandler#endBodyPart()},
* {@link ContentHandler#endMessage()}, etc to match previous calls
* to
* {@link ContentHandler#startMultipart(BodyDescriptor)},
* {@link ContentHandler#startBodyPart()},
* {@link ContentHandler#startMessage()}, etc.
*/
public void stop() {
rootStream.truncate();
}
/**
* Parses an entity which consists of a header followed by a body containing
* arbitrary data, body parts or an embedded message.
*
* @param is the stream to parse.
* @throws IOException on I/O errors.
*/
private void parseEntity(InputStream is) throws IOException {
BodyDescriptor bd = parseHeader(is);
if (bd.isMultipart()) {
bodyDescriptors.addFirst(bd);
handler.startMultipart(bd);
MimeBoundaryInputStream tempIs =
new MimeBoundaryInputStream(is, bd.getBoundary());
handler.preamble(new CloseShieldInputStream(tempIs));
tempIs.consume();
while (tempIs.hasMoreParts()) {
tempIs = new MimeBoundaryInputStream(is, bd.getBoundary());
parseBodyPart(tempIs);
tempIs.consume();
if (tempIs.parentEOF()) {
if (log.isWarnEnabled()) {
log.warn("Line " + rootStream.getLineNumber()
+ ": Body part ended prematurely. "
+ "Higher level boundary detected or "
+ "EOF reached.");
}
break;
}
}
handler.epilogue(new CloseShieldInputStream(is));
handler.endMultipart();
bodyDescriptors.removeFirst();
} else if (bd.isMessage()) {
if (bd.isBase64Encoded()) {
log.warn("base64 encoded message/rfc822 detected");
is = new EOLConvertingInputStream(
new Base64InputStream(is));
} else if (bd.isQuotedPrintableEncoded()) {
log.warn("quoted-printable encoded message/rfc822 detected");
is = new EOLConvertingInputStream(
new QuotedPrintableInputStream(is));
}
bodyDescriptors.addFirst(bd);
parseMessage(is);
bodyDescriptors.removeFirst();
} else {
handler.body(bd, new CloseShieldInputStream(is));
}
/*
* Make sure the stream has been consumed.
*/
while (is.read() != -1) {
}
}
private void parseMessage(InputStream is) throws IOException {
if (raw) {
handler.raw(new CloseShieldInputStream(is));
} else {
handler.startMessage();
parseEntity(is);
handler.endMessage();
}
}
private void parseBodyPart(InputStream is) throws IOException {
if (raw) {
handler.raw(new CloseShieldInputStream(is));
} else {
handler.startBodyPart();
parseEntity(is);
handler.endBodyPart();
}
}
/**
* Parses a header.
*
* @param is the stream to parse.
* @return a <code>BodyDescriptor</code> describing the body following
* the header.
*/
private BodyDescriptor parseHeader(InputStream is) throws IOException {
BodyDescriptor bd = new BodyDescriptor(bodyDescriptors.isEmpty()
? null : (BodyDescriptor) bodyDescriptors.getFirst());
handler.startHeader();
int lineNumber = rootStream.getLineNumber();
StringBuffer sb = new StringBuffer();
int curr = 0;
int prev = 0;
while ((curr = is.read()) != -1) {
if (curr == '\n' && (prev == '\n' || prev == 0)) {
/*
* [\r]\n[\r]\n or an immediate \r\n have been seen.
*/
sb.deleteCharAt(sb.length() - 1);
break;
}
sb.append((char) curr);
prev = curr == '\r' ? prev : curr;
}
if (curr == -1 && log.isWarnEnabled()) {
log.warn("Line " + rootStream.getLineNumber()
+ ": Unexpected end of headers detected. "
+ "Boundary detected in header or EOF reached.");
}
int start = 0;
int pos = 0;
int startLineNumber = lineNumber;
while (pos < sb.length()) {
while (pos < sb.length() && sb.charAt(pos) != '\r') {
pos++;
}
if (pos < sb.length() - 1 && sb.charAt(pos + 1) != '\n') {
pos++;
continue;
}
if (pos >= sb.length() - 2 || fieldChars.get(sb.charAt(pos + 2))) {
/*
* field should be the complete field data excluding the
* trailing \r\n.
*/
String field = sb.substring(start, pos);
start = pos + 2;
/*
* Check for a valid field.
*/
int index = field.indexOf(':');
boolean valid = false;
if (index != -1 && fieldChars.get(field.charAt(0))) {
valid = true;
String fieldName = field.substring(0, index).trim();
for (int i = 0; i < fieldName.length(); i++) {
if (!fieldChars.get(fieldName.charAt(i))) {
valid = false;
break;
}
}
if (valid) {
handler.field(field);
bd.addField(fieldName, field.substring(index + 1));
}
}
if (!valid && log.isWarnEnabled()) {
log.warn("Line " + startLineNumber
+ ": Ignoring invalid field: '" + field.trim() + "'");
}
startLineNumber = lineNumber;
}
pos += 2;
lineNumber++;
}
handler.endHeader();
return bd;
}
/**
* Sets the <code>ContentHandler</code> to use when reporting
* parsing events.
*
* @param h the <code>ContentHandler</code>.
*/
public void setContentHandler(ContentHandler h) {
this.handler = h;
}
}

View File

@ -1,111 +0,0 @@
/****************************************************************
* 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;
import java.io.IOException;
import java.io.InputStream;
/**
* <code>InputStream</code> used by the parser to wrap the original user
* supplied stream. This stream keeps track of the current line number and
* can also be truncated. When truncated the stream will appear to have
* reached end of file. This is used by the parser's
* {@link org.apache.james.mime4j.MimeStreamParser#stop()} method.
*
*
* @version $Id: RootInputStream.java,v 1.2 2004/10/02 12:41:10 ntherning Exp $
*/
class RootInputStream extends InputStream {
private InputStream is = null;
private int lineNumber = 1;
private int prev = -1;
private boolean truncated = false;
/**
* Creates a new <code>RootInputStream</code>.
*
* @param in the stream to read from.
*/
public RootInputStream(InputStream is) {
this.is = is;
}
/**
* Gets the current line number starting at 1
* (the number of <code>\r\n</code> read so far plus 1).
*
* @return the current line number.
*/
public int getLineNumber() {
return lineNumber;
}
/**
* Truncates this <code>InputStream</code>. After this call any
* call to {@link #read()}, {@link #read(byte[]) or
* {@link #read(byte[], int, int)} will return
* -1 as if end-of-file had been reached.
*/
public void truncate() {
this.truncated = true;
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
if (truncated) {
return -1;
}
int b = is.read();
if (prev == '\r' && b == '\n') {
lineNumber++;
}
prev = b;
return b;
}
/**
*
* @see java.io.InputStream#read(byte[], int, int)
*/
public int read(byte[] b, int off, int len) throws IOException {
if (truncated) {
return -1;
}
int n = is.read(b, off, len);
for (int i = off; i < off + n; i++) {
if (prev == '\r' && b[i] == '\n') {
lineNumber++;
}
prev = b[i];
}
return n;
}
/**
* @see java.io.InputStream#read(byte[])
*/
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
}

View File

@ -1,100 +0,0 @@
/****************************************************************
* 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;
import org.apache.james.mime4j.decoder.Base64InputStream;
import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
import org.apache.james.mime4j.field.Field;
import org.apache.james.mime4j.message.Header;
import java.io.InputStream;
import java.io.IOException;
/**
* Abstract implementation of ContentHandler that automates common
* tasks. Currently performs header parsing and applies content-transfer
* decoding to body parts.
*
*
*/
public abstract class SimpleContentHandler extends AbstractContentHandler {
/**
* Called after headers are parsed.
*/
public abstract void headers(Header header);
/**
* Called when the body of a discrete (non-multipart) entity is encountered.
* @param bd encapsulates the values (either read from the
* message stream or, if not present, determined implictly
* as described in the
* MIME rfc:s) of the <code>Content-Type</code> and
* <code>Content-Transfer-Encoding</code> header fields.
* @param is the contents of the body. Base64 or quoted-printable
* decoding will be applied transparently.
* @throws IOException should be thrown on I/O errors.
*/
public abstract void bodyDecoded(BodyDescriptor bd, InputStream is) throws IOException;
/* Implement introduced callbacks. */
private Header currHeader;
/**
* @see org.apache.james.mime4j.AbstractContentHandler#startHeader()
*/
public final void startHeader() {
currHeader = new Header();
}
/**
* @see org.apache.james.mime4j.AbstractContentHandler#field(java.lang.String)
*/
public final void field(String fieldData) {
currHeader.addField(Field.parse(fieldData));
}
/**
* @see org.apache.james.mime4j.AbstractContentHandler#endHeader()
*/
public final void endHeader() {
Header tmp = currHeader;
currHeader = null;
headers(tmp);
}
/**
* @see org.apache.james.mime4j.AbstractContentHandler#body(org.apache.james.mime4j.BodyDescriptor, java.io.InputStream)
*/
public final void body(BodyDescriptor bd, InputStream is) throws IOException {
if (bd.isBase64Encoded()) {
bodyDecoded(bd, new Base64InputStream(is));
}
else if (bd.isQuotedPrintableEncoded()) {
bodyDecoded(bd, new QuotedPrintableInputStream(is));
}
else {
bodyDecoded(bd, is);
}
}
}

View File

@ -0,0 +1,286 @@
/****************************************************************
* 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.codec;
import java.io.IOException;
import java.io.InputStream;
import org.apache.james.mime4j.util.ByteArrayBuffer;
/**
* Performs Base-64 decoding on an underlying stream.
*/
public class Base64InputStream extends InputStream {
private static final int ENCODED_BUFFER_SIZE = 1536;
private static final int[] BASE64_DECODE = new int[256];
static {
for (int i = 0; i < 256; i++)
BASE64_DECODE[i] = -1;
for (int i = 0; i < Base64OutputStream.BASE64_TABLE.length; i++)
BASE64_DECODE[Base64OutputStream.BASE64_TABLE[i] & 0xff] = i;
}
private static final byte BASE64_PAD = '=';
private static final int EOF = -1;
private final byte[] singleByte = new byte[1];
private final InputStream in;
private final byte[] encoded;
private final ByteArrayBuffer decodedBuf;
private int position = 0; // current index into encoded buffer
private int size = 0; // current size of encoded buffer
private boolean closed = false;
private boolean eof; // end of file or pad character reached
private final DecodeMonitor monitor;
public Base64InputStream(InputStream in, DecodeMonitor monitor) {
this(ENCODED_BUFFER_SIZE, in, monitor);
}
protected Base64InputStream(int bufsize, InputStream in, DecodeMonitor monitor) {
if (in == null)
throw new IllegalArgumentException();
this.encoded = new byte[bufsize];
this.decodedBuf = new ByteArrayBuffer(512);
this.in = in;
this.monitor = monitor;
}
public Base64InputStream(InputStream in) {
this(in, false);
}
public Base64InputStream(InputStream in, boolean strict) {
this(ENCODED_BUFFER_SIZE, in, strict ? DecodeMonitor.STRICT : DecodeMonitor.SILENT);
}
@Override
public int read() throws IOException {
if (closed)
throw new IOException("Stream has been closed");
while (true) {
int bytes = read0(singleByte, 0, 1);
if (bytes == EOF)
return EOF;
if (bytes == 1)
return singleByte[0] & 0xff;
}
}
@Override
public int read(byte[] buffer) throws IOException {
if (closed)
throw new IOException("Stream has been closed");
if (buffer == null)
throw new NullPointerException();
if (buffer.length == 0)
return 0;
return read0(buffer, 0, buffer.length);
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
if (closed)
throw new IOException("Stream has been closed");
if (buffer == null)
throw new NullPointerException();
if (offset < 0 || length < 0 || offset + length > buffer.length)
throw new IndexOutOfBoundsException();
if (length == 0)
return 0;
return read0(buffer, offset, length);
}
@Override
public void close() throws IOException {
if (closed)
return;
closed = true;
}
private int read0(final byte[] buffer, final int off, final int len) throws IOException {
int from = off;
int to = off + len;
int index = off;
// check if a previous invocation left decoded content
if (decodedBuf.length() > 0) {
int chunk = Math.min(decodedBuf.length(), len);
System.arraycopy(decodedBuf.buffer(), 0, buffer, index, chunk);
decodedBuf.remove(0, chunk);
index += chunk;
}
// eof or pad reached?
if (eof)
return index == from ? EOF : index - from;
// decode into given buffer
int data = 0; // holds decoded data; up to four sextets
int sextets = 0; // number of sextets
while (index < to) {
// make sure buffer not empty
while (position == size) {
int n = in.read(encoded, 0, encoded.length);
if (n == EOF) {
eof = true;
if (sextets != 0) {
// error in encoded data
handleUnexpectedEof(sextets);
}
return index == from ? EOF : index - from;
} else if (n > 0) {
position = 0;
size = n;
} else {
assert n == 0;
}
}
// decode buffer
while (position < size && index < to) {
int value = encoded[position++] & 0xff;
if (value == BASE64_PAD) {
index = decodePad(data, sextets, buffer, index, to);
return index - from;
}
int decoded = BASE64_DECODE[value];
if (decoded < 0) { // -1: not a base64 char
if (value != 0x0D && value != 0x0A && value != 0x20) {
if (monitor.warn("Unexpected base64 byte: "+(byte) value, "ignoring."))
throw new IOException("Unexpected base64 byte");
}
continue;
}
data = (data << 6) | decoded;
sextets++;
if (sextets == 4) {
sextets = 0;
byte b1 = (byte) (data >>> 16);
byte b2 = (byte) (data >>> 8);
byte b3 = (byte) data;
if (index < to - 2) {
buffer[index++] = b1;
buffer[index++] = b2;
buffer[index++] = b3;
} else {
if (index < to - 1) {
buffer[index++] = b1;
buffer[index++] = b2;
decodedBuf.append(b3);
} else if (index < to) {
buffer[index++] = b1;
decodedBuf.append(b2);
decodedBuf.append(b3);
} else {
decodedBuf.append(b1);
decodedBuf.append(b2);
decodedBuf.append(b3);
}
assert index == to;
return to - from;
}
}
}
}
assert sextets == 0;
assert index == to;
return to - from;
}
private int decodePad(int data, int sextets, final byte[] buffer,
int index, final int end) throws IOException {
eof = true;
if (sextets == 2) {
// one byte encoded as "XY=="
byte b = (byte) (data >>> 4);
if (index < end) {
buffer[index++] = b;
} else {
decodedBuf.append(b);
}
} else if (sextets == 3) {
// two bytes encoded as "XYZ="
byte b1 = (byte) (data >>> 10);
byte b2 = (byte) ((data >>> 2) & 0xFF);
if (index < end - 1) {
buffer[index++] = b1;
buffer[index++] = b2;
} else if (index < end) {
buffer[index++] = b1;
decodedBuf.append(b2);
} else {
decodedBuf.append(b1);
decodedBuf.append(b2);
}
} else {
// error in encoded data
handleUnexpecedPad(sextets);
}
return index;
}
private void handleUnexpectedEof(int sextets) throws IOException {
if (monitor.warn("Unexpected end of BASE64 stream", "dropping " + sextets + " sextet(s)"))
throw new IOException("Unexpected end of BASE64 stream");
}
private void handleUnexpecedPad(int sextets) throws IOException {
if (monitor.warn("Unexpected padding character", "dropping " + sextets + " sextet(s)"))
throw new IOException("Unexpected padding character");
}
}

View File

@ -0,0 +1,321 @@
/****************************************************************
* 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.codec;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
/**
* This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite>
* from RFC 2045 <cite>Multipurpose Internet Mail Extensions (MIME) Part One:
* Format of Internet Message Bodies</cite> by Freed and Borenstein.
* <p>
* Code is based on Base64 and Base64OutputStream code from Commons-Codec 1.4.
*
* @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
*/
public class Base64OutputStream extends FilterOutputStream {
// Default line length per RFC 2045 section 6.8.
private static final int DEFAULT_LINE_LENGTH = 76;
// CRLF line separator per RFC 2045 section 2.1.
private static final byte[] CRLF_SEPARATOR = { '\r', '\n' };
// This array is a lookup table that translates 6-bit positive integer index
// values into their "Base64 Alphabet" equivalents as specified in Table 1
// of RFC 2045.
static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', '+', '/' };
// Byte used to pad output.
private static final byte BASE64_PAD = '=';
// This set contains all base64 characters including the pad character. Used
// solely to check if a line separator contains any of these characters.
private static final Set<Byte> BASE64_CHARS = new HashSet<Byte>();
static {
for (byte b : BASE64_TABLE) {
BASE64_CHARS.add(b);
}
BASE64_CHARS.add(BASE64_PAD);
}
// Mask used to extract 6 bits
private static final int MASK_6BITS = 0x3f;
private static final int ENCODED_BUFFER_SIZE = 2048;
private final byte[] singleByte = new byte[1];
private final int lineLength;
private final byte[] lineSeparator;
private boolean closed = false;
private final byte[] encoded;
private int position = 0;
private int data = 0;
private int modulus = 0;
private int linePosition = 0;
/**
* Creates a <code>Base64OutputStream</code> that writes the encoded data
* to the given output stream using the default line length (76) and line
* separator (CRLF).
*
* @param out
* underlying output stream.
*/
public Base64OutputStream(OutputStream out) {
this(out, DEFAULT_LINE_LENGTH, CRLF_SEPARATOR);
}
/**
* Creates a <code>Base64OutputStream</code> that writes the encoded data
* to the given output stream using the given line length and the default
* line separator (CRLF).
* <p>
* The given line length will be rounded up to the nearest multiple of 4. If
* the line length is zero then the output will not be split into lines.
*
* @param out
* underlying output stream.
* @param lineLength
* desired line length.
*/
public Base64OutputStream(OutputStream out, int lineLength) {
this(out, lineLength, CRLF_SEPARATOR);
}
/**
* Creates a <code>Base64OutputStream</code> that writes the encoded data
* to the given output stream using the given line length and line
* separator.
* <p>
* The given line length will be rounded up to the nearest multiple of 4. If
* the line length is zero then the output will not be split into lines and
* the line separator is ignored.
* <p>
* The line separator must not include characters from the BASE64 alphabet
* (including the padding character <code>=</code>).
*
* @param out
* underlying output stream.
* @param lineLength
* desired line length.
* @param lineSeparator
* line separator to use.
*/
public Base64OutputStream(OutputStream out, int lineLength,
byte[] lineSeparator) {
super(out);
if (out == null)
throw new IllegalArgumentException();
if (lineLength < 0)
throw new IllegalArgumentException();
checkLineSeparator(lineSeparator);
this.lineLength = lineLength;
this.lineSeparator = new byte[lineSeparator.length];
System.arraycopy(lineSeparator, 0, this.lineSeparator, 0,
lineSeparator.length);
this.encoded = new byte[ENCODED_BUFFER_SIZE];
}
@Override
public final void write(final int b) throws IOException {
if (closed)
throw new IOException("Base64OutputStream has been closed");
singleByte[0] = (byte) b;
write0(singleByte, 0, 1);
}
@Override
public final void write(final byte[] buffer) throws IOException {
if (closed)
throw new IOException("Base64OutputStream has been closed");
if (buffer == null)
throw new NullPointerException();
if (buffer.length == 0)
return;
write0(buffer, 0, buffer.length);
}
@Override
public final void write(final byte[] buffer, final int offset,
final int length) throws IOException {
if (closed)
throw new IOException("Base64OutputStream has been closed");
if (buffer == null)
throw new NullPointerException();
if (offset < 0 || length < 0 || offset + length > buffer.length)
throw new IndexOutOfBoundsException();
if (length == 0)
return;
write0(buffer, offset, offset + length);
}
@Override
public void flush() throws IOException {
if (closed)
throw new IOException("Base64OutputStream has been closed");
flush0();
}
@Override
public void close() throws IOException {
if (closed)
return;
closed = true;
close0();
}
private void write0(final byte[] buffer, final int from, final int to)
throws IOException {
for (int i = from; i < to; i++) {
data = (data << 8) | (buffer[i] & 0xff);
if (++modulus == 3) {
modulus = 0;
// write line separator if necessary
if (lineLength > 0 && linePosition >= lineLength) {
// writeLineSeparator() inlined for performance reasons
linePosition = 0;
if (encoded.length - position < lineSeparator.length)
flush0();
for (byte ls : lineSeparator)
encoded[position++] = ls;
}
// encode data into 4 bytes
if (encoded.length - position < 4)
flush0();
encoded[position++] = BASE64_TABLE[(data >> 18) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data >> 12) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data >> 6) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[data & MASK_6BITS];
linePosition += 4;
}
}
}
private void flush0() throws IOException {
if (position > 0) {
out.write(encoded, 0, position);
position = 0;
}
}
private void close0() throws IOException {
if (modulus != 0)
writePad();
// write line separator at the end of the encoded data
if (lineLength > 0 && linePosition > 0) {
writeLineSeparator();
}
flush0();
}
private void writePad() throws IOException {
// write line separator if necessary
if (lineLength > 0 && linePosition >= lineLength) {
writeLineSeparator();
}
// encode data into 4 bytes
if (encoded.length - position < 4)
flush0();
if (modulus == 1) {
encoded[position++] = BASE64_TABLE[(data >> 2) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data << 4) & MASK_6BITS];
encoded[position++] = BASE64_PAD;
encoded[position++] = BASE64_PAD;
} else {
assert modulus == 2;
encoded[position++] = BASE64_TABLE[(data >> 10) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data >> 4) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data << 2) & MASK_6BITS];
encoded[position++] = BASE64_PAD;
}
linePosition += 4;
}
private void writeLineSeparator() throws IOException {
linePosition = 0;
if (encoded.length - position < lineSeparator.length)
flush0();
for (byte ls : lineSeparator)
encoded[position++] = ls;
}
private void checkLineSeparator(byte[] lineSeparator) {
if (lineSeparator.length > ENCODED_BUFFER_SIZE)
throw new IllegalArgumentException("line separator length exceeds "
+ ENCODED_BUFFER_SIZE);
for (byte b : lineSeparator) {
if (BASE64_CHARS.contains(b)) {
throw new IllegalArgumentException(
"line separator must not contain base64 character '"
+ (char) (b & 0xff) + "'");
}
}
}
}

View File

@ -0,0 +1,108 @@
/****************************************************************
* 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.codec;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Utility methods related to codecs.
*/
public class CodecUtil {
static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024;
/**
* Copies the contents of one stream to the other.
* @param in not null
* @param out not null
* @throws IOException
*/
public static void copy(final InputStream in, final OutputStream out) throws IOException {
final byte[] buffer = new byte[DEFAULT_ENCODING_BUFFER_SIZE];
int inputLength;
while (-1 != (inputLength = in.read(buffer))) {
out.write(buffer, 0, inputLength);
}
}
/**
* Encodes the given stream using Quoted-Printable.
* This assumes that stream is binary and therefore escapes
* all line endings.
* @param in not null
* @param out not null
* @throws IOException
*/
public static void encodeQuotedPrintableBinary(final InputStream in, final OutputStream out) throws IOException {
QuotedPrintableOutputStream qpOut = new QuotedPrintableOutputStream(out, true);
copy(in, qpOut);
qpOut.close();
}
/**
* Encodes the given stream using Quoted-Printable.
* This assumes that stream is text and therefore does not escape
* all line endings.
* @param in not null
* @param out not null
* @throws IOException
*/
public static void encodeQuotedPrintable(final InputStream in, final OutputStream out) throws IOException {
QuotedPrintableOutputStream qpOut = new QuotedPrintableOutputStream(out, false);
copy(in, qpOut);
qpOut.close();
}
/**
* Encodes the given stream using base64.
*
* @param in not null
* @param out not null
* @throws IOException if an I/O error occurs
*/
public static void encodeBase64(final InputStream in, final OutputStream out) throws IOException {
Base64OutputStream b64Out = new Base64OutputStream(out);
copy(in, b64Out);
b64Out.close();
}
/**
* Wraps the given stream in a Quoted-Printable encoder.
* @param out not null
* @return encoding outputstream
* @throws IOException
*/
public static OutputStream wrapQuotedPrintable(final OutputStream out, boolean binary) throws IOException {
return new QuotedPrintableOutputStream(out, binary);
}
/**
* Wraps the given stream in a Base64 encoder.
* @param out not null
* @return encoding outputstream
* @throws IOException
*/
public static OutputStream wrapBase64(final OutputStream out) throws IOException {
return new Base64OutputStream(out);
}
}

View File

@ -0,0 +1,65 @@
/****************************************************************
* 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.codec;
/**
* This class is used to drive how decoder/parser should deal with malformed
* and unexpected data.
*
* 2 basic implementations are provided:
* STRICT return "true" on any occourence.
* SILENT ignores any problem.
*
* @see org.apache.james.mime4j.field.LoggingMonitor for an example
* about logging malformations via Commons-logging.
*/
public class DecodeMonitor {
/**
* The STRICT monitor throws an exception on every event.
*/
public static final DecodeMonitor STRICT = new DecodeMonitor() {
@Override
public boolean warn(String error, String dropDesc) {
return true;
}
@Override
public boolean isListening() {
return true;
}
};
/**
* The SILENT monitor ignore requests.
*/
public static final DecodeMonitor SILENT = new DecodeMonitor();
public boolean warn(String error, String dropDesc) {
return false;
}
public boolean isListening() {
return false;
}
}

View File

@ -0,0 +1,260 @@
/****************************************************************
* 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.codec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.james.mime4j.util.CharsetUtil;
/**
* Static methods for decoding strings, byte arrays and encoded words.
*/
public class DecoderUtil {
private static final Pattern PATTERN_ENCODED_WORD = Pattern.compile(
"(.*?)=\\?([^\\?]+?)\\?(\\w)\\?([^\\?]+?)\\?=", Pattern.DOTALL);
/**
* Decodes a string containing quoted-printable encoded data.
*
* @param s the string to decode.
* @return the decoded bytes.
*/
private static byte[] decodeQuotedPrintable(String s, DecodeMonitor monitor) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] bytes = s.getBytes("US-ASCII");
QuotedPrintableInputStream is = new QuotedPrintableInputStream(
new ByteArrayInputStream(bytes), monitor);
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
} catch (IOException e) {
// This should never happen!
throw new IllegalStateException(e);
}
return baos.toByteArray();
}
/**
* Decodes a string containing base64 encoded data.
*
* @param s the string to decode.
* @param monitor
* @return the decoded bytes.
*/
private static byte[] decodeBase64(String s, DecodeMonitor monitor) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] bytes = s.getBytes("US-ASCII");
Base64InputStream is = new Base64InputStream(
new ByteArrayInputStream(bytes), monitor);
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
} catch (IOException e) {
// This should never happen!
throw new IllegalStateException(e);
}
return baos.toByteArray();
}
/**
* Decodes an encoded text encoded with the 'B' encoding (described in
* RFC 2047) found in a header field body.
*
* @param encodedText the encoded text to decode.
* @param charset the Java charset to use.
* @param monitor
* @return the decoded string.
* @throws UnsupportedEncodingException if the given Java charset isn't
* supported.
*/
static String decodeB(String encodedText, String charset, DecodeMonitor monitor)
throws UnsupportedEncodingException {
byte[] decodedBytes = decodeBase64(encodedText, monitor);
return new String(decodedBytes, charset);
}
/**
* Decodes an encoded text encoded with the 'Q' encoding (described in
* RFC 2047) found in a header field body.
*
* @param encodedText the encoded text to decode.
* @param charset the Java charset to use.
* @return the decoded string.
* @throws UnsupportedEncodingException if the given Java charset isn't
* supported.
*/
static String decodeQ(String encodedText, String charset, DecodeMonitor monitor)
throws UnsupportedEncodingException {
encodedText = replaceUnderscores(encodedText);
byte[] decodedBytes = decodeQuotedPrintable(encodedText, monitor);
return new String(decodedBytes, charset);
}
static String decodeEncodedWords(String body) {
return decodeEncodedWords(body, DecodeMonitor.SILENT);
}
/**
* Decodes a string containing encoded words as defined by RFC 2047. Encoded
* words have the form =?charset?enc?encoded-text?= where enc is either 'Q'
* or 'q' for quoted-printable and 'B' or 'b' for base64.
*
* @param body the string to decode
* @param monitor the DecodeMonitor to be used.
* @return the decoded string.
* @throws IllegalArgumentException only if the DecodeMonitor strategy throws it (Strict parsing)
*/
public static String decodeEncodedWords(String body, DecodeMonitor monitor) throws IllegalArgumentException {
int tailIndex = 0;
boolean lastMatchValid = false;
StringBuilder sb = new StringBuilder();
for (Matcher matcher = PATTERN_ENCODED_WORD.matcher(body); matcher.find();) {
String separator = matcher.group(1);
String mimeCharset = matcher.group(2);
String encoding = matcher.group(3);
String encodedText = matcher.group(4);
String decoded = null;
decoded = tryDecodeEncodedWord(mimeCharset, encoding, encodedText, monitor);
if (decoded == null) {
sb.append(matcher.group(0));
} else {
if (!lastMatchValid || !CharsetUtil.isWhitespace(separator)) {
sb.append(separator);
}
sb.append(decoded);
}
tailIndex = matcher.end();
lastMatchValid = decoded != null;
}
if (tailIndex == 0) {
return body;
} else {
sb.append(body.substring(tailIndex));
return sb.toString();
}
}
// return null on error
private static String tryDecodeEncodedWord(final String mimeCharset,
final String encoding, final String encodedText, DecodeMonitor monitor) throws IllegalArgumentException {
String charset = CharsetUtil.toJavaCharset(mimeCharset);
if (charset == null) {
monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded",
"Mime charser '", mimeCharset, "' doesn't have a corresponding Java charset");
return null;
} else if (!CharsetUtil.isDecodingSupported(charset)) {
monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded",
"Current JDK doesn't support decoding of charset '", charset,
"' - MIME charset '", mimeCharset, "' in encoded word");
return null;
}
if (encodedText.length() == 0) {
monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded",
"Missing encoded text in encoded word");
return null;
}
try {
if (encoding.equalsIgnoreCase("Q")) {
return DecoderUtil.decodeQ(encodedText, charset, monitor);
} else if (encoding.equalsIgnoreCase("B")) {
return DecoderUtil.decodeB(encodedText, charset, monitor);
} else {
monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded",
"Warning: Unknown encoding in encoded word");
return null;
}
} catch (UnsupportedEncodingException e) {
// should not happen because of isDecodingSupported check above
monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded",
"Unsupported encoding (", e.getMessage(), ") in encoded word");
return null;
} catch (RuntimeException e) {
monitor(monitor, mimeCharset, encoding, encodedText, "leaving word encoded",
"Could not decode (", e.getMessage(), ") encoded word");
return null;
}
}
private static void monitor(DecodeMonitor monitor, String mimeCharset, String encoding,
String encodedText, String dropDesc, String... strings) throws IllegalArgumentException {
if (monitor.isListening()) {
String encodedWord = recombine(mimeCharset, encoding, encodedText);
StringBuilder text = new StringBuilder();
for (String str : strings) {
text.append(str);
}
text.append(" (");
text.append(encodedWord);
text.append(")");
String exceptionDesc = text.toString();
if (monitor.warn(exceptionDesc, dropDesc))
throw new IllegalArgumentException(text.toString());
}
}
private static String recombine(final String mimeCharset,
final String encoding, final String encodedText) {
return "=?" + mimeCharset + "?" + encoding + "?" + encodedText + "?=";
}
// Replace _ with =20
private static String replaceUnderscores(String str) {
// probably faster than String#replace(CharSequence, CharSequence)
StringBuilder sb = new StringBuilder(128);
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '_') {
sb.append("=20");
} else {
sb.append(c);
}
}
return sb.toString();
}
}

View File

@ -26,31 +26,14 @@ import java.util.Locale;
import org.apache.james.mime4j.util.CharsetUtil;
/**
* ANDROID: THIS CLASS IS COPIED FROM A NEWER VERSION OF MIME4J
*/
/**
* Static methods for encoding header field values. This includes encoded-words
* as defined in <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC 2047</a>
* or display-names of an e-mail address, for example.
*
*/
public class EncoderUtil {
// This array is a lookup table that translates 6-bit positive integer index
// values into their "Base64 Alphabet" equivalents as specified in Table 1
// of RFC 2045.
// ANDROID: THIS TABLE IS COPIED FROM BASE64OUTPUTSTREAM
static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', '+', '/' };
// Byte used to pad output.
private static final byte BASE64_PAD = '=';
private static final byte[] BASE64_TABLE = Base64OutputStream.BASE64_TABLE;
private static final char BASE64_PAD = '=';
private static final BitSet Q_REGULAR_CHARS = initChars("=_?");
@ -374,14 +357,14 @@ public class EncoderUtil {
sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]);
sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]);
sb.append((char) BASE64_TABLE[data >> 6 & 0x3f]);
sb.append((char) BASE64_PAD);
sb.append(BASE64_PAD);
} else if (idx == end - 1) {
int data = (bytes[idx] & 0xff) << 16;
sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]);
sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]);
sb.append((char) BASE64_PAD);
sb.append((char) BASE64_PAD);
sb.append(BASE64_PAD);
sb.append(BASE64_PAD);
}
return sb.toString();
@ -518,14 +501,12 @@ public class EncoderUtil {
if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) {
return prefix + encodeB(bytes) + ENC_WORD_SUFFIX;
} else {
int splitOffset = text.offsetByCodePoints(text.length() / 2, -1);
String part1 = text.substring(0, splitOffset);
String part1 = text.substring(0, text.length() / 2);
byte[] bytes1 = encode(part1, charset);
String word1 = encodeB(prefix, part1, usedCharacters, charset,
bytes1);
String part2 = text.substring(splitOffset);
String part2 = text.substring(text.length() / 2);
byte[] bytes2 = encode(part2, charset);
String word2 = encodeB(prefix, part2, 0, charset, bytes2);
@ -546,14 +527,12 @@ public class EncoderUtil {
if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) {
return prefix + encodeQ(bytes, usage) + ENC_WORD_SUFFIX;
} else {
int splitOffset = text.offsetByCodePoints(text.length() / 2, -1);
String part1 = text.substring(0, splitOffset);
String part1 = text.substring(0, text.length() / 2);
byte[] bytes1 = encode(part1, charset);
String word1 = encodeQ(prefix, part1, usage, usedCharacters,
charset, bytes1);
String part2 = text.substring(splitOffset);
String part2 = text.substring(text.length() / 2);
byte[] bytes2 = encode(part2, charset);
String word2 = encodeQ(prefix, part2, usage, 0, charset, bytes2);

View File

@ -1,271 +0,0 @@
/****************************************************************
* 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.codec;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
// Taken from Apache Mime4j 0.6
final class QuotedPrintableEncoder
{
private static final byte TAB = 0x09;
private static final byte SPACE = 0x20;
private static final byte EQUALS = 0x3D;
private static final byte CR = 0x0D;
private static final byte LF = 0x0A;
private static final byte QUOTED_PRINTABLE_LAST_PLAIN = 0x7E;
private static final int QUOTED_PRINTABLE_MAX_LINE_LENGTH = 76;
private static final int QUOTED_PRINTABLE_OCTETS_PER_ESCAPE = 3;
private static final byte[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
private final byte[] inBuffer;
private final byte[] outBuffer;
private final boolean binary;
private boolean pendingSpace;
private boolean pendingTab;
private boolean pendingCR;
private int nextSoftBreak;
private int outputIndex;
private OutputStream out;
public QuotedPrintableEncoder(int bufferSize, boolean binary)
{
inBuffer = new byte[bufferSize];
outBuffer = new byte[3 * bufferSize];
outputIndex = 0;
nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
out = null;
this.binary = binary;
pendingSpace = false;
pendingTab = false;
pendingCR = false;
}
void initEncoding(final OutputStream out)
{
this.out = out;
pendingSpace = false;
pendingTab = false;
pendingCR = false;
nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
}
void encodeChunk(byte[] buffer, int off, int len) throws IOException
{
for (int inputIndex = off; inputIndex < len + off; inputIndex++)
{
encode(buffer[inputIndex]);
}
}
void completeEncoding() throws IOException
{
writePending();
flushOutput();
}
public void encode(final InputStream in, final OutputStream out)
throws IOException
{
initEncoding(out);
int inputLength;
while ((inputLength = in.read(inBuffer)) > -1)
{
encodeChunk(inBuffer, 0, inputLength);
}
completeEncoding();
}
private void writePending() throws IOException
{
if (pendingSpace)
{
plain(SPACE);
}
else if (pendingTab)
{
plain(TAB);
}
else if (pendingCR)
{
plain(CR);
}
clearPending();
}
private void clearPending() throws IOException
{
pendingSpace = false;
pendingTab = false;
pendingCR = false;
}
private void encode(byte next) throws IOException
{
if (next == LF)
{
if (binary)
{
writePending();
escape(next);
}
else
{
if (pendingCR)
{
// Expect either space or tab pending
// but not both
if (pendingSpace)
{
escape(SPACE);
}
else if (pendingTab)
{
escape(TAB);
}
lineBreak();
clearPending();
}
else
{
writePending();
plain(next);
}
}
}
else if (next == CR)
{
if (binary)
{
escape(next);
}
else
{
pendingCR = true;
}
}
else
{
writePending();
if (next == SPACE)
{
if (binary)
{
escape(next);
}
else
{
pendingSpace = true;
}
}
else if (next == TAB)
{
if (binary)
{
escape(next);
}
else
{
pendingTab = true;
}
}
else if (next < SPACE)
{
escape(next);
}
else if (next > QUOTED_PRINTABLE_LAST_PLAIN)
{
escape(next);
}
else if (next == EQUALS)
{
escape(next);
}
else
{
plain(next);
}
}
}
private void plain(byte next) throws IOException
{
if (--nextSoftBreak <= 1)
{
softBreak();
}
write(next);
}
private void escape(byte next) throws IOException
{
if (--nextSoftBreak <= QUOTED_PRINTABLE_OCTETS_PER_ESCAPE)
{
softBreak();
}
int nextUnsigned = next & 0xff;
write(EQUALS);
--nextSoftBreak;
write(HEX_DIGITS[nextUnsigned >> 4]);
--nextSoftBreak;
write(HEX_DIGITS[nextUnsigned % 0x10]);
}
private void write(byte next) throws IOException
{
outBuffer[outputIndex++] = next;
if (outputIndex >= outBuffer.length)
{
flushOutput();
}
}
private void softBreak() throws IOException
{
write(EQUALS);
lineBreak();
}
private void lineBreak() throws IOException
{
write(CR);
write(LF);
nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH;
}
void flushOutput() throws IOException
{
if (outputIndex < outBuffer.length)
{
out.write(outBuffer, 0, outputIndex);
}
else
{
out.write(outBuffer);
}
outputIndex = 0;
}
}

View File

@ -0,0 +1,309 @@
/****************************************************************
* 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.codec;
import java.io.IOException;
import java.io.InputStream;
import org.apache.james.mime4j.util.ByteArrayBuffer;
/**
* Performs Quoted-Printable decoding on an underlying stream.
*/
public class QuotedPrintableInputStream extends InputStream {
private static final int DEFAULT_BUFFER_SIZE = 1024 * 2;
private static final byte EQ = 0x3D;
private static final byte CR = 0x0D;
private static final byte LF = 0x0A;
private final byte[] singleByte = new byte[1];
private final InputStream in;
private final ByteArrayBuffer decodedBuf;
private final ByteArrayBuffer blanks;
private final byte[] encoded;
private int pos = 0; // current index into encoded buffer
private int limit = 0; // current size of encoded buffer
private boolean closed;
private final DecodeMonitor monitor;
public QuotedPrintableInputStream(final InputStream in, DecodeMonitor monitor) {
this(DEFAULT_BUFFER_SIZE, in, monitor);
}
protected QuotedPrintableInputStream(final int bufsize, final InputStream in, DecodeMonitor monitor) {
super();
this.in = in;
this.encoded = new byte[bufsize];
this.decodedBuf = new ByteArrayBuffer(512);
this.blanks = new ByteArrayBuffer(512);
this.closed = false;
this.monitor = monitor;
}
protected QuotedPrintableInputStream(final int bufsize, final InputStream in, boolean strict) {
this(bufsize, in, strict ? DecodeMonitor.STRICT : DecodeMonitor.SILENT);
}
public QuotedPrintableInputStream(final InputStream in, boolean strict) {
this(DEFAULT_BUFFER_SIZE, in, strict);
}
public QuotedPrintableInputStream(final InputStream in) {
this(in, false);
}
/**
* Terminates Quoted-Printable coded content. This method does NOT close
* the underlying input stream.
*
* @throws IOException on I/O errors.
*/
@Override
public void close() throws IOException {
closed = true;
}
private int fillBuffer() throws IOException {
// Compact buffer if needed
if (pos < limit) {
System.arraycopy(encoded, pos, encoded, 0, limit - pos);
limit -= pos;
pos = 0;
} else {
limit = 0;
pos = 0;
}
int capacity = encoded.length - limit;
if (capacity > 0) {
int bytesRead = in.read(encoded, limit, capacity);
if (bytesRead > 0) {
limit += bytesRead;
}
return bytesRead;
} else {
return 0;
}
}
private int getnext() {
if (pos < limit) {
byte b = encoded[pos];
pos++;
return b & 0xFF;
} else {
return -1;
}
}
private int peek(int i) {
if (pos + i < limit) {
return encoded[pos + i] & 0xFF;
} else {
return -1;
}
}
private int transfer(
final int b, final byte[] buffer, final int from, final int to, boolean keepblanks) throws IOException {
int index = from;
if (keepblanks && blanks.length() > 0) {
int chunk = Math.min(blanks.length(), to - index);
System.arraycopy(blanks.buffer(), 0, buffer, index, chunk);
index += chunk;
int remaining = blanks.length() - chunk;
if (remaining > 0) {
decodedBuf.append(blanks.buffer(), chunk, remaining);
}
blanks.clear();
} else if (blanks.length() > 0 && !keepblanks) {
StringBuilder sb = new StringBuilder(blanks.length() * 3);
for (int i = 0; i < blanks.length(); i++) sb.append(" "+blanks.byteAt(i));
if (monitor.warn("ignored blanks", sb.toString()))
throw new IOException("ignored blanks");
}
if (b != -1) {
if (index < to) {
buffer[index++] = (byte) b;
} else {
decodedBuf.append(b);
}
}
return index;
}
private int read0(final byte[] buffer, final int off, final int len) throws IOException {
boolean eof = false;
int from = off;
int to = off + len;
int index = off;
// check if a previous invocation left decoded content
if (decodedBuf.length() > 0) {
int chunk = Math.min(decodedBuf.length(), to - index);
System.arraycopy(decodedBuf.buffer(), 0, buffer, index, chunk);
decodedBuf.remove(0, chunk);
index += chunk;
}
while (index < to) {
if (limit - pos < 3) {
int bytesRead = fillBuffer();
eof = bytesRead == -1;
}
// end of stream?
if (limit - pos == 0 && eof) {
return index == from ? -1 : index - from;
}
boolean lastWasCR = false;
while (pos < limit && index < to) {
int b = encoded[pos++] & 0xFF;
if (lastWasCR && b != LF) {
if (monitor.warn("Found CR without LF", "Leaving it as is"))
throw new IOException("Found CR without LF");
index = transfer(CR, buffer, index, to, false);
} else if (!lastWasCR && b == LF) {
if (monitor.warn("Found LF without CR", "Translating to CRLF"))
throw new IOException("Found LF without CR");
}
if (b == CR) {
lastWasCR = true;
continue;
} else {
lastWasCR = false;
}
if (b == LF) {
// at end of line
if (blanks.length() == 0) {
index = transfer(CR, buffer, index, to, false);
index = transfer(LF, buffer, index, to, false);
} else {
if (blanks.byteAt(0) != EQ) {
// hard line break
index = transfer(CR, buffer, index, to, false);
index = transfer(LF, buffer, index, to, false);
}
}
blanks.clear();
} else if (b == EQ) {
if (limit - pos < 2 && !eof) {
// not enough buffered data
pos--;
break;
}
// found special char '='
int b2 = getnext();
if (b2 == EQ) {
index = transfer(b2, buffer, index, to, true);
// deal with '==\r\n' brokenness
int bb1 = peek(0);
int bb2 = peek(1);
if (bb1 == LF || (bb1 == CR && bb2 == LF)) {
monitor.warn("Unexpected ==EOL encountered", "== 0x"+bb1+" 0x"+bb2);
blanks.append(b2);
} else {
monitor.warn("Unexpected == encountered", "==");
}
} else if (Character.isWhitespace((char) b2)) {
// soft line break
index = transfer(-1, buffer, index, to, true);
if (b2 != LF) {
blanks.append(b);
blanks.append(b2);
}
} else {
int b3 = getnext();
int upper = convert(b2);
int lower = convert(b3);
if (upper < 0 || lower < 0) {
monitor.warn("Malformed encoded value encountered", "leaving "+((char) EQ)+((char) b2)+((char) b3)+" as is");
// TODO see MIME4J-160
index = transfer(EQ, buffer, index, to, true);
index = transfer(b2, buffer, index, to, false);
index = transfer(b3, buffer, index, to, false);
} else {
index = transfer((upper << 4) | lower, buffer, index, to, true);
}
}
} else if (Character.isWhitespace(b)) {
blanks.append(b);
} else {
index = transfer((int) b & 0xFF, buffer, index, to, true);
}
}
}
return to - from;
}
/**
* Converts '0' => 0, 'A' => 10, etc.
* @param c ASCII character value.
* @return Numeric value of hexadecimal character.
*/
private int convert(int c) {
if (c >= '0' && c <= '9') {
return (c - '0');
} else if (c >= 'A' && c <= 'F') {
return (0xA + (c - 'A'));
} else if (c >= 'a' && c <= 'f') {
return (0xA + (c - 'a'));
} else {
return -1;
}
}
@Override
public int read() throws IOException {
if (closed) {
throw new IOException("Stream has been closed");
}
for (;;) {
int bytes = read(singleByte, 0, 1);
if (bytes == -1) {
return -1;
}
if (bytes == 1) {
return singleByte[0] & 0xff;
}
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException("Stream has been closed");
}
return read0(b, off, len);
}
}

View File

@ -23,66 +23,211 @@ import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
// Taken from Apache Mime4j 0.6
/**
* Performs Quoted-Printable encoding on an underlying stream.
*/
public class QuotedPrintableOutputStream extends FilterOutputStream
{
public class QuotedPrintableOutputStream extends FilterOutputStream {
private static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024;
private static final int DEFAULT_BUFFER_SIZE = 1024 * 3;
private static final byte TB = 0x09;
private static final byte SP = 0x20;
private static final byte EQ = 0x3D;
private static final byte CR = 0x0D;
private static final byte LF = 0x0A;
private static final byte QUOTED_PRINTABLE_LAST_PLAIN = 0x7E;
private static final int QUOTED_PRINTABLE_MAX_LINE_LENGTH = 76;
private static final int QUOTED_PRINTABLE_OCTETS_PER_ESCAPE = 3;
private static final byte[] HEX_DIGITS = {
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
private final byte[] outBuffer;
private final boolean binary;
private boolean pendingSpace;
private boolean pendingTab;
private boolean pendingCR;
private int nextSoftBreak;
private int outputIndex;
private QuotedPrintableEncoder encoder;
private boolean closed = false;
public QuotedPrintableOutputStream(OutputStream out, boolean binary)
{
private byte[] singleByte = new byte[1];
public QuotedPrintableOutputStream(int bufsize, OutputStream out, boolean binary) {
super(out);
encoder = new QuotedPrintableEncoder(DEFAULT_ENCODING_BUFFER_SIZE, binary);
encoder.initEncoding(out);
this.outBuffer = new byte[bufsize];
this.binary = binary;
this.pendingSpace = false;
this.pendingTab = false;
this.pendingCR = false;
this.outputIndex = 0;
this.nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH + 1;
}
public QuotedPrintableOutputStream(OutputStream out, boolean binary) {
this(DEFAULT_BUFFER_SIZE, out, binary);
}
private void encodeChunk(byte[] buffer, int off, int len) throws IOException {
for (int inputIndex = off; inputIndex < len + off; inputIndex++) {
encode(buffer[inputIndex]);
}
}
private void completeEncoding() throws IOException {
writePending();
flushOutput();
}
private void writePending() throws IOException {
if (pendingSpace) {
plain(SP);
} else if (pendingTab) {
plain(TB);
} else if (pendingCR) {
plain(CR);
}
clearPending();
}
private void clearPending() throws IOException {
pendingSpace = false;
pendingTab = false;
pendingCR = false;
}
private void encode(byte next) throws IOException {
if (next == LF) {
if (binary) {
writePending();
escape(next);
} else {
if (pendingCR) {
// Expect either space or tab pending
// but not both
if (pendingSpace) {
escape(SP);
} else if (pendingTab) {
escape(TB);
}
lineBreak();
clearPending();
} else {
writePending();
plain(next);
}
}
} else if (next == CR) {
if (binary) {
escape(next);
} else {
pendingCR = true;
}
} else {
writePending();
if (next == SP) {
if (binary) {
escape(next);
} else {
pendingSpace = true;
}
} else if (next == TB) {
if (binary) {
escape(next);
} else {
pendingTab = true;
}
} else if (next < SP) {
escape(next);
} else if (next > QUOTED_PRINTABLE_LAST_PLAIN) {
escape(next);
} else if (next == EQ) {
escape(next);
} else {
plain(next);
}
}
}
private void plain(byte next) throws IOException {
if (--nextSoftBreak <= 1) {
softBreak();
}
write(next);
}
private void escape(byte next) throws IOException {
if (--nextSoftBreak <= QUOTED_PRINTABLE_OCTETS_PER_ESCAPE) {
softBreak();
}
int nextUnsigned = next & 0xff;
write(EQ);
--nextSoftBreak;
write(HEX_DIGITS[nextUnsigned >> 4]);
--nextSoftBreak;
write(HEX_DIGITS[nextUnsigned % 0x10]);
}
private void write(byte next) throws IOException {
outBuffer[outputIndex++] = next;
if (outputIndex >= outBuffer.length) {
flushOutput();
}
}
private void softBreak() throws IOException {
write(EQ);
lineBreak();
}
private void lineBreak() throws IOException {
write(CR);
write(LF);
nextSoftBreak = QUOTED_PRINTABLE_MAX_LINE_LENGTH;
}
void flushOutput() throws IOException {
if (outputIndex < outBuffer.length) {
out.write(outBuffer, 0, outputIndex);
} else {
out.write(outBuffer);
}
outputIndex = 0;
}
@Override
public void close() throws IOException
{
public void close() throws IOException {
if (closed)
{
return;
}
try
{
encoder.completeEncoding();
try {
completeEncoding();
// do not close the wrapped stream
}
finally
{
} finally {
closed = true;
}
}
@Override
public void flush() throws IOException
{
encoder.flushOutput();
public void flush() throws IOException {
flushOutput();
}
@Override
public void write(int b) throws IOException
{
this.write(new byte[] { (byte) b }, 0, 1);
public void write(int b) throws IOException {
singleByte[0] = (byte) b;
this.write(singleByte, 0, 1);
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
if (closed)
{
throw new IOException("QuotedPrintableOutputStream has been closed");
public void write(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException("Stream has been closed");
}
encoder.encodeChunk(b, off, len);
encodeChunk(b, off, len);
}
}
}

View File

@ -1,146 +0,0 @@
/****************************************************************
* 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.decoder;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Performs Base-64 decoding on an underlying stream.
*
*
* @version $Id: Base64InputStream.java,v 1.3 2004/11/29 13:15:47 ntherning Exp $
*/
public class Base64InputStream extends InputStream {
private static Log log = LogFactory.getLog(Base64InputStream.class);
private final InputStream s;
private final ByteQueue byteq = new ByteQueue(3);
private boolean done = false;
public Base64InputStream(InputStream s) {
this.s = s;
}
/**
* Closes the underlying stream.
*
* @throws IOException on I/O errors.
*/
public void close() throws IOException {
s.close();
}
public int read() throws IOException {
if (byteq.count() == 0) {
fillBuffer();
if (byteq.count() == 0) {
return -1;
}
}
byte val = byteq.dequeue();
if (val >= 0)
return val;
else
return val & 0xFF;
}
/**
* Retrieve data from the underlying stream, decode it,
* and put the results in the byteq.
* @throws IOException
*/
private void fillBuffer() throws IOException {
byte[] data = new byte[4];
int pos = 0;
int i;
while (!done) {
switch (i = s.read()) {
case -1:
if (pos > 0) {
log.warn("Unexpected EOF in MIME parser, dropping "
+ pos + " sextets");
}
return;
case '=':
decodeAndEnqueue(data, pos);
done = true;
break;
default:
byte sX = TRANSLATION[i];
if (sX < 0)
continue;
data[pos++] = sX;
if (pos == data.length) {
decodeAndEnqueue(data, pos);
return;
}
break;
}
}
}
private void decodeAndEnqueue(byte[] data, int len) {
int accum = 0;
accum |= data[0] << 18;
accum |= data[1] << 12;
accum |= data[2] << 6;
accum |= data[3];
byte b1 = (byte)(accum >>> 16);
byteq.enqueue(b1);
if (len > 2) {
byte b2 = (byte)((accum >>> 8) & 0xFF);
byteq.enqueue(b2);
if (len > 3) {
byte b3 = (byte)(accum & 0xFF);
byteq.enqueue(b3);
}
}
}
private static byte[] TRANSLATION = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 0x20 */
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 0x30 */
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 0x40 */
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 0x50 */
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60 */
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 0x70 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xA0 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xB0 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xC0 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xD0 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xE0 */
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* 0xF0 */
};
}

View File

@ -1,277 +0,0 @@
/****************************************************************
* 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.decoder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.james.mime4j.util.CharsetUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* Static methods for decoding strings, byte arrays and encoded words.
*
*
* @version $Id: DecoderUtil.java,v 1.3 2005/02/07 15:33:59 ntherning Exp $
*/
public class DecoderUtil {
private static Log log = LogFactory.getLog(DecoderUtil.class);
/**
* Decodes a string containing quoted-printable encoded data.
*
* @param s the string to decode.
* @return the decoded bytes.
*/
public static byte[] decodeBaseQuotedPrintable(String s) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] bytes = s.getBytes("US-ASCII");
QuotedPrintableInputStream is = new QuotedPrintableInputStream(
new ByteArrayInputStream(bytes));
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
} catch (IOException e) {
/*
* This should never happen!
*/
log.error(e);
}
return baos.toByteArray();
}
/**
* Decodes a string containing base64 encoded data.
*
* @param s the string to decode.
* @return the decoded bytes.
*/
public static byte[] decodeBase64(String s) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] bytes = s.getBytes("US-ASCII");
Base64InputStream is = new Base64InputStream(
new ByteArrayInputStream(bytes));
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
} catch (IOException e) {
/*
* This should never happen!
*/
log.error(e);
}
return baos.toByteArray();
}
/**
* Decodes an encoded word encoded with the 'B' encoding (described in
* RFC 2047) found in a header field body.
*
* @param encodedWord the encoded word to decode.
* @param charset the Java charset to use.
* @return the decoded string.
* @throws UnsupportedEncodingException if the given Java charset isn't
* supported.
*/
public static String decodeB(String encodedWord, String charset)
throws UnsupportedEncodingException {
return new String(decodeBase64(encodedWord), charset);
}
/**
* Decodes an encoded word encoded with the 'Q' encoding (described in
* RFC 2047) found in a header field body.
*
* @param encodedWord the encoded word to decode.
* @param charset the Java charset to use.
* @return the decoded string.
* @throws UnsupportedEncodingException if the given Java charset isn't
* supported.
*/
public static String decodeQ(String encodedWord, String charset)
throws UnsupportedEncodingException {
/*
* Replace _ with =20
*/
StringBuffer sb = new StringBuffer();
for (int i = 0; i < encodedWord.length(); i++) {
char c = encodedWord.charAt(i);
if (c == '_') {
sb.append("=20");
} else {
sb.append(c);
}
}
return new String(decodeBaseQuotedPrintable(sb.toString()), charset);
}
/**
* Decodes a string containing encoded words as defined by RFC 2047.
* Encoded words in have the form
* =?charset?enc?Encoded word?= where enc is either 'Q' or 'q' for
* quoted-printable and 'B' or 'b' for Base64.
*
* ANDROID: COPIED FROM A NEWER VERSION OF MIME4J
*
* @param body the string to decode.
* @return the decoded string.
*/
public static String decodeEncodedWords(String body) {
// ANDROID: Most strings will not include "=?" so a quick test can prevent unneeded
// object creation. This could also be handled via lazy creation of the StringBuilder.
if (body.indexOf("=?") == -1) {
return body;
}
int previousEnd = 0;
boolean previousWasEncoded = false;
StringBuilder sb = new StringBuilder();
while (true) {
int begin = body.indexOf("=?", previousEnd);
// ANDROID: The mime4j original version has an error here. It gets confused if
// the encoded string begins with an '=' (just after "?Q?"). This patch seeks forward
// to find the two '?' in the "header", before looking for the final "?=".
int endScan = begin + 2;
if (begin != -1) {
int qm1 = body.indexOf('?', endScan + 2);
int qm2 = body.indexOf('?', qm1 + 1);
if (qm2 != -1) {
endScan = qm2 + 1;
}
}
int end = begin == -1 ? -1 : body.indexOf("?=", endScan);
if (end == -1) {
if (previousEnd == 0)
return body;
sb.append(body.substring(previousEnd));
return sb.toString();
}
end += 2;
String sep = body.substring(previousEnd, begin);
String decoded = decodeEncodedWord(body, begin, end);
if (decoded == null) {
sb.append(sep);
sb.append(body.substring(begin, end));
} else {
if (!previousWasEncoded || !CharsetUtil.isWhitespace(sep)) {
sb.append(sep);
}
sb.append(decoded);
}
previousEnd = end;
previousWasEncoded = decoded != null;
}
}
// return null on error
private static String decodeEncodedWord(String body, int begin, int end) {
int qm1 = body.indexOf('?', begin + 2);
if (qm1 == end - 2)
return null;
int qm2 = body.indexOf('?', qm1 + 1);
if (qm2 == end - 2)
return null;
String mimeCharset = body.substring(begin + 2, qm1);
String encoding = body.substring(qm1 + 1, qm2);
String encodedText = body.substring(qm2 + 1, end - 2);
String charset = CharsetUtil.toJavaCharset(mimeCharset);
if (charset == null) {
if (log.isWarnEnabled()) {
log.warn("MIME charset '" + mimeCharset + "' in encoded word '"
+ body.substring(begin, end) + "' doesn't have a "
+ "corresponding Java charset");
}
return null;
} else if (!CharsetUtil.isDecodingSupported(charset)) {
if (log.isWarnEnabled()) {
log.warn("Current JDK doesn't support decoding of charset '"
+ charset + "' (MIME charset '" + mimeCharset
+ "' in encoded word '" + body.substring(begin, end)
+ "')");
}
return null;
}
if (encodedText.length() == 0) {
if (log.isWarnEnabled()) {
log.warn("Missing encoded text in encoded word: '"
+ body.substring(begin, end) + "'");
}
return null;
}
try {
if (encoding.equalsIgnoreCase("Q")) {
return DecoderUtil.decodeQ(encodedText, charset);
} else if (encoding.equalsIgnoreCase("B")) {
return DecoderUtil.decodeB(encodedText, charset);
} else {
if (log.isWarnEnabled()) {
log.warn("Warning: Unknown encoding in encoded word '"
+ body.substring(begin, end) + "'");
}
return null;
}
} catch (UnsupportedEncodingException e) {
// should not happen because of isDecodingSupported check above
if (log.isWarnEnabled()) {
log.warn("Unsupported encoding in encoded word '"
+ body.substring(begin, end) + "'", e);
}
return null;
} catch (RuntimeException e) {
if (log.isWarnEnabled()) {
log.warn("Could not decode encoded word '"
+ body.substring(begin, end) + "'", e);
}
return null;
}
}
}

View File

@ -1,227 +0,0 @@
/****************************************************************
* 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.decoder;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Performs Quoted-Printable decoding on an underlying stream.
*
*
*
* @version $Id: QuotedPrintableInputStream.java,v 1.3 2004/11/29 13:15:47 ntherning Exp $
*/
public class QuotedPrintableInputStream extends InputStream {
private static Log log = LogFactory.getLog(QuotedPrintableInputStream.class);
private InputStream stream;
ByteQueue byteq = new ByteQueue();
ByteQueue pushbackq = new ByteQueue();
private byte state = 0;
public QuotedPrintableInputStream(InputStream stream) {
this.stream = stream;
}
/**
* Closes the underlying stream.
*
* @throws IOException on I/O errors.
*/
public void close() throws IOException {
stream.close();
}
public int read() throws IOException {
fillBuffer();
if (byteq.count() == 0)
return -1;
else {
byte val = byteq.dequeue();
if (val >= 0)
return val;
else
return val & 0xFF;
}
}
/**
* Pulls bytes out of the underlying stream and places them in the
* pushback queue. This is necessary (vs. reading from the
* underlying stream directly) to detect and filter out "transport
* padding" whitespace, i.e., all whitespace that appears immediately
* before a CRLF.
*
* @throws IOException Underlying stream threw IOException.
*/
private void populatePushbackQueue() throws IOException {
//Debug.verify(pushbackq.count() == 0, "PopulatePushbackQueue called when pushback queue was not empty!");
if (pushbackq.count() != 0)
return;
while (true) {
int i = stream.read();
switch (i) {
case -1:
// stream is done
pushbackq.clear(); // discard any whitespace preceding EOF
return;
case ' ':
case '\t':
pushbackq.enqueue((byte)i);
break;
case '\r':
case '\n':
pushbackq.clear(); // discard any whitespace preceding EOL
pushbackq.enqueue((byte)i);
return;
default:
pushbackq.enqueue((byte)i);
return;
}
}
}
/**
* Causes the pushback queue to get populated if it is empty, then
* consumes and decodes bytes out of it until one or more bytes are
* in the byte queue. This decoding step performs the actual QP
* decoding.
*
* @throws IOException Underlying stream threw IOException.
*/
private void fillBuffer() throws IOException {
byte msdChar = 0; // first digit of escaped num
while (byteq.count() == 0) {
if (pushbackq.count() == 0) {
populatePushbackQueue();
if (pushbackq.count() == 0)
return;
}
byte b = (byte)pushbackq.dequeue();
switch (state) {
case 0: // start state, no bytes pending
if (b != '=') {
byteq.enqueue(b);
break; // state remains 0
} else {
state = 1;
break;
}
case 1: // encountered "=" so far
if (b == '\r') {
state = 2;
break;
} else if ((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')) {
state = 3;
msdChar = b; // save until next digit encountered
break;
} else if (b == '=') {
/*
* Special case when == is encountered.
* Emit one = and stay in this state.
*/
if (log.isWarnEnabled()) {
log.warn("Malformed MIME; got ==");
}
byteq.enqueue((byte)'=');
break;
} else {
if (log.isWarnEnabled()) {
log.warn("Malformed MIME; expected \\r or "
+ "[0-9A-Z], got " + b);
}
state = 0;
byteq.enqueue((byte)'=');
byteq.enqueue(b);
break;
}
case 2: // encountered "=\r" so far
if (b == '\n') {
state = 0;
break;
} else {
if (log.isWarnEnabled()) {
log.warn("Malformed MIME; expected "
+ (int)'\n' + ", got " + b);
}
state = 0;
byteq.enqueue((byte)'=');
byteq.enqueue((byte)'\r');
byteq.enqueue(b);
break;
}
case 3: // encountered =<digit> so far; expecting another <digit> to complete the octet
if ((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')) {
byte msd = asciiCharToNumericValue(msdChar);
byte low = asciiCharToNumericValue(b);
state = 0;
byteq.enqueue((byte)((msd << 4) | low));
break;
} else {
if (log.isWarnEnabled()) {
log.warn("Malformed MIME; expected "
+ "[0-9A-Z], got " + b);
}
state = 0;
byteq.enqueue((byte)'=');
byteq.enqueue(msdChar);
byteq.enqueue(b);
break;
}
default: // should never happen
log.error("Illegal state: " + state);
state = 0;
byteq.enqueue(b);
break;
}
}
}
/**
* Converts '0' => 0, 'A' => 10, etc.
* @param c ASCII character value.
* @return Numeric value of hexadecimal character.
*/
private byte asciiCharToNumericValue(byte c) {
if (c >= '0' && c <= '9') {
return (byte)(c - '0');
} else if (c >= 'A' && c <= 'Z') {
return (byte)(0xA + (c - 'A'));
} else if (c >= 'a' && c <= 'z') {
return (byte)(0xA + (c - 'a'));
} else {
/*
* This should never happen since all calls to this method
* are preceded by a check that c is in [0-9A-Za-z]
*/
throw new IllegalArgumentException((char) c
+ " is not a hexadecimal digit");
}
}
}

View File

@ -1,272 +0,0 @@
/****************************************************************
* 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.decoder;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* UnboundedFifoByteBuffer is a very efficient buffer implementation.
* According to performance testing, it exhibits a constant access time, but it
* also outperforms ArrayList when used for the same purpose.
* <p>
* The removal order of an <code>UnboundedFifoByteBuffer</code> is based on the insertion
* order; elements are removed in the same order in which they were added.
* The iteration order is the same as the removal order.
* <p>
* The {@link #remove()} and {@link #get()} operations perform in constant time.
* The {@link #add(Object)} operation performs in amortized constant time. All
* other operations perform in linear time or worse.
* <p>
* Note that this implementation is not synchronized. The following can be
* used to provide synchronized access to your <code>UnboundedFifoByteBuffer</code>:
* <pre>
* Buffer fifo = BufferUtils.synchronizedBuffer(new UnboundedFifoByteBuffer());
* </pre>
* <p>
* This buffer prevents null objects from being added.
*
* @since Commons Collections 3.0 (previously in main package v2.1)
* @version $Revision: 1.1 $ $Date: 2004/08/24 06:52:02 $
*
*
*
*
*
*
*/
class UnboundedFifoByteBuffer {
protected byte[] buffer;
protected int head;
protected int tail;
/**
* Constructs an UnboundedFifoByteBuffer with the default number of elements.
* It is exactly the same as performing the following:
*
* <pre>
* new UnboundedFifoByteBuffer(32);
* </pre>
*/
public UnboundedFifoByteBuffer() {
this(32);
}
/**
* Constructs an UnboundedFifoByteBuffer with the specified number of elements.
* The integer must be a positive integer.
*
* @param initialSize the initial size of the buffer
* @throws IllegalArgumentException if the size is less than 1
*/
public UnboundedFifoByteBuffer(int initialSize) {
if (initialSize <= 0) {
throw new IllegalArgumentException("The size must be greater than 0");
}
buffer = new byte[initialSize + 1];
head = 0;
tail = 0;
}
/**
* Returns the number of elements stored in the buffer.
*
* @return this buffer's size
*/
public int size() {
int size = 0;
if (tail < head) {
size = buffer.length - head + tail;
} else {
size = tail - head;
}
return size;
}
/**
* Returns true if this buffer is empty; false otherwise.
*
* @return true if this buffer is empty
*/
public boolean isEmpty() {
return (size() == 0);
}
/**
* Adds the given element to this buffer.
*
* @param b the byte to add
* @return true, always
*/
public boolean add(final byte b) {
if (size() + 1 >= buffer.length) {
byte[] tmp = new byte[((buffer.length - 1) * 2) + 1];
int j = 0;
for (int i = head; i != tail;) {
tmp[j] = buffer[i];
buffer[i] = 0;
j++;
i++;
if (i == buffer.length) {
i = 0;
}
}
buffer = tmp;
head = 0;
tail = j;
}
buffer[tail] = b;
tail++;
if (tail >= buffer.length) {
tail = 0;
}
return true;
}
/**
* Returns the next object in the buffer.
*
* @return the next object in the buffer
* @throws BufferUnderflowException if this buffer is empty
*/
public byte get() {
if (isEmpty()) {
throw new IllegalStateException("The buffer is already empty");
}
return buffer[head];
}
/**
* Removes the next object from the buffer
*
* @return the removed object
* @throws BufferUnderflowException if this buffer is empty
*/
public byte remove() {
if (isEmpty()) {
throw new IllegalStateException("The buffer is already empty");
}
byte element = buffer[head];
head++;
if (head >= buffer.length) {
head = 0;
}
return element;
}
/**
* Increments the internal index.
*
* @param index the index to increment
* @return the updated index
*/
private int increment(int index) {
index++;
if (index >= buffer.length) {
index = 0;
}
return index;
}
/**
* Decrements the internal index.
*
* @param index the index to decrement
* @return the updated index
*/
private int decrement(int index) {
index--;
if (index < 0) {
index = buffer.length - 1;
}
return index;
}
/**
* Returns an iterator over this buffer's elements.
*
* @return an iterator over this buffer's elements
*/
public Iterator iterator() {
return new Iterator() {
private int index = head;
private int lastReturnedIndex = -1;
public boolean hasNext() {
return index != tail;
}
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
lastReturnedIndex = index;
index = increment(index);
return new Byte(buffer[lastReturnedIndex]);
}
public void remove() {
if (lastReturnedIndex == -1) {
throw new IllegalStateException();
}
// First element can be removed quickly
if (lastReturnedIndex == head) {
UnboundedFifoByteBuffer.this.remove();
lastReturnedIndex = -1;
return;
}
// Other elements require us to shift the subsequent elements
int i = lastReturnedIndex + 1;
while (i != tail) {
if (i >= buffer.length) {
buffer[i - 1] = buffer[0];
i = 0;
} else {
buffer[i - 1] = buffer[i];
i++;
}
}
lastReturnedIndex = -1;
tail = decrement(tail);
buffer[tail] = 0;
index = decrement(index);
}
};
}
}

View File

@ -0,0 +1,33 @@
/****************************************************************
* 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;
/**
* A body containing binary data.
*/
public abstract class BinaryBody extends SingleBody {
/**
* Sole constructor.
*/
protected BinaryBody() {
}
}

View File

@ -17,38 +17,30 @@
* under the License. *
****************************************************************/
package org.apache.james.mime4j.message;
import java.io.IOException;
import java.io.OutputStream;
package org.apache.james.mime4j.dom;
/**
* Encapsulates the body of an entity (see RFC 2045).
*
*
* @version $Id: Body.java,v 1.4 2004/10/04 15:36:43 ntherning Exp $
* <p>
* A body can be a {@link Message}, a {@link Multipart} or a {@link SingleBody}.
* This interface should not be implemented directly by classes other than
* those.
*/
public interface Body {
public interface Body extends Disposable {
/**
* Gets the parent of this body.
*
*
* @return the parent.
*/
Entity getParent();
/**
* Sets the parent of this body.
*
* @param parent the parent.
*
* @param parent
* the parent.
*/
void setParent(Entity parent);
/**
* Writes this body to the given stream in MIME message format.
*
* @param out the stream to write to.
* @throws IOException on I/O errors.
*/
void writeTo(OutputStream out) throws IOException;
}

View File

@ -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.dom;
/**
* A <tt>Disposable</tt> is an object that should be disposed of explicitly
* when it is no longer needed.
*
* The dispose method is invoked to release resources that the object is
* holding (such as open files).
*/
public interface Disposable {
/**
* Free any resources this object is holding and prepares this object
* for garbage collection. Once an object has been disposed of it can no
* longer be used.
*/
void dispose();
}

View File

@ -0,0 +1,550 @@
/****************************************************************
* 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.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.james.mime4j.dom.field.ContentDispositionField;
import org.apache.james.mime4j.dom.field.ContentTransferEncodingField;
import org.apache.james.mime4j.dom.field.ContentTypeField;
import org.apache.james.mime4j.dom.field.Field;
import org.apache.james.mime4j.dom.field.FieldName;
/**
* MIME entity. An entity has a header and a body (see RFC 2045).
*/
public abstract class Entity implements Disposable {
private Header header = null;
private Body body = null;
private Entity parent = null;
/**
* Creates a new <code>Entity</code>. Typically invoked implicitly by a
* subclass constructor.
*/
protected Entity() {
}
/**
* Gets the parent entity of this entity.
* Returns <code>null</code> if this is the root entity.
*
* @return the parent or <code>null</code>.
*/
public Entity getParent() {
return parent;
}
/**
* Sets the parent entity of this entity.
*
* @param parent the parent entity or <code>null</code> if
* this will be the root entity.
*/
public void setParent(Entity parent) {
this.parent = parent;
}
/**
* Gets the entity header.
*
* @return the header.
*/
public Header getHeader() {
return header;
}
/**
* Sets the entity header.
*
* @param header the header.
*/
public void setHeader(Header header) {
this.header = header;
}
/**
* Gets the body of this entity.
*
* @return the body,
*/
public Body getBody() {
return body;
}
/**
* Sets the body of this entity.
*
* @param body the body.
* @throws IllegalStateException if the body has already been set.
*/
public void setBody(Body body) {
if (this.body != null)
throw new IllegalStateException("body already set");
this.body = body;
body.setParent(this);
}
/**
* Removes and returns the body of this entity. The removed body may be
* attached to another entity. If it is no longer needed it should be
* {@link Disposable#dispose() disposed} of.
*
* @return the removed body or <code>null</code> if no body was set.
*/
public Body removeBody() {
if (body == null)
return null;
Body body = this.body;
this.body = null;
body.setParent(null);
return body;
}
/**
* Sets the specified message as body of this entity and the content type to
* &quot;message/rfc822&quot;. A <code>Header</code> is created if this
* entity does not already have one.
*
* @param message
* the message to set as body.
*/
public void setMessage(Message message) {
setBody(message, "message/rfc822", null);
}
/**
* Sets the specified multipart as body of this entity. Also sets the
* content type accordingly and creates a message boundary string. A
* <code>Header</code> is created if this entity does not already have
* one.
*
* @param multipart
* the multipart to set as body.
*/
public void setMultipart(Multipart multipart) {
String mimeType = "multipart/" + multipart.getSubType();
Map<String, String> parameters = Collections.singletonMap("boundary",
newUniqueBoundary());
setBody(multipart, mimeType, parameters);
}
/**
* Sets the specified multipart as body of this entity. Also sets the
* content type accordingly and creates a message boundary string. A
* <code>Header</code> is created if this entity does not already have
* one.
*
* @param multipart
* the multipart to set as body.
* @param parameters
* additional parameters for the Content-Type header field.
*/
public void setMultipart(Multipart multipart, Map<String, String> parameters) {
String mimeType = "multipart/" + multipart.getSubType();
if (!parameters.containsKey("boundary")) {
parameters = new HashMap<String, String>(parameters);
parameters.put("boundary", newUniqueBoundary());
}
setBody(multipart, mimeType, parameters);
}
/**
* Sets the specified <code>TextBody</code> as body of this entity and the
* content type to &quot;text/plain&quot;. A <code>Header</code> is
* created if this entity does not already have one.
*
* @param textBody
* the <code>TextBody</code> to set as body.
* @see org.apache.james.mime4j.message.BodyFactory#textBody(String)
*/
public void setText(TextBody textBody) {
setText(textBody, "plain");
}
/**
* Sets the specified <code>TextBody</code> as body of this entity. Also
* sets the content type according to the specified sub-type. A
* <code>Header</code> is created if this entity does not already have
* one.
*
* @param textBody
* the <code>TextBody</code> to set as body.
* @param subtype
* the text subtype (e.g. &quot;plain&quot;, &quot;html&quot; or
* &quot;xml&quot;).
* @see org.apache.james.mime4j.message.BodyFactory#textBody(String)
*/
public void setText(TextBody textBody, String subtype) {
String mimeType = "text/" + subtype;
Map<String, String> parameters = null;
String mimeCharset = textBody.getMimeCharset();
if (mimeCharset != null && !mimeCharset.equalsIgnoreCase("us-ascii")) {
parameters = Collections.singletonMap("charset", mimeCharset);
}
setBody(textBody, mimeType, parameters);
}
/**
* Sets the body of this entity and sets the content-type to the specified
* value. A <code>Header</code> is created if this entity does not already
* have one.
*
* @param body
* the body.
* @param mimeType
* the MIME media type of the specified body
* (&quot;type/subtype&quot;).
*/
public void setBody(Body body, String mimeType) {
setBody(body, mimeType, null);
}
/**
* Sets the body of this entity and sets the content-type to the specified
* value. A <code>Header</code> is created if this entity does not already
* have one.
*
* @param body
* the body.
* @param mimeType
* the MIME media type of the specified body
* (&quot;type/subtype&quot;).
* @param parameters
* additional parameters for the Content-Type header field.
*/
public void setBody(Body body, String mimeType,
Map<String, String> parameters) {
setBody(body);
Header header = obtainHeader();
header.setField(newContentType(mimeType, parameters));
}
/**
* Determines the MIME type of this <code>Entity</code>. The MIME type
* is derived by looking at the parent's Content-Type field if no
* Content-Type field is set for this <code>Entity</code>.
*
* @return the MIME type.
*/
public String getMimeType() {
ContentTypeField child =
getContentTypeField();
ContentTypeField parent = getParent() != null
? (ContentTypeField) getParent().getHeader().
getField(FieldName.CONTENT_TYPE)
: null;
return calcMimeType(child, parent);
}
private ContentTypeField getContentTypeField() {
return (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE);
}
/**
* Determines the MIME character set encoding of this <code>Entity</code>.
*
* @return the MIME character set encoding.
*/
public String getCharset() {
return calcCharset((ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE));
}
/**
* Determines the transfer encoding of this <code>Entity</code>.
*
* @return the transfer encoding.
*/
public String getContentTransferEncoding() {
ContentTransferEncodingField f = (ContentTransferEncodingField)
getHeader().getField(FieldName.CONTENT_TRANSFER_ENCODING);
return calcTransferEncoding(f);
}
/**
* Sets the transfer encoding of this <code>Entity</code> to the specified
* value.
*
* @param contentTransferEncoding
* transfer encoding to use.
*/
public void setContentTransferEncoding(String contentTransferEncoding) {
Header header = obtainHeader();
header.setField(newContentTransferEncoding(contentTransferEncoding));
}
/**
* Return the disposition type of the content disposition of this
* <code>Entity</code>.
*
* @return the disposition type or <code>null</code> if no disposition
* type has been set.
*/
public String getDispositionType() {
ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION);
if (field == null)
return null;
return field.getDispositionType();
}
/**
* Sets the content disposition of this <code>Entity</code> to the
* specified disposition type. No filename, size or date parameters
* are included in the content disposition.
*
* @param dispositionType
* disposition type value (usually <code>inline</code> or
* <code>attachment</code>).
*/
public void setContentDisposition(String dispositionType) {
Header header = obtainHeader();
header.setField(newContentDisposition(dispositionType, null, -1, null,
null, null));
}
/**
* Sets the content disposition of this <code>Entity</code> to the
* specified disposition type and filename. No size or date parameters are
* included in the content disposition.
*
* @param dispositionType
* disposition type value (usually <code>inline</code> or
* <code>attachment</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
*/
public void setContentDisposition(String dispositionType, String filename) {
Header header = obtainHeader();
header.setField(newContentDisposition(dispositionType, filename, -1,
null, null, null));
}
/**
* Sets the content disposition of this <code>Entity</code> to the
* specified values. No date parameters are included in the content
* disposition.
*
* @param dispositionType
* disposition type value (usually <code>inline</code> or
* <code>attachment</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @param size
* size parameter value or <code>-1</code> if the parameter
* should not be included.
*/
public void setContentDisposition(String dispositionType, String filename,
long size) {
Header header = obtainHeader();
header.setField(newContentDisposition(dispositionType, filename, size,
null, null, null));
}
/**
* Sets the content disposition of this <code>Entity</code> to the
* specified values.
*
* @param dispositionType
* disposition type value (usually <code>inline</code> or
* <code>attachment</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @param size
* size parameter value or <code>-1</code> if the parameter
* should not be included.
* @param creationDate
* creation-date parameter value or <code>null</code> if the
* parameter should not be included.
* @param modificationDate
* modification-date parameter value or <code>null</code> if
* the parameter should not be included.
* @param readDate
* read-date parameter value or <code>null</code> if the
* parameter should not be included.
*/
public void setContentDisposition(String dispositionType, String filename,
long size, Date creationDate, Date modificationDate, Date readDate) {
Header header = obtainHeader();
header.setField(newContentDisposition(dispositionType, filename, size,
creationDate, modificationDate, readDate));
}
/**
* Returns the filename parameter of the content disposition of this
* <code>Entity</code>.
*
* @return the filename parameter of the content disposition or
* <code>null</code> if the filename has not been set.
*/
public String getFilename() {
ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION);
if (field == null)
return null;
return field.getFilename();
}
/**
* Sets the filename parameter of the content disposition of this
* <code>Entity</code> to the specified value. If this entity does not
* have a content disposition header field a new one with disposition type
* <code>attachment</code> is created.
*
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should be removed.
*/
public void setFilename(String filename) {
Header header = obtainHeader();
ContentDispositionField field = (ContentDispositionField) header
.getField(FieldName.CONTENT_DISPOSITION);
if (field == null) {
if (filename != null) {
header.setField(newContentDisposition(
ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT,
filename, -1, null, null, null));
}
} else {
String dispositionType = field.getDispositionType();
Map<String, String> parameters = new HashMap<String, String>(field
.getParameters());
if (filename == null) {
parameters.remove(ContentDispositionField.PARAM_FILENAME);
} else {
parameters
.put(ContentDispositionField.PARAM_FILENAME, filename);
}
header.setField(newContentDisposition(dispositionType, parameters));
}
}
/**
* Determines if the MIME type of this <code>Entity</code> matches the
* given one. MIME types are case-insensitive.
*
* @param type the MIME type to match against.
* @return <code>true</code> on match, <code>false</code> otherwise.
*/
public boolean isMimeType(String type) {
return getMimeType().equalsIgnoreCase(type);
}
/**
* Determines if the MIME type of this <code>Entity</code> is
* <code>multipart/*</code>. Since multipart-entities must have
* a boundary parameter in the <code>Content-Type</code> field this
* method returns <code>false</code> if no boundary exists.
*
* @return <code>true</code> on match, <code>false</code> otherwise.
*/
public boolean isMultipart() {
ContentTypeField f = getContentTypeField();
return f != null
&& f.getBoundary() != null
&& getMimeType().startsWith(
ContentTypeField.TYPE_MULTIPART_PREFIX);
}
/**
* Disposes of the body of this entity. Note that the dispose call does not
* get forwarded to the parent entity of this Entity.
*
* Subclasses that need to free resources should override this method and
* invoke super.dispose().
*
* @see org.apache.james.mime4j.dom.Disposable#dispose()
*/
public void dispose() {
if (body != null) {
body.dispose();
}
}
/**
* Obtains the header of this entity. Creates and sets a new header if this
* entity's header is currently <code>null</code>.
*
* @return the header of this entity; never <code>null</code>.
*/
Header obtainHeader() {
if (header == null) {
header = new Header();
}
return header;
}
/**
* Obtains the header field with the specified name.
*
* @param <F>
* concrete field type.
* @param fieldName
* name of the field to retrieve.
* @return the header field or <code>null</code> if this entity has no
* header or the header contains no such field.
*/
<F extends Field> F obtainField(String fieldName) {
Header header = getHeader();
if (header == null)
return null;
@SuppressWarnings("unchecked")
F field = (F) header.getField(fieldName);
return field;
}
protected abstract String newUniqueBoundary();
protected abstract ContentDispositionField newContentDisposition(
String dispositionType, String filename, long size,
Date creationDate, Date modificationDate, Date readDate);
protected abstract ContentDispositionField newContentDisposition(
String dispositionType, Map<String, String> parameters);
protected abstract ContentTypeField newContentType(String mimeType,
Map<String, String> parameters);
protected abstract ContentTransferEncodingField newContentTransferEncoding(
String contentTransferEncoding);
protected abstract String calcMimeType(ContentTypeField child, ContentTypeField parent);
protected abstract String calcTransferEncoding(ContentTransferEncodingField f);
protected abstract String calcCharset(ContentTypeField contentType);
}

View File

@ -0,0 +1,204 @@
/****************************************************************
* 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.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.dom.field.Field;
/**
* The header of an entity (see RFC 2045).
*/
public class Header implements Iterable<Field> {
private List<Field> fields = new LinkedList<Field>();
private Map<String, List<Field>> fieldMap = new HashMap<String, List<Field>>();
/**
* Creates a new empty <code>Header</code>.
*/
public Header() {
}
/**
* Creates a new <code>Header</code> from the specified
* <code>Header</code>. The <code>Header</code> instance is initialized
* with a copy of the list of {@link Field}s of the specified
* <code>Header</code>. The <code>Field</code> objects are not copied
* because they are immutable and can safely be shared between headers.
*
* @param other
* header to copy.
*/
public Header(Header other) {
for (Field otherField : other.fields) {
addField(otherField);
}
}
/**
* Adds a field to the end of the list of fields.
*
* @param field the field to add.
*/
public void addField(Field field) {
List<Field> values = fieldMap.get(field.getName().toLowerCase());
if (values == null) {
values = new LinkedList<Field>();
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<Field> 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<Field> l = fieldMap.get(name.toLowerCase());
if (l != null && !l.isEmpty()) {
return 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<Field> getFields(final String name) {
final String lowerCaseName = name.toLowerCase();
final List<Field> l = fieldMap.get(lowerCaseName);
final List<Field> results;
if (l == null || l.isEmpty()) {
results = Collections.emptyList();
} else {
results = Collections.unmodifiableList(l);
}
return results;
}
/**
* Returns an iterator over the list of fields of this header.
*
* @return an iterator.
*/
public Iterator<Field> iterator() {
return Collections.unmodifiableList(fields).iterator();
}
/**
* 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<Field> removed = fieldMap.remove(lowerCaseName);
if (removed == null || removed.isEmpty())
return 0;
for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext();) {
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 {@link #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<Field> l = fieldMap.get(lowerCaseName);
if (l == null || l.isEmpty()) {
addField(field);
return;
}
l.clear();
l.add(field);
int firstOccurrence = -1;
int index = 0;
for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext(); index++) {
Field f = 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
*/
@Override
public String toString() {
StringBuilder str = new StringBuilder(128);
for (Field field : fields) {
str.append(field.toString());
str.append("\r\n");
}
return str.toString();
}
}

View File

@ -0,0 +1,512 @@
/****************************************************************
* 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.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.TimeZone;
import org.apache.james.mime4j.dom.address.Address;
import org.apache.james.mime4j.dom.address.AddressList;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.dom.field.AddressListField;
import org.apache.james.mime4j.dom.field.DateTimeField;
import org.apache.james.mime4j.dom.field.Field;
import org.apache.james.mime4j.dom.field.FieldName;
import org.apache.james.mime4j.dom.field.MailboxField;
import org.apache.james.mime4j.dom.field.MailboxListField;
import org.apache.james.mime4j.dom.field.UnstructuredField;
public abstract class Message extends Entity implements Body {
/**
* Write the content to the given output stream using the
* {@link org.apache.james.mime4j.message.MessageWriter#DEFAULT default} message writer.
*
* @param out
* the output stream to write to.
* @throws IOException
* in case of an I/O error
* @see org.apache.james.mime4j.message.MessageWriter
*/
public abstract void writeTo(OutputStream out) throws IOException;
/**
* Returns the value of the <i>Message-ID</i> header field of this message
* or <code>null</code> if it is not present.
*
* @return the identifier of this message.
*/
public String getMessageId() {
Field field = obtainField(FieldName.MESSAGE_ID);
if (field == null)
return null;
return field.getBody();
}
/**
* Creates and sets a new <i>Message-ID</i> header field for this message.
* A <code>Header</code> is created if this message does not already have
* one.
*
* @param hostname
* host name to be included in the identifier or
* <code>null</code> if no host name should be included.
*/
public void createMessageId(String hostname) {
Header header = obtainHeader();
header.setField(newMessageId(hostname));
}
protected abstract Field newMessageId(String hostname);
/**
* Returns the (decoded) value of the <i>Subject</i> header field of this
* message or <code>null</code> if it is not present.
*
* @return the subject of this message.
*/
public String getSubject() {
UnstructuredField field = obtainField(FieldName.SUBJECT);
if (field == null)
return null;
return field.getValue();
}
/**
* Sets the <i>Subject</i> header field for this message. The specified
* string may contain non-ASCII characters, in which case it gets encoded as
* an 'encoded-word' automatically. A <code>Header</code> is created if
* this message does not already have one.
*
* @param subject
* subject to set or <code>null</code> to remove the subject
* header field.
*/
public void setSubject(String subject) {
Header header = obtainHeader();
if (subject == null) {
header.removeFields(FieldName.SUBJECT);
} else {
header.setField(newSubject(subject));
}
}
/**
* Returns the value of the <i>Date</i> header field of this message as
* <code>Date</code> object or <code>null</code> if it is not present.
*
* @return the date of this message.
*/
public Date getDate() {
DateTimeField dateField = obtainField(FieldName.DATE);
if (dateField == null)
return null;
return dateField.getDate();
}
/**
* Sets the <i>Date</i> header field for this message. This method uses the
* default <code>TimeZone</code> of this host to encode the specified
* <code>Date</code> object into a string.
*
* @param date
* date to set or <code>null</code> to remove the date header
* field.
*/
public void setDate(Date date) {
setDate(date, null);
}
/**
* Sets the <i>Date</i> header field for this message. The specified
* <code>TimeZone</code> is used to encode the specified <code>Date</code>
* object into a string.
*
* @param date
* date to set or <code>null</code> to remove the date header
* field.
* @param zone
* a time zone.
*/
public void setDate(Date date, TimeZone zone) {
Header header = obtainHeader();
if (date == null) {
header.removeFields(FieldName.DATE);
} else {
header.setField(newDate(date, zone));
}
}
/**
* Returns the value of the <i>Sender</i> header field of this message as
* <code>Mailbox</code> object or <code>null</code> if it is not
* present.
*
* @return the sender of this message.
*/
public Mailbox getSender() {
return getMailbox(FieldName.SENDER);
}
/**
* Sets the <i>Sender</i> header field of this message to the specified
* mailbox address.
*
* @param sender
* address to set or <code>null</code> to remove the header
* field.
*/
public void setSender(Mailbox sender) {
setMailbox(FieldName.SENDER, sender);
}
/**
* Returns the value of the <i>From</i> header field of this message as
* <code>MailboxList</code> object or <code>null</code> if it is not
* present.
*
* @return value of the from field of this message.
*/
public MailboxList getFrom() {
return getMailboxList(FieldName.FROM);
}
/**
* Sets the <i>From</i> header field of this message to the specified
* mailbox address.
*
* @param from
* address to set or <code>null</code> to remove the header
* field.
*/
public void setFrom(Mailbox from) {
setMailboxList(FieldName.FROM, from);
}
/**
* Sets the <i>From</i> header field of this message to the specified
* mailbox addresses.
*
* @param from
* addresses to set or <code>null</code> or no arguments to
* remove the header field.
*/
public void setFrom(Mailbox... from) {
setMailboxList(FieldName.FROM, from);
}
/**
* Sets the <i>From</i> header field of this message to the specified
* mailbox addresses.
*
* @param from
* addresses to set or <code>null</code> or an empty collection
* to remove the header field.
*/
public void setFrom(Collection<Mailbox> from) {
setMailboxList(FieldName.FROM, from);
}
/**
* Returns the value of the <i>To</i> header field of this message as
* <code>AddressList</code> object or <code>null</code> if it is not
* present.
*
* @return value of the to field of this message.
*/
public AddressList getTo() {
return getAddressList(FieldName.TO);
}
/**
* Sets the <i>To</i> header field of this message to the specified
* address.
*
* @param to
* address to set or <code>null</code> to remove the header
* field.
*/
public void setTo(Address to) {
setAddressList(FieldName.TO, to);
}
/**
* Sets the <i>To</i> header field of this message to the specified
* addresses.
*
* @param to
* addresses to set or <code>null</code> or no arguments to
* remove the header field.
*/
public void setTo(Address... to) {
setAddressList(FieldName.TO, to);
}
/**
* Sets the <i>To</i> header field of this message to the specified
* addresses.
*
* @param to
* addresses to set or <code>null</code> or an empty collection
* to remove the header field.
*/
public void setTo(Collection<Address> to) {
setAddressList(FieldName.TO, to);
}
/**
* Returns the value of the <i>Cc</i> header field of this message as
* <code>AddressList</code> object or <code>null</code> if it is not
* present.
*
* @return value of the cc field of this message.
*/
public AddressList getCc() {
return getAddressList(FieldName.CC);
}
/**
* Sets the <i>Cc</i> header field of this message to the specified
* address.
*
* @param cc
* address to set or <code>null</code> to remove the header
* field.
*/
public void setCc(Address cc) {
setAddressList(FieldName.CC, cc);
}
/**
* Sets the <i>Cc</i> header field of this message to the specified
* addresses.
*
* @param cc
* addresses to set or <code>null</code> or no arguments to
* remove the header field.
*/
public void setCc(Address... cc) {
setAddressList(FieldName.CC, cc);
}
/**
* Sets the <i>Cc</i> header field of this message to the specified
* addresses.
*
* @param cc
* addresses to set or <code>null</code> or an empty collection
* to remove the header field.
*/
public void setCc(Collection<Address> cc) {
setAddressList(FieldName.CC, cc);
}
/**
* Returns the value of the <i>Bcc</i> header field of this message as
* <code>AddressList</code> object or <code>null</code> if it is not
* present.
*
* @return value of the bcc field of this message.
*/
public AddressList getBcc() {
return getAddressList(FieldName.BCC);
}
/**
* Sets the <i>Bcc</i> header field of this message to the specified
* address.
*
* @param bcc
* address to set or <code>null</code> to remove the header
* field.
*/
public void setBcc(Address bcc) {
setAddressList(FieldName.BCC, bcc);
}
/**
* Sets the <i>Bcc</i> header field of this message to the specified
* addresses.
*
* @param bcc
* addresses to set or <code>null</code> or no arguments to
* remove the header field.
*/
public void setBcc(Address... bcc) {
setAddressList(FieldName.BCC, bcc);
}
/**
* Sets the <i>Bcc</i> header field of this message to the specified
* addresses.
*
* @param bcc
* addresses to set or <code>null</code> or an empty collection
* to remove the header field.
*/
public void setBcc(Collection<Address> bcc) {
setAddressList(FieldName.BCC, bcc);
}
/**
* Returns the value of the <i>Reply-To</i> header field of this message as
* <code>AddressList</code> object or <code>null</code> if it is not
* present.
*
* @return value of the reply to field of this message.
*/
public AddressList getReplyTo() {
return getAddressList(FieldName.REPLY_TO);
}
/**
* Sets the <i>Reply-To</i> header field of this message to the specified
* address.
*
* @param replyTo
* address to set or <code>null</code> to remove the header
* field.
*/
public void setReplyTo(Address replyTo) {
setAddressList(FieldName.REPLY_TO, replyTo);
}
/**
* Sets the <i>Reply-To</i> header field of this message to the specified
* addresses.
*
* @param replyTo
* addresses to set or <code>null</code> or no arguments to
* remove the header field.
*/
public void setReplyTo(Address... replyTo) {
setAddressList(FieldName.REPLY_TO, replyTo);
}
/**
* Sets the <i>Reply-To</i> header field of this message to the specified
* addresses.
*
* @param replyTo
* addresses to set or <code>null</code> or an empty collection
* to remove the header field.
*/
public void setReplyTo(Collection<Address> replyTo) {
setAddressList(FieldName.REPLY_TO, replyTo);
}
private Mailbox getMailbox(String fieldName) {
MailboxField field = obtainField(fieldName);
if (field == null)
return null;
return field.getMailbox();
}
private void setMailbox(String fieldName, Mailbox mailbox) {
Header header = obtainHeader();
if (mailbox == null) {
header.removeFields(fieldName);
} else {
header.setField(newMailbox(fieldName, mailbox));
}
}
private MailboxList getMailboxList(String fieldName) {
MailboxListField field = obtainField(fieldName);
if (field == null)
return null;
return field.getMailboxList();
}
private void setMailboxList(String fieldName, Mailbox mailbox) {
setMailboxList(fieldName, mailbox == null ? null : Collections
.singleton(mailbox));
}
private void setMailboxList(String fieldName, Mailbox... mailboxes) {
setMailboxList(fieldName, mailboxes == null ? null : Arrays
.asList(mailboxes));
}
private void setMailboxList(String fieldName, Collection<Mailbox> mailboxes) {
Header header = obtainHeader();
if (mailboxes == null || mailboxes.isEmpty()) {
header.removeFields(fieldName);
} else {
header.setField(newMailboxList(fieldName, mailboxes));
}
}
private AddressList getAddressList(String fieldName) {
AddressListField field = obtainField(fieldName);
if (field == null)
return null;
return field.getAddressList();
}
private void setAddressList(String fieldName, Address address) {
setAddressList(fieldName, address == null ? null : Collections
.singleton(address));
}
private void setAddressList(String fieldName, Address... addresses) {
setAddressList(fieldName, addresses == null ? null : Arrays
.asList(addresses));
}
private void setAddressList(String fieldName, Collection<Address> addresses) {
Header header = obtainHeader();
if (addresses == null || addresses.isEmpty()) {
header.removeFields(fieldName);
} else {
header.setField(newAddressList(fieldName, addresses));
}
}
protected abstract AddressListField newAddressList(String fieldName, Collection<Address> addresses);
protected abstract UnstructuredField newSubject(String subject);
protected abstract DateTimeField newDate(Date date, TimeZone zone);
protected abstract MailboxField newMailbox(String fieldName, Mailbox mailbox);
protected abstract MailboxListField newMailboxList(String fieldName, Collection<Mailbox> mailboxes);
}

View File

@ -0,0 +1,46 @@
/****************************************************************
* 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.IOException;
import java.io.InputStream;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.codec.DecodeMonitor;
/**
* Defines the API to obtain Message instances from a mime stream.
*/
public abstract class MessageBuilder {
public abstract Message newMessage();
public abstract Message newMessage(Message source);
public abstract Message parse(InputStream source) throws MimeException, IOException;
public abstract void setDecodeMonitor(
DecodeMonitor decodeMonitor);
public abstract void setContentDecoding(boolean contentDecoding);
public abstract void setFlatMode();
}

View File

@ -0,0 +1,41 @@
/****************************************************************
* 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 org.apache.james.mime4j.MimeException;
/**
* A MessageBuilderFactory is used to create EntityBuilder instances.
*
* MessageBuilderFactory.newInstance() is used to get access to an implementation
* of MessageBuilderFactory.
* Then the method newMessageBuilder is used to create a new EntityBuilder object.
*/
public abstract class MessageBuilderFactory {
public abstract MessageBuilder newMessageBuilder() throws MimeException;
public static MessageBuilderFactory newInstance() throws MimeException {
return ServiceLoader.load(MessageBuilderFactory.class);
}
public abstract void setAttribute(String name, Object value) throws IllegalArgumentException;
}

View File

@ -0,0 +1,238 @@
/****************************************************************
* 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.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* 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.
*/
public abstract class Multipart implements Body {
protected List<Entity> bodyParts = new LinkedList<Entity>();
private Entity parent = null;
private String subType;
/**
* Creates a new empty <code>Multipart</code> instance.
*/
public Multipart(String subType) {
this.subType = subType;
}
/**
* Gets the multipart sub-type. E.g. <code>alternative</code> (the
* default) or <code>parallel</code>. 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. <code>alternative</code> or
* <code>parallel</code>. See RFC 2045 for common sub-types and their
* meaning.
*
* @param subType
* the sub-type.
*/
public void setSubType(String subType) {
this.subType = subType;
}
/**
* @see org.apache.james.mime4j.dom.Body#getParent()
*/
public Entity getParent() {
return parent;
}
/**
* @see org.apache.james.mime4j.dom.Body#setParent(org.apache.james.mime4j.dom.Entity)
*/
public void setParent(Entity parent) {
this.parent = parent;
for (Entity bodyPart : bodyParts) {
bodyPart.setParent(parent);
}
}
/**
* Returns the number of body parts.
*
* @return number of <code>Entity</code> objects.
*/
public int getCount() {
return bodyParts.size();
}
/**
* Gets the list of body parts. The list is immutable.
*
* @return the list of <code>Entity</code> objects.
*/
public List<Entity> getBodyParts() {
return Collections.unmodifiableList(bodyParts);
}
/**
* Sets the list of body parts.
*
* @param bodyParts
* the new list of <code>Entity</code> objects.
*/
public void setBodyParts(List<Entity> bodyParts) {
this.bodyParts = bodyParts;
for (Entity bodyPart : bodyParts) {
bodyPart.setParent(parent);
}
}
/**
* Adds a body part to the end of the list of body parts.
*
* @param bodyPart
* the body part.
*/
public void addBodyPart(Entity 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.
*
* @param bodyPart
* the body part.
* @param index
* index at which the specified body part is to be inserted.
* @throws IndexOutOfBoundsException
* if the index is out of range (index &lt; 0 || index &gt;
* getCount()).
*/
public void addBodyPart(Entity 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.
*
* @param index
* index of the body part to be removed.
* @return the removed body part.
* @throws IndexOutOfBoundsException
* if the index is out of range (index &lt; 0 || index &gt;=
* getCount()).
*/
public Entity removeBodyPart(int index) {
Entity bodyPart = bodyParts.remove(index);
bodyPart.setParent(null);
return bodyPart;
}
/**
* Replaces the body part at the specified position in the list of body
* parts with the specified body part.
*
* @param bodyPart
* body part to be stored at the specified position.
* @param index
* index of body part to replace.
* @return the replaced body part.
* @throws IndexOutOfBoundsException
* if the index is out of range (index &lt; 0 || index &gt;=
* getCount()).
*/
public Entity replaceBodyPart(Entity bodyPart, int index) {
if (bodyPart == null)
throw new IllegalArgumentException();
Entity replacedEntity = bodyParts.set(index, bodyPart);
if (bodyPart == replacedEntity)
throw new IllegalArgumentException(
"Cannot replace body part with itself");
bodyPart.setParent(parent);
replacedEntity.setParent(null);
return replacedEntity;
}
/**
* Gets the preamble or null if the message has no preamble.
*
* @return the preamble.
*/
public abstract String getPreamble();
/**
* Sets the preamble with a value or null to remove the preamble.
*
* @param preamble
* the preamble.
*/
public abstract void setPreamble(String preamble);
/**
* Gets the epilogue or null if the message has no epilogue
*
* @return the epilogue.
*/
public abstract String getEpilogue();
/**
* Sets the epilogue value, or remove it if the value passed is null.
*
* @param epilogue
* the epilogue.
*/
public abstract void setEpilogue(String epilogue);
/**
* Disposes of the BodyParts of this Multipart. Note that the dispose call
* does not get forwarded to the parent entity of this Multipart.
*
* @see org.apache.james.mime4j.dom.Disposable#dispose()
*/
public void dispose() {
for (Entity bodyPart : bodyParts) {
bodyPart.dispose();
}
}
}

View File

@ -0,0 +1,95 @@
/****************************************************************
* 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.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
/**
* Utility class to load Service Providers (SPI).
* This will deprecated as soon as mime4j will be upgraded to Java6
* as Java6 has javax.util.ServiceLoader as a core class.
*/
class ServiceLoader {
private ServiceLoader() {
}
/**
* Loads a Service Provider for the given interface/class (SPI).
*/
static <T> T load(Class<T> spiClass) {
String spiResURI = "META-INF/services/" + spiClass.getName();
ClassLoader classLoader = spiClass.getClassLoader();
Enumeration<URL> resources;
try {
resources = classLoader.getResources(spiResURI);
} catch (IOException e) {
return null;
}
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(resource
.openStream()));
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
int cmtIdx = line.indexOf('#');
if (cmtIdx != -1) {
line = line.substring(0, cmtIdx);
line = line.trim();
}
if (line.length() == 0) {
continue;
}
Class<?> implClass;
try {
implClass = classLoader.loadClass(line);
if (spiClass.isAssignableFrom(implClass)) {
Object impl = implClass.newInstance();
return spiClass.cast(impl);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,139 @@
/****************************************************************
* 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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Abstract implementation of a single message body; that is, a body that does
* not contain (directly or indirectly) any other child bodies. It also provides
* the parent functionality required by bodies.
*/
public abstract class SingleBody implements Body {
private Entity parent = null;
/**
* Sole constructor.
*/
protected SingleBody() {
}
/**
* @see org.apache.james.mime4j.dom.Body#getParent()
*/
public Entity getParent() {
return parent;
}
/**
* @see org.apache.james.mime4j.dom.Body#setParent(org.apache.james.mime4j.dom.Entity)
*/
public void setParent(Entity parent) {
this.parent = parent;
}
/**
* Gets a <code>InputStream</code> which reads the bytes of the body.
*
* @return the stream, transfer decoded
* @throws IOException
* on I/O errors.
*/
public abstract InputStream getInputStream() throws IOException;
/**
* Writes this single body to the given stream. The default implementation copies
* the input stream obtained by {@link #getInputStream()} to the specified output
* stream. May be overwritten by a subclass to improve performance.
*
* @param out
* the stream to write to.
* @throws IOException
* in case of an I/O error
*/
public void writeTo(OutputStream out) throws IOException {
if (out == null)
throw new IllegalArgumentException();
InputStream in = getInputStream();
SingleBody.copy(in, out);
in.close();
}
/**
* Returns a copy of this <code>SingleBody</code> (optional operation).
* <p>
* The general contract of this method is as follows:
* <ul>
* <li>Invoking {@link #getParent()} on the copy returns <code>null</code>.
* That means that the copy is detached from the parent entity of this
* <code>SingleBody</code>. The copy may get attached to a different
* entity later on.</li>
* <li>The underlying content does not have to be copied. Instead it may be
* shared between multiple copies of a <code>SingleBody</code>.</li>
* <li>If the underlying content is shared by multiple copies the
* implementation has to make sure that the content gets deleted when the
* last copy gets disposed of (and not before that).</li>
* </ul>
* <p>
* This implementation always throws an
* <code>UnsupportedOperationException</code>.
*
* @return a copy of this <code>SingleBody</code>.
* @throws UnsupportedOperationException
* if the <code>copy</code> operation is not supported by this
* single body.
*/
public SingleBody copy() {
throw new UnsupportedOperationException();
}
/**
* Subclasses should override this method if they have allocated resources
* that need to be freed explicitly (e.g. cannot be simply reclaimed by the
* garbage collector).
*
* The default implementation of this method does nothing.
*
* @see org.apache.james.mime4j.dom.Disposable#dispose()
*/
public void dispose() {
}
static final int DEFAULT_ENCODING_BUFFER_SIZE = 1024;
/**
* Copies the contents of one stream to the other.
* @param in not null
* @param out not null
* @throws IOException
*/
private static void copy(final InputStream in, final OutputStream out) throws IOException {
final byte[] buffer = new byte[DEFAULT_ENCODING_BUFFER_SIZE];
int inputLength;
while (-1 != (inputLength = in.read(buffer))) {
out.write(buffer, 0, inputLength);
}
}
}

View File

@ -17,26 +17,37 @@
* under the License. *
****************************************************************/
package org.apache.james.mime4j.message;
package org.apache.james.mime4j.dom;
import java.io.IOException;
import java.io.Reader;
/**
* Encapsulates the contents of a <code>text/*</code> entity body.
*
*
* @version $Id: TextBody.java,v 1.3 2004/10/02 12:41:11 ntherning Exp $
*/
public interface TextBody extends Body {
public abstract class TextBody extends SingleBody {
/**
* Sole constructor.
*/
protected TextBody() {
}
/**
* Returns the MIME charset of this text body.
*
* @return the MIME charset.
*/
public abstract String getMimeCharset();
/**
* Gets a <code>Reader</code> which may be used to read out the contents
* of this body.
*
*
* @return the <code>Reader</code>.
* @throws IOException on I/O errors.
* @throws IOException
* on I/O errors.
*/
Reader getReader() throws IOException;
public abstract Reader getReader() throws IOException;
}

View File

@ -17,36 +17,32 @@
* under the License. *
****************************************************************/
package org.apache.james.mime4j.field.address;
package org.apache.james.mime4j.dom.address;
import java.util.ArrayList;
import java.io.Serializable;
import java.util.List;
/**
* The abstract base for classes that represent RFC2822 addresses.
* This includes groups and mailboxes.
*
* Currently, no public methods are introduced on this class.
*
*
* The abstract base for classes that represent RFC2822 addresses. This includes
* groups and mailboxes.
*/
public abstract class Address {
public abstract class Address implements Serializable {
/**
* Adds any mailboxes represented by this address
* into the given ArrayList. Note that this method
* has default (package) access, so a doAddMailboxesTo
* method is needed to allow the behavior to be
* overridden by subclasses.
*/
final void addMailboxesTo(ArrayList results) {
doAddMailboxesTo(results);
}
/**
* Adds any mailboxes represented by this address
* into the given ArrayList. Must be overridden by
* concrete subclasses.
*/
protected abstract void doAddMailboxesTo(ArrayList results);
private static final long serialVersionUID = 634090661990433426L;
}
/**
* Adds any mailboxes represented by this address into the given List. Note
* that this method has default (package) access, so a doAddMailboxesTo
* method is needed to allow the behavior to be overridden by subclasses.
*/
final void addMailboxesTo(List<Mailbox> results) {
doAddMailboxesTo(results);
}
/**
* Adds any mailboxes represented by this address into the given List. Must
* be overridden by concrete subclasses.
*/
protected abstract void doAddMailboxesTo(List<Mailbox> results);
}

View File

@ -0,0 +1,98 @@
/****************************************************************
* 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.address;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An immutable, random-access list of Address objects.
*/
public class AddressList extends AbstractList<Address> implements Serializable {
private static final long serialVersionUID = 1L;
private final List<? extends Address> addresses;
/**
* @param addresses
* A List that contains only Address objects.
* @param dontCopy
* true iff it is not possible for the addresses list to be
* modified by someone else.
*/
public AddressList(List<? extends Address> addresses, boolean dontCopy) {
if (addresses != null)
this.addresses = dontCopy ? addresses : new ArrayList<Address>(
addresses);
else
this.addresses = Collections.emptyList();
}
/**
* The number of elements in this list.
*/
@Override
public int size() {
return addresses.size();
}
/**
* Gets an address.
*/
@Override
public Address get(int index) {
return addresses.get(index);
}
/**
* Returns a flat list of all mailboxes represented in this address list.
* Use this if you don't care about grouping.
*/
public MailboxList flatten() {
// in the common case, all addresses are mailboxes
boolean groupDetected = false;
for (Address addr : addresses) {
if (!(addr instanceof Mailbox)) {
groupDetected = true;
break;
}
}
if (!groupDetected) {
@SuppressWarnings("unchecked")
final List<Mailbox> mailboxes = (List<Mailbox>) addresses;
return new MailboxList(mailboxes, true);
}
List<Mailbox> results = new ArrayList<Mailbox>();
for (Address addr : addresses) {
addr.addMailboxesTo(results);
}
// copy-on-construct this time, because subclasses
// could have held onto a reference to the results
return new MailboxList(results, false);
}
}

View File

@ -0,0 +1,95 @@
/****************************************************************
* 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.address;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An immutable, random-access list of Strings (that are supposedly domain names
* or domain literals).
*/
public class DomainList extends AbstractList<String> implements Serializable {
private static final long serialVersionUID = 1L;
private final List<String> domains;
/**
* @param domains
* A List that contains only String objects.
* @param dontCopy
* true iff it is not possible for the domains list to be
* modified by someone else.
*/
public DomainList(List<String> domains, boolean dontCopy) {
if (domains != null)
this.domains = dontCopy ? domains : new ArrayList<String>(domains);
else
this.domains = Collections.emptyList();
}
/**
* The number of elements in this list.
*/
@Override
public int size() {
return domains.size();
}
/**
* Gets the domain name or domain literal at the specified index.
*
* @throws IndexOutOfBoundsException
* If index is &lt; 0 or &gt;= size().
*/
@Override
public String get(int index) {
return domains.get(index);
}
/**
* Returns the list of domains formatted as a route string (not including
* the trailing ':').
*/
public String toRouteString() {
StringBuilder sb = new StringBuilder();
for (String domain : domains) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append("@");
sb.append(domain);
}
return sb.toString();
}
@Override
public String toString() {
return toRouteString();
}
}

View File

@ -0,0 +1,113 @@
/****************************************************************
* 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.address;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* A named group of zero or more mailboxes.
*/
public class Group extends Address {
private static final long serialVersionUID = 1L;
private final String name;
private final MailboxList mailboxList;
/**
* @param name
* The group name.
* @param mailboxes
* The mailboxes in this group.
*/
public Group(String name, MailboxList mailboxes) {
if (name == null)
throw new IllegalArgumentException();
if (mailboxes == null)
throw new IllegalArgumentException();
this.name = name;
this.mailboxList = mailboxes;
}
/**
* @param name
* The group name.
* @param mailboxes
* The mailboxes in this group.
*/
public Group(String name, Mailbox... mailboxes) {
this(name, new MailboxList(Arrays.asList(mailboxes), true));
}
/**
* @param name
* The group name.
* @param mailboxes
* The mailboxes in this group.
*/
public Group(String name, Collection<Mailbox> mailboxes) {
this(name, new MailboxList(new ArrayList<Mailbox>(mailboxes), true));
}
/**
* Returns the group name.
*/
public String getName() {
return name;
}
/**
* Returns the mailboxes in this group.
*/
public MailboxList getMailboxes() {
return mailboxList;
}
@Override
protected void doAddMailboxesTo(List<Mailbox> results) {
for (Mailbox mailbox : mailboxList) {
results.add(mailbox);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append(':');
boolean first = true;
for (Mailbox mailbox : mailboxList) {
if (first) {
first = false;
} else {
sb.append(',');
}
sb.append(' ');
sb.append(mailbox);
}
sb.append(";");
return sb.toString();
}
}

View File

@ -0,0 +1,204 @@
/****************************************************************
* 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.address;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.apache.james.mime4j.util.LangUtils;
/**
* Represents a single e-mail address.
*/
public class Mailbox extends Address {
private static final long serialVersionUID = 1L;
private static final DomainList EMPTY_ROUTE_LIST = new DomainList(
Collections.<String> emptyList(), true);
private final String name;
private final DomainList route;
private final String localPart;
private final String domain;
/**
* Creates a named mailbox with a route. Routes are obsolete.
*
* @param name
* the name of the e-mail address. May be <code>null</code>.
* @param route
* The zero or more domains that make up the route. May be
* <code>null</code>.
* @param localPart
* The part of the e-mail address to the left of the "@".
* @param domain
* The part of the e-mail address to the right of the "@".
*/
public Mailbox(String name, DomainList route, String localPart,
String domain) {
if (localPart == null || localPart.length() == 0)
throw new IllegalArgumentException();
this.name = name == null || name.length() == 0 ? null : name;
this.route = route == null ? EMPTY_ROUTE_LIST : route;
this.localPart = localPart;
this.domain = domain == null || domain.length() == 0 ? null : domain;
}
/**
* Creates a named mailbox based on an unnamed mailbox. Package private;
* internally used by Builder.
*/
Mailbox(String name, Mailbox baseMailbox) {
this(name, baseMailbox.getRoute(), baseMailbox.getLocalPart(),
baseMailbox.getDomain());
}
/**
* Creates an unnamed mailbox without a route. Routes are obsolete.
*
* @param localPart
* The part of the e-mail address to the left of the "@".
* @param domain
* The part of the e-mail address to the right of the "@".
*/
public Mailbox(String localPart, String domain) {
this(null, null, localPart, domain);
}
/**
* Creates an unnamed mailbox with a route. Routes are obsolete.
*
* @param route
* The zero or more domains that make up the route. May be
* <code>null</code>.
* @param localPart
* The part of the e-mail address to the left of the "@".
* @param domain
* The part of the e-mail address to the right of the "@".
*/
public Mailbox(DomainList route, String localPart, String domain) {
this(null, route, localPart, domain);
}
/**
* Creates a named mailbox without a route. Routes are obsolete.
*
* @param name
* the name of the e-mail address. May be <code>null</code>.
* @param localPart
* The part of the e-mail address to the left of the "@".
* @param domain
* The part of the e-mail address to the right of the "@".
*/
public Mailbox(String name, String localPart, String domain) {
this(name, null, localPart, domain);
}
/**
* Returns the name of the mailbox or <code>null</code> if it does not
* have a name.
*/
public String getName() {
return name;
}
/**
* Returns the route list. If the mailbox does not have a route an empty
* domain list is returned.
*/
public DomainList getRoute() {
return route;
}
/**
* Returns the left part of the e-mail address (before "@").
*/
public String getLocalPart() {
return localPart;
}
/**
* Returns the right part of the e-mail address (after "@").
*/
public String getDomain() {
return domain;
}
/**
* Returns the address in the form <i>localPart@domain</i>.
*
* @return the address part of this mailbox.
*/
public String getAddress() {
if (domain == null) {
return localPart;
} else {
return localPart + '@' + domain;
}
}
@Override
protected final void doAddMailboxesTo(List<Mailbox> results) {
results.add(this);
}
@Override
public int hashCode() {
int hash = LangUtils.HASH_SEED;
hash = LangUtils.hashCode(hash, this.localPart);
hash = LangUtils.hashCode(hash, this.domain != null ?
this.domain.toLowerCase(Locale.US) : null);
return hash;
}
/**
* Indicates whether some other object is "equal to" this mailbox.
* <p>
* An object is considered to be equal to this mailbox if it is an instance
* of class <code>Mailbox</code> that holds the same address as this one.
* The domain is considered to be case-insensitive but the local-part is not
* (because of RFC 5321: <cite>the local-part of a mailbox MUST BE treated
* as case sensitive</cite>).
*
* @param obj
* the object to test for equality.
* @return <code>true</code> if the specified object is a
* <code>Mailbox</code> that holds the same address as this one.
*/
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof Mailbox))
return false;
Mailbox that = (Mailbox) obj;
return LangUtils.equals(this.localPart, that.localPart) &&
LangUtils.equalsIgnoreCase(this.domain, that.domain);
}
@Override
public String toString() {
return getAddress();
}
}

View File

@ -17,55 +17,52 @@
* under the License. *
****************************************************************/
package org.apache.james.mime4j.field.address;
package org.apache.james.mime4j.dom.address;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An immutable, random-access list of Mailbox objects.
*
*
*/
public class MailboxList {
public class MailboxList extends AbstractList<Mailbox> implements Serializable {
private ArrayList mailboxes;
/**
* @param mailboxes An ArrayList that contains only Mailbox objects.
* @param dontCopy true iff it is not possible for the mailboxes ArrayList to be modified by someone else.
*/
public MailboxList(ArrayList mailboxes, boolean dontCopy) {
if (mailboxes != null)
this.mailboxes = (dontCopy ? mailboxes : (ArrayList) mailboxes.clone());
else
this.mailboxes = new ArrayList(0);
}
/**
* The number of elements in this list.
*/
public int size() {
return mailboxes.size();
}
/**
* Gets an address.
*/
public Mailbox get(int index) {
if (0 > index || size() <= index)
throw new IndexOutOfBoundsException();
return (Mailbox) mailboxes.get(index);
}
/**
* Dumps a representation of this mailbox list to
* stdout, for debugging purposes.
*/
public void print() {
for (int i = 0; i < size(); i++) {
Mailbox mailbox = get(i);
System.out.println(mailbox.toString());
}
}
private static final long serialVersionUID = 1L;
private final List<Mailbox> mailboxes;
/**
* @param mailboxes
* A List that contains only Mailbox objects.
* @param dontCopy
* true iff it is not possible for the mailboxes list to be
* modified by someone else.
*/
public MailboxList(List<Mailbox> mailboxes, boolean dontCopy) {
if (mailboxes != null)
this.mailboxes = dontCopy ? mailboxes : new ArrayList<Mailbox>(
mailboxes);
else
this.mailboxes = Collections.emptyList();
}
/**
* The number of elements in this list.
*/
@Override
public int size() {
return mailboxes.size();
}
/**
* Gets an address.
*/
@Override
public Mailbox get(int index) {
return mailboxes.get(index);
}
}

View File

@ -17,17 +17,12 @@
* under the License. *
****************************************************************/
package org.apache.james.mime4j.field.datetime;
package org.apache.james.mime4j.dom.datetime;
import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
import org.apache.james.mime4j.field.datetime.parser.ParseException;
import org.apache.james.mime4j.field.datetime.parser.TokenMgrError;
import java.util.Date;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.Date;
import java.util.GregorianCalendar;
import java.io.StringReader;
import java.util.TimeZone;
public class DateTime {
private final Date date;
@ -112,16 +107,59 @@ public class DateTime {
}
public void print() {
System.out.println(getYear() + " " + getMonth() + " " + getDay() + "; " + getHour() + " " + getMinute() + " " + getSecond() + " " + getTimeZone());
System.out.println(toString());
}
@Override
public String toString() {
return getYear() + " " + getMonth() + " " + getDay() + "; " + getHour() + " " + getMinute() + " " + getSecond() + " " + getTimeZone();
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((date == null) ? 0 : date.hashCode());
result = PRIME * result + day;
result = PRIME * result + hour;
result = PRIME * result + minute;
result = PRIME * result + month;
result = PRIME * result + second;
result = PRIME * result + timeZone;
result = PRIME * result + year;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final DateTime other = (DateTime) obj;
if (date == null) {
if (other.date != null)
return false;
} else if (!date.equals(other.date))
return false;
if (day != other.day)
return false;
if (hour != other.hour)
return false;
if (minute != other.minute)
return false;
if (month != other.month)
return false;
if (second != other.second)
return false;
if (timeZone != other.timeZone)
return false;
if (year != other.year)
return false;
return true;
}
public static DateTime parse(String dateString) throws ParseException {
try {
return new DateTimeParser(new StringReader(dateString)).parseAll();
}
catch (TokenMgrError err) {
throw new ParseException(err.getMessage());
}
}
}

View File

@ -0,0 +1,28 @@
/****************************************************************
* 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.field;
import org.apache.james.mime4j.dom.address.AddressList;
public interface AddressListField extends ParsedField {
AddressList getAddressList();
}

View File

@ -0,0 +1,117 @@
package org.apache.james.mime4j.dom.field;
import java.util.Date;
import java.util.Map;
public interface ContentDispositionField extends ParsedField {
/** The <code>inline</code> disposition type. */
public static final String DISPOSITION_TYPE_INLINE = "inline";
/** The <code>attachment</code> disposition type. */
public static final String DISPOSITION_TYPE_ATTACHMENT = "attachment";
/** The name of the <code>filename</code> parameter. */
public static final String PARAM_FILENAME = "filename";
/** The name of the <code>creation-date</code> parameter. */
public static final String PARAM_CREATION_DATE = "creation-date";
/** The name of the <code>modification-date</code> parameter. */
public static final String PARAM_MODIFICATION_DATE = "modification-date";
/** The name of the <code>read-date</code> parameter. */
public static final String PARAM_READ_DATE = "read-date";
/** The name of the <code>size</code> parameter. */
public static final String PARAM_SIZE = "size";
/**
* Gets the disposition type defined in this Content-Disposition field.
*
* @return the disposition type or an empty string if not set.
*/
String getDispositionType();
/**
* Gets the value of a parameter. Parameter names are case-insensitive.
*
* @param name
* the name of the parameter to get.
* @return the parameter value or <code>null</code> if not set.
*/
String getParameter(String name);
/**
* Gets all parameters.
*
* @return the parameters.
*/
Map<String, String> getParameters();
/**
* Determines if the disposition type of this field matches the given one.
*
* @param dispositionType
* the disposition type to match against.
* @return <code>true</code> if the disposition type of this field
* matches, <code>false</code> otherwise.
*/
boolean isDispositionType(String dispositionType);
/**
* Return <code>true</code> if the disposition type of this field is
* <i>inline</i>, <code>false</code> otherwise.
*
* @return <code>true</code> if the disposition type of this field is
* <i>inline</i>, <code>false</code> otherwise.
*/
boolean isInline();
/**
* Return <code>true</code> if the disposition type of this field is
* <i>attachment</i>, <code>false</code> otherwise.
*
* @return <code>true</code> if the disposition type of this field is
* <i>attachment</i>, <code>false</code> otherwise.
*/
public abstract boolean isAttachment();
/**
* Gets the value of the <code>filename</code> parameter if set.
*
* @return the <code>filename</code> parameter value or <code>null</code>
* if not set.
*/
String getFilename();
/**
* Gets the value of the <code>creation-date</code> parameter if set and
* valid.
*
* @return the <code>creation-date</code> parameter value or
* <code>null</code> if not set or invalid.
*/
Date getCreationDate();
/**
* Gets the value of the <code>modification-date</code> parameter if set
* and valid.
*
* @return the <code>modification-date</code> parameter value or
* <code>null</code> if not set or invalid.
*/
Date getModificationDate();
/**
* Gets the value of the <code>read-date</code> parameter if set and
* valid.
*
* @return the <code>read-date</code> parameter value or <code>null</code>
* if not set or invalid.
*/
Date getReadDate();
/**
* Gets the value of the <code>size</code> parameter if set and valid.
*
* @return the <code>size</code> parameter value or <code>-1</code> if
* not set or invalid.
*/
long getSize();
}

View File

@ -0,0 +1,31 @@
/****************************************************************
* 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.field;
public interface ContentTransferEncodingField extends ParsedField {
/**
* Gets the encoding defined in this field.
*
* @return the encoding or an empty string if not set.
*/
String getEncoding();
}

View File

@ -0,0 +1,97 @@
/****************************************************************
* 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.field;
import java.util.Map;
public interface ContentTypeField extends ParsedField {
/** The prefix of all <code>multipart</code> MIME types. */
public static final String TYPE_MULTIPART_PREFIX = "multipart/";
/** The <code>multipart/digest</code> MIME type. */
public static final String TYPE_MULTIPART_DIGEST = "multipart/digest";
/** The <code>text/plain</code> MIME type. */
public static final String TYPE_TEXT_PLAIN = "text/plain";
/** The <code>message/rfc822</code> MIME type. */
public static final String TYPE_MESSAGE_RFC822 = "message/rfc822";
/** The name of the <code>boundary</code> parameter. */
public static final String PARAM_BOUNDARY = "boundary";
/** The name of the <code>charset</code> parameter. */
public static final String PARAM_CHARSET = "charset";
/**
* Gets the MIME type defined in this Content-Type field.
*
* @return the MIME type or an empty string if not set.
*/
String getMimeType();
/**
* Gets the value of a parameter. Parameter names are case-insensitive.
*
* @param name
* the name of the parameter to get.
* @return the parameter value or <code>null</code> if not set.
*/
String getParameter(String name);
/**
* Gets all parameters.
*
* @return the parameters.
*/
Map<String, String> getParameters();
/**
* Determines if the MIME type of this field matches the given one.
*
* @param mimeType
* the MIME type to match against.
* @return <code>true</code> if the MIME type of this field matches,
* <code>false</code> otherwise.
*/
boolean isMimeType(String mimeType);
/**
* Determines if the MIME type of this field is <code>multipart/*</code>.
*
* @return <code>true</code> if this field is has a
* <code>multipart/*</code> MIME type, <code>false</code>
* otherwise.
*/
boolean isMultipart();
/**
* Gets the value of the <code>boundary</code> parameter if set.
*
* @return the <code>boundary</code> parameter value or <code>null</code>
* if not set.
*/
String getBoundary();
/**
* Gets the value of the <code>charset</code> parameter if set.
*
* @return the <code>charset</code> parameter value or <code>null</code>
* if not set.
*/
String getCharset();
}

View File

@ -0,0 +1,9 @@
package org.apache.james.mime4j.dom.field;
import java.util.Date;
public interface DateTimeField extends ParsedField {
Date getDate();
}

View File

@ -0,0 +1,51 @@
/****************************************************************
* 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.field;
import java.io.IOException;
import java.io.OutputStream;
/**
* Abstract MIME field.
*/
public interface Field {
/**
* Gets the name of the field (<code>Subject</code>, <code>From</code>, etc).
*
* @return the field name.
*/
String getName();
/**
* Gets the unparsed and possibly encoded (see RFC 2047) field body string.
*
* @return the unparsed field body string.
*/
String getBody();
/**
* Writes the original raw field bytes to an output stream.
* The output is folded, the last CRLF is not included.
* @throws IOException
*/
void writeTo(OutputStream out) throws IOException;
}

View File

@ -17,46 +17,37 @@
* under the License. *
****************************************************************/
package org.apache.james.mime4j.decoder;
package org.apache.james.mime4j.dom.field;
import java.util.Iterator;
/**
* Constants for common header field names.
*/
public class FieldName {
public class ByteQueue {
public static final String CONTENT_DISPOSITION = "Content-Disposition";
public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
public static final String CONTENT_TYPE = "Content-Type";
private UnboundedFifoByteBuffer buf;
private int initialCapacity = -1;
public static final String DATE = "Date";
public static final String MESSAGE_ID = "Message-ID";
public static final String SUBJECT = "Subject";
public ByteQueue() {
buf = new UnboundedFifoByteBuffer();
public static final String FROM = "From";
public static final String SENDER = "Sender";
public static final String TO = "To";
public static final String CC = "Cc";
public static final String BCC = "Bcc";
public static final String REPLY_TO = "Reply-To";
public static final String RESENT_DATE = "Resent-Date";
public static final String RESENT_FROM = "Resent-From";
public static final String RESENT_SENDER = "Resent-Sender";
public static final String RESENT_TO = "Resent-To";
public static final String RESENT_CC = "Resent-Cc";
public static final String RESENT_BCC = "Resent-Bcc";
private FieldName() {
}
public ByteQueue(int initialCapacity) {
buf = new UnboundedFifoByteBuffer(initialCapacity);
this.initialCapacity = initialCapacity;
}
public void enqueue(byte b) {
buf.add(b);
}
public byte dequeue() {
return buf.remove();
}
public int count() {
return buf.size();
}
public void clear() {
if (initialCapacity != -1)
buf = new UnboundedFifoByteBuffer(initialCapacity);
else
buf = new UnboundedFifoByteBuffer();
}
public Iterator iterator() {
return buf.iterator();
}
}

View File

@ -0,0 +1,28 @@
/****************************************************************
* 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.field;
import org.apache.james.mime4j.dom.address.Mailbox;
public interface MailboxField extends ParsedField {
Mailbox getMailbox();
}

View File

@ -0,0 +1,28 @@
/****************************************************************
* 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.field;
import org.apache.james.mime4j.dom.address.MailboxList;
public interface MailboxListField extends ParsedField {
MailboxList getMailboxList();
}

View File

@ -0,0 +1,64 @@
/****************************************************************
* 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.field;
import org.apache.james.mime4j.MimeException;
/**
* This exception is thrown when parse errors are encountered.
*/
public class ParseException extends MimeException {
private static final long serialVersionUID = 1L;
/**
* Constructs a new parse exception with the specified detail message.
*
* @param message
* detail message
*/
protected ParseException(String message) {
super(message);
}
/**
* Constructs a new parse exception with the specified cause.
*
* @param cause
* the cause
*/
protected ParseException(Throwable cause) {
super(cause);
}
/**
* Constructs a new parse exception with the specified detail message and
* cause.
*
* @param message
* detail message
* @param cause
* the cause
*/
protected ParseException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,45 @@
/****************************************************************
* 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.field;
public interface ParsedField extends Field {
/**
* Returns <code>true</code> if this field is valid, i.e. no errors were
* encountered while parsing the field value.
*
* @return <code>true</code> if this field is valid, <code>false</code>
* otherwise.
* @see #getParseException()
*/
boolean isValidField();
/**
* Returns the exception that was thrown by the field parser while parsing
* the field value. The result is <code>null</code> if the field is valid
* and no errors were encountered.
*
* @return the exception that was thrown by the field parser or
* <code>null</code> if the field is valid.
*/
ParseException getParseException();
}

View File

@ -0,0 +1,26 @@
/****************************************************************
* 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.field;
public interface UnstructuredField extends ParsedField {
String getValue();
}

View File

@ -0,0 +1,97 @@
/****************************************************************
* 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.field;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.field.ParseException;
import org.apache.james.mime4j.dom.field.ParsedField;
import org.apache.james.mime4j.util.ByteSequence;
/**
* The base class of all field classes.
*/
public abstract class AbstractField implements ParsedField {
private final String name;
private final String body;
private final ByteSequence raw;
protected DecodeMonitor monitor;
protected AbstractField(
final String name,
final String body,
final ByteSequence raw,
final DecodeMonitor monitor) {
this.name = name;
this.body = body;
this.raw = raw;
this.monitor = monitor != null ? monitor : DecodeMonitor.SILENT;
}
/**
* Gets the name of the field (<code>Subject</code>,
* <code>From</code>, etc).
*
* @return the field name.
*/
public String getName() {
return name;
}
/**
* @see org.apache.james.mime4j.dom.field.Field#writeTo(java.io.OutputStream)
*/
public void writeTo(OutputStream out) throws IOException {
out.write(raw.toByteArray());
}
/**
* Gets the unfolded, unparsed and possibly encoded (see RFC 2047) field
* body string.
*
* @return the unfolded unparsed field body string.
*/
public String getBody() {
return body;
}
/**
* @see ParsedField#isValidField()
*/
public boolean isValidField() {
return getParseException() == null;
}
/**
* @see ParsedField#getParseException()
*/
public ParseException getParseException() {
return null;
}
@Override
public String toString() {
return name + ": " + body;
}
}

View File

@ -19,45 +19,63 @@
package org.apache.james.mime4j.field;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.james.mime4j.field.address.AddressList;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.address.AddressList;
import org.apache.james.mime4j.field.address.parser.AddressBuilder;
import org.apache.james.mime4j.field.address.parser.ParseException;
import org.apache.james.mime4j.util.ByteSequence;
/**
* Address list field such as <code>To</code> or <code>Reply-To</code>.
*/
public class AddressListFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.AddressListField {
private boolean parsed = false;
public class AddressListField extends Field {
private AddressList addressList;
private ParseException parseException;
protected AddressListField(String name, String body, String raw, AddressList addressList, ParseException parseException) {
super(name, body, raw);
this.addressList = addressList;
this.parseException = parseException;
AddressListFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) {
super(name, body, raw, monitor);
}
/**
* @see org.apache.james.mime4j.dom.field.AddressListField#getAddressList()
*/
public AddressList getAddressList() {
if (!parsed)
parse();
return addressList;
}
/**
* @see org.apache.james.mime4j.dom.field.AddressListField#getParseException()
*/
@Override
public ParseException getParseException() {
if (!parsed)
parse();
return parseException;
}
public static class Parser implements FieldParser {
private static Log log = LogFactory.getLog(Parser.class);
private void parse() {
String body = getBody();
public Field parse(final String name, final String body, final String raw) {
AddressList addressList = null;
ParseException parseException = null;
try {
addressList = AddressList.parse(body);
}
catch (ParseException e) {
if (log.isDebugEnabled()) {
log.debug("Parsing value '" + body + "': "+ e.getMessage());
}
parseException = e;
}
return new AddressListField(name, body, raw, addressList, parseException);
try {
addressList = AddressBuilder.parseAddressList(body, monitor);
} catch (ParseException e) {
parseException = e;
}
parsed = true;
}
static final FieldParser<AddressListFieldImpl> PARSER = new FieldParser<AddressListFieldImpl>() {
public AddressListFieldImpl parse(final String name, final String body,
final ByteSequence raw, DecodeMonitor monitor) {
return new AddressListFieldImpl(name, body, raw, monitor);
}
};
}

View File

@ -0,0 +1,253 @@
/****************************************************************
* 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.field;
import java.io.StringReader;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.field.contentdisposition.parser.ContentDispositionParser;
import org.apache.james.mime4j.field.contentdisposition.parser.ParseException;
import org.apache.james.mime4j.field.contentdisposition.parser.TokenMgrError;
import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
import org.apache.james.mime4j.util.ByteSequence;
/**
* Represents a <code>Content-Disposition</code> field.
*/
public class ContentDispositionFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.ContentDispositionField {
private boolean parsed = false;
private String dispositionType = "";
private Map<String, String> parameters = new HashMap<String, String>();
private ParseException parseException;
private boolean creationDateParsed;
private Date creationDate;
private boolean modificationDateParsed;
private Date modificationDate;
private boolean readDateParsed;
private Date readDate;
ContentDispositionFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) {
super(name, body, raw, monitor);
}
/**
* Gets the exception that was raised during parsing of the field value, if
* any; otherwise, null.
*/
@Override
public ParseException getParseException() {
if (!parsed)
parse();
return parseException;
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#getDispositionType()
*/
public String getDispositionType() {
if (!parsed)
parse();
return dispositionType;
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#getParameter(java.lang.String)
*/
public String getParameter(String name) {
if (!parsed)
parse();
return parameters.get(name.toLowerCase());
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#getParameters()
*/
public Map<String, String> getParameters() {
if (!parsed)
parse();
return Collections.unmodifiableMap(parameters);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#isDispositionType(java.lang.String)
*/
public boolean isDispositionType(String dispositionType) {
if (!parsed)
parse();
return this.dispositionType.equalsIgnoreCase(dispositionType);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#isInline()
*/
public boolean isInline() {
if (!parsed)
parse();
return dispositionType.equals(DISPOSITION_TYPE_INLINE);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#isAttachment()
*/
public boolean isAttachment() {
if (!parsed)
parse();
return dispositionType.equals(DISPOSITION_TYPE_ATTACHMENT);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#getFilename()
*/
public String getFilename() {
return getParameter(PARAM_FILENAME);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#getCreationDate()
*/
public Date getCreationDate() {
if (!creationDateParsed) {
creationDate = parseDate(PARAM_CREATION_DATE);
creationDateParsed = true;
}
return creationDate;
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#getModificationDate()
*/
public Date getModificationDate() {
if (!modificationDateParsed) {
modificationDate = parseDate(PARAM_MODIFICATION_DATE);
modificationDateParsed = true;
}
return modificationDate;
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#getReadDate()
*/
public Date getReadDate() {
if (!readDateParsed) {
readDate = parseDate(PARAM_READ_DATE);
readDateParsed = true;
}
return readDate;
}
/**
* @see org.apache.james.mime4j.dom.field.ContentDispositionField#getSize()
*/
public long getSize() {
String value = getParameter(PARAM_SIZE);
if (value == null)
return -1;
try {
long size = Long.parseLong(value);
return size < 0 ? -1 : size;
} catch (NumberFormatException e) {
return -1;
}
}
private Date parseDate(String paramName) {
String value = getParameter(paramName);
if (value == null) {
monitor.warn("Parsing " + paramName + " null", "returning null");
return null;
}
try {
return new DateTimeParser(new StringReader(value)).parseAll()
.getDate();
} catch (org.apache.james.mime4j.field.datetime.parser.ParseException e) {
monitor.warn("Parsing " + paramName + " '" + value + "': "
+ e.getMessage(), "returning null");
return null;
} catch (org.apache.james.mime4j.field.datetime.parser.TokenMgrError e) {
monitor.warn("Parsing " + paramName + " '" + value + "': "
+ e.getMessage(), "returning null");
return null;
}
}
private void parse() {
String body = getBody();
ContentDispositionParser parser = new ContentDispositionParser(
new StringReader(body));
try {
parser.parseAll();
} catch (ParseException e) {
parseException = e;
} catch (TokenMgrError e) {
parseException = new ParseException(e.getMessage());
}
final String dispositionType = parser.getDispositionType();
if (dispositionType != null) {
this.dispositionType = dispositionType.toLowerCase(Locale.US);
List<String> paramNames = parser.getParamNames();
List<String> paramValues = parser.getParamValues();
if (paramNames != null && paramValues != null) {
final int len = Math.min(paramNames.size(), paramValues.size());
for (int i = 0; i < len; i++) {
String paramName = paramNames.get(i).toLowerCase(Locale.US);
String paramValue = paramValues.get(i);
parameters.put(paramName, paramValue);
}
}
}
parsed = true;
}
static final FieldParser<ContentDispositionFieldImpl> PARSER = new FieldParser<ContentDispositionFieldImpl>() {
public ContentDispositionFieldImpl parse(final String name, final String body,
final ByteSequence raw, DecodeMonitor monitor) {
return new ContentDispositionFieldImpl(name, body, raw, monitor);
}
};
}

View File

@ -19,70 +19,47 @@
package org.apache.james.mime4j.field;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.field.ContentTransferEncodingField;
import org.apache.james.mime4j.util.ByteSequence;
import org.apache.james.mime4j.util.MimeUtil;
/**
* Represents a <code>Content-Transfer-Encoding</code> field.
*
*
* @version $Id: ContentTransferEncodingField.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $
*/
public class ContentTransferEncodingField extends Field {
/**
* The <code>7bit</code> encoding.
*/
public static final String ENC_7BIT = "7bit";
/**
* The <code>8bit</code> encoding.
*/
public static final String ENC_8BIT = "8bit";
/**
* The <code>binary</code> encoding.
*/
public static final String ENC_BINARY = "binary";
/**
* The <code>quoted-printable</code> encoding.
*/
public static final String ENC_QUOTED_PRINTABLE = "quoted-printable";
/**
* The <code>base64</code> encoding.
*/
public static final String ENC_BASE64 = "base64";
public class ContentTransferEncodingFieldImpl extends AbstractField implements ContentTransferEncodingField {
private String encoding;
protected ContentTransferEncodingField(String name, String body, String raw, String encoding) {
super(name, body, raw);
this.encoding = encoding;
ContentTransferEncodingFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) {
super(name, body, raw, monitor);
encoding = body.trim().toLowerCase();
}
/**
* Gets the encoding defined in this field.
*
* @return the encoding or an empty string if not set.
* @see org.apache.james.mime4j.dom.field.ContentTransferEncodingField#getEncoding()
*/
public String getEncoding() {
return encoding;
}
/**
* Gets the encoding of the given field if. Returns the default
* <code>7bit</code> if not set or if
* <code>f</code> is <code>null</code>.
*
* Gets the encoding of the given field if. Returns the default
* <code>7bit</code> if not set or if <code>f</code> is
* <code>null</code>.
*
* @return the encoding.
*/
public static String getEncoding(ContentTransferEncodingField f) {
if (f != null && f.getEncoding().length() != 0) {
return f.getEncoding();
}
return ENC_7BIT;
return MimeUtil.ENC_7BIT;
}
public static class Parser implements FieldParser {
public Field parse(final String name, final String body, final String raw) {
final String encoding = body.trim().toLowerCase();
return new ContentTransferEncodingField(name, body, raw, encoding);
static final FieldParser<ContentTransferEncodingFieldImpl> PARSER = new FieldParser<ContentTransferEncodingFieldImpl>() {
public ContentTransferEncodingFieldImpl parse(final String name, final String body,
final ByteSequence raw, DecodeMonitor monitor) {
return new ContentTransferEncodingFieldImpl(name, body, raw, monitor);
}
}
};
}

View File

@ -1,256 +0,0 @@
/****************************************************************
* 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.field;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.james.mime4j.field.contenttype.parser.ContentTypeParser;
import org.apache.james.mime4j.field.contenttype.parser.ParseException;
import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError;
/**
* Represents a <code>Content-Type</code> field.
*
* <p>TODO: Remove dependency on Java 1.4 regexps</p>
*
*
* @version $Id: ContentTypeField.java,v 1.6 2005/01/27 14:16:31 ntherning Exp $
*/
public class ContentTypeField extends Field {
/**
* The prefix of all <code>multipart</code> MIME types.
*/
public static final String TYPE_MULTIPART_PREFIX = "multipart/";
/**
* The <code>multipart/digest</code> MIME type.
*/
public static final String TYPE_MULTIPART_DIGEST = "multipart/digest";
/**
* The <code>text/plain</code> MIME type.
*/
public static final String TYPE_TEXT_PLAIN = "text/plain";
/**
* The <code>message/rfc822</code> MIME type.
*/
public static final String TYPE_MESSAGE_RFC822 = "message/rfc822";
/**
* The name of the <code>boundary</code> parameter.
*/
public static final String PARAM_BOUNDARY = "boundary";
/**
* The name of the <code>charset</code> parameter.
*/
public static final String PARAM_CHARSET = "charset";
private String mimeType = "";
private Map parameters = null;
private ParseException parseException;
protected ContentTypeField(String name, String body, String raw, String mimeType, Map parameters, ParseException parseException) {
super(name, body, raw);
this.mimeType = mimeType;
this.parameters = parameters;
this.parseException = parseException;
}
/**
* Gets the exception that was raised during parsing of
* the field value, if any; otherwise, null.
*/
public ParseException getParseException() {
return parseException;
}
/**
* Gets the MIME type defined in this Content-Type field.
*
* @return the MIME type or an empty string if not set.
*/
public String getMimeType() {
return mimeType;
}
/**
* Gets the MIME type defined in the child's
* Content-Type field or derives a MIME type from the parent
* if child is <code>null</code> or hasn't got a MIME type value set.
* If child's MIME type is multipart but no boundary
* has been set the MIME type of child will be derived from
* the parent.
*
* @param child the child.
* @param parent the parent.
* @return the MIME type.
*/
public static String getMimeType(ContentTypeField child,
ContentTypeField parent) {
if (child == null || child.getMimeType().length() == 0
|| child.isMultipart() && child.getBoundary() == null) {
if (parent != null && parent.isMimeType(TYPE_MULTIPART_DIGEST)) {
return TYPE_MESSAGE_RFC822;
} else {
return TYPE_TEXT_PLAIN;
}
}
return child.getMimeType();
}
/**
* Gets the value of a parameter. Parameter names are case-insensitive.
*
* @param name the name of the parameter to get.
* @return the parameter value or <code>null</code> if not set.
*/
public String getParameter(String name) {
return parameters != null
? (String) parameters.get(name.toLowerCase())
: null;
}
/**
* Gets all parameters.
*
* @return the parameters.
*/
public Map getParameters() {
return parameters != null
? Collections.unmodifiableMap(parameters)
: Collections.EMPTY_MAP;
}
/**
* Gets the value of the <code>boundary</code> parameter if set.
*
* @return the <code>boundary</code> parameter value or <code>null</code>
* if not set.
*/
public String getBoundary() {
return getParameter(PARAM_BOUNDARY);
}
/**
* Gets the value of the <code>charset</code> parameter if set.
*
* @return the <code>charset</code> parameter value or <code>null</code>
* if not set.
*/
public String getCharset() {
return getParameter(PARAM_CHARSET);
}
/**
* Gets the value of the <code>charset</code> parameter if set for the
* given field. Returns the default <code>us-ascii</code> if not set or if
* <code>f</code> is <code>null</code>.
*
* @return the <code>charset</code> parameter value.
*/
public static String getCharset(ContentTypeField f) {
if (f != null) {
if (f.getCharset() != null && f.getCharset().length() > 0) {
return f.getCharset();
}
}
return "us-ascii";
}
/**
* Determines if the MIME type of this field matches the given one.
*
* @param mimeType the MIME type to match against.
* @return <code>true</code> if the MIME type of this field matches,
* <code>false</code> otherwise.
*/
public boolean isMimeType(String mimeType) {
return this.mimeType.equalsIgnoreCase(mimeType);
}
/**
* Determines if the MIME type of this field is <code>multipart/*</code>.
*
* @return <code>true</code> if this field is has a <code>multipart/*</code>
* MIME type, <code>false</code> otherwise.
*/
public boolean isMultipart() {
return mimeType.startsWith(TYPE_MULTIPART_PREFIX);
}
public static class Parser implements FieldParser {
private static Log log = LogFactory.getLog(Parser.class);
public Field parse(final String name, final String body, final String raw) {
ParseException parseException = null;
String mimeType = "";
Map parameters = null;
ContentTypeParser parser = new ContentTypeParser(new StringReader(body));
try {
parser.parseAll();
}
catch (ParseException e) {
if (log.isDebugEnabled()) {
log.debug("Parsing value '" + body + "': "+ e.getMessage());
}
parseException = e;
}
catch (TokenMgrError e) {
if (log.isDebugEnabled()) {
log.debug("Parsing value '" + body + "': "+ e.getMessage());
}
parseException = new ParseException(e.getMessage());
}
try {
final String type = parser.getType();
final String subType = parser.getSubType();
if (type != null && subType != null) {
mimeType = (type + "/" + parser.getSubType()).toLowerCase();
ArrayList paramNames = parser.getParamNames();
ArrayList paramValues = parser.getParamValues();
if (paramNames != null && paramValues != null) {
for (int i = 0; i < paramNames.size() && i < paramValues.size(); i++) {
if (parameters == null)
parameters = new HashMap((int)(paramNames.size() * 1.3 + 1));
String paramName = ((String)paramNames.get(i)).toLowerCase();
String paramValue = ((String)paramValues.get(i));
parameters.put(paramName, paramValue);
}
}
}
}
catch (NullPointerException npe) {
}
return new ContentTypeField(name, body, raw, mimeType, parameters, parseException);
}
}
}

View File

@ -0,0 +1,208 @@
/****************************************************************
* 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.field;
import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.field.ContentTypeField;
import org.apache.james.mime4j.field.contenttype.parser.ContentTypeParser;
import org.apache.james.mime4j.field.contenttype.parser.ParseException;
import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError;
import org.apache.james.mime4j.util.ByteSequence;
/**
* Represents a <code>Content-Type</code> field.
*/
public class ContentTypeFieldImpl extends AbstractField implements ContentTypeField {
private boolean parsed = false;
private String mimeType = "";
private Map<String, String> parameters = new HashMap<String, String>();
private ParseException parseException;
ContentTypeFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) {
super(name, body, raw, monitor);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentTypeField#getParseException()
*/
@Override
public ParseException getParseException() {
if (!parsed)
parse();
return parseException;
}
/**
* @see org.apache.james.mime4j.dom.field.ContentTypeField#getMimeType()
*/
public String getMimeType() {
if (!parsed)
parse();
return mimeType;
}
/**
* @see org.apache.james.mime4j.dom.field.ContentTypeField#getParameter(java.lang.String)
*/
public String getParameter(String name) {
if (!parsed)
parse();
return parameters.get(name.toLowerCase());
}
/**
* @see org.apache.james.mime4j.dom.field.ContentTypeField#getParameters()
*/
public Map<String, String> getParameters() {
if (!parsed)
parse();
return Collections.unmodifiableMap(parameters);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentTypeField#isMimeType(java.lang.String)
*/
public boolean isMimeType(String mimeType) {
if (!parsed)
parse();
return this.mimeType.equalsIgnoreCase(mimeType);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentTypeField#isMultipart()
*/
public boolean isMultipart() {
if (!parsed)
parse();
return mimeType.startsWith(TYPE_MULTIPART_PREFIX);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentTypeField#getBoundary()
*/
public String getBoundary() {
return getParameter(PARAM_BOUNDARY);
}
/**
* @see org.apache.james.mime4j.dom.field.ContentTypeField#getCharset()
*/
public String getCharset() {
return getParameter(PARAM_CHARSET);
}
/**
* Gets the MIME type defined in the child's Content-Type field or derives a
* MIME type from the parent if child is <code>null</code> or hasn't got a
* MIME type value set. If child's MIME type is multipart but no boundary
* has been set the MIME type of child will be derived from the parent.
*
* @param child
* the child.
* @param parent
* the parent.
* @return the MIME type.
*/
public static String getMimeType(ContentTypeField child,
ContentTypeField parent) {
if (child == null || child.getMimeType().length() == 0
|| child.isMultipart() && child.getBoundary() == null) {
if (parent != null && parent.isMimeType(TYPE_MULTIPART_DIGEST)) {
return TYPE_MESSAGE_RFC822;
} else {
return TYPE_TEXT_PLAIN;
}
}
return child.getMimeType();
}
/**
* Gets the value of the <code>charset</code> parameter if set for the
* given field. Returns the default <code>us-ascii</code> if not set or if
* <code>f</code> is <code>null</code>.
*
* @return the <code>charset</code> parameter value.
*/
public static String getCharset(ContentTypeField f) {
if (f != null) {
String charset = f.getCharset();
if (charset != null && charset.length() > 0) {
return charset;
}
}
return "us-ascii";
}
private void parse() {
String body = getBody();
ContentTypeParser parser = new ContentTypeParser(new StringReader(body));
try {
parser.parseAll();
} catch (ParseException e) {
parseException = e;
} catch (TokenMgrError e) {
parseException = new ParseException(e.getMessage());
}
final String type = parser.getType();
final String subType = parser.getSubType();
if (type != null && subType != null) {
mimeType = (type + "/" + subType).toLowerCase();
List<String> paramNames = parser.getParamNames();
List<String> paramValues = parser.getParamValues();
if (paramNames != null && paramValues != null) {
final int len = Math.min(paramNames.size(), paramValues.size());
for (int i = 0; i < len; i++) {
String paramName = paramNames.get(i).toLowerCase();
String paramValue = paramValues.get(i);
parameters.put(paramName, paramValue);
}
}
}
parsed = true;
}
static final FieldParser<ContentTypeFieldImpl> PARSER = new FieldParser<ContentTypeFieldImpl>() {
public ContentTypeFieldImpl parse(final String name, final String body,
final ByteSequence raw, DecodeMonitor monitor) {
return new ContentTypeFieldImpl(name, body, raw, monitor);
}
};
}

View File

@ -1,65 +0,0 @@
/****************************************************************
* 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.field;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.james.mime4j.field.datetime.DateTime;
import org.apache.james.mime4j.field.datetime.parser.ParseException;
import java.util.Date;
public class DateTimeField extends Field {
private Date date;
private ParseException parseException;
protected DateTimeField(String name, String body, String raw, Date date, ParseException parseException) {
super(name, body, raw);
this.date = date;
this.parseException = parseException;
}
public Date getDate() {
return date;
}
public ParseException getParseException() {
return parseException;
}
public static class Parser implements FieldParser {
private static Log log = LogFactory.getLog(Parser.class);
public Field parse(final String name, final String body, final String raw) {
Date date = null;
ParseException parseException = null;
try {
date = DateTime.parse(body).getDate();
}
catch (ParseException e) {
if (log.isDebugEnabled()) {
log.debug("Parsing value '" + body + "': "+ e.getMessage());
}
parseException = e;
}
return new DateTimeField(name, body, raw, date, parseException);
}
}
}

View File

@ -0,0 +1,86 @@
/****************************************************************
* 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.field;
import java.io.StringReader;
import java.util.Date;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
import org.apache.james.mime4j.field.datetime.parser.ParseException;
import org.apache.james.mime4j.field.datetime.parser.TokenMgrError;
import org.apache.james.mime4j.util.ByteSequence;
/**
* Date-time field such as <code>Date</code> or <code>Resent-Date</code>.
*/
public class DateTimeFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.DateTimeField {
private boolean parsed = false;
private Date date;
private ParseException parseException;
DateTimeFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) {
super(name, body, raw, monitor);
}
/**
* @see org.apache.james.mime4j.dom.field.DateTimeField#getDate()
*/
public Date getDate() {
if (!parsed)
parse();
return date;
}
/**
* @see org.apache.james.mime4j.dom.field.DateTimeField#getParseException()
*/
@Override
public ParseException getParseException() {
if (!parsed)
parse();
return parseException;
}
private void parse() {
String body = getBody();
try {
date = new DateTimeParser(new StringReader(body)).parseAll()
.getDate();
} catch (ParseException e) {
parseException = e;
} catch (TokenMgrError e) {
parseException = new ParseException(e.getMessage());
}
parsed = true;
}
static final FieldParser<DateTimeFieldImpl> PARSER = new FieldParser<DateTimeFieldImpl>() {
public DateTimeFieldImpl parse(final String name, final String body,
final ByteSequence raw, DecodeMonitor monitor) {
return new DateTimeFieldImpl(name, body, raw, monitor);
}
};
}

View File

@ -1,45 +1,133 @@
/*
* Copyright 2006 the mime4j project
*
* Licensed 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.
*/
/****************************************************************
* 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.field;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.field.FieldName;
import org.apache.james.mime4j.dom.field.ParsedField;
import org.apache.james.mime4j.field.AddressListFieldImpl;
import org.apache.james.mime4j.field.ContentDispositionFieldImpl;
import org.apache.james.mime4j.field.ContentTransferEncodingFieldImpl;
import org.apache.james.mime4j.field.ContentTypeFieldImpl;
import org.apache.james.mime4j.field.DateTimeFieldImpl;
import org.apache.james.mime4j.field.MailboxFieldImpl;
import org.apache.james.mime4j.field.MailboxListFieldImpl;
import org.apache.james.mime4j.field.UnstructuredFieldImpl;
import org.apache.james.mime4j.stream.RawField;
import org.apache.james.mime4j.util.ByteSequence;
import org.apache.james.mime4j.util.ContentUtil;
public class DefaultFieldParser extends DelegatingFieldParser {
private static final DefaultFieldParser PARSER = new DefaultFieldParser();
public DefaultFieldParser() {
setFieldParser(Field.CONTENT_TRANSFER_ENCODING, new ContentTransferEncodingField.Parser());
setFieldParser(Field.CONTENT_TYPE, new ContentTypeField.Parser());
final DateTimeField.Parser dateTimeParser = new DateTimeField.Parser();
setFieldParser(Field.DATE, dateTimeParser);
setFieldParser(Field.RESENT_DATE, dateTimeParser);
final MailboxListField.Parser mailboxListParser = new MailboxListField.Parser();
setFieldParser(Field.FROM, mailboxListParser);
setFieldParser(Field.RESENT_FROM, mailboxListParser);
final MailboxField.Parser mailboxParser = new MailboxField.Parser();
setFieldParser(Field.SENDER, mailboxParser);
setFieldParser(Field.RESENT_SENDER, mailboxParser);
final AddressListField.Parser addressListParser = new AddressListField.Parser();
setFieldParser(Field.TO, addressListParser);
setFieldParser(Field.RESENT_TO, addressListParser);
setFieldParser(Field.CC, addressListParser);
setFieldParser(Field.RESENT_CC, addressListParser);
setFieldParser(Field.BCC, addressListParser);
setFieldParser(Field.RESENT_BCC, addressListParser);
setFieldParser(Field.REPLY_TO, addressListParser);
/**
* Gets the default parser used to parse fields.
*
* @return the default field parser
*/
public static DefaultFieldParser getParser() {
return PARSER;
}
/**
* Parses the given byte sequence and returns an instance of the
* <code>Field</code> 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.
* @param monitor a DecodeMonitor object used while parsing/decoding.
* @return a <code>ParsedField</code> instance.
* @throws MimeException if the raw string cannot be split into field name and body.
*/
public static ParsedField parse(
final ByteSequence raw,
final DecodeMonitor monitor) throws MimeException {
RawField rawField = new RawField(raw);
return PARSER.parse(rawField.getName(), rawField.getBody(), raw, monitor);
}
/**
* Parses the given string and returns an instance of the
* <code>Field</code> class. The type of the class returned depends on
* the field name:
* <p>
* <table>
* <tr><th>Class returned</th><th>Field names</th></tr>
* <tr><td>{@link ContentTypeFieldImpl}</td><td>Content-Type</td></tr>
* <tr><td>{@link ContentTransferEncodingFieldImpl}</td><td>Content-Transfer-Encoding</td></tr>
* <tr><td>{@link ContentDispositionFieldImpl}</td><td>Content-Disposition</td></tr>
* <tr><td>{@link DateTimeFieldImpl}</td><td>Date, Resent-Date</td></tr>
* <tr><td>{@link MailboxFieldImpl}</td><td>Sender, Resent-Sender</td></tr>
* <tr><td>{@link MailboxListFieldImpl}</td><td>From, Resent-From</td></tr>
* <tr><td>{@link AddressListFieldImpl}</td><td>To, Cc, Bcc, Reply-To, Resent-To, Resent-Cc, Resent-Bcc</td></tr>
* <tr><td>{@link UnstructuredFieldImpl}</td><td>Subject and others</td></tr>
* </table>
*
* @param rawStr the string to parse.
* @return a <code>ParsedField</code> instance.
* @throws MimeException if the raw string cannot be split into field name and body.
*/
public static ParsedField parse(
final String rawStr,
final DecodeMonitor monitor) throws MimeException {
ByteSequence raw = ContentUtil.encode(rawStr);
return parse(raw, monitor);
}
public static ParsedField parse(final String rawStr) throws MimeException {
ByteSequence raw = ContentUtil.encode(rawStr);
return parse(raw, DecodeMonitor.SILENT);
}
public DefaultFieldParser() {
setFieldParser(FieldName.CONTENT_TRANSFER_ENCODING,
ContentTransferEncodingFieldImpl.PARSER);
setFieldParser(FieldName.CONTENT_TYPE, ContentTypeFieldImpl.PARSER);
setFieldParser(FieldName.CONTENT_DISPOSITION,
ContentDispositionFieldImpl.PARSER);
final FieldParser<DateTimeFieldImpl> dateTimeParser = DateTimeFieldImpl.PARSER;
setFieldParser(FieldName.DATE, dateTimeParser);
setFieldParser(FieldName.RESENT_DATE, dateTimeParser);
final FieldParser<MailboxListFieldImpl> mailboxListParser = MailboxListFieldImpl.PARSER;
setFieldParser(FieldName.FROM, mailboxListParser);
setFieldParser(FieldName.RESENT_FROM, mailboxListParser);
final FieldParser<MailboxFieldImpl> mailboxParser = MailboxFieldImpl.PARSER;
setFieldParser(FieldName.SENDER, mailboxParser);
setFieldParser(FieldName.RESENT_SENDER, mailboxParser);
final FieldParser<AddressListFieldImpl> addressListParser = AddressListFieldImpl.PARSER;
setFieldParser(FieldName.TO, addressListParser);
setFieldParser(FieldName.RESENT_TO, addressListParser);
setFieldParser(FieldName.CC, addressListParser);
setFieldParser(FieldName.RESENT_CC, addressListParser);
setFieldParser(FieldName.BCC, addressListParser);
setFieldParser(FieldName.RESENT_BCC, addressListParser);
setFieldParser(FieldName.REPLY_TO, addressListParser);
}
}

View File

@ -1,47 +1,56 @@
/*
* Copyright 2006 the mime4j project
*
* Licensed 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.
*/
/****************************************************************
* 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.field;
import java.util.HashMap;
import java.util.Map;
public class DelegatingFieldParser implements FieldParser {
private Map parsers = new HashMap();
private FieldParser defaultParser = new UnstructuredField.Parser();
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.field.ParsedField;
import org.apache.james.mime4j.field.UnstructuredFieldImpl;
import org.apache.james.mime4j.util.ByteSequence;
public class DelegatingFieldParser implements FieldParser<ParsedField> {
private static final FieldParser<UnstructuredFieldImpl> DEFAULT_PARSER = UnstructuredFieldImpl.PARSER;
private Map<String, FieldParser<? extends ParsedField>> parsers = new HashMap<String, FieldParser<? extends ParsedField>>();
/**
* Sets the parser used for the field named <code>name</code>.
* @param name the name of the field
* @param parser the parser for fields named <code>name</code>
*/
public void setFieldParser(final String name, final FieldParser parser) {
public void setFieldParser(final String name, final FieldParser<? extends ParsedField> parser) {
parsers.put(name.toLowerCase(), parser);
}
public FieldParser getParser(final String name) {
final FieldParser field = (FieldParser) parsers.get(name.toLowerCase());
if(field==null) {
return defaultParser;
public FieldParser<? extends ParsedField> getParser(final String name) {
final FieldParser<? extends ParsedField> field = parsers.get(name.toLowerCase());
if (field == null) {
return DEFAULT_PARSER;
}
return field;
}
public Field parse(final String name, final String body, final String raw) {
final FieldParser parser = getParser(name);
return parser.parse(name, body, raw);
public ParsedField parse(final String name, final String body, final ByteSequence raw, DecodeMonitor monitor) {
final FieldParser<? extends ParsedField> parser = getParser(name);
return parser.parse(name, body, raw, monitor);
}
}

View File

@ -1,192 +0,0 @@
/****************************************************************
* 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.field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The base class of all field classes.
*
*
* @version $Id: Field.java,v 1.6 2004/10/25 07:26:46 ntherning Exp $
*/
public abstract class Field {
public static final String SENDER = "Sender";
public static final String FROM = "From";
public static final String TO = "To";
public static final String CC = "Cc";
public static final String BCC = "Bcc";
public static final String REPLY_TO = "Reply-To";
public static final String RESENT_SENDER = "Resent-Sender";
public static final String RESENT_FROM = "Resent-From";
public static final String RESENT_TO = "Resent-To";
public static final String RESENT_CC = "Resent-Cc";
public static final String RESENT_BCC = "Resent-Bcc";
public static final String DATE = "Date";
public static final String RESENT_DATE = "Resent-Date";
public static final String SUBJECT = "Subject";
public static final String CONTENT_TYPE = "Content-Type";
public static final String CONTENT_TRANSFER_ENCODING =
"Content-Transfer-Encoding";
private static final String FIELD_NAME_PATTERN =
"^([\\x21-\\x39\\x3b-\\x7e]+)[ \t]*:";
private static final Pattern fieldNamePattern =
Pattern.compile(FIELD_NAME_PATTERN);
private static final DefaultFieldParser parser = new DefaultFieldParser();
private final String name;
private final String body;
private final String raw;
protected Field(final String name, final String body, final String raw) {
this.name = name;
this.body = body;
this.raw = raw;
}
/**
* Parses the given string and returns an instance of the
* <code>Field</code> class. The type of the class returned depends on
* the field name:
* <table>
* <tr>
* <td><em>Field name</em></td><td><em>Class returned</em></td>
* <td>Content-Type</td><td>org.apache.james.mime4j.field.ContentTypeField</td>
* <td>other</td><td>org.apache.james.mime4j.field.UnstructuredField</td>
* </tr>
* </table>
*
* @param s the string to parse.
* @return a <code>Field</code> instance.
* @throws IllegalArgumentException on parse errors.
*/
public static Field parse(final String raw) {
/*
* Unfold the field.
*/
final String unfolded = raw.replaceAll("\r|\n", "");
/*
* Split into name and value.
*/
final Matcher fieldMatcher = fieldNamePattern.matcher(unfolded);
if (!fieldMatcher.find()) {
throw new IllegalArgumentException("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);
}
/**
* Gets the default parser used to parse fields.
* @return the default field parser
*/
public static DefaultFieldParser getParser() {
return parser;
}
/**
* Gets the name of the field (<code>Subject</code>,
* <code>From</code>, etc).
*
* @return the field name.
*/
public String getName() {
return name;
}
/**
* Gets the original raw field string.
*
* @return the original raw field string.
*/
public String getRaw() {
return raw;
}
/**
* Gets the unfolded, unparsed and possibly encoded (see RFC 2047) field
* body string.
*
* @return the unfolded unparsed field body string.
*/
public String getBody() {
return body;
}
/**
* Determines if this is a <code>Content-Type</code> field.
*
* @return <code>true</code> if this is a <code>Content-Type</code> field,
* <code>false</code> otherwise.
*/
public boolean isContentType() {
return CONTENT_TYPE.equalsIgnoreCase(name);
}
/**
* Determines if this is a <code>Subject</code> field.
*
* @return <code>true</code> if this is a <code>Subject</code> field,
* <code>false</code> otherwise.
*/
public boolean isSubject() {
return SUBJECT.equalsIgnoreCase(name);
}
/**
* Determines if this is a <code>From</code> field.
*
* @return <code>true</code> if this is a <code>From</code> field,
* <code>false</code> otherwise.
*/
public boolean isFrom() {
return FROM.equalsIgnoreCase(name);
}
/**
* Determines if this is a <code>To</code> field.
*
* @return <code>true</code> if this is a <code>To</code> field,
* <code>false</code> otherwise.
*/
public boolean isTo() {
return TO.equalsIgnoreCase(name);
}
/**
* @see #getRaw()
*/
public String toString() {
return raw;
}
}

View File

@ -1,21 +1,30 @@
/*
* Copyright 2006 the mime4j project
*
* Licensed 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.
*/
/****************************************************************
* 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.field;
public interface FieldParser {
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.field.ParsedField;
import org.apache.james.mime4j.util.ByteSequence;
public interface FieldParser<T extends ParsedField> {
T parse(final String name, final String body, final ByteSequence raw, DecodeMonitor monitor);
Field parse(final String name, final String body, final String raw);
}

View File

@ -0,0 +1,643 @@
/****************************************************************
* 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.field;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.codec.EncoderUtil;
import org.apache.james.mime4j.dom.address.Address;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.field.AddressListField;
import org.apache.james.mime4j.dom.field.ContentDispositionField;
import org.apache.james.mime4j.dom.field.ContentTransferEncodingField;
import org.apache.james.mime4j.dom.field.ContentTypeField;
import org.apache.james.mime4j.dom.field.DateTimeField;
import org.apache.james.mime4j.dom.field.Field;
import org.apache.james.mime4j.dom.field.FieldName;
import org.apache.james.mime4j.dom.field.MailboxField;
import org.apache.james.mime4j.dom.field.MailboxListField;
import org.apache.james.mime4j.dom.field.ParsedField;
import org.apache.james.mime4j.dom.field.UnstructuredField;
import org.apache.james.mime4j.field.AddressListFieldImpl;
import org.apache.james.mime4j.field.ContentDispositionFieldImpl;
import org.apache.james.mime4j.field.ContentTransferEncodingFieldImpl;
import org.apache.james.mime4j.field.ContentTypeFieldImpl;
import org.apache.james.mime4j.field.DateTimeFieldImpl;
import org.apache.james.mime4j.field.MailboxFieldImpl;
import org.apache.james.mime4j.field.MailboxListFieldImpl;
import org.apache.james.mime4j.field.UnstructuredFieldImpl;
import org.apache.james.mime4j.field.address.formatter.AddressFormatter;
import org.apache.james.mime4j.stream.RawField;
import org.apache.james.mime4j.util.MimeUtil;
/**
* Factory for concrete {@link Field} instances.
*/
public class Fields {
private static final Pattern FIELD_NAME_PATTERN = Pattern
.compile("[\\x21-\\x39\\x3b-\\x7e]+");
private Fields() {
}
/**
* Creates a <i>Content-Type</i> field from the specified raw field value.
* The specified string gets folded into a multiple-line representation if
* necessary but is otherwise taken as is.
*
* @param contentType
* raw content type containing a MIME type and optional
* parameters.
* @return the newly created <i>Content-Type</i> field.
*/
public static ContentTypeField contentType(String contentType) {
return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE,
contentType);
}
/**
* Creates a <i>Content-Type</i> field from the specified MIME type and
* parameters.
*
* @param mimeType
* a MIME type (such as <code>&quot;text/plain&quot;</code> or
* <code>&quot;application/octet-stream&quot;</code>).
* @param parameters
* map containing content-type parameters such as
* <code>&quot;boundary&quot;</code>.
* @return the newly created <i>Content-Type</i> field.
*/
public static ContentTypeField contentType(String mimeType,
Map<String, String> parameters) {
if (!isValidMimeType(mimeType))
throw new IllegalArgumentException();
if (parameters == null || parameters.isEmpty()) {
return parse(ContentTypeFieldImpl.PARSER, FieldName.CONTENT_TYPE,
mimeType);
} else {
StringBuilder sb = new StringBuilder(mimeType);
for (Map.Entry<String, String> entry : parameters.entrySet()) {
sb.append("; ");
sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(),
entry.getValue()));
}
String contentType = sb.toString();
return contentType(contentType);
}
}
/**
* Creates a <i>Content-Transfer-Encoding</i> field from the specified raw
* field value.
*
* @param contentTransferEncoding
* an encoding mechanism such as <code>&quot;7-bit&quot;</code>
* or <code>&quot;quoted-printable&quot;</code>.
* @return the newly created <i>Content-Transfer-Encoding</i> field.
*/
public static ContentTransferEncodingField contentTransferEncoding(
String contentTransferEncoding) {
return parse(ContentTransferEncodingFieldImpl.PARSER,
FieldName.CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
}
/**
* Creates a <i>Content-Disposition</i> field from the specified raw field
* value. The specified string gets folded into a multiple-line
* representation if necessary but is otherwise taken as is.
*
* @param contentDisposition
* raw content disposition containing a disposition type and
* optional parameters.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String contentDisposition) {
return parse(ContentDispositionFieldImpl.PARSER,
FieldName.CONTENT_DISPOSITION, contentDisposition);
}
/**
* Creates a <i>Content-Disposition</i> field from the specified
* disposition type and parameters.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param parameters
* map containing disposition parameters such as
* <code>&quot;filename&quot;</code>.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, Map<String, String> parameters) {
if (!isValidDispositionType(dispositionType))
throw new IllegalArgumentException();
if (parameters == null || parameters.isEmpty()) {
return parse(ContentDispositionFieldImpl.PARSER,
FieldName.CONTENT_DISPOSITION, dispositionType);
} else {
StringBuilder sb = new StringBuilder(dispositionType);
for (Map.Entry<String, String> entry : parameters.entrySet()) {
sb.append("; ");
sb.append(EncoderUtil.encodeHeaderParameter(entry.getKey(),
entry.getValue()));
}
String contentDisposition = sb.toString();
return contentDisposition(contentDisposition);
}
}
/**
* Creates a <i>Content-Disposition</i> field from the specified
* disposition type and filename.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, String filename) {
return contentDisposition(dispositionType, filename, -1, null, null,
null);
}
/**
* Creates a <i>Content-Disposition</i> field from the specified values.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @param size
* size parameter value or <code>-1</code> if the parameter
* should not be included.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, String filename, long size) {
return contentDisposition(dispositionType, filename, size, null, null,
null);
}
/**
* Creates a <i>Content-Disposition</i> field from the specified values.
*
* @param dispositionType
* a disposition type (usually <code>&quot;inline&quot;</code>
* or <code>&quot;attachment&quot;</code>).
* @param filename
* filename parameter value or <code>null</code> if the
* parameter should not be included.
* @param size
* size parameter value or <code>-1</code> if the parameter
* should not be included.
* @param creationDate
* creation-date parameter value or <code>null</code> if the
* parameter should not be included.
* @param modificationDate
* modification-date parameter value or <code>null</code> if
* the parameter should not be included.
* @param readDate
* read-date parameter value or <code>null</code> if the
* parameter should not be included.
* @return the newly created <i>Content-Disposition</i> field.
*/
public static ContentDispositionField contentDisposition(
String dispositionType, String filename, long size,
Date creationDate, Date modificationDate, Date readDate) {
Map<String, String> parameters = new HashMap<String, String>();
if (filename != null) {
parameters.put(ContentDispositionFieldImpl.PARAM_FILENAME, filename);
}
if (size >= 0) {
parameters.put(ContentDispositionFieldImpl.PARAM_SIZE, Long
.toString(size));
}
if (creationDate != null) {
parameters.put(ContentDispositionFieldImpl.PARAM_CREATION_DATE,
MimeUtil.formatDate(creationDate, null));
}
if (modificationDate != null) {
parameters.put(ContentDispositionFieldImpl.PARAM_MODIFICATION_DATE,
MimeUtil.formatDate(modificationDate, null));
}
if (readDate != null) {
parameters.put(ContentDispositionFieldImpl.PARAM_READ_DATE, MimeUtil
.formatDate(readDate, null));
}
return contentDisposition(dispositionType, parameters);
}
/**
* Creates a <i>Date</i> field from the specified <code>Date</code>
* value. The default time zone of the host is used to format the date.
*
* @param date
* date value for the header field.
* @return the newly created <i>Date</i> field.
*/
public static DateTimeField date(Date date) {
return date0(FieldName.DATE, date, null);
}
/**
* Creates a date field from the specified field name and <code>Date</code>
* value. The default time zone of the host is used to format the date.
*
* @param fieldName
* a field name such as <code>Date</code> or
* <code>Resent-Date</code>.
* @param date
* date value for the header field.
* @return the newly created date field.
*/
public static DateTimeField date(String fieldName, Date date) {
checkValidFieldName(fieldName);
return date0(fieldName, date, null);
}
/**
* Creates a date field from the specified field name, <code>Date</code>
* and <code>TimeZone</code> values.
*
* @param fieldName
* a field name such as <code>Date</code> or
* <code>Resent-Date</code>.
* @param date
* date value for the header field.
* @param zone
* the time zone to be used for formatting the date.
* @return the newly created date field.
*/
public static DateTimeField date(String fieldName, Date date, TimeZone zone) {
checkValidFieldName(fieldName);
return date0(fieldName, date, zone);
}
/**
* Creates a <i>Message-ID</i> field for the specified host name.
*
* @param hostname
* host name to be included in the message ID or
* <code>null</code> if no host name should be included.
* @return the newly created <i>Message-ID</i> field.
*/
public static UnstructuredField messageId(String hostname) {
String fieldValue = MimeUtil.createUniqueMessageId(hostname);
return parse(UnstructuredFieldImpl.PARSER, FieldName.MESSAGE_ID, fieldValue);
}
/**
* Creates a <i>Subject</i> field from the specified string value. The
* specified string may contain non-ASCII characters.
*
* @param subject
* the subject string.
* @return the newly created <i>Subject</i> field.
*/
public static UnstructuredField subject(String subject) {
int usedCharacters = FieldName.SUBJECT.length() + 2;
String fieldValue = EncoderUtil.encodeIfNecessary(subject,
EncoderUtil.Usage.TEXT_TOKEN, usedCharacters);
return parse(UnstructuredFieldImpl.PARSER, FieldName.SUBJECT, fieldValue);
}
/**
* Creates a <i>Sender</i> field for the specified mailbox address.
*
* @param mailbox
* address to be included in the field.
* @return the newly created <i>Sender</i> field.
*/
public static MailboxField sender(Mailbox mailbox) {
return mailbox0(FieldName.SENDER, mailbox);
}
/**
* Creates a <i>From</i> field for the specified mailbox address.
*
* @param mailbox
* address to be included in the field.
* @return the newly created <i>From</i> field.
*/
public static MailboxListField from(Mailbox mailbox) {
return mailboxList0(FieldName.FROM, Collections.singleton(mailbox));
}
/**
* Creates a <i>From</i> field for the specified mailbox addresses.
*
* @param mailboxes
* addresses to be included in the field.
* @return the newly created <i>From</i> field.
*/
public static MailboxListField from(Mailbox... mailboxes) {
return mailboxList0(FieldName.FROM, Arrays.asList(mailboxes));
}
/**
* Creates a <i>From</i> field for the specified mailbox addresses.
*
* @param mailboxes
* addresses to be included in the field.
* @return the newly created <i>From</i> field.
*/
public static MailboxListField from(Iterable<Mailbox> mailboxes) {
return mailboxList0(FieldName.FROM, mailboxes);
}
/**
* Creates a <i>To</i> field for the specified mailbox or group address.
*
* @param address
* mailbox or group address to be included in the field.
* @return the newly created <i>To</i> field.
*/
public static AddressListField to(Address address) {
return addressList0(FieldName.TO, Collections.singleton(address));
}
/**
* Creates a <i>To</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>To</i> field.
*/
public static AddressListField to(Address... addresses) {
return addressList0(FieldName.TO, Arrays.asList(addresses));
}
/**
* Creates a <i>To</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>To</i> field.
*/
public static AddressListField to(Iterable<Address> addresses) {
return addressList0(FieldName.TO, addresses);
}
/**
* Creates a <i>Cc</i> field for the specified mailbox or group address.
*
* @param address
* mailbox or group address to be included in the field.
* @return the newly created <i>Cc</i> field.
*/
public static AddressListField cc(Address address) {
return addressList0(FieldName.CC, Collections.singleton(address));
}
/**
* Creates a <i>Cc</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Cc</i> field.
*/
public static AddressListField cc(Address... addresses) {
return addressList0(FieldName.CC, Arrays.asList(addresses));
}
/**
* Creates a <i>Cc</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Cc</i> field.
*/
public static AddressListField cc(Iterable<Address> addresses) {
return addressList0(FieldName.CC, addresses);
}
/**
* Creates a <i>Bcc</i> field for the specified mailbox or group address.
*
* @param address
* mailbox or group address to be included in the field.
* @return the newly created <i>Bcc</i> field.
*/
public static AddressListField bcc(Address address) {
return addressList0(FieldName.BCC, Collections.singleton(address));
}
/**
* Creates a <i>Bcc</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Bcc</i> field.
*/
public static AddressListField bcc(Address... addresses) {
return addressList0(FieldName.BCC, Arrays.asList(addresses));
}
/**
* Creates a <i>Bcc</i> field for the specified mailbox or group addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Bcc</i> field.
*/
public static AddressListField bcc(Iterable<Address> addresses) {
return addressList0(FieldName.BCC, addresses);
}
/**
* Creates a <i>Reply-To</i> field for the specified mailbox or group
* address.
*
* @param address
* mailbox or group address to be included in the field.
* @return the newly created <i>Reply-To</i> field.
*/
public static AddressListField replyTo(Address address) {
return addressList0(FieldName.REPLY_TO, Collections.singleton(address));
}
/**
* Creates a <i>Reply-To</i> field for the specified mailbox or group
* addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Reply-To</i> field.
*/
public static AddressListField replyTo(Address... addresses) {
return addressList0(FieldName.REPLY_TO, Arrays.asList(addresses));
}
/**
* Creates a <i>Reply-To</i> field for the specified mailbox or group
* addresses.
*
* @param addresses
* mailbox or group addresses to be included in the field.
* @return the newly created <i>Reply-To</i> field.
*/
public static AddressListField replyTo(Iterable<Address> addresses) {
return addressList0(FieldName.REPLY_TO, addresses);
}
/**
* Creates a mailbox field from the specified field name and mailbox
* address. Valid field names are <code>Sender</code> and
* <code>Resent-Sender</code>.
*
* @param fieldName
* the name of the mailbox field (<code>Sender</code> or
* <code>Resent-Sender</code>).
* @param mailbox
* mailbox address for the field value.
* @return the newly created mailbox field.
*/
public static MailboxField mailbox(String fieldName, Mailbox mailbox) {
checkValidFieldName(fieldName);
return mailbox0(fieldName, mailbox);
}
/**
* Creates a mailbox-list field from the specified field name and mailbox
* addresses. Valid field names are <code>From</code> and
* <code>Resent-From</code>.
*
* @param fieldName
* the name of the mailbox field (<code>From</code> or
* <code>Resent-From</code>).
* @param mailboxes
* mailbox addresses for the field value.
* @return the newly created mailbox-list field.
*/
public static MailboxListField mailboxList(String fieldName,
Iterable<Mailbox> mailboxes) {
checkValidFieldName(fieldName);
return mailboxList0(fieldName, mailboxes);
}
/**
* Creates an address-list field from the specified field name and mailbox
* or group addresses. Valid field names are <code>To</code>,
* <code>Cc</code>, <code>Bcc</code>, <code>Reply-To</code>,
* <code>Resent-To</code>, <code>Resent-Cc</code> and
* <code>Resent-Bcc</code>.
*
* @param fieldName
* the name of the mailbox field (<code>To</code>,
* <code>Cc</code>, <code>Bcc</code>, <code>Reply-To</code>,
* <code>Resent-To</code>, <code>Resent-Cc</code> or
* <code>Resent-Bcc</code>).
* @param addresses
* mailbox or group addresses for the field value.
* @return the newly created address-list field.
*/
public static AddressListField addressList(String fieldName,
Iterable<Address> addresses) {
checkValidFieldName(fieldName);
return addressList0(fieldName, addresses);
}
private static DateTimeField date0(String fieldName, Date date,
TimeZone zone) {
final String formattedDate = MimeUtil.formatDate(date, zone);
return parse(DateTimeFieldImpl.PARSER, fieldName, formattedDate);
}
private static MailboxField mailbox0(String fieldName, Mailbox mailbox) {
String fieldValue = encodeAddresses(Collections.singleton(mailbox));
return parse(MailboxFieldImpl.PARSER, fieldName, fieldValue);
}
private static MailboxListField mailboxList0(String fieldName,
Iterable<Mailbox> mailboxes) {
String fieldValue = encodeAddresses(mailboxes);
return parse(MailboxListFieldImpl.PARSER, fieldName, fieldValue);
}
private static AddressListField addressList0(String fieldName,
Iterable<Address> addresses) {
String fieldValue = encodeAddresses(addresses);
return parse(AddressListFieldImpl.PARSER, fieldName, fieldValue);
}
private static void checkValidFieldName(String fieldName) {
if (!FIELD_NAME_PATTERN.matcher(fieldName).matches())
throw new IllegalArgumentException("Invalid field name");
}
private static boolean isValidMimeType(String mimeType) {
if (mimeType == null)
return false;
int idx = mimeType.indexOf('/');
if (idx == -1)
return false;
String type = mimeType.substring(0, idx);
String subType = mimeType.substring(idx + 1);
return EncoderUtil.isToken(type) && EncoderUtil.isToken(subType);
}
private static boolean isValidDispositionType(String dispositionType) {
if (dispositionType == null)
return false;
return EncoderUtil.isToken(dispositionType);
}
private static <F extends ParsedField> F parse(FieldParser<F> parser,
String fieldName, String fieldBody) {
RawField rawField = new RawField(fieldName, fieldBody);
return parser.parse(rawField.getName(), rawField.getBody(), rawField.getRaw(),
DecodeMonitor.SILENT);
}
private static String encodeAddresses(Iterable<? extends Address> addresses) {
StringBuilder sb = new StringBuilder();
for (Address address : addresses) {
if (sb.length() > 0) {
sb.append(", ");
}
AddressFormatter.encode(sb, address);
}
return sb.toString();
}
}

View File

@ -1,68 +0,0 @@
/****************************************************************
* 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.field;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.james.mime4j.field.address.AddressList;
import org.apache.james.mime4j.field.address.Mailbox;
import org.apache.james.mime4j.field.address.MailboxList;
import org.apache.james.mime4j.field.address.parser.ParseException;
public class MailboxField extends Field {
private final Mailbox mailbox;
private final ParseException parseException;
protected MailboxField(final String name, final String body, final String raw, final Mailbox mailbox, final ParseException parseException) {
super(name, body, raw);
this.mailbox = mailbox;
this.parseException = parseException;
}
public Mailbox getMailbox() {
return mailbox;
}
public ParseException getParseException() {
return parseException;
}
public static class Parser implements FieldParser {
private static Log log = LogFactory.getLog(Parser.class);
public Field parse(final String name, final String body, final String raw) {
Mailbox mailbox = null;
ParseException parseException = null;
try {
MailboxList mailboxList = AddressList.parse(body).flatten();
if (mailboxList.size() > 0) {
mailbox = mailboxList.get(0);
}
}
catch (ParseException e) {
if (log.isDebugEnabled()) {
log.debug("Parsing value '" + body + "': "+ e.getMessage());
}
parseException = e;
}
return new MailboxField(name, body, raw, mailbox, parseException);
}
}
}

View File

@ -0,0 +1,84 @@
/****************************************************************
* 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.field;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.field.address.parser.AddressBuilder;
import org.apache.james.mime4j.field.address.parser.ParseException;
import org.apache.james.mime4j.util.ByteSequence;
/**
* Mailbox field such as <code>Sender</code> or <code>Resent-Sender</code>.
*/
public class MailboxFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.MailboxField {
private boolean parsed = false;
private Mailbox mailbox;
private ParseException parseException;
MailboxFieldImpl(final String name, final String body, final ByteSequence raw, DecodeMonitor monitor) {
super(name, body, raw, monitor);
}
/**
* @see org.apache.james.mime4j.dom.field.MailboxField#getMailbox()
*/
public Mailbox getMailbox() {
if (!parsed)
parse();
return mailbox;
}
/**
* @see org.apache.james.mime4j.dom.field.MailboxField#getParseException()
*/
@Override
public ParseException getParseException() {
if (!parsed)
parse();
return parseException;
}
private void parse() {
String body = getBody();
try {
MailboxList mailboxList = AddressBuilder.parseAddressList(body, monitor).flatten();
if (mailboxList.size() > 0) {
mailbox = mailboxList.get(0);
}
} catch (ParseException e) {
parseException = e;
}
parsed = true;
}
static final FieldParser<MailboxFieldImpl> PARSER = new FieldParser<MailboxFieldImpl>() {
public MailboxFieldImpl parse(final String name, final String body,
final ByteSequence raw, DecodeMonitor monitor) {
return new MailboxFieldImpl(name, body, raw, monitor);
}
};
}

View File

@ -19,47 +19,62 @@
package org.apache.james.mime4j.field;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.james.mime4j.field.address.AddressList;
import org.apache.james.mime4j.field.address.MailboxList;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.field.address.parser.AddressBuilder;
import org.apache.james.mime4j.field.address.parser.ParseException;
import org.apache.james.mime4j.util.ByteSequence;
/**
* Mailbox-list field such as <code>From</code> or <code>Resent-From</code>.
*/
public class MailboxListFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.MailboxListField {
private boolean parsed = false;
public class MailboxListField extends Field {
private MailboxList mailboxList;
private ParseException parseException;
protected MailboxListField(final String name, final String body, final String raw, final MailboxList mailboxList, final ParseException parseException) {
super(name, body, raw);
this.mailboxList = mailboxList;
this.parseException = parseException;
MailboxListFieldImpl(final String name, final String body, final ByteSequence raw, DecodeMonitor monitor) {
super(name, body, raw, monitor);
}
/**
* @see org.apache.james.mime4j.dom.field.MailboxListField#getMailboxList()
*/
public MailboxList getMailboxList() {
if (!parsed)
parse();
return mailboxList;
}
/**
* @see org.apache.james.mime4j.dom.field.MailboxListField#getParseException()
*/
@Override
public ParseException getParseException() {
if (!parsed)
parse();
return parseException;
}
public static class Parser implements FieldParser {
private static Log log = LogFactory.getLog(Parser.class);
public Field parse(final String name, final String body, final String raw) {
MailboxList mailboxList = null;
ParseException parseException = null;
try {
mailboxList = AddressList.parse(body).flatten();
}
catch (ParseException e) {
if (log.isDebugEnabled()) {
log.debug("Parsing value '" + body + "': "+ e.getMessage());
}
parseException = e;
}
return new MailboxListField(name, body, raw, mailboxList, parseException);
private void parse() {
String body = getBody();
try {
mailboxList = AddressBuilder.parseAddressList(body, monitor).flatten();
} catch (ParseException e) {
parseException = e;
}
parsed = true;
}
static final FieldParser<MailboxListFieldImpl> PARSER = new FieldParser<MailboxListFieldImpl>() {
public MailboxListFieldImpl parse(final String name, final String body,
final ByteSequence raw, DecodeMonitor monitor) {
return new MailboxListFieldImpl(name, body, raw, monitor);
}
};
}

View File

@ -0,0 +1,62 @@
/****************************************************************
* 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.field;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.codec.DecoderUtil;
import org.apache.james.mime4j.util.ByteSequence;
/**
* Simple unstructured field such as <code>Subject</code>.
*/
public class UnstructuredFieldImpl extends AbstractField implements org.apache.james.mime4j.dom.field.UnstructuredField {
private boolean parsed = false;
private String value;
UnstructuredFieldImpl(String name, String body, ByteSequence raw, DecodeMonitor monitor) {
super(name, body, raw, monitor);
}
/**
* @see org.apache.james.mime4j.dom.field.UnstructuredField#getValue()
*/
public String getValue() {
if (!parsed)
parse();
return value;
}
private void parse() {
String body = getBody();
value = DecoderUtil.decodeEncodedWords(body, monitor);
parsed = true;
}
static final FieldParser<UnstructuredFieldImpl> PARSER = new FieldParser<UnstructuredFieldImpl>() {
public UnstructuredFieldImpl parse(final String name, final String body,
final ByteSequence raw, DecodeMonitor monitor) {
return new UnstructuredFieldImpl(name, body, raw, monitor);
}
};
}

View File

@ -1,140 +0,0 @@
/****************************************************************
* 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.field.address;
import org.apache.james.mime4j.field.address.parser.AddressListParser;
import org.apache.james.mime4j.field.address.parser.ParseException;
import java.io.StringReader;
import java.util.ArrayList;
/**
* An immutable, random-access list of Address objects.
*
*
*/
public class AddressList {
private ArrayList addresses;
/**
* @param addresses An ArrayList that contains only Address objects.
* @param dontCopy true iff it is not possible for the addresses ArrayList to be modified by someone else.
*/
public AddressList(ArrayList addresses, boolean dontCopy) {
if (addresses != null)
this.addresses = (dontCopy ? addresses : (ArrayList) addresses.clone());
else
this.addresses = new ArrayList(0);
}
/**
* The number of elements in this list.
*/
public int size() {
return addresses.size();
}
/**
* Gets an address.
*/
public Address get(int index) {
if (0 > index || size() <= index)
throw new IndexOutOfBoundsException();
return (Address) addresses.get(index);
}
/**
* Returns a flat list of all mailboxes represented
* in this address list. Use this if you don't care
* about grouping.
*/
public MailboxList flatten() {
// in the common case, all addresses are mailboxes
boolean groupDetected = false;
for (int i = 0; i < size(); i++) {
if (!(get(i) instanceof Mailbox)) {
groupDetected = true;
break;
}
}
if (!groupDetected)
return new MailboxList(addresses, true);
ArrayList results = new ArrayList();
for (int i = 0; i < size(); i++) {
Address addr = get(i);
addr.addMailboxesTo(results);
}
// copy-on-construct this time, because subclasses
// could have held onto a reference to the results
return new MailboxList(results, false);
}
/**
* Dumps a representation of this address list to
* stdout, for debugging purposes.
*/
public void print() {
for (int i = 0; i < size(); i++) {
Address addr = get(i);
System.out.println(addr.toString());
}
}
/**
* Parse the address list string, such as the value
* of a From, To, Cc, Bcc, Sender, or Reply-To
* header.
*
* The string MUST be unfolded already.
*/
public static AddressList parse(String rawAddressList) throws ParseException {
AddressListParser parser = new AddressListParser(new StringReader(rawAddressList));
return Builder.getInstance().buildAddressList(parser.parse());
}
/**
* Test console.
*/
public static void main(String[] args) throws Exception {
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
while (true) {
try {
System.out.print("> ");
String line = reader.readLine();
if (line.length() == 0 || line.toLowerCase().equals("exit") || line.toLowerCase().equals("quit")) {
System.out.println("Goodbye.");
return;
}
AddressList list = parse(line);
list.print();
}
catch(Exception e) {
e.printStackTrace();
Thread.sleep(300);
}
}
}
}

View File

@ -1,244 +0,0 @@
/****************************************************************
* 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.field.address;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.james.mime4j.decoder.DecoderUtil;
import org.apache.james.mime4j.field.address.parser.*;
import org.apache.james.mime4j.field.address.parser.ASTaddr_spec;
import org.apache.james.mime4j.field.address.parser.ASTaddress;
import org.apache.james.mime4j.field.address.parser.ASTaddress_list;
import org.apache.james.mime4j.field.address.parser.ASTangle_addr;
import org.apache.james.mime4j.field.address.parser.ASTdomain;
import org.apache.james.mime4j.field.address.parser.ASTgroup_body;
import org.apache.james.mime4j.field.address.parser.ASTlocal_part;
import org.apache.james.mime4j.field.address.parser.ASTmailbox;
import org.apache.james.mime4j.field.address.parser.ASTname_addr;
import org.apache.james.mime4j.field.address.parser.ASTphrase;
import org.apache.james.mime4j.field.address.parser.ASTroute;
import org.apache.james.mime4j.field.address.parser.Node;
import org.apache.james.mime4j.field.address.parser.SimpleNode;
import org.apache.james.mime4j.field.address.parser.Token;
/**
* Transforms the JJTree-generated abstract syntax tree
* into a graph of org.apache.james.mime4j.field.address objects.
*
*
*/
class Builder {
private static Builder singleton = new Builder();
public static Builder getInstance() {
return singleton;
}
public AddressList buildAddressList(ASTaddress_list node) {
ArrayList list = new ArrayList();
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
ASTaddress childNode = (ASTaddress) node.jjtGetChild(i);
Address address = buildAddress(childNode);
list.add(address);
}
return new AddressList(list, true);
}
private Address buildAddress(ASTaddress node) {
ChildNodeIterator it = new ChildNodeIterator(node);
Node n = it.nextNode();
if (n instanceof ASTaddr_spec) {
return buildAddrSpec((ASTaddr_spec)n);
}
else if (n instanceof ASTangle_addr) {
return buildAngleAddr((ASTangle_addr)n);
}
else if (n instanceof ASTphrase) {
String name = buildString((ASTphrase)n, false);
Node n2 = it.nextNode();
if (n2 instanceof ASTgroup_body) {
return new Group(name, buildGroupBody((ASTgroup_body)n2));
}
else if (n2 instanceof ASTangle_addr) {
name = DecoderUtil.decodeEncodedWords(name);
return new NamedMailbox(name, buildAngleAddr((ASTangle_addr)n2));
}
else {
throw new IllegalStateException();
}
}
else {
throw new IllegalStateException();
}
}
private MailboxList buildGroupBody(ASTgroup_body node) {
ArrayList results = new ArrayList();
ChildNodeIterator it = new ChildNodeIterator(node);
while (it.hasNext()) {
Node n = it.nextNode();
if (n instanceof ASTmailbox)
results.add(buildMailbox((ASTmailbox)n));
else
throw new IllegalStateException();
}
return new MailboxList(results, true);
}
private Mailbox buildMailbox(ASTmailbox node) {
ChildNodeIterator it = new ChildNodeIterator(node);
Node n = it.nextNode();
if (n instanceof ASTaddr_spec) {
return buildAddrSpec((ASTaddr_spec)n);
}
else if (n instanceof ASTangle_addr) {
return buildAngleAddr((ASTangle_addr)n);
}
else if (n instanceof ASTname_addr) {
return buildNameAddr((ASTname_addr)n);
}
else {
throw new IllegalStateException();
}
}
private NamedMailbox buildNameAddr(ASTname_addr node) {
ChildNodeIterator it = new ChildNodeIterator(node);
Node n = it.nextNode();
String name;
if (n instanceof ASTphrase) {
name = buildString((ASTphrase)n, false);
}
else {
throw new IllegalStateException();
}
n = it.nextNode();
if (n instanceof ASTangle_addr) {
name = DecoderUtil.decodeEncodedWords(name);
return new NamedMailbox(name, buildAngleAddr((ASTangle_addr) n));
}
else {
throw new IllegalStateException();
}
}
private Mailbox buildAngleAddr(ASTangle_addr node) {
ChildNodeIterator it = new ChildNodeIterator(node);
DomainList route = null;
Node n = it.nextNode();
if (n instanceof ASTroute) {
route = buildRoute((ASTroute)n);
n = it.nextNode();
}
else if (n instanceof ASTaddr_spec)
; // do nothing
else
throw new IllegalStateException();
if (n instanceof ASTaddr_spec)
return buildAddrSpec(route, (ASTaddr_spec)n);
else
throw new IllegalStateException();
}
private DomainList buildRoute(ASTroute node) {
ArrayList results = new ArrayList(node.jjtGetNumChildren());
ChildNodeIterator it = new ChildNodeIterator(node);
while (it.hasNext()) {
Node n = it.nextNode();
if (n instanceof ASTdomain)
results.add(buildString((ASTdomain)n, true));
else
throw new IllegalStateException();
}
return new DomainList(results, true);
}
private Mailbox buildAddrSpec(ASTaddr_spec node) {
return buildAddrSpec(null, node);
}
private Mailbox buildAddrSpec(DomainList route, ASTaddr_spec node) {
ChildNodeIterator it = new ChildNodeIterator(node);
String localPart = buildString((ASTlocal_part)it.nextNode(), true);
String domain = buildString((ASTdomain)it.nextNode(), true);
return new Mailbox(route, localPart, domain);
}
private String buildString(SimpleNode node, boolean stripSpaces) {
Token head = node.firstToken;
Token tail = node.lastToken;
StringBuffer out = new StringBuffer();
while (head != tail) {
out.append(head.image);
head = head.next;
if (!stripSpaces)
addSpecials(out, head.specialToken);
}
out.append(tail.image);
return out.toString();
}
private void addSpecials(StringBuffer out, Token specialToken) {
if (specialToken != null) {
addSpecials(out, specialToken.specialToken);
out.append(specialToken.image);
}
}
private static class ChildNodeIterator implements Iterator {
private SimpleNode simpleNode;
private int index;
private int len;
public ChildNodeIterator(SimpleNode simpleNode) {
this.simpleNode = simpleNode;
this.len = simpleNode.jjtGetNumChildren();
this.index = 0;
}
public void remove() {
throw new UnsupportedOperationException();
}
public boolean hasNext() {
return index < len;
}
public Object next() {
return nextNode();
}
public Node nextNode() {
return simpleNode.jjtGetChild(index++);
}
}
}

View File

@ -1,76 +0,0 @@
/****************************************************************
* 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.field.address;
import java.util.ArrayList;
/**
* An immutable, random-access list of Strings (that
* are supposedly domain names or domain literals).
*
*
*/
public class DomainList {
private ArrayList domains;
/**
* @param domains An ArrayList that contains only String objects.
* @param dontCopy true iff it is not possible for the domains ArrayList to be modified by someone else.
*/
public DomainList(ArrayList domains, boolean dontCopy) {
if (domains != null)
this.domains = (dontCopy ? domains : (ArrayList) domains.clone());
else
this.domains = new ArrayList(0);
}
/**
* The number of elements in this list.
*/
public int size() {
return domains.size();
}
/**
* Gets the domain name or domain literal at the
* specified index.
* @throws IndexOutOfBoundsException If index is &lt; 0 or &gt;= size().
*/
public String get(int index) {
if (0 > index || size() <= index)
throw new IndexOutOfBoundsException();
return (String) domains.get(index);
}
/**
* Returns the list of domains formatted as a route
* string (not including the trailing ':').
*/
public String toRouteString() {
StringBuffer out = new StringBuffer();
for (int i = 0; i < domains.size(); i++) {
out.append("@");
out.append(get(i));
if (i + 1 < domains.size())
out.append(",");
}
return out.toString();
}
}

View File

@ -1,73 +0,0 @@
/****************************************************************
* 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.field.address;
import java.util.ArrayList;
/**
* A named group of zero or more mailboxes.
*
*
*/
public class Group extends Address {
private String name;
private MailboxList mailboxList;
/**
* @param name The group name.
* @param mailboxes The mailboxes in this group.
*/
public Group(String name, MailboxList mailboxes) {
this.name = name;
this.mailboxList = mailboxes;
}
/**
* Returns the group name.
*/
public String getName() {
return name;
}
/**
* Returns the mailboxes in this group.
*/
public MailboxList getMailboxes() {
return mailboxList;
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(name);
buf.append(":");
for (int i = 0; i < mailboxList.size(); i++) {
buf.append(mailboxList.get(i).toString());
if (i + 1 < mailboxList.size())
buf.append(",");
}
buf.append(";");
return buf.toString();
}
protected void doAddMailboxesTo(ArrayList results) {
for (int i = 0; i < mailboxList.size(); i++)
results.add(mailboxList.get(i));
}
}

View File

@ -1,119 +0,0 @@
/****************************************************************
* 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.field.address;
import java.util.ArrayList;
/**
* Represents a single e-mail address.
*
*
*/
public class Mailbox extends Address {
private DomainList route;
private String localPart;
private String domain;
/**
* Creates a mailbox without a route. Routes are obsolete.
* @param localPart The part of the e-mail address to the left of the "@".
* @param domain The part of the e-mail address to the right of the "@".
*/
public Mailbox(String localPart, String domain) {
this(null, localPart, domain);
}
/**
* Creates a mailbox with a route. Routes are obsolete.
* @param route The zero or more domains that make up the route. Can be null.
* @param localPart The part of the e-mail address to the left of the "@".
* @param domain The part of the e-mail address to the right of the "@".
*/
public Mailbox(DomainList route, String localPart, String domain) {
this.route = route;
this.localPart = localPart;
this.domain = domain;
}
/**
* Returns the route list.
*/
public DomainList getRoute() {
return route;
}
/**
* Returns the left part of the e-mail address
* (before "@").
*/
public String getLocalPart() {
return localPart;
}
/**
* Returns the right part of the e-mail address
* (after "@").
*/
public String getDomain() {
return domain;
}
/**
* Formats the address as a string, not including
* the route.
*
* @see #getAddressString(boolean)
*/
public String getAddressString() {
return getAddressString(false);
}
/**
* Note that this value may not be usable
* for transport purposes, only display purposes.
*
* For example, if the unparsed address was
*
* <"Joe Cheng"@joecheng.com>
*
* this method would return
*
* <Joe Cheng@joecheng.com>
*
* which is not valid for transport; the local part
* would need to be re-quoted.
*
* @param includeRoute true if the route should be included if it exists.
*/
public String getAddressString(boolean includeRoute) {
return "<" + (!includeRoute || route == null ? "" : route.toRouteString() + ":")
+ localPart
+ (domain == null ? "" : "@")
+ domain + ">";
}
protected final void doAddMailboxesTo(ArrayList results) {
results.add(this);
}
public String toString() {
return getAddressString();
}
}

View File

@ -1,70 +0,0 @@
/****************************************************************
* 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.field.address;
/**
* A Mailbox that has a name/description.
*
*
*/
public class NamedMailbox extends Mailbox {
private String name;
/**
* @see Mailbox#Mailbox(String, String)
*/
public NamedMailbox(String name, String localPart, String domain) {
super(localPart, domain);
this.name = name;
}
/**
* @see Mailbox#Mailbox(DomainList, String, String)
*/
public NamedMailbox(String name, DomainList route, String localPart, String domain) {
super(route, localPart, domain);
this.name = name;
}
/**
* Creates a named mailbox based on an unnamed mailbox.
*/
public NamedMailbox(String name, Mailbox baseMailbox) {
super(baseMailbox.getRoute(), baseMailbox.getLocalPart(), baseMailbox.getDomain());
this.name = name;
}
/**
* Returns the name of the mailbox.
*/
public String getName() {
return this.name;
}
/**
* Same features (or problems) as Mailbox.getAddressString(boolean),
* only more so.
*
* @see Mailbox#getAddressString(boolean)
*/
public String getAddressString(boolean includeRoute) {
return (name == null ? "" : name + " ") + super.getAddressString(includeRoute);
}
}

View File

@ -0,0 +1,209 @@
/****************************************************************
* 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.field.address.formatter;
import org.apache.james.mime4j.codec.EncoderUtil;
import org.apache.james.mime4j.dom.address.Address;
import org.apache.james.mime4j.dom.address.Group;
import org.apache.james.mime4j.dom.address.Mailbox;
public class AddressFormatter {
/**
* Formats the address as a human readable string, not including the route.
* The resulting string is intended for display purposes only and cannot be
* used for transport purposes.
*
* For example, if the unparsed address was
*
* <"Joe Cheng"@joecheng.com>
*
* this method would return
*
* <Joe Cheng@joecheng.com>
*
* which is not valid for transport; the local part would need to be
* re-quoted.
*
* @param includeRoute
* <code>true</code> if the route should be included if it
* exists, <code>false</code> otherwise.
* @return a string representation of this address intended to be displayed.
*/
public static void format(final StringBuilder sb, final Address address, boolean includeRoute) {
if (address == null) {
return;
}
if (address instanceof Mailbox) {
format(sb, (Mailbox) address, includeRoute);
} else if (address instanceof Group) {
format(sb, (Group) address, includeRoute);
} else {
throw new IllegalArgumentException("Unsuppported Address class: " + address.getClass());
}
}
/**
* Returns a string representation of this address that can be used for
* transport purposes. The route is never included in this representation
* because routes are obsolete and RFC 5322 states that obsolete syntactic
* forms MUST NOT be generated.
*
* @return a string representation of this address intended for transport
* purposes.
*/
public static void encode(final StringBuilder sb, final Address address) {
if (address == null) {
return;
}
if (address instanceof Mailbox) {
encode(sb, (Mailbox) address);
} else if (address instanceof Group) {
encode(sb, (Group) address);
} else {
throw new IllegalArgumentException("Unsuppported Address class: " + address.getClass());
}
}
public static void format(final StringBuilder sb, final Mailbox mailbox, boolean includeRoute) {
if (sb == null) {
throw new IllegalArgumentException("StringBuilder may not be null");
}
if (mailbox == null) {
throw new IllegalArgumentException("Mailbox may not be null");
}
includeRoute &= mailbox.getRoute() != null;
boolean includeAngleBrackets = mailbox.getName() != null || includeRoute;
if (mailbox.getName() != null) {
sb.append(mailbox.getName());
sb.append(' ');
}
if (includeAngleBrackets) {
sb.append('<');
}
if (includeRoute) {
sb.append(mailbox.getRoute().toRouteString());
sb.append(':');
}
sb.append(mailbox.getLocalPart());
if (mailbox.getDomain() != null) {
sb.append('@');
sb.append(mailbox.getDomain());
}
if (includeAngleBrackets) {
sb.append('>');
}
}
public static String format(final Mailbox mailbox, boolean includeRoute) {
StringBuilder sb = new StringBuilder();
format(sb, mailbox, includeRoute);
return sb.toString();
}
public static void encode(final StringBuilder sb, final Mailbox mailbox) {
if (sb == null) {
throw new IllegalArgumentException("StringBuilder may not be null");
}
if (mailbox == null) {
throw new IllegalArgumentException("Mailbox may not be null");
}
if (mailbox.getName() != null) {
sb.append(EncoderUtil.encodeAddressDisplayName(mailbox.getName()));
sb.append(" <");
}
sb.append(EncoderUtil.encodeAddressLocalPart(mailbox.getLocalPart()));
// domain = dot-atom / domain-literal
// domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
// dtext = %d33-90 / %d94-126
if (mailbox.getDomain() != null) {
sb.append('@');
sb.append(mailbox.getDomain());
}
if (mailbox.getName() != null) {
sb.append('>');
}
}
public static String encode(final Mailbox mailbox) {
StringBuilder sb = new StringBuilder();
encode(sb, mailbox);
return sb.toString();
}
public static void format(final StringBuilder sb, final Group group, boolean includeRoute) {
if (sb == null) {
throw new IllegalArgumentException("StringBuilder may not be null");
}
if (group == null) {
throw new IllegalArgumentException("Group may not be null");
}
sb.append(group.getName());
sb.append(':');
boolean first = true;
for (Mailbox mailbox : group.getMailboxes()) {
if (first) {
first = false;
} else {
sb.append(',');
}
sb.append(' ');
format(sb, mailbox, includeRoute);
}
sb.append(";");
}
public static String format(final Group group, boolean includeRoute) {
StringBuilder sb = new StringBuilder();
format(sb, group, includeRoute);
return sb.toString();
}
public static void encode(final StringBuilder sb, final Group group) {
if (sb == null) {
throw new IllegalArgumentException("StringBuilder may not be null");
}
if (group == null) {
throw new IllegalArgumentException("Group may not be null");
}
sb.append(EncoderUtil.encodeAddressDisplayName(group.getName()));
sb.append(':');
boolean first = true;
for (Mailbox mailbox : group.getMailboxes()) {
if (first) {
first = false;
} else {
sb.append(',');
}
sb.append(' ');
encode(sb, mailbox);
}
sb.append(';');
}
public static String encode(final Group group) {
StringBuilder sb = new StringBuilder();
encode(sb, group);
return sb.toString();
}
}

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTaddr_spec.java */
/* Generated By:JJTree: Do not edit this line. ASTaddr_spec.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTaddr_spec extends SimpleNode {
public
class ASTaddr_spec extends SimpleNode {
public ASTaddr_spec(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTaddr_spec extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=750ab0a2f6a942d3f4a7a7e076d12a4d (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTaddress.java */
/* Generated By:JJTree: Do not edit this line. ASTaddress.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTaddress extends SimpleNode {
public
class ASTaddress extends SimpleNode {
public ASTaddress(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTaddress extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=73be0a52ecfe4cf5d5a926be94fbb411 (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTaddress_list.java */
/* Generated By:JJTree: Do not edit this line. ASTaddress_list.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTaddress_list extends SimpleNode {
public
class ASTaddress_list extends SimpleNode {
public ASTaddress_list(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTaddress_list extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=6615a805f4abebfcf7252d9aad462299 (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTangle_addr.java */
/* Generated By:JJTree: Do not edit this line. ASTangle_addr.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTangle_addr extends SimpleNode {
public
class ASTangle_addr extends SimpleNode {
public ASTangle_addr(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTangle_addr extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=2201bedb23ef9d1b75dd88b2a5571384 (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTdomain.java */
/* Generated By:JJTree: Do not edit this line. ASTdomain.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTdomain extends SimpleNode {
public
class ASTdomain extends SimpleNode {
public ASTdomain(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTdomain extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=0105eb2d00d34d34b0665fd5ced14d52 (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTgroup_body.java */
/* Generated By:JJTree: Do not edit this line. ASTgroup_body.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTgroup_body extends SimpleNode {
public
class ASTgroup_body extends SimpleNode {
public ASTgroup_body(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTgroup_body extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=29b09d0a20de5b5f3d7e08c7e325d23f (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTlocal_part.java */
/* Generated By:JJTree: Do not edit this line. ASTlocal_part.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTlocal_part extends SimpleNode {
public
class ASTlocal_part extends SimpleNode {
public ASTlocal_part(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTlocal_part extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=42e77dd54203428772aecd61a95fc01c (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTmailbox.java */
/* Generated By:JJTree: Do not edit this line. ASTmailbox.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTmailbox extends SimpleNode {
public
class ASTmailbox extends SimpleNode {
public ASTmailbox(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTmailbox extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=246fe7d146969407e2c7977748e2fc99 (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTname_addr.java */
/* Generated By:JJTree: Do not edit this line. ASTname_addr.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTname_addr extends SimpleNode {
public
class ASTname_addr extends SimpleNode {
public ASTname_addr(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTname_addr extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=37e69dc07dfc157b3fb2449483ff82b6 (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTphrase.java */
/* Generated By:JJTree: Do not edit this line. ASTphrase.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTphrase extends SimpleNode {
public
class ASTphrase extends SimpleNode {
public ASTphrase(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTphrase extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=f1c7166e3c5b192d1f6db62b8239b0be (do not edit this line) */

View File

@ -1,8 +1,9 @@
/* Generated By:JJTree: Do not edit this line. ASTroute.java */
/* Generated By:JJTree: Do not edit this line. ASTroute.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=org.apache.james.mime4j.field.address.parser.BaseNode,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package org.apache.james.mime4j.field.address.parser;
public class ASTroute extends SimpleNode {
public
class ASTroute extends SimpleNode {
public ASTroute(int id) {
super(id);
}
@ -17,3 +18,4 @@ public class ASTroute extends SimpleNode {
return visitor.visit(this, data);
}
}
/* JavaCC - OriginalChecksum=bcec06c89402cfcb3700aefe8d5f14f9 (do not edit this line) */

View File

@ -0,0 +1,115 @@
/****************************************************************
* 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.field.address.parser;
import java.io.StringReader;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.dom.address.Address;
import org.apache.james.mime4j.dom.address.AddressList;
import org.apache.james.mime4j.dom.address.Group;
import org.apache.james.mime4j.dom.address.Mailbox;
public class AddressBuilder {
/**
* Parses the specified raw string into an address.
*
* @param rawAddressString
* string to parse.
* @param monitor the DecodeMonitor to be used while parsing/decoding
* @return an <code>Address</code> object for the specified string.
* @throws ParseException if the raw string does not represent a single address.
*/
public static Address parseAddress(String rawAddressString, DecodeMonitor monitor) throws ParseException {
AddressListParser parser = new AddressListParser(new StringReader(
rawAddressString));
return Builder.getInstance().buildAddress(parser.parseAddress(), monitor);
}
public static Address parseAddress(String rawAddressString) throws ParseException {
return parseAddress(rawAddressString, DecodeMonitor.STRICT);
}
/**
* Parse the address list string, such as the value of a From, To, Cc, Bcc,
* Sender, or Reply-To header.
*
* The string MUST be unfolded already.
* @param monitor the DecodeMonitor to be used while parsing/decoding
*/
public static AddressList parseAddressList(String rawAddressList, DecodeMonitor monitor)
throws ParseException {
AddressListParser parser = new AddressListParser(new StringReader(
rawAddressList));
try {
return Builder.getInstance().buildAddressList(parser.parseAddressList(), monitor);
} catch (RuntimeException e) {
throw new ParseException(e.getMessage());
}
}
public static AddressList parseAddressList(String rawAddressList) throws ParseException {
return parseAddressList(rawAddressList, DecodeMonitor.STRICT);
}
/**
* Parses the specified raw string into a mailbox address.
*
* @param rawMailboxString
* string to parse.
* @param monitor the DecodeMonitor to be used while parsing/decoding.
* @return a <code>Mailbox</code> object for the specified string.
* @throws ParseException
* if the raw string does not represent a single mailbox
* address.
*/
public static Mailbox parseMailbox(String rawMailboxString, DecodeMonitor monitor) throws ParseException {
AddressListParser parser = new AddressListParser(new StringReader(
rawMailboxString));
return Builder.getInstance().buildMailbox(parser.parseMailbox(), monitor);
}
public static Mailbox parseMailbox(String rawMailboxString) throws ParseException {
return parseMailbox(rawMailboxString, DecodeMonitor.STRICT);
}
/**
* Parses the specified raw string into a group address.
*
* @param rawGroupString
* string to parse.
* @return a <code>Group</code> object for the specified string.
* @throws ParseException
* if the raw string does not represent a single group address.
*/
public static Group parseGroup(String rawGroupString, DecodeMonitor monitor) throws ParseException {
Address address = parseAddress(rawGroupString, monitor);
if (!(address instanceof Group))
throw new ParseException("Not a group address");
return (Group) address;
}
public static Group parseGroup(String rawGroupString) throws ParseException {
return parseGroup(rawGroupString, DecodeMonitor.STRICT);
}
}

View File

@ -1,55 +1,71 @@
/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParser.java */
/*
* Copyright 2004 the mime4j project
*
* Licensed 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.
*/
/****************************************************************
* 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.field.address.parser;
public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeConstants, AddressListParserConstants {/*@bgen(jjtree)*/
protected JJTAddressListParserState jjtree = new JJTAddressListParserState();public static void main(String args[]) throws ParseException {
while (true) {
try {
AddressListParser parser = new AddressListParser(System.in);
parser.parseLine();
((SimpleNode)parser.jjtree.rootNode()).dump("> ");
} catch (Exception x) {
x.printStackTrace();
return;
}
}
while (true) {
try {
AddressListParser parser = new AddressListParser(System.in);
parser.parseLine();
((SimpleNode) parser.jjtree.rootNode()).dump("> ");
} catch (Exception x) {
x.printStackTrace();
return;
}
}
}
private static void log(String msg) {
System.out.print(msg);
}
public ASTaddress_list parse() throws ParseException {
public ASTaddress_list parseAddressList() throws ParseException {
try {
parseAll();
return (ASTaddress_list)jjtree.rootNode();
parseAddressList0();
return (ASTaddress_list) jjtree.rootNode();
} catch (TokenMgrError tme) {
throw new ParseException(tme.getMessage());
}
}
public ASTaddress parseAddress() throws ParseException {
try {
parseAddress0();
return (ASTaddress) jjtree.rootNode();
} catch (TokenMgrError tme) {
throw new ParseException(tme.getMessage());
}
}
public ASTmailbox parseMailbox() throws ParseException {
try {
parseMailbox0();
return (ASTmailbox) jjtree.rootNode();
} catch (TokenMgrError tme) {
throw new ParseException(tme.getMessage());
}
}
void jjtreeOpenNodeScope(Node n) {
((SimpleNode)n).firstToken = getToken(1);
((SimpleNode) n).firstToken = getToken(1);
}
void jjtreeCloseNodeScope(Node n) {
((SimpleNode)n).lastToken = getToken(0);
((SimpleNode) n).lastToken = getToken(0);
}
final public void parseLine() throws ParseException {
@ -65,11 +81,21 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
jj_consume_token(2);
}
final public void parseAll() throws ParseException {
final public void parseAddressList0() throws ParseException {
address_list();
jj_consume_token(0);
}
final public void parseAddress0() throws ParseException {
address();
jj_consume_token(0);
}
final public void parseMailbox0() throws ParseException {
mailbox();
jj_consume_token(0);
}
final public void address_list() throws ParseException {
/*@bgen(jjtree) address_list */
ASTaddress_list jjtn000 = new ASTaddress_list(JJTADDRESS_LIST);
@ -537,7 +563,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
jj_la1[17] = jj_gen;
;
}
if (t.image.charAt(t.image.length() - 1) != '.' || t.kind == AddressListParserConstants.QUOTEDSTRING)
if ( t.kind == AddressListParserConstants.QUOTEDSTRING || t.image.charAt(t.image.length() - 1) != '.')
{if (true) throw new ParseException("Words in local part must be separated by '.'");}
switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
case DOTATOM:
@ -610,21 +636,21 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
}
}
final private boolean jj_2_1(int xla) {
private boolean jj_2_1(int xla) {
jj_la = xla; jj_lastpos = jj_scanpos = token;
try { return !jj_3_1(); }
catch(LookaheadSuccess ls) { return true; }
finally { jj_save(0, xla); }
}
final private boolean jj_2_2(int xla) {
private boolean jj_2_2(int xla) {
jj_la = xla; jj_lastpos = jj_scanpos = token;
try { return !jj_3_2(); }
catch(LookaheadSuccess ls) { return true; }
finally { jj_save(1, xla); }
}
final private boolean jj_3R_11() {
private boolean jj_3R_11() {
Token xsp;
xsp = jj_scanpos;
if (jj_scan_token(9)) jj_scanpos = xsp;
@ -636,7 +662,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
return false;
}
final private boolean jj_3R_13() {
private boolean jj_3R_13() {
Token xsp;
xsp = jj_scanpos;
if (jj_scan_token(9)) jj_scanpos = xsp;
@ -644,19 +670,19 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
return false;
}
final private boolean jj_3R_8() {
private boolean jj_3R_8() {
if (jj_3R_9()) return true;
if (jj_scan_token(8)) return true;
if (jj_3R_10()) return true;
return false;
}
final private boolean jj_3_1() {
private boolean jj_3_1() {
if (jj_3R_8()) return true;
return false;
}
final private boolean jj_3R_12() {
private boolean jj_3R_12() {
if (jj_scan_token(DOTATOM)) return true;
Token xsp;
while (true) {
@ -666,7 +692,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
return false;
}
final private boolean jj_3R_10() {
private boolean jj_3R_10() {
Token xsp;
xsp = jj_scanpos;
if (jj_3R_12()) {
@ -676,12 +702,12 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
return false;
}
final private boolean jj_3_2() {
private boolean jj_3_2() {
if (jj_3R_8()) return true;
return false;
}
final private boolean jj_3R_9() {
private boolean jj_3R_9() {
Token xsp;
xsp = jj_scanpos;
if (jj_scan_token(14)) {
@ -695,35 +721,39 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
return false;
}
/** Generated Token Manager. */
public AddressListParserTokenManager token_source;
SimpleCharStream jj_input_stream;
public Token token, jj_nt;
/** Current token. */
public Token token;
/** Next token. */
public Token jj_nt;
private int jj_ntk;
private Token jj_scanpos, jj_lastpos;
private int jj_la;
public boolean lookingAhead = false;
private boolean jj_semLA;
private int jj_gen;
final private int[] jj_la1 = new int[22];
static private int[] jj_la1_0;
static private int[] jj_la1_1;
static {
jj_la1_0();
jj_la1_1();
jj_la1_init_0();
jj_la1_init_1();
}
private static void jj_la1_0() {
private static void jj_la1_init_0() {
jj_la1_0 = new int[] {0x2,0x80004040,0x8,0x80004040,0x50,0x80004040,0x80004040,0x80004040,0x8,0x80004040,0x100,0x108,0x8,0x80004000,0x80004000,0x80004000,0x80004200,0x200,0x80004000,0x4200,0x200,0x44000,};
}
private static void jj_la1_1() {
private static void jj_la1_init_1() {
jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,};
}
final private JJCalls[] jj_2_rtns = new JJCalls[2];
private boolean jj_rescan = false;
private int jj_gc = 0;
/** Constructor with InputStream. */
public AddressListParser(java.io.InputStream stream) {
this(stream, null);
}
/** Constructor with InputStream and supplied encoding */
public AddressListParser(java.io.InputStream stream, String encoding) {
try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
token_source = new AddressListParserTokenManager(jj_input_stream);
@ -734,9 +764,11 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
}
/** Reinitialise. */
public void ReInit(java.io.InputStream stream) {
ReInit(stream, null);
}
/** Reinitialise. */
public void ReInit(java.io.InputStream stream, String encoding) {
try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
token_source.ReInit(jj_input_stream);
@ -748,6 +780,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
}
/** Constructor. */
public AddressListParser(java.io.Reader stream) {
jj_input_stream = new SimpleCharStream(stream, 1, 1);
token_source = new AddressListParserTokenManager(jj_input_stream);
@ -758,6 +791,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
}
/** Reinitialise. */
public void ReInit(java.io.Reader stream) {
jj_input_stream.ReInit(stream, 1, 1);
token_source.ReInit(jj_input_stream);
@ -769,6 +803,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
}
/** Constructor with generated Token Manager. */
public AddressListParser(AddressListParserTokenManager tm) {
token_source = tm;
token = new Token();
@ -778,6 +813,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
}
/** Reinitialise. */
public void ReInit(AddressListParserTokenManager tm) {
token_source = tm;
token = new Token();
@ -788,7 +824,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
}
final private Token jj_consume_token(int kind) throws ParseException {
private Token jj_consume_token(int kind) throws ParseException {
Token oldToken;
if ((oldToken = token).next != null) token = token.next;
else token = token.next = token_source.getNextToken();
@ -814,7 +850,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
static private final class LookaheadSuccess extends java.lang.Error { }
final private LookaheadSuccess jj_ls = new LookaheadSuccess();
final private boolean jj_scan_token(int kind) {
private boolean jj_scan_token(int kind) {
if (jj_scanpos == jj_lastpos) {
jj_la--;
if (jj_scanpos.next == null) {
@ -835,6 +871,8 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
return false;
}
/** Get the next Token. */
final public Token getNextToken() {
if (token.next != null) token = token.next;
else token = token.next = token_source.getNextToken();
@ -843,8 +881,9 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
return token;
}
/** Get the specific Token. */
final public Token getToken(int index) {
Token t = lookingAhead ? jj_scanpos : token;
Token t = token;
for (int i = 0; i < index; i++) {
if (t.next != null) t = t.next;
else t = t.next = token_source.getNextToken();
@ -852,14 +891,14 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
return t;
}
final private int jj_ntk() {
private int jj_ntk() {
if ((jj_nt=token.next) == null)
return (jj_ntk = (token.next=token_source.getNextToken()).kind);
else
return (jj_ntk = jj_nt.kind);
}
private java.util.Vector jj_expentries = new java.util.Vector();
private java.util.List<int[]> jj_expentries = new java.util.ArrayList<int[]>();
private int[] jj_expentry;
private int jj_kind = -1;
private int[] jj_lasttokens = new int[100];
@ -874,31 +913,26 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
for (int i = 0; i < jj_endpos; i++) {
jj_expentry[i] = jj_lasttokens[i];
}
boolean exists = false;
for (java.util.Enumeration e = jj_expentries.elements(); e.hasMoreElements();) {
int[] oldentry = (int[])(e.nextElement());
jj_entries_loop: for (java.util.Iterator<?> it = jj_expentries.iterator(); it.hasNext();) {
int[] oldentry = (int[])(it.next());
if (oldentry.length == jj_expentry.length) {
exists = true;
for (int i = 0; i < jj_expentry.length; i++) {
if (oldentry[i] != jj_expentry[i]) {
exists = false;
break;
continue jj_entries_loop;
}
}
if (exists) break;
jj_expentries.add(jj_expentry);
break jj_entries_loop;
}
}
if (!exists) jj_expentries.addElement(jj_expentry);
if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
}
}
/** Generate ParseException. */
public ParseException generateParseException() {
jj_expentries.removeAllElements();
jj_expentries.clear();
boolean[] la1tokens = new boolean[34];
for (int i = 0; i < 34; i++) {
la1tokens[i] = false;
}
if (jj_kind >= 0) {
la1tokens[jj_kind] = true;
jj_kind = -1;
@ -919,7 +953,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
if (la1tokens[i]) {
jj_expentry = new int[1];
jj_expentry[0] = i;
jj_expentries.addElement(jj_expentry);
jj_expentries.add(jj_expentry);
}
}
jj_endpos = 0;
@ -927,18 +961,20 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
jj_add_error_token(0, 0);
int[][] exptokseq = new int[jj_expentries.size()][];
for (int i = 0; i < jj_expentries.size(); i++) {
exptokseq[i] = (int[])jj_expentries.elementAt(i);
exptokseq[i] = jj_expentries.get(i);
}
return new ParseException(token, exptokseq, tokenImage);
}
/** Enable tracing. */
final public void enable_tracing() {
}
/** Disable tracing. */
final public void disable_tracing() {
}
final private void jj_rescan_token() {
private void jj_rescan_token() {
jj_rescan = true;
for (int i = 0; i < 2; i++) {
try {
@ -958,7 +994,7 @@ public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeC
jj_rescan = false;
}
final private void jj_save(int index, int xla) {
private void jj_save(int index, int xla) {
JJCalls p = jj_2_rtns[index];
while (p.gen > jj_gen) {
if (p.next == null) { p = p.next = new JJCalls(); break; }

View File

@ -1,595 +0,0 @@
/*@bgen(jjtree) Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParser.jj */
/*@egen*//****************************************************************
* 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. *
****************************************************************/
/**
* RFC2822 address list parser.
*
* Created 9/17/2004
* by Joe Cheng <code@joecheng.com>
*/
options {
STATIC=false;
LOOKAHEAD=1;
//DEBUG_PARSER=true;
//DEBUG_TOKEN_MANAGER=true;
}
PARSER_BEGIN(AddressListParser)
/*
* Copyright 2004 the mime4j project
*
* Licensed 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.field.address.parser;
public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeConstants/*@egen*/ {/*@bgen(jjtree)*/
protected JJTAddressListParserState jjtree = new JJTAddressListParserState();
/*@egen*/
public static void main(String args[]) throws ParseException {
while (true) {
try {
AddressListParser parser = new AddressListParser(System.in);
parser.parseLine();
((SimpleNode)parser.jjtree.rootNode()).dump("> ");
} catch (Exception x) {
x.printStackTrace();
return;
}
}
}
private static void log(String msg) {
System.out.print(msg);
}
public ASTaddress_list parse() throws ParseException {
try {
parseAll();
return (ASTaddress_list)jjtree.rootNode();
} catch (TokenMgrError tme) {
throw new ParseException(tme.getMessage());
}
}
void jjtreeOpenNodeScope(Node n) {
((SimpleNode)n).firstToken = getToken(1);
}
void jjtreeCloseNodeScope(Node n) {
((SimpleNode)n).lastToken = getToken(0);
}
}
PARSER_END(AddressListParser)
void parseLine() :
{}
{
address_list() ["\r"] "\n"
}
void parseAll() :
{}
{
address_list() <EOF>
}
void address_list() :
{/*@bgen(jjtree) address_list */
ASTaddress_list jjtn000 = new ASTaddress_list(JJTADDRESS_LIST);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) address_list */
try {
/*@egen*/
[ address() ]
(
","
[ address() ]
)*/*@bgen(jjtree)*/
} catch (Throwable jjte000) {
if (jjtc000) {
jjtree.clearNodeScope(jjtn000);
jjtc000 = false;
} else {
jjtree.popNode();
}
if (jjte000 instanceof RuntimeException) {
throw (RuntimeException)jjte000;
}
if (jjte000 instanceof ParseException) {
throw (ParseException)jjte000;
}
throw (Error)jjte000;
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void address() :
{/*@bgen(jjtree) address */
ASTaddress jjtn000 = new ASTaddress(JJTADDRESS);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) address */
try {
/*@egen*/
LOOKAHEAD(2147483647)
addr_spec()
| angle_addr()
| ( phrase() (group_body() | angle_addr()) )/*@bgen(jjtree)*/
} catch (Throwable jjte000) {
if (jjtc000) {
jjtree.clearNodeScope(jjtn000);
jjtc000 = false;
} else {
jjtree.popNode();
}
if (jjte000 instanceof RuntimeException) {
throw (RuntimeException)jjte000;
}
if (jjte000 instanceof ParseException) {
throw (ParseException)jjte000;
}
throw (Error)jjte000;
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void mailbox() :
{/*@bgen(jjtree) mailbox */
ASTmailbox jjtn000 = new ASTmailbox(JJTMAILBOX);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) mailbox */
try {
/*@egen*/
LOOKAHEAD(2147483647)
addr_spec()
| angle_addr()
| name_addr()/*@bgen(jjtree)*/
} catch (Throwable jjte000) {
if (jjtc000) {
jjtree.clearNodeScope(jjtn000);
jjtc000 = false;
} else {
jjtree.popNode();
}
if (jjte000 instanceof RuntimeException) {
throw (RuntimeException)jjte000;
}
if (jjte000 instanceof ParseException) {
throw (ParseException)jjte000;
}
throw (Error)jjte000;
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void name_addr() :
{/*@bgen(jjtree) name_addr */
ASTname_addr jjtn000 = new ASTname_addr(JJTNAME_ADDR);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) name_addr */
try {
/*@egen*/
phrase() angle_addr()/*@bgen(jjtree)*/
} catch (Throwable jjte000) {
if (jjtc000) {
jjtree.clearNodeScope(jjtn000);
jjtc000 = false;
} else {
jjtree.popNode();
}
if (jjte000 instanceof RuntimeException) {
throw (RuntimeException)jjte000;
}
if (jjte000 instanceof ParseException) {
throw (ParseException)jjte000;
}
throw (Error)jjte000;
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void group_body() :
{/*@bgen(jjtree) group_body */
ASTgroup_body jjtn000 = new ASTgroup_body(JJTGROUP_BODY);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) group_body */
try {
/*@egen*/
":"
[ mailbox() ]
(
","
[ mailbox() ]
)*
";"/*@bgen(jjtree)*/
} catch (Throwable jjte000) {
if (jjtc000) {
jjtree.clearNodeScope(jjtn000);
jjtc000 = false;
} else {
jjtree.popNode();
}
if (jjte000 instanceof RuntimeException) {
throw (RuntimeException)jjte000;
}
if (jjte000 instanceof ParseException) {
throw (ParseException)jjte000;
}
throw (Error)jjte000;
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void angle_addr() :
{/*@bgen(jjtree) angle_addr */
ASTangle_addr jjtn000 = new ASTangle_addr(JJTANGLE_ADDR);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) angle_addr */
try {
/*@egen*/
"<" [ route() ] addr_spec() ">"/*@bgen(jjtree)*/
} catch (Throwable jjte000) {
if (jjtc000) {
jjtree.clearNodeScope(jjtn000);
jjtc000 = false;
} else {
jjtree.popNode();
}
if (jjte000 instanceof RuntimeException) {
throw (RuntimeException)jjte000;
}
if (jjte000 instanceof ParseException) {
throw (ParseException)jjte000;
}
throw (Error)jjte000;
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void route() :
{/*@bgen(jjtree) route */
ASTroute jjtn000 = new ASTroute(JJTROUTE);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) route */
try {
/*@egen*/
"@" domain() ( (",")* "@" domain() )* ":"/*@bgen(jjtree)*/
} catch (Throwable jjte000) {
if (jjtc000) {
jjtree.clearNodeScope(jjtn000);
jjtc000 = false;
} else {
jjtree.popNode();
}
if (jjte000 instanceof RuntimeException) {
throw (RuntimeException)jjte000;
}
if (jjte000 instanceof ParseException) {
throw (ParseException)jjte000;
}
throw (Error)jjte000;
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void phrase() :
{/*@bgen(jjtree) phrase */
ASTphrase jjtn000 = new ASTphrase(JJTPHRASE);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) phrase */
try {
/*@egen*/
( <DOTATOM>
| <QUOTEDSTRING>
)+/*@bgen(jjtree)*/
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void addr_spec() :
{/*@bgen(jjtree) addr_spec */
ASTaddr_spec jjtn000 = new ASTaddr_spec(JJTADDR_SPEC);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/}
{/*@bgen(jjtree) addr_spec */
try {
/*@egen*/
( local_part() "@" domain() )/*@bgen(jjtree)*/
} catch (Throwable jjte000) {
if (jjtc000) {
jjtree.clearNodeScope(jjtn000);
jjtc000 = false;
} else {
jjtree.popNode();
}
if (jjte000 instanceof RuntimeException) {
throw (RuntimeException)jjte000;
}
if (jjte000 instanceof ParseException) {
throw (ParseException)jjte000;
}
throw (Error)jjte000;
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void local_part() :
{/*@bgen(jjtree) local_part */
ASTlocal_part jjtn000 = new ASTlocal_part(JJTLOCAL_PART);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/ Token t; }
{/*@bgen(jjtree) local_part */
try {
/*@egen*/
( t=<DOTATOM> | t=<QUOTEDSTRING> )
( [t="."]
{
if (t.image.charAt(t.image.length() - 1) != '.' || t.kind == AddressListParserConstants.QUOTEDSTRING)
throw new ParseException("Words in local part must be separated by '.'");
}
( t=<DOTATOM> | t=<QUOTEDSTRING> )
)*/*@bgen(jjtree)*/
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
void domain() :
{/*@bgen(jjtree) domain */
ASTdomain jjtn000 = new ASTdomain(JJTDOMAIN);
boolean jjtc000 = true;
jjtree.openNodeScope(jjtn000);
jjtreeOpenNodeScope(jjtn000);
/*@egen*/ Token t; }
{/*@bgen(jjtree) domain */
try {
/*@egen*/
( t=<DOTATOM>
( [t="."]
{
if (t.image.charAt(t.image.length() - 1) != '.')
throw new ParseException("Atoms in domain names must be separated by '.'");
}
t=<DOTATOM>
)*
)
| <DOMAINLITERAL>/*@bgen(jjtree)*/
} finally {
if (jjtc000) {
jjtree.closeNodeScope(jjtn000, true);
jjtreeCloseNodeScope(jjtn000);
}
}
/*@egen*/
}
SPECIAL_TOKEN :
{
< WS: ( [" ", "\t"] )+ >
}
TOKEN :
{
< #ALPHA: ["a" - "z", "A" - "Z"] >
| < #DIGIT: ["0" - "9"] >
| < #ATEXT: ( <ALPHA> | <DIGIT>
| "!" | "#" | "$" | "%"
| "&" | "'" | "*" | "+"
| "-" | "/" | "=" | "?"
| "^" | "_" | "`" | "{"
| "|" | "}" | "~"
)>
| < DOTATOM: <ATEXT> ( <ATEXT> | "." )* >
}
TOKEN_MGR_DECLS :
{
// Keeps track of how many levels of comment nesting
// we've encountered. This is only used when the 2nd
// level is reached, for example ((this)), not (this).
// This is because the outermost level must be treated
// specially anyway, because the outermost ")" has a
// different token type than inner ")" instances.
static int commentNest;
}
MORE :
{
// domain literal
"[" : INDOMAINLITERAL
}
<INDOMAINLITERAL>
MORE :
{
< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
| < ~["[", "]", "\\"] >
}
<INDOMAINLITERAL>
TOKEN :
{
< DOMAINLITERAL: "]" > { matchedToken.image = image.toString(); }: DEFAULT
}
MORE :
{
// starts a comment
"(" : INCOMMENT
}
<INCOMMENT>
SKIP :
{
// ends a comment
< COMMENT: ")" > : DEFAULT
// if this is ever changed to not be a SKIP, need
// to make sure matchedToken.token = token.toString()
// is called.
}
<INCOMMENT>
MORE :
{
< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
| "(" { commentNest = 1; } : NESTED_COMMENT
| < <ANY>>
}
<NESTED_COMMENT>
MORE :
{
< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
| "(" { ++commentNest; }
| ")" { --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); }
| < <ANY>>
}
// QUOTED STRINGS
MORE :
{
"\"" { image.deleteCharAt(image.length() - 1); } : INQUOTEDSTRING
}
<INQUOTEDSTRING>
MORE :
{
< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
| < (~["\"", "\\"])+ >
}
<INQUOTEDSTRING>
TOKEN :
{
< QUOTEDSTRING: "\"" > { matchedToken.image = image.substring(0, image.length() - 1); } : DEFAULT
}
// GLOBALS
<*>
TOKEN :
{
< #QUOTEDPAIR: "\\" <ANY> >
| < #ANY: ~[] >
}
// ERROR!
/*
<*>
TOKEN :
{
< UNEXPECTED_CHAR: <ANY> >
}
*/

View File

@ -1,41 +1,66 @@
/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParserConstants.java */
/*
* Copyright 2004 the mime4j project
*
* Licensed 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.
*/
/****************************************************************
* 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.field.address.parser;
/**
* Token literal values and constants.
* Generated by org.javacc.parser.OtherFilesGen#start()
*/
public interface AddressListParserConstants {
/** End of File. */
int EOF = 0;
/** RegularExpression Id. */
int WS = 10;
/** RegularExpression Id. */
int ALPHA = 11;
/** RegularExpression Id. */
int DIGIT = 12;
/** RegularExpression Id. */
int ATEXT = 13;
/** RegularExpression Id. */
int DOTATOM = 14;
/** RegularExpression Id. */
int DOMAINLITERAL = 18;
/** RegularExpression Id. */
int COMMENT = 20;
/** RegularExpression Id. */
int QUOTEDSTRING = 31;
/** RegularExpression Id. */
int QUOTEDPAIR = 32;
/** RegularExpression Id. */
int ANY = 33;
/** Lexical state. */
int DEFAULT = 0;
/** Lexical state. */
int INDOMAINLITERAL = 1;
/** Lexical state. */
int INCOMMENT = 2;
/** Lexical state. */
int NESTED_COMMENT = 3;
/** Lexical state. */
int INQUOTEDSTRING = 4;
/** Literal token values. */
String[] tokenImage = {
"<EOF>",
"\"\\r\"",

View File

@ -1,21 +1,25 @@
/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParserTokenManager.java */
/*
* Copyright 2004 the mime4j project
*
* Licensed 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.
*/
/****************************************************************
* 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.field.address.parser;
/** Token Manager. */
public class AddressListParserTokenManager implements AddressListParserConstants
{
// Keeps track of how many levels of comment nesting
@ -25,7 +29,10 @@ public class AddressListParserTokenManager implements AddressListParserConstants
// specially anyway, because the outermost ")" has a
// different token type than inner ")" instances.
static int commentNest;
/** Debug output. */
public java.io.PrintStream debugStream = System.out;
/** Set debug output. */
public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
private final int jjStopStringLiteralDfa_0(int pos, long active0)
{
@ -39,21 +46,13 @@ private final int jjStartNfa_0(int pos, long active0)
{
return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
}
private final int jjStopAtPos(int pos, int kind)
private int jjStopAtPos(int pos, int kind)
{
jjmatchedKind = kind;
jjmatchedPos = pos;
return pos + 1;
}
private final int jjStartNfaWithStates_0(int pos, int kind, int state)
{
jjmatchedKind = kind;
jjmatchedPos = pos;
try { curChar = input_stream.readChar(); }
catch(java.io.IOException e) { return pos + 1; }
return jjMoveNfa_0(state, pos + 1);
}
private final int jjMoveStringLiteralDfa0_0()
private int jjMoveStringLiteralDfa0_0()
{
switch(curChar)
{
@ -85,44 +84,13 @@ private final int jjMoveStringLiteralDfa0_0()
return jjMoveNfa_0(1, 0);
}
}
private final void jjCheckNAdd(int state)
private int jjMoveNfa_0(int startState, int curPos)
{
if (jjrounds[state] != jjround)
{
jjstateSet[jjnewStateCnt++] = state;
jjrounds[state] = jjround;
}
}
private final void jjAddStates(int start, int end)
{
do {
jjstateSet[jjnewStateCnt++] = jjnextStates[start];
} while (start++ != end);
}
private final void jjCheckNAddTwoStates(int state1, int state2)
{
jjCheckNAdd(state1);
jjCheckNAdd(state2);
}
private final void jjCheckNAddStates(int start, int end)
{
do {
jjCheckNAdd(jjnextStates[start]);
} while (start++ != end);
}
private final void jjCheckNAddStates(int start)
{
jjCheckNAdd(jjnextStates[start]);
jjCheckNAdd(jjnextStates[start + 1]);
}
private final int jjMoveNfa_0(int startState, int curPos)
{
int[] nextStates;
int startsAt = 0;
jjnewStateCnt = 3;
int i = 1;
jjstateSet[0] = startState;
int j, kind = 0x7fffffff;
int kind = 0x7fffffff;
for (;;)
{
if (++jjround == 0x7fffffff)
@ -130,7 +98,7 @@ private final int jjMoveNfa_0(int startState, int curPos)
if (curChar < 64)
{
long l = 1L << curChar;
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -168,7 +136,7 @@ private final int jjMoveNfa_0(int startState, int curPos)
else if (curChar < 128)
{
long l = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -188,7 +156,7 @@ private final int jjMoveNfa_0(int startState, int curPos)
{
int i2 = (curChar & 0xff) >> 6;
long l2 = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -221,15 +189,7 @@ private final int jjStartNfa_2(int pos, long active0)
{
return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1);
}
private final int jjStartNfaWithStates_2(int pos, int kind, int state)
{
jjmatchedKind = kind;
jjmatchedPos = pos;
try { curChar = input_stream.readChar(); }
catch(java.io.IOException e) { return pos + 1; }
return jjMoveNfa_2(state, pos + 1);
}
private final int jjMoveStringLiteralDfa0_2()
private int jjMoveStringLiteralDfa0_2()
{
switch(curChar)
{
@ -244,14 +204,13 @@ private final int jjMoveStringLiteralDfa0_2()
static final long[] jjbitVec0 = {
0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
};
private final int jjMoveNfa_2(int startState, int curPos)
private int jjMoveNfa_2(int startState, int curPos)
{
int[] nextStates;
int startsAt = 0;
jjnewStateCnt = 3;
int i = 1;
jjstateSet[0] = startState;
int j, kind = 0x7fffffff;
int kind = 0x7fffffff;
for (;;)
{
if (++jjround == 0x7fffffff)
@ -259,7 +218,7 @@ private final int jjMoveNfa_2(int startState, int curPos)
if (curChar < 64)
{
long l = 1L << curChar;
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -278,7 +237,7 @@ private final int jjMoveNfa_2(int startState, int curPos)
else if (curChar < 128)
{
long l = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -304,7 +263,7 @@ private final int jjMoveNfa_2(int startState, int curPos)
{
int i2 = (curChar & 0xff) >> 6;
long l2 = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -345,15 +304,7 @@ private final int jjStartNfa_4(int pos, long active0)
{
return jjMoveNfa_4(jjStopStringLiteralDfa_4(pos, active0), pos + 1);
}
private final int jjStartNfaWithStates_4(int pos, int kind, int state)
{
jjmatchedKind = kind;
jjmatchedPos = pos;
try { curChar = input_stream.readChar(); }
catch(java.io.IOException e) { return pos + 1; }
return jjMoveNfa_4(state, pos + 1);
}
private final int jjMoveStringLiteralDfa0_4()
private int jjMoveStringLiteralDfa0_4()
{
switch(curChar)
{
@ -363,14 +314,13 @@ private final int jjMoveStringLiteralDfa0_4()
return jjMoveNfa_4(0, 0);
}
}
private final int jjMoveNfa_4(int startState, int curPos)
private int jjMoveNfa_4(int startState, int curPos)
{
int[] nextStates;
int startsAt = 0;
jjnewStateCnt = 3;
int i = 1;
jjstateSet[0] = startState;
int j, kind = 0x7fffffff;
int kind = 0x7fffffff;
for (;;)
{
if (++jjround == 0x7fffffff)
@ -378,7 +328,7 @@ private final int jjMoveNfa_4(int startState, int curPos)
if (curChar < 64)
{
long l = 1L << curChar;
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -401,7 +351,7 @@ private final int jjMoveNfa_4(int startState, int curPos)
else if (curChar < 128)
{
long l = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -434,7 +384,7 @@ private final int jjMoveNfa_4(int startState, int curPos)
{
int i2 = (curChar & 0xff) >> 6;
long l2 = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -479,15 +429,7 @@ private final int jjStartNfa_3(int pos, long active0)
{
return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1);
}
private final int jjStartNfaWithStates_3(int pos, int kind, int state)
{
jjmatchedKind = kind;
jjmatchedPos = pos;
try { curChar = input_stream.readChar(); }
catch(java.io.IOException e) { return pos + 1; }
return jjMoveNfa_3(state, pos + 1);
}
private final int jjMoveStringLiteralDfa0_3()
private int jjMoveStringLiteralDfa0_3()
{
switch(curChar)
{
@ -499,14 +441,13 @@ private final int jjMoveStringLiteralDfa0_3()
return jjMoveNfa_3(0, 0);
}
}
private final int jjMoveNfa_3(int startState, int curPos)
private int jjMoveNfa_3(int startState, int curPos)
{
int[] nextStates;
int startsAt = 0;
jjnewStateCnt = 3;
int i = 1;
jjstateSet[0] = startState;
int j, kind = 0x7fffffff;
int kind = 0x7fffffff;
for (;;)
{
if (++jjround == 0x7fffffff)
@ -514,7 +455,7 @@ private final int jjMoveNfa_3(int startState, int curPos)
if (curChar < 64)
{
long l = 1L << curChar;
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -533,7 +474,7 @@ private final int jjMoveNfa_3(int startState, int curPos)
else if (curChar < 128)
{
long l = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -559,7 +500,7 @@ private final int jjMoveNfa_3(int startState, int curPos)
{
int i2 = (curChar & 0xff) >> 6;
long l2 = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -600,15 +541,7 @@ private final int jjStartNfa_1(int pos, long active0)
{
return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1);
}
private final int jjStartNfaWithStates_1(int pos, int kind, int state)
{
jjmatchedKind = kind;
jjmatchedPos = pos;
try { curChar = input_stream.readChar(); }
catch(java.io.IOException e) { return pos + 1; }
return jjMoveNfa_1(state, pos + 1);
}
private final int jjMoveStringLiteralDfa0_1()
private int jjMoveStringLiteralDfa0_1()
{
switch(curChar)
{
@ -618,14 +551,13 @@ private final int jjMoveStringLiteralDfa0_1()
return jjMoveNfa_1(0, 0);
}
}
private final int jjMoveNfa_1(int startState, int curPos)
private int jjMoveNfa_1(int startState, int curPos)
{
int[] nextStates;
int startsAt = 0;
jjnewStateCnt = 3;
int i = 1;
jjstateSet[0] = startState;
int j, kind = 0x7fffffff;
int kind = 0x7fffffff;
for (;;)
{
if (++jjround == 0x7fffffff)
@ -633,7 +565,7 @@ private final int jjMoveNfa_1(int startState, int curPos)
if (curChar < 64)
{
long l = 1L << curChar;
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -652,7 +584,7 @@ private final int jjMoveNfa_1(int startState, int curPos)
else if (curChar < 128)
{
long l = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -681,7 +613,7 @@ private final int jjMoveNfa_1(int startState, int curPos)
{
int i2 = (curChar & 0xff) >> 6;
long l2 = 1L << (curChar & 077);
MatchLoop: do
do
{
switch(jjstateSet[--i])
{
@ -712,17 +644,23 @@ private final int jjMoveNfa_1(int startState, int curPos)
}
static final int[] jjnextStates = {
};
/** Token literal values. */
public static final String[] jjstrLiteralImages = {
"", "\15", "\12", "\54", "\72", "\73", "\74", "\76", "\100", "\56", null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, };
/** Lexer state names. */
public static final String[] lexStateNames = {
"DEFAULT",
"INDOMAINLITERAL",
"INCOMMENT",
"NESTED_COMMENT",
"INQUOTEDSTRING",
"DEFAULT",
"INDOMAINLITERAL",
"INCOMMENT",
"NESTED_COMMENT",
"INQUOTEDSTRING",
};
/** Lex State array. */
public static final int[] jjnewLexState = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 0, 2, 0, -1, 3, -1, -1,
-1, -1, -1, 4, -1, -1, 0, -1, -1,
@ -742,19 +680,25 @@ static final long[] jjtoMore = {
protected SimpleCharStream input_stream;
private final int[] jjrounds = new int[3];
private final int[] jjstateSet = new int[6];
StringBuffer image;
int jjimageLen;
int lengthOfMatch;
private final StringBuilder jjimage = new StringBuilder();
private StringBuilder image = jjimage;
private int jjimageLen;
private int lengthOfMatch;
protected char curChar;
/** Constructor. */
public AddressListParserTokenManager(SimpleCharStream stream){
if (SimpleCharStream.staticFlag)
throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
input_stream = stream;
}
/** Constructor. */
public AddressListParserTokenManager(SimpleCharStream stream, int lexState){
this(stream);
SwitchTo(lexState);
}
/** Reinitialise parser. */
public void ReInit(SimpleCharStream stream)
{
jjmatchedPos = jjnewStateCnt = 0;
@ -762,18 +706,22 @@ public void ReInit(SimpleCharStream stream)
input_stream = stream;
ReInitRounds();
}
private final void ReInitRounds()
private void ReInitRounds()
{
int i;
jjround = 0x80000001;
for (i = 3; i-- > 0;)
jjrounds[i] = 0x80000000;
}
/** Reinitialise parser. */
public void ReInit(SimpleCharStream stream, int lexState)
{
ReInit(stream);
SwitchTo(lexState);
}
/** Switch to specified lex state. */
public void SwitchTo(int lexState)
{
if (lexState >= 5 || lexState < 0)
@ -784,14 +732,25 @@ public void SwitchTo(int lexState)
protected Token jjFillToken()
{
Token t = Token.newToken(jjmatchedKind);
t.kind = jjmatchedKind;
final Token t;
final String curTokenImage;
final int beginLine;
final int endLine;
final int beginColumn;
final int endColumn;
String im = jjstrLiteralImages[jjmatchedKind];
t.image = (im == null) ? input_stream.GetImage() : im;
t.beginLine = input_stream.getBeginLine();
t.beginColumn = input_stream.getBeginColumn();
t.endLine = input_stream.getEndLine();
t.endColumn = input_stream.getEndColumn();
curTokenImage = (im == null) ? input_stream.GetImage() : im;
beginLine = input_stream.getBeginLine();
beginColumn = input_stream.getBeginColumn();
endLine = input_stream.getEndLine();
endColumn = input_stream.getEndColumn();
t = Token.newToken(jjmatchedKind, curTokenImage);
t.beginLine = beginLine;
t.endLine = endLine;
t.beginColumn = beginColumn;
t.endColumn = endColumn;
return t;
}
@ -802,28 +761,29 @@ int jjround;
int jjmatchedPos;
int jjmatchedKind;
/** Get the next Token. */
public Token getNextToken()
{
int kind;
Token specialToken = null;
Token matchedToken;
int curPos = 0;
EOFLoop :
for (;;)
{
try
{
{
try
{
curChar = input_stream.BeginToken();
}
}
catch(java.io.IOException e)
{
{
jjmatchedKind = 0;
matchedToken = jjFillToken();
matchedToken.specialToken = specialToken;
return matchedToken;
}
image = null;
image = jjimage;
image.setLength(0);
jjimageLen = 0;
for (;;)
@ -927,62 +887,46 @@ void MoreLexicalActions()
switch(jjmatchedKind)
{
case 16 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen));
jjimageLen = 0;
image.deleteCharAt(image.length() - 2);
break;
case 21 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen));
jjimageLen = 0;
image.deleteCharAt(image.length() - 2);
break;
case 22 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen));
jjimageLen = 0;
commentNest = 1;
break;
case 24 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen));
jjimageLen = 0;
image.deleteCharAt(image.length() - 2);
break;
case 25 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen));
jjimageLen = 0;
++commentNest;
break;
case 26 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen));
jjimageLen = 0;
--commentNest; if (commentNest == 0) SwitchTo(INCOMMENT);
break;
case 28 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen));
jjimageLen = 0;
image.deleteCharAt(image.length() - 1);
break;
case 29 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen));
jjimageLen = 0;
image.deleteCharAt(image.length() - 2);
break;
default :
default :
break;
}
}
@ -991,19 +935,35 @@ void TokenLexicalActions(Token matchedToken)
switch(jjmatchedKind)
{
case 18 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
matchedToken.image = image.toString();
break;
case 31 :
if (image == null)
image = new StringBuffer();
image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
matchedToken.image = image.substring(0, image.length() - 1);
break;
default :
default :
break;
}
}
private void jjCheckNAdd(int state)
{
if (jjrounds[state] != jjround)
{
jjstateSet[jjnewStateCnt++] = state;
jjrounds[state] = jjround;
}
}
private void jjAddStates(int start, int end)
{
do {
jjstateSet[jjnewStateCnt++] = jjnextStates[start];
} while (start++ != end);
}
private void jjCheckNAddTwoStates(int state1, int state2)
{
jjCheckNAdd(state1);
jjCheckNAdd(state2);
}
}

View File

@ -1,5 +1,4 @@
/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java */
/* Generated By:JavaCC: Do not edit this line. AddressListParserTreeConstants.java Version 5.0 */
package org.apache.james.mime4j.field.address.parser;
public interface AddressListParserTreeConstants
@ -33,3 +32,4 @@ public interface AddressListParserTreeConstants
"domain",
};
}
/* JavaCC - OriginalChecksum=e7d2b24000a70a573955cf10036f0056 (do not edit this line) */

View File

@ -1,5 +1,4 @@
/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java */
/* Generated By:JavaCC: Do not edit this line. AddressListParserVisitor.java Version 5.0 */
package org.apache.james.mime4j.field.address.parser;
public interface AddressListParserVisitor
@ -17,3 +16,4 @@ public interface AddressListParserVisitor
public Object visit(ASTlocal_part node, Object data);
public Object visit(ASTdomain node, Object data);
}
/* JavaCC - OriginalChecksum=f57edd9a1eb17afa5907a87b3c51e284 (do not edit this line) */

View File

@ -19,8 +19,6 @@
package org.apache.james.mime4j.field.address.parser;
import org.apache.james.mime4j.field.address.parser.Node;
import org.apache.james.mime4j.field.address.parser.Token;
public abstract class BaseNode implements Node {

View File

@ -0,0 +1,229 @@
/****************************************************************
* 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.field.address.parser;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.james.mime4j.codec.DecodeMonitor;
import org.apache.james.mime4j.codec.DecoderUtil;
import org.apache.james.mime4j.dom.address.Address;
import org.apache.james.mime4j.dom.address.AddressList;
import org.apache.james.mime4j.dom.address.DomainList;
import org.apache.james.mime4j.dom.address.Group;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.address.MailboxList;
/**
* Transforms the JJTree-generated abstract syntax tree into a graph of
* org.apache.james.mime4j.dom.address objects.
*/
class Builder {
private static Builder singleton = new Builder();
public static Builder getInstance() {
return singleton;
}
public AddressList buildAddressList(ASTaddress_list node, DecodeMonitor monitor) throws ParseException {
List<Address> list = new ArrayList<Address>();
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
ASTaddress childNode = (ASTaddress) node.jjtGetChild(i);
Address address = buildAddress(childNode, monitor);
list.add(address);
}
return new AddressList(list, true);
}
public Address buildAddress(ASTaddress node, DecodeMonitor monitor) throws ParseException {
ChildNodeIterator it = new ChildNodeIterator(node);
Node n = it.next();
if (n instanceof ASTaddr_spec) {
return buildAddrSpec((ASTaddr_spec) n);
} else if (n instanceof ASTangle_addr) {
return buildAngleAddr((ASTangle_addr) n);
} else if (n instanceof ASTphrase) {
String name = buildString((ASTphrase) n, false);
Node n2 = it.next();
if (n2 instanceof ASTgroup_body) {
return new Group(name, buildGroupBody((ASTgroup_body) n2, monitor));
} else if (n2 instanceof ASTangle_addr) {
try {
name = DecoderUtil.decodeEncodedWords(name, monitor);
} catch (IllegalArgumentException e) {
throw new ParseException(e.getMessage());
}
Mailbox mb = buildAngleAddr((ASTangle_addr) n2);
return new Mailbox(name, mb.getRoute(), mb.getLocalPart(),
mb.getDomain());
} else {
throw new ParseException();
}
} else {
throw new ParseException();
}
}
private MailboxList buildGroupBody(ASTgroup_body node, DecodeMonitor monitor) throws ParseException {
List<Mailbox> results = new ArrayList<Mailbox>();
ChildNodeIterator it = new ChildNodeIterator(node);
while (it.hasNext()) {
Node n = it.next();
if (n instanceof ASTmailbox)
results.add(buildMailbox((ASTmailbox) n, monitor));
else
throw new ParseException();
}
return new MailboxList(results, true);
}
public Mailbox buildMailbox(ASTmailbox node, DecodeMonitor monitor) throws ParseException {
ChildNodeIterator it = new ChildNodeIterator(node);
Node n = it.next();
if (n instanceof ASTaddr_spec) {
return buildAddrSpec((ASTaddr_spec) n);
} else if (n instanceof ASTangle_addr) {
return buildAngleAddr((ASTangle_addr) n);
} else if (n instanceof ASTname_addr) {
return buildNameAddr((ASTname_addr) n, monitor);
} else {
throw new ParseException();
}
}
private Mailbox buildNameAddr(ASTname_addr node, DecodeMonitor monitor) throws ParseException {
ChildNodeIterator it = new ChildNodeIterator(node);
Node n = it.next();
String name;
if (n instanceof ASTphrase) {
name = buildString((ASTphrase) n, false);
} else {
throw new ParseException();
}
n = it.next();
if (n instanceof ASTangle_addr) {
try {
name = DecoderUtil.decodeEncodedWords(name, monitor);
} catch (IllegalArgumentException e) {
throw new ParseException(e.getMessage());
}
Mailbox mb = buildAngleAddr((ASTangle_addr) n);
return new Mailbox(name, mb.getRoute(), mb.getLocalPart(),
mb.getDomain());
} else {
throw new ParseException();
}
}
private Mailbox buildAngleAddr(ASTangle_addr node) throws ParseException {
ChildNodeIterator it = new ChildNodeIterator(node);
DomainList route = null;
Node n = it.next();
if (n instanceof ASTroute) {
route = buildRoute((ASTroute) n);
n = it.next();
} else if (n instanceof ASTaddr_spec) {
// do nothing
}
else
throw new ParseException();
if (n instanceof ASTaddr_spec)
return buildAddrSpec(route, (ASTaddr_spec) n);
else
throw new ParseException();
}
private DomainList buildRoute(ASTroute node) throws ParseException {
List<String> results = new ArrayList<String>(node.jjtGetNumChildren());
ChildNodeIterator it = new ChildNodeIterator(node);
while (it.hasNext()) {
Node n = it.next();
if (n instanceof ASTdomain)
results.add(buildString((ASTdomain) n, true));
else
throw new ParseException();
}
return new DomainList(results, true);
}
private Mailbox buildAddrSpec(ASTaddr_spec node) {
return buildAddrSpec(null, node);
}
private Mailbox buildAddrSpec(DomainList route, ASTaddr_spec node) {
ChildNodeIterator it = new ChildNodeIterator(node);
String localPart = buildString((ASTlocal_part) it.next(), true);
String domain = buildString((ASTdomain) it.next(), true);
return new Mailbox(route, localPart, domain);
}
private String buildString(SimpleNode node, boolean stripSpaces) {
Token head = node.firstToken;
Token tail = node.lastToken;
StringBuilder out = new StringBuilder();
while (head != tail) {
out.append(head.image);
head = head.next;
if (!stripSpaces)
addSpecials(out, head.specialToken);
}
out.append(tail.image);
return out.toString();
}
private void addSpecials(StringBuilder out, Token specialToken) {
if (specialToken != null) {
addSpecials(out, specialToken.specialToken);
out.append(specialToken.image);
}
}
private static class ChildNodeIterator implements Iterator<Node> {
private SimpleNode simpleNode;
private int index;
private int len;
public ChildNodeIterator(SimpleNode simpleNode) {
this.simpleNode = simpleNode;
this.len = simpleNode.jjtGetNumChildren();
this.index = 0;
}
public void remove() {
throw new UnsupportedOperationException();
}
public boolean hasNext() {
return index < len;
}
public Node next() {
return simpleNode.jjtGetChild(index++);
}
}
}

Some files were not shown because too many files have changed in this diff Show More