bug 307150 - Incomplete implementation of javax.mail.Message in grendel.storage.MessageReadOnly. r=rj.keller@beonex.com. Patch by Kieran Maclean <kieran [at] eternal.undonet.com>

This commit is contained in:
rj.keller%beonex.com 2005-09-24 15:53:10 +00:00
parent 9bea533899
commit 142ac2d257
8 changed files with 401 additions and 355 deletions

View File

@ -41,8 +41,9 @@
<mkdir dir="dist"/>
<antcall target="unzipResources"/>
<echo message="-- Compiling WikiRadio"/>
<echo message="-- Compiling Grendel"/>
<javac srcdir="." destdir="dist"
classpath="**/*.jar"
optimize="false"
debug="true"
includes="**/*.java"/>

View File

@ -20,6 +20,7 @@
* Contributor(s):
*
* Created: Terry Weissman <terry@netscape.com>, 22 Aug 1997.
* Kieran Maclean
*/
package grendel.storage;
@ -94,8 +95,9 @@ abstract class FolderBase extends Folder implements FolderExtra {
void noticeInitialMessage(Message m) {
fMessages.addElement(m);
// #### How the hell are we supposed to do this?
BerkeleyMessage bm=(BerkeleyMessage)m;
bm.setMessageNumber(fMessages.size() - 1);
// XXX This is not a good idea, this a general class
//BerkeleyMessage bm=(BerkeleyMessage)m;
//bm.setMessageNumber(fMessages.size() - 1);
}
ByteStringTable getStringTable() {

View File

@ -17,7 +17,8 @@
* Copyright (C) 1997 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Contributor(s):
* Kieran Maclean
*/
@ -36,6 +37,7 @@ import java.util.NoSuchElementException;
import java.util.Vector;
import javax.activation.DataHandler;
import javax.activation.MimeType;
import javax.mail.Address;
import javax.mail.Flags;
import javax.mail.Folder;
@ -50,23 +52,23 @@ import javax.mail.internet.InternetAddress;
import javax.mail.internet.InternetHeaders;
abstract class MessageBase extends MessageReadOnly implements MessageExtra {
public abstract class MessageBase extends MessageReadOnly implements MessageExtra {
/* **********************************************************
Class variables
**********************************************************
**********************************************************
*/
/* Don't confuse these flags with the values used in X-Mozilla-Status
headers: they occupy a different space, and range. There is a
difference between the in-memory format, and the storage format.
This is partially because there are potentially in-memory flags
that don't get saved to disk.
Parsing of the X-Mozilla-Status header (and conversion to this form)
happens in the BerkeleyMessage class.
There can be up to 64 flag-bits (the in-memory flags.)
There can only be up to 16 X-Mozilla-Status flags (the persistent flags.)
*/
@ -81,12 +83,12 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
public static final long FLAG_SMTP_AUTH = 0x00000100; // Gag
public static final long FLAG_PARTIAL = 0x00000200; // POP3
public static final long FLAG_QUEUED = 0x00000400; // Offline
// The mapping between our internal flag bits and javamail's flag strings.
// The editable entry defines whether this flag can be changed from the
// outside.
static class FlagMap {
long flag;
Flags.Flag builtin;
@ -119,14 +121,14 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
FLAGDEFS[i++] = new FlagMap(FLAG_QUEUED, "Queued", false);
Assert.Assertion(i == FLAGDEFS.length);
};
// This bit, when set, means that some change has been made to the flags
// which should be written out to the X-Mozilla-Status header in the
// folder's disk file. This is done so that we can lazily update the
// file, rather than writing it every time the flags change.
public static final long FLAG_DIRTY = 0x00000800;
/* Some string-constants in bytebuf form that we use for interrogating
headers during parsing.
*/
@ -139,19 +141,19 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
protected static final ByteBuf MESSAGE_ID = new ByteBuf("message-id");
protected static final ByteBuf REFERENCES = new ByteBuf("references");
protected static final ByteBuf IN_REPLY_TO = new ByteBuf("in-reply-to");
/* For simplifiedDate() */
private static Date scratch_date = new Date();
private static DateFormat date_format = null;
/* ***********************************************************
Instance variables -- add them sparingly, memory is scarce.
***********************************************************
***********************************************************
*/
long flags; // see `FLAG_READ', etc, above.
long sentDate; // milliseconds since the Epoch.
// These slots are ints but really represent ByteStrings: they are indexes
// into a ByteStringTable. It's quite likely that we could live with these
// being of type `short' instead of `int'. Should memory usage be a problem,
@ -160,60 +162,60 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
int author_name; // name (not address) of the From or Sender.
int recipient_name; // name of first To, or CC, or newsgroup.
int subject; // subject minus "Re:" (see `FLAG_HAS_RE').
// These slots are as above, but represent MessageID objects instead of
// ByteString objects. These also could stand to be of type `short'.
//
int message_id; // will never be -1 (meaning null).
int references[]; // may be null; else length > 0.
/* **********************************************************
Methods
**********************************************************
**********************************************************
*/
MessageBase(FolderBase f) {
super();
this.folder = f;
}
// New constructor copied from above but sets msgnum correctly.
MessageBase(FolderBase f, int num) {
super(f, num);
this.folder = f;
}
MessageBase(FolderBase f, InternetHeaders h) {
this(f);
initialize(f, h);
}
// New constructor copied from above but sets msgnum correctly.
MessageBase(FolderBase f, int num, InternetHeaders h) {
this(f, num);
initialize(f, h);
}
MessageBase(FolderBase f,
long date,
long flags,
ByteBuf author,
ByteBuf recipient,
ByteBuf subj,
ByteBuf id,
ByteBuf refs[]) {
long date,
long flags,
ByteBuf author,
ByteBuf recipient,
ByteBuf subj,
ByteBuf id,
ByteBuf refs[]) {
this(f);
ByteStringTable string_table = f.getStringTable();
MessageIDTable id_table = f.getMessageIDTable();
if (id == null || id.length() == 0) {
// #### In previous versions, we did this by getting the MD5 hash
// #### of the whole header block. We should do that here too...
if (id == null) id = new ByteBuf();
id.append(grendel.util.MessageIDGenerator.generate("missing-id"));
}
this.folder = f;
this.flags = flags;
this.sentDate = date;
@ -221,7 +223,7 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
this.recipient_name = string_table.intern(recipient);
this.subject = string_table.intern(subj);
this.message_id = id_table.intern(id);
if (refs == null || refs.length == 0)
this.references = null;
else {
@ -231,28 +233,28 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
references[i] = id_table.intern(refs[i]);
}
}
// New constructor copied from above but sets msgnum correctly.
MessageBase(FolderBase f,
int num,
long date,
long flags,
ByteBuf author,
ByteBuf recipient,
ByteBuf subj,
ByteBuf id,
ByteBuf refs[]) {
int num,
long date,
long flags,
ByteBuf author,
ByteBuf recipient,
ByteBuf subj,
ByteBuf id,
ByteBuf refs[]) {
this(f, num);
ByteStringTable string_table = f.getStringTable();
MessageIDTable id_table = f.getMessageIDTable();
if (id == null || id.length() == 0) {
// #### In previous versions, we did this by getting the MD5 hash
// #### of the whole header block. We should do that here too...
if (id == null) id = new ByteBuf();
id.append(grendel.util.MessageIDGenerator.generate("missing-id"));
}
this.folder = f;
this.flags = flags;
this.sentDate = date;
@ -260,7 +262,7 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
this.recipient_name = string_table.intern(recipient);
this.subject = string_table.intern(subj);
this.message_id = id_table.intern(id);
if (refs == null || refs.length == 0)
this.references = null;
else {
@ -270,36 +272,36 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
references[i] = id_table.intern(refs[i]);
}
}
MessageBase(FolderBase f,
long date,
long flags,
ByteBuf author,
ByteBuf recipient,
ByteBuf subj,
MessageID id,
MessageID refs[]) {
long date,
long flags,
ByteBuf author,
ByteBuf recipient,
ByteBuf subj,
MessageID id,
MessageID refs[]) {
this(f);
ByteStringTable string_table = f.getStringTable();
MessageIDTable id_table = f.getMessageIDTable();
if (id != null) {
this.message_id = id_table.intern(id);
} else {
// #### In previous versions, we did this by getting the MD5 hash
// #### of the whole header block. We should do that here too...
ByteBuf b =
new ByteBuf(grendel.util.MessageIDGenerator.generate("missing-id"));
new ByteBuf(grendel.util.MessageIDGenerator.generate("missing-id"));
this.message_id = id_table.intern(b);
}
this.folder = f;
this.flags = flags;
this.sentDate = date;
this.author_name = string_table.intern(author);
this.recipient_name = string_table.intern(recipient);
this.subject = string_table.intern(subj);
if (refs == null || refs.length == 0)
this.references = null;
else {
@ -309,38 +311,38 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
references[i] = id_table.intern(refs[i]);
}
}
// New constructor copied from above but sets msgnum correctly.
MessageBase(FolderBase f,
int num,
long date,
long flags,
ByteBuf author,
ByteBuf recipient,
ByteBuf subj,
MessageID id,
MessageID refs[]) {
int num,
long date,
long flags,
ByteBuf author,
ByteBuf recipient,
ByteBuf subj,
MessageID id,
MessageID refs[]) {
this(f, num);
ByteStringTable string_table = f.getStringTable();
MessageIDTable id_table = f.getMessageIDTable();
if (id != null) {
this.message_id = id_table.intern(id);
} else {
// #### In previous versions, we did this by getting the MD5 hash
// #### of the whole header block. We should do that here too...
ByteBuf b =
new ByteBuf(grendel.util.MessageIDGenerator.generate("missing-id"));
new ByteBuf(grendel.util.MessageIDGenerator.generate("missing-id"));
this.message_id = id_table.intern(b);
}
this.folder = f;
this.flags = flags;
this.sentDate = date;
this.author_name = string_table.intern(author);
this.recipient_name = string_table.intern(recipient);
this.subject = string_table.intern(subj);
if (refs == null || refs.length == 0)
this.references = null;
else {
@ -350,40 +352,40 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
references[i] = id_table.intern(refs[i]);
}
}
protected void initialize(Folder f, InternetHeaders h) {
folder = f;
FolderBase fb = (FolderBase) f;
ByteStringTable string_table = fb.getStringTable();
MessageIDTable id_table = fb.getMessageIDTable();
String hh[];
hh = h.getHeader("From");
author_name = (hh == null || hh.length == 0 ? -1 :
string_table.intern(hh[0].trim()));
// #### need an address parser here...
string_table.intern(hh[0].trim()));
// #### need an address parser here...
recipient_name = -1;
/*
hh = h.getHeader("To");
// #### deal with multiple to fields
recipient_name = (hh == null || hh.length == 0 ? -1 :
string_table.intern(hh[0].trim()));
if (recipient == -1) {
// #### deal with multiple cc fields
hh = h.getHeader("CC");
recipient_name = (hh == null || hh.length == 0 ? -1 :
string_table.intern(hh[0].trim()));
}
if (recipient == -1) {
hh = h.getHeader("Newsgroups");
recipient_name = (hh == null || hh.length == 0 ? -1 :
string_table.intern(hh[0].trim()));
}
*/
hh = h.getHeader("Subject");
if (hh != null && hh.length != 0) {
// Much of this code is duplicated in MessageExtraFactory. Sigh. ###
@ -398,19 +400,19 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
flags |= FLAG_HAS_RE; // yes, we found it.
} else if (c == '[' || c == '(') {
int i = 3; // skip over "Re[" or "Re("
// Skip forward over digits after the "[" or "(".
int length = value.length();
while (i < length &&
value.byteAt(i) >= '0' &&
value.byteAt(i) <= '9') {
value.byteAt(i) >= '0' &&
value.byteAt(i) <= '9') {
i++;
}
// Now ensure that the following thing is "]:" or "):"
// Only if it is do we treat this all as a "Re"-ish thing.
if (i < (length-1) &&
(value.byteAt(i) == ']' ||
value.byteAt(i) == ')') &&
value.byteAt(i) == ')') &&
value.byteAt(i+1) == ':') {
value.remove(0, i+2); // Skip the whole thing.
value.trim(); // Remove any whitespace after colon
@ -420,11 +422,11 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
}
subject = string_table.intern(value);
}
hh = h.getHeader("Date");
if (hh != null && hh.length != 0)
sentDate = NetworkDate.parseLong(new ByteBuf(hh[0]), true);
hh = h.getHeader("Message-ID");
if (hh != null && hh.length != 0) {
ByteBuf value = new ByteBuf(hh[0]);
@ -438,7 +440,7 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
}
message_id = id_table.intern(value.trim());
}
// There must be a message ID on every message.
if (message_id == -1) {
// #### In previous versions, we did this by getting the MD5 hash
@ -446,25 +448,26 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
String id = grendel.util.MessageIDGenerator.generate("missing-id");
message_id = id_table.intern(new ByteBuf(id));
}
hh = h.getHeader("References");
if (hh != null && hh.length != 0) {
ByteBuf value = new ByteBuf(hh[0]);
references = internReferences(id_table, value.trim());
}
// Only examine the In-Reply-To header if there is no References header.
if (references == null) {
hh = h.getHeader("In-Reply-To");
if (hh != null && hh.length != 0) {
ByteBuf value = new ByteBuf(hh[0]);
references = internReferences(id_table, value.trim());
}
}
base_headers = h;
}
// Ported from akbar's "msg_intern_references", mailsum.c.
protected int[] internReferences(MessageIDTable id_table, ByteBuf refs) {
byte data[] = refs.toBytes();
@ -476,17 +479,17 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
n_refs++;
}
}
if (n_refs == 0)
return null;
int result[] = new int[n_refs];
int start = 0;
int cur = 0;
s = 0;
while (s < length) {
// The old way was to skip over whitespace, then skip an optional "<".
// The new way is to skip over everything up to and including "<".
// This lets us deal better with In-Reply-To headers, in addition to
@ -520,21 +523,21 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
//
while (start < length && data[start] != '<')
start++;
// skip over consecutive "<" -- I've seen "<<ID@HOST>>".
while (start < length && data[start] == '<')
start++;
s = start;
while (s < length && data[s] != '>')
s++;
if (s > start &&
s < length &&
data[s] == '>') {
result[cur++] = id_table.intern(data, start, s - start);
start = s + 1;
// skip over consecutive ">" -- I've seen "<<ID@HOST>>".
while (start < length && data[start] == '>')
start++;
@ -542,7 +545,7 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
s++;
}
}
if (cur != n_refs) {
// Whoops! Something's funny about this line, and the number of
// ">" characters didn't equal the number of IDs we extracted.
@ -556,75 +559,75 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
result = r2;
}
}
return result;
}
public Object getMessageID() {
MessageIDTable id_table = ((FolderBase)folder).getMessageIDTable();
return (MessageID) id_table.getObject(message_id);
}
public String getSubject() {
String result = simplifiedSubject();
if (subjectIsReply()) result = "Re: " + result;
return result;
}
public String getAuthor() {
ByteStringTable string_table = ((FolderBase)folder).getStringTable();
ByteString a = (ByteString)
string_table.getObject(author_name);
string_table.getObject(author_name);
if (a == null) return "";
else return a.toString();
}
public String getRecipient() {
ByteStringTable string_table = ((FolderBase)folder).getStringTable();
ByteString r = (ByteString) string_table.getObject(recipient_name);
if (r == null) return "";
else return r.toString();
}
public Object[] messageThreadReferences() {
if (references == null) return null;
int count = references.length;
if (count == 0) return null;
// Note: this conses.
MessageIDTable id_table = ((FolderBase)folder).getMessageIDTable();
Object result[] = new Object[count];
for (int i = 0; i < result.length; i++)
result[i] = id_table.getObject(references[i]);
return result;
}
public long getSentDateAsLong() {
return sentDate;
}
public Date getSentDate() {
return new Date(sentDate);
}
public Date getReceivedDate() {
// ### We don't currently remember this info. Should we?
return getSentDate();
}
public Folder getFolder() {
return folder;
}
// #### Warning, this is untested -- the "javax.mail.Flags" class changed
// around a bunch since the last time we tried to use this code, and I had
// to beat on this.
public Flags getFlags() {
Flags result = new Flags();
for (int i=0 ; i<FLAGDEFS.length ; i++) {
@ -638,7 +641,7 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
}
return result;
}
public boolean isSet(Flags.Flag flag) {
for (int i=0; i < FLAGDEFS.length ; i++) {
if (flag.equals(FLAGDEFS[i].builtin)) {
@ -647,7 +650,7 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
}
return false;
}
public boolean isSet(String flag) {
for (int i=0; i < FLAGDEFS.length ; i++) {
if (flag.equals(FLAGDEFS[i].non_builtin)) {
@ -656,39 +659,39 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
}
return false;
}
public void setFlags(Flags flag, boolean set) throws MessagingException {
Flags.Flag[] builtin_flags = flag.getSystemFlags();
String [] non_builtin_flags = flag.getUserFlags();
NEXT_BUILTIN:
for (int i = 0; i < builtin_flags.length ; i++) {
Flags.Flag name = builtin_flags[i];
for (int j=0 ; j<FLAGDEFS.length ; j++) {
if (FLAGDEFS[j].editable && name.equals(FLAGDEFS[j].builtin)) {
setFlagBit(FLAGDEFS[j].flag, set);
continue NEXT_BUILTIN;
NEXT_BUILTIN:
for (int i = 0; i < builtin_flags.length ; i++) {
Flags.Flag name = builtin_flags[i];
for (int j=0 ; j<FLAGDEFS.length ; j++) {
if (FLAGDEFS[j].editable && name.equals(FLAGDEFS[j].builtin)) {
setFlagBit(FLAGDEFS[j].flag, set);
continue NEXT_BUILTIN;
}
}
throw new IllegalWriteException("Can't change flag " + name +
" on message " + this);
}
throw new IllegalWriteException("Can't change flag " + name +
" on message " + this);
}
NEXT_NON_BUILTIN:
for (int i = 0; i < non_builtin_flags.length ; i++) {
String name = non_builtin_flags[i];
for (int j=0 ; j<FLAGDEFS.length ; j++) {
if (FLAGDEFS[j].editable && name.equals(FLAGDEFS[j].non_builtin)) {
setFlagBit(FLAGDEFS[j].flag, set);
continue NEXT_NON_BUILTIN;
NEXT_NON_BUILTIN:
for (int i = 0; i < non_builtin_flags.length ; i++) {
String name = non_builtin_flags[i];
for (int j=0 ; j<FLAGDEFS.length ; j++) {
if (FLAGDEFS[j].editable && name.equals(FLAGDEFS[j].non_builtin)) {
setFlagBit(FLAGDEFS[j].flag, set);
continue NEXT_NON_BUILTIN;
}
}
throw new IllegalWriteException("Can't change flag " + name +
" on message " + this);
}
}
throw new IllegalWriteException("Can't change flag " + name +
" on message " + this);
}
}
protected synchronized void setFlagBit(long flag, boolean value) {
long newflags = flags;
if (value) {
@ -699,138 +702,155 @@ abstract class MessageBase extends MessageReadOnly implements MessageExtra {
if (flags != newflags) {
flags = newflags;
((FolderBase)folder).doNotifyMessageChangedListeners
(MessageChangedEvent.FLAGS_CHANGED, this);
(MessageChangedEvent.FLAGS_CHANGED, this);
}
}
// #### are these still part of the JavaMail spec? I don't see them in
// http://www.javasoft.com/products/javamail/javadocs/javax/mail/Message.html
public boolean isRead() {
return ((flags & FLAG_READ) != 0);
}
public void setIsRead(boolean value) {
setFlagBit(FLAG_READ, value);
}
public boolean isReplied() {
return ((flags & FLAG_REPLIED) != 0);
}
public void setReplied(boolean value) {
setFlagBit(FLAG_REPLIED, value);
}
public boolean isForwarded() {
return ((flags & FLAG_FORWARDED) != 0);
}
public void setForwarded(boolean value) {
setFlagBit(FLAG_FORWARDED, value);
}
public boolean isFlagged() {
return ((flags & FLAG_MARKED) != 0);
}
public void setFlagged(boolean value) {
setFlagBit(FLAG_MARKED, value);
}
public boolean isDeleted() {
return ((flags & FLAG_DELETED) != 0);
}
public void setDeleted(boolean value) {
setFlagBit(FLAG_DELETED, value);
}
public boolean isSigned() {
return ((flags & FLAG_SIGNED) != 0);
}
public boolean isEncrypted() {
return ((flags & FLAG_ENCRYPTED) != 0);
}
public boolean flagsAreDirty() {
return ((flags & FLAG_DIRTY) != 0);
}
public void setFlagsDirty(boolean value) {
setFlagBit(FLAG_DIRTY, value);
}
public void saveChanges() {
// ### Should this actually do something?
}
public InputStream getRawText() throws IOException {
return null;
}
public String simplifiedSubject() {
ByteStringTable string_table = ((FolderBase)folder).getStringTable();
ByteString s = (ByteString) string_table.getObject(subject);
if (s == null) return "";
else return s.toString(); // #### fix me
}
public boolean subjectIsReply() {
return ((flags & FLAG_HAS_RE) != 0);
}
public String simplifiedDate() {
return SimplifyADate(sentDate);
}
static String SimplifyADate(long ldate) {
public static String SimplifyADate(long ldate) {
if (ldate <= 0) return "";
synchronized(scratch_date) {
if (date_format == null) {
date_format = DateFormat.getDateTimeInstance();
}
scratch_date.setTime(ldate);
/* #### This needs to be more clever. I guess we should make our
own subclass of DateFormat and put the cleverness there. */
return date_format.format(scratch_date);
}
}
public void putByteStream(OutputStream out) throws MessagingException {
throw new MethodNotSupportedException("MessageBase.putByteStream");
}
public DataHandler getDataHandler() {
return null;
}
public Object getContent() {
try
{
StringBuffer buff=new StringBuffer();
Reader r=new InputStreamReader(getInputStream());
char[] b = new char[1024];
while(r.read(b)!=-1)
{
buff.append(b);
private DataHandler data_handler = null;
public DataHandler getDataHandler() throws MessagingException {
if (data_handler==null) {
String type = "text/plain"; //XXX lie to it...
String[] content_types = getHeader("Content-Type");
if ((content_types!=null)&&(content_types.length>0)) {
type = content_types[0];
}
data_handler = new DataHandler(getContentAsString(),type);
}
r.close();
return buff.toString();
}
catch(Exception e) {e.printStackTrace(); return null;}
return data_handler;
}
private String getContentAsString() {
try {
StringWriter writer =new StringWriter();
Reader r=new InputStreamReader(getInputStream());
int read = r.read();;
while(read!=-1) {
writer.write(read);
read = r.read();
}
r.close();
return writer.toString();
} catch(Exception e) {e.printStackTrace(); return null;}
}
public Object getContent() throws IOException, MessagingException {
return getDataHandler().getContent();
}
private InternetHeaders base_headers = null;
private InternetHeaders full_headers = null;
/** Get the InternetHeaders object. Someday, maybe we'll keep this around
in a weak link or something. But for now, we always recreate it.
Subclasses might want to redefine just this, or they might want to
redefine all the routines below that use this. */
* in a weak link or something. But for now, we always recreate it.
* Subclasses might want to redefine just this, or they might want to
* redefine all the routines below that use this. */
protected InternetHeaders getHeadersObj() throws MessagingException {
InputStream is=getInputStreamWithHeaders();
InternetHeaders ih=new InternetHeaders(is);
try { is.close(); } catch(Exception e) {e.printStackTrace();}
return ih;
// return new InternetHeaders(getInputStreamWithHeaders());
if (full_headers==null) {
try {
InputStream is=getInputStreamWithHeaders();
full_headers=new InternetHeaders(is);
try { is.close(); } catch(Exception e) {e.printStackTrace();}
} catch (IOException ioe) {
if (session.getDebug()) {
ioe.printStackTrace();
}
return base_headers;
}
}
return full_headers;
}
}

View File

@ -20,10 +20,12 @@
* Contributor(s):
*
* Created: Terry Weissman <terry@netscape.com>, 22 Oct 1997.
* Kieran Maclean
*/
package grendel.storage;
import java.io.IOException;
import java.io.InputStream;
import javax.mail.MessagingException;
@ -68,7 +70,7 @@ public interface MessageExtra {
/** Gets the input stream for the message, in RFC822 format: a bunch of
headers, and then a blank line, and then the message itself. */
public InputStream getInputStreamWithHeaders() throws MessagingException;
public InputStream getInputStreamWithHeaders() throws MessagingException, IOException;
};

View File

@ -309,7 +309,7 @@ class MessageExtraWrapper implements MessageExtra
// Removes leading "Re:" or similar from the given StringBuffer. Returns
// true if it found such a string to remove; false otherwise.
protected boolean stripRe(StringBuffer buf)
protected static boolean stripRe(StringBuffer buf)
{
// Much of this code is duplicated in MessageBase. Sigh. ###
if (buf==null) {

View File

@ -20,40 +20,44 @@
* Contributor(s):
*
* Created: Terry Weissman <terry@netscape.com>, 24 Nov 1997.
* Kieran Maclean
*/
package grendel.storage;
import calypso.util.NetworkDate;
import calypso.util.Assert;
import calypso.util.ByteBuf;
import java.io.InputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Header;
import javax.mail.IllegalWriteException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
/** This implements those methods on Message which are commonly used in all
of our read-only message implementations. It also provides an
implementation for the header-reading routines, all of which assume that
the subclass implements getHeadersObj, a method to get an InternetHeaders
object for this message. */
* of our read-only message implementations. It also provides an
* implementation for the header-reading routines, all of which assume that
* the subclass implements getHeadersObj, a method to get an InternetHeaders
* object for this message. */
abstract class MessageReadOnly extends Message {
@ -101,7 +105,7 @@ abstract class MessageReadOnly extends Message {
readonly();
}
public String getContentType() {
public String getContentType() throws MessagingException {
return "message/rfc822";
}
@ -129,8 +133,8 @@ abstract class MessageReadOnly extends Message {
readonly();
}
public void setDataHandler(DataHandler d) {
Assert.NotYetImplemented("Can't set a datahandler on our readonly messages!");
public void setDataHandler(DataHandler d) throws MessagingException {
readonly();
}
public void setText(String s) throws MessagingException {
@ -141,12 +145,12 @@ abstract class MessageReadOnly extends Message {
readonly();
}
public void setContent(Object o, String s) {
Assert.NotYetImplemented("Can't set content on our readonly messages!");
public void setContent(Object o, String s) throws MessagingException {
readonly();
}
public void setContent(Multipart m) {
Assert.NotYetImplemented("Can't set content on our readonly messages!");
public void setContent(Multipart m) throws MessagingException {
readonly();
}
public void addHeader(String s1, String s2) throws MessagingException {
@ -235,70 +239,67 @@ abstract class MessageReadOnly extends Message {
return getOneHeader("Subject");
}
public Enumeration getAllHeaders() {
Assert.NotYetImplemented("MessageBase.getAllHeaders -- don't know what it's really supposed to do!");
return null;
public Enumeration getAllHeaders() throws MessagingException {
return getHeadersObj().getAllHeaders();
}
public Enumeration getMatchingHeaders(String s[]) {
Assert.NotYetImplemented("MessageBase.getMatchingHeaders -- don't know what it's really supposed to do!");
return null;
public Enumeration getMatchingHeaders(String s[]) throws MessagingException {
return getHeadersObj().getMatchingHeaders(s);
}
public Enumeration getNonMatchingHeaders(String s[]) {
Assert.NotYetImplemented("MessageBase.getNonMatchingHeaders -- don't know what it's really supposed to do!");
return null;
public Enumeration getNonMatchingHeaders(String s[]) throws MessagingException {
return getHeadersObj().getNonMatchingHeaders(s);
}
public Message reply(boolean replyToAll) {
Assert.NotYetImplemented("MessageReadOnly.reply");
return null;
}
public boolean isMimeType(String mimeType) {
Assert.NotYetImplemented("MessageReadOnly.isMimeType");
return false;
}
/** This default implementation of getInputStream() works in terms of
getInputStreamWithHeaders. Subclasses are encouraged to define this
more directly if they have an easy way of doing so. */
public InputStream getInputStream() throws MessagingException {
InputStream in = getInputStreamWithHeaders();
if (in == null) return null;
// Now read until we hit a blank line.
int b1 = 0;
int b2 = 0;
try {
while ((b2 = in.read()) >= 0) {
if (b1 == '\r') {
if (b2 == '\r') {
// Two CR's in a row; done.
break;
}
} else if (b1 == '\n') {
if (b2 == '\n') {
// Two LF's in a row; done.
break;
} else if (b2 == '\r') {
// We have a LFCR. It's gotta be the middle of a CRLFCRLF. So,
// we need to read one more character and then we're done.
b1 = b2;
b2 = in.read();
if (b2 == '\n') break;
}
}
b1 = b2;
}
} catch (IOException e) {
throw new MessagingException("Can't skip headers in stream", e);
public Message reply(boolean replyToAll) throws MessagingException {
MimeMessage mm = new MimeMessage(this.session);
Address[] reply_to = this.getReplyTo();
if ((reply_to==null)||(reply_to.length==0)) {
reply_to = this.getFrom();
}
return in;
if (replyToAll) {
Address[] recipitants = this.getRecipients(Message.RecipientType.TO);
List<Address> recipitants_list = new ArrayList<Address>();
if ((recipitants!=null)&&(recipitants.length>0)) {
recipitants_list.addAll(Arrays.asList(recipitants));
}
if ((reply_to!=null)&&(reply_to.length>0)) {
recipitants_list.addAll(Arrays.asList(reply_to));
}
public InputStream getInputStreamWithHeaders() throws MessagingException {
throw new MethodNotSupportedException("MessageBase.getInputStreamWithHeaders");
mm.setRecipients(Message.RecipientType.TO,recipitants_list.toArray(new Address[0]));
recipitants = this.getRecipients(Message.RecipientType.CC);
mm.setRecipients(Message.RecipientType.CC,recipitants);
recipitants = this.getRecipients(Message.RecipientType.BCC);
mm.setRecipients(Message.RecipientType.BCC,recipitants);
} else {
if ((reply_to==null)||(reply_to.length==0)) {
throw new MessagingException("No Addresses to send reply to, failed!");
}
mm.setRecipients(Message.RecipientType.TO,new Address[]{reply_to[0]});
}
StringBuffer subject = new StringBuffer(getSubject());
MessageExtraWrapper.stripRe(subject);
subject.insert(0,"Re: ");
mm.setSubject(subject.toString());
return mm;
}
public boolean isMimeType(String mimeType) throws MessagingException {
return getContentType().equalsIgnoreCase(mimeType);
}
/** This default implementation of getInputStream() works in terms of
* getInputStreamWithHeaders. Subclasses are encouraged to define this
* more directly if they have an easy way of doing so. */
public InputStream getInputStream() throws MessagingException, IOException {
return getDataHandler().getInputStream();
}
public abstract InputStream getInputStreamWithHeaders() throws MessagingException, IOException;
}

View File

@ -20,6 +20,7 @@
* Contributor(s):
*
* Created: Jamie Zawinski <jwz@netscape.com>, 1 Dec 1997.
* Kieran Maclean
*/
package grendel.storage;
@ -29,18 +30,19 @@ import calypso.util.ByteBuf;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.mail.Flags;
import java.util.Enumeration;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
class NewsMessage extends MessageBase {
class NewsMessage extends MessageBase
{
int article_number = -1;
int byte_length = -1;
int line_length = -1;
NewsMessage(NewsFolder f, InternetHeaders h) {
@ -69,6 +71,18 @@ class NewsMessage extends MessageBase {
super(f, date, flags, author, recipient, subj, id, refs);
}
public String getContentType() throws MessagingException {
String[] content_types = getHeader("Content-Type");
if ((content_types == null) || (content_types.length == 0)) {
return getDataHandler().getContentType();
}
for (int i = content_types.length - 1; i > -1; i++) {
if (content_types[i].contains("/")) {
return content_types[i];
}
}
return content_types[0];
}
void setSize(int l) {
byte_length = l;

View File

@ -17,9 +17,10 @@
* Copyright (C) 1997 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Contributor(s):
*
* Created: Jamie Zawinski <jwz@netscape.com>, 20 Nov 1997.
* Kieran Maclean
*/
package grendel.storage;
@ -49,49 +50,49 @@ import grendel.util.Constants;
/** Store for News (NNTP) folders.
<p>
This class really shouldn't be public, but I haven't figured out how to
tie into javamail's Session class properly. So, instead of using
<tt>Session.getStore(String)</tt>, you instead need to call
<tt>NewsStore.GetDefaultStore(Session)</tt>.
*/
* <p>
* This class really shouldn't be public, but I haven't figured out how to
* tie into javamail's Session class properly. So, instead of using
* <tt>Session.getStore(String)</tt>, you instead need to call
* <tt>NewsStore.GetDefaultStore(Session)</tt>.
*/
public class NewsStore extends Store {
protected NNTPConnection nntp = null;
protected NewsRC newsrc = null;
protected NewsFolderRoot root_folder = null;
static protected NewsStore DefaultStore = null;
public static Store GetDefaultStore(Session s) {
if (DefaultStore == null) {
DefaultStore = new NewsStore(s);
}
return DefaultStore;
}
public NewsStore(Session s) {
super(s, null);
}
public NewsStore(Session s, URLName u) {
super(s, u);
}
NewsRC getNewsRC() {
return newsrc;
}
private void loadNewsRC(String host) throws IOException {
Assert.Assertion(newsrc == null);
String dir = System.getProperty("user.home");
boolean secure = false; // ####
String name = null;
if (Constants.ISUNIX) {
String name1 = (secure ? ".snewsrc" : ".newsrc");
String name2 = name1 + "-" + host;
if (new File(dir, name2).exists())
@ -100,9 +101,9 @@ public class NewsStore extends Store {
name = name1;
else
name = name2;
} else if (Constants.ISWINDOWS) {
String name1 = (secure ? "snews" : "news");
String name2 = name1 + "-" + host + ".rc";
String name3 = name1 + ".rc";
@ -112,14 +113,14 @@ public class NewsStore extends Store {
name = name3;
else
name = name2;
} else {
Assert.NotYetImplemented("newsrc names only implemented for windows 'n' unix...");
}
File f = new File(dir, name);
newsrc = new NewsRC(f);
// If the newsrc file didn't exist, subscribe to some default newsgroups.
if (!f.exists()) {
String subs[] = getDefaultSubscriptions();
@ -128,19 +129,19 @@ public class NewsStore extends Store {
ng.setSubscribed(true);
}
}
}
protected boolean protocolConnect(String host,
int port,
String user,
String password)
throws MessagingException {
int port,
String user,
String password)
throws MessagingException {
if (nntp != null)
return true; // Already connected.
if (host == null) {
// #### better name?
host = session.getProperty("mail.default_nntp_server");
@ -149,45 +150,45 @@ public class NewsStore extends Store {
host = "news";
}
}
try {
loadNewsRC(host);
} catch (IOException e) {
throw new MessagingException("loading newsrc file", e);
}
nntp = new NNTPConnection();
try {
boolean status = nntp.connect(host, port, user, password);
if (!status) return false;
} catch (UnknownHostException e) { // This sucks, Beavis!
throw new MessagingException("Unknown host", e);
} catch (IOException e) {
throw new MessagingException("I/O Error", e);
}
if (!newsrc.file().exists()) {
// #### subscribe to the server's default groups
}
notifyStoreListeners(StoreEvent.NOTICE, "opened"); // #### ???
// notifyConnectionListeners(ConnectionEvent.OPENED);
return true;
}
public void close() {
if (nntp == null) {
// already closed.
Assert.Assertion(newsrc == null);
return;
}
nntp.close();
nntp = null;
Assert.Assertion(newsrc != null);
if (newsrc != null) {
try {
@ -197,13 +198,13 @@ public class NewsStore extends Store {
// Just ignore it, I guess...
}
}
notifyStoreListeners(StoreEvent.NOTICE, "closed"); // #### ???
// notifyConnectionListeners(ConnectionEvent.CLOSED);
root_folder = null;
}
public Folder getDefaultFolder() {
if (root_folder == null) {
synchronized (this) {
@ -214,23 +215,28 @@ public class NewsStore extends Store {
}
return root_folder;
}
public Folder getFolder(String name) throws MessagingException {
return getDefaultFolder().getFolder(name);
}
public Folder getFolder(URL url) {
Assert.NotYetImplemented("NewsStore.getFolder(URL)");
return null;
public Folder getFolder(URL url) throws MessagingException {
return getFolder(new URLName(url));
}
public Folder getFolder(URLName urlName) {
Assert.NotYetImplemented("NewsStore.getFolder(URLName)");
return null;
public Folder getFolder(URLName urlName) throws MessagingException {
if (! urlName.getHost().equalsIgnoreCase(url.getHost()))
throw new MessagingException("Not the same host.");
if (urlName.getPort() != url.getPort())
throw new MessagingException("Not the same port.");
String file = urlName.getFile();
if (file.contains("/"))
throw new MessagingException("Invalid URL.");
return getFolder(file);
}
InputStream getMessageStream(NewsMessage message, boolean headers_too)
throws NNTPException, IOException {
throws NNTPException, IOException {
Folder f = message.getFolder();
String group = (f == null ? null : f.getFullName());
int n = (group == null ? -1 : message.getStorageFolderIndex());
@ -251,9 +257,9 @@ public class NewsStore extends Store {
return nntp.BODY(group, n);
}
}
/** Returns array of int: [ nmessages low hi ]
*/
*/
int[] getGroupCounts(NewsFolder folder) {
String group = folder.getFullName();
try {
@ -262,11 +268,11 @@ public class NewsStore extends Store {
return null;
}
}
/** Returns a list of newsgroups to which new users should be subscribed,
if they don't have a newsrc file. Tries to ask the news server for
this list; otherwise, uses some builtin defaults.
*/
* if they don't have a newsrc file. Tries to ask the news server for
* this list; otherwise, uses some builtin defaults.
*/
String[] getDefaultSubscriptions() {
String s[] = null;
try {
@ -276,13 +282,13 @@ public class NewsStore extends Store {
}
if (s == null || s.length == 0)
s = new String[] { "news.announce.newusers",
"news.newusers.questions",
"news.groups.questions",
"alt.fan.mozilla"
};
return s;
"news.newusers.questions",
"news.groups.questions",
"alt.fan.mozilla"
};
return s;
}
void openNewsgroup(NewsFolder folder, long from, long to) {
Enumeration e;
try {
@ -292,7 +298,7 @@ public class NewsStore extends Store {
} catch (IOException ex) {
return;
}
while (e.hasMoreElements()) {
Message m = (Message) e.nextElement();
folder.noticeInitialMessage(m);