/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.content.mbox.parser;
import java.io.*;
import java.util.*;
import javax.activation.DataHandler;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.log4j.*;
import org.mulgara.util.ObjectUtil;
/*
* MboxMessage.java
* Copyright (C) 1999 dog <dog@dog.net.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* You also have permission to link it with the Sun Microsystems, Inc.
* JavaMail(tm) extension and run that combination.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* You may retrieve the latest version of this library from
* http://www.dog.net.uk/knife/
*/
/**
* The message class implementing the Mbox mail protocol.
* <P/>
*
* This code is derived from the 'knife' mail and user news client
* at http://www.dog.net.uk/knife.
* <P/>
*
* @created 2001-8-21
*
* @author dog@dog.net.uk
* @author Andrew Newman
* @author Ben Warren
* @author Mark Ludlow
*
* @version $Revision: 1.8 $
*
* @modified $Date: 2005/01/05 04:57:41 $
*
* @maintenanceAuthor $Author: newmana $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright © 2001
* <A href="http://www.PIsoftware.com/">Plugged In Software Pty Ltd</A>
*
* @licence <A href="{@docRoot}/../../LICENCE_LGPL.txt">Licence description</A>
*/
public class MboxMessage extends MimeMessage {
/** The category to log to. */
private static final Logger logger = Logger.getLogger(MboxMessage.class);
/**
* The offset of the start of this message from the beginning of the file.
*/
protected long startOffset = -1;
/**
* The offset of the start of this message's content from the beginning of the file.
*/
protected long contentOffset = -1;
protected MboxMessage(MboxFolderImpl folder, BufferedInputStream in,
int msgnum) throws MessagingException {
super(folder, msgnum);
headers = new InternetHeaders(in);
try {
int fetchsize = MboxStoreImpl.fetchsize;
byte bytes[];
ByteArrayOutputStream out = new ByteArrayOutputStream();
bytes = new byte[fetchsize];
int len;
while ((len = in.read(bytes, 0, fetchsize)) != -1) {
out.write(bytes, 0, len);
}
bytes = out.toByteArray();
content = bytes;
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
readStatusHeader();
}
/**
* Creates a Mbox message.
* This is called by the MboxStore.
*/
protected MboxMessage(MboxFolderImpl folder, InputStream in, int msgnum) throws
MessagingException {
super(folder, msgnum);
if (!(in instanceof ByteArrayInputStream) &&
!(in instanceof BufferedInputStream)) {
in = new BufferedInputStream(in);
}
headers = new InternetHeaders(in);
try {
int fetchsize = MboxStoreImpl.fetchsize;
byte bytes[];
if (in instanceof ByteArrayInputStream) {
fetchsize = in.available();
bytes = new byte[fetchsize];
in.read(bytes, 0, fetchsize);
} else {
ByteArrayOutputStream out = new ByteArrayOutputStream();
bytes = new byte[fetchsize];
int len;
while ((len = in.read(bytes, 0, fetchsize)) != -1)
out.write(bytes, 0, len);
bytes = out.toByteArray();
}
content = bytes;
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
readStatusHeader();
}
/**
* Creates a Mbox message.
* This is called by the MboxStore.
*/
protected MboxMessage(MboxFolderImpl folder, RandomAccessFile file,
int msgnum) throws MessagingException {
super(folder, msgnum);
// just create the headers for now
headers = new InternetHeaders();
try {
startOffset = file.getFilePointer();
String line;
while ((line = file.readLine()) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Checking header for MBox Message: " + line);
}
int len = line.length();
if (len == 0 || (len == 1 && line.charAt(0) == '\r'))
break;
headers.addHeaderLine(line);
}
/*
// Retrieve the Content-Type header from the message
String contentType = headers.getHeader("Content-Type", null);
if (logger.isDebugEnabled()) {
logger.debug("Content-Type is: " + contentType);
}
if (contentType.trim().endsWith(";")) {
// If the content type ends with a semi-colon then remove it
headers.setHeader("Content-Type",
contentType.trim().substring(0,
contentType.length() - 1));
if (logger.isDebugEnabled()) {
logger.debug("Changed Content-Type to: " +
headers.getHeader("Content-Type", null));
}
}*/
contentOffset = file.getFilePointer();
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
readStatusHeader();
}
/**
* Creates a Mbox message.
* This is called by the MboxFolder when appending.
* It creates a copy of the specified message for the new folder.
*/
@SuppressWarnings("unchecked")
protected MboxMessage(MboxFolderImpl folder, MimeMessage message, int msgnum) throws
MessagingException {
super(folder, msgnum);
headers = new InternetHeaders();
for (Enumeration<String> enumeration = (Enumeration<String>)message.getAllHeaderLines(); enumeration.hasMoreElements();) {
headers.addHeaderLine((String) enumeration.nextElement());
}
try {
InputStream in = message.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
for (int len = in.read(bytes); len > -1; len = in.read(bytes))
out.write(bytes, 0, len);
content = out.toByteArray();
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
readStatusHeader();
}
/**
* Returns the content of this message as a Java object.
*
* @throws MessagingException if retrieve content fails.
* @throws IOException if the file read fails.
*/
public Object getContent() throws MessagingException, IOException {
if (content == null) {
retrieveContent();
}
return super.getContent();
}
/**
* Returns the content of this message as a byte stream.
*
* @throws MessagingException if the retrieve content fails.
*/
public InputStream getContentStream() throws MessagingException {
if (content == null) {
try {
retrieveContent();
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
}
return super.getContentStream();
}
/**
* Returns the content of this message as a decoded stream.
*/
public InputStream getInputStream() throws MessagingException, IOException {
if (content == null)
retrieveContent();
return super.getInputStream();
}
/**
* Returns the number of lines in the content of this message.
*/
public int getLineCount() throws MessagingException {
if (content == null) {
try {
retrieveContent();
} catch (IOException e) {
throw new MessagingException("I/O error", e);
}
}
return super.getLineCount();
}
protected void retrieveContent() throws IOException {
if (contentOffset < 0 || content != null) {
return;
}
RandomAccessFile file =
new RandomAccessFile(((MboxFolderImpl) folder).getFile(), "r");
file.seek(contentOffset);
String line;
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (line = file.readLine(); line != null; line = file.readLine()) {
int fromIndex = line.toLowerCase().indexOf("from ");
if (fromIndex == 0) { // line begins with From_, end of message
content = out.toByteArray();
break;
} else {
// strip quoting if necessary
if (fromIndex > 0) {
String prefix = line.substring(0, fromIndex);
boolean quoted = true;
for (int i = 0; i < prefix.length(); i++)
if (prefix.charAt(i) != '>') {
quoted = false;
break;
}
if (quoted) {
String suffix = line.substring(fromIndex);
line = prefix.substring(1) + suffix;
}
}
if (line.endsWith("\r"))
line = line.substring(0, line.length() - 1);
out.write(line.getBytes());
out.write('\n');
}
}
// KEITH - close the reader!
file.close();
if (line == null) // end of file
content = out.toByteArray();
}
/**
* Returns the from address.
*/
public Address[] getFrom() throws MessagingException {
Address[] a = getAddressHeader("From");
if (a == null)
a = getAddressHeader("Sender");
return a;
}
/**
* Returns the recipients' addresses.
*/
public Address[] getRecipients(RecipientType type) throws MessagingException {
if (type == RecipientType.NEWSGROUPS) {
String key = getHeader("Newsgroups", ",");
if (key == null)
return null;
return NewsAddress.parse(key);
} else {
return getAddressHeader(getHeaderKey(type));
}
}
/**
* Returns the reply-to address.
*/
public Address[] getReplyTo() throws MessagingException {
Address[] a = getAddressHeader("Reply-To");
if (a == null)
a = getFrom();
return a;
}
/**
* Returns an array of addresses for the specified header key.
*/
protected Address[] getAddressHeader(String key) throws MessagingException {
String header = getHeader(key, ",");
if (header == null)
return null;
try {
return InternetAddress.parse(header);
} catch (AddressException e) {
String message = e.getMessage();
if (message != null && message.indexOf("@domain") > -1) {
try {
return parseAddress(header, "localhost");
} catch (AddressException e2) {
throw new MessagingException("Invalid address: " + header, e);
}
}
throw e;
}
}
/**
* Makes a pass at parsing internet addresses.
*/
protected Address[] parseAddress(String in, String defhost) throws
AddressException {
//Vector v = new Vector();
ArrayList<Address> v = new ArrayList<Address>();
for (StringTokenizer st = new StringTokenizer(in, ","); st.hasMoreTokens(); ) {
String s = st.nextToken().trim();
try {
//v.addElement(new InternetAddress(s));
v.add(new InternetAddress(s));
} catch (AddressException e) {
int index = s.indexOf('>');
if (index > -1) { // name <address>
StringBuffer buffer = new StringBuffer();
buffer.append(s.substring(0, index));
buffer.append('@');
buffer.append(defhost);
buffer.append(s.substring(index));
//v.addElement(new InternetAddress(buffer.toString()));
v.add(new InternetAddress(buffer.toString()));
} else {
index = s.indexOf(" (");
if (index > -1) {
// address (name)
StringBuffer buffer = new StringBuffer();
buffer.append(s.substring(0, index));
buffer.append('@');
buffer.append(defhost);
buffer.append(s.substring(index));
//v.addElement(new InternetAddress(buffer.toString()));
v.add(new InternetAddress(buffer.toString()));
} else { // address
//v.addElement(new InternetAddress(s+"@"+defhost));
v.add(new InternetAddress(s + "@" + defhost));
}
}
}
}
//Address[] a = new Address[v.size()];
//v.copyInto(a);
return (Address[]) v.toArray();
}
/**
* Returns the header key for the specified RecipientType.
*/
protected String getHeaderKey(RecipientType type) throws MessagingException {
if (type == RecipientType.TO) {
return "To";
}
if (type == RecipientType.CC) {
return "Cc";
}
if (type == RecipientType.BCC) {
return "Bcc";
}
if (type == RecipientType.NEWSGROUPS) {
return "Newsgroups";
}
throw new MessagingException("Invalid recipient type: " + type);
}
// -- Need to override these since we are read-only --
/**
* Mbox messages are read-only.
*/
public void setFrom(Address address) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void addFrom(Address aaddress[]) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setRecipients(javax.mail.Message.RecipientType recipienttype,
Address aaddress[]) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void addRecipients(javax.mail.Message.RecipientType recipienttype,
Address aaddress[]) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setReplyTo(Address aaddress[]) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setSubject(String s, String s1) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setSentDate(Date date) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setDisposition(String s) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setContentID(String s) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setContentMD5(String s) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setDescription(String s, String s1) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Mbox messages are read-only.
*/
public void setDataHandler(DataHandler datahandler) throws MessagingException {
throw new IllegalWriteException("MboxMessage is read-only");
}
/**
* Ok, Mbox messages aren't entirely read-only.
*/
public synchronized void setFlags(Flags flag, boolean set) throws
MessagingException {
if (set) {
flags.add(flag);
} else {
flags.remove(flag);
}
updateStatusHeader();
}
/**
* Updates the status header from the current flags.
*/
private void updateStatusHeader() throws MessagingException {
if (!flags.contains(Flags.Flag.SEEN))
this.setHeader("Status", "O");
else
this.setHeader("Status", "RO");
}
/**
* Updates the status header from the current flags.
*/
private void readStatusHeader() throws MessagingException {
String[] currentStatus = this.getHeader("Status");
if (currentStatus != null && currentStatus.length > 0) {
if (currentStatus[0].indexOf('R') >= 0)
flags.add(Flags.Flag.SEEN);
if (currentStatus[0].indexOf('O') < 0)
flags.add(Flags.Flag.RECENT);
}
updateStatusHeader();
}
// -- Utility methods --
/**
* Checks to see whether the passed object is the same type and that the
* values are all equivalent. This has been improved on the previous
* version which only checked that the folder and the message number were
* the same. Checks flags, folder, from, linecount, messageID, messageNumber,
* receivedDate, Receipients (to, cc, bcc), Reply To, Sent Date, Size and
* Subject.
*
* @param other the object to check for equality with the object.
*/
public boolean equals(Object other) {
boolean isEqual = false;
if (other instanceof MboxMessage) {
MboxMessage message = (MboxMessage) other;
try {
isEqual = (
flagsEqual(message.getFlags(), getFlags()) &&
foldersEqual(message.getFolder(), getFolder()) &&
addressesEqual(message.getFrom(), getFrom()) &&
message.getLineCount() == getLineCount() &&
messageIdsEqual(message.getMessageID(), getMessageID()) &&
message.getMessageNumber() == getMessageNumber() &&
datesEqual(message.getReceivedDate(), getReceivedDate()) &&
addressesEqual(message.getRecipients(Message.RecipientType.TO),
getRecipients(Message.RecipientType.TO)) &&
addressesEqual(message.getRecipients(Message.RecipientType.CC),
getRecipients(Message.RecipientType.CC)) &&
addressesEqual(message.getRecipients(Message.RecipientType.BCC),
getRecipients(Message.RecipientType.BCC)) &&
addressesEqual(message.getReplyTo(), getReplyTo()) &&
datesEqual(message.getSentDate(), getSentDate()) &&
message.getSize() == getSize() &&
subjectsEqual(message.getSubject(), getSubject()));
} catch (javax.mail.MessagingException me) {
logger.warn("Exception comparing messages", me);
}
}
return isEqual;
}
/**
* Test whether two flags are equal. This is true if they are both are null
* or .equals.
*
* @param flag1 the first flag to test against.
* @param flag2 the second flag to test against.
* @return the flags are equal.
*/
public static boolean flagsEqual(Flags flag1, Flags flag2) {
return ObjectUtil.eq(flag1, flag2);
}
/**
* Test whether two folders are equal. This is true if they are both are null
* or .equals.
*
* @param folder1 the first folder to test against.
* @param folder2 the second folder to test against.
* @return the folders are equal.
*/
public static boolean foldersEqual(Folder folder1, Folder folder2) {
return ObjectUtil.eq(folder1, folder2);
}
/**
* Test whether two addresses are equal. This is true if Array.equals is
* true.
*
* @param address1 the first address to test against.
* @param address2 the second address to test against.
* @return the addresses are equal.
*/
public static boolean addressesEqual(Address[] address1, Address[] address2) {
return (((address1 == null) && (address2 == null)) ||
(Arrays.equals(address1, address2))
);
}
/**
* Test whether two message ids are equal. This is true if they are both are
* null or .equals.
*
* @param messageId1 the first message to test against.
* @param messageId2 the second message to test against.
* @return the messages are equal.
*/
public static boolean messageIdsEqual(String messageId1, String messageId2) {
return ObjectUtil.eq(messageId1, messageId2);
}
/**
* Test whether two dates are equal. This is true if they are both are null
* or .equals.
*
* @param date1 the first date to test against.
* @param date2 the second date to test against.
* @return the dates are equal.
*/
public static boolean datesEqual(Date date1, Date date2) {
return ObjectUtil.eq(date1, date2);
}
/**
* Test whether two subjects are equal. This is true if they are both are
* null or .equals.
*
* @param subject1 the first flag to test against.
* @param subject2 the second flag to test against.
* @return the subjects are equal.
*/
public static boolean subjectsEqual(String subject1, String subject2) {
return ObjectUtil.eq(subject1, subject2);
}
}