diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index 63536c6f..a4f5925c 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -98,6 +98,17 @@ public abstract class ExchangeSession { protected static final int FREE_BUSY_INTERVAL = 15; + protected static final String PUBLIC_ROOT = "/public"; + protected static final String CALENDAR = "calendar"; + protected static final String CONTACTS = "contacts"; + protected static final String INBOX = "INBOX"; + protected static final String SENT = "Sent"; + protected static final String DRAFTS = "Drafts"; + protected static final String TRASH = "Trash"; + protected static final String JUNK = "Junk"; + protected static final String UNSENT = "Unsent Messages"; + + protected static final Namespace DAV = Namespace.getNamespace("DAV:"); protected static final Namespace URN_SCHEMAS_HTTPMAIL = Namespace.getNamespace("urn:schemas:httpmail:"); protected static final Namespace SCHEMAS_EXCHANGE = Namespace.getNamespace("http://schemas.microsoft.com/exchange/"); @@ -123,6 +134,7 @@ public abstract class ExchangeSession { WELL_KNOWN_FOLDERS.add(DavPropertyName.create("drafts", URN_SCHEMAS_HTTPMAIL)); WELL_KNOWN_FOLDERS.add(DavPropertyName.create("calendar", URN_SCHEMAS_HTTPMAIL)); WELL_KNOWN_FOLDERS.add(DavPropertyName.create("contacts", URN_SCHEMAS_HTTPMAIL)); + WELL_KNOWN_FOLDERS.add(DavPropertyName.create("outbox", URN_SCHEMAS_HTTPMAIL)); } protected static final DavPropertyNameSet DISPLAY_NAME = new DavPropertyNameSet(); @@ -168,6 +180,7 @@ public abstract class ExchangeSession { protected String draftsUrl; protected String calendarUrl; protected String contactsUrl; + protected String outboxUrl; protected String publicFolderUrl; /** @@ -851,13 +864,23 @@ public abstract class ExchangeSession { } } - protected abstract Condition and(Condition... condition); + protected abstract static class IsNullCondition extends Condition { + protected String attributeName; - protected abstract Condition or(Condition... condition); + protected IsNullCondition(String attributeName) { + this.attributeName = attributeName; + } + } - protected abstract Condition not(Condition condition); + public abstract Condition and(Condition... condition); - protected abstract AttributeCondition equals(String attributeName, String value); + public abstract Condition or(Condition... condition); + + public abstract Condition not(Condition condition); + + public abstract Condition equals(String attributeName, String value); + + public abstract Condition isNull(String attributeName); /** * Search mail and generic folders under given folder. @@ -869,7 +892,9 @@ public abstract class ExchangeSession { * @throws IOException on error */ public List getSubFolders(String folderName, boolean recursive) throws IOException { - return getSubFolders(folderName, or(equals("folderclass", "IPF.Note"), equals("folderclass", "IPF")), recursive); + return getSubFolders(folderName, + or(equals("folderclass", "IPF.Note"), isNull("folderclass")), + recursive); } /** @@ -1017,18 +1042,18 @@ public abstract class ExchangeSession { */ public String getFolderPath(String folderName) { String folderPath; - if (folderName.startsWith("INBOX")) { - folderPath = folderName.replaceFirst("INBOX", inboxUrl); - } else if (folderName.startsWith("Trash")) { - folderPath = folderName.replaceFirst("Trash", deleteditemsUrl); - } else if (folderName.startsWith("Drafts")) { - folderPath = folderName.replaceFirst("Drafts", draftsUrl); - } else if (folderName.startsWith("Sent")) { - folderPath = folderName.replaceFirst("Sent", sentitemsUrl); - } else if (folderName.startsWith("calendar")) { - folderPath = folderName.replaceFirst("calendar", calendarUrl); - } else if (folderName.startsWith("contacts")) { - folderPath = folderName.replaceFirst("contacts", contactsUrl); + if (folderName.startsWith(INBOX)) { + folderPath = folderName.replaceFirst(INBOX, inboxUrl); + } else if (folderName.startsWith(TRASH)) { + folderPath = folderName.replaceFirst(TRASH, deleteditemsUrl); + } else if (folderName.startsWith(DRAFTS)) { + folderPath = folderName.replaceFirst(DRAFTS, draftsUrl); + } else if (folderName.startsWith(SENT)) { + folderPath = folderName.replaceFirst(SENT, sentitemsUrl); + } else if (folderName.startsWith(CALENDAR)) { + folderPath = folderName.replaceFirst(CALENDAR, calendarUrl); + } else if (folderName.startsWith(CONTACTS)) { + folderPath = folderName.replaceFirst(CONTACTS, contactsUrl); } else if (folderName.startsWith("public")) { folderPath = publicFolderUrl + folderName.substring("public".length()); // absolute folder path diff --git a/src/java/davmail/exchange/dav/DavExchangeSession.java b/src/java/davmail/exchange/dav/DavExchangeSession.java index e0536305..1410f8ca 100644 --- a/src/java/davmail/exchange/dav/DavExchangeSession.java +++ b/src/java/davmail/exchange/dav/DavExchangeSession.java @@ -126,16 +126,18 @@ public class DavExchangeSession extends ExchangeSession { draftsUrl = getURIPropertyIfExists(properties, "drafts", URN_SCHEMAS_HTTPMAIL); calendarUrl = getURIPropertyIfExists(properties, "calendar", URN_SCHEMAS_HTTPMAIL); contactsUrl = getURIPropertyIfExists(properties, "contacts", URN_SCHEMAS_HTTPMAIL); + outboxUrl = getURIPropertyIfExists(properties, "outbox", URN_SCHEMAS_HTTPMAIL); + // junk folder not available over webdav // default public folder path - publicFolderUrl = "/public"; + publicFolderUrl = PUBLIC_ROOT; // check public folder access try { if (inboxUrl != null) { // try to build full public URI from inboxUrl URI publicUri = new URI(inboxUrl, false); - publicUri.setPath("/public"); + publicUri.setPath(PUBLIC_ROOT); publicFolderUrl = publicUri.getURI(); } PropFindMethod propFindMethod = new PropFindMethod(publicFolderUrl, CONTENT_TAG, 0); @@ -155,14 +157,15 @@ public class DavExchangeSession extends ExchangeSession { publicFolderUrl = "/public"; } - LOGGER.debug("Inbox URL : " + inboxUrl + - " Trash URL : " + deleteditemsUrl + - " Sent URL : " + sentitemsUrl + - " Send URL : " + sendmsgUrl + - " Drafts URL : " + draftsUrl + - " Calendar URL : " + calendarUrl + - " Contacts URL : " + contactsUrl + - " Public folder URL : " + publicFolderUrl + LOGGER.debug("Inbox URL: " + inboxUrl + + " Trash URL: " + deleteditemsUrl + + " Sent URL: " + sentitemsUrl + + " Send URL: " + sendmsgUrl + + " Drafts URL: " + draftsUrl + + " Calendar URL: " + calendarUrl + + " Contacts URL: " + contactsUrl + + " Outbox URL: " + outboxUrl + + " Public folder URL: " + publicFolderUrl ); } catch (IOException e) { LOGGER.error(e.getMessage()); @@ -231,26 +234,43 @@ public class DavExchangeSession extends ExchangeSession { } } + protected static class IsNullCondition extends ExchangeSession.IsNullCondition { + protected IsNullCondition(String attributeName) { + super(attributeName); + } + + @Override + public void appendTo(StringBuilder buffer) { + buffer.append('"').append(attributeMap.get(attributeName)).append('"'); + buffer.append(" is null"); + } + } + @Override - protected Condition and(Condition... condition) { + public Condition and(Condition... condition) { return new MultiCondition(Operator.And, condition); } @Override - protected Condition or(Condition... condition) { + public Condition or(Condition... condition) { return new MultiCondition(Operator.Or, condition); } @Override - protected Condition not(Condition condition) { + public Condition not(Condition condition) { return new NotCondition(condition); } @Override - protected AttributeCondition equals(String attributeName, String value) { + public Condition equals(String attributeName, String value) { return new AttributeCondition(attributeName, Operator.IsEqualTo, value); } + @Override + public Condition isNull(String attributeName) { + return new IsNullCondition(attributeName); + } + protected Folder buildFolder(MultiStatusResponse entity) throws IOException { String href = URIUtil.decode(entity.getHref()); @@ -265,13 +285,17 @@ public class DavExchangeSession extends ExchangeSession { // replace well known folder names if (href.startsWith(inboxUrl)) { - folder.folderPath = href.replaceFirst(inboxUrl, "INBOX"); + folder.folderPath = href.replaceFirst(inboxUrl, INBOX); } else if (href.startsWith(sentitemsUrl)) { - folder.folderPath = href.replaceFirst(sentitemsUrl, "Sent"); + folder.folderPath = href.replaceFirst(sentitemsUrl, SENT); } else if (href.startsWith(draftsUrl)) { - folder.folderPath = href.replaceFirst(draftsUrl, "Drafts"); + folder.folderPath = href.replaceFirst(draftsUrl, DRAFTS); } else if (href.startsWith(deleteditemsUrl)) { - folder.folderPath = href.replaceFirst(deleteditemsUrl, "Trash"); + folder.folderPath = href.replaceFirst(deleteditemsUrl, TRASH); + } else if (href.startsWith(calendarUrl)) { + folder.folderPath = href.replaceFirst(calendarUrl, CALENDAR); + } else if (href.startsWith(contactsUrl)) { + folder.folderPath = href.replaceFirst(contactsUrl, CONTACTS); } else { int index = href.indexOf(mailPath.substring(0, mailPath.length() - 1)); if (index >= 0) { @@ -315,7 +339,8 @@ public class DavExchangeSession extends ExchangeSession { */ @Override public List getSubFolders(String folderName, Condition condition, boolean recursive) throws IOException { - String mode = recursive ? "DEEP" : "SHALLOW"; + boolean isPublic = folderName.startsWith("/public"); + String mode = (!isPublic && recursive) ? "DEEP" : "SHALLOW"; List folders = new ArrayList(); StringBuilder searchRequest = new StringBuilder(); searchRequest.append("Select \"DAV:nosubs\", \"DAV:hassubs\", \"http://schemas.microsoft.com/exchange/outlookfolderclass\", " + @@ -330,7 +355,11 @@ public class DavExchangeSession extends ExchangeSession { httpClient, URIUtil.encodePath(getFolderPath(folderName)), searchRequest.toString()); for (MultiStatusResponse response : responses) { + Folder folder = buildFolder(response); folders.add(buildFolder(response)); + if (isPublic && recursive) { + getSubFolders(folder.folderPath, condition, recursive); + } } return folders; } diff --git a/src/java/davmail/exchange/ews/EwsExchangeSession.java b/src/java/davmail/exchange/ews/EwsExchangeSession.java index 0b357920..24849e05 100644 --- a/src/java/davmail/exchange/ews/EwsExchangeSession.java +++ b/src/java/davmail/exchange/ews/EwsExchangeSession.java @@ -39,6 +39,8 @@ import java.util.Map; */ public class EwsExchangeSession extends ExchangeSession { + protected Map folderIdMap; + protected class Folder extends ExchangeSession.Folder { public FolderId folderId; } @@ -66,6 +68,22 @@ public class EwsExchangeSession extends ExchangeSession { } finally { headMethod.releaseConnection(); } + + try { + folderIdMap = new HashMap(); + // load actual well known folder ids + folderIdMap.put(getFolder(INBOX).folderId.value, INBOX); + folderIdMap.put(getFolder(CALENDAR).folderId.value, CALENDAR); + folderIdMap.put(getFolder(CONTACTS).folderId.value, CONTACTS); + folderIdMap.put(getFolder(SENT).folderId.value, SENT); + folderIdMap.put(getFolder(DRAFTS).folderId.value, DRAFTS); + folderIdMap.put(getFolder(TRASH).folderId.value, TRASH); + folderIdMap.put(getFolder(JUNK).folderId.value, JUNK); + folderIdMap.put(getFolder(UNSENT).folderId.value, UNSENT); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + throw new DavMailAuthenticationException("EXCEPTION_EWS_NOT_AVAILABLE"); + } } protected static class MultiCondition extends ExchangeSession.MultiCondition implements SearchExpression { @@ -124,26 +142,44 @@ public class EwsExchangeSession extends ExchangeSession { } } + protected static class IsNullCondition extends ExchangeSession.IsNullCondition implements SearchExpression { + protected IsNullCondition(String attributeName) { + super(attributeName); + } + + @Override + public void appendTo(StringBuilder buffer) { + buffer.append(""); + attributeMap.get(attributeName).appendTo(buffer); + buffer.append(""); + } + } + @Override - protected Condition and(Condition... condition) { + public Condition and(Condition... condition) { return new MultiCondition(Operator.And, condition); } @Override - protected Condition or(Condition... condition) { + public Condition or(Condition... condition) { return new MultiCondition(Operator.Or, condition); } @Override - protected Condition not(Condition condition) { + public Condition not(Condition condition) { return new NotCondition(condition); } @Override - protected AttributeCondition equals(String attributeName, String value) { + public Condition equals(String attributeName, String value) { return new AttributeCondition(attributeName, Operator.IsEqualTo, value); } + @Override + public Condition isNull(String attributeName) { + return new IsNullCondition(attributeName); + } + protected Folder buildFolder(EWSMethod.Item item) { Folder folder = new Folder(); folder.folderId = new FolderId(item.get("FolderId")); @@ -151,7 +187,6 @@ public class EwsExchangeSession extends ExchangeSession { folder.etag = item.get(ExtendedFieldURI.PR_LAST_MODIFICATION_TIME.getPropertyTag()); // TODO: implement ctag folder.ctag = String.valueOf(System.currentTimeMillis()); - // TODO: implement contentClass, noInferiors folder.unreadCount = item.getInt("UnreadCount"); folder.hasChildren = item.getInt("ChildFolderCount") != 0; // noInferiors not implemented @@ -185,6 +220,8 @@ public class EwsExchangeSession extends ExchangeSession { Folder folder = buildFolder(item); if (parentFolderPath.length() > 0) { folder.folderPath = parentFolderPath + '/' + item.get(ExtendedFieldURI.PR_URL_COMP_NAME.getPropertyTag()); + } else if (folderIdMap.get(folder.folderId.value) != null) { + folder.folderPath = folderIdMap.get(folder.folderId.value); } else { folder.folderPath = item.get(ExtendedFieldURI.PR_URL_COMP_NAME.getPropertyTag()); } @@ -199,7 +236,7 @@ public class EwsExchangeSession extends ExchangeSession { * @inheritDoc */ @Override - public ExchangeSession.Folder getFolder(String folderPath) throws IOException { + public EwsExchangeSession.Folder getFolder(String folderPath) throws IOException { GetFolderMethod getFolderMethod = new GetFolderMethod(BaseShape.ALL_PROPERTIES, getFolderId(folderPath)); getFolderMethod.addAdditionalProperty(ExtendedFieldURI.PR_URL_COMP_NAME); getFolderMethod.addAdditionalProperty(ExtendedFieldURI.PR_LAST_MODIFICATION_TIME); @@ -219,28 +256,43 @@ public class EwsExchangeSession extends ExchangeSession { return folder; } - protected static final String PUBLIC_ROOT = "/public"; private FolderId getFolderId(String folderPath) throws IOException { String[] folderNames; FolderId currentFolderId; - if (folderPath.startsWith("/public")) { - currentFolderId = DistinguishedFolderId.PUBLICFOLDERSROOT; + if (folderPath.startsWith(PUBLIC_ROOT)) { + currentFolderId = DistinguishedFolderId.PUBLICFOLDERSROOT; folderNames = folderPath.substring(PUBLIC_ROOT.length()).split("/"); + } else if (folderPath.startsWith(INBOX)) { + currentFolderId = DistinguishedFolderId.INBOX; + folderNames = folderPath.substring(INBOX.length()).split("/"); + } else if (folderPath.startsWith(CALENDAR)) { + currentFolderId = DistinguishedFolderId.CALENDAR; + folderNames = folderPath.substring(CALENDAR.length()).split("/"); + } else if (folderPath.startsWith(CONTACTS)) { + currentFolderId = DistinguishedFolderId.CONTACTS; + folderNames = folderPath.substring(CONTACTS.length()).split("/"); + } else if (folderPath.startsWith(SENT)) { + currentFolderId = DistinguishedFolderId.SENTITEMS; + folderNames = folderPath.substring(SENT.length()).split("/"); + } else if (folderPath.startsWith(DRAFTS)) { + currentFolderId = DistinguishedFolderId.DRAFTS; + folderNames = folderPath.substring(DRAFTS.length()).split("/"); + } else if (folderPath.startsWith(TRASH)) { + currentFolderId = DistinguishedFolderId.DELETEDITEMS; + folderNames = folderPath.substring(TRASH.length()).split("/"); + } else if (folderPath.startsWith(JUNK)) { + currentFolderId = DistinguishedFolderId.JUNKEMAIL; + folderNames = folderPath.substring(JUNK.length()).split("/"); + } else if (folderPath.startsWith(UNSENT)) { + currentFolderId = DistinguishedFolderId.OUTBOX; + folderNames = folderPath.substring(UNSENT.length()).split("/"); } else { - currentFolderId = DistinguishedFolderId.MSGFOLDERROOT; - folderNames = folderPath.split("/"); + currentFolderId = DistinguishedFolderId.MSGFOLDERROOT; + folderNames = folderPath.split("/"); } for (String folderName : folderNames) { - if ("INBOX".equals(folderName)) { - currentFolderId = DistinguishedFolderId.INBOX; - } else if ("Sent".equals(folderName)) { - currentFolderId = DistinguishedFolderId.SENTITEMS; - } else if ("Drafts".equals(folderName)) { - currentFolderId = DistinguishedFolderId.DRAFTS; - } else if ("Trash".equals(folderName)) { - currentFolderId = DistinguishedFolderId.DELETEDITEMS; - } else if (folderName.length() > 0) { + if (folderName.length() > 0) { currentFolderId = getSubFolderByName(currentFolderId, folderName); } } @@ -261,7 +313,9 @@ public class EwsExchangeSession extends ExchangeSession { findFolderMethod.releaseConnection(); } EWSMethod.Item item = findFolderMethod.getResponseItem(); - // TODO: handle not found error + if (item == null) { + throw new DavMailException("EXCEPTION_FOLDER_NOT_FOUND", folderName); + } return new FolderId(item.get("FolderId")); } diff --git a/src/java/davmailmessages.properties b/src/java/davmailmessages.properties index 50df8346..0db9f26c 100644 --- a/src/java/davmailmessages.properties +++ b/src/java/davmailmessages.properties @@ -258,3 +258,4 @@ UI_IMAP_AUTO_EXPUNGE_HELP=Delete messages immediately on the server over IMAP UI_IMAP_IDLE_DELAY=IDLE folder monitor delay (IMAP): UI_IMAP_IDLE_DELAY_HELP=IMAP folder idle monitor delay in minutes, leave empty to disable IDLE support EXCEPTION_EWS_NOT_AVAILABLE=EWS end point not available +EXCEPTION_FOLDER_NOT_FOUND=Folder {0} not found diff --git a/src/java/davmailmessages_fr.properties b/src/java/davmailmessages_fr.properties index 267d02a8..82692a18 100644 --- a/src/java/davmailmessages_fr.properties +++ b/src/java/davmailmessages_fr.properties @@ -257,3 +257,4 @@ UI_IMAP_IDLE_DELAY_HELP=D UI_IMAP_AUTO_EXPUNGE=IMAP suppression immédiate : UI_IMAP_AUTO_EXPUNGE_HELP=Supprimer immédiatement les messages du serveur via IMAP EXCEPTION_EWS_NOT_AVAILABLE=Point d''accès EWS non disponible +EXCEPTION_FOLDER_NOT_FOUND=Dossier {0} non trouvé