You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ch...@apache.org on 2001/05/11 13:01:59 UTC
cvs commit: jakarta-james/src/org/apache/james/imapserver ACL.java ACLMailbox.java BaseCommand.java CommandFetch.java CommandStore.java DefaultRecordRepository.java FileMailbox.java Flags.java FolderRecord.java Host.java IMAPServer.java IMAPServer.xinfo IMAPSystem.java ImapRequest.java JamesHost.java Mailbox.java MailboxEvent.java MailboxEventListener.java MailboxEventSource.java MailboxException.java MessageAttributes.java MessageHeader.java RecordRepository.java SimpleFolderRecord.java SimpleMessageAttributes.java SimpleSystem.java SingleThreadedConnectionHandler.java
charlesb 01/05/11 04:01:57
Modified: . build.xml
Added: src/java/org/apache/james/imapserver ACL.java
ACLMailbox.java BaseCommand.java CommandFetch.java
CommandStore.java DefaultRecordRepository.java
FileMailbox.java Flags.java FolderRecord.java
Host.java IMAPServer.java IMAPServer.xinfo
ImapRequest.java JamesHost.java Mailbox.java
MailboxEvent.java MailboxEventListener.java
MailboxEventSource.java MailboxException.java
MessageAttributes.java MessageHeader.java
RecordRepository.java SimpleFolderRecord.java
SimpleMessageAttributes.java SimpleSystem.java
SingleThreadedConnectionHandler.java
Removed: src/org/apache/james/imapserver ACL.java ACLMailbox.java
BaseCommand.java CommandFetch.java
CommandStore.java DefaultRecordRepository.java
FileMailbox.java Flags.java FolderRecord.java
Host.java IMAPServer.java IMAPServer.xinfo
IMAPSystem.java ImapRequest.java JamesHost.java
Mailbox.java MailboxEvent.java
MailboxEventListener.java MailboxEventSource.java
MailboxException.java MessageAttributes.java
MessageHeader.java RecordRepository.java
SimpleFolderRecord.java
SimpleMessageAttributes.java SimpleSystem.java
SingleThreadedConnectionHandler.java
Log:
Moving from src/org to src/java/org
Revision Changes Path
1.59 +1 -1 jakarta-james/build.xml
Index: build.xml
===================================================================
RCS file: /home/cvs/jakarta-james/build.xml,v
retrieving revision 1.58
retrieving revision 1.59
diff -u -r1.58 -r1.59
--- build.xml 2001/05/10 15:28:14 1.58
+++ build.xml 2001/05/11 11:01:06 1.59
@@ -88,7 +88,7 @@
-->
<property name="src.dir" value="src"/>
- <property name="java.dir" value="${src.dir}"/>
+ <property name="java.dir" value="${src.dir}/java"/>
<property name="conf.dir" value="conf"/>
<property name="lib.dir" value="lib"/>
<property name="tools.dir" value="tools"/>
1.1 jakarta-james/src/java/org/apache/james/imapserver/ACL.java
Index: ACL.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.Serializable;
import java.util.Set;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
/**
* Interface for objects representing for an IMAP4rev1 Access Control List.
* There should be one instance of this class per open mailbox. An Access
* control list, for IMAP purposes, is a list of <identifier, rights> pairs.
*
* <p>The standard rights in RFC2086 are:
* <br>l - lookup (mailbox is visible to LIST/LSUB commands)
* <br>r - read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL, SEARCH,
* COPY from mailbox)
* <br>s - keep seen/unseen information across sessions (STORE SEEN flag)
* <br>w - write (STORE flags other than SEEN and DELETED)
* <br>i - insert (perform APPEND, COPY into mailbox)
* <br>p - post (send mail to submission address for mailbox, not enforced by
* IMAP4 itself)
* <br>c - create (CREATE new sub-mailboxes in any implementation-defined
* hierarchy)
* <br>d - delete (STORE DELETED flag, perform EXPUNGE)
* <br>a - administer (perform SETACL)
*
*
* <p>References: rfc 2060, rfc 2086
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public interface ACL
extends Serializable {
char LOOKUP_RIGHTS = 'l';
char READ_RIGHTS = 'r';
char KEEP_SEEN_RIGHTS = 's';
char WRITE_RIGHTS = 'w';
char INSERT_RIGHTS = 'i';
char POST_RIGHTS = 'p';
char CREATE_RIGHTS = 'c';
char DELETE_RIGHTS = 'd';
char ADMIN_RIGHTS = 'a';
char ADD_RIGHTS = '+';
char REMOVE_RIGHTS = '-';
char[] RIGHTS =
{
LOOKUP_RIGHTS, READ_RIGHTS, KEEP_SEEN_RIGHTS, WRITE_RIGHTS,
INSERT_RIGHTS, POST_RIGHTS, CREATE_RIGHTS, DELETE_RIGHTS,
ADMIN_RIGHTS
};
/**
* Store access rights for a given identity.
* The setter is the user setting the rights, the identifier is the user
* whose rights are affected.
* The setter and identifier arguments must be non-null and non-empty.
* The modification argument must be non-null and follow the syntax of the
* third argument to a SETACL command.
* If the modification argument is an empty string, that identifier is
* removed from the ACL, if currently present.
*
* @param setter String representing user attempting to set rights, must
* be non-null and non-empty
* @param identity String representing user whose rights are being set,
* must be non-null and non-empty
* @param modification String representing the change in rights, following
* the syntax specified in rfc 2086
* @returns true if requested modification succeeded. A return value of
* false means an error other than an AccessControlException or
* AuthorizationException.
* @throws AccessControlException if setter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if specified setter does not have the
* administer right (ie the right to write ACL rights), or if the result
* of this method would leave no identities with admin rights.
*/
boolean setRights( String setter,
String identifier,
String modification)
throws AccessControlException, AuthorizationException;
/**
* Retrieve access rights for a specific identity.
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
String getRights( String getter, String identity )
throws AccessControlException, AuthorizationException;
/**
* Retrieves a String of one or more <identity space rights> who have
* rights in this ACL
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @returns String of rights sets usingrfc2086 syntax
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL to this getter.
*/
String getAllRights( String getter )
throws AccessControlException, AuthorizationException;
/**
* Retrieve rights which will always be granted to the specified identity.
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* guaranteed rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
String getRequiredRights( String getter, String identity )
throws AccessControlException, AuthorizationException;
/**
* Retrieve rights which may be granted to the specified identity.
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* guaranteed rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
String getOptionalRights( String getter, String identity )
throws AccessControlException, AuthorizationException;
/**
* Helper boolean methods.
* Provided for cases where you need to check the ACL before selecting the
* mailbox.
*
* @param username String representing user
* @returns true if user has the requested right.
* &throws AccessControlException if username does not have lookup rights.
* (Except for hasLookupRights which just returns false.
*/
boolean hasReadRights( String username )
throws AccessControlException;
boolean hasKeepSeenRights( String username )
throws AccessControlException;
boolean hasWriteRights( String username )
throws AccessControlException;
boolean hasInsertRights( String username )
throws AccessControlException;
boolean hasDeleteRights( String username )
throws AccessControlException;
boolean hasAdminRights( String username )
throws AccessControlException;
Set getUsersWithLookupRights();
Set getUsersWithReadRights();
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/ACLMailbox.java
Index: ACLMailbox.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.context.Contextualizable;
/**
* Interface for objects representing an IMAP4rev1 mailbox (folder) with
* embedded Access Control List.
*
* Reference: RFC 2060
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
* @see Mailbox
* @see ACL
*/
public interface ACLMailbox
extends ACL, Mailbox, Contextualizable, Initializable, Disposable {
/**
* Set the details particular to this Mailbox. Should only be called once,
* at creation, and not when restored from storage.
*
* @param user String email local part of owner of a personal mailbox.
* @param abName String absolute, ie user-independent, name of mailbox.
* @param initialAdminUser String email local-part of a user who will be assigned admin rights on this mailbox
*/
void prepareMailbox( String user, String absName, String initialAdminUser );
/**
* Re-initialises mailbox when restored from storage. Must be called after
* setConfiguration, setContext, setComponentManager, if they are called,
* but before any opertional methods are called.
*/
void reinitialize()
throws Exception;
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/BaseCommand.java
Index: BaseCommand.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.*;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import org.apache.avalon.framework.logger.AbstractLoggable;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
import org.apache.james.BaseConnectionHandler;
import org.apache.james.core.EnhancedMimeMessage;
/**
* Provides methods useful for IMAP command objects.
*
* References: rfc 2060, rfc 2193, rfc 2221
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 17 Jan 2001
*/
public abstract class BaseCommand
extends BaseConnectionHandler {
//mainly to switch on stack traces and catch responses;
private static final boolean DEEP_DEBUG = true;
/**
* Turns a protocol-compliant string representing a message sequence
* number set into a List of integers. Use of the wildcard * (star) relies
* on contiguous property of msns.
*
* @param rawSet the IMAP protocol compliant string to be decoded
* @param exists the number of messages in this mailbox
* @returns a List of Integers, one per message in set
*/
protected List decodeSet( String rawSet, int exists ) throws IllegalArgumentException {
if (rawSet == null) {
getLogger().debug("Null argument in decodeSet");
throw new IllegalArgumentException("Null argument");
} else if (rawSet.equals("")) {
getLogger().debug("Empty argument in decodeSet");
throw new IllegalArgumentException("Empty string argument");
}
getLogger().debug(" decodeSet called for: " + rawSet);
List response = new ArrayList();
int checkComma = rawSet.indexOf(",");
if (checkComma == -1) {
int checkColon = rawSet.indexOf(":");
if (checkColon == -1) {
Integer seqNum = new Integer(rawSet.trim());
if (seqNum.intValue() < 1) {
throw new IllegalArgumentException("Not a positive integer");
} else {
response.add(seqNum);
}
} else {
Integer firstNum = new Integer(rawSet.substring(0, checkColon));
int first = firstNum.intValue();
Integer lastNum;
int last;
if (rawSet.indexOf("*") != -1) {
last = exists;
lastNum = new Integer(last);
} else {
lastNum = new Integer(rawSet.substring(checkColon + 1));
last = lastNum.intValue();
}
if (first < 1 || last < 1) {
throw new IllegalArgumentException("Not a positive integer");
} else if (first < last) {
response.add(firstNum);
for (int i = (first + 1); i < last; i++) {
response.add(new Integer(i));
}
response.add(lastNum);
} else if (first == last) {
response.add(firstNum);
} else {
throw new IllegalArgumentException("Not an increasing range");
}
}
} else {
try {
String firstRawSet = rawSet.substring(0, checkComma);
String secondRawSet = rawSet.substring(checkComma + 1);
response.addAll(decodeSet(firstRawSet, exists));
response.addAll(decodeSet(secondRawSet, exists));
} catch (IllegalArgumentException e) {
getLogger().debug("Wonky arguments in: " + rawSet + " " + e);
throw e;
}
}
return response;
}
/**
* Turns a protocol-compliant string representing a uid set into a
* List of integers. Where the string requests ranges or uses the * (star)
* wildcard, the results are uids that exist in the mailbox. This
* minimizes attempts to refer to non-existent messages.
*
* @param rawSet the IMAP protocol compliant string to be decoded
* @param uidsList List of uids of messages in mailbox
* @returns a List of Integers, one per message in set
*/
protected List decodeUIDSet( String rawSet, List uidsList )
throws IllegalArgumentException {
if (rawSet == null) {
getLogger().debug("Null argument in decodeSet");
throw new IllegalArgumentException("Null argument");
} else if (rawSet.equals("")) {
getLogger().debug("Empty argument in decodeSet");
throw new IllegalArgumentException("Empty string argument");
}
getLogger().debug(" decodeUIDSet called for: " + rawSet);
Iterator it = uidsList.iterator();
while (it.hasNext()) {
getLogger().info ("uids present : " + (Integer)it.next() );
}
List response = new ArrayList();
int checkComma = rawSet.indexOf(",");
if (checkComma == -1) {
int checkColon = rawSet.indexOf(":");
if (checkColon == -1) {
Integer seqNum = new Integer(rawSet.trim());
if (seqNum.intValue() < 1) {
throw new IllegalArgumentException("Not a positive integer");
} else {
response.add(seqNum);
}
} else {
Integer firstNum = new Integer(rawSet.substring(0, checkColon));
int first = firstNum.intValue();
Integer lastNum;
if (rawSet.indexOf("*") == -1) {
lastNum = new Integer(rawSet.substring(checkColon + 1));
} else {
lastNum = (Integer)uidsList.get(uidsList.size()-1);
}
int last;
last = lastNum.intValue();
if (first < 1 || last < 1) {
throw new IllegalArgumentException("Not a positive integer");
} else if (first < last) {
response.add(firstNum);
Collection uids;
if(uidsList.size() > 50) {
uids = new HashSet(uidsList);
} else {
uids = uidsList;
}
for (int i = (first + 1); i < last; i++) {
Integer test = new Integer(i);
if (uids.contains(test)) {
response.add(test);
}
}
response.add(lastNum);
} else if (first == last) {
response.add(firstNum);
} else {
throw new IllegalArgumentException("Not an increasing range");
}
}
} else {
try {
String firstRawSet = rawSet.substring(0, checkComma);
String secondRawSet = rawSet.substring(checkComma + 1);
response.addAll(decodeUIDSet(firstRawSet, uidsList));
response.addAll(decodeUIDSet(secondRawSet, uidsList));
} catch (IllegalArgumentException e) {
getLogger().debug("Wonky arguments in: " + rawSet + " " + e);
throw e;
}
}
return response;
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/CommandFetch.java
Index: CommandFetch.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.*;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
import org.apache.james.core.EnhancedMimeMessage;
/**
* Implements the IMAP FETCH command for a given ImapRequest.
*
* References: rfc 2060, rfc 2193, rfc 2221
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 17 Jan 2001
*/
public class CommandFetch
extends BaseCommand {
//mainly to switch on stack traces and catch responses;
private static final boolean DEEP_DEBUG = true;
private static final String OK = "OK";
private static final String NO = "NO";
private static final String BAD = "BAD";
private static final String UNTAGGED = "*";
private static final String SP = " ";
private StringTokenizer commandLine;
private boolean useUIDs;
private ACLMailbox currentMailbox;
private String commandRaw;
private PrintWriter out;
private OutputStream outs;
private String tag;
private String user;
private SingleThreadedConnectionHandler caller;
private String currentFolder;
/**
* Debugging method - will probably disappear
*/
public void setRequest(ImapRequest request) {
commandLine = request.getCommandLine();
useUIDs = request.useUIDs();
currentMailbox = request.getCurrentMailbox();
commandRaw = request.getCommandRaw();
tag = request.getTag();
currentFolder = request.getCurrentFolder();
caller = request.getCaller();
out = caller.getPrintWriter();
outs = caller.getOutputStream();
user = caller.getUser();
}
/**
* Implements IMAP fetch commands given an ImapRequest.
* This implementation attempts to satisfy the fetch command with the
* smallest objects deserialized from storage.
* <p>Warning - maybecome service(ImapRequest request)
* <p>Not yet complete - no partial (octet-counted or sub-parts) fetches.
*/
public void service() {
// decode the message set
List set;
List uidsList = null;
String setArg = commandLine.nextToken();
if (useUIDs) {
uidsList = currentMailbox.listUIDs(user);
set = decodeUIDSet(setArg, uidsList);
} else {
set = decodeSet(setArg, currentMailbox.getExists());
}
if (DEEP_DEBUG) {
getLogger().debug("Fetching message set of size: " + set.size());
}
String firstFetchArg = commandLine.nextToken();
int pos = commandRaw.indexOf(firstFetchArg);
//int pos = commandRaw.indexOf(setArg) + setArg.length() + 1;
String fetchAttrsRaw = null;
if (firstFetchArg.startsWith("(")) { //paranthesised fetch attrs
fetchAttrsRaw = commandRaw.substring(pos + 1, commandRaw.lastIndexOf(")"));
} else {
fetchAttrsRaw = commandRaw.substring(pos);
}
if (DEEP_DEBUG) {
getLogger().debug("Found fetchAttrsRaw: " + fetchAttrsRaw);
}
// decode the fetch attributes
List fetchAttrs = new ArrayList();
StringTokenizer fetchTokens = new StringTokenizer(fetchAttrsRaw);
while (fetchTokens.hasMoreTokens()) {
String attr = fetchTokens.nextToken();
if (attr.indexOf("(") == -1 ) { //not the start of a fields list
fetchAttrs.add(attr);
} else {
StringBuffer attrWithFields = new StringBuffer();
attrWithFields.append(fetchAttrs.remove(fetchAttrs.size() -1));
attrWithFields.append(" " + attr);
boolean endOfFields = false;
while (! endOfFields) {
String field = fetchTokens.nextToken();
attrWithFields.append(" " + field);
if (field.indexOf(")") != -1) {
endOfFields = true;
}
}
fetchAttrs.add(attrWithFields.toString());
}
}
// convert macro fetch commands to basic commands
for(int k = 0; k < fetchAttrs.size(); k++) {
String arg = (String)fetchAttrs.get(k);
if (arg.equalsIgnoreCase("FAST")) {
fetchAttrs.add("FLAGS");
fetchAttrs.add("INTERNALDATE");
fetchAttrs.add("RFC822.SIZE");
} else if (arg.equalsIgnoreCase("ALL")) {
fetchAttrs.add("FLAGS");
fetchAttrs.add("INTERNALDATE");
fetchAttrs.add("RFC822.SIZE");
fetchAttrs.add("ENVELOPE");
} else if (arg.equalsIgnoreCase("FULL")) {
fetchAttrs.add("FLAGS");
fetchAttrs.add("INTERNALDATE");
fetchAttrs.add("RFC822.SIZE");
fetchAttrs.add("ENVELOPE");
fetchAttrs.add("BODY");
}
getLogger().debug("Found fetchAttrs: " + arg);
}
try {
for (int i = 0; i < set.size(); i++) {
Integer uidObject = null;
int uid = 0;
int msn = 0;
if (useUIDs) {
uidObject = (Integer)set.get(i);
uid = uidObject.intValue();
msn = uidsList.indexOf(uidObject) + 1;
} else {
msn = ((Integer)set.get(i)).intValue();
}
MessageAttributes attrs = null;
String flags = null;
EnhancedMimeMessage msg = null;
String response = UNTAGGED + SP + msn + SP + "FETCH (";
boolean responseAdded = false;
Iterator it = fetchAttrs.iterator();
while(it.hasNext()) {
String arg = (String) it.next();
// commands that only need flags object
if (arg.equalsIgnoreCase("FLAGS")) {
if (flags == null) {
if (useUIDs) {
flags = currentMailbox.getFlagsUID(uid, user);
} else {
flags = currentMailbox.getFlags(msn, user);
}
}
if (flags == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message flags.");
getLogger().error("Retrieved null flags for msn:" + msn);
return;
}
if (responseAdded) {
response += SP + "FLAGS " + flags ;
} else {
response += "FLAGS " + flags ;
responseAdded = true;
}
}
// command that only need MessageAttributes object
else if (arg.equalsIgnoreCase("INTERNALDATE")) {
if (attrs == null) {
if (useUIDs) {
attrs = currentMailbox.getMessageAttributesUID(uid, user);
} else {
attrs = currentMailbox.getMessageAttributes(msn, user);
}
}
if (attrs == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message attributes.");
getLogger().error("Retrieved null attributes for msn:" + msn);
return;
}
if (responseAdded) {
response += SP + "INTERNALDATE \""
+ attrs.getInternalDateAsString() + "\")" ;
} else {
response += "INTERNALDATE \""
+ attrs.getInternalDateAsString() + "\")" ;
responseAdded = true;
}
} else if (arg.equalsIgnoreCase("RFC822.SIZE")) {
if (attrs == null) {
if (useUIDs) {
attrs = currentMailbox.getMessageAttributesUID(uid, user);
} else {
attrs = currentMailbox.getMessageAttributes(msn, user);
}
}
if (attrs == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message attributes.");
getLogger().error("Retrieved null attributes for msn:" + msn);
return;
}
if (responseAdded) {
response += SP + "RFC822.SIZE " + attrs.getSize();
} else {
response += "RFC822.SIZE " + attrs.getSize();
responseAdded = true;
}
} else if (arg.equalsIgnoreCase("ENVELOPE")) {
if (attrs == null) {
if (useUIDs) {
attrs = currentMailbox.getMessageAttributesUID(uid, user);
} else {
attrs = currentMailbox.getMessageAttributes(msn, user);
}
}
if (attrs == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message attributes.");
getLogger().error("Retrieved null attributes for msn:" + msn);
return;
}
if (responseAdded) {
response += SP + "ENVELOPE " + attrs.getEnvelope();
} else {
response += "ENVELOPE " + attrs.getEnvelope();
responseAdded = true;
}
} else if (arg.equalsIgnoreCase("BODY")) {
if (attrs == null) {
if (useUIDs) {
attrs = currentMailbox.getMessageAttributesUID(uid, user);
} else {
attrs = currentMailbox.getMessageAttributes(msn, user);
}
}
if (attrs == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message attributes.");
getLogger().error("Retrieved null attributes for msn:" + msn);
return;
}
if (responseAdded) {
response += SP + "BODY " + attrs.getBodyStructure();
} else {
response += "BODY " + attrs.getBodyStructure();
responseAdded = true;
}
} else if (arg.equalsIgnoreCase("BODYSTRUCTURE")) {
if (attrs == null) {
if (useUIDs) {
attrs = currentMailbox.getMessageAttributesUID(uid, user);
} else {
attrs = currentMailbox.getMessageAttributes(msn, user);
}
}
if (attrs == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message attributes.");
getLogger().error("Retrieved null attributes for msn:" + msn);
return;
}
if (responseAdded) {
response += SP + "BODYSTRUCTURE "+ attrs.getBodyStructure();
} else {
response += "BODYSTRUCTURE "+ attrs.getBodyStructure();
responseAdded = true;
}
} else if (arg.equalsIgnoreCase("UID")) {
if (!useUIDs){
if (attrs == null) {
attrs = currentMailbox.getMessageAttributes(msn, user);
uid = attrs.getUID();
}
if (attrs == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message attributes.");
getLogger().error("Retrieved null attributes for msn:" + msn);
return;
}
if (responseAdded) {
response += SP + "UID "+ uid;
} else {
response += "UID "+ uid;
responseAdded = true;
}
} // don't duplicate on UID FETCH requests
}
// commands that can be satisifed with just top-level headers of message and flags
else if (arg.equalsIgnoreCase("BODY[HEADER]")
|| arg.equalsIgnoreCase("BODY.PEEK[HEADER]")) {
if (responseAdded) { // unlikely
if (useUIDs) {
response += " UID " + uid + ")";
} else {
response += ")";
}
out.println(response);
getLogger().debug("Sending: " + response);
}
InternetHeaders headers = null;
if (useUIDs) {
headers = currentMailbox.getInternetHeadersUID(uid, user);
} else {
headers = currentMailbox.getInternetHeaders(msn, user);
}
if (headers == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message.");
getLogger().error("Retrieved null headers for msn:" + msn);
return;
}
if (flags == null) {
if (useUIDs) {
flags = currentMailbox.getFlagsUID(uid, user);
} else {
flags = currentMailbox.getFlags(msn, user);
}
}
response = UNTAGGED + SP + msn + SP + "FETCH (";
//if (arg.equalsIgnoreCase("BODY[Header]")) {
response += "BODY[HEADER] ";
//} else {
// response += "BODY.PEEK[HEADER] ";
//}
Enumeration enum = headers.getAllHeaderLines();
List lines = new ArrayList();
int count = 0;
while (enum.hasMoreElements()) {
String line = (String)enum.nextElement();
count += line.length() + 2;
lines.add(line);
}
response += "{" + (count + 2) + "}";
out.println(response);
getLogger().debug("Sending: " + response);
Iterator lit = lines.iterator();
while (lit.hasNext()) {
String line = (String)lit.next();
out.println(line);
getLogger().debug("Sending: " + line);
}
out.println();
getLogger().debug("Sending blank line");
if (useUIDs) {
out.println( " UID " + uid + ")");
getLogger().debug("Sending: UID " + uid + ")");
} else {
out.println( ")" );
getLogger().debug("Sending: )");
}
if (! arg.equalsIgnoreCase("BODY.PEEK[HEADER]")) {
try { // around setFlags()
if (flags.indexOf("Seen") == -1 ) {
String newflags;
if (useUIDs) {
currentMailbox.setFlagsUID(uid, user, "+flags (\\Seen)");
newflags = currentMailbox.getFlagsUID(uid, user);
out.println(UNTAGGED + SP + msn + SP + "FETCH (FLAGS "
+ newflags + " UID " + uid +")");
} else {
currentMailbox.setFlags(msn, user, "+flags (\\Seen)");
newflags = currentMailbox.getFlags(msn, user);
out.println(UNTAGGED + SP + msn + SP + "FETCH (FLAGS "
+ newflags + ")");
}
}
} catch (AccessControlException ace) {
getLogger().error("Exception storing flags for message: " + ace);
} catch (AuthorizationException aze) {
getLogger().error("Exception storing flags for message: " + aze);
} catch (Exception e) {
getLogger().error("Unanticipated exception storing flags for message: " + e);
}
}
response = UNTAGGED + SP + msn + SP + "FETCH (";
responseAdded = false;
} else if (arg.toUpperCase().startsWith("BODY[HEADER.FIELDS")
|| arg.toUpperCase().startsWith("BODY.PEEK[HEADER.FIELDS")) {
if (responseAdded) {
if (useUIDs) {
response += " UID " + uid + ")";
} else {
response += ")";
}
out.println(response);
getLogger().debug("Sending: " + response);
}
InternetHeaders headers = null;
if (useUIDs) {
headers = currentMailbox.getInternetHeadersUID(uid, user);
} else {
headers = currentMailbox.getInternetHeaders(msn, user);
}
if (headers == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message.");
getLogger().error("Retrieved null headers for msn:" + msn);
return;
}
if (flags == null) {
if (useUIDs) {
flags = currentMailbox.getFlagsUID(uid, user);
} else {
flags = currentMailbox.getFlags(msn, user);
}
}
boolean not = (commandRaw.toUpperCase().indexOf("HEADER.FIELDS.NOT") != -1);
boolean peek = (commandRaw.toUpperCase().indexOf("PEEK") != -1);
response = UNTAGGED + SP + msn + SP + "FETCH (BODY" ;
//if (peek) {response += ".PEEK";}
if (not) {
response += "[HEADER.FIELDS.NOT (";
} else {
response += "[HEADER.FIELDS (";
}
responseAdded = false;
//int h = commandRaw.indexOf("[");
int left = arg.indexOf("(");
int right = arg.indexOf(")");
String fieldsRequested = arg.substring(left + 1, right);
response += fieldsRequested + ")] ";
ArrayList fields = new ArrayList();
if (fieldsRequested.indexOf(" ") == -1) { //only one field
fields.add(fieldsRequested);
} else {
StringTokenizer tok = new StringTokenizer(fieldsRequested);
while (tok.hasMoreTokens()) {
fields.add((String)tok.nextToken());
}
}
Iterator it2 = fields.iterator();
while (it2.hasNext()) {
getLogger().debug("request for field: " + (String)it2.next());
}
String[] names = (String[])fields.toArray(new String[fields.size()]);
Enumeration enum = null;
if (not) {
enum = headers.getNonMatchingHeaderLines(names);
} else {
enum = headers.getMatchingHeaderLines(names);
}
List lines = new ArrayList();
int count = 0;
while (enum.hasMoreElements()) {
String line = (String)enum.nextElement();
count += line.length() + 2;
lines.add(line);
}
response += "{" + (count + 2) + "}";
out.println(response);
getLogger().debug("Sending: " + response);
Iterator lit = lines.iterator();
while (lit.hasNext()) {
String line = (String)lit.next();
out.println(line);
getLogger().debug("Sending: " + line);
}
out.println();
if (useUIDs) {
out.println( " UID " + uid + ")");
} else {
out.println(")");
}
if (! peek) {
if (flags.indexOf("Seen") == -1 ) {
try {
String newflags;
if (useUIDs) {
currentMailbox.setFlagsUID(uid, user, "+flags (\\Seen)");
newflags = currentMailbox.getFlagsUID(uid, user);
out.println(UNTAGGED + SP + msn + SP + "FETCH (FLAGS "
+ newflags + " UID " + uid +")");
} else {
currentMailbox.setFlags(msn, user, "+flags (\\Seen)");
newflags = currentMailbox.getFlags(msn, user);
out.println(UNTAGGED + SP + msn + SP + "FETCH (FLAGS "
+ newflags + ")");
}
} catch (AccessControlException ace) {
getLogger().error("Exception storing flags for message: " + ace);
} catch (AuthorizationException aze) {
getLogger().error("Exception storing flags for message: " + aze);
} catch (Exception e) {
getLogger().error("Unanticipated exception storing flags for message: " + e);
}
}
}
response = UNTAGGED + SP + msn + SP + "FETCH (";
responseAdded = false;
}
// Calls to underlying MimeMessage
else if (arg.equalsIgnoreCase("RFC822")
|| arg.equalsIgnoreCase("BODY[]")
|| arg.equalsIgnoreCase("BODY.PEEK[]")) {
if (responseAdded) { // unlikely
if (useUIDs) {
response += " UID " + uid + ")";
} else {
response += ")";
}
out.println(response);
}
if (msg == null) { // probably
if (useUIDs) {
msg = currentMailbox.retrieveUID(uid, user);
} else {
msg = currentMailbox.retrieve(msn, user);
}
}
if (flags == null) {
if (useUIDs) {
flags = currentMailbox.getFlagsUID(uid, user);
} else {
flags = currentMailbox.getFlags(msn, user);
}
}
if (msg == null) { // bad
out.println(tag + SP + msn + SP + BAD + "Error retrieving message.");
getLogger().error("Retrieved null message");
return;
}
try {
int size = msg.getMessageSize();
if (arg.equalsIgnoreCase("RFC822")) {
out.println(UNTAGGED + SP + msn + SP + "FETCH ( RFC822 {" + size + "}");
} else {
out.println(UNTAGGED + SP + msn + SP + "FETCH ( BODY[] {" + size + "}");
}
msg.writeTo(outs);
if (useUIDs) {
out.println(" UID " + uid + ")");
} else {
out.println(")");
}
if (! arg.equalsIgnoreCase("BODY.PEEK[]")) {
if (flags.indexOf("Seen") == -1 ) {
String newflags;
if (useUIDs) {
currentMailbox.setFlagsUID(uid, user, "+flags (\\Seen)");
newflags = currentMailbox.getFlagsUID(uid, user);
out.println(UNTAGGED + SP + msn + SP + "FETCH (FLAGS "
+ newflags + " UID " + uid +")");
} else {
currentMailbox.setFlags(msn, user, "+flags (\\Seen)");
newflags = currentMailbox.getFlags(msn, user);
out.println(UNTAGGED + SP + msn + SP + "FETCH (FLAGS "
+ newflags + ")");
}
}
}
} catch (MessagingException me) {
out.println(UNTAGGED + SP + NO + SP + "Error retrieving message");
getLogger().error("Exception retrieving message: " + me);
} catch (IOException ioe) {
out.println(UNTAGGED + SP + NO + SP + "Error retrieving message");
getLogger().error("Exception sending message: " + ioe);
} catch (Exception e) {
out.println(UNTAGGED + SP + NO + SP + "Error retrieving message");
getLogger().error("Unanticipated exception retrieving message: " + e);
}
response = UNTAGGED + SP + msn + SP + "FETCH (";
responseAdded = false;
} else if (arg.equalsIgnoreCase("RFC822.TEXT")
|| arg.equalsIgnoreCase("BODY[TEXT]")
|| arg.equalsIgnoreCase("BODY.PEEK[TEXT]")) {
if (responseAdded) { // unlikely
if (useUIDs) {
response += " UID " + uid + ")";
} else {
response += ")";
}
out.println(response);
}
if (msg == null) { // probably
if (useUIDs) {
msg = currentMailbox.retrieveUID(uid, user);
} else {
msg = currentMailbox.retrieve(msn, user);
}
}
if (flags == null) {
if (useUIDs) {
flags = currentMailbox.getFlagsUID(uid, user);
} else {
flags = currentMailbox.getFlags(msn, user);
}
}
if (msg == null) { // bad
out.println(tag + SP + msn + SP + NO + "Error retrieving message.");
getLogger().error("Retrieved null message");
return;
}
try {
int size = msg.getSize();
if (arg.equalsIgnoreCase("RFC822.TEXT")) {
out.println(UNTAGGED + SP + msn + SP + "FETCH ( RFC822.TEXT {" + size + "}");
} else {
out.println(UNTAGGED + SP + msn + SP + "FETCH ( BODY[TEXT] {" + size + "}");
}
msg.writeContentTo(outs);
if (useUIDs) {
out.println( " UID " + uid + ")");
} else {
out.println(")");
}
if (! arg.equalsIgnoreCase("BODY.PEEK[TEXT]")) {
if (flags.indexOf("Seen") == -1 ) {
String newflags;
if (useUIDs) {
currentMailbox.setFlagsUID(uid, user, "+flags (\\Seen)");
newflags = currentMailbox.getFlagsUID(uid, user);
out.println(UNTAGGED + SP + msn + SP + "FETCH (FLAGS "
+ newflags + " UID " + uid +")");
} else {
currentMailbox.setFlags(msn, user, "+flags (\\Seen)");
newflags = currentMailbox.getFlags(msn, user);
out.println(UNTAGGED + SP + msn + SP + "FETCH (FLAGS "
+ newflags + ")");
}
}
}
} catch (MessagingException me) {
out.println(UNTAGGED + SP + NO + SP + "Error retrieving message");
getLogger().error("Exception retrieving message: " + me);
} catch (IOException ioe) {
out.println(UNTAGGED + SP + NO + SP + "Error retrieving message");
getLogger().error("Exception sending message: " + ioe);
} catch (Exception e) {
out.println(UNTAGGED + SP + NO + SP + "Error retrieving message");
getLogger().error("Unanticipated exception retrieving message: " + e);
}
response = UNTAGGED + SP + msn + SP + "FETCH (";
responseAdded = false;
} else { //unrecognised or not yet implemented
if (responseAdded) {
if (useUIDs) {
response += " UID " + uid + ")";
} else {
response += ")";
}
out.println(response);
}
out.println(tag + SP + NO + SP
+ "FETCH attribute not recognized");
getLogger().error("Received: " + arg + " as argument to fetch");
return;
}
} // end while loop
if (responseAdded) {
if (useUIDs) {
response += " UID " + uid + ")";
} else {
response += ")";
}
out.println(response);
}
} // end for loop
out.println(tag + SP + OK + SP + "FETCH completed");
caller.checkSize();
return;
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox");
caller.logACE(ace);
return;
} catch (AuthorizationException aze) {
out.println(tag + SP + NO + SP
+ "You do not have the rights to read from mailbox: " + currentFolder);
caller.logAZE(aze);
return ;
} catch (Exception e) {
out.println(tag + SP + NO + SP
+ "Unknown server error.");
getLogger().error("Exception expunging mailbox " + currentFolder + " by user " + user + " was : " + e);
if (DEEP_DEBUG) {e.printStackTrace();}
return;
}
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/CommandStore.java
Index: CommandStore.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.*;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
import org.apache.james.core.EnhancedMimeMessage;
/**
* Implements the IMAP FETCH command for a given ImapRequest.
*
* References: rfc 2060, rfc 2193, rfc 2221
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 17 Jan 2001
*/
public class CommandStore
extends BaseCommand {
//mainly to switch on stack traces and catch responses;
private static final boolean DEEP_DEBUG = true;
private static final String OK = "OK";
private static final String NO = "NO";
private static final String BAD = "BAD";
private static final String UNTAGGED = "*";
private static final String SP = " ";
private StringTokenizer commandLine;
private boolean useUIDs;
private ACLMailbox currentMailbox;
private String commandRaw;
private PrintWriter out;
private OutputStream outs;
private String tag;
private String user;
private SingleThreadedConnectionHandler caller;
private String currentFolder;
/**
* Debugging method - will probably disappear
*/
public void setRequest(ImapRequest request) {
commandLine = request.getCommandLine();
useUIDs = request.useUIDs();
currentMailbox = request.getCurrentMailbox();
commandRaw = request.getCommandRaw();
tag = request.getTag();
currentFolder = request.getCurrentFolder();
caller = request.getCaller();
out = caller.getPrintWriter();
outs = caller.getOutputStream();
user = caller.getUser();
}
/**
* Implements IMAP store commands given an ImapRequest.
* <p>Warning - maybecome service(ImapRequest request)
*/
public void service() {
List set;
List uidsList = null;
if (useUIDs) {
set = decodeUIDSet(commandLine.nextToken(),
currentMailbox.listUIDs(user));
} else {
set = decodeSet(commandLine.nextToken(),
currentMailbox.getExists());
}
StringBuffer buf = new StringBuffer();
while (commandLine.hasMoreTokens()) {
buf.append(commandLine.nextToken());
}
String request = buf.toString();
try {
for (int i = 0; i < set.size(); i++) {
if (useUIDs) {
Integer uidObject = (Integer)set.get(i);
int uid = uidObject.intValue();
if (currentMailbox.setFlagsUID(uid, user, request)) {
if (request.toUpperCase().indexOf("SILENT") == -1) {
String newflags
= currentMailbox.getFlagsUID(uid, user);
int msn = uidsList.indexOf(uidObject) + 1;
out.println(UNTAGGED + SP + msn + SP
+ "FETCH (FLAGS " + newflags
+ " UID " + uid + ")");
} else {
//silent
}
} else {
//failed
out.println(tag + SP + NO + SP
+ "Unable to store flags for message: "
+ uid);
}
} else {
int msn = ((Integer)set.get(i)).intValue();
if (currentMailbox.setFlags(msn, user, request)) {
if (request.toUpperCase().indexOf("SILENT") == -1) {
String newflags
= currentMailbox.getFlags(msn, user);
out.println(UNTAGGED + SP + msn + SP
+ "FETCH (FLAGS " + newflags + ")");
} else {
//silent
}
} else {
//failed
out.println(tag + SP + NO + SP
+ "Unable to store flags for message: "
+ msn);
}
}
}
caller.checkSize();
out.println(tag + SP + OK + SP + "STORE completed");
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox");
caller.logACE(ace);
return;
} catch (AuthorizationException aze) {
out.println(tag + SP + NO + SP
+ "You do not have the rights to store those flags");
caller.logAZE(aze);
return;
} catch (IllegalArgumentException iae) {
out.println(tag + SP + BAD + SP
+ "Arguments to store not recognised.");
getLogger().error("Unrecognised arguments for STORE by user " + user
+ " with " + commandRaw);
return;
}
return;
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/DefaultRecordRepository.java
Index: DefaultRecordRepository.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.*;
import java.util.*;
import org.apache.log.LogKit;
import org.apache.log.Logger;
/**
* Implementation of a RecordRepository on a FileSystem.
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
* @see RecordRepository
*/
public class DefaultRecordRepository implements RecordRepository {
private String path;
private File repository;
private Logger logger = LogKit.getLoggerFor("james.mailstore");
public void setPath(final String rootPath) {
if (path != null) {
throw new RuntimeException("Error: Attempt to reset AvalonRecordRepository");
}
path = rootPath;
repository = new File(rootPath);
if (!repository.isDirectory()) {
if (! repository.mkdirs()){
throw new RuntimeException("Error: Cannot create directory for AvalonRecordRepository at: " + rootPath);
}
} else if (!repository.canWrite()) {
throw new RuntimeException("Error: Cannot write to directory for AvalonRecordRepository at: " + rootPath);
}
}
public synchronized void store( final FolderRecord fr) {
ObjectOutputStream out = null;
try {
String key = path + File.separator + fr.getAbsoluteName();
out = new ObjectOutputStream( new FileOutputStream(key) );
out.writeObject(fr);
out.close();
logger.info("Record stored for: " + fr.getAbsoluteName());
notifyAll();
} catch (Exception e) {
if (out != null) {
try {
out.close();
} catch (Exception ignored) {
}
}
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Folder Record: " + e);
}
}
public synchronized Iterator getAbsoluteNames() {
String[] names = repository.list();
return Collections.unmodifiableList(Arrays.asList(names)).iterator();
}
public synchronized FolderRecord retrieve(final String folderAbsoluteName) {
FolderRecord fr = null;
ObjectInputStream in = null;
try {
String key = path + File.separator + folderAbsoluteName;
in = new ObjectInputStream( new FileInputStream(key) );
fr = (FolderRecord) in.readObject();
in.close();
} catch (Exception e) {
if (in != null) {
try {
in.close();
} catch (Exception ignored) {
}
}
e.printStackTrace();
throw new
RuntimeException("Exception caught while reading Folder Record: " + e);
} finally {
notifyAll();
}
return fr;
}
public boolean containsRecord(String folderAbsoluteName) {
File testFile = new File(repository, folderAbsoluteName);
return testFile.exists();
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/FileMailbox.java
Index: FileMailbox.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
import org.apache.james.Constants;
import org.apache.james.core.EnhancedMimeMessage;
import org.apache.james.services.UsersRepository;
import org.apache.log.LogKit;
import org.apache.log.Logger;
import org.apache.mailet.Mail;
/**
* Object representing an IMAP4rev1 mailbox (folder) on a local file system.
* The mailbox contains messages, message attributes and an access control
* list.
*
* <p> from Interface Mailbox
*
* <p>Mailbox Related Flags (rfc2060 name attributes)
* <br> \Noinferiors It is not possible for any child levels of hierarchy
* to exist under this name; no child levels exist now and none can be created
* in the future.
* <br> \Noselect It is not possible to use this name as a selectable
* mailbox.
* <br> \Marked The mailbox has been marked "interesting" by the
* server; the mailbox probably contains messages that have been added since
* the last time the mailbox was selected.
* <br> \Unmarked The mailbox does not contain any additional
* messages since the last time the mailbox was selected.
*
* <p>Message related flags.
* <br>The flags allowed per message are specific to each mailbox.
* <br>The minimum list (rfc2060 system flags) is:
* <br> \Seen Message has been read
* <br> \Answered Message has been answered
* <br> \Flagged Message is "flagged" for urgent/special attention
* <br> \Deleted Message is "deleted" for removal by later EXPUNGE
* <br> \Draft Message has not completed composition (marked as a draft).
* <br> \Recent Message is "recently" arrived in this mailbox. This
* session is the first session to have been notified about this message;
* subsequent sessions will not see \Recent set for this message. This flag
* can not be altered by the client.
* <br> If it is not possible to determine whether or not this
* session is the first session to be notified about a message, then that
* message SHOULD be considered recent.
*
* <p> From interface ACL </p>
*
* The standard rights in RFC2086 are:
* <br>l - lookup (mailbox is visible to LIST/LSUB commands)
* <br>r - read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL, SEARCH,
* COPY from mailbox)
* <br>s - keep seen/unseen information across sessions (STORE SEEN flag)
* <br>w - write (STORE flags other than SEEN and DELETED)
* <br>i - insert (perform APPEND, COPY into mailbox)
* <br>p - post (send mail to submission address for mailbox, not enforced by
* IMAP4 itself)
* <br>c - create (CREATE new sub-mailboxes in any implementation-defined
* hierarchy)
* <br>d - delete (STORE DELETED flag, perform EXPUNGE)
* <br>a - administer (perform SETACL)
*
* <p> Serialization. Not all fields are serialized. Dispose() must be called to
* write serialized fiels to disc before finishing with this object.
*
* <p> Deserialization. On recover from disc, configure, compose,
* contextualize and reInit must be called before object is ready for use.
*
* Reference: RFC 2060
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class FileMailbox
implements ACLMailbox, Serializable {
public static final String MAILBOX_FILE_NAME = "mailbox.mbr";
private static final String MESSAGE_EXTENSION = ".msg";
private static final String ATTRIBUTES_EXTENSION = ".att";
private static final String FLAGS_EXTENSION = ".flags";
private static final int NUMBER_OF_RIGHTS = 9;
// Map string identities to boolean[NUMBER_OF_RIGHTS] arrays of rights
//The rights are, in order: l,r,s,w,i,p,c,d,a
private static final int LOOKUP = 0;
private static final int READ = 1;
private static final int KEEP_SEEN = 2;
private static final int WRITE = 3;
private static final int INSERT = 4;
private static final int POST = 5;
private static final int CREATE = 6;
private static final int DELETE = 7;
private static final int ADMIN = 8;
private static final boolean[] NO_RIGHTS
= {false, false, false, false, false, false, false, false, false};
private static final boolean[] ALL_RIGHTS
= {true, true, true, true, true, true, true, true, true};
private static final String DENY_ACCESS = "Access denied by ACL";
private static final String DENY_AUTH = "Action not authorized for: ";
private static final String OPTIONAL_RIGHTS = "l r s w i p c d a";
private static final char[] DELETE_MODS
= {'-', 'l', 'r', 's', 'w', 'i', 'p', 'c', 'd', 'a'};
/* Transient fields - reInit must be called on recover from disc. */
private transient Context context;
private transient Configuration conf;
private transient ComponentManager compMgr;
private transient Logger logger = LogKit.getLoggerFor("james.MailRepository");
private transient UsersRepository localUsers;
private transient HashSet listeners;
/* Serialized fileds */
private String path; // does not end with File.separator
private File directory ;
private String owner;
private String absoluteName;
private Map acl;
private String name;
private int uidValidity;
private int highestUID;
private int mailboxSize; // octets
private boolean inferiorsAllowed;
private boolean marked;
private boolean notSelectableByAnyone;
// The message sequence number of a msg is its index in List sequence + 1
private List sequence; //List of UIDs of messages in mailbox
// Set of UIDs of messages with recent flag set
private Set recentMessages;
// Set of UIDs of messages with delete flag set
private Set messagesForDeletion;
//map of user String to Integer uid, 0 for no unseen messages
private Map oldestUnseenMessage;
public void configure(Configuration conf) throws ConfigurationException {
this.conf = conf;
}
public void contextualize(Context context) {
this.context = context;
}
public void compose(ComponentManager comp) {
compMgr = comp;
}
public void prepareMailbox(String user, String absName,
String initialAdminUser) {
if (user != null && (user.length() > 0)) {
owner = user;
} else {
throw new RuntimeException("Incorrect user argument for a"
+ " FileMailbox constructor.");
}
if (absName != null && (absName.length() > 0)) {
absoluteName = absName;
} else {
throw new RuntimeException("Incorrect absoluteName argument for a"
+ " FileMailbox constructor.");
}
if (initialAdminUser != null && (initialAdminUser.length() > 0)) {
acl = new HashMap(7);
acl.put(initialAdminUser, ALL_RIGHTS);
//acl = new SimpleACL(initialAdminUser);
} else {
throw new RuntimeException("Incorrect initialAdminUser argument"
+ " for a FileMailbox constructor.");
}
}
public void initialize() throws Exception {
uidValidity = 1;
highestUID = 0;
mailboxSize = 0;
inferiorsAllowed = true;
marked = false;
notSelectableByAnyone = false;
oldestUnseenMessage = new HashMap();
listeners = new HashSet();
sequence = new ArrayList();
recentMessages = new HashSet();
messagesForDeletion = new HashSet();
logger.info("FileMailbox init for " + absoluteName);
localUsers = (UsersRepository)compMgr.lookup("org.apache.james.services.UsersRepository");
String rootPath
= conf.getChild("mailboxRepository").getValue();
if (!rootPath.endsWith(File.separator)) {
rootPath = rootPath + File.separator;
}
Configuration namespaces = conf.getChild("namespaces");
String namespaceToken = namespaces.getAttribute("token");
String privateNamespace
= namespaces.getChild("privateNamespace").getValue();
String privateNamespaceSeparator
= namespaces.getChild("privateNamespace").getAttribute("separator");
String sharedNamespace
= namespaces.getChild("sharedNamespace").getValue();
String sharedNamespaceSeparator
= namespaces.getChild("sharedNamespace").getAttribute("separator");
if (absoluteName.startsWith(privateNamespace)) {
String path1
= absoluteName.substring(privateNamespace.length()
+ privateNamespaceSeparator.length()
+ owner.length());
path = rootPath + owner
+ path1.replace(privateNamespaceSeparator.charAt(0),
File.separatorChar);
name = absoluteName.substring(absoluteName.lastIndexOf(privateNamespaceSeparator) + 1);
if (name.equals(owner)) {
name = "";
}
} else if (absoluteName.startsWith(sharedNamespace)) {
String path2 = absoluteName.substring(namespaceToken.length());
path = rootPath + path2.replace(sharedNamespaceSeparator.charAt(0),
File.separatorChar);
name = absoluteName.substring(absoluteName.lastIndexOf(sharedNamespaceSeparator) + 1);
if (name.equals(sharedNamespace)) {
name = "";
}
} else {
logger.error("FileMailbox init error: unknown namespace - "
+ absoluteName);
throw new RuntimeException("Unknown namespace for absoluteName"
+" argument for a FileMailbox"
+" constructor." + absoluteName);
}
//Check for writable directory
File mailboxDir = new File(path);
if (mailboxDir.exists()) {
throw new RuntimeException("Error: Attempt to overwrite mailbox directory at " + path);
} else if (! mailboxDir.mkdir()){
throw new RuntimeException("Error: Cannot create mailbox directory at " + path);
} else if (!mailboxDir.canWrite()) {
throw new RuntimeException("Error: Cannot write to directory at " + path);
}
writeMailbox();
logger.info("FileMailbox init complete: " + absoluteName);
}
/**
* Re-initialises mailbox after reading from file-system. Cannot assume that this is the same instance of James that wrote it.
*
* <p> Contract is that re-init must be called after configure, contextualize, compose.
*/
public void reinitialize() throws Exception {
listeners = new HashSet();
logger = LogKit.getLoggerFor("james.MailRepository");
logger.info("FileMailbox reInit for " + absoluteName);
localUsers = (UsersRepository)compMgr.lookup("org.apache.james.services.UsersRepository");
String rootPath
= conf.getChild("mailboxRepository").getValue();
if (!rootPath.endsWith(File.separator)) {
rootPath = rootPath + File.separator;
}
}
/**
* Call when host has finished with a mailbox.
* This is really a stop rather than destroy.
* Writes current mailbox object to disc.
*/
public void dispose() throws Exception {
writeMailbox();
logger.info("FileMailbox object destroyed: " + absoluteName);
}
/**
* Returns true once this Mailbox has been checkpointed.
* This implementation just writes its mailbox record to disc. Unless something is
* broken all messages added, amended or removed from this mailbox will have been
* handled by this object.
* <br> This implementation always returns true.
*
* @returns true
*/
public synchronized boolean checkpoint() {
writeMailbox();
logger.info("FileMailbox: " + absoluteName + " checkpointed.");
return true;
}
/**
* Remove \Recent flag from all messages in mailbox. Should be called
* whenever a user session finishes.
*/
public synchronized void unsetRecent() {
Iterator it = recentMessages.iterator();
while(it.hasNext()) {
Integer uidObj =(Integer)it.next();
int uid = uidObj.intValue();
Flags flags = readFlags(uid);
if (flags != null) {
flags.setRecent(false);
writeFlags(uid, flags);
}
}
recentMessages.clear();
}
// Methods that don't involve the ACL ------------------
/**
* Returns name of this mailbox relative to its parent in the mailbox
* hierarchy.
* Example: 'NewIdeas'
*
* @returns String name of mailbox relative to its immeadiate parent in
* the mailbox hierarchy.
*/
public String getName() {
return name;
}
/**
* Returns absolute, that is user-independent, hierarchical name of
* mailbox (including namespace)
* Example: '#mail.fred.flintstone.apache.James.NewIdeas'
*
* @returns String name of mailbox in absolute form
*/
public String getAbsoluteName() {
return absoluteName;
}
/** Returns namespace starting with namespace token.
* Example: '#mail'
*
* @returns String containing user-independent namespace of this mailbox.
*/
// public String getNamespace();
/** Returns true if the argument is the relative or absolute name of
* this mailbox
*
* @param name possible name for this Mailbox
* @returns true if name matches either getName() or getAbsoluteName()
*/
public boolean matchesName(String testName) {
return (name == testName || name == absoluteName);
}
/**
* Returns the current unique id validity value of this mailbox.
*
* @returns int current 32 bit unique id validity value of this mailbox
*/
public int getUIDValidity() {
return uidValidity;
}
/**
* Returns the 32 bit uid available for the next message.
*
* @returns int the next UID that would be used.
*/
public int getNextUID() {
return highestUID + 1;
}
/**
* Returns mailbox size in octets. Should only include actual messages
* and not any implementation-specific data, such as message attributes.
*
* @returns int mailbox size in octets
*/
public synchronized int getMailboxSize() {
return mailboxSize;
}
/**
* Indicates if child folders may be created. It does not indicate which
* users can create child folders.
*
* @returns boolean TRUE if inferiors aree allowed
*/
public boolean getInferiorsAllowed() {
return inferiorsAllowed;
}
/**
* Indicates that messages have been added since this mailbox was last
* selected by any user.
*
* @returns boolean TRUE if new messages since any user last selected
* mailbox
*/
public synchronized boolean isMarked() {
return marked;
}
/**
* Returns all flags supported by this mailbox.
* e.g. \Answered \Deleted
*
* @returns String a space seperated list of message flags which are
* supported by this mailbox.
*/
public String getSupportedFlags() {
return SYSTEM_FLAGS;
}
/**
* Indicates no of messages with \Recent flag set
*
* @returns int no of messages with \Recent flag set
*/
public synchronized int getRecent() {
return recentMessages.size();
}
/**
* Indicates the oldest unseen message for the specified user.
*
* @returns int Message Sequence Number of first message without \Seen
* flag set for this User. 0 means no unseen messages in this mailbox.
*/
public synchronized int getOldestUnseen(String user) {
int response = 0;
if (oldestUnseenMessage.containsKey(user)) {
Integer uidObj = ((Integer)oldestUnseenMessage.get(user));
if (! (uidObj.intValue() == 0)) {
response = sequence.indexOf(uidObj);
}
} else {
if (sequence.size() > 0) {
response = 1;
oldestUnseenMessage.put(user, (Integer)sequence.get(0));
} else {
oldestUnseenMessage.put(user, (new Integer(0)));
}
}
return response;
}
/**
* Indicates number of messages in folder
*
* @returns int number of messages
*/
public synchronized int getExists() {
return sequence.size();
}
/**
* Indicates the number of unseen messages for the specified user.
*
* @returns int number of messages without \Seen flag set for this User.
*/
public int getUnseen(String user) {
if (oldestUnseenMessage.containsKey(user)) {
int response = 0; //indicates no unseen messages
Integer uidObj = ((Integer)oldestUnseenMessage.get(user));
int oldUID = uidObj.intValue();
if (oldUID != 0) {
ListIterator lit
= sequence.listIterator(sequence.indexOf(uidObj));
while (lit.hasNext() ) {
int uid = ((Integer)lit.next()).intValue();
Flags flags = readFlags(uid);
if (!flags.isSeen(user)) {
response ++;
}
}
}
return response;
} else { // user has never selected mailbox
return sequence.size();
}
}
/** Mailbox Events are used to inform registered listeners of events in the Mailbox.
* E.g. if mail is delivered to an Inbox or if another user appends/ deletes a message.
*/
public synchronized void addMailboxEventListener(MailboxEventListener mel) {
listeners.add(mel);
}
public synchronized void removeMailboxEventListener(MailboxEventListener mel) {
listeners.remove(mel);
}
/**
* Mark this mailbox as not selectable by anyone.
* Example folders at the roots of hierarchies, e. #mail for each user.
*
* @param state true if folder is not selectable by anyone
*/
public void setNotSelectableByAnyone(boolean state) {
notSelectableByAnyone = state;
}
public boolean isNotSelectableByAnyone() {
return notSelectableByAnyone;
}
// Methods for the embedded ACL ------------------------
/**
* Store access rights for a given identity.
* The setter is the user setting the rights, the identifier is the user
* whose rights are affected.
* The setter and identifier arguments must be non-null and non-empty.
* The modification argument must be non-null and follow the syntax of the
* third argument to a SETACL command.
* If the modification argument is an empty string, that identifier is
* removed from the ACL, if currently present.
*
* @param setter String representing user attempting to set rights, must
* be non-null and non-empty
* @param identity String representing user whose rights are being set,
* must be non-null and non-empty
* @param modification String representing the change in rights, following
* the syntax specified in rfc 2086. Note a blank string means delete all
* rights for given identity.
* @returns true if requested modification succeeded. A return value of
* false means an error other than an AccessControlException or
* AuthorizationException.
* @throws AccessControlException if setter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if specified setter does not have the
* administer right (ie the right to write ACL rights), or if the result
* of this method would leave no identities with admin rights.
*/
public boolean setRights(String setter, String identifier,
String modification)
throws AccessControlException, AuthorizationException {
boolean[] settersRights = (boolean[]) acl.get(setter);
if (settersRights == null
|| (settersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if (settersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + setter);
}
boolean[] existingRights = (boolean[]) acl.get(identifier);
char[] mods = modification.toCharArray();
if (mods.length == 0) { // means delete all
mods = DELETE_MODS;
}
if(existingRights == null) {
if ( mods[0] == REMOVE_RIGHTS ) {
return false;
} else {
existingRights = new boolean[NUMBER_OF_RIGHTS];
System.arraycopy(NO_RIGHTS, 0, existingRights, 0,
NUMBER_OF_RIGHTS);
}
}
boolean change;
boolean[] rights = new boolean[NUMBER_OF_RIGHTS];
if (mods[0] == ADD_RIGHTS) {
change = true;
System.arraycopy(existingRights, 0, rights, 0,
NUMBER_OF_RIGHTS);
} else if (mods[0] == REMOVE_RIGHTS) {
change = false;
System.arraycopy(existingRights, 0, rights, 0,
NUMBER_OF_RIGHTS);
} else { // means replace
System.arraycopy(NO_RIGHTS, 0, rights, 0,
NUMBER_OF_RIGHTS);
char[] new_mods = new char[mods.length + 1];
System.arraycopy(mods, 0, new_mods, 1, mods.length);
mods = new_mods;
change = true;
}
for (int i=1; i <mods.length; i++) {
switch(mods[i]) {
case LOOKUP_RIGHTS: rights[LOOKUP] = change;
break;
case READ_RIGHTS: rights[READ] = change;
break;
case KEEP_SEEN_RIGHTS: rights[KEEP_SEEN] = change;
break;
case WRITE_RIGHTS: rights[WRITE] = change;
break;
case INSERT_RIGHTS: rights[INSERT] = change;
break;
case POST_RIGHTS: rights[POST] = change;
break;
case CREATE_RIGHTS: rights[CREATE] = change;
break;
case DELETE_RIGHTS: rights[DELETE] = change;
break;
case ADMIN_RIGHTS: rights[ADMIN] = change;
break;
default: return false;
}
}
// All rights above lookup require lookup
if(rights[LOOKUP] == false && !Arrays.equals(rights, NO_RIGHTS)) {
return false;
}
// Each right requires all the rights before it.
int count = 0;
for (int i=1; i< NUMBER_OF_RIGHTS; i++) {
if(rights[i-1] ^ rights[i]) {
count++;
}
}
switch (count) {
case 0: // now Admin or deleted
if (rights[ADMIN]) {
acl.put(identifier, rights);
break;
} else {
if (otherAdmin(identifier)) {
acl.remove(identifier);
break;
} else {
return false;
}
}
case 2: // not allowed
return false;
case 1: // not Admin, check there remains an Admin
// Iterator namesIt = acl.keySet().iterator();
//boolean otherAdmin = false;
//while(namesIt.hasNext() && !otherAdmin) {
//String name = (String)namesIt.next();
//if (name != identifier) {
// boolean[] otherRights = (boolean[]) acl.get(name);
// otherAdmin = otherRights[ADMIN];
//}
//}
if (otherAdmin(identifier)) {
acl.put(identifier, rights);
break;
} else {
return false;
}
default: // not allowed
return false;
}
writeMailbox();
return true;
}
/**
* Check there is a person other than identifier who has Admin rights.
*/
private boolean otherAdmin(String identifier) {
Iterator namesIt = acl.keySet().iterator();
boolean result = false;
while(namesIt.hasNext() && !result) {
String name = (String)namesIt.next();
if (!name.equals(identifier)) {
boolean[] otherRights = (boolean[]) acl.get(name);
result = otherRights[ADMIN];
}
}
return result;
}
/**
* Retrieve access rights for a specific identity.
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
public String getRights(String getter, String identity)
throws AccessControlException, AuthorizationException {
boolean[] gettersRights = (boolean[]) acl.get(getter);
if (gettersRights == null
|| (gettersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if (!getter.equals(identity) && gettersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + getter);
}
boolean[] rights = (boolean[]) acl.get(identity);
if (rights == null) {
return null;
} else {
StringBuffer buf = new StringBuffer(NUMBER_OF_RIGHTS);
for (int i = 0; i<NUMBER_OF_RIGHTS; i++) {
if (rights[i]) {
buf.append(RIGHTS[i]);
}
}
return buf.toString();
}
}
/**
* Retrieves a String of one or more <identity space rights> who have
* rights in this ACL
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @returns String of rights sets usingrfc2086 syntax
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL to this getter.
*/
public String getAllRights(String getter)
throws AccessControlException, AuthorizationException {
boolean[] gettersRights = (boolean[]) acl.get(getter);
if (gettersRights == null
|| (gettersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if ( gettersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + getter);
}
Iterator namesIt = acl.keySet().iterator();
StringBuffer response = new StringBuffer(20*acl.size());
while(namesIt.hasNext()) {
String name = (String)namesIt.next();
response.append("<" + name + " ");
boolean[] rights = (boolean[]) acl.get(name);
for (int i = 0; i<NUMBER_OF_RIGHTS; i++) {
if (rights[i]) {
response.append(RIGHTS[i]);
}
}
response.append("> ");
}
return response.toString();
}
/**
* Retrieve rights which will always be granted to the specified identity.
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* guaranteed rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
public String getRequiredRights(String getter, String identity)
throws AccessControlException, AuthorizationException {
boolean[] gettersRights = (boolean[]) acl.get(getter);
if (gettersRights == null
|| (gettersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if (!getter.equals(identity) && gettersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + getter);
}
return "\"\"";
}
/**
* Retrieve rights which may be granted to the specified identity.
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* guaranteed rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
public String getOptionalRights(String getter, String identity)
throws AccessControlException, AuthorizationException {
boolean[] gettersRights = (boolean[]) acl.get(getter);
if (gettersRights == null
|| (gettersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if (!getter.equals(identity) && gettersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + getter);
}
return OPTIONAL_RIGHTS;
}
/**
* Helper boolean methods.
* Provided for cases where you need to check the ACL before selecting the
* mailbox.
*
* @param username String representing user
* @returns true if user has the requested right.
* &throws AccessControlException if username does not have lookup rights.
* (Except for hasLookupRights which just returns false.
*/
public boolean hasLookupRights(String username) {
boolean[] usersRights = (boolean[]) acl.get(username);
return (( usersRights == null || (usersRights[LOOKUP] == false))
? false : true);
}
public boolean hasReadRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[READ];
}
public boolean hasKeepSeenRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[KEEP_SEEN];
}
public boolean hasWriteRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[WRITE];
}
public boolean hasInsertRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[INSERT];
}
public boolean hasCreateRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[CREATE];
}
public boolean hasDeleteRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[DELETE];
}
public boolean hasAdminRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[ADMIN];
}
// Mailbox methods using the ACL ---------------------------
/**
* Indicates if this folder may be selected by the specified user. Requires
* user to have at least read rights. It does not indicate whether user
* can write to mailbox
*
* @param username String represnting user
* @returns boolean TRUE if specified user can Select mailbox.
* @throws AccessControlException if username does not have lookup rights
*/
public synchronized boolean isSelectable(String username)
throws AccessControlException {
return (!notSelectableByAnyone && hasReadRights(username));
}
/**
* Indicates if specified user can change any flag on a permanent basis,
* except for \Recent which can never be changed by a user.
*
* @param username String represnting user
* @returns true if specified user can change all flags permanently.
*/
public synchronized boolean allFlags(String username)
throws AccessControlException {
// relies on implementation that each right implies those
// before it in list: l,r,s,w,i,p,c,d,a
return hasDeleteRights(username);
}
/**
* Indicates which flags this user can change permanently. If allFlags()
* returns true for this user, then this method must have the same return
* value as getSupportedFlags.
*
* @param username String represnting user
* @returns String a space seperated list of message flags which this user
* can set permanently
*/
public synchronized String getPermanentFlags(String username)
throws AccessControlException {
if (hasDeleteRights(username)) {
return SYSTEM_FLAGS;
} else if (hasWriteRights(username)) {
return "\\Seen \\Answered \\Flagged \\Draft";
} else if (hasKeepSeenRights(username)) {
return "\\Seen";
} else {
return "";
}
}
/**
* Provides a reference to the access control list for this mailbox.
*
* @returns the AccessControlList for this Mailbox
*/
// public ACL getACL();
/**
* Indicates state in which the mailbox will be opened by specified user.
* A return value of true indicates Read Only, false indicates Read-Write
* and an AccessControlException is thrown if user does not have read
* rights.
* <p>Implementations decide if Read Only means only lookup and read
* rights (lr) or lookup, read and keep seen rights (lrs). This may even
* vary between mailboxes.
*
* @param username String represnting user
* @returns true if specified user can only open the mailbox Read-Only.
* @throws AccessControlException if the user can not open this mailbox
* at least Read-Only.
*/
public synchronized boolean isReadOnly(String username)
throws AccessControlException {
return (! hasWriteRights(username));
}
// Message handling methods ---------------------------
/**
* Stores a message in this mailbox. User must have insert rights.
*
* @param message the MimeMessage to be stored
* @param username String represnting user
* @returns boolean true if successful
* @throws AccessControlException if username does not have lookup rights
* for this mailbox.
* @throws AuthorizationException if username has lookup rights but does
* not have insert rights.
*/
public synchronized boolean store(MimeMessage message, String username)
throws AccessControlException, AuthorizationException,
IllegalArgumentException {
if (message == null || username == null) {
logger.error("Null argument received in store.");
throw new IllegalArgumentException("Null argument received in store.");
}
if (!hasInsertRights(username)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to insert.");
}
SimpleMessageAttributes attrs = new SimpleMessageAttributes();
try {
attrs.setAttributesFor(message);
} catch (javax.mail.MessagingException me) {
throw new RuntimeException("Exception creating SimpleMessageAttributes: " + me);
}
Flags flags = new Flags();
flags.initialize();
return store(message, username, attrs, flags);
}
/**
* Stores a message in this mailbox, using passed MessageAttributes and
* Flags. User must have insert rights.
* <br>Current implementation requires MessageAttributs to be of
* class SimpleMessageAttributes
*
* @param mail the message to be stored
* @param username String represnting user
* @param msgAttrs non-null MessageAttributes for use with this Message
* @returns boolean true if successful
* @throws AccessControlException if username does not have lookup
* rights for this mailbox.
* @throws AuthorizationException if username has lookup rights but does
* not have insert rights.
*/
public boolean store(MimeMessage message, String username,
MessageAttributes msgAttrs, Flags flags)
throws AccessControlException, AuthorizationException,
IllegalArgumentException {
if (msgAttrs == null || message == null || username == null) {
logger.error("Null argument received in store.");
throw new IllegalArgumentException("Null argument received in store.");
}
if (! (msgAttrs instanceof SimpleMessageAttributes)) {
logger.error("Wrong class for Attributes");
throw new IllegalArgumentException("Wrong class for Attributes");
}
SimpleMessageAttributes attrs = (SimpleMessageAttributes)msgAttrs;
int newUID = ++highestUID;
attrs.setUID(newUID);
sequence.add(new Integer(newUID));
attrs.setMessageSequenceNumber(sequence.size());
BufferedOutputStream outMsg = null;
ObjectOutputStream outAttrs = null;
try {
outMsg = new BufferedOutputStream( new FileOutputStream(path + File.separator + newUID + MESSAGE_EXTENSION));
message.writeTo(outMsg);
outMsg.close();
outAttrs = new ObjectOutputStream( new FileOutputStream(path + File.separator + newUID + ATTRIBUTES_EXTENSION));
outAttrs.writeObject(attrs);
outAttrs.close();
} catch(Exception e) {
logger.error("Error writing message to disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Mail: "
+ e);
} finally {
try {
outMsg.close();
outAttrs.close();
} catch (IOException ie) {
logger.error("Error closing streams: " + ie);
}
}
marked = true;
if (flags.isRecent()) {
recentMessages.add(new Integer(newUID));
}
if (flags.isDeleted()) {
messagesForDeletion.add(new Integer(newUID));
}
//if (!flags.isSeen(username)) {
//If a user had no unseen messages, they do, now.
Iterator it = oldestUnseenMessage.keySet().iterator();
while (it.hasNext()) {
String user = (String)it.next();
if ( ((Integer)oldestUnseenMessage.get(user)).intValue() == -1) {
oldestUnseenMessage.put(user, new Integer(newUID));
}
}
//}
writeFlags(newUID, flags);
logger.info("Mail " + newUID + " written in " + absoluteName);
return true;
}
/**
* Retrieves a message given a message sequence number.
*
* @param msn the message sequence number
* @param username String represnting user
* @returns an EnhancedMimeMessage object containing the message, null if no message with
* the given msn.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
public synchronized EnhancedMimeMessage retrieve(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (msn > sequence.size()) {
return null;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return retrieveUID(uid, user);
}
}
/**
* Retrieves a message given a unique identifier.
*
* @param uid the unique identifier of a message
* @param username String represnting user
* @returns an EnhancedMimeMessage object containing the message, null if no message with
* the given msn.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
public synchronized EnhancedMimeMessage retrieveUID(int uid, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
EnhancedMimeMessage response = null;
if (sequence.contains(new Integer(uid))) {
BufferedInputStream inMsg = null;
try {
inMsg = new BufferedInputStream( new FileInputStream(path + File.separator + uid + MESSAGE_EXTENSION));
response = new EnhancedMimeMessage(Session.getDefaultInstance(System.getProperties(), null),inMsg);
inMsg.close();
} catch(Exception e) {
logger.error("Error reading message from disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while retrieving Mail: "
+ e);
} finally {
try {
inMsg.close();
} catch (IOException ie) {
logger.error("Error closing streams: " + ie);
}
}
logger.info("EnhancedMimeMessage " + uid + " read from " + absoluteName);
return response;
} else {
return null;
}
}
/**
* Marks a message for deletion given a message sequence number.
*
* @param msn the message sequence number
* @param username String represnting user
* @returns boolean true if successful.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized boolean markDeleted(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasDeleteRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to delete.");
}
//TBD
return false;
}
/**
* Marks a message for deletion given a unique identifier.
*
* @param uidunique identifier
* @param username String represnting user
* @returns boolean true if successful, false if failed including no
* message with the given uid.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized boolean markDeletedUID(int uid, String user)
throws AccessControlException, AuthorizationException {
if (!hasDeleteRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to delete.");
}
//TBD
return false;
}
/**
* Returns the message attributes for a message.
*
* @param msn message sequence number
* @param username String represnting user
* @returns MessageAttributes for message, null if no such message.
* Changing the MessageAttributes object must not affect the actual
* MessageAttributes.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized MessageAttributes getMessageAttributes(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (msn > sequence.size()) {
return null;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return getMessageAttributesUID(uid, user);
}
}
/**
* Returns the message attributes for a message.
*
* @param uid unique identifier
* @param username String represnting user
* @returns MessageAttributes for message, null if no such message.
* Changing the MessageAttributes object must not affect the actual
* MessageAttributes.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized MessageAttributes getMessageAttributesUID(int uid, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
SimpleMessageAttributes response = null;
if (sequence.contains(new Integer(uid))) {
ObjectInputStream inAttrs = null;
try {
inAttrs = new ObjectInputStream( new FileInputStream(path + File.separator + uid + ATTRIBUTES_EXTENSION));
response = (SimpleMessageAttributes)inAttrs.readObject();
response.reinitialize();
} catch(Exception e) {
logger.error("Error reading attributes from disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while retrieving Message attributes: "
+ e);
} finally {
try {
inAttrs.close();
} catch (IOException ie) {
logger.error("Error closing streams: " + ie);
}
}
logger.info("MessageAttributes for " + uid + " read from " + absoluteName);
return response;
} else {
return null;
}
}
/**
* Updates the attributes of a message.This may be incorporated into setFlags().
*
* @param MessageAttributes of a message already in this Mailbox
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public boolean updateMessageAttributes(MessageAttributes attrs, String user)
throws AccessControlException, AuthorizationException {
if (!hasKeepSeenRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to store flags.");
}
int uid = attrs.getUID();
if (sequence.contains(new Integer(uid))) {
// Really, we should check whether the exact change is authorized.
ObjectOutputStream outAttrs = null;
try {
outAttrs = new ObjectOutputStream( new FileOutputStream(path + File.separator + uid + ATTRIBUTES_EXTENSION));
outAttrs.writeObject(attrs);
outAttrs.close();
} catch(Exception e) {
logger.error("Error writing message to disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Attributes: "
+ e);
} finally {
try {
outAttrs.close();
} catch (IOException ie) {
logger.error("Error closing streams: " + ie);
}
}
logger.info("MessageAttributes for " + uid + " written in " + absoluteName);
return true;
} else {
return false;
}
}
/**
* Get the IMAP-formatted String of flags for specified message.
*
* @param msn message sequence number for a message in this mailbox
* @param username String represnting user
* @returns flags for this message and user, null if no such message.
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
public synchronized String getFlags(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (msn > sequence.size()) {
return null;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return getFlagsUID(uid, user);
}
}
/**
* Get the IMAP-formatted String of flags for specified message.
*
* @param uid UniqueIdentifier for a message in this mailbox
* @param username String represnting user
* @returns flags for this message and user, null if no such message.
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
public synchronized String getFlagsUID(int uid, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (!sequence.contains(new Integer(uid))) {
return null;
} else {
Flags flags = readFlags(uid);
return flags.getFlags(user);
}
}
/**
* Updates the flags for a message.
*
* @param msn MessageSequenceNumber of a message already in this Mailbox
* @param username String represnting user
* @param request IMAP-formatted String representing requested change to
* flags.
* @returns true if succeeded, false otherwise, including no such message
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized boolean setFlags(int msn, String user, String request)
throws AccessControlException, AuthorizationException,
IllegalArgumentException {
if (!hasKeepSeenRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to store any flags.");
}
if (msn > sequence.size()) {
return false;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return setFlagsUID(uid, user, request);
}
}
/**
* Updates the flags for a message.
*
* @param uid Unique Identifier of a message already in this Mailbox
* @param username String represnting user
* @param request IMAP-formatted String representing requested change to
* flags.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized boolean setFlagsUID(int uid, String user, String request)
throws AccessControlException, AuthorizationException,
IllegalArgumentException {
if (!hasKeepSeenRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to store any flags.");
}
if ((request.toUpperCase().indexOf("DELETED") != -1) && (!hasDeleteRights(user))) { //throws AccessControlException
throw new AuthorizationException("Not authorized to delete.");
}
if (sequence.contains(new Integer(uid))) {
Flags flags = readFlags(uid);
boolean wasRecent = flags.isRecent();
boolean wasDeleted = flags.isDeleted();
boolean wasSeen = flags.isSeen(user);
if (flags.setFlags(request, user)) {
if (flags.isDeleted()) {
if (! wasDeleted) { messagesForDeletion.add(new Integer(uid)); }
}
if (flags.isSeen(user) != wasSeen) {
if (flags.isSeen(user)) {
int previousOld = ((Integer)oldestUnseenMessage.get(user)).intValue();
if (uid == previousOld) {
int newOld = findOldestUnseen(user, previousOld);
oldestUnseenMessage.put(user, (new Integer(newOld)));
}
} else { // seen flag unset
if (uid < ((Integer)oldestUnseenMessage.get(user)).intValue()) {
oldestUnseenMessage.put(user, (new Integer(uid)));
}
}
}
writeFlags(uid, flags);
logger.debug("Flags for message uid " + uid + " in " + absoluteName + " updated.");
return true;
} else {
return false;
}
} else {
return false;
}
}
private int findOldestUnseen(String user, int previousOld)
throws AccessControlException, AuthorizationException {
int response = 0; //indicates no unseen messages
ListIterator lit = sequence.listIterator(previousOld);
boolean found = false;
while (!found && lit.hasNext() ) {
int uid = ((Integer)lit.next()).intValue();
Flags flags = readFlags(uid);
if (!flags.isSeen(user)) {
response = uid;
found = true;
}
}
return response;
}
private Flags readFlags(int uid) {
Flags response = null;
if (sequence.contains(new Integer(uid))) {
ObjectInputStream inFlags = null;
try {
inFlags = new ObjectInputStream( new FileInputStream(path + File.separator + uid + FLAGS_EXTENSION));
response = (Flags)inFlags.readObject();
} catch(Exception e) {
logger.error("Error reading flags from disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while retrieving Message flags: "
+ e);
} finally {
try {
inFlags.close();
} catch (IOException ie) {
logger.error("Error closing streams: " + ie);
}
}
logger.info("Flags for " + uid + " read from " + absoluteName);
}
return response;
}
private boolean writeFlags(int uid, Flags flags) {
if (sequence.contains(new Integer(uid))) {
ObjectOutputStream outFlags = null;
try {
outFlags = new ObjectOutputStream( new FileOutputStream(path + File.separator + uid + FLAGS_EXTENSION));
outFlags.writeObject(flags);
outFlags.close();
} catch(Exception e) {
logger.error("Error writing message to disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Flags: "
+ e);
} finally {
try {
outFlags.close();
} catch (IOException ie) {
logger.error("Error closing streams: " + ie);
}
}
logger.info("Flags for " + uid + " written in " + absoluteName);
return true;
} else {
return false;
}
}
/**
* Removes all messages marked Deleted. User must have delete rights.
*
* @param username String represnting user
* @returns true if successful
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has delete rights but does not
* have delete rights.
*/
public synchronized boolean expunge(String user)
throws AccessControlException, AuthorizationException {
if (!hasDeleteRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to delete.");
}
Iterator it = messagesForDeletion.iterator();
while (it.hasNext()) {
Integer uidObj = (Integer)it.next();
int uid = uidObj.intValue();
if (sequence.contains(uidObj)) {
try {
final File msgFile = new File(path + File.separator + uid + MESSAGE_EXTENSION );
msgFile.delete();
final File attrFile = new File(path + File.separator + uid + ATTRIBUTES_EXTENSION );
attrFile.delete();
sequence.remove(uidObj);
logger.debug( "Removed message uid " + uid );
} catch ( final Exception e ) {
throw new RuntimeException( "Exception caught while removing" +
" a message: " + e );
}
}
}
for (int i = 0; i < sequence.size(); i++) {
System.err.println("Message with msn " + i + " has uid " + sequence.get(i));
}
return true;
}
private void writeMailbox() {
String mailboxRecordFile = path + File.separator + MAILBOX_FILE_NAME;
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream( new FileOutputStream(mailboxRecordFile));
out.writeObject(this);
out.close();
} catch(Exception e) {
if (out != null) {
try {
out.close();
} catch (Exception ignored) {
}
}
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Mailbox: " + e);
}
logger.info("FileMailbox written: " + absoluteName);
}
/**
* Lists uids of messages in mailbox indexed by MSN.
*
* @param username String represnting user
* @returns List of Integers wrapping uids of message
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
*/
public List listUIDs(String user) {
return new ArrayList(Collections.unmodifiableList(sequence));
}
public Set getUsersWithLookupRights() {
Set response = new HashSet();
Iterator it = acl.keySet().iterator();
while (it.hasNext()) {
String user = (String) it.next();
boolean[] rights = (boolean[]) acl.get(user);
if (rights[LOOKUP] == true) {
response.add(user);
}
}
return response;
}
public Set getUsersWithReadRights() {
Set response = new HashSet();
Iterator it = acl.keySet().iterator();
while (it.hasNext()) {
String user = (String) it.next();
boolean[] rights = (boolean[]) acl.get(user);
if (rights[READ] == true) {
response.add(user);
}
}
return response;
}
public Map getUnseenByUser() {
Map response = new HashMap();
Iterator it = oldestUnseenMessage.keySet().iterator();
while (it.hasNext()) {
String user = (String) it.next();
Integer uidObj = ((Integer)oldestUnseenMessage.get(user));
int oldUID = uidObj.intValue();
if (oldUID == 0) {
response.put(user, uidObj);
} else {
int count = 0;
ListIterator lit
= sequence.listIterator(sequence.indexOf(uidObj));
while (lit.hasNext() ) {
int uid = ((Integer)lit.next()).intValue();
Flags flags = readFlags(uid);
if (!flags.isSeen(user)) {
count ++;
}
}
response.put(user, new Integer(count));
}
}
return response;
}
public InternetHeaders getInternetHeaders(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (msn > sequence.size()) {
return null;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return getInternetHeadersUID(uid, user);
}
}
public InternetHeaders getInternetHeadersUID(int uid, String user)
throws AccessControlException, AuthorizationException {
InternetHeaders response = null;
if (sequence.contains(new Integer(uid))) {
BufferedInputStream inMsg = null;
try {
inMsg = new BufferedInputStream( new FileInputStream(path + File.separator + uid + MESSAGE_EXTENSION));
response = new InternetHeaders(inMsg);
inMsg.close();
} catch(Exception e) {
logger.error("Error reading headers of message from disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caughtt while retrieving InternetHeaders: " + e);
} finally {
try {
inMsg.close();
} catch (IOException ie) {
logger.error("Error closing streams: " + ie);
}
}
logger.info("InternetHeaders for message " + uid + " read from "
+ absoluteName);
return response;
} else {
return null;
}
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/Flags.java
Index: Flags.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import org.apache.avalon.framework.activity.Initializable;
/**
* The set of flags associated with a message. The \Seen flag is maintained
* on a per-user basis.
*
* <p>Reference: RFC 2060 - para 2.3
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class Flags
implements Serializable, Initializable {
public static final int ANSWERED = 0;
public static final int DELETED = 1;
public static final int DRAFT = 2;
public static final int FLAGGED = 3;
public static final int RECENT = 4;
public static final int SEEN = 5;
// Array does not include seen flag
private boolean[] flags = {false, false, false, false, true};
//users who have seen this message
private Set users;
public Flags() {
}
/**
* Initialisation - only for object creation not on deserialisation.
*/
public void initialize() {
users = new HashSet();
}
/**
* Returns IMAP formatted String of Flags for named user
*/
public String getFlags(String user) {
StringBuffer buf = new StringBuffer();
buf.append("(");
if (flags[ANSWERED]) { buf.append("\\ANSWERED ");}
if (flags[DELETED]) { buf.append("\\DELETED ");}
if (flags[DRAFT]) { buf.append("\\DRAFT ");}
if (flags[FLAGGED]) { buf.append("\\FLAGGED ");}
if (flags[RECENT]) { buf.append("\\RECENT ");}
if (users.contains(user)) { buf.append("\\SEEN ");}
buf.append(")");
return buf.toString();
}
/**
* Sets Flags for message from IMAP-forammted string parameter.
* <BR> The FLAGS<list> form overwrites existing flags, ie sets all other
* flags to false.
* <BR> The +FLAGS<list> form adds the flags in list to the existing flags
* <BR> The -FLAGS<list> form removes the flags in list from the existing
* flags
* <BR> Note that the Recent flag cannot be set by user and is ignored by
* this method.
*
* @param flagString a string formatted according to
* RFC2060 store_att_flags
* @param user the String email address of the user
* @returns true if successful, false if not (including uninterpretable
* argument)
*/
public boolean setFlags(String flagString, String user) {
flagString = flagString.toUpperCase();
if (flagString.startsWith("FLAGS")) {
boolean [] newflags = new boolean[5];
newflags[ANSWERED]
= (flagString.indexOf("\\ANSWERED") != -1) ? true : false;
newflags[DELETED]
= (flagString.indexOf("\\DELETED") != -1) ? true : false;
newflags[DRAFT]
= (flagString.indexOf("\\DRAFT") != -1) ? true : false;
newflags[FLAGGED]
= (flagString.indexOf("\\FLAGGED") != -1) ? true : false;
newflags[RECENT] = false;
if (flagString.indexOf("\\SEEN") != -1) {
users.add(user);
}
System.arraycopy(newflags, 0, flags, 0, newflags.length);
return true;
} else if (flagString.startsWith("+FLAGS") ||flagString.startsWith("-FLAGS") ) {
boolean mod = (flagString.startsWith("+") ? true : false);
if (flagString.indexOf("\\ANSWERED") != -1) {
flags[ANSWERED] = mod;
}
if (flagString.indexOf("\\DELETED") != -1) {
flags[DELETED] = mod;
}
if (flagString.indexOf("\\DRAFT") != -1) {
flags[DRAFT] = mod;
}
if (flagString.indexOf("\\FLAGGED") != -1) {
flags[FLAGGED] = mod;
}
if (flagString.indexOf("\\SEEN") != -1) {
if( mod) {
users.add(user);
} else {
users.remove(user);
}
}
return true;
} else {
return false;
}
}
public void setAnswered(boolean newState) {
flags[ANSWERED] = newState;
}
public boolean isAnswered() {
return flags[ANSWERED];
}
public void setDeleted(boolean newState) {
flags[DELETED] = newState;
}
public boolean isDeleted() {
return flags[DELETED];
}
public void setDraft(boolean newState) {
flags[DRAFT] = newState;
}
public boolean isDraft() {
return flags[DRAFT];
}
public void setFlagged(boolean newState) {
flags[FLAGGED] = newState;
}
public boolean isFlagged() {
return flags[FLAGGED];
}
public void setRecent(boolean newState) {
flags[RECENT] = newState;
}
public boolean isRecent() {
return flags[RECENT];
}
public void setSeen(boolean newState, String user) {
if( newState) {
users.add(user);
} else {
users.remove(user);
}
}
public boolean isSeen(String user) {
return users.contains(user);
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/FolderRecord.java
Index: FolderRecord.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.util.Map;
import java.util.Set;
/**
* Interface for objects representing the record of a folder on an IMAP host.
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public interface FolderRecord {
/**
* Returns the full name, including namespace, of this mailbox.
* Example 1: '#mail.projectBonzi'
* Example 2: '#shared.projectBonzi'
*
* @returns String mailbox hierarchical name including namespace
*/
String getFullName();
/**
* Returns the user in whose namespace the mailbox existed.
* Example 1: 'fred.flintstone'
* Example 2: ''
*
* @param user String a user.An empty string indicates that the
* mailbox name is absolute.
*/
String getUser();
/**
* Returns the absolute name of this mailbox. The absolute name is
* user-independent and unique for a given host.
* Example 1: 'privatemail.fred.flintstone.projectBonzi'
* Example 2: '#shared.projectBonzi'
*
* @returns String mailbox absolute name
*/
String getAbsoluteName();
/**
* Records if this mailbox name is currently in use. The mailbox name is
* in use when a mailbox with this name has been created. Implementations
* that allow shared mailboxes may encounter a sate where the mailbox has
* been deleted but there are clients who were already connected to the
* mailbox. In this case the name remains in use until all clients have
* either de-selected the mailbox or been disconnected from the server.
*
* @param state boolean true when mailbox created, false when name no
* longer in use.
*/
void setNameInUse(boolean state);
/**
* Returns unavailability of name for a new mailbox.
*
* @returns true if this name is in use. Must return true if isDeleted
* returns false.
*/
boolean isNameInUse();
/**
* Records if the corresponding mailbox has been deleted.
*
* @param state boolean true when mailbox deleted, false when created
*/
void setDeleted(boolean state);
/**
* Returns whether mailbox has been deleted. A deleted mailbox is an
* invalid argument to any IMAP command..
*
* @returns boolean true if mailbox does not exist
*/
boolean isDeleted();
/**
* Records the Unique Identifier Validity Value for this mailbox.
*
* @param uidValidity int the uid validity value must be incremented if
* the current uid values overlap uid values of this or a previous
* incarnation of the mailbox.
*/
void setUidValidity(int uidValidity);
/**
* Returns current uid validity value
*
* @returns int uid validity value
*/
int getUidValidity();
/**
* Records the highest assigned Unique Identifier Value for this mailbox.
*
* @param uid int the highest uid assigned to a message in this mailbox.
*/
void setHighestUid(int uid);
/**
* Returns current highest assigned uid value
*
* @returns int uid value
*/
int getHighestUid();
/**
* Record which users have LookupRights.
*
* @param users Set of Strings, one per user with Lookup rights
*/
void setLookupRights(Set users);
/**
* Indicates if given user has lookup rights for this mailbox. Need
* lookup rights to be included in a List response.
*
* @returns boolean true if user has lookup rights
*/
boolean hasLookupRights(String user);
/**
* Record which users have ReadRights.
*
* @param users Set of Strings, one per user with read rights
*/
void setReadRights(Set users);
/**
* Indicates if given user has read rights for this mailbox. Need read
* rights for user to select or examine mailbox.
*
* @returns boolean true if user has read rights
*/
boolean hasReadRights(String user);
/**
* Record if mailbox is marked.
*/
void setMarked(boolean mark);
/**
* Indicates if the mailbox is marked. Usually means unseen mail.
*
* @returns boolean true if marked
*/
boolean isMarked();
/**
* Mark this mailbox as not selectable by anyone.
* Example folders at the roots of hierarchies, e. #mail for each user.
*
* @param state true if folder is not selectable by anyone
*/
void setNotSelectableByAnyone(boolean state);
boolean isNotSelectableByAnyone();
/**
* A folder is selectable by a given user if both it is not
* NotSelectableByAnyone and the named user has read rights.
*
* @parm user the user to be tested
* @returns true if user can SELECT this mailbox.
*/
boolean isSelectable(String user);
/**
* Set number of messages in this folder
*/
void setExists(int num);
/**
* Indicates number of messages in folder
*
* @returns int number of messages
*/
int getExists();
/**
* Set number of messages in this folder with Recent flag set
*/
void setRecent(int num);
/**
* Indicates no of messages with \Recent flag set
*
* @returns int no of messages with \Recent flag set
*/
int getRecent();
/**
* Set map of users versus number of messages in this folder without
* \Seen flag set for them
*/
void setUnseenbyUser(Map unseen);
/**
* Indicates the number of unseen messages for the specified user.
*
* @returns int number of messages without \Seen flag set for this User.
*/
int getUnseen(String user);
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/Host.java
Index: Host.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.util.Collection;
import java.util.List;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.phoenix.Service;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
/**
* A host machine that has an IMAP4rev1 messaging server. There should be one
* instance of this class per instance of James.
* An IMAP messaging system may span more than one host.
* <p><code>String</code> parameters representing mailbox names must be the
* full hierarchical name of the target, with namespace, as used by the
* specified user. Examples:
* '#mail.Inbox' or '#shared.finance.Q2Earnings'.
* <p>An imap Host must keep track of existing and deleted mailboxes.
*
* References: rfc 2060, rfc 2193, rfc 2221
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
* @see FolderRecord
* @see RecordRepository
*/
public interface Host
extends Configurable, Composable, Contextualizable, Service {
String IMAP_HOST = "IMAP_HOST";
/**
* Establishes whether this host is the Home Server for the specified user.
* Used during login to decide whether a LOGIN_REFERRAL has to be sent to
* the client.
*
* @param username an email address
* @returns true if inbox (and private mailfolders) are accessible through
* this host.
*/
boolean isHomeServer (String username);
/**
* Establishes if the specified user can access any mailboxes on this host.
* Used during login process to decide what sort of LOGIN-REFERRAL must be
* sent to client.
*
* @param username an email address
* @returns true if the specified user has at least read access to any
* mailboxes on this host.
*/
boolean hasLocalAccess (String username);
/**
* Returns a reference to an existing Mailbox. The requested mailbox
* must already exists on this server and the requesting user must have at
* least lookup rights.
*
* @param user email address on whose behalf the request is made.
* @param mailboxName String name of the target.
* @returns an Mailbox reference.
* @throws AccessControlException if the user does not have at least
* lookup rights.
* @throws MailboxException if mailbox does not exist locally.
*/
ACLMailbox getMailbox(String user, String mailboxName)
throws AccessControlException, MailboxException;
/**
* Returns a reference to a newly created Mailbox. The request should
* specify a mailbox that does not already exist on this server, that
* could exist on this server and that the user has rights to create.
* If a system allocates different namespaces to different hosts then a
* request to create a mailbox in a namespace not served by this host would
* be an error.
* It is an error to create a mailbox with the name of a mailbox that has
* been deleted, if that name is still in use.
*
* @param user email address on whose behalf the request is made.
* @param mailboxName String name of the target
* @returns an Mailbox reference.
* @throws MailboxException if mailbox already exists, locally or remotely,
* or if mailbox cannot be created locally.
* @throws AccessControlException if the user does not have lookup rights
* for parent or any needed ancestor folder
* lookup rights.
* @throws AuthorizationException if mailbox could be created locally but
* user does not have create rights.
* @see FolderRecord
*/
ACLMailbox createMailbox(String user, String mailboxName)
throws AccessControlException, AuthorizationException, MailboxException;
/**
* Deletes an existing MailBox. Specified mailbox must already exist on
* this server, and the user must have rights to delete it. (Mailbox delete
* rights are implementation defined, one way is if the user would have the
* right to create it).
* Implementations must track deleted mailboxes
*
* @param user email address on whose behalf the request is made.
* @param mailboxName String name of the target
* @returns true if mailbox deleted successfully
* @throws MailboxException if mailbox does not exist locally or is any
* identities INBOX.
* @throws AuthorizationException if mailbox exists locally but user does
* not have rights to delete it.
* @see FolderRecord
*/
boolean deleteMailbox(String user, String mailboxName)
throws MailboxException, AuthorizationException;
/**
* Renames an existing MailBox. The specified mailbox must already
* exist locally, the requested name must not exist locally already but
* must be able to be created locally and the user must have rights to
* delete the existing mailbox and create a mailbox with the new name.
* Any inferior hierarchical names must also be renamed.
* If INBOX is renamed, the contents of INBOX are transferred to a new
* folder with the new name, but INBOX is not deleted. If INBOX has
* inferior mailboxes these are not renamed.
* It is an error to create a mailbox with the name of a mailbox that has
* been deleted, if that name is still in use.
* Implementations must track deleted mailboxes
*
* @param user email address on whose behalf the request is made.
* @param oldMailboxName String name of the existing mailbox
* @param newMailboxName String target new name
* @returns true if rename completed successfully
* @throws MailboxException if mailbox does not exist locally, or there
* is an existing mailbox with the new name.
* @throws AuthorizationException if user does not have rights to delete
* the existing mailbox or create the new mailbox.
* @see FolderRecord
*/
boolean renameMailbox( String user,
String oldMailboxName,
String newMailboxName )
throws MailboxException, AuthorizationException;
/**
* Releases a reference to a mailbox, allowing Host to do any housekeeping.
*
* @param username String user who has finished with this mailbox
* @param mbox a non-null reference to an ACL Mailbox.
*/
void releaseMailbox( String user, ACLMailbox mbox );
/**
* Returns the namespace which should be used for this user unless they
* expicitly request another.
*
* @param username String an email address
* @returns a String of a namespace
*/
String getDefaultNamespace( String username );
/**
* Return UIDValidity for named mailbox. Implementations should track
* existing and deleted folders.
*
* @param mailbox String name of the existing mailbox
* @returns an integer containing the current UID Validity value.
*/
// public int getUIDValidity(String mailbox);
/**
* Returns an iterator over an unmodifiable collection of Strings
* representing mailboxes on this host and their attributes. The specified
* user must have at least lookup rights for each mailbox returned.
* If the subscribedOnly flag is set, only mailboxes to which the
* specified user is currently subscribed should be returned.
* Implementations that may export circular hierarchies SHOULD restrict the
* levels of hierarchy returned. The depth suggested by rfc 2683 is 20
* hierarchy levels.
* <p>The reference name must be non-empty. If the mailbox name is empty,
* implementations must not throw either exception but must return a single
* String (described below) if the reference name specifies a local mailbox
* accessible to the user and a one-character String containing the
* hierarchy delimiter of the referenced namespace, otherwise.
* <p>Each String returned should be a space seperated triple of name
* attributes, hierarchy delimiter and full mailbox name. The mailbox
* name should include the namespace and be relative to the specified user.
* <p> RFC comments: Implementations SHOULD return quickly. They SHOULD
* NOT go to excess trouble to calculate\Marked or \Unmarked status.
* <p>JAMES comment: By elimination, implementations should usually include
* \Noinferiors or \Noselect, if appropriate. Also, if the reference name
* and mailbox name resolve to a single local mailbox, implementations
* should establish all attributes.
* <p> Note that servers cannot unilaterally remove mailboxes from the
* subscribed list. A request with the subscribedOnly flag set that
* attempts to list a deleted mailbox must return that mailbox with the
* \Noselect attribute.
*
* @param username String non-empty email address of requester
* @param referenceName String non-empty name, including namespace, of a
* mailbox or level of mailbox hierarchy, relative to user.
* @param mailboxName String name of a mailbox possible including a
* wildcard.
* @param subscribedOnly only return mailboxes currently subscribed.
* @returns Collection of strings representing a set of mailboxes.
* @throws AccessControlException if the user does not have at least
* lookup rights to at least one mailbox in the set requested.
* @throws MailboxException if the referenceName is not local or if
* referenceName and mailbox name resolve to a single mailbox which does
* not exist locally.
*/
Collection listMailboxes( String username,
String referenceName,
String mailboxName,
boolean subscribedOnly )
throws MailboxException, AccessControlException;
/**
* Subscribes a user to a mailbox. The mailbox must exist locally and the
* user must have at least lookup rights to it.
*
* @param username String representation of an email address
* @param mailbox String representation of a mailbox name.
* @returns true if subscribe completes successfully
* @throws AccessControlException if the mailbox exists but the user does
* not have lookup rights.
* @throws MailboxException if the mailbox does not exist locally.
*/
boolean subscribe( String username, String mailbox )
throws MailboxException, AccessControlException;
/**
* Unsubscribes from a given mailbox.
*
* @param username String representation of an email address
* @param mailbox String representation of a mailbox name.
* @returns true if unsubscribe completes successfully
*/
boolean unsubscribe( String username, String mailbox )
throws MailboxException, AccessControlException;
/**
* Returns a string giving the status of a mailbox on requested criteria.
* Currently defined staus items are:
* MESSAGES - Nummber of messages in mailbox
* RECENT - Number of messages with \Recent flag set
* UIDNEXT - The UID that will be assigned to the next message entering
* the mailbox
* UIDVALIDITY - The current UIDValidity value for the mailbox
* UNSEEN - The number of messages which do not have the \Seen flag set.
*
* @param username String non-empty email address of requester
* @param mailboxName String name of a mailbox (no wildcards allowed).
* @param dataItems Vector of one or more Strings each of a single
* status item.
* @returns String consisting of space seperated pairs:
* dataItem-space-number.
* @throws AccessControlException if the user does not have at least
* lookup rights to the mailbox requested.
* @throws MailboxException if the mailboxName does not exist locally.
*/
String getMailboxStatus( String username,
String mailboxName,
List dataItems )
throws MailboxException, AccessControlException;
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/IMAPServer.java
Index: IMAPServer.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.cornerstone.services.connection.AbstractService;
import org.apache.avalon.cornerstone.services.connection.ConnectionHandlerFactory;
import org.apache.avalon.cornerstone.services.connection.DefaultHandlerFactory;
/**
* The Server listens on a specified port and passes connections to a
* ConnectionHandler. In this implementation, each ConnectionHandler runs in
* its own thread.
*
* @version o.1 on 14 Dec 2000
* @author Federico Barbieri <sc...@pop.systemy.it>
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
*/
public class IMAPServer
extends AbstractService {
protected ConnectionHandlerFactory createFactory()
{
return new DefaultHandlerFactory( SingleThreadedConnectionHandler.class );
}
public void configure( final Configuration configuration )
throws ConfigurationException {
m_port = configuration.getChild( "port" ).getValueAsInteger( 143 );
try
{
final String bindAddress = configuration.getChild( "bind" ).getValue( null );
if( null != bindAddress )
{
m_bindTo = InetAddress.getByName( bindAddress );
}
}
catch( final UnknownHostException unhe )
{
throw new ConfigurationException( "Malformed bind parameter", unhe );
}
final String useTLS = configuration.getChild("useTLS").getValue( "" );
if( useTLS.equals( "TRUE" ) ) m_serverSocketType = "ssl";
super.configure( configuration.getChild( "handler" ) );
}
public void initialize() throws Exception {
getLogger().info( "IMAPServer init..." );
getLogger().info( "IMAPListener using " + m_serverSocketType + " on port " + m_port );
super.initialize();
getLogger().info("IMAPServer ...init end");
System.out.println("Started IMAP Server "+m_connectionName);
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/IMAPServer.xinfo
Index: IMAPServer.xinfo
===================================================================
<?xml version="1.0"?>
<blockinfo>
<services>
<service name="org.apache.avalon.framework.component.Component" version="1.0"/>
</services>
<dependencies>
<dependency>
<role>org.apache.james.services.MailStore</role>
<service name="org.apache.james.services.MailStore" version="1.0"/>
</dependency>
<dependency>
<role>org.apache.james.services.UsersStore</role>
<service name="org.apache.james.services.UsersStore" version="1.0"/>
</dependency>
<dependency>
<role>org.apache.avalon.cornerstone.services.connection.ConnectionManager</role>
<service name="org.apache.avalon.cornerstone.services.connection.ConnectionManager"
version="1.0"/>
</dependency>
<dependency>
<role>org.apache.avalon.cornerstone.services.sockets.SocketManager</role>
<service name="org.apache.avalon.cornerstone.services.sockets.SocketManager" version="1.0"/>
</dependency>
<dependency>
<role>org.apache.avalon.cornerstone.services.scheduler.TimeScheduler</role>
<service name="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" version="1.0"/>
</dependency>
<dependency>
<role>org.apache.james.services.MailServer</role>
<service name="org.apache.james.services.MailServer" version="1.0"/>
</dependency>
</dependencies>
</blockinfo>
1.1 jakarta-james/src/java/org/apache/james/imapserver/ImapRequest.java
Index: ImapRequest.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;
/**
* An single client request to an IMAP server, with necessary details for
* command processing
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 17 Jan 2001
*/
public class ImapRequest {
private StringTokenizer commandLine;
private boolean useUIDs;
private ACLMailbox currentMailbox;
private String commandRaw;
private String tag;
private SingleThreadedConnectionHandler caller;
private String currentFolder;
public ImapRequest(SingleThreadedConnectionHandler handler) {
caller = handler;
}
public SingleThreadedConnectionHandler getCaller() {
return caller;
}
public void setCommandLine(StringTokenizer st) {
commandLine = st;
}
public StringTokenizer getCommandLine() {
return commandLine;
}
public void setUseUIDs(boolean state) {
useUIDs = state;
}
public boolean useUIDs() {
return useUIDs;
}
public void setCurrentMailbox(ACLMailbox mbox) {
currentMailbox = mbox;
}
public ACLMailbox getCurrentMailbox() {
return currentMailbox;
}
public void setCommandRaw(String raw) {
commandRaw = raw;
}
public String getCommandRaw() {
return commandRaw;
}
public void setTag(String t) {
tag = t;
}
public String getTag() {
return tag;
}
public void setCurrentFolder(String f) {
currentFolder = f;
}
public String getCurrentFolder() {
return currentFolder;
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/JamesHost.java
Index: JamesHost.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
import org.apache.james.services.*;
import org.apache.log.LogKit;
import org.apache.log.Logger;
/**
* A single host that has an IMAP4rev1 messaging server.
* There should be one instance of this class per instance of James.
* An IMAP messaging system may span more than one host.
* <p><code>String</code> parameters representing mailbox names must be the
* full hierarchical name of the target, with namespace, as used by the
* specified user. Examples:
* '#mail.Inbox' or '#shared.finance.Q2Earnings'.
* <p>An imap Host must keep track of existing and deleted mailboxes.
*
* References: rfc 2060, rfc 2193, rfc 2221
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
* @see FolderRecord
* @see RecordRepository
*/
public class JamesHost
implements Host, Component, Initializable {
private Context context;
private Configuration conf;
private ComponentManager compMgr;
private Logger logger = LogKit.getLoggerFor("james.JamesHost");
private String rootPath; // ends with File.seperator
private File rootFolder;
private IMAPSystem imapSystem;
//private UserManager usersManager;
private UsersRepository localUsers;
private RecordRepository recordRep;
private Map openMailboxes; //maps absoluteName to ACLMailbox
private Map mailboxCounts; // maps absoluteName to Integer count of extant references.
private String namespaceToken;
private String privateNamespace;
private String privateNamespaceSeparator;
private String otherUsersNamespace;
private String otherUsersNamespaceSeparator;
private String sharedNamespace;
private String sharedNamespaceSeparator;
/*
* Note on implemented namespaces.
* 3 namespaces are (partially) implemented.
* 1) Private namespace ie access to a user's own mailboxes.
* Full mailbox names (ie what user sees) of the form:
* #mail.Inbox or #mail.apache.James
* Absolute names of the form:
* #mail.fred.flintstone.Inbox or #mail.fred.flintstone.apache.James
* 2) Other users namespace ie access to other users mailboxes
* subject to access control, of course
* Full mailbox names (ie what user sees) of the form:
* #users.captain.scarlet.Inbox or #users.RobinHood.apache.James
* Absolute names of the form:
* #mail.captain.scarlet.Inbox or #mail.RobinHood.apache.James
* 3) Shared mailboxes
* not fully implemented.
* Full mailbox names (ie what user sees) of the form:
* #shared.projectAlpha.masterplan or #shared.customerservice.Inbox
* Absolute names of the form:
* #shared.projectAlpha.masterplan or #shared.customerservice.Inbox
*/
/*
* Note on filename extensions
* Records in AvalonRecordRepository - no extension
* Mailboxes - mbr
* MimeMessage - msg
* MessageAttributes - att
*/
/* No constructor */
public void configure(Configuration conf) throws ConfigurationException {
this.conf = conf;
}
public void contextualize(Context context) {
this.context = context;
}
public void compose(ComponentManager comp) {
compMgr = comp;
}
public void initialize() throws Exception {
logger.info("JamesHost init...");
imapSystem = (IMAPSystem) compMgr.lookup("org.apache.james.imapserver.IMAPSystem");
localUsers = (UsersRepository)compMgr.lookup("org.apache.james.services.UsersRepository");
String recordRepDest
= conf.getChild("recordRepository").getValue();
recordRep = new DefaultRecordRepository();
recordRep.setPath(recordRepDest);
logger.info("AvalonRecordRepository opened at " + recordRepDest);
rootPath = conf.getChild("mailboxRepository").getValue();
if (!rootPath.endsWith(File.separator)) {
rootPath = rootPath + File.separator;
}
rootFolder = new File(rootPath);
if (!rootFolder.isDirectory()) {
if (! rootFolder.mkdir()){
throw new RuntimeException("Error: Cannot create directory for MailboxRepository");
}
} else if (!rootFolder.canWrite()) {
throw new RuntimeException("Error: Cannot write to directory for MailboxRepository");
}
logger.info("IMAP Mailbox Repository opened at " + rootPath);
Configuration namespaces = conf.getChild("namespaces");
namespaceToken = namespaces.getAttribute("token");
privateNamespace
= namespaces.getChild("privateNamespace").getValue();
privateNamespaceSeparator
= namespaces.getChild("privateNamespace").getAttribute("separator");
otherUsersNamespace
= namespaces.getChild("otherusersNamespace").getValue();
otherUsersNamespaceSeparator
= namespaces.getChild("otherusersNamespace").getAttribute("separator");
sharedNamespace
= namespaces.getChild("sharedNamespace").getValue();
sharedNamespaceSeparator
= namespaces.getChild("sharedNamespace").getAttribute("separator");
logger.info("Handling mail for namespaces: "+ privateNamespace + ", " + otherUsersNamespace + ", " + sharedNamespace);
openMailboxes = new HashMap(31); // how big should this start?
mailboxCounts = new HashMap(31);
logger.info("JamesHost ...init end");
}
/**
* Establishes whether this host is the Home Server for the specified user.
* Used during login to decide whether a LOGIN_REFERRAL has to be sent to
* the client.
*
* @param username an email address
* @returns true if inbox (and private mailfolders) are accessible through
* this host.
*/
public boolean isHomeServer (String username) {
return localUsers.contains(username);
}
/**
* Establishes if the specified user can access any mailboxes on this host.
* Used during login process to decide what sort of LOGIN-REFERRAL must be
* sent to client.
*
* @param username an email address
* @returns true if the specified user has at least read access to any
* mailboxes on this host.
*/
public boolean hasLocalAccess (String username) {
return localUsers.contains(username);
}
/**
* Returns a reference to an existing Mailbox. The requested mailbox
* must already exists on this server and the requesting user must have at
* least lookup rights.
*
* @param user email address on whose behalf the request is made.
* @param mailboxName String name of the target.
* @returns an Mailbox reference.
* @throws AccessControlException if the user does not have at least
* lookup rights.
* @throws MailboxException if mailbox does not exist locally.
*/
public synchronized ACLMailbox getMailbox(String user, String mailboxName)
throws AccessControlException, MailboxException {
if (user == null || mailboxName == null) {
logger.error("Null parameters received in getMailbox(). " );
throw new RuntimeException("Null parameters received.");
} else if (user.equals("")
||(!mailboxName.startsWith(namespaceToken))) {
logger.error("Empty/ incorrect parameters received in getMailbox().");
throw new RuntimeException("Empty/incorrect parameters received.");
}
logger.debug("Getting mailbox " + mailboxName + " for " + user);
String absoluteName = getAbsoluteName(user, mailboxName);
if (absoluteName == null) {
logger.error("Parameters in getMailbox() cannot be interpreted. ");
throw new RuntimeException("Parameters in getMailbox() cannot be interpreted.");
}
return getAbsoluteMailbox(user, absoluteName);
}
private synchronized ACLMailbox getAbsoluteMailbox(String user, String absoluteName)
throws AccessControlException, MailboxException {
ACLMailbox mailbox = null;
FolderRecord record = null;
// Has a folder with this name ever been created?
if(! recordRep.containsRecord(absoluteName)) {
throw new MailboxException("Mailbox: " + absoluteName + " has never been created.", MailboxException.NOT_LOCAL);
} else {
record = recordRep.retrieve(absoluteName);
if (record.isDeleted()) {
throw new MailboxException("Mailbox has been deleted", MailboxException.LOCAL_BUT_DELETED);
} else if (openMailboxes.containsKey(absoluteName)) {
mailbox = (ACLMailbox) openMailboxes.get(absoluteName);
if (!mailbox.hasLookupRights(user)) {
throw new AccessControlException("No lookup rights.");
} else {
Integer c = (Integer)mailboxCounts.get(absoluteName);
int count = c.intValue() + 1;
mailboxCounts.put(absoluteName, (new Integer(count)));
logger.info("Request no " + count + " for " + absoluteName);
return mailbox;
}
} else {
String owner = record.getUser();
String key = getPath(absoluteName, owner);
ObjectInputStream in = null;
try {
in = new ObjectInputStream( new FileInputStream(key + File.separator + FileMailbox.MAILBOX_FILE_NAME) );
mailbox = (FileMailbox) in.readObject();
mailbox.configure(conf);
mailbox.contextualize(context);
mailbox.compose(compMgr);
mailbox.reinitialize();
} catch (Exception e) {
e.printStackTrace();
throw new
RuntimeException("Exception caught while reading FileMailbox: " + e);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception ignored) {
}
}
notifyAll();
}
if (!mailbox.hasLookupRights(user)) {
throw new AccessControlException("No lookup rights.");
}
openMailboxes.put(absoluteName, mailbox);
mailboxCounts.put(absoluteName, new Integer(1));
return mailbox;
}
}
}
/**
* Returns a reference to a newly created Mailbox. The request should
* specify a mailbox that does not already exist on this server, that
* could exist on this server and that the user has rights to create.
* If a system allocates different namespaces to different hosts then a
* request to create a mailbox in a namespace not served by this host would
* be an error.
* It is an error to create a mailbox with the name of a mailbox that has
* been deleted, if that name is still in use.
*
* @param user email address on whose behalf the request is made.
* @param mailboxName String name of the target
* @returns an Mailbox reference.
* @throws AccessControlException if the user does not have lookup rights
* for parent or any needed ancestor folder
* lookup rights.
* @throws AuthorizationException if mailbox could be created locally but
* user does not have create rights.
* @throws MailboxException if mailbox already exists, locally or remotely,
* or if mailbox cannot be created locally.
* @see FolderRecord
*/
public synchronized ACLMailbox createMailbox(String user, String mailboxName)
throws AccessControlException, AuthorizationException,
MailboxException {
if (user == null || mailboxName == null) {
logger.error("Null parameters received in createMailbox(). " );
throw new RuntimeException("Null parameters received.");
} else if (user.equals("")
||(!mailboxName.startsWith(namespaceToken))) {
logger.error("Empty/ incorrect parameters received in createMailbox().");
throw new RuntimeException("Empty/incorrect parameters received.");
}
String absoluteName = getAbsoluteName(user, mailboxName);
if (absoluteName == null) {
logger.error("Parameters in createMailbox() cannot be interpreted. ");
throw new RuntimeException("Parameters in createMailbox() cannot be interpreted.");
}
logger.debug("JamesHost createMailbox() for: " + absoluteName);
return createAbsoluteMailbox(user, absoluteName);
}
private synchronized ACLMailbox createAbsoluteMailbox(String user, String absoluteName)
throws AccessControlException, AuthorizationException,
MailboxException {
ACLMailbox mailbox = null;
FolderRecord record = null;
ACLMailbox parentMailbox = null;
// Has a folder with this name ever been created?
if( recordRep.containsRecord(absoluteName)) {
record = recordRep.retrieve(absoluteName);
if (!record.isDeleted()) {
logger.error("Attempt to create an existing Mailbox.");
throw new MailboxException("Mailbox already exists", MailboxException.ALREADY_EXISTS_LOCALLY);
}
} else {
String parent
= absoluteName.substring(0, absoluteName.lastIndexOf(privateNamespaceSeparator));
if (!(parent.startsWith(privateNamespace + privateNamespaceSeparator) || parent.startsWith(sharedNamespace + sharedNamespaceSeparator))) {
logger.warn("No such parent: " + parent);
throw new MailboxException("No such parent: " + parent);
}
//Recurse to a created and not deleted mailbox
try {
parentMailbox = getAbsoluteMailbox(user, parent);
} catch (MailboxException mbe) {
if (mbe.getStatus().equals(MailboxException.NOT_LOCAL)
|| mbe.getStatus().equals(MailboxException.LOCAL_BUT_DELETED)) {
parentMailbox = createAbsoluteMailbox(user, parent);
} else {
throw new MailboxException(mbe.getMessage(), mbe.getStatus());
}
}
// Does user have create rights in parent mailbox?
if (!parentMailbox.hasCreateRights(user)) {
throw new AuthorizationException("User does not have create rights.");
}
try {
mailbox = new FileMailbox();
mailbox.configure(conf);
mailbox.contextualize(context);
mailbox.compose(compMgr);
mailbox.prepareMailbox(user, absoluteName, user);
mailbox.initialize();
} catch (Exception e) {
logger.error("Exception creating mailbox: " + e);
throw new MailboxException("Exception creating mailbox: " + e);
}
String mailboxName
= absoluteName.substring(0, absoluteName.indexOf(user))
+ absoluteName.substring(absoluteName.indexOf(user) + user.length(), absoluteName.length());
SimpleFolderRecord fr
= new SimpleFolderRecord(mailboxName, user, absoluteName);
fr.initialize();
recordRep.store(fr);
openMailboxes.put(absoluteName, mailbox);
mailboxCounts.put(absoluteName, new Integer(1));
}
return mailbox;
}
/**
* Releases a reference to a mailbox, allowing Host to do any housekeeping.
*
* @param mbox a non-null reference to an ACL Mailbox.
*/
public void releaseMailbox(String user, ACLMailbox mailbox) {
if (mailbox == null) {
logger.debug("Attempt to release mailbox with null reference");
return;
}
if (user != MailServer.MDA) {
mailbox.unsetRecent();
}
String absName = mailbox.getAbsoluteName();
Integer c = (Integer)mailboxCounts.get(absName);
int count = c.intValue() - 1;
if (count < 1) {
openMailboxes.remove(absName);
mailboxCounts.remove(absName);
try {
FolderRecord fr = recordRep.retrieve(absName);
fr.setUidValidity(mailbox.getUIDValidity());
fr.setHighestUid(mailbox.getNextUID() -1);
fr.setLookupRights(mailbox.getUsersWithLookupRights());
fr.setReadRights(mailbox.getUsersWithReadRights());
fr.setMarked(mailbox.isMarked());
fr.setNotSelectableByAnyone(mailbox.isNotSelectableByAnyone());
fr.setExists(mailbox.getExists());
fr.setRecent(mailbox.getRecent());
fr.setUnseenbyUser(mailbox.getUnseenByUser());
recordRep.store(fr);
mailbox.dispose();
mailbox = null;
logger.info("Mailbox object destroyed: " + absName);
} catch (Exception e) {
logger.error("Exception destroying mailbox object: " + e);
e.printStackTrace();
}
} else {
logger.info("Mailbox " + absName + " now has " + count + "live references");
mailboxCounts.put(absName, (new Integer(count)));
}
}
/**
* Deletes an existing MailBox. Specified mailbox must already exist on
* this server, and the user must have rights to delete it. (Mailbox delete
* rights are implementation defined, one way is if the user would have the
* right to create it).
* Implementations must track deleted mailboxes
*
* @param user email address on whose behalf the request is made.
* @param mailboxName String name of the target
* @returns true if mailbox deleted successfully
* @throws MailboxException if mailbox does not exist locally or is any
* identities INBOX.
* @throws AuthorizationException if mailbox exists locally but user does
* not have rights to delete it.
* @see FolderRecord
*/
public boolean deleteMailbox(String user, String mailboxName)
throws MailboxException, AuthorizationException {
if (user == null || mailboxName == null) {
logger.error("Null parameters received in deleteMailbox(). ");
throw new RuntimeException("Null parameters received.");
} else if (user.equals("")
||(!mailboxName.startsWith(namespaceToken))) {
logger.error("Empty/ incorrect parameters received in deleteMailbox().");
throw new RuntimeException("Empty/incorrect parameters received.");
}
String absoluteName = getAbsoluteName(user, mailboxName);
if (absoluteName == null) {
logger.error("Parameters in deleteMailbox() cannot be interpreted. ");
throw new RuntimeException("Parameters in deleteMailbox() cannot be interpreted.");
}
logger.debug("JamesHost deleteMailbox() called for: " + absoluteName);
return false;
//return deleteAbsoluteMailbox(user, absoluteName);
}
/**
* Renames an existing MailBox. The specified mailbox must already
* exist locally, the requested name must not exist locally already but
* must be able to be created locally and the user must have rights to
* delete the existing mailbox and create a mailbox with the new name.
* Any inferior hierarchical names must also be renamed.
* If INBOX is renamed, the contents of INBOX are transferred to a new
* folder with the new name, but INBOX is not deleted. If INBOX has
* inferior mailboxes these are not renamed.
* It is an error to create a mailbox with the name of a mailbox that has
* been deleted, if that name is still in use.
* Implementations must track deleted mailboxes
*
* @param user email address on whose behalf the request is made.
* @param oldMailboxName String name of the existing mailbox
* @param newMailboxName String target new name
* @returns true if rename completed successfully
* @throws MailboxException if mailbox does not exist locally, or there
* is an existing mailbox with the new name.
* @throws AuthorizationException if user does not have rights to delete
* the existing mailbox or create the new mailbox.
* @see FolderRecord
*/
public boolean renameMailbox(String user, String oldMailboxName,
String newMailboxName)
throws MailboxException, AuthorizationException {
return false;
}
/**
* Returns the namespace which should be used for this user unless they
* expicitly request another.
*
* @param username String an email address
* @returns a String of a namespace
*/
public String getDefaultNamespace(String username) {
return privateNamespace;
}
/**
* Return UIDValidity for named mailbox. Implementations should track
* existing and deleted folders.
*
* @param mailbox String name of the existing mailbox
* @returns an integer containing the current UID Validity value.
*/
// public int getUIDValidity(String mailbox);
/**
* Returns an iterator over an unmodifiable collection of Strings
* representing mailboxes on this host and their attributes. The specified
* user must have at least lookup rights for each mailbox returned.
* If the subscribedOnly flag is set, only mailboxes to which the
* specified user is currently subscribed should be returned.
* Implementations that may export circular hierarchies SHOULD restrict the
* levels of hierarchy returned. The depth suggested by rfc 2683 is 20
* hierarchy levels.
* <p>The reference name must be non-empty. If the mailbox name is empty,
* implementations must not throw either exception but must return a single
* String (described below) if the reference name specifies a local mailbox
* accessible to the user and a one-character String containing the
* hierarchy delimiter of the referenced namespace, otherwise.
* <p>Each String returned should be a space seperated triple of name
* attributes, hierarchy delimiter and full mailbox name. The mailbox
* name should include the namespace and be relative to the specified user.
* <p> RFC comments: Implementations SHOULD return quickly. They SHOULD
* NOT go to excess trouble to calculate\Marked or \Unmarked status.
* <p>JAMES comment: By elimination, implementations should usually include
* \Noinferiors or \Noselect, if appropriate. Also, if the reference name
* and mailbox name resolve to a single local mailbox, implementations
* should establish all attributes.
* <p> Note that servers cannot unilaterally remove mailboxes from the
* subscribed list. A request with the subscribedOnly flag set that
* attempts to list a deleted mailbox must return that mailbox with the
* \Noselect attribute.
*
* @param username String non-empty email address of requester
* @param referenceName String non-empty name, including namespace, of a
* mailbox or level of mailbox hierarchy, relative to user.
* @param mailboxName String name of a mailbox possible including a
* wildcard.
* @param subscribedOnly only return mailboxes currently subscribed.
* @returns Collection of strings representing a set of mailboxes.
* @throws AccessControlException if the user does not have at least
* lookup rights to at least one mailbox in the set requested.
* @throws MailboxException if the referenceName is not local or if
* referenceName and mailbox name resolve to a single mailbox which does
* not exist locally.
*/
public synchronized Collection listMailboxes(String username,
String referenceName,
String mailboxName,
boolean subscribedOnly)
throws MailboxException, AccessControlException {
logger.debug("Listing for user: " + username + " ref " + referenceName + " mailbox " + mailboxName);
List responseList = new ArrayList();
if (subscribedOnly == true ) {
return null;
}
if (mailboxName.equals("")) {
// means don't List but give root of hierarchy and separator
String response;
if (referenceName.startsWith(privateNamespace)) {
response = "(\\Noselect) \"" + privateNamespaceSeparator
+ "\" " + privateNamespace;
} else if (referenceName.startsWith(otherUsersNamespace)) {
response = "(\\Noselect) \"" + otherUsersNamespaceSeparator
+ "\" " + otherUsersNamespace;
} else if (referenceName.startsWith(sharedNamespace)) {
response = "(\\Noselect) \"" + sharedNamespaceSeparator
+ "\" " + sharedNamespace;
} else {
logger.error("Weird arguments for LIST? referenceName was: " + referenceName + " and mailbox names was " + mailboxName);
return null;
}
responseList.add(response);
return responseList;
}
//short-circuit evaluation for namespaces
String response = null;
if (mailboxName.equals(privateNamespace + "%")) {
response = "(\\Noselect) \"" + privateNamespaceSeparator + "\" " + privateNamespace;
} else if (mailboxName.equals(otherUsersNamespace + "%")) {
response = "(\\Noselect) \"" + otherUsersNamespaceSeparator + "\" " + otherUsersNamespace;
} else if (mailboxName.equals(otherUsersNamespace + "%")) {
response = "(\\Noselect) \"" + sharedNamespaceSeparator + "\" " + sharedNamespace;
}
if (response != null) {
responseList.add(response);
return responseList;
}
try { // for debugging purposes
//Short-circuit for Netscape client calls - remove first % in, e.g. #mail%.%
// Eventually we need to handle % anywhere in mailboxname
if (mailboxName.startsWith(privateNamespace + "%")) {
mailboxName = privateNamespace + mailboxName.substring(privateNamespace.length() + 1);
} else if (mailboxName.startsWith(otherUsersNamespace + "%")) {
mailboxName = otherUsersNamespace + mailboxName.substring(otherUsersNamespace.length() + 1);
} else if (mailboxName.startsWith(sharedNamespace + "%")) {
mailboxName = sharedNamespace + mailboxName.substring(sharedNamespace.length() + 1);
}
//mailboxName = mailboxName.substring(0,mailboxName.length() -1);
logger.debug("Refined mailboxName to: " + mailboxName);
String userTarget;
if (mailboxName.startsWith("#")) {
userTarget = mailboxName;
} else {
if (referenceName.endsWith(".")) {
userTarget = referenceName + mailboxName;
} else {
userTarget = referenceName + "." + mailboxName;
}
}
String target = getAbsoluteName(username, userTarget);
logger.info("Target is: " + target);
if (target == null) { return new HashSet();}
int firstPercent = target.indexOf("%");
int firstStar = target.indexOf("*");
logger.info("First percent at index: " + firstPercent);
logger.info("First star at index: " + firstStar);
Iterator all = recordRep.getAbsoluteNames();
Set matches = new HashSet();
while (all.hasNext()) {
boolean match = false;
String test = (String)all.next();
logger.info("Test is: " + test);
if (firstPercent == -1 && firstStar == -1) {
// no wildcards so exact or nothing
match = test.equals(target);
logger.debug("match/ no match at test 1");
} else if (firstStar == -1) {
// only % wildcards
if (!test.startsWith(target.substring(0, firstPercent))) {
match = false;
logger.debug("fail match at test 2");
} else if (firstPercent == target.length() -1) {
// only one % and it is terminating char
target = target.substring(0, firstPercent);
logger.debug("Refined target to: " + target);
if (test.equals(target)) {
match = true;
logger.debug("pass match at test 3");
} else if ( (test.length() > target.length())
&& (test.indexOf('.', target.length())
== -1)
) {
match = true;
logger.debug("pass match at test 4");
} else {
match = false;
logger.debug("fail match at test 5");
}
} else {
int secondPercent = target.indexOf("%", firstPercent + 1);
match = false; // unfinished
logger.debug("fail match at test 6");
}
} else {
//at least one star
int firstWildcard = -1;
if (firstPercent != -1 && firstStar == -1) {
firstWildcard = firstPercent;
} else if (firstStar != -1 && firstPercent == -1) {
firstWildcard = firstStar;
} else if (firstPercent < firstStar) {
firstWildcard = firstPercent;
} else {
firstWildcard = firstStar;
}
if (!test.startsWith(target.substring(0, firstWildcard))) {
match = false;
} else {
match = false;
}
}
if (match) {
logger.info("Processing match for : " + test);
FolderRecord record = recordRep.retrieve(test);
ACLMailbox mailbox = null;
StringBuffer buf = new StringBuffer();
buf.append("(");
if (!record.isDeleted() && openMailboxes.containsKey(target)) {
mailbox = (ACLMailbox) openMailboxes.get(target);
}
if (record.isDeleted()) {
buf.append("\\Noselect");
} else if(openMailboxes.containsKey(target)) {
mailbox = (ACLMailbox) openMailboxes.get(target);
if (!mailbox.isSelectable(username)) {
buf.append("\\Noselect");
}
if (mailbox.isMarked()) {
buf.append("\\Marked");
} else {
buf.append("\\Unmarked");
}
} else {
if (!record.isSelectable(username)) {
buf.append("\\Noselect");
}
if (record.isMarked()) {
buf.append("\\Marked");
} else {
buf.append("\\Unmarked");
}
}
buf.append(") \"");
if(userTarget.startsWith(privateNamespace)) {
buf.append(privateNamespaceSeparator);
} else if(userTarget.startsWith(otherUsersNamespace)) {
buf.append(otherUsersNamespaceSeparator);
} else {
buf.append(sharedNamespaceSeparator);
}
buf.append("\" ");
if (test.toUpperCase().indexOf("INBOX") == -1) {
buf.append(getFullMailboxName(username, test) );
} else {
buf.append( "INBOX");
}
matches.add(buf.toString());
}
}
return matches;
} catch (Exception e) {
logger.error("Exception with list request for mailbox " + mailboxName);
e.printStackTrace();
return null;
}
}
/**
* Subscribes a user to a mailbox. The mailbox must exist locally and the
* user must have at least lookup rights to it.
*
* @param username String representation of an email address
* @param mailbox String representation of a mailbox name.
* @returns true if subscribe completes successfully
* @throws AccessControlException if the mailbox exists but the user does
* not have lookup rights.
* @throws MailboxException if the mailbox does not exist locally.
*/
public boolean subscribe(String username, String mailbox)
throws MailboxException, AccessControlException {
return false;
}
/**
* Unsubscribes from a given mailbox.
*
* @param username String representation of an email address
* @param mailbox String representation of a mailbox name.
* @returns true if unsubscribe completes successfully
*/
public boolean unsubscribe(String username, String mailbox)
throws MailboxException, AccessControlException {
return false;
}
/**
* Returns a string giving the status of a mailbox on requested criteria.
* Currently defined staus items are:
* MESSAGES - Nummber of messages in mailbox
* RECENT - Number of messages with \Recent flag set
* UIDNEXT - The UID that will be assigned to the next message entering
* the mailbox
* UIDVALIDITY - The current UIDValidity value for the mailbox
* UNSEEN - The number of messages which do not have the \Seen flag set.
*
* @param username String non-empty email address of requester
* @param mailboxName String name of a mailbox (no wildcards allowed).
* @param dataItems Vector of one or more Strings each of a single
* status item.
* @returns String consisting of space seperated pairs:
* dataItem-space-number.
* @throws AccessControlException if the user does not have at least
* lookup rights to the mailbox requested.
* @throws MailboxException if the mailboxName does not exist locally.
*/
public String getMailboxStatus(String username, String mailboxName,
List dataItems)
throws MailboxException, AccessControlException {
String absoluteName = getAbsoluteName(username, mailboxName);
ACLMailbox mailbox = null;
FolderRecord record = null;
Iterator it = dataItems.iterator();
String response = null;
// Has a folder with this name ever been created?
if(! recordRep.containsRecord(absoluteName)) {
throw new MailboxException("Mailbox: " + absoluteName + " has never been created.", MailboxException.NOT_LOCAL);
} else {
record = recordRep.retrieve(absoluteName);
if (record.isDeleted()) {
throw new MailboxException("Mailbox has been deleted", MailboxException.LOCAL_BUT_DELETED);
} else if (openMailboxes.containsKey(absoluteName)) {
response = new String();
mailbox = (ACLMailbox) openMailboxes.get(absoluteName);
if (!mailbox.hasLookupRights(username)) {
throw new AccessControlException("No lookup rights.");
}
while(it.hasNext()) {
String dataItem = (String) it.next();
if (dataItem.equalsIgnoreCase("MESSAGES")) {
response += "MESSAGES " + mailbox.getExists();
} else if (dataItem.equalsIgnoreCase("RECENT")) {
response += "RECENT " + mailbox.getRecent();
} else if (dataItem.equalsIgnoreCase("UIDNEXT")) {
response += "UIDNEXT " + mailbox.getNextUID();
} else if (dataItem.equalsIgnoreCase("UIDVALIDITY")) {
response += "UIDVALIDITY " + mailbox.getUIDValidity();
} else if (dataItem.equalsIgnoreCase("UNSEEN")) {
response += "UNSEEN " + mailbox.getUnseen(username);
}
if (it.hasNext()) { response += " ";}
}
return response;
} else {
if (!record.hasLookupRights(username)) {
throw new AccessControlException("No lookup rights.");
}
response = new String();
while(it.hasNext()) {
String dataItem = (String) it.next();
if (dataItem.equalsIgnoreCase("MESSAGES")) {
response += "MESSAGES " + record.getExists();
} else if (dataItem.equalsIgnoreCase("RECENT")) {
response += "RECENT " + record.getRecent();
} else if (dataItem.equalsIgnoreCase("UIDNEXT")) {
response += "UIDNEXT " + (record.getHighestUid() + 1);
} else if (dataItem.equalsIgnoreCase("UIDVALIDITY")) {
response += "UIDVALIDITY " + record.getUidValidity();
} else if (dataItem.equalsIgnoreCase("UNSEEN")) {
response += "UNSEEN " + record.getUnseen(username);
}
if (it.hasNext()) { response += " ";}
}
return response;
}
}
}
/**
* Convert a user-based full mailbox name into a server absolute name.
* Example:
* <br> Convert from "#mail.INBOX" for user "Fred.Flinstone" into
* absolute name: "#mail.Fred.Flintstone.INBOX"
*
* @returns String of absoluteName, null if not valid selection
*/
private String getAbsoluteName(String user, String fullMailboxName) {
if (fullMailboxName.equals(privateNamespace)) {
return fullMailboxName + user + privateNamespaceSeparator;
} else if (fullMailboxName.equals(privateNamespace
+ privateNamespaceSeparator)) {
return fullMailboxName + user + privateNamespaceSeparator;
} else if (fullMailboxName.startsWith(privateNamespace)) {
return new String(privateNamespace + privateNamespaceSeparator
+ user + privateNamespaceSeparator
+ fullMailboxName.substring(privateNamespace.length() + privateNamespaceSeparator.length()));
} else if (fullMailboxName.equals(otherUsersNamespace)) {
return null;
}else if (fullMailboxName.equals(otherUsersNamespace
+ otherUsersNamespaceSeparator)) {
return null;
} else if (fullMailboxName.startsWith(otherUsersNamespace)) {
return new String(privateNamespace + privateNamespaceSeparator
+ fullMailboxName.substring(otherUsersNamespace.length() + otherUsersNamespaceSeparator.length()));
} else if (fullMailboxName.startsWith(sharedNamespace)) {
return fullMailboxName;
} else {
return null;
}
}
private String getFullMailboxName(String user, String absoluteName) {
if(absoluteName.startsWith(privateNamespace)) {
if (absoluteName.equals(privateNamespace + privateNamespaceSeparator + user)) {
return new String(privateNamespace );
} else if (absoluteName.startsWith(privateNamespace + privateNamespaceSeparator + user)){
return new String(privateNamespace
+ absoluteName.substring(privateNamespace.length() + privateNamespaceSeparator.length() + user.length()));
} else {
// It's another users mailbox
// Where is separator between name and mailboxes?
int pos = absoluteName.substring(privateNamespace.length() + privateNamespaceSeparator.length()).indexOf(privateNamespaceSeparator);
return new String(otherUsersNamespace
+ otherUsersNamespaceSeparator
+ absoluteName.substring(pos ));
}
} else if (absoluteName.startsWith(sharedNamespace)) {
return absoluteName;
} else {
return null;
}
}
/**
* Return the file-system path to a given absoluteName mailbox.
*
* @param absoluteName the user-independent name of the mailbox
* @param owner string name of owner of mailbox
*/
private String getPath(String absoluteName, String owner) {
String path;
if (absoluteName.startsWith(privateNamespace)) {
String path1 = rootPath + owner;
String path2
= absoluteName.substring(privateNamespace.length()
+ privateNamespaceSeparator.length()
+ owner.length());
path = path1 + path2.replace(privateNamespaceSeparator.charAt(0), File.separatorChar);
} else if (absoluteName.startsWith(sharedNamespace)) {
String path3 = absoluteName.substring(namespaceToken.length());
path = rootPath + File.separator + path3.replace(privateNamespaceSeparator.charAt(0), File.separatorChar);
} else {
path = null;
}
return path;
}
public boolean createPrivateMailAccount(String user) {
if (user == null || user.equals("")) {
throw new RuntimeException("Bad parameter for createPrivateMailAccount.");
}
String userRootAbsName
= privateNamespace + privateNamespaceSeparator + user;
String userInboxAbsName
= userRootAbsName + privateNamespaceSeparator + "INBOX";
SimpleFolderRecord userRootRecord
= new SimpleFolderRecord(privateNamespace
+ privateNamespaceSeparator, user,
userRootAbsName);
SimpleFolderRecord userInboxRecord
= new SimpleFolderRecord(privateNamespace
+ privateNamespaceSeparator + "INBOX",
user, userInboxAbsName);
ACLMailbox userRootFolder = new FileMailbox();
ACLMailbox userInbox = new FileMailbox();
try{
userRootFolder.configure(conf);
userRootFolder.contextualize(context);
userRootFolder.compose(compMgr);
userRootFolder.prepareMailbox(user, userRootAbsName, user);
userInbox.configure(conf);
userInbox.contextualize(context);
userInbox.compose(compMgr);
userInbox.prepareMailbox(user, userInboxAbsName, user);
userRootFolder.initialize();
userRootFolder.setNotSelectableByAnyone(true);
userInbox.initialize();
userInbox.setRights(user, MailServer.MDA, "lrswi");
} catch (Exception e) {
logger.error("Exception creating new account: " + e);
return false;
}
userInboxRecord.initialize();
userInboxRecord.setUidValidity(userInbox.getUIDValidity());
userInboxRecord.setHighestUid(userInbox.getNextUID() -1);
userInboxRecord.setLookupRights(userInbox.getUsersWithLookupRights());
userInboxRecord.setReadRights(userInbox.getUsersWithReadRights());
userInboxRecord.setNotSelectableByAnyone(userInbox.isNotSelectableByAnyone());
userRootRecord.initialize();
userRootRecord.setLookupRights(userRootFolder.getUsersWithLookupRights());
userRootRecord.setReadRights(userRootFolder.getUsersWithReadRights());
userRootRecord.setNotSelectableByAnyone(userRootFolder.isNotSelectableByAnyone());
recordRep.store(userRootRecord);
recordRep.store(userInboxRecord);
//No one is using these mailboxes
//try {
// userRootFolder.destroy();
// userInbox.destroy();
//} catch (Exception e) {
// logger.error("Exception closing new account mailbox: " + e);
// return false;
//}
userRootFolder = null;
userInbox = null;
return true;
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/Mailbox.java
Index: Mailbox.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.util.List;
import java.util.Map;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMessage;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
import org.apache.james.core.EnhancedMimeMessage;
import org.apache.mailet.Mail;
/**
* Interface for objects representing an IMAP4rev1 mailbox (folder). Contains
* logical information and provides a simple API. There should be one instance
* of this class for every open IMAP mailbox.
* Implementations may choose to store this object or recreate it on access.
* Storing is recommended.
* <p>Several methods throw AccessControlException. In normal use, these
* shouldn't get thrown because the Host will have checked access before
* returning a reference to this mailbox. However, having the methods here
* throw this exception allows the acl to be changed while a mailbox is
* selected.
*
* Mailbox Related Flags (rfc2060 name attributes)
* \Noinferiors It is not possible for any child levels of hierarchy to
* exist under this name; no child levels exist now and none can be created
* in the future.
* \Noselect It is not possible to use this name as a selectable
* mailbox.
* \Marked The mailbox has been marked "interesting" by the server;
* the mailbox probably contains messages that have been added since the last
* time the mailbox was selected.
* \Unmarked The mailbox does not contain any additional messages
* since the last time the mailbox was selected.
*
* Message related flags.
* The flags allowed per message are specific to each mailbox.
* The minimum list (rfc2060 system flags) is:
* \Seen Message has been read
* \Answered Message has been answered
* \Flagged Message is "flagged" for urgent/special attention
* \Deleted Message is "deleted" for removal by later EXPUNGE
* \Draft Message has not completed composition (marked as a draft).
* \Recent Message is "recently" arrived in this mailbox. This session
* is the first session to have been notified about this message; subsequent
* sessions will not see \Recent set for this message. This flag can not be
* altered by the client.
* If it is not possible to determine whether or not this session
* is the first session to be notified about a message, then that message
* SHOULD be considered recent.
*
* Reference: RFC 2060
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public interface Mailbox
extends Configurable, Composable {
String SYSTEM_FLAGS = "\\Seen \\Answered \\Flagged \\Deleted \\Draft";
String RECENT_FLAG = "\\Recent";
/**
* Returns name of this mailbox relative to its parent in the mailbox
* hierarchy.
* Example: 'NewIdeas'
*
* @returns String name of mailbox relative to its immeadiate parent in
* the mailbox hierarchy.
*/
String getName();
/**
* Returns absolute, that is user-independent, hierarchical name of
* mailbox (including namespace)
* Example: '#mail.fred.flintstone.apache.James.NewIdeas'
*
* @returns String name of mailbox in absolute form
*/
String getAbsoluteName();
/** Returns namespace starting with namespace token.
* Example: '#mail'
*
* @returns String containing user-independent namespace of this mailbox.
*/
// public String getNamespace();
/** Returns true if the argument is the relative or absolute name of
* this mailbox
*
* @param name possible name for this Mailbox
* @returns true if name matches either getName() or getAbsoluteName()
*/
boolean matchesName(String name);
/**
* Returns the current unique id validity value of this mailbox.
*
* @returns int current 32 bit unique id validity value of this mailbox
*/
int getUIDValidity();
/**
* Returns the 32 bit uid available for the next message.
*
* @returns int the next UID that would be used.
*/
int getNextUID();
/**
* Returns mailbox size in octets. Should only include actual messages
* and not any implementation-specific data, such as message attributes.
*
* @returns int mailbox size in octets
*/
int getMailboxSize();
/**
* Indicates if child folders may be created. It does not indicate which
* users can create child folders.
*
* @returns boolean TRUE if inferiors aree allowed
*/
boolean getInferiorsAllowed();
/**
* Indicates if this folder may be selected by the specified user.
* Requires both that the mailbox is not NotSelectableByAnyone and that the
* user has at least read rights. It does not indicate whether user
* can write to mailbox
*
* @param username String represnting user
* @returns boolean TRUE if specified user can Select mailbox.
* @throws AccessControlException if username does not have lookup rights
*/
boolean isSelectable(String username) throws AccessControlException;
/**
* Indicates that messages have been added since this mailbox was last
* selected by any user.
*
* @returns boolean TRUE if new messages since any user last selected
* mailbox
*/
boolean isMarked();
/**
* Returns all flags supported by this mailbox.
* e.g. \Answered \Deleted
*
* @returns String a space seperated list of message flags which are
* supported by this mailbox.
*/
String getSupportedFlags();
/**
* Indicates if specified user can change any flag on a permanent basis,
* except for \Recent which can never be changed by a user.
*
* @param username String represnting user
* @returns true if specified user can change all flags permanently.
* @throws AccessControlException if username does not have lookup rights
*/
boolean allFlags(String username) throws AccessControlException;
/**
* Indicates which flags this user can change permanently. If allFlags()
* returns true for this user, then this method must have the same return
* value as getSupportedFlags.
*
* @param username String represnting user
* @returns String a space seperated list of message flags which this user
* can set permanently
*/
String getPermanentFlags( String username )
throws AccessControlException;
/**
* Indicates number of messages in folder
*
* @returns int number of messages
*/
int getExists();
/**
* Indicates no of messages with \Recent flag set
*
* @returns int no of messages with \Recent flag set
*/
int getRecent();
/**
* Remove \Recent flag from all messages in mailbox. Should be called
* whenever a user session finishes.
*/
void unsetRecent();
/**
* Indicates the oldest unseen message for the specified user.
*
* @returns int Message Sequence Number of first message without \Seen
* flag set for this User.
* <br> -1 means all messages have \Seen flag set for this user.
* <br> 0 means no message (Seen or unseen) in this mailbox.
*/
int getOldestUnseen( String user );
/**
* Indicates the number of unseen messages for the specified user.
*
* @returns int number of messages without \Seen flag set for this User.
*/
int getUnseen( String user );
/**
* Indicates state in which the mailbox will be opened by specified user.
* A return value of true indicates Read Only, false indicates Read-Write
* and an AccessControlException is thrown if user does not have read
* rights.
* <p>Implementations decide if Read Only means only lookup and read
* rights (lr) or lookup, read and keep seen rights (lrs). This may even
* vary between mailboxes.
*
* @param username String represnting user
* @returns true if specified user can only open the mailbox Read-Only.
* @throws AccessControlException if the user can not open this mailbox
* at least Read-Only.
*/
boolean isReadOnly( String username )
throws AccessControlException;
/**
* Mailbox Events are used to inform registered listeners of events in the
* Mailbox.
* Example if mail is delivered to an Inbox or if another user appends/
* deletes a message.
*/
void addMailboxEventListener( MailboxEventListener mel );
void removeMailboxEventListener( MailboxEventListener mel );
/**
* Stores a message in this mailbox. User must have insert rights.
*
* @param mail the message to be stored
* @param username String represnting user
* @returns boolean true if successful
* @throws AccessControlException if username does not have lookup rights for this mailbox.
* @throws AuthorizationException if username has lookup rights but does not have insert rights.
*/
boolean store( MimeMessage message, String username )
throws AccessControlException, AuthorizationException, IllegalArgumentException;
/**
* Stores a message in this mailbox, using passed MessageAttributes and
* Flags. User must have insert rights.
*
* @param mail the message to be stored
* @param username String represnting user
* @param attrs non-null MessageAttributes for use with this Message
* @param flags a Flags object for this message
* @returns boolean true if successful
* @throws AccessControlException if username does not have lookup rights
* for this mailbox.
* @throws AuthorizationException if username has lookup rights but does
* not have insert rights.
*/
boolean store( MimeMessage message,
String username,
MessageAttributes attrs,
Flags flags )
throws AccessControlException, AuthorizationException, IllegalArgumentException;
/**
* Retrieves a message given a message sequence number.
*
* @param msn the message sequence number
* @param username String represnting user
* @returns a Mail object containing the message, null if no message with
* the given msn.
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
EnhancedMimeMessage retrieve( int msn, String user )
throws AccessControlException, AuthorizationException;
/**
* Retrieves a message given a unique identifier.
*
* @param uid the unique identifier of a message
* @param username String represnting user
* @returns a Mail object containing the message, null if no message with
* the given msn.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
EnhancedMimeMessage retrieveUID( int uid, String user )
throws AccessControlException, AuthorizationException;
/**
* Marks a message for deletion given a message sequence number.
*
* @param msn the message sequence number
* @param username String represnting user
* @returns boolean true if successful.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
boolean markDeleted( int msn, String user )
throws AccessControlException, AuthorizationException;
/**
* Marks a message for deletion given a unique identifier.
*
* @param uidunique identifier
* @param username String represnting user
* @returns boolean true if successful, false if failed including no
* message with the given uid.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
boolean markDeletedUID( int uid, String user )
throws AccessControlException, AuthorizationException;
/**
* Returns the message attributes for a message.
*
* @param msn message sequence number
* @param username String represnting user
* @returns MessageAttributes for message, null if no such message.
* Changing the MessageAttributes object must not affect the actual
* MessageAttributes.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
MessageAttributes getMessageAttributes( int msn, String user )
throws AccessControlException, AuthorizationException;
/**
* Returns the message attributes for a message.
*
* @param uid unique identifier
* @param username String represnting user
* @returns MessageAttributes for message, null if no such message.
* Changing the MessageAttributes object must not affect the actual
* MessageAttributes.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
MessageAttributes getMessageAttributesUID( int uid, String user )
throws AccessControlException, AuthorizationException;
/**
* Updates the attributes of a message.
*
* @param MessageAttributes of a message already in this Mailbox
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
boolean updateMessageAttributes( MessageAttributes attrs, String user )
throws AccessControlException, AuthorizationException;
/**
* Get the IMAP-formatted String of flags for specified message.
*
* @param msn message sequence number for a message in this mailbox
* @param username String represnting user
* @returns flags for this message and user, null if no such message.
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
String getFlags( int msn, String user )
throws AccessControlException, AuthorizationException;
/**
* Get the IMAP-formatted String of flags for specified message.
*
* @param uid UniqueIdentifier for a message in this mailbox
* @param username String represnting user
* @returns flags for this message and user, null if no such message.
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
String getFlagsUID(int uid, String user)
throws AccessControlException, AuthorizationException;
/**
* Updates the flags of a message.
*
* @param msn message sequence number for a message in this mailbox
* @param username String represnting user
* @param request IMAP formatted string of flag request
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
boolean setFlags( int msn, String user, String request )
throws AccessControlException, AuthorizationException, IllegalArgumentException;
/**
* Updates the flags of a message.
*
* @param uid UniqueIdentifier for a message in this mailbox
* @param username String represnting user
* @param request IMAP formatted string of flag request
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
boolean setFlagsUID( int uid, String user, String request )
throws AccessControlException, AuthorizationException, IllegalArgumentException;
/**
* Returns the Internet Headers for a message. These are the top-level
* headers only, ie not MIME part headers or headers of encapsulated
* messages.
*
* @param msn message sequence number
* @param username String represnting user
* @returns InternetHeaders for message, null if no such message.
* Changing the InternetHeaders object must not affect the actual
* InternetHeaders of the underlying message.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
InternetHeaders getInternetHeaders( int msn, String user )
throws AccessControlException, AuthorizationException;
/**
* Returns the Internet Headers for a message. These are the top-level
* headers only, ie not MIME part headers or headers of encapsulated
* messages.
*
* @param uid UniqueIdentifier for a message in this mailbox
* @param username String represnting user
* @returns InternetHeaders for message, null if no such message.
* Changing the InternetHeaders object must not affect the actual
* InternetHeaders of the underlying message.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
InternetHeaders getInternetHeadersUID( int uid, String user )
throws AccessControlException, AuthorizationException;
/**
* Removes all messages marked Deleted. User must have delete rights.
*
* @param username String represnting user
* @returns true if successful
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has delete rights but does not
* have delete rights.
*/
boolean expunge( String user )
throws AccessControlException, AuthorizationException, IllegalArgumentException;
/**
* Establishes if specified user has lookup rights for this mailbox.
*
* @param username String represnting user
* @returns true if user has at least lookup rights
*/
boolean hasLookupRights( String user );
/**
* Establishes if specified user has create rights for this mailbox.
*
* @param username String represnting user
* @returns true if user has at create rights
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
*/
boolean hasCreateRights( String user )
throws AccessControlException;
/**
* Lists uids of messages in mailbox indexed by MSN.
*
* @param username String represnting user
* @returns List of Integers wrapping uids of message
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
*/
List listUIDs( String user );
/**
* Returns true once this Mailbox has been checkpointed.
* This may include resolving in-memory state with disk state.
* Implementations not requiring checkpointing should return immeadiately.
*
* @returns true if check completes normally, false otherwise.
*/
boolean checkpoint();
/**
* Mark this mailbox as not selectable by anyone.
* Example folders at the roots of hierarchies, e. #mail for each user.
*
* @param state true if folder is not selectable by anyone
*/
void setNotSelectableByAnyone( boolean state );
boolean isNotSelectableByAnyone();
/**
* Gets map of users to number of unseen messages. User key will only be
* present if getOldestUnseen() has been called, usually as a result of
* an IMAP SELECT or EXAMINE.
*/
Map getUnseenByUser();
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/MailboxEvent.java
Index: MailboxEvent.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.util.EventObject;
/**
* EventObject representing a change in a Mailbox which needs to be
* communicated to MailboxEventListeners.
* Uses include warning of addition/ deletion of messages.
*
* <p>Not currently used in this implementation
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class MailboxEvent
extends EventObject {
private String callingMailbox = null;
public MailboxEvent( final Object source, final String mailbox ) {
super( source );
callingMailbox = mailbox;
}
public String getMailbox() {
return callingMailbox;
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/MailboxEventListener.java
Index: MailboxEventListener.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.Serializable;
import java.util.EventListener;
/**
* Interface for objects that need to be informed of changes in a Mailbox.
*
* <p>Not currently active in this implementaiton
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public interface MailboxEventListener
extends EventListener, Serializable {
void receiveEvent( MailboxEvent me );
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/MailboxEventSource.java
Index: MailboxEventSource.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
/**
* Interface for objects that are sources for Mailbox Events. Mailbox Events
* are used to inform registered listeners of events in this Source. For
* example, if mail is delivered to an Inbox or if another user appends or
* deletes a message.
*
* <p>Not currently active in this implementation
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public interface MailboxEventSource {
/**
* Registers a MailboxEventListener.
*
* @param mel MailboxEventListener to be registered with this source.
*/
void addMailboxEventListener( MailboxEventListener mel );
/**
* Deregisters a MailboxEventListener.
*
* @param mel MailboxEventListener to be deregistered from this source.
*/
void removeMailboxEventListener( MailboxEventListener mel );
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/MailboxException.java
Index: MailboxException.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
/**
* Thrown on an inappropriate attempt to reference a mailbox.
* Includes attempting to create a mailbox that already exists and attempting
* to open a mailbox that does not exist.
* If status is ALREADY_EXISTS_REMOTELY or IF_CREATED_REMOTE then field
* remoteServer should be set to the url of the remote server, formatted for
* Mailbox Referral.
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class MailboxException extends Exception {
public final static String ALREADY_EXISTS_LOCALLY
= "Already exists locally";
public final static String ALREADY_EXISTS_REMOTELY
= "Already exists remotely";
public final static String IF_CREATED_LOCAL
= "If created, mailbox would be local";
public final static String IF_CREATED_REMOTE
= "If created, mailbox would be remote";
public final static String NOT_LOCAL
= "Does not exist locally, no further information available";
public final static String LOCAL_BUT_DELETED
= "Was local but has been deleted.";
protected String status = null;
protected String remoteServer = null;
/**
* Construct a new <code>MailboxException</code> instance.
*
* @param message The detail message for this exception (mandatory).
*/
public MailboxException(String message) {
super(message);
}
/**
* Construct a new <code>MailBoxException</code> instance.
*
* @param message The detail message for this exception (mandatory).
* @param aStatus String constant indicating condition
*/
public MailboxException(String message, String aStatus) {
super(message);
this.status = aStatus;
}
/**
* Construct a new <code>MailBoxException</code> instance.
*
* @param message The detail message for this exception (mandatory).
* @param aStatus String constant indicating condition
* @param sServer String indicating another server where Mailbox should be.
*/
public MailboxException(String message, String aStatus, String aServer) {
super(message);
this.status = aStatus;
this.remoteServer = aServer;
}
public String getStatus() {
return status;
}
public String getRemoteServer() {
return remoteServer;
}
public boolean isRemote() {
return ( status.equals(ALREADY_EXISTS_REMOTELY)
|| status.equals(IF_CREATED_REMOTE) ) ;
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/MessageAttributes.java
Index: MessageAttributes.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.util.Date;
/**
* Interface for objects holding IMAP4rev1 Message Attributes. Message
* Attributes should be set when a message enters a mailbox. Implementations
* are encouraged to implement and store MessageAttributes apart from the
* underlying message. This allows the Mailbox to respond to questions about
* very large message without needing to access them directly.
* <p> Note that the message in a mailbox have the same order using either
* Message Sequence Numbers or UIDs.
*
* Reference: RFC 2060 - para 2.3
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public interface MessageAttributes {
/**
* Provides the current Message Sequence Number for this message. MSNs
* change when messages are expunged from the mailbox.
*
* @returns int a positive non-zero integer
*/
int getMessageSequenceNumber();
/**
* Provides the unique identity value for this message. UIDs combined with
* a UIDValidity value form a unique reference for a message in a given
* mailbox. UIDs persist across sessions unless the UIDValidity value is
* incremented. UIDs are not copied if a message is copied to another
* mailbox.
*
* @returns int a 32-bit value
*/
int getUID();
/**
* Provides the date and time at which the message was received. In the
* case of delivery by SMTP, this SHOULD be the date and time of final
* delivery as defined for SMTP. In the case of messages copied from
* another mailbox, it shuld be the internalDate of the source message. In
* the case of messages Appended to the mailbox, example drafts, the
* internalDate is either specified in the Append command or is the
* current dat and time at the time of the Append.
*
* @returns Date imap internal date
*/
Date getInternalDate();
/**
* Returns IMAP formatted String representation of Date
*/
String getInternalDateAsString();
/**
* Provides the sizeof the message in octets.
*
* @returns int number of octets in message.
*/
int getSize();
/**
* Provides the Envelope structure information for this message.
* This is a parsed representation of the rfc-822 envelope information.
* This is not to be confused with the SMTP envelope!
*
* @returns String satisfying envelope syntax in rfc 2060.
*/
String getEnvelope();
/**
* Provides the Body Structure information for this message.
* This is a parsed representtion of the MIME structure of the message.
*
* @returns String satisfying body syntax in rfc 2060.
*/
String getBodyStructure();
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/MessageHeader.java
Index: MessageHeader.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.Serializable;
/**
* Class for holding the name-value pairs of an RFC822 or MIME header.
* Like javax.mail.Header but serializable
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class MessageHeader implements Serializable {
public static final String CRLF = "\r\n";
public static final String CRLFHTAB = "\r\n\t";
public static final String CRLFWS = "\r\n ";
private final String name;
private final String value;
public MessageHeader(String headerLine) {
int colon = headerLine.indexOf(":");
name = headerLine.substring(0, colon);
StringBuffer unwrapped = new StringBuffer(headerLine.length());
boolean finished = false;
int pos = colon + 1;
while (!finished) {
int crlf = headerLine.indexOf(CRLF, pos);
if (crlf == -1) {
unwrapped.append(headerLine.substring(pos));
finished = true;
} else {
unwrapped.append(headerLine.substring(pos, crlf));
unwrapped.append(" ");
pos = crlf +3;
}
}
value = unwrapped.toString();
}
public MessageHeader(String name, String value) {
this.name = name;
this.value = value;
}
/**
* Get the name, aka field name, of this header.
*
* @returns a String
*/
public String getName() {
return name;
}
/**
* Get the value, aka field value, of this Header
*
* @returns String
*/
public String getValue() {
return value;
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/RecordRepository.java
Index: RecordRepository.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.util.Iterator;
/**
* Interface for objects representing a Repository of FolderRecords.
* There should be a RecordRepository for every Host.
* <p>Note that there is no method for removing Records: an IMAP host is
* meant to retain information about deleted folders.
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
* @see FolderRecord
*/
public interface RecordRepository {
String RECORD = "RECORD";
/**
* Sets the location of this repository.
*
* @param rootPath String location of this repository
*/
void setPath( String rootPath );
/**
* Stores a folder record in this repository.
*
* @param fr FolderRecord to be stored
*/
void store( FolderRecord fr );
/**
* Returns Iterator over names of folders in repository
*
* @returns Iterator over Strings of AbsoluteNames of Folders. Calling
* objects cannot change contents of Iterator.
*/
Iterator getAbsoluteNames();
/**
* Retrieves a folder record given the folder's full name.
*
* @param folderAbsoluteName String name of a folder
* @returns FolderRecord for specified folder, null if no such FolderRecord
*/
FolderRecord retrieve( String folderAbsoluteName );
/**
* Tests if there is a folder record for the given folder name.
*
* @param folderAbsoluteName String name of a folder
* @returns boolean True if there is a record for the specified folder.
*/
boolean containsRecord( String folderAbsoluteName );
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/SimpleFolderRecord.java
Index: SimpleFolderRecord.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.Serializable;
import java.util.Map;
import java.util.Set;
import org.apache.avalon.framework.activity.Initializable;
/**
* Object representing the record of a folder in an IMAP on an IMAP Host.
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class SimpleFolderRecord
implements FolderRecord, Serializable, Initializable {
private final String fullName;
private final String owner;
private final String absoluteName;
private boolean nameInUse;
private boolean deleted;
private int uidValidity;
private int highestUID;
private Set usersWithLookupRights;
private Set usersWithReadRights;
private boolean marked;
private boolean notSelectableByAnyone;
private int exists;
private int recent;
private Map unseenByUser;
/**
* Constructor Records the full name, including namespace, of this mailbox
* relative, to a specified user, and the absolute name..
*
* @param mailboxName String mailbox hierarchical name including namespace
* @param user String a user. An empty user parameter indicates that the
* mailbox name is absolute.
*/
public SimpleFolderRecord(String mailboxName, String user,
String absName) {
fullName = mailboxName;
owner = user;
absoluteName = absName;
}
public void initialize() {
nameInUse = true;
deleted = false;
uidValidity = 1;
highestUID = 1;
}
public String getFullName() {
return fullName;
}
public String getUser() {
return owner;
}
public String getAbsoluteName() {
return absoluteName;
}
public void setNameInUse(boolean state) {
nameInUse = state;
}
public boolean isNameInUse() {
return nameInUse;
}
public void setDeleted(boolean state) {
deleted = state;
}
public boolean isDeleted() {
return deleted;
}
public void setUidValidity(int uidV) {
if (uidV > uidValidity) {
uidValidity = uidV;
}
}
public int getUidValidity() {
return uidValidity;
}
public void setHighestUid(int uid) {
highestUID = uid;
}
public int getHighestUid() {
return highestUID;
}
public void setLookupRights(Set users) {
usersWithLookupRights = users;
}
public boolean hasLookupRights(String user) {
return usersWithLookupRights.contains(user) ;
}
public void setReadRights(Set users) {
usersWithReadRights = users;
}
public boolean hasReadRights(String user) {
return usersWithReadRights.contains(user) ;
}
public void setMarked(boolean mark) {
marked = mark;
}
public boolean isMarked() {
return marked;
}
public void setNotSelectableByAnyone(boolean state) {
notSelectableByAnyone = state;
}
public boolean isNotSelectableByAnyone() {
return notSelectableByAnyone;
}
public boolean isSelectable(String user) {
return (!notSelectableByAnyone && hasReadRights(user));
}
public void setExists(int num) {
exists = num;
}
public int getExists() {
return exists;
}
public void setRecent(int num) {
recent = num;
}
public int getRecent() {
return recent;
}
public void setUnseenbyUser(Map unseen) {
unseenByUser = unseen;
}
public int getUnseen(String user) {
if (unseenByUser.containsKey(user)) {
Integer unseen = ((Integer)unseenByUser.get(user));
return unseen.intValue();
} else {
return exists;
}
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/SimpleMessageAttributes.java
Index: SimpleMessageAttributes.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.*;
import java.util.*;
import javax.mail.BodyPart;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.*;
import org.apache.james.core.EnhancedMimeMessage;
import org.apache.james.util.RFC822DateFormat;
import org.apache.log.LogKit;
import org.apache.log.Logger;
import org.apache.mailet.*;
/**
* Attributes of a Message in IMAP4rev1 style. Message
* Attributes should be set when a message enters a mailbox.
* <p> Note that the message in a mailbox have the same order using either
* Message Sequence Numbers or UIDs.
* <p> reinitialize() must be called on deserialization to reset Logger
*
* Reference: RFC 2060 - para 2.3
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class SimpleMessageAttributes
implements MessageAttributes, Serializable {
private final static String SP = " ";
private final static String NIL = "NIL";
private final static String Q = "\"";
private final static String LB = "(";
private final static String RB = ")";
private final static boolean DEBUG = true;
private final static String MULTIPART = "MULTIPART";
private final static String MESSAGE = "MESSAGE";
//Only available in first incarnation of object
private transient Logger logger = LogKit.getLoggerFor("james.JamesHost");
private int uid;
private int messageSequenceNumber;
private Date internalDate;
private String internalDateString;
private String bodyStructure;
private String envelope;
private int size;
private int lineCount;
private MessageAttributes[] parts;
private List headers;
//rfc822 or MIME header fields
//arrays only if multiple values allowed under rfc822
private String subject;
private String[] from;
private String[] sender;
private String[] replyTo;
private String[] to;
private String[] cc;
private String[] bcc;
private String[] inReplyTo;
private String[] date;
private String[] messageID;
private String contentType;
private String primaryType; // parsed from contentType
private String secondaryType; // parsed from contentType
private Set parameters; // parsed from contentType
private String contentID;
private String contentDesc;
private String contentEncoding;
SimpleMessageAttributes() {
}
void reinitialize() {
logger = LogKit.getLoggerFor("james.JamesHost");
}
void setAttributesFor(MimeMessage msg) throws MessagingException {
size = msg.getSize();
try {
internalDate = msg.getSentDate();
if (DEBUG) logger.debug("setAttributes - getSentDate: " + internalDate);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getSentDate: " + me);
internalDate = new Date();
}
if (DEBUG) {
logger.debug("HeaderLines recieved were: ");
Enumeration enum = msg.getAllHeaderLines();
while(enum.hasMoreElements()) {
logger.debug((String)enum.nextElement());
}
// logger.debug("Header objects available are:");
// Enumeration e = msg.getAllHeaders();
// while(e.hasMoreElements()) {
//Header h = (Header) e.nextElement();
//logger.debug("Name: " + h.getName());
//logger.debug("Value: " + h.getValue());
// }
}
internalDateString = RFC822DateFormat.toString(internalDate); // not right format
parseMimePart(msg);
envelope = null;
bodyStructure = null;
}
void setUID(int thisUID) {
uid = thisUID;
}
/**
* Parses key data items from a MimeMessage for seperate storage.
*/
void parseMimePart(MimePart part) {
// Section 1 - Message Headers
if (part instanceof MimeMessage) {
try {
subject = ((MimeMessage)part).getSubject();
if (DEBUG) logger.debug("parseMessage - subject: " + subject);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getSubject: " + me);
}
}
try {
from = part.getHeader("From");
if (DEBUG) logger.debug("parseMessage - from: " + from);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(From): " + me);
}
try {
sender = part.getHeader("Sender");
if (DEBUG) logger.debug("parseMessage - sender: " + sender);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(Sender): " + me);
}
try {
replyTo = part.getHeader("Reply To");
if (DEBUG) logger.debug("parseMessage - ReplyTo: " + replyTo);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(Reply To): " + me);
}
try {
to = part.getHeader("To");
if (DEBUG) logger.debug("parseMessage - To: " + to);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(To): " + me);
}
try {
cc = part.getHeader("Cc");
if (DEBUG) logger.debug("parseMessage - cc: " + cc);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(To): " + me);
}
try {
bcc = part.getHeader("Bcc");
if (DEBUG) logger.debug("parseMessage - bcc: " + bcc);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(To): " + me);
}
try {
inReplyTo = part.getHeader("In Reply To");
if (DEBUG) logger.debug("parseMessage - In Reply To: " + inReplyTo);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(In Reply To): " + me);
}
try {
date = part.getHeader("Date");
if (DEBUG) logger.debug("parseMessage - date: " + date);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(Date): " + me);
}
try {
messageID = part.getHeader("Message-ID");
if (DEBUG) logger.debug("parseMessage - messageID: " + messageID);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getHeader(messageID): " + me);
}
String contentTypeLine = null;
try {
contentTypeLine = part.getContentType();
if (DEBUG) logger.debug("parseMessage - contentType: " + contentTypeLine);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getContentType(): " + me);
}
if (contentTypeLine !=null ) {
decodeContentType(contentTypeLine);
}
try {
contentID = part.getContentID();
if (DEBUG) logger.debug("parseMessage - contentID: " + contentID);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getContentUD(): " + me);
}
try {
contentDesc = part.getDescription();
if (DEBUG) logger.debug("parseMessage - contentDesc: " + contentDesc);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getDescription(): " + me);
}
try {
contentEncoding = part.getEncoding();
if (DEBUG) logger.debug("parseMessage - contentEncoding: " + contentEncoding);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getEncoding(): " + me);
}
if (DEBUG) {
try {
String contentDisposition = part.getDisposition();
logger.debug("parseMessage - contentDisposition: " + contentEncoding);
} catch (MessagingException me) {
logger.debug("Messaging Exception for getEncoding(): " + me);
}
}
try {
lineCount = part.getLineCount();
if (DEBUG) logger.debug("parseMessage - Line Count: " + lineCount);
} catch (MessagingException me) {
if (DEBUG) logger.debug("Messaging Exception for getLineCount(): " + me);
if (DEBUG) logger.debug(me.getMessage());
} catch (Exception e) {
if (DEBUG) logger.debug("Exception for getLineCount(): " + e);
if (DEBUG) logger.debug("Exception message was: " + e.getMessage());
}
// Recurse through any embedded parts
if (primaryType.equalsIgnoreCase(MULTIPART)) {
MimeMultipart container;
try {
container =(MimeMultipart) part.getContent();
int count = container.getCount();
logger.info("This part contains " + count + " parts.");
parts = new SimpleMessageAttributes[count];
for (int i = 0; i < count ; i ++) {
logger.info("Getting embedded part: " + i);
BodyPart nextPart = container.getBodyPart(i);
if (nextPart instanceof MimePart) {
SimpleMessageAttributes partAttrs = new SimpleMessageAttributes();
partAttrs.parseMimePart((MimePart)nextPart);
parts[i] = partAttrs;
} else {
logger.info("Found a non-Mime bodyPart");
}
logger.info("Finished with embedded part: " + i);
}
} catch (Exception e) {
logger.debug("Messaging Exception for getContent(): " + e);
}
} else if (primaryType.equalsIgnoreCase("message")) {
logger.info("This part contains an embedded message of subtype: " + secondaryType);
logger.info("Uses java class: " + part.getClass().getName());
if (secondaryType.equalsIgnoreCase("RFC822")) {
try {
EnhancedMimeMessage message = new EnhancedMimeMessage(Session.getDefaultInstance(System.getProperties(), null), part.getInputStream());
SimpleMessageAttributes msgAttrs = new SimpleMessageAttributes();
msgAttrs.setAttributesFor(message);
if (part instanceof MimeMessage) {
MimeMessage msg1 = (MimeMessage) part;
EnhancedMimeMessage message2 = new EnhancedMimeMessage(msg1);
SimpleMessageAttributes msgAttrs2 = new SimpleMessageAttributes();
msgAttrs.setAttributesFor(message2);
}
parts = new SimpleMessageAttributes[1];
parts[0] = msgAttrs;
} catch (Exception e) {
logger.error("Error interpreting a message/rfc822: " + e);
e.printStackTrace();
}
} else {
logger.info("Unknown subtype of message encountered.");
}
logger.info("Finished with embedded message. " );
}
}
/**
* Builds IMAP envelope String from pre-parsed data.
*/
String parseEnvelope() {
List response = new ArrayList();
response.add( LB + Q + internalDateString + Q + SP);
if (subject != null && (!subject.equals(""))) {
response.add( Q + subject + Q + SP );
} else {
response.add( NIL + SP );
}
if (from != null && from.length > 0) {
response.add(LB);
for (int i=0; i<from.length; i++) {
response.add(parseAddress( from[i]) );
}
response.add(RB);
} else {
response.add( NIL);
}
response.add(SP);
if (sender != null && sender.length >0) {
if (DEBUG) logger.debug("parsingEnvelope - sender[0] is: " + sender[0]);
//Check for Netscape feature - sender is local part only
if (sender[0].indexOf("@") == -1) {
response.add(LB + (String)response.get(3) + RB); //first From address
} else {
response.add(LB);
for (int i=0; i<sender.length; i++) {
response.add( parseAddress(sender[i]));
}
response.add(RB);
}
} else {
if (from != null && from.length > 0) {
response.add(LB + (String)response.get(3) + RB); //first From address
} else {
response.add( NIL);
}
}
response.add(SP);
if (replyTo != null && replyTo.length >0) {
if (replyTo[0].indexOf("@") == -1) {
response.add(LB + (String)response.get(3) + RB); //first From address
} else {
response.add(LB);
for (int i=0; i<replyTo.length; i++) {
response.add( parseAddress(replyTo[i]));
}
response.add(RB);
}
} else {
if (from != null && from.length > 0) {
response.add(LB + (String)response.get(3) + RB); //first From address
} else {
response.add( NIL);
}
}
response.add(SP);
if (to != null && to.length >0) {
response.add(LB);
for (int i=0; i<to.length; i++) {
response.add( parseAddress(to[i]));
}
response.add(RB);
} else {
response.add( NIL);
}
response.add(SP);
if (cc != null && cc.length >0) {
response.add(LB);
for (int i=0; i<cc.length; i++) {
response.add( parseAddress(cc[i]));
}
response.add(RB);
} else {
response.add( NIL);
}
response.add(SP);
if (bcc != null && bcc.length >0) {
response.add(LB);
for (int i=0; i<bcc.length; i++) {
response.add( parseAddress(bcc[i]));
}
response.add(RB);
} else {
response.add( NIL);
}
response.add(SP);
if (inReplyTo != null && inReplyTo.length>0) {
response.add( inReplyTo[0]);
} else {
response.add( NIL);
}
response.add(SP);
if (messageID != null && messageID.length>0) {
response.add(Q + messageID[0] + Q);
} else {
response.add( NIL);
}
response.add(RB);
StringBuffer buf = new StringBuffer(16 * response.size());
for (int j=0; j<response.size(); j++) {
buf.append((String)response.get(j));
}
return buf.toString();
}
/**
* Parses a String email address to an IMAP address string.
*/
String parseAddress(String address) {
logger.info("Parsing address: " + address);
int comma = address.indexOf(",");
StringBuffer buf = new StringBuffer();
if (comma == -1) { //single address
buf.append(LB);
InternetAddress netAddr = null;
try {
netAddr = new InternetAddress(address);
} catch (AddressException ae) {
return null;
}
String personal = netAddr.getPersonal();
if (personal != null && (!personal.equals(""))) {
buf.append(Q + personal + Q);
} else {
buf.append( NIL);
}
buf.append( SP);
buf.append( NIL) ; // should add route-addr
buf.append( SP);
try {
MailAddress mailAddr = new MailAddress(netAddr);
buf.append(Q + mailAddr.getUser() + Q);
buf.append(SP);
buf.append(Q + mailAddr.getHost() + Q);
} catch (ParseException pe) {
buf.append( NIL + SP + NIL);
}
buf.append(RB);
} else {
buf.append(parseAddress(address.substring(0, comma)));
buf.append(SP);
buf.append(parseAddress(address.substring(comma + 1)));
}
return buf.toString();
}
/**
* Decode a content Type header line into types and parameters pairs
*/
void decodeContentType(String rawLine) {
if (DEBUG) logger.debug("decoding: " + rawLine);
int slash = rawLine.indexOf("/");
if( slash == -1){
if (DEBUG) logger.debug("decoding ... no slash found");
return;
} else {
primaryType = rawLine.substring(0, slash).trim();
}
int semicolon = rawLine.indexOf(";");
if (semicolon == -1) {
if (DEBUG) logger.debug("decoding ... no semicolon found");
secondaryType = rawLine.substring(slash + 1).trim();
return;
}
// have parameters
parameters = new HashSet();
secondaryType = rawLine.substring(slash + 1, semicolon).trim();
int pos = semicolon;
int nextsemi = rawLine.indexOf(";", pos+1);
while (nextsemi != -1) {
if (DEBUG) logger.debug("decoding ... found another semicolon");
String param = rawLine.substring(pos + 1, nextsemi);
int esign = param.indexOf("=") ;
if (esign == -1) {
if (DEBUG) logger.debug("Whacky parameter found: " + param);
} else {
String name = param.substring(0, esign).trim();
String value = param.substring(esign + 1).trim();
parameters.add(name + SP + value);
if (DEBUG) logger.debug("Found parameter: " + name + SP + value);
}
pos = nextsemi;
nextsemi = rawLine.indexOf(";", pos +1);
}
String lastParam = rawLine.substring(pos + 1);
int esign = lastParam.indexOf("=") ;
if (esign == -1) {
if (DEBUG) logger.debug("Whacky parameter found: " + lastParam);
} else {
String name = lastParam.substring(0, esign).trim();
String value = lastParam.substring(esign + 1).trim();
parameters.add(Q + name + Q + SP + Q + value + Q);
if (DEBUG) logger.debug("Found parameter: " + name + SP + value);
}
}
String parseBodyFields() {
logger.debug("Parsing body fields");
StringBuffer buf = new StringBuffer();
if (parameters == null || parameters.isEmpty()) {
buf.append(NIL);
} else {
buf.append(LB);
Iterator it = parameters.iterator();
while(it.hasNext()) {
buf.append((String)it.next());
}
buf.append(RB);
}
buf.append(SP);
if(contentID == null) {
buf.append(NIL);
} else {
buf.append(Q + contentID + Q);
}
buf.append(SP);
if(contentDesc == null) {
buf.append(NIL);
} else {
buf.append(Q + contentDesc + Q);
}
buf.append(SP);
if(contentEncoding == null) {
buf.append(NIL);
} else {
buf.append(Q + contentEncoding + Q);
}
buf.append(SP);
buf.append(size);
return buf.toString();
}
/**
* Produce the IMAP formatted String for the BodyStructure of a pre-parsed MimeMessage
*/
String parseBodyStructure() {
logger.debug("Parsing bodyStructure.");
try {
String fields = parseBodyFields();
StringBuffer buf = new StringBuffer();
buf.append(LB);
if (primaryType.equalsIgnoreCase("Text")) {
logger.debug("Assembling bodystrucuture for type TEXT.");
buf.append("\"Text\" \"" + secondaryType + "\" ");
buf.append(fields + " " + lineCount);
} else if (primaryType.equalsIgnoreCase(MESSAGE) && secondaryType.equalsIgnoreCase("rfc822")) {
logger.debug("Assembling bodyStructure for type MESSAGE/FRC822");
buf.append("\"MESSAGE\" \"RFC822\" ");
buf.append(fields + SP);
((SimpleMessageAttributes)parts[0]).reinitialize(); // reset transient logger
buf.append(parts[0].getEnvelope() + SP);
buf.append(parts[0].getBodyStructure() + SP);
buf.append(lineCount);
} else if (primaryType.equalsIgnoreCase(MULTIPART)) {
logger.debug("Assembling bodystructure for type MULTIPART");
for (int i=0; i<parts.length; i++) {
logger.debug("Parsing part: " + i);
((SimpleMessageAttributes)parts[i]).reinitialize(); // reset transient logger
buf.append(parts[i].getBodyStructure());
}
buf.append(SP + secondaryType);
}
buf.append(RB);
return buf.toString();
} catch (Exception e) {
logger.error("Exception while parsing BodyStrucuture: " + e);
e.printStackTrace();
throw new RuntimeException("Exception in parseBodyStructure");
}
}
/**
* Provides the current Message Sequence Number for this message. MSNs
* change when messages are expunged from the mailbox.
*
* @returns int a positive non-zero integer
*/
public int getMessageSequenceNumber() {
return messageSequenceNumber;
}
void setMessageSequenceNumber(int newMsn) {
messageSequenceNumber = newMsn;
}
/**
* Provides the unique identity value for this message. UIDs combined with
* a UIDValidity value form a unique reference for a message in a given
* mailbox. UIDs persist across sessions unless the UIDValidity value is
* incremented. UIDs are not copied if a message is copied to another
* mailbox.
*
* @returns int a 32-bit value
*/
public int getUID() {
return uid;
}
/**
* Provides the date and time at which the message was received. In the
* case of delivery by SMTP, this SHOULD be the date and time of final
* delivery as defined for SMTP. In the case of messages copied from
* another mailbox, it shuld be the internalDate of the source message. In
* the case of messages Appended to the mailbox, example drafts, the
* internalDate is either specified in the Append command or is the
* current dat and time at the time of the Append.
*
* @returns Date imap internal date
*/
public Date getInternalDate() {
return internalDate;
}
public String getInternalDateAsString() {
return internalDateString;
}
/**
* Provides the sizeof the message in octets.
*
* @returns int number of octets in message.
*/
public int getSize() {
return size;
}
/**
* Provides the Envelope structure information for this message. This is a parsed representation of the rfc-822 envelope information. This is not to be confused with the SMTP envelope!
*
* @returns String satisfying envelope syntax in rfc 2060.
*/
public String getEnvelope() {
return parseEnvelope();
}
/**
* Provides the Body Structure information for this message. This is a parsed representtion of the MIME structure of the message.
*
* @returns String satisfying body syntax in rfc 2060.
*/
public String getBodyStructure() {
return parseBodyStructure();
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/SimpleSystem.java
Index: SimpleSystem.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.util.*;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.james.AuthenticationException;
/**
* A simple, single-server, implementation of IMAPSystem.
*
* References: rfc 2060, rfc 2193, rfc 2221
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class SimpleSystem
implements IMAPSystem, Component, Initializable {
private static final String namespaceToken = "#";
private static final String hierarchySeperator = ".";
private static final String namespace
= "((\"#mail.\" \".\")) ((\"#users.\" \".\")) ((\"#shared.\" \".\"))";
private static String singleServer;
private Set servers = new HashSet();
private Context context;
private Configuration conf;
private ComponentManager compMgr;
public void configure(Configuration conf) throws ConfigurationException {
this.conf = conf;
}
public void contextualize(Context context) {
this.context = context;
}
public void compose(ComponentManager comp) {
compMgr = comp;
}
public void initialize() throws Exception {
// Derive namespace and namespaceToken from conf
singleServer = (String) context.get("HostName");
servers.add(singleServer);
}
/**
* Returns the token indicating a namespace. Implementation dependent but
* by convention, '#'.
* Example: #news.org.apache vs #mail.org.apache
*/
public String getNamespaceToken() {
return namespaceToken;
}
/**
* Returns the home server (server with user's INBOX) for specified user.
* Enables Login Referrals per RFC2221. (Ie user attempts to login to a
* server which is not their Home Server.) The returned string must comply
* with RFC2192, IMAP URL Scheme.
*
* @param username String representation of a user
* @returns String holding an IMAP URL for the user's home server
* @throws AuthenticationException if this System does not recognise
* the user.
*/
public String getHomeServer(String username)
throws AuthenticationException {
return singleServer;
}
/**
* Returns the character used as a mail hierarchy seperator in a given
* namespace. A namespace must use the same seperator at all levels of
* hierarchy.
* <p>Recommendations (from rfc 2683) are period (US)/ full stop (Brit),
* forward slash or backslash.
*
* @param namespace String identifying a namespace
* @returns char, usually '.', '/', or '\'
*/
public String getHierarchySeperator(String namespace) {
return hierarchySeperator;
}
/**
* Provides the set of namesapces a given user can access. Implementations
* should but are not required to reveal all namespaces that a user can
* access. Different namespaces may be handled by different
* <code>IMAPHosts</code>
*
* @param username String identifying a user of this System
* @returns String whose contents should be a space seperated triple
* <personal namespaces(s)> space <other users' namespace(s)> space
* <shared namespace(s)>, per RFC2342
*/
public String getNamespaces(String username) {
return namespace;
}
/**
* Returns an iterator over the collection of servers on which this user
* has access. The collection should be unmodifiable.
* Enable Mailbox Referrals - RFC 2193.
*
* @param username String identifying a user
* @return iterator over a collection of strings
*/
public Iterator getAccessibleServers(String username) {
return Collections.unmodifiableSet(servers).iterator();
}
}
1.1 jakarta-james/src/java/org/apache/james/imapserver/SingleThreadedConnectionHandler.java
Index: SingleThreadedConnectionHandler.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import javax.mail.internet.*;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
import org.apache.avalon.cornerstone.services.scheduler.PeriodicTimeTrigger;
import org.apache.avalon.cornerstone.services.scheduler.Target;
import org.apache.avalon.cornerstone.services.scheduler.TimeScheduler;
import org.apache.james.AccessControlException;
import org.apache.james.AuthenticationException;
import org.apache.james.AuthorizationException;
import org.apache.james.Constants;
import org.apache.james.services.*;
import org.apache.james.util.InternetPrintWriter;
import org.apache.log.LogKit;
import org.apache.log.Logger;
/**
* An IMAP Handler handles one IMAP connection. TBC - it may spawn worker
* threads someday.
*
* <p> Based on SMTPHandler and POP3Handler by Federico Barbieri <sc...@systemy.it>
*
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class SingleThreadedConnectionHandler
extends BaseCommand
implements ConnectionHandler, Composable, Configurable,
Initializable, Disposable, Target, MailboxEventListener {
//mainly to switch on stack traces and catch responses;
private static final boolean DEEP_DEBUG = true;
// Connection states
private static final int NON_AUTHENTICATED = 0;
private static final int AUTHENTICATED = 1;
private static final int SELECTED = 2;
private static final int LOGOUT = 3;
// Connection termination options
private static final int NORMAL_CLOSE = 0;
private static final int OK_BYE = 1;
private static final int UNTAGGED_BYE = 2;
private static final int TAGGED_NO = 3;
private static final int NO_BYE = 4;
// Basic response types
private static final String OK = "OK";
private static final String NO = "NO";
private static final String BAD = "BAD";
private static final String UNTAGGED = "*";
private static final String SP = " ";
private static final String VERSION = "IMAP4rev1";
private static final String CAPABILITY_RESPONSE = "CAPABILITY " + VERSION
+ " NAMESPACE" + " ACL"; //add as implemented
private static final String LIST_WILD = "*";
private static final String LIST_WILD_FLAT = "%";
private static final char[] CTL = {};
private static final String[] ATOM_SPECIALS
= {"(", ")", "{", " ", LIST_WILD, LIST_WILD_FLAT,};
private static final String AUTH_FAIL_MSG
= "NO Command not authorized on this mailbox";
private static final String BAD_LISTRIGHTS_MSG
= "BAD Command should be <tag> <LISTRIGHTS> <mailbox> <identifier>";
private static final String BAD_MYRIGHTS_MSG
= "BAD Command should be <tag> <MYRIGHTS> <mailbox>";
private static final String BAD_LIST_MSG
= "BAD Command should be <tag> <LIST> <reference name> <mailbox>";
private static final String BAD_LSUB_MSG
= "BAD Command should be <tag> <LSUB> <reference name> <mailbox>";
private static final String NO_NOTLOCAL_MSG
= "NO Mailbox does not exist on this server";
private Logger securityLogger = LogKit.getLoggerFor("james.Security");
private MailServer mailServer;
private UsersRepository users;
private TimeScheduler scheduler;
private Socket socket;
private BufferedReader in;
private PrintWriter out;
private OutputStream outs;
private String remoteHost;
private String remoteIP;
private String softwaretype = "JAMES IMAP4rev1 Server " + Constants.SOFTWARE_VERSION;
private int state;
private String user;
private IMAPSystem imapSystem;
private Host imapHost;
private String namespaceToken;
private String currentNamespace = null;
private String currentSeperator = null;
private String commandRaw;
//currentFolder holds the client-dependent absolute address of the current
//folder, that is current Namespace and full mailbox hierarchy.
private String currentFolder = null;
private ACLMailbox currentMailbox = null;
private boolean currentIsReadOnly = false;
private boolean connectionClosed = false;
private String tag;
private boolean checkMailboxFlag = false;
private int exists;
private int recent;
private List sequence;
public void compose( final ComponentManager componentManager )
throws ComponentException {
mailServer = (MailServer)componentManager.
lookup("org.apache.james.services.MailServer");
users = (UsersRepository)componentManager.
lookup("org.apache.james.services.UsersRepository");
scheduler = (TimeScheduler)componentManager.
lookup("org.apache.avalon.cornerstone.services.scheduler.TimeScheduler");
imapSystem = (IMAPSystem)componentManager.
lookup("org.apache.james.imapserver.IMAPSystem");
imapHost = (Host)componentManager.
lookup("org.apache.james.imapserver.Host");
}
public void initialize() throws Exception {
getLogger().info("SingleThreadedConnectionHandler starting ...");
namespaceToken = imapSystem.getNamespaceToken();
getLogger().info("SingleThreadedConnectionHandler initialized");
}
/**
* Handle a connection.
* This handler is responsible for processing connections as they occur.
*
* @param connection the connection
* @exception IOException if an error reading from socket occurs
* @exception ProtocolException if an error handling connection occurs
*/
public void handleConnection( final Socket connection )
throws IOException {
try {
this.socket = connection;
in = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
outs = socket.getOutputStream();
out = new InternetPrintWriter(outs, true);
remoteHost = socket.getInetAddress ().getHostName ();
remoteIP = socket.getInetAddress ().getHostAddress ();
} catch (Exception e) {
getLogger().error("Cannot open connection from " + remoteHost + " ("
+ remoteIP + "): " + e.getMessage());
}
getLogger().info("Connection from " + remoteHost + " (" + remoteIP + ")");
try {
final PeriodicTimeTrigger trigger = new PeriodicTimeTrigger( timeout, -1 );
scheduler.addTrigger( this.toString(), trigger, this );
if (false) { // arbitrary rejection of connection
// could screen connections by IP or host or implement
// connection pool management
connectionClosed = closeConnection(UNTAGGED_BYE,
" connection rejected.",
"");
} else {
if (false) { // connection is pre-authenticated
out.println(UNTAGGED + SP + "PREAUTH" + SP + VERSION + SP
+ "server" + SP + this.helloName + SP
+ "logged in as" + SP + user);
state = AUTHENTICATED;
user = "preauth user";
securityLogger.info("Pre-authenticated connection from "
+ remoteHost + "(" + remoteIP
+ ") received by SingleThreadedConnectionHandler");
} else {
out.println(UNTAGGED + SP + OK + SP + VERSION + SP
+ "server " + this.helloName + SP + "ready.");
state = NON_AUTHENTICATED;
user = "unknown";
securityLogger.info("Non-authenticated connection from "
+ remoteHost + "(" + remoteIP
+ ") received by SingleThreadedConnectionHandler");
}
while (parseCommand(in.readLine())) {
scheduler.resetTrigger(this.toString());
}
}
if (!connectionClosed) {
connectionClosed
= closeConnection(UNTAGGED_BYE,
"Server error, closing connection", "");
}
} catch (Exception e) {
// This should never happen once code is debugged
getLogger().error("Exception during connection from " + remoteHost
+ " (" + remoteIP + ") : " + e.getMessage());
e.printStackTrace();
connectionClosed = closeConnection(UNTAGGED_BYE,
"Error processing command.", "");
}
scheduler.removeTrigger(this.toString());
}
public void targetTriggered( final String triggerName ) {
getLogger().info("Connection timeout on socket");
connectionClosed = closeConnection(UNTAGGED_BYE,
"Autologout. Idle too long.", "");
}
private boolean closeConnection( int exitStatus,
String message1,
String message2 ) {
scheduler.removeTrigger(this.toString());
if (state == SELECTED) {
currentMailbox.removeMailboxEventListener(this);
imapHost.releaseMailbox(user, currentMailbox);
}
try {
switch(exitStatus) {
case 0 :
out.println(UNTAGGED + SP + "BYE" + SP + "server logging out");
out.println(tag + SP + OK + SP + "LOGOUT completed");
break;
case 1 :
out.println(UNTAGGED + SP + "BYE" + SP + message1);
out.println(tag + SP + OK + SP + message2);
break;
case 2:
out.println(UNTAGGED + SP + "BYE" + SP + message1);
break;
case 3 :
out.println(tag + SP + NO + SP + message1);
break;
case 4 :
out.println(UNTAGGED + SP + "BYE" + SP + message1);
out.println(tag + SP + NO + SP + message2);
break;
}
out.flush();
socket.close();
getLogger().info("Connection closed" + SP + exitStatus + SP + message1
+ SP + message2);
} catch (IOException ioe) {
getLogger().error("Exception while closing connection from " + remoteHost
+ " (" + remoteIP + ") : " + ioe.getMessage());
try {
socket.close();
} catch (IOException ioe2) {
}
}
return true;
}
private boolean parseCommand(String next) {
commandRaw = next;
String folder = null;
String command = null;
boolean subscribeOnly = false;
if (commandRaw == null) return false;
// getLogger().debug("Command recieved: " + commandRaw + " from " + remoteHost
// + "(" + remoteIP + ")");
//String command = commandRaw.trim();
StringTokenizer commandLine = new StringTokenizer(commandRaw.trim(), " ");
int arguments = commandLine.countTokens();
if (arguments == 0) {
return true;
} else {
String test = commandLine.nextToken();
if (test.length() < 10) {// this stops overlong junk.
// we should validate the tag contents
tag = test;
} else {
out.println(UNTAGGED + SP + BAD + SP + "tag too long");
return true;
}
}
if (arguments > 1) {
String test = commandLine.nextToken();
if (test.length() < 13) {// this stops overlong junk.
// we could validate the command contents,
// but may not be worth it
command = test;
}
else {
out.println(tag + SP + BAD + SP
+ "overlong command attempted");
return true;
}
} else {
out.println(UNTAGGED + SP + BAD + SP + "no command sent");
return true;
}
// At this stage we have a tag and a string which may be a command
// Start with commands that are valid in any state
// CAPABILITY, NOOP, LOGOUT
if (command.equalsIgnoreCase("CAPABILITY")) {
out.println(UNTAGGED + SP + CAPABILITY_RESPONSE);
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
out.println(tag + SP + OK + SP + "CAPABILITY completed");
getLogger().debug("Capability command completed for " + remoteHost
+ "(" + remoteIP + ")");
return true;
} else if (command.equalsIgnoreCase("NOOP")) {
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
// we could send optional untagged status responses as well
out.println(tag + SP + OK + SP + "NOOP completed");
getLogger().debug("Noop command completed for " + remoteHost
+ "(" + remoteIP + ")");
return true;
} else if (command.equalsIgnoreCase("LOGOUT")) {
connectionClosed = closeConnection(NORMAL_CLOSE, "", "");
return false;
}
// Commands only valid in NON_AUTHENTICATED state
// AUTHENTICATE, LOGIN
if (state == NON_AUTHENTICATED) {
if (command.equalsIgnoreCase("AUTHENTICATE")) {
out.println(tag + SP + NO + SP + "Auth type not supported.");
getLogger().info("Attempt to use Authenticate command by "
+ remoteHost + "(" + remoteIP + ")");
securityLogger.info("Attempt to use Authenticate command by "
+ remoteHost + "(" + remoteIP + ")");
return true;
} else if (command.equalsIgnoreCase("LOGIN")) {
if (arguments != 4) {
out.println(tag + SP + BAD + SP
+ "Command should be <tag> <LOGIN> <username> <password>");
getLogger().info("Wrong number of arguments for LOGIN command from "
+ remoteHost + "(" + remoteIP + ")");
return true;
}
user = decodeAstring(commandLine.nextToken());
String password = decodeAstring(commandLine.nextToken());
if (users.test(user, password)) {
securityLogger.info("Login successful for " + user + " from "
+ remoteHost + "(" + remoteIP + ")");
// four possibilites handled:
// private mail: isLocal, is Remote
// other mail (shared, news, etc.) is Local, is Remote
if (imapHost.isHomeServer(user)) {
out.println(tag + SP + OK + SP + "LOGIN completed");
state = AUTHENTICATED;
} else {
String remoteServer = null;
try {
remoteServer
= imapSystem.getHomeServer(user);
} catch (AuthenticationException ae) {
connectionClosed
= closeConnection(TAGGED_NO,
" cannot find your inbox, closing connection",
"");
return false;
}
if (imapHost.hasLocalAccess(user)) {
out.println(tag + SP + OK + SP + "[REFERRAL "
+ remoteServer +"]" + SP
+ "Your home server is remote, other mailboxes available here");
state = AUTHENTICATED;
} else {
closeConnection(TAGGED_NO, " [REFERRAL" + SP
+ remoteServer +"]" + SP
+ "No mailboxes available here, try remote server", "");
return false;
}
}
currentNamespace = imapHost.getDefaultNamespace(user);
currentSeperator
= imapSystem.getHierarchySeperator(currentNamespace);
// position at root of default Namespace,
// which is not actually a folder
currentFolder = currentNamespace + currentSeperator + "";
getLogger().debug("Current folder for user " + user + " from "
+ remoteHost + "(" + remoteIP + ") is "
+ currentFolder);
return true;
} // failed password test
// We should add ability to monitor attempts to login
out.println(tag + SP + NO + SP + "LOGIN failed");
securityLogger.error("Failed attempt to use Login command for account "
+ user + " from " + remoteHost + "(" + remoteIP
+ ")");
return true;
}
// bad client
out.println(tag + SP + NO + SP + "Must authenticate first");
return true;
} // end of if (state == NON_AUTHENTICATED)
// Commands not yet processed should be valid in either
// Authenticated or Selected states.
getLogger().debug("Command recieved: " + commandRaw + " from " + remoteHost
+ "(" + remoteIP + ")");
// Create ImapRequest object here - is this the right stage?
ImapRequest request = new ImapRequest(this);
request.setCommandLine(commandLine);
request.setUseUIDs(false);
request.setCurrentMailbox(currentMailbox);
request.setCommandRaw(commandRaw);
request.setTag(tag);
request.setCurrentFolder(currentFolder);
// Commands valid in both Authenticated and Selected states
// NAMESPACE, GETACL, SETACL, DELETEACL, LISTRIGHTS, MYRIGHTS, SELECT
if (state == AUTHENTICATED || state == SELECTED) {
// NAMESPACE capability ------------------------------
if (command.equalsIgnoreCase("NAMESPACE")) {
String namespaces = imapSystem.getNamespaces(user);
out.println(UNTAGGED + SP + "NAMESPACE " + namespaces);
getLogger().info("Provided NAMESPACE: " + namespaces );
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
out.println(tag + SP + OK + SP
+ "NAMESPACE command completed");
return true;
// ACL Capability ---------------------------------------
} else if (command.equalsIgnoreCase("GETACL")) {
ACLMailbox target = null;
if (arguments != 3) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <GETACL> <mailbox>");
return true;
}
folder = getFullName(commandLine.nextToken());
if ( state == SELECTED && currentFolder.equals(folder) ) {
target = currentMailbox;
} else {
target = getBox(user, folder);
if (target == null) return true;
}
try {
out.println(UNTAGGED + SP + "ACL " + target.getName() + SP
+ target.getAllRights(user ));
getLogger().debug(UNTAGGED + SP + "ACL " + target.getName() + SP
+ target.getAllRights(user ));
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "Unknown mailbox");
logACE(ace);
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + AUTH_FAIL_MSG);
logAZE(aze);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
out.println(tag + SP + OK + SP
+ "GetACL command completed");
return true;
} else if (command.equalsIgnoreCase("SETACL")) {
ACLMailbox target = null;
if (arguments != 5) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <SETACL> <mailbox> <identity> <rights modification>");
return true;
}
folder = getFullName(commandLine.nextToken());
String identity = commandLine.nextToken();
String changes = commandLine.nextToken();
if ( (state == SELECTED && currentFolder.equals(folder))) {
target = currentMailbox;
} else {
target = getBox(user, folder);
if (target == null) return true;
}
try {
if (target.setRights(user, identity, changes)) {
out.println(tag + SP + OK + SP
+ "SetACL command completed");
securityLogger.info("ACL rights for " + identity + " in "
+ folder + " changed by " + user + " : "
+ changes);
} else {
out.println(tag + SP + NO + SP
+ "SetACL command failed");
securityLogger.info("Failed attempt to change ACL rights for "
+ identity + " in " + folder + " by "
+ user);
}
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "Unknown mailbox");
logACE(ace);
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + AUTH_FAIL_MSG);
logAZE(aze);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("DELETEACL")) {
ACLMailbox target = null;
if (arguments != 4) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <DELETEACL> <mailbox> <identity>");
return true;
}
folder = getFullName(commandLine.nextToken());
String identity = commandLine.nextToken();
String changes = "";
if ( (state == SELECTED && currentFolder.equals(folder))) {
target = currentMailbox;
} else {
target = getBox(user, folder);
if (target == null) return true;
}
try {
if (target.setRights(user, identity, changes)) {
out.println(tag + SP + OK + SP
+ "DeleteACL command completed");
securityLogger.info("ACL rights for " + identity + " in "
+ folder + " deleted by " + user);
} else {
out.println(tag + SP + NO + SP
+ "SetACL command failed");
securityLogger.info("Failed attempt to change ACL rights for "
+ identity + " in " + folder + " by "
+ user);
}
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "Unknown mailbox");
logACE(ace);
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + AUTH_FAIL_MSG);
logAZE(aze);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("LISTRIGHTS")) {
ACLMailbox target = null;
if (arguments != 4) {
out.println(tag + SP + BAD_LISTRIGHTS_MSG);
return true;
}
folder = getFullName(commandLine.nextToken());
String identity = commandLine.nextToken();
if ( state == SELECTED && currentFolder.equals(folder) ) {
target = currentMailbox;
} else {
target = getBox(user, folder);
if (target == null) return true;
}
try {
out.println(UNTAGGED + SP + "LISTRIGHTS "
+ target.getName() + SP + identity + SP
+ target.getRequiredRights(user, identity)
+ SP
+ target.getOptionalRights(user, identity));
out.println(tag + SP + OK + SP
+ "ListRights command completed");
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "Unknown mailbox");
logACE(ace);
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + AUTH_FAIL_MSG);
logAZE(aze);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("MYRIGHTS")) {
ACLMailbox target = null;
if (arguments != 3) {
out.println(tag + SP + BAD_MYRIGHTS_MSG);
return true;
}
folder = getFullName(commandLine.nextToken());
if ( state == SELECTED && currentFolder.equals(folder) ) {
target = currentMailbox;
} else {
target = getBox(user, folder);
if (target == null) return true;
}
try {
out.println(UNTAGGED + SP + "MYRIGHTS "
+ target.getName() + SP
+ target.getRights(user, user));
out.println(tag + SP + OK + SP
+ "MYRIGHTS command completed");
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "Unknown mailbox");
logACE(ace);
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + AUTH_FAIL_MSG);
logAZE(aze);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
// Standard IMAP commands --------------------------
} else if (command.equalsIgnoreCase("SELECT")
|| command.equalsIgnoreCase("EXAMINE")) {
// selecting a mailbox deselects current mailbox,
// even if this select fails
if (state == SELECTED) {
currentMailbox.removeMailboxEventListener(this);
imapHost.releaseMailbox(user, currentMailbox);
state = AUTHENTICATED;
currentMailbox = null;
currentIsReadOnly = false;
}
if (arguments != 3) {
if (command.equalsIgnoreCase("SELECT") ){
out.println(tag + SP + BAD + SP
+ "Command should be <tag> <SELECT> <mailbox>");
} else {
out.println(tag + SP + BAD + SP
+ "Command should be <tag> <EXAMINE> <mailbox>");
}
return true;
}
folder = getFullName(commandLine.nextToken());
currentMailbox = getBox(user, folder);
if (currentMailbox == null) {
return true;
}
try { // long tries clause against an AccessControlException
if (!currentMailbox.hasReadRights(user)) {
out.println(tag + SP + NO + SP
+ "Read access not granted." );
return true;
}
if (command.equalsIgnoreCase("SELECT") ){
if (!currentMailbox.isSelectable(user)) {
out.println(tag + SP + NO + SP
+ "Mailbox exists but is not selectable");
return true;
}
}
// Have mailbox with at least read rights. Server setup.
currentMailbox.addMailboxEventListener(this);
currentFolder = folder;
state = SELECTED;
exists = -1;
recent = -1;
getLogger().debug("Current folder for user " + user + " from "
+ remoteHost + "(" + remoteIP + ") is "
+ currentFolder);
// Inform client
out.println(UNTAGGED + SP + "FLAGS ("
+ currentMailbox.getSupportedFlags() + ")" );
if (!currentMailbox.allFlags(user)) {
out.println(UNTAGGED + SP + OK + " [PERMANENTFLAGS ("
+ currentMailbox.getPermanentFlags(user)
+ ") ]");
}
checkSize();
out.println(UNTAGGED + SP + OK + " [UIDVALIDITY "
+ currentMailbox.getUIDValidity() + " ]");
int oldestUnseen = currentMailbox.getOldestUnseen(user);
if (oldestUnseen > 0 ) {
out.println(UNTAGGED + SP + OK + " [UNSEEN "
+ oldestUnseen + " ]");
} else {
out.println(UNTAGGED + SP + OK + " No unseen messages");
}
sequence = currentMailbox.listUIDs(user);
if (command.equalsIgnoreCase("EXAMINE")) {
currentIsReadOnly = true;
out.println(tag + SP + OK + SP
+ "[READ-ONLY] Examine completed");
return true;
} else if (currentMailbox.isReadOnly(user)) {
currentIsReadOnly = true;
out.println(tag + SP + OK + SP
+ "[READ-ONLY] Select completed");
return true;
}
out.println(tag + SP + OK + SP
+ "[READ-WRITE] Select completed");
return true;
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox.");
logACE(ace);
return true;
}
// End of SELECT || EXAMINE
} else if (command.equalsIgnoreCase("CREATE")) {
if (arguments != 3) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <CREATE> <mailbox>");
return true;
}
folder = getFullName(commandLine.nextToken());
if(currentFolder == folder) {
out.println(tag + SP + NO + SP
+ "Folder exists and is selected." );
return true;
}
try {
ACLMailbox target = imapHost.createMailbox(user, folder);
out.println(tag + SP + OK + SP + "Create completed");
imapHost.releaseMailbox(user, target);
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP
+ "No such mailbox. ");
logACE(ace);
return true;
} catch (MailboxException mbe) {
if (mbe.isRemote()) {
out.println(tag + SP + NO + SP + "[REFERRAL "
+ mbe.getRemoteServer() +"]"
+ SP + "Wrong server. Try remote." );
} else {
out.println(tag + SP + NO + SP + mbe.getStatus() );
}
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + NO + SP
+ "You do not have the rights to create mailbox: "
+ folder);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("DELETE")) {
if (arguments != 3) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <DELETE> <mailbox>");
return true;
}
folder = getFullName(commandLine.nextToken());
if(currentFolder == folder) {
out.println(tag + SP + NO + SP
+ "You can't delete a folder while you have it selected." );
return true;
}
try {
if( imapHost.deleteMailbox(user, folder)) {
out.println(tag + SP + OK + SP + "Delete completed");
} else {
out.println(tag + SP + NO + SP
+ "Delete failed, unknown error");
getLogger().info("Attempt to delete mailbox " + folder
+ " by user " + user + " failed.");
}
} catch (MailboxException mbe) {
if (mbe.getStatus().equals(MailboxException.NOT_LOCAL)) {
out.println(tag + SP + NO_NOTLOCAL_MSG);
} else {
out.println(tag + SP + NO + SP + mbe.getMessage() );
}
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + NO + SP
+ "You do not have the rights to delete mailbox: " + folder);
logAZE(aze);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("RENAME")) {
if (arguments != 4) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <RENAME> <oldname> <newname>");
return true;
}
folder = getFullName(commandLine.nextToken());
String newName = getFullName(commandLine.nextToken());
if(currentFolder == folder) {
out.println(tag + SP + NO + SP
+ "You can't rename a folder while you have it selected." );
return true;
}
try {
if(imapHost.renameMailbox(user, folder, newName)) {
out.println(tag + SP + OK + SP + "Rename completed");
} else {
out.println(tag + SP + NO + SP
+ "Rename failed, unknown error");
getLogger().info("Attempt to rename mailbox " + folder
+ " to " + newName
+ " by user " + user + " failed.");
}
} catch (MailboxException mbe) {
if (mbe.getStatus().equals(MailboxException.NOT_LOCAL)) {
out.println(tag + SP + NO_NOTLOCAL_MSG);
} else {
out.println(tag + SP + NO + SP + mbe.getMessage() );
}
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + NO + SP
+ "You do not have the rights to delete mailbox: " + folder);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("SUBSCRIBE")) {
if (arguments != 3) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <SUBSCRIBE> <mailbox>");
return true;
}
folder = getFullName(commandLine.nextToken());
try {
if( imapHost.subscribe(user, folder) ) {
out.println(tag + SP + OK + SP
+ "Subscribe completed");
} else {
out.println(tag + SP + NO + SP + "Unknown error." );
}
} catch (MailboxException mbe) {
if (mbe.isRemote()) {
out.println(tag + SP + NO + SP + "[REFERRAL "
+ mbe.getRemoteServer() +"]"
+ SP + "Wrong server. Try remote." );
} else {
out.println(tag + SP + NO + SP + "No such mailbox" );
}
return true;
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox");
logACE(ace);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("UNSUBSCRIBE")) {
if (arguments != 3) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <UNSUBSCRIBE> <mailbox>");
return true;
}
folder = getFullName(commandLine.nextToken());
try {
if( imapHost.unsubscribe(user, folder) ) {
out.println(tag + SP + OK + SP
+ "Unsubscribe completed");
} else {
out.println(tag + SP + NO + SP + "Unknown error." );
}
} catch (MailboxException mbe) {
if (mbe.isRemote()) {
out.println(tag + SP + NO + SP + "[REFERRAL "
+ mbe.getRemoteServer() +"]"
+ SP + "Wrong server. Try remote." );
} else {
out.println(tag + SP + NO + SP + "No such mailbox" );
}
return true;
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox");
logACE(ace);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("LIST")
|| command.equalsIgnoreCase("LSUB")) {
if (arguments != 4) {
if (command.equalsIgnoreCase("LIST")) {
out.println(tag + SP + BAD_LIST_MSG);
} else {
out.println(tag + SP + BAD_LSUB_MSG);
}
return true;
}
if (command.equalsIgnoreCase("LIST")) {
subscribeOnly =false;
} else {
subscribeOnly = true;
}
String reference = decodeAstring(commandLine.nextToken());
folder = decodeAstring(commandLine.nextToken());
if (reference.equals("")) {
reference = currentFolder;
} else {
reference = getFullName(reference);
}
Collection list = null;
try {
list = imapHost.listMailboxes(user, reference, folder,
subscribeOnly);
if (list == null) {
getLogger().debug(tag + SP + NO + SP + command
+ " unable to interpret mailbox");
out.println(tag + SP + NO + SP + command
+ " unable to interpret mailbox");
} else if (list.size() == 0) {
getLogger().debug("List request matches zero mailboxes: " + commandRaw);
out.println(tag + SP + OK + SP + command
+ " completed");
} else {
Iterator it = list.iterator();
while (it.hasNext()) {
String listResponse = (String)it.next();
out.println(UNTAGGED + SP + command.toUpperCase()
+ SP + listResponse);
getLogger().debug(UNTAGGED + SP + command.toUpperCase()
+ SP + listResponse);
}
out.println(tag + SP + OK + SP + command
+ " completed");
}
} catch (MailboxException mbe) {
if (mbe.isRemote()) {
out.println(tag + SP + NO + SP + "[REFERRAL "
+ mbe.getRemoteServer() +"]"
+ SP + "Wrong server. Try remote." );
} else {
out.println(tag + SP + NO + SP
+ "No such mailbox" );
}
return true;
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox");
logACE(ace);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("STATUS")) {
if (arguments < 4) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <STATUS> <mailboxname> (status data items)");
return true;
}
folder = getFullName(commandLine.nextToken());
List dataNames = new ArrayList();
String attr = commandLine.nextToken();
if (! attr.startsWith("(")) { //single attr
out.println(tag + SP + BAD + SP +
"Command should be <tag> <STATUS> <mailboxname> (status data items)");
return true;
} else if (attr.endsWith(")")){ //single attr in paranthesis
dataNames.add(attr.substring(1, attr.length()-1 ));
} else { // multiple attrs
dataNames.add(attr.substring(1).trim());
while(commandLine.hasMoreTokens()) {
attr = commandLine.nextToken();
if (attr.endsWith(")")) {
dataNames.add(attr.substring(0, attr.length()-1 ));
} else {
dataNames.add(attr);
}
}
}
try {
String response = imapHost.getMailboxStatus(user, folder,
dataNames);
out.println(UNTAGGED + " STATUS " + folder + " ("
+ response + ")");
out.println(tag + SP + OK + SP + "Status completed");
} catch (MailboxException mbe) {
if (mbe.isRemote()) {
out.println(tag + SP + NO + SP + "[REFERRAL "
+ mbe.getRemoteServer() +"]"
+ SP + "Wrong server. Try remote." );
} else {
out.println(tag + SP + NO + SP
+ "No such mailbox" );
}
return true;
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox");
logACE(ace);
return true;
}
if (state == SELECTED ) {
checkSize();
checkExpunge();
}
return true;
} else if (command.equalsIgnoreCase("APPEND")) {
if (true) {
out.println(tag + SP + BAD + SP +
"Append Command not yet implemented.");
return true;
}
}
} // end of Auth & Selected
// Commands valid only in Authenticated State
// None
if (state == AUTHENTICATED) {
out.println(tag + SP + BAD + SP
+ "Command not valid in this state");
return true;
}
// Commands valid only in Selected state
// CHECK
if (state == SELECTED) {
if (command.equalsIgnoreCase("CHECK")) {
if (currentMailbox.checkpoint()) {
out.println(tag + SP + OK + SP
+ "Check completed");
checkSize();
checkExpunge();
return true;
} else {
out.println(tag + SP + NO + SP
+ "Check failed");
return true;
}
} else if (command.equalsIgnoreCase("CLOSE")) {
try {
currentMailbox.expunge(user);
} catch (Exception e) {
getLogger().error("Exception while expunging mailbox on CLOSE : " + e);
}
currentMailbox.removeMailboxEventListener(this);
imapHost.releaseMailbox(user, currentMailbox);
state = AUTHENTICATED;
currentMailbox = null;
currentIsReadOnly = false;
out.println(tag + SP + OK + SP
+ "CLOSE completed");
return true;
} else if (command.equalsIgnoreCase("COPY")) {
if (arguments < 4) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <COPY> <message set> <mailbox name>");
return true;
}
List set = decodeSet(commandLine.nextToken(),
currentMailbox.getExists());
getLogger().debug("Fetching message set of size: " + set.size());
String targetFolder = getFullName(commandLine.nextToken());
ACLMailbox targetMailbox = getBox(user, targetFolder);
if (targetMailbox == null) {
return true;
}
try { // long tries clause against an AccessControlException
if (!currentMailbox.hasInsertRights(user)) {
out.println(tag + SP + NO + SP
+ "Insert access not granted." );
return true;
}
for (int i = 0; i < set.size(); i++) {
int msn = ((Integer)set.get(i)).intValue();
MessageAttributes attrs = currentMailbox.getMessageAttributes(msn, user);
}
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox.");
logACE(ace);
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + NO + SP
+ "You do not have the rights to expunge mailbox: " + folder);
logAZE(aze);
return true;
}
out.println(tag + SP + OK + SP
+ "CLOSE completed");
return true;
} else if (command.equalsIgnoreCase("EXPUNGE")) {
try {
if(currentMailbox.expunge(user)) {
checkExpunge();
checkSize();
out.println(tag + SP + OK + SP
+ "EXPUNGE complete.");
} else {
out.println(tag + SP + NO + SP
+ "Unknown server error.");
}
return true;
} catch (AccessControlException ace) {
out.println(tag + SP + NO + SP + "No such mailbox");
logACE(ace);
return true;
} catch (AuthorizationException aze) {
out.println(tag + SP + NO + SP
+ "You do not have the rights to expunge mailbox: " + folder);
logAZE(aze);
return true;
} catch (Exception e) {
out.println(tag + SP + NO + SP
+ "Unknown server error.");
getLogger().error("Exception expunging mailbox " + folder + " by user " + user + " was : " + e);
if (DEEP_DEBUG) {e.printStackTrace();}
return true;
}
} else if (command.equalsIgnoreCase("FETCH")) {
if (arguments < 4) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <FETCH> <message set> <message data item names>");
return true;
}
CommandFetch fetcher = new CommandFetch();
fetcher.setLogger( getLogger() );
fetcher.setRequest(request);
fetcher.service();
return true;
// end of FETCH
} else if (command.equalsIgnoreCase("STORE")) {
if (arguments < 5) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <STORE> <message set> <message data item names> <value for message data item>");
return true;
}
//storeCommand(commandLine, false);
CommandStore storer = new CommandStore();
storer.setLogger( getLogger() );
storer.setRequest(request);
storer.service();
return true;
} else if (command.equalsIgnoreCase("UID")) {
if (arguments < 4) {
out.println(tag + SP + BAD + SP +
"Command should be <tag> <UID> <command> <command parameters>");
return true;
}
String uidCommand = commandLine.nextToken();
if (uidCommand.equalsIgnoreCase("STORE")) {
//storeCommand(commandLine, true);
CommandStore storer = new CommandStore();
storer.setLogger( getLogger() );
storer.setRequest(request);
storer.service();
return true;
} else if (uidCommand.equalsIgnoreCase("FETCH")) {
CommandFetch fetcher = new CommandFetch();
fetcher.setLogger( getLogger() );
fetcher.setRequest(request);
fetcher.service();
return true;
}
} else {
// Other commands for selected state .....
out.println(tag + SP + BAD + SP + "Protocol error");
return true;
} // end state SELECTED
}
// Shouldn't happen
out.println(tag + SP + BAD + SP + "Protocol error");
return true;
} // end of parseCommand
public void dispose() {
// todo
getLogger().error("Stop IMAPHandler");
}
public void receiveEvent(MailboxEvent me) {
if (state == SELECTED) {
checkMailboxFlag = true;
}
}
private ACLMailbox getBox(String user, String mailboxName) {
ACLMailbox tempMailbox = null;
try {
tempMailbox = imapHost.getMailbox(user, mailboxName);
} catch (MailboxException me) {
if (me.isRemote()) {
out.println(tag + SP + NO + SP + "[REFERRAL " + me.getRemoteServer() +"]" + SP + "Remote mailbox" );
} else {
out.println(tag + SP + NO + SP + "Unknown mailbox" );
getLogger().info("MailboxException in method getBox for user: "
+ user + " mailboxName: " + mailboxName + " was "
+ me.getMessage());
}
} catch (AccessControlException e) {
out.println(tag + SP + NO + SP + "Unknown mailbox" );
}
return tempMailbox;
}
private String getFullName(String name) {
getLogger().debug("Method getFullName called for " + name);
name = decodeAstring(name);
if (name == null) {
getLogger().error("Received null name");
return null;
}
int inbox = name.toUpperCase().indexOf("INBOX");
if (inbox == -1) {
if (name.startsWith(namespaceToken)) { //absolute reference
return name;
} else if (name.startsWith(currentSeperator)) {//rooted relative ref
return currentNamespace + name;
}else { //unrooted relative ref
if (currentFolder.equals(currentNamespace + currentSeperator )) {
return currentFolder + name;
} else {
return currentFolder + currentSeperator + name;
}
}
} else {
return ("#mail.INBOX");
}
}
public void logACE(AccessControlException ace) {
securityLogger.error("AccessControlException by user " + user
+ " from " + remoteHost + "(" + remoteIP
+ ") with " + commandRaw + " was "
+ ace.getMessage());
}
public void logAZE(AuthorizationException aze) {
securityLogger.error("AuthorizationException by user " + user
+ " from " + remoteHost + "(" + remoteIP
+ ") with " + commandRaw + " was "
+ aze.getMessage());
}
public PrintWriter getPrintWriter() {
return out;
}
public OutputStream getOutputStream() {
return outs;
}
public String getUser() {
return user;
}
private String decodeAstring(String rawAstring) {
if (rawAstring.startsWith("\"")) {
//quoted string
if (rawAstring.endsWith("\"")) {
if (rawAstring.length() == 2) {
return new String(); //ie blank
} else {
return rawAstring.substring(1, rawAstring.length() - 1);
}
} else {
getLogger().error("Quoted string with no closing quote.");
return null;
}
} else {
//atom
return rawAstring;
}
}
public void checkSize() {
int newExists = currentMailbox.getExists();
if (newExists != exists) {
out.println(UNTAGGED + SP + newExists + " EXISTS");
exists = newExists;
}
int newRecent = currentMailbox.getRecent();
if (newRecent != recent) {
out.println(UNTAGGED + SP + newRecent + " RECENT");
recent = newRecent;
}
return;
}
private void checkExpunge() {
List newList = currentMailbox.listUIDs(user);
for (int k = 0; k < newList.size(); k++) {
getLogger().debug("New List msn " + (k+1) + " is uid " + newList.get(k));
}
for (int i = sequence.size() -1; i > -1 ; i--) {
Integer j = (Integer)sequence.get(i);
getLogger().debug("Looking for old msn " + (i+1) + " was uid " + j);
if (! newList.contains((Integer)sequence.get(i))) {
out.println(UNTAGGED + SP + (i+1) + " EXPUNGE");
}
}
sequence = newList;
//newList = null;
return;
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: james-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: james-dev-help@jakarta.apache.org