diff --git a/src/java/davmail/caldav/CaldavConnection.java b/src/java/davmail/caldav/CaldavConnection.java index 259e4e90..e45c7e64 100644 --- a/src/java/davmail/caldav/CaldavConnection.java +++ b/src/java/davmail/caldav/CaldavConnection.java @@ -208,10 +208,10 @@ public class CaldavConnection extends AbstractConnection { if (request.isPropFind() && request.isPathLength(3)) { sendUserRoot(request); } else { - handleCalendar(request); + handleFolder(request); } } else if (request.isPath(1, "public")) { - handleCalendar(request); + handleFolder(request); } else { sendUnsupported(request); } @@ -238,7 +238,7 @@ public class CaldavConnection extends AbstractConnection { } } - protected void handleCalendar(CaldavRequest request) throws IOException { + protected void handleFolder(CaldavRequest request) throws IOException { String lastPath = xmlDecodeName(request.getLastPath()); // folder requests if (request.isPropFind() && "inbox".equals(lastPath)) { @@ -254,7 +254,7 @@ public class CaldavConnection extends AbstractConnection { sendHttpResponse(status); } } else if (request.isPropFind()) { - sendCalendar(request); + sendFolder(request); } else if (request.isPropPatch()) { patchCalendar(request); } else if (request.isReport()) { @@ -359,20 +359,29 @@ public class CaldavConnection extends AbstractConnection { } /** - * Append calendar object to Caldav response. + * Append folder object to Caldav response. * * @param response Caldav response * @param request Caldav request * @param subFolder calendar folder path relative to request path * @throws IOException on error */ - public void appendCalendar(CaldavResponse response, CaldavRequest request, String subFolder) throws IOException { + public void appendFolder(CaldavResponse response, CaldavRequest request, String subFolder) throws IOException { + ExchangeSession.Folder folder = session.getFolder(request.getExchangeFolderPath(subFolder)); + response.startResponse(URIUtil.encodePath(request.getPath(subFolder))); response.startPropstat(); if (request.hasProperty("resourcetype")) { - response.appendProperty("D:resourcetype", "" + - ""); + if (folder.isContact()) { + response.appendProperty("D:resourcetype", "CD=\"urn:ietf:params:xml:ns:carddav\"", "" + + ""); + } else if (folder.isCalendar()) { + response.appendProperty("D:resourcetype", "" + ""); + } else { + response.appendProperty("D:resourcetype", ""); + } + } if (request.hasProperty("owner")) { if ("users".equals(request.getPathElement(1))) { @@ -382,14 +391,18 @@ public class CaldavConnection extends AbstractConnection { } } if (request.hasProperty("getcontenttype")) { - response.appendProperty("D:getcontenttype", "text/calendar; component=vevent"); + if (folder.isContact()) { + response.appendProperty("D:getcontenttype", "text/x-vcard"); + } else if (folder.isCalendar()) { + response.appendProperty("D:getcontenttype", "text/calendar; component=vevent"); + } } if (request.hasProperty("getetag")) { - response.appendProperty("D:getetag", session.getFolderResourceTag(request.getExchangeFolderPath(subFolder))); + response.appendProperty("D:getetag", folder.etag); } if (request.hasProperty("getctag")) { response.appendProperty("CS:getctag", "CS=\"http://calendarserver.org/ns/\"", - base64Encode(session.getFolderCtag(request.getExchangeFolderPath(subFolder)))); + base64Encode(folder.ctag)); } if (request.hasProperty("displayname")) { if (subFolder == null || subFolder.length() == 0) { @@ -401,7 +414,7 @@ public class CaldavConnection extends AbstractConnection { if (request.hasProperty("calendar-description")) { response.appendProperty("C:calendar-description", ""); } - if (request.hasProperty("supported-calendar-component-set")) { + if (request.hasProperty("supported-calendar-component-set") && folder.isCalendar()) { response.appendProperty("C:supported-calendar-component-set", ""); } @@ -507,7 +520,7 @@ public class CaldavConnection extends AbstractConnection { CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS); response.startMultistatus(); appendInbox(response, request, null); - // do not try to access inbox on shared calendar + // do not try to access inbox on shared calendar if (!session.isSharedFolder(request.getExchangeFolderPath(null)) && request.getDepth() == 1 && !Settings.getBooleanProperty("davmail.caldavDisableInbox")) { try { DavGatewayTray.debug(new BundleMessage("LOG_SEARCHING_CALENDAR_MESSAGES")); @@ -543,11 +556,11 @@ public class CaldavConnection extends AbstractConnection { * @param request Caldav request * @throws IOException on error */ - public void sendCalendar(CaldavRequest request) throws IOException { + public void sendFolder(CaldavRequest request) throws IOException { String folderPath = request.getExchangeFolderPath(); CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS); response.startMultistatus(); - appendCalendar(response, request, null); + appendFolder(response, request, null); if (request.getDepth() == 1) { DavGatewayTray.debug(new BundleMessage("LOG_SEARCHING_CALENDAR_EVENTS", folderPath)); List events = session.getAllEvents(folderPath); @@ -557,7 +570,7 @@ public class CaldavConnection extends AbstractConnection { if (!folderPath.startsWith("/public")) { List folderList = session.getSubCalendarFolders(folderPath, false); for (ExchangeSession.Folder folder : folderList) { - appendCalendar(response, request, folder.folderPath.substring(folder.folderPath.indexOf('/') + 1)); + appendFolder(response, request, folder.folderPath.substring(folder.folderPath.indexOf('/') + 1)); } } } @@ -670,7 +683,7 @@ public class CaldavConnection extends AbstractConnection { if (request.getDepth() == 1) { appendInbox(response, request, "inbox"); appendOutbox(response, request, "outbox"); - appendCalendar(response, request, "calendar"); + appendFolder(response, request, "calendar"); } response.endResponse(); response.endMultistatus(); @@ -1003,7 +1016,7 @@ public class CaldavConnection extends AbstractConnection { if (status != HttpStatus.SC_UNAUTHORIZED) { String version = DavGateway.getCurrentVersion(); sendClient("Server: DavMail Gateway " + (version == null ? "" : version)); - sendClient("DAV: 1, calendar-access, calendar-schedule, calendarserver-private-events"); + sendClient("DAV: 1, calendar-access, calendar-schedule, calendarserver-private-events, addressbook"); SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH); // force GMT timezone formatter.setTimeZone(ExchangeSession.GMT_TIMEZONE); diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index 1f72f7f8..aca3f475 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -133,10 +133,12 @@ public class ExchangeSession { protected static final DavPropertyNameSet FOLDER_PROPERTIES = new DavPropertyNameSet(); static { + FOLDER_PROPERTIES.add(DavPropertyName.create("contentclass", Namespace.getNamespace("DAV:"))); FOLDER_PROPERTIES.add(DavPropertyName.create("hassubs")); FOLDER_PROPERTIES.add(DavPropertyName.create("nosubs")); FOLDER_PROPERTIES.add(DavPropertyName.create("unreadcount", URN_SCHEMAS_HTTPMAIL)); FOLDER_PROPERTIES.add(DavPropertyName.create("contenttag", Namespace.getNamespace("http://schemas.microsoft.com/repl/"))); + FOLDER_PROPERTIES.add(DavPropertyName.create("resourcetag", Namespace.getNamespace("http://schemas.microsoft.com/repl/"))); } protected static final DavPropertyNameSet CONTENT_TAG = new DavPropertyNameSet(); @@ -969,10 +971,12 @@ public class ExchangeSession { String href = URIUtil.decode(entity.getHref()); Folder folder = new Folder(); DavPropertySet properties = entity.getProperties(HttpStatus.SC_OK); + folder.contentClass = getPropertyIfExists(properties, "contentclass", Namespace.getNamespace("DAV:")); folder.hasChildren = "1".equals(getPropertyIfExists(properties, "hassubs", Namespace.getNamespace("DAV:"))); folder.noInferiors = "1".equals(getPropertyIfExists(properties, "nosubs", Namespace.getNamespace("DAV:"))); folder.unreadCount = getIntPropertyIfExists(properties, "unreadcount", URN_SCHEMAS_HTTPMAIL); - folder.contenttag = getPropertyIfExists(properties, "contenttag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")); + folder.ctag = getPropertyIfExists(properties, "contenttag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")); + folder.etag = getPropertyIfExists(properties, "resourcetag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")); // replace well known folder names if (href.startsWith(inboxUrl)) { @@ -1178,15 +1182,15 @@ public class ExchangeSession { */ public boolean refreshFolder(Folder currentFolder) throws IOException { Folder newFolder = getFolder(currentFolder.folderName); - if (currentFolder.contenttag == null || !currentFolder.contenttag.equals(newFolder.contenttag)) { + if (currentFolder.ctag == null || !currentFolder.ctag.equals(newFolder.ctag)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Contenttag changed on " + currentFolder.folderName + ' ' - + currentFolder.contenttag + " => " + newFolder.contenttag + ", reloading messages"); + + currentFolder.ctag + " => " + newFolder.ctag + ", reloading messages"); } currentFolder.hasChildren = newFolder.hasChildren; currentFolder.noInferiors = newFolder.noInferiors; currentFolder.unreadCount = newFolder.unreadCount; - currentFolder.contenttag = newFolder.contenttag; + currentFolder.ctag = newFolder.ctag; currentFolder.loadMessages(); return true; } else { @@ -1322,6 +1326,11 @@ public class ExchangeSession { * Logical (IMAP) folder path. */ public String folderPath; + + /** + * Folder content class. + */ + public String contentClass; /** * Folder unread message count. */ @@ -1341,7 +1350,11 @@ public class ExchangeSession { /** * Folder content tag (to detect folder content changes). */ - public String contenttag; + public String ctag; + /** + * Folder etag (to detect folder object changes). + */ + public String etag; /** * Folder message list, empty before loadMessages call. */ @@ -1441,6 +1454,22 @@ public class ExchangeSession { public Message get(int index) { return messages.get(index); } + + /** + * Calendar folder flag. + * @return true if this is a calendar folder + */ + public boolean isCalendar() { + return "urn:content-classes:calendarfolder".equals(contentClass); + } + + /** + * Contact folder flag. + * @return true if this is a calendar folder + */ + public boolean isContact() { + return "urn:content-classes:contactfolder".equals(contentClass); + } } /**