[SCM] davmail packaging branch, master, updated. 02c2b101f323ee4dc652f941a0d9441a59cfa0da
Alexandre Rossi
alexandre.rossi at gmail.com
Mon Oct 1 09:07:57 UTC 2012
The following commit has been merged in the master branch:
commit 53954f6134ccce2d33b051a70f8030622c1df570
Author: Alexandre Rossi <alexandre.rossi at gmail.com>
Date: Mon Oct 1 11:03:26 2012 +0200
Imported Upstream version 4.1.0-2042
diff --git a/build.xml b/build.xml
index 0cbba32..9528088 100644
--- a/build.xml
+++ b/build.xml
@@ -1,6 +1,6 @@
<project name="DavMail" default="dist" basedir=".">
<property file="user.properties"/>
- <property name="version" value="4.0.0"/>
+ <property name="version" value="4.1.0"/>
<path id="classpath">
<pathelement location="classes"/>
@@ -23,7 +23,10 @@
</condition>
<condition property="is.java6">
- <equals arg1="${ant.java.version}" arg2="1.6"/>
+ <or>
+ <equals arg1="${ant.java.version}" arg2="1.6"/>
+ <equals arg1="${ant.java.version}" arg2="1.7"/>
+ </or>
</condition>
<target name="check-java6" unless="is.java6">
@@ -111,6 +114,9 @@
<!-- prepare hide from dock option -->
<replaceregexp file="dist/DavMail.app/Contents/Info.plist" match="<key>CFBundleName</key>"
replace="<key>LSUIElement</key><string>0</string><key>CFBundleName</key>"/>
+ <!-- Retina display support -->
+ <replaceregexp file="dist/DavMail.app/Contents/Info.plist" match="<key>CFBundleName</key>"
+ replace="<key>NSHighResolutionCapable</key><true/><key>CFBundleName</key>"/>
<zip file="dist/DavMail-MacOSX-${release}.app.zip">
<zipfileset dir="dist">
<include name="DavMail.app/**/*"/>
diff --git a/pom.xml b/pom.xml
index edbe389..e4514b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
<groupId>davmail</groupId>
<artifactId>davmail</artifactId>
<packaging>jar</packaging>
- <version>4.0.0</version>
+ <version>4.1.0</version>
<name>DavMail POP/IMAP/SMTP/Caldav/Carddav/LDAP Exchange Gateway</name>
<organization>
<name>Mickaël Guessant</name>
diff --git a/releasenotes.txt b/releasenotes.txt
index e462000..d1b5bdf 100644
--- a/releasenotes.txt
+++ b/releasenotes.txt
@@ -1,3 +1,45 @@
+** DavMail 4.1.0 released **
+Bugfix release with improved IMAP support, including IMAP flags mapping to Outlook categories,
+enhanced IMAP noop/idle support, fixed emClient Caldav support and many Caldav and EWS fixes.
+
+Documentation:
+- Doc: update roadmap
+- Doc: new FAQ entry, Exchange RSA two factor authentication form
+
+Caldav:
+- Caldav: do not try to load tasks MIME body
+- Caldav: workaround for 3569922: quick fix for broken Israeli Timezone issue
+- Caldav: remove urlencoding workaround for emClient >= 4
+- Caldav: Ignore 401 unauthorized on public event, return 200
+- Caldav: Rename TZID also in RECURRENCE-ID
+- Caldav: force 403 forbidden instead of 401 on unauthorized update to public folder item
+- Caldav: Fix 3569934 NullPointerException on broken PROPFIND request
+- Caldav: Fix 3567364, regression on from/to/cc handling in calendar related to IMAP search enhancement. Separate mapping for message fields/headers
+
+IMAP:
+- IMAP: send updated flags on folder refresh
+- IMAP: fix keyword handling to avoid sending \Seen as keyword
+- IMAP: retrieve message count on folder
+- IMAP: apply flag to keyword conversion in SEARCH, refresh folder before search
+- IMAP: improve keyword support, map $label1 to 5 from Thunderbird to Outlook categories
+- IMAP: fix keywords implementation, make it case insensitive, implement KEYWORD search
+- IMAP: implement generic FLAGS mapping to Outlook categories
+- IMAP: fix 3566412, range iterator is on folder messages, not messages returned from search
+
+EWS:
+- EWS: Get primary smtp email address with ResolveNames in direct EWS mode
+
+Enhancements
+- Allow Java 7 to build DavMail
+- Prepare message keywords/categories support
+
+WebDav:
+- Dav: implement multivalued property suppord in ExchangeDavMethod
+
+Web:
+- Web: Fix 3566941 Imap protocol is not activated by default in .war
+
+
** DavMail 4.0.0 released **
Includes full Exchange 2007 and 2010 support with EWS implementation,
fixed OSX Mountain Lion support, switched Windows wrappers to WinRun4J
diff --git a/src/java/davmail/caldav/CaldavConnection.java b/src/java/davmail/caldav/CaldavConnection.java
index 516d9c9..18fc922 100644
--- a/src/java/davmail/caldav/CaldavConnection.java
+++ b/src/java/davmail/caldav/CaldavConnection.java
@@ -1428,7 +1428,7 @@ public class CaldavConnection extends AbstractConnection {
}
protected boolean isBrokenHrefEncoding() {
- return isUserAgent("DAVKit/3") || isUserAgent("eM Client/") || isBrokenLightning();
+ return isUserAgent("DAVKit/3") || isUserAgent("eM Client/3") || isBrokenLightning();
}
protected boolean isBrokenLightning() {
@@ -1480,6 +1480,9 @@ public class CaldavConnection extends AbstractConnection {
}
protected void parseXmlBody() throws IOException {
+ if (body == null) {
+ throw new DavMailException("EXCEPTION_INVALID_CALDAV_REQUEST", "Missing body");
+ }
XMLStreamReader streamReader = null;
try {
streamReader = XMLStreamUtil.createXMLStreamReader(body);
diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java
index 68c53df..89e8350 100644
--- a/src/java/davmail/exchange/ExchangeSession.java
+++ b/src/java/davmail/exchange/ExchangeSession.java
@@ -788,6 +788,7 @@ public abstract class ExchangeSession {
IMAP_MESSAGE_ATTRIBUTES.add("lastmodified");
// OSX IMAP requests content-class
IMAP_MESSAGE_ATTRIBUTES.add("contentclass");
+ IMAP_MESSAGE_ATTRIBUTES.add("keywords");
}
protected static final Set<String> UID_MESSAGE_ATTRIBUTES = new HashSet<String>();
@@ -1470,6 +1471,43 @@ public abstract class ExchangeSession {
protected abstract void moveToTrash(Message message) throws IOException;
/**
+ * Convert keyword value to IMAP flag.
+ * @param value keyword value
+ * @return IMAP flag
+ */
+ public String convertKeywordToFlag(String value) {
+ String result = value;
+ // convert flags to Thunderbird flags
+ ResourceBundle flagBundle = ResourceBundle.getBundle("imapflags");
+ Enumeration<String> flagEnumeration = flagBundle.getKeys();
+ while (flagEnumeration.hasMoreElements()) {
+ String key = flagEnumeration.nextElement();
+ if (value.equalsIgnoreCase(flagBundle.getString(key))) {
+ result = key;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Convert IMAP flag to keyword value.
+ * @param value IMAP flag
+ * @return keyword value
+ */
+ public String convertFlagToKeyword(String value) {
+ String result = value;
+ // convert flags to Thunderbird flags
+ ResourceBundle flagBundle = ResourceBundle.getBundle("imapflags");
+ try {
+ result = flagBundle.getString(value);
+ } catch (MissingResourceException e) {
+ // ignore
+ }
+
+ return result;
+ }
+
+ /**
* Exchange folder with IMAP properties
*/
public class Folder {
@@ -1487,6 +1525,10 @@ public abstract class ExchangeSession {
*/
public String folderClass;
/**
+ * Folder message count.
+ */
+ public int count;
+ /**
* Folder unread message count.
*/
public int unreadCount;
@@ -1622,16 +1664,16 @@ public abstract class ExchangeSession {
}
/**
- * Get current folder messages imap uids
+ * Get current folder messages imap uids and flags
*
* @return imap uid list
*/
- public List<Long> getImapUidList() {
- ArrayList<Long> imapUidList = new ArrayList<Long>();
+ public TreeMap<Long,String> getImapFlagMap() {
+ TreeMap<Long,String> imapFlagMap = new TreeMap<Long,String>();
for (ExchangeSession.Message message : messages) {
- imapUidList.add(message.getImapUid());
+ imapFlagMap.put(message.getImapUid(), message.getImapFlags());
}
- return imapUidList;
+ return imapFlagMap;
}
/**
@@ -1696,6 +1738,10 @@ public abstract class ExchangeSession {
*/
public String contentClass;
/**
+ * Message keywords (categories).
+ */
+ public String keywords;
+ /**
* Message IMAP uid, unique in folder (x0e230003).
*/
public long imapUid;
@@ -1817,6 +1863,11 @@ public abstract class ExchangeSession {
if (forwarded) {
buffer.append("$Forwarded ");
}
+ if (keywords != null) {
+ for (String keyword:keywords.split(",")) {
+ buffer.append(convertKeywordToFlag(keyword)).append(" ");
+ }
+ }
return buffer.toString().trim();
}
@@ -1989,6 +2040,51 @@ public abstract class ExchangeSession {
public int hashCode() {
return (int) (imapUid ^ (imapUid >>> 32));
}
+
+ public String removeFlag(String flag) {
+ if (keywords != null) {
+ final String exchangeFlag = convertFlagToKeyword(flag);
+ Set<String> keywordSet = new HashSet<String>();
+ String[] keywordArray = keywords.split(",");
+ for (String value : keywordArray) {
+ if (!value.equalsIgnoreCase(exchangeFlag)) {
+ keywordSet.add(value);
+ }
+ }
+ keywords = StringUtil.join(keywordSet, ",");
+ }
+ return keywords;
+ }
+
+ public String addFlag(String flag) {
+ final String exchangeFlag = convertFlagToKeyword(flag);
+ HashSet<String> keywordSet = new HashSet<String>();
+ boolean hasFlag = false;
+ if (keywords != null) {
+ String[] keywordArray = keywords.split(",");
+ for (String value : keywordArray) {
+ keywordSet.add(value);
+ if (value.equalsIgnoreCase(exchangeFlag)) {
+ hasFlag = true;
+ }
+ }
+ }
+ if (!hasFlag) {
+ keywordSet.add(exchangeFlag);
+ }
+ keywords = StringUtil.join(keywordSet, ",");
+ return keywords;
+ }
+
+ public String setFlags(HashSet<String> flags) {
+ HashSet<String> keywordSet = new HashSet<String>();
+ for (String flag : flags) {
+ keywordSet.add(convertFlagToKeyword(flag));
+ }
+ keywords = StringUtil.join(keywordSet, ",");
+ return keywords;
+ }
+
}
/**
diff --git a/src/java/davmail/exchange/ExchangeSessionFactory.java b/src/java/davmail/exchange/ExchangeSessionFactory.java
index 6094f71..1b2be21 100644
--- a/src/java/davmail/exchange/ExchangeSessionFactory.java
+++ b/src/java/davmail/exchange/ExchangeSessionFactory.java
@@ -169,6 +169,8 @@ public final class ExchangeSessionFactory {
throw exc;
} catch (IllegalStateException exc) {
throw exc;
+ } catch (NullPointerException exc) {
+ throw exc;
} catch (Exception exc) {
handleNetworkDown(exc);
}
diff --git a/src/java/davmail/exchange/VCalendar.java b/src/java/davmail/exchange/VCalendar.java
index c61f06b..ec9b3fc 100644
--- a/src/java/davmail/exchange/VCalendar.java
+++ b/src/java/davmail/exchange/VCalendar.java
@@ -246,6 +246,10 @@ public class VCalendar extends VObject {
if (dtEnd != null && dtStart.getParam("TZID") != null) {
dtEnd.setParam("TZID", tzid);
}
+ VProperty reccurrenceId = vObject.getProperty("RECURRENCE-ID");
+ if (reccurrenceId != null && reccurrenceId.getParam("TZID") != null) {
+ reccurrenceId.setParam("TZID", tzid);
+ }
}
// remove unsupported attachment reference
if (vObject.getProperty("ATTACH") != null) {
@@ -319,6 +323,15 @@ public class VCalendar extends VObject {
vTimezone.vObjects.add(standard);
vTimezone.vObjects.add(daylight);
}
+ // fix 3569922: quick workaround for broken Israeli Timezone issue
+ if (vTimezone != null && vTimezone.vObjects != null) {
+ for (VObject vObject:vTimezone.vObjects) {
+ VProperty rrule = vObject.getProperty("RRULE");
+ if (rrule != null && rrule.getValues().size() == 3 && "BYDAY=-2SU".equals(rrule.getValues().get(1))) {
+ rrule.getValues().set(1, "BYDAY=4SU");
+ }
+ }
+ }
}
private void fixTzid(VProperty property) {
diff --git a/src/java/davmail/exchange/dav/DavExchangeSession.java b/src/java/davmail/exchange/dav/DavExchangeSession.java
index 8b97101..7639eb8 100644
--- a/src/java/davmail/exchange/dav/DavExchangeSession.java
+++ b/src/java/davmail/exchange/dav/DavExchangeSession.java
@@ -1347,27 +1347,30 @@ public class DavExchangeSession extends ExchangeSession {
@Override
public byte[] getEventContent() throws IOException {
byte[] result = null;
- LOGGER.debug("Get event subject: " + subject + " href: " + getHref() + " permanentUrl: " + permanentUrl);
- // try to get PR_INTERNET_CONTENT
- try {
- result = getICSFromInternetContentProperty();
- if (result == null) {
- GetMethod method = new GetMethod(encodeAndFixUrl(permanentUrl));
- method.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
- method.setRequestHeader("Translate", "f");
- try {
- DavGatewayHttpClientFacade.executeGetMethod(httpClient, method, true);
- result = getICS(method.getResponseBodyAsStream());
- } finally {
- method.releaseConnection();
+ LOGGER.debug("Get event subject: " + subject + " contentclass: "+contentClass+" href: " + getHref() + " permanentUrl: " + permanentUrl);
+ // do not try to load tasks MIME body
+ if (!"urn:content-classes:task".equals(contentClass)) {
+ // try to get PR_INTERNET_CONTENT
+ try {
+ result = getICSFromInternetContentProperty();
+ if (result == null) {
+ GetMethod method = new GetMethod(encodeAndFixUrl(permanentUrl));
+ method.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
+ method.setRequestHeader("Translate", "f");
+ try {
+ DavGatewayHttpClientFacade.executeGetMethod(httpClient, method, true);
+ result = getICS(method.getResponseBodyAsStream());
+ } finally {
+ method.releaseConnection();
+ }
}
+ } catch (DavException e) {
+ LOGGER.warn(e.getMessage());
+ } catch (IOException e) {
+ LOGGER.warn(e.getMessage());
+ } catch (MessagingException e) {
+ LOGGER.warn(e.getMessage());
}
- } catch (DavException e) {
- LOGGER.warn(e.getMessage());
- } catch (IOException e) {
- LOGGER.warn(e.getMessage());
- } catch (MessagingException e) {
- LOGGER.warn(e.getMessage());
}
// failover: rebuild event from MAPI properties
@@ -1676,6 +1679,9 @@ public class DavExchangeSession extends ExchangeSession {
// 440 means forbidden on Exchange
if (status == 440) {
status = HttpStatus.SC_FORBIDDEN;
+ } else if (status == HttpStatus.SC_UNAUTHORIZED && getHref().startsWith("/public")) {
+ LOGGER.warn("Ignore 401 unauthorized on public event");
+ status = HttpStatus.SC_OK;
}
itemResult.status = status;
if (putMethod.getResponseHeader("GetETag") != null) {
@@ -1715,7 +1721,10 @@ public class DavExchangeSession extends ExchangeSession {
folder.folderClass = getPropertyIfExists(properties, "folderclass");
folder.hasChildren = "1".equals(getPropertyIfExists(properties, "hassubs"));
folder.noInferiors = "1".equals(getPropertyIfExists(properties, "nosubs"));
+ folder.count = getIntPropertyIfExists(properties, "count");
folder.unreadCount = getIntPropertyIfExists(properties, "unreadcount");
+ // fake recent value
+ folder.recent = folder.unreadCount;
folder.ctag = getPropertyIfExists(properties, "contenttag");
folder.etag = getPropertyIfExists(properties, "lastmodified");
@@ -1764,6 +1773,7 @@ public class DavExchangeSession extends ExchangeSession {
FOLDER_PROPERTIES.add("folderclass");
FOLDER_PROPERTIES.add("hassubs");
FOLDER_PROPERTIES.add("nosubs");
+ FOLDER_PROPERTIES.add("count");
FOLDER_PROPERTIES.add("unreadcount");
FOLDER_PROPERTIES.add("contenttag");
FOLDER_PROPERTIES.add("lastmodified");
@@ -1929,7 +1939,13 @@ public class DavExchangeSession extends ExchangeSession {
if (buffer.length() > 0) {
buffer.append(',');
}
- buffer.append(((Node) node).getTextContent());
+ if (node instanceof Node) {
+ // jackrabbit
+ buffer.append(((Node) node).getTextContent());
+ } else {
+ // ExchangeDavMethod
+ buffer.append(node);
+ }
}
return buffer.toString();
} else {
@@ -2010,6 +2026,8 @@ public class DavExchangeSession extends ExchangeSession {
String lastmodified = convertDateFromExchange(getPropertyIfExists(properties, "lastmodified"));
message.recent = !message.read && lastmodified != null && lastmodified.equals(message.date);
+ message.keywords = getPropertyIfExists(properties, "keywords");
+
if (LOGGER.isDebugEnabled()) {
StringBuilder buffer = new StringBuilder();
buffer.append("Message");
@@ -2066,6 +2084,7 @@ public class DavExchangeSession extends ExchangeSession {
ITEM_PROPERTIES.add("instancetype");
ITEM_PROPERTIES.add("urlcompname");
ITEM_PROPERTIES.add("subject");
+ ITEM_PROPERTIES.add("contentclass");
}
protected Set<String> getItemProperties() {
@@ -2486,6 +2505,8 @@ public class DavExchangeSession extends ExchangeSession {
list.add(Field.createDavProperty("deleted", entry.getValue()));
} else if ("datereceived".equals(entry.getKey())) {
list.add(Field.createDavProperty("datereceived", entry.getValue()));
+ } else if ("keywords".equals(entry.getKey())) {
+ list.add(Field.createDavProperty("keywords", entry.getValue()));
}
}
}
diff --git a/src/java/davmail/exchange/dav/ExchangeDavMethod.java b/src/java/davmail/exchange/dav/ExchangeDavMethod.java
index 757a703..c48d4dd 100644
--- a/src/java/davmail/exchange/dav/ExchangeDavMethod.java
+++ b/src/java/davmail/exchange/dav/ExchangeDavMethod.java
@@ -178,12 +178,32 @@ public abstract class ExchangeDavMethod extends PostMethod {
if (XMLStreamUtil.isStartTag(reader)) {
Namespace namespace = Namespace.getNamespace(reader.getNamespaceURI());
String tagLocalName = reader.getLocalName();
+ if (reader.getAttributeCount() > 0 && "mv.string".equals(reader.getAttributeValue(0))) {
+ handleMultiValuedProperty(reader, multiStatusResponse);
+ } else {
+ String tagContent = getTagContent(reader);
+ if (tagContent != null) {
+ multiStatusResponse.add(new DefaultDavProperty(tagLocalName, tagContent, namespace));
+ }
+ }
+ }
+ }
+ }
+
+ protected void handleMultiValuedProperty(XMLStreamReader reader, MultiStatusResponse multiStatusResponse) throws XMLStreamException {
+ String tagLocalName = reader.getLocalName();
+ Namespace namespace = Namespace.getNamespace(reader.getNamespaceURI());
+ ArrayList<String> values = new ArrayList<String>();
+ while (reader.hasNext() && !XMLStreamUtil.isEndTag(reader, tagLocalName)) {
+ reader.next();
+ if (XMLStreamUtil.isStartTag(reader)) {
String tagContent = getTagContent(reader);
if (tagContent != null) {
- multiStatusResponse.add(new DefaultDavProperty(tagLocalName, tagContent, namespace));
+ values.add(tagContent);
}
}
}
+ multiStatusResponse.add(new DefaultDavProperty(tagLocalName, values, namespace));
}
protected String getTagContent(XMLStreamReader reader) throws XMLStreamException {
diff --git a/src/java/davmail/exchange/dav/Field.java b/src/java/davmail/exchange/dav/Field.java
index fabfe59..15b71b9 100644
--- a/src/java/davmail/exchange/dav/Field.java
+++ b/src/java/davmail/exchange/dav/Field.java
@@ -106,6 +106,7 @@ public class Field {
createField("folderclass", SCHEMAS_EXCHANGE, "outlookfolderclass");
createField(DAV, "hassubs");
createField(DAV, "nosubs");
+ createField("count", DAV, "objectcount");
createField(URN_SCHEMAS_HTTPMAIL, "unreadcount");
createField(SCHEMAS_REPL, "contenttag");
@@ -537,7 +538,7 @@ public class Field {
} else if (field.isMultivalued) {
// multivalued field, split values separated by \n
List<XmlSerializable> valueList = new ArrayList<XmlSerializable>();
- String[] values = value.split("\n");
+ String[] values = value.split(",");
for (final String singleValue : values) {
valueList.add(new XmlSerializable() {
public Element toXml(Document document) {
diff --git a/src/java/davmail/exchange/ews/EwsExchangeSession.java b/src/java/davmail/exchange/ews/EwsExchangeSession.java
index 41b25f3..5cd960e 100644
--- a/src/java/davmail/exchange/ews/EwsExchangeSession.java
+++ b/src/java/davmail/exchange/ews/EwsExchangeSession.java
@@ -186,7 +186,12 @@ public class EwsExchangeSession extends ExchangeSession {
// no need to check logon method body
if (method != null) {
method.releaseConnection();
- // need to retrieve email and alias
+ }
+ boolean directEws = method == null || "/ews/services.wsdl".equalsIgnoreCase(method.getPath());
+
+ // options page is not available in direct EWS mode
+ if (!directEws) {
+ // retrieve email and alias from options page
getEmailAndAliasFromOptions();
}
@@ -240,6 +245,23 @@ public class EwsExchangeSession extends ExchangeSession {
httpClient.getParams().setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true);
}
+ // direct EWS: get primary smtp email address with ResolveNames
+ if (directEws) {
+ try {
+ ResolveNamesMethod resolveNamesMethod = new ResolveNamesMethod(alias);
+ executeMethod(resolveNamesMethod);
+ List<EWSMethod.Item> responses = resolveNamesMethod.getResponseItems();
+ for (EWSMethod.Item response : responses) {
+ if (alias.equalsIgnoreCase(response.get("Name"))) {
+ email = response.get("EmailAddress");
+ currentMailboxPath = "/users/" + email.toLowerCase();
+ }
+ }
+ } catch (IOException e) {
+ LOGGER.warn("Unable to get primary email address with ResolveNames", e);
+ }
+ }
+
try {
folderIdMap = new HashMap<String, String>();
// load actual well known folder ids
@@ -428,6 +450,8 @@ public class EwsExchangeSession extends ExchangeSession {
list.add(Field.createFieldUpdate("deleted", entry.getValue()));
} else if ("datereceived".equals(entry.getKey())) {
list.add(Field.createFieldUpdate("datereceived", entry.getValue()));
+ } else if ("keywords".equals(entry.getKey())) {
+ list.add(Field.createFieldUpdate("keywords", entry.getValue()));
}
}
return list;
@@ -617,6 +641,8 @@ public class EwsExchangeSession extends ExchangeSession {
String lastmodified = convertDateFromExchange(response.get(Field.get("lastmodified").getResponseName()));
message.recent = !message.read && lastmodified != null && lastmodified.equals(message.date);
+ message.keywords = response.get(Field.get("keywords").getResponseName());
+
if (LOGGER.isDebugEnabled()) {
StringBuilder buffer = new StringBuilder();
buffer.append("Message");
@@ -865,10 +891,11 @@ public class EwsExchangeSession extends ExchangeSession {
@Override
public Condition headerIsEqualTo(String headerName, String value) {
if (serverVersion.startsWith("Exchange2010")) {
- if ("message-id".equals(headerName)
- || "from".equals(headerName)
+ if ("from".equals(headerName)
|| "to".equals(headerName)
- || "cc".equals(headerName)
+ || "cc".equals(headerName)) {
+ return new AttributeCondition("msg"+headerName, Operator.Contains, value, ContainmentMode.Substring, ContainmentComparison.IgnoreCase);
+ } else if ("message-id".equals(headerName)
|| "bcc".equals(headerName)) {
return new AttributeCondition(headerName, Operator.Contains, value, ContainmentMode.Substring, ContainmentComparison.IgnoreCase);
} else {
@@ -933,6 +960,7 @@ public class EwsExchangeSession extends ExchangeSession {
FOLDER_PROPERTIES.add(Field.get("lastmodified"));
FOLDER_PROPERTIES.add(Field.get("folderclass"));
FOLDER_PROPERTIES.add(Field.get("ctag"));
+ FOLDER_PROPERTIES.add(Field.get("count"));
FOLDER_PROPERTIES.add(Field.get("unread"));
FOLDER_PROPERTIES.add(Field.get("hassubs"));
FOLDER_PROPERTIES.add(Field.get("uidNext"));
@@ -946,7 +974,10 @@ public class EwsExchangeSession extends ExchangeSession {
folder.folderClass = item.get(Field.get("folderclass").getResponseName());
folder.etag = item.get(Field.get("lastmodified").getResponseName());
folder.ctag = item.get(Field.get("ctag").getResponseName());
+ folder.count = item.getInt(Field.get("count").getResponseName());
folder.unreadCount = item.getInt(Field.get("unread").getResponseName());
+ // fake recent value
+ folder.recent = folder.unreadCount;
folder.hasChildren = item.getBoolean(Field.get("hassubs").getResponseName());
// noInferiors not implemented
folder.uidNext = item.getInt(Field.get("uidNext").getResponseName());
@@ -2088,7 +2119,7 @@ public class EwsExchangeSession extends ExchangeSession {
protected FolderId getFolderIdIfExists(String folderPath) throws IOException {
String lowerCaseFolderPath = folderPath.toLowerCase();
- if (currentMailboxPath.equals(lowerCaseFolderPath)) {
+ if (lowerCaseFolderPath.equals(currentMailboxPath)) {
return getSubFolderIdIfExists(null, "");
} else if (lowerCaseFolderPath.startsWith(currentMailboxPath + '/')) {
return getSubFolderIdIfExists(null, folderPath.substring(currentMailboxPath.length() + 1));
diff --git a/src/java/davmail/exchange/ews/ExtendedFieldURI.java b/src/java/davmail/exchange/ews/ExtendedFieldURI.java
index 9571801..702ba01 100644
--- a/src/java/davmail/exchange/ews/ExtendedFieldURI.java
+++ b/src/java/davmail/exchange/ews/ExtendedFieldURI.java
@@ -128,7 +128,7 @@ public class ExtendedFieldURI implements FieldURI {
appendTo(buffer);
if (propertyType == PropertyType.StringArray) {
buffer.append("<t:Values>");
- String[] values = value.split("\n");
+ String[] values = value.split(",");
for (final String singleValue : values) {
buffer.append("<t:Value>");
buffer.append(StringUtil.xmlEncode(singleValue));
diff --git a/src/java/davmail/exchange/ews/Field.java b/src/java/davmail/exchange/ews/Field.java
index f7b0c0e..3fa0549 100644
--- a/src/java/davmail/exchange/ews/Field.java
+++ b/src/java/davmail/exchange/ews/Field.java
@@ -40,7 +40,9 @@ public final class Field {
// folder
FIELD_MAP.put("ctag", new ExtendedFieldURI(0x670a, ExtendedFieldURI.PropertyType.SystemTime)); // PR_LOCAL_COMMIT_TIME_MAX
+ FIELD_MAP.put("count", new ExtendedFieldURI(0x3602, ExtendedFieldURI.PropertyType.Integer)); // PR_CONTENT_COUNT
FIELD_MAP.put("unread", new ExtendedFieldURI(0x3603, ExtendedFieldURI.PropertyType.Integer)); // PR_CONTENT_UNREAD
+
FIELD_MAP.put("hassubs", new ExtendedFieldURI(0x360a, ExtendedFieldURI.PropertyType.Boolean)); // PR_SUBFOLDERS
FIELD_MAP.put("folderDisplayName", new UnindexedFieldURI("folder:DisplayName"));
@@ -74,15 +76,22 @@ public final class Field {
FIELD_MAP.put("iconIndex", new ExtendedFieldURI(0x1080, ExtendedFieldURI.PropertyType.Integer));// PR_ICON_INDEX
FIELD_MAP.put("datereceived", new ExtendedFieldURI(0x0e06, ExtendedFieldURI.PropertyType.SystemTime));// PR_MESSAGE_DELIVERY_TIME
- FIELD_MAP.put("to", new UnindexedFieldURI("message:ToRecipients"));
- FIELD_MAP.put("cc", new UnindexedFieldURI("message:CcRecipients"));
- FIELD_MAP.put("from", new UnindexedFieldURI("message:From"));
+ FIELD_MAP.put("msgfrom", new UnindexedFieldURI("message:From"));
+ FIELD_MAP.put("msgto", new UnindexedFieldURI("message:ToRecipients"));
+ FIELD_MAP.put("msgcc", new UnindexedFieldURI("message:CcRecipients"));
+
+ FIELD_MAP.put("from", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "from"));
+ FIELD_MAP.put("to", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "to"));
+ FIELD_MAP.put("cc", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "cc"));
+ FIELD_MAP.put("bcc", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "bcc"));
+
+ FIELD_MAP.put("message-id", new UnindexedFieldURI("message:InternetMessageId"));
FIELD_MAP.put("bcc", new UnindexedFieldURI("message:BccRecipients"));
FIELD_MAP.put("messageheaders", new ExtendedFieldURI(0x007D, ExtendedFieldURI.PropertyType.String)); // PR_TRANSPORT_MESSAGE_HEADERS
FIELD_MAP.put("contentclass", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.InternetHeaders, "content-class"));
- FIELD_MAP.put("message-id", new UnindexedFieldURI("message:InternetMessageId"));
+
FIELD_MAP.put("body", new UnindexedFieldURI("item:Body"));
diff --git a/src/java/davmail/imap/ImapConnection.java b/src/java/davmail/imap/ImapConnection.java
index ecabef9..b42004e 100644
--- a/src/java/davmail/imap/ImapConnection.java
+++ b/src/java/davmail/imap/ImapConnection.java
@@ -228,7 +228,7 @@ public class ImapConnection extends AbstractConnection {
sendClient("* OK [UIDNEXT " + currentFolder.getUidNext() + ']');
}
sendClient("* FLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded Junk)");
- sendClient("* OK [PERMANENTFLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded Junk)]");
+ sendClient("* OK [PERMANENTFLAGS (\\Answered \\Deleted \\Draft \\Flagged \\Seen $Forwarded Junk \\*)]");
if ("select".equalsIgnoreCase(command)) {
sendClient(commandId + " OK [READ-WRITE] " + command + " completed");
} else {
@@ -434,7 +434,7 @@ public class ImapConnection extends AbstractConnection {
// handle optional flags
String nextToken = tokens.nextQuotedToken();
if (nextToken.startsWith("(")) {
- flags = removeQuotes(nextToken);
+ flags = StringUtil.removeQuotes(nextToken);
if (tokens.hasMoreTokens()) {
nextToken = tokens.nextToken();
if (tokens.hasMoreTokens()) {
@@ -443,7 +443,7 @@ public class ImapConnection extends AbstractConnection {
}
}
} else if (tokens.hasMoreTokens()) {
- date = removeQuotes(nextToken);
+ date = StringUtil.removeQuotes(nextToken);
nextToken = tokens.nextToken();
}
@@ -492,7 +492,7 @@ public class ImapConnection extends AbstractConnection {
properties.put("datereceived", dateFormatter.format(dateReceived));
}
- int size = Integer.parseInt(removeQuotes(nextToken));
+ int size = Integer.parseInt(StringUtil.removeQuotes(nextToken));
sendClient("+ send literal data");
byte[] buffer = in.readContent(size);
// empty line
@@ -517,9 +517,9 @@ public class ImapConnection extends AbstractConnection {
while (in.available() == 0) {
if (++count >= imapIdleDelay) {
count = 0;
- List<Long> previousImapUidList = currentFolder.getImapUidList();
+ TreeMap<Long,String> previousImapFlagMap = currentFolder.getImapFlagMap();
if (session.refreshFolder(currentFolder)) {
- handleRefresh(previousImapUidList, currentFolder.getImapUidList());
+ handleRefresh(previousImapFlagMap, currentFolder.getImapFlagMap());
}
}
// sleep 1 second
@@ -542,9 +542,9 @@ public class ImapConnection extends AbstractConnection {
} else if ("noop".equalsIgnoreCase(command) || "check".equalsIgnoreCase(command)) {
if (currentFolder != null) {
DavGatewayTray.debug(new BundleMessage("LOG_IMAP_COMMAND", command, currentFolder.folderPath));
- List<Long> previousImapUidList = currentFolder.getImapUidList();
+ TreeMap<Long,String> previousImapFlagMap = currentFolder.getImapFlagMap();
if (session.refreshFolder(currentFolder)) {
- handleRefresh(previousImapUidList, currentFolder.getImapUidList());
+ handleRefresh(previousImapFlagMap, currentFolder.getImapFlagMap());
}
}
sendClient(commandId + " OK " + command + " completed");
@@ -691,16 +691,21 @@ public class ImapConnection extends AbstractConnection {
* @param imapUidList uid list after refresh
* @throws IOException on error
*/
- private void handleRefresh(List<Long> previousImapUidList, List<Long> imapUidList) throws IOException {
- //
+ private void handleRefresh(TreeMap<Long,String> previousImapFlagMap, TreeMap<Long,String> imapFlagMap) throws IOException {
+ // send deleted message expunge notification
int index = 1;
- for (long previousImapUid : previousImapUidList) {
- if (!imapUidList.contains(previousImapUid)) {
+ for (long previousImapUid : previousImapFlagMap.keySet()) {
+ if (!imapFlagMap.keySet().contains(previousImapUid)) {
sendClient("* " + index + " EXPUNGE");
} else {
+ // send updated flags
+ if (!previousImapFlagMap.get(previousImapUid).equals(imapFlagMap.get(previousImapUid))) {
+ sendClient("* " + index + " FETCH (UID "+previousImapUid+" FLAGS ("+imapFlagMap.get(previousImapUid)+"))");
+ }
index++;
}
}
+
sendClient("* " + currentFolder.count() + " EXISTS");
sendClient("* " + currentFolder.recent + " RECENT");
}
@@ -931,14 +936,22 @@ public class ImapConnection extends AbstractConnection {
protected List<Long> handleSearch(IMAPTokenizer tokens) throws IOException {
List<Long> uidList = new ArrayList<Long>();
+ List<Long> localMessagesUidList = null;
SearchConditions conditions = new SearchConditions();
ExchangeSession.Condition condition = buildConditions(conditions, tokens);
+ session.refreshFolder(currentFolder);
ExchangeSession.MessageList localMessages = currentFolder.searchMessages(condition);
Iterator<ExchangeSession.Message> iterator;
if (conditions.uidRange != null) {
iterator = new UIDRangeIterator(localMessages, conditions.uidRange);
} else if (conditions.indexRange != null) {
- iterator = new RangeIterator(localMessages, conditions.indexRange);
+ // range iterator is on folder messages, not messages returned from search
+ iterator = new RangeIterator(currentFolder.messages, conditions.indexRange);
+ localMessagesUidList = new ArrayList<Long>();
+ // build search result uid list
+ for (ExchangeSession.Message message:localMessages) {
+ localMessagesUidList.add(message.getImapUid());
+ }
} else {
iterator = localMessages.iterator();
}
@@ -946,7 +959,9 @@ public class ImapConnection extends AbstractConnection {
ExchangeSession.Message message = iterator.next();
if ((conditions.flagged == null || message.flagged == conditions.flagged)
&& (conditions.answered == null || message.answered == conditions.answered)
- && (conditions.draft == null || message.draft == conditions.draft)) {
+ && (conditions.draft == null || message.draft == conditions.draft)
+ // range iterator: include messages available in search result
+ && (localMessagesUidList == null || localMessagesUidList.contains(message.getImapUid()))) {
uidList.add(message.getImapUid());
}
}
@@ -1262,6 +1277,8 @@ public class ImapConnection extends AbstractConnection {
session.contains("from", value),
session.contains("to", value),
session.contains("cc", value));
+ } else if ("KEYWORD".equals(token)) {
+ return session.contains("keywords", session.convertFlagToKeyword(tokens.nextToken()));
} else if ("FROM".equals(token)) {
return session.contains("from", tokens.nextToken());
} else if ("TO".equals(token)) {
@@ -1381,48 +1398,76 @@ public class ImapConnection extends AbstractConnection {
StringTokenizer flagtokenizer = new StringTokenizer(flags);
while (flagtokenizer.hasMoreTokens()) {
String flag = flagtokenizer.nextToken();
- if ("\\Seen".equalsIgnoreCase(flag) && message.read) {
- properties.put("read", "0");
- message.read = false;
- } else if ("\\Flagged".equalsIgnoreCase(flag) && message.flagged) {
- properties.put("flagged", "0");
- message.flagged = false;
- } else if ("\\Deleted".equalsIgnoreCase(flag) && message.deleted) {
- properties.put("deleted", null);
- message.deleted = false;
- } else if ("Junk".equalsIgnoreCase(flag) && message.junk) {
- properties.put("junk", "0");
- message.junk = false;
- } else if ("$Forwarded".equalsIgnoreCase(flag) && message.forwarded) {
- properties.put("forwarded", null);
- message.forwarded = false;
- } else if ("\\Answered".equalsIgnoreCase(flag) && message.answered) {
- properties.put("answered", null);
- message.answered = false;
+ if ("\\Seen".equalsIgnoreCase(flag)) {
+ if (message.read) {
+ properties.put("read", "0");
+ message.read = false;
+ }
+ } else if ("\\Flagged".equalsIgnoreCase(flag)) {
+ if (message.flagged) {
+ properties.put("flagged", "0");
+ message.flagged = false;
+ }
+ } else if ("\\Deleted".equalsIgnoreCase(flag)) {
+ if (message.deleted) {
+ properties.put("deleted", null);
+ message.deleted = false;
+ }
+ } else if ("Junk".equalsIgnoreCase(flag)) {
+ if (message.junk) {
+ properties.put("junk", "0");
+ message.junk = false;
+ }
+ } else if ("$Forwarded".equalsIgnoreCase(flag)) {
+ if (message.forwarded) {
+ properties.put("forwarded", null);
+ message.forwarded = false;
+ }
+ } else if ("\\Answered".equalsIgnoreCase(flag)) {
+ if (message.answered) {
+ properties.put("answered", null);
+ message.answered = false;
+ }
+ } else if (message.keywords != null) {
+ properties.put("keywords", message.removeFlag(flag));
}
}
} else if ("+Flags".equalsIgnoreCase(action) || "+FLAGS.SILENT".equalsIgnoreCase(action)) {
StringTokenizer flagtokenizer = new StringTokenizer(flags);
while (flagtokenizer.hasMoreTokens()) {
String flag = flagtokenizer.nextToken();
- if ("\\Seen".equalsIgnoreCase(flag) && !message.read) {
- properties.put("read", "1");
- message.read = true;
- } else if ("\\Deleted".equalsIgnoreCase(flag) && !message.deleted) {
- message.deleted = true;
- properties.put("deleted", "1");
- } else if ("\\Flagged".equalsIgnoreCase(flag) && !message.flagged) {
- properties.put("flagged", "2");
- message.flagged = true;
- } else if ("\\Answered".equalsIgnoreCase(flag) && !message.answered) {
- properties.put("answered", "102");
- message.answered = true;
- } else if ("$Forwarded".equalsIgnoreCase(flag) && !message.forwarded) {
- properties.put("forwarded", "104");
- message.forwarded = true;
- } else if ("Junk".equalsIgnoreCase(flag) && !message.junk) {
- properties.put("junk", "1");
- message.junk = true;
+ if ("\\Seen".equalsIgnoreCase(flag)) {
+ if (!message.read) {
+ properties.put("read", "1");
+ message.read = true;
+ }
+ } else if ("\\Deleted".equalsIgnoreCase(flag)) {
+ if (!message.deleted) {
+ message.deleted = true;
+ properties.put("deleted", "1");
+ }
+ } else if ("\\Flagged".equalsIgnoreCase(flag)) {
+ if (!message.flagged) {
+ properties.put("flagged", "2");
+ message.flagged = true;
+ }
+ } else if ("\\Answered".equalsIgnoreCase(flag)) {
+ if (!message.answered) {
+ properties.put("answered", "102");
+ message.answered = true;
+ }
+ } else if ("$Forwarded".equalsIgnoreCase(flag)) {
+ if (!message.forwarded) {
+ properties.put("forwarded", "104");
+ message.forwarded = true;
+ }
+ } else if ("Junk".equalsIgnoreCase(flag)) {
+ if (!message.junk) {
+ properties.put("junk", "1");
+ message.junk = true;
+ }
+ } else {
+ properties.put("keywords", message.addFlag(flag));
}
}
} else if ("FLAGS".equalsIgnoreCase(action) || "FLAGS.SILENT".equalsIgnoreCase(action)) {
@@ -1433,6 +1478,7 @@ public class ImapConnection extends AbstractConnection {
boolean flagged = false;
boolean answered = false;
boolean forwarded = false;
+ HashSet<String> keywords = null;
// set flags from new flag list
StringTokenizer flagtokenizer = new StringTokenizer(flags);
while (flagtokenizer.hasMoreTokens()) {
@@ -1449,8 +1495,16 @@ public class ImapConnection extends AbstractConnection {
forwarded = true;
} else if ("Junk".equalsIgnoreCase(flag)) {
junk = true;
+ } else {
+ if (keywords == null) {
+ keywords = new HashSet<String>();
+ }
+ keywords.add(flag);
}
}
+ if (keywords != null) {
+ properties.put("keywords", message.setFlags(keywords));
+ }
if (read != message.read) {
message.read = read;
if (message.read) {
@@ -1533,17 +1587,6 @@ public class ImapConnection extends AbstractConnection {
}
}
- protected String removeQuotes(String value) {
- String result = value;
- if (result.startsWith("\"") || result.startsWith("{") || result.startsWith("(")) {
- result = result.substring(1);
- }
- if (result.endsWith("\"") || result.endsWith("}") || result.endsWith(")")) {
- result = result.substring(0, result.length() - 1);
- }
- return result;
- }
-
/**
* Filter to output only headers, also count full size
*/
@@ -1808,7 +1851,7 @@ public class ImapConnection extends AbstractConnection {
@Override
public String nextToken() {
- return removeQuotes(nextQuotedToken());
+ return StringUtil.removeQuotes(nextQuotedToken());
}
public String nextQuotedToken() {
diff --git a/src/java/imapflags.properties b/src/java/imapflags.properties
new file mode 100644
index 0000000..a3c0ef4
--- /dev/null
+++ b/src/java/imapflags.properties
@@ -0,0 +1,23 @@
+#
+# DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+# Copyright (C) 2012 Mickael Guessant
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+$label1=Important
+$label2=Work
+$label3=Personal
+$label4=To Do
+$label5=Later
diff --git a/src/java/imapflags_fr.properties b/src/java/imapflags_fr.properties
new file mode 100644
index 0000000..057ba67
--- /dev/null
+++ b/src/java/imapflags_fr.properties
@@ -0,0 +1,23 @@
+#
+# DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
+# Copyright (C) 2012 Mickael Guessant
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+$label1=Important
+$label2=Travail
+$label3=Personnel
+$label4=À faire
+$label5=En attente
diff --git a/src/site/xdoc/faq.xml b/src/site/xdoc/faq.xml
index 08754e3..c180f9b 100644
--- a/src/site/xdoc/faq.xml
+++ b/src/site/xdoc/faq.xml
@@ -63,7 +63,6 @@
<p>Note to Mac users: OSX applications do not like username with backslash, you have to set windows
domain name in DavMail advanced settings and use the simple username in client application.
</p>
-
<p>
<strong>Authentication fails with error in parsing the status line</strong>
</p>
@@ -75,6 +74,14 @@
OWA url.
</p>
<p>
+ <strong>Exchange RSA two factor authentication form</strong>
+ </p>
+ <p>Exchange now supports two factor authentication for RSA tokens. This requires two potentially
+ different user names in fields userid and username. Use a pipe in client to provide both values:
+ <code>userid|username</code>. In some cases you will need to also provide domain name:
+ <code>userid|domain\username</code>, do not set default windows domain name.
+ </p>
+ <p>
<strong>Where can I find DavMail settings file ?</strong>
</p>
<p>The default location for DavMail settings is a file named .davmail.properties in user home
diff --git a/src/site/xdoc/roadmap.xml b/src/site/xdoc/roadmap.xml
index 4bde3fd..d25137f 100644
--- a/src/site/xdoc/roadmap.xml
+++ b/src/site/xdoc/roadmap.xml
@@ -15,17 +15,7 @@
for improvement. The following section lists the expected new features
and enhancements in next DavMail versions.
</p>
- <subsection name="4.0">
- <p>
- <strong>Next major version</strong>
- </p>
- <ul>
- <li>Implement optional ICS Todo/Task conversion to Outlook tasks: Done</li>
- <li>Exchange 2010 support through EWS: Done</li>
- <li>Switch wrappers to WinRun4J</li>
- </ul>
- </subsection>
- <subsection name="4.0.1">
+ <subsection name="4.1.1">
<p>
<strong>Next patch release</strong>
</p>
@@ -33,7 +23,7 @@
<li>Test windows install on seven without admin rights</li>
</ul>
</subsection>
- <subsection name="4.1">
+ <subsection name="4.2">
<p>
<strong>Next minor release</strong>
</p>
diff --git a/src/web/WEB-INF/classes/davmail.properties b/src/web/WEB-INF/classes/davmail.properties
index 0a49025..0ac9ffc 100644
--- a/src/web/WEB-INF/classes/davmail.properties
+++ b/src/web/WEB-INF/classes/davmail.properties
@@ -1,6 +1,7 @@
davmail.url=http://exchangeServer/exchange/
davmail.popPort=1110
davmail.smtpPort=1025
+davmail.imapPort=1143
davmail.keepDelay=30
davmail.sentKeepDelay=90
davmail.caldavPastDelay=90
--
davmail packaging
More information about the pkg-java-commits
mailing list