diff --git a/src/java/davmail/exchange/dav/Field.java b/src/java/davmail/exchange/dav/Field.java index 29fab20b..7fa7cee4 100644 --- a/src/java/davmail/exchange/dav/Field.java +++ b/src/java/davmail/exchange/dav/Field.java @@ -336,8 +336,6 @@ public class Field { createField("commonstart", DistinguishedPropertySetType.Common, 0x8516, "commonstart", PropertyType.SystemTime); createField("commonend", DistinguishedPropertySetType.Common, 0x8517, "commonend", PropertyType.SystemTime); - - createField("categories", DistinguishedPropertySetType.PublicStrings, 0x9000, "categories", PropertyType.StringArray); } protected static String toHexString(int propertyTag) { diff --git a/src/java/davmail/exchange/ews/EwsExchangeSession.java b/src/java/davmail/exchange/ews/EwsExchangeSession.java index 1abc9c4d..38966925 100644 --- a/src/java/davmail/exchange/ews/EwsExchangeSession.java +++ b/src/java/davmail/exchange/ews/EwsExchangeSession.java @@ -34,6 +34,7 @@ import org.apache.commons.httpclient.*; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.util.URIUtil; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; @@ -1168,7 +1169,7 @@ public class EwsExchangeSession extends ExchangeSession { String type; boolean isException; - protected Event(EWSMethod.Item response) { + protected Event(EWSMethod.Item response) throws URIException { itemId = new ItemId(response); type = response.type; @@ -1177,7 +1178,7 @@ public class EwsExchangeSession extends ExchangeSession { etag = response.get(Field.get("etag").getResponseName()); displayName = response.get(Field.get("displayname").getResponseName()); subject = response.get(Field.get("subject").getResponseName()); - itemName = response.get(Field.get("urlcompname").getResponseName()); + itemName = StringUtil.decodeUrlcompname(response.get(Field.get("urlcompname").getResponseName())); // workaround for missing urlcompname in Exchange 2010 if (itemName == null) { itemName = StringUtil.base64ToUrl(itemId.id) + ".EML"; @@ -1252,7 +1253,15 @@ public class EwsExchangeSession extends ExchangeSession { updates.add(Field.createFieldUpdate("taskstatus", vTodoToTaskStatusMap.get(vTodoStatus))); } updates.add(Field.createFieldUpdate("keywords", vCalendar.getFirstVeventPropertyValue("CATEGORIES"))); - updates.add(Field.createFieldUpdate("duedate", convertCalendarDateToExchange(vCalendar.getFirstVeventPropertyValue("DUE")))); + updates.add(Field.createFieldUpdate("startdate", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DTSTART")))); + updates.add(Field.createFieldUpdate("duedate", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DUE")))); + updates.add(Field.createFieldUpdate("datecompleted", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("COMPLETED")))); + + updates.add(Field.createFieldUpdate("commonstart", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DTSTART")))); + updates.add(Field.createFieldUpdate("commonend", convertTaskDateToZulu(vCalendar.getFirstVeventPropertyValue("DUE")))); + + //updates.add(Field.createFieldUpdate("iscomplete", "COMPLETED".equals(vTodoStatus)?"True":"False")); + if (currentItemId != null) { // update createOrUpdateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly, @@ -1382,8 +1391,11 @@ public class EwsExchangeSession extends ExchangeSession { getItemMethod.addAdditionalProperty(Field.get("description")); getItemMethod.addAdditionalProperty(Field.get("percentcomplete")); getItemMethod.addAdditionalProperty(Field.get("taskstatus")); + getItemMethod.addAdditionalProperty(Field.get("startdate")); getItemMethod.addAdditionalProperty(Field.get("duedate")); - getItemMethod.addAdditionalProperty(Field.get("keywords")); + getItemMethod.addAdditionalProperty(Field.get("datecompleted")); + getItemMethod.addAdditionalProperty(Field.get("keywords")); + } else if (!"Message".equals(type)) { getItemMethod = new GetItemMethod(BaseShape.ID_ONLY, itemId, true); getItemMethod.addAdditionalProperty(Field.get("reminderset")); @@ -1417,11 +1429,13 @@ public class EwsExchangeSession extends ExchangeSession { vTodo.setPropertyValue("DESCRIPTION", getItemMethod.getResponseItem().get(Field.get("description").getResponseName())); vTodo.setPropertyValue("PERCENT-COMPLETE", getItemMethod.getResponseItem().get(Field.get("percentcomplete").getResponseName())); vTodo.setPropertyValue("STATUS", taskTovTodoStatusMap.get(getItemMethod.getResponseItem().get(Field.get("taskstatus").getResponseName()))); + + vTodo.setPropertyValue("DUE;VALUE=DATE", convertDateFromExchangeToTaskDate(getItemMethod.getResponseItem().get(Field.get("duedate").getResponseName()))); + vTodo.setPropertyValue("DTSTART;VALUE=DATE", convertDateFromExchangeToTaskDate(getItemMethod.getResponseItem().get(Field.get("startdate").getResponseName()))); + vTodo.setPropertyValue("COMPLETED;VALUE=DATE", convertDateFromExchangeToTaskDate(getItemMethod.getResponseItem().get(Field.get("datecompleted").getResponseName()))); + vTodo.setPropertyValue("CATEGORIES", getItemMethod.getResponseItem().get(Field.get("keywords").getResponseName())); - VProperty vProperty = new VProperty("DUE", convertDateFromExchange(getItemMethod.getResponseItem().get(Field.get("duedate").getResponseName()))); - vProperty.setParam("TZID", vTimezone.getPropertyValue("TZID")); - vTodo.addProperty(vProperty); localVCalendar.addVObject(vTodo); content = localVCalendar.toString().getBytes("UTF-8"); } else { @@ -2072,6 +2086,55 @@ public class EwsExchangeSession extends ExchangeSession { return zuluDateValue; } + protected String convertDateFromExchangeToTaskDate(String exchangeDateValue) throws DavMailException { + String zuluDateValue = null; + if (exchangeDateValue != null) { + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH); + dateFormat.setTimeZone(GMT_TIMEZONE); + zuluDateValue = dateFormat.format(getExchangeZuluDateFormat().parse(exchangeDateValue)); + } catch (ParseException e) { + throw new DavMailException("EXCEPTION_INVALID_DATE", exchangeDateValue); + } + } + return zuluDateValue; + } + + protected String convertTaskDateToZulu(String value) { + String result = null; + if (value != null && value.length() > 0) { + try { + SimpleDateFormat parser; + if (value.length() == 8) { + parser = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH); + parser.setTimeZone(GMT_TIMEZONE); + } else if (value.length() == 15) { + parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH); + parser.setTimeZone(GMT_TIMEZONE); + } else if (value.length() == 16) { + parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); + parser.setTimeZone(GMT_TIMEZONE); + } else { + parser = ExchangeSession.getExchangeZuluDateFormat(); + } + Calendar calendarValue = Calendar.getInstance(GMT_TIMEZONE); + calendarValue.setTime(parser.parse(value)); + // zulu time: add 12 hours + if (value.length() == 16) { + calendarValue.add(Calendar.HOUR, 12); + } + calendarValue.set(Calendar.HOUR, 0); + calendarValue.set(Calendar.MINUTE, 0); + calendarValue.set(Calendar.SECOND, 0); + result = ExchangeSession.getExchangeZuluDateFormat().format(calendarValue.getTime()); + } catch (ParseException e) { + LOGGER.warn("Invalid date: " + value); + } + } + + return result; + } + /** * Format date to exchange search format. * diff --git a/src/java/davmail/exchange/ews/Field.java b/src/java/davmail/exchange/ews/Field.java index deb6d005..a90b03e0 100644 --- a/src/java/davmail/exchange/ews/Field.java +++ b/src/java/davmail/exchange/ews/Field.java @@ -18,6 +18,8 @@ */ package davmail.exchange.ews; +import davmail.exchange.dav.PropertyType; + import java.util.HashMap; import java.util.Map; @@ -56,7 +58,7 @@ public final class Field { FIELD_MAP.put("mimeContent", new UnindexedFieldURI("item:MimeContent")); - // use PR_RECORD_KEY as unique key + // use PR_RECORD_KEY as unique key FIELD_MAP.put("uid", new ExtendedFieldURI(0x0FF9, ExtendedFieldURI.PropertyType.Binary)); FIELD_MAP.put("messageFlags", new ExtendedFieldURI(0x0e07, ExtendedFieldURI.PropertyType.Integer));//PR_MESSAGE_FLAGS FIELD_MAP.put("imapUid", new ExtendedFieldURI(0x0e23, ExtendedFieldURI.PropertyType.Integer)); @@ -210,7 +212,14 @@ public final class Field { // task FIELD_MAP.put("percentcomplete", new UnindexedFieldURI("task:PercentComplete")); FIELD_MAP.put("taskstatus", new UnindexedFieldURI("task:Status")); - FIELD_MAP.put("duedate", new UnindexedFieldURI("task:DueDate")); + + FIELD_MAP.put("startdate", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x8104, ExtendedFieldURI.PropertyType.SystemTime)); + FIELD_MAP.put("duedate", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x8105, ExtendedFieldURI.PropertyType.SystemTime)); + FIELD_MAP.put("datecompleted", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x810F, ExtendedFieldURI.PropertyType.SystemTime)); + FIELD_MAP.put("iscomplete", new UnindexedFieldURI("task:IsComplete")); + + FIELD_MAP.put("commonstart", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x8516, ExtendedFieldURI.PropertyType.SystemTime)); + FIELD_MAP.put("commonend", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.Task, 0x8517, ExtendedFieldURI.PropertyType.SystemTime)); // attachments FIELD_MAP.put("attachments", new UnindexedFieldURI("item:Attachments")); diff --git a/src/java/davmail/util/StringUtil.java b/src/java/davmail/util/StringUtil.java index f5fec44e..76c8ccc4 100644 --- a/src/java/davmail/util/StringUtil.java +++ b/src/java/davmail/util/StringUtil.java @@ -137,7 +137,15 @@ public final class StringUtil { private static final Pattern CR_PATTERN = Pattern.compile("\r"); private static final Pattern LF_PATTERN = Pattern.compile("\n"); + private static final Pattern URLENCODED_F8FF_PATTERN = Pattern.compile(String.valueOf((char) 0xF8FF)); private static final Pattern URLENCODED_AMP_PATTERN = Pattern.compile("%26"); + private static final Pattern URLENCODED_PLUS_PATTERN = Pattern.compile("%2B"); + private static final Pattern URLENCODED_COLON_PATTERN = Pattern.compile("%3A"); + private static final Pattern URLENCODED_LT_PATTERN = Pattern.compile("%3C"); + private static final Pattern URLENCODED_GT_PATTERN = Pattern.compile("%3E"); + private static final Pattern URLENCODED_QUOTE_PATTERN = Pattern.compile("%22"); + private static final Pattern URLENCODED_X0D0A_PATTERN = Pattern.compile("\r\n"); + private static final Pattern URLENCODED_PERCENT_PATTERN = Pattern.compile("%25"); private static final Pattern ENCODED_AMP_PATTERN = Pattern.compile("&"); private static final Pattern ENCODED_LT_PATTERN = Pattern.compile("<"); @@ -284,6 +292,9 @@ public final class StringUtil { */ public static String encodeUrlcompname(String value) { String result = value; + if (result.indexOf('%') >= 0) { + result = PERCENT_PATTERN.matcher(result).replaceAll("%25"); + } if (result.indexOf("_xF8FF_") >= 0) { result = F8FF_PATTERN.matcher(result).replaceAll(String.valueOf((char) 0xF8FF)); } @@ -308,8 +319,43 @@ public final class StringUtil { if (result.indexOf("_x000D__x000A_") >= 0) { result = X0D0A_PATTERN.matcher(result).replaceAll("\r\n"); } - if (result.indexOf('%') >= 0) { - result = PERCENT_PATTERN.matcher(result).replaceAll("%25"); + return result; + } + + /** + * Decode urlcompname to get item name. + * + * @param urlcompname encoded value + * @return decoded value + */ + public static String decodeUrlcompname(String urlcompname) { + String result = urlcompname; + if (result.indexOf((char) 0xF8FF) >= 0) { + result = URLENCODED_F8FF_PATTERN.matcher(result).replaceAll("_xF8FF_"); + } + if (result.indexOf("%26") >= 0) { + result = URLENCODED_AMP_PATTERN.matcher(result).replaceAll("&"); + } + if (result.indexOf("%2B") >= 0) { + result = URLENCODED_PLUS_PATTERN.matcher(result).replaceAll("+"); + } + if (result.indexOf("%3A") >= 0) { + result = URLENCODED_COLON_PATTERN.matcher(result).replaceAll(":"); + } + if (result.indexOf("%3C") >= 0) { + result = URLENCODED_LT_PATTERN.matcher(result).replaceAll("<"); + } + if (result.indexOf("%3E") >= 0) { + result = URLENCODED_GT_PATTERN.matcher(result).replaceAll(">"); + } + if (result.indexOf("%22") >= 0) { + result = URLENCODED_QUOTE_PATTERN.matcher(result).replaceAll("\""); + } + if (result.indexOf("\r\n") >= 0) { + result = URLENCODED_X0D0A_PATTERN.matcher(result).replaceAll("_x000D__x000A_"); + } + if (result.indexOf("%25") >= 0) { + result = URLENCODED_PERCENT_PATTERN.matcher(result).replaceAll("%"); } return result; }