From 2a9c62ec14388ae04789aaede27334b531a9c9ba Mon Sep 17 00:00:00 2001 From: mguessan Date: Tue, 27 Jul 2010 09:34:04 +0000 Subject: [PATCH] Caldav: failover for 404 not found on items containing '+' in url, search item by urlcompname to get permanenturl git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1295 3d1905a2-6b24-0410-a738-b14d5a86fcbd --- src/java/davmail/caldav/CaldavConnection.java | 21 +-- .../exchange/dav/DavExchangeSession.java | 138 ++++++++++++------ src/java/davmail/util/StringUtil.java | 26 +++- 3 files changed, 119 insertions(+), 66 deletions(-) diff --git a/src/java/davmail/caldav/CaldavConnection.java b/src/java/davmail/caldav/CaldavConnection.java index 5ebfdc94..766017ca 100644 --- a/src/java/davmail/caldav/CaldavConnection.java +++ b/src/java/davmail/caldav/CaldavConnection.java @@ -131,7 +131,7 @@ public class CaldavConnection extends AbstractConnection { tokens = new StringTokenizer(line); String command = tokens.nextToken(); Map headers = parseHeaders(); - String encodedPath = encodePlusSign(tokens.nextToken()); + String encodedPath = StringUtil.encodePlusSign(tokens.nextToken()); String path = URIUtil.decode(encodedPath); String content = getContent(headers.get("content-length")); setSocketTimeout(headers.get("keep-alive")); @@ -1186,21 +1186,6 @@ public class CaldavConnection extends AbstractConnection { } - /** - * Make sure + sign in URL is encoded. - * - * @param path URL path - * @return reencoded path - */ - protected String encodePlusSign(String path) { - // make sure + sign in URL is encoded - if (path.indexOf('+') >= 0) { - return path.replaceAll("\\+", "%2B"); - } else { - return path; - } - } - protected class CaldavRequest { protected final String command; protected final String path; @@ -1332,7 +1317,7 @@ public class CaldavConnection extends AbstractConnection { } protected boolean isBrokenHrefEncoding() { - return isUserAgent("DAVKit/3") || isUserAgent("eM Client/") || isUserAgent("Lightning/1.0b2"); + return isUserAgent("DAVKit/3") || isUserAgent("eM Client/")/* || isUserAgent("Lightning/1.0b2")*/; } protected boolean isLightning() { @@ -1400,7 +1385,7 @@ public class CaldavConnection extends AbstractConnection { if (isBrokenHrefEncoding()) { hrefs.add(streamReader.getElementText()); } else { - hrefs.add(URIUtil.decode(encodePlusSign(streamReader.getElementText()))); + hrefs.add(URIUtil.decode(StringUtil.encodePlusSign(streamReader.getElementText()))); } } } diff --git a/src/java/davmail/exchange/dav/DavExchangeSession.java b/src/java/davmail/exchange/dav/DavExchangeSession.java index f1d262fb..75170034 100644 --- a/src/java/davmail/exchange/dav/DavExchangeSession.java +++ b/src/java/davmail/exchange/dav/DavExchangeSession.java @@ -47,7 +47,6 @@ import org.w3c.dom.Node; import javax.mail.MessagingException; import java.io.*; -import java.net.HttpURLConnection; import java.net.NoRouteToHostException; import java.net.URL; import java.net.UnknownHostException; @@ -611,15 +610,8 @@ public class DavExchangeSession extends ExchangeSession { return propertyValues; } - /** - * Create or update contact - * - * @return action result - * @throws IOException on error - */ - public ItemResult createOrUpdate() throws IOException { - int status = 0; - ExchangePropPatchMethod propPatchMethod = new ExchangePropPatchMethod(URIUtil.encodePath(getHref()), buildProperties()); + protected ExchangePropPatchMethod internalCreateOrUpdate(String encodedHref) throws IOException { + ExchangePropPatchMethod propPatchMethod = new ExchangePropPatchMethod(encodedHref, buildProperties()); propPatchMethod.setRequestHeader("Translate", "f"); if (etag != null) { propPatchMethod.setRequestHeader("If-Match", etag); @@ -628,21 +620,51 @@ public class DavExchangeSession extends ExchangeSession { propPatchMethod.setRequestHeader("If-None-Match", noneMatch); } try { - status = httpClient.executeMethod(propPatchMethod); - if (status == HttpStatus.SC_MULTI_STATUS) { - status = propPatchMethod.getResponseStatusCode(); - //noinspection VariableNotUsedInsideIf - if (status == HttpStatus.SC_CREATED) { - LOGGER.debug("Created contact " + getHref()); - } else { - LOGGER.debug("Updated contact " + getHref()); - } - } else { - LOGGER.warn("Unable to create or update contact " + status + ' ' + propPatchMethod.getStatusLine()); - } + httpClient.executeMethod(propPatchMethod); } finally { propPatchMethod.releaseConnection(); } + return propPatchMethod; + } + + /** + * Create or update contact + * + * @return action result + * @throws IOException on error + */ + public ItemResult createOrUpdate() throws IOException { + String encodedHref = URIUtil.encodePath(getHref()); + ExchangePropPatchMethod propPatchMethod = internalCreateOrUpdate(encodedHref); + int status = propPatchMethod.getStatusCode(); + if (status == HttpStatus.SC_MULTI_STATUS) { + status = propPatchMethod.getResponseStatusCode(); + //noinspection VariableNotUsedInsideIf + if (status == HttpStatus.SC_CREATED) { + LOGGER.debug("Created contact " + encodedHref); + } else { + LOGGER.debug("Updated contact " + encodedHref); + } + } else if (status == HttpStatus.SC_NOT_FOUND) { + LOGGER.debug("Contact not found at " + encodedHref + ", searching permanenturl by urlcompname"); + // failover, search item by urlcompname + MultiStatusResponse[] responses = searchItems(folderPath, EVENT_REQUEST_PROPERTIES, DavExchangeSession.this.equals("urlcompname", convertItemNameToEML(itemName)), FolderQueryTraversal.Shallow); + if (responses.length == 1) { + encodedHref = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "permanenturl"); + LOGGER.warn("Contact found, permanenturl is " + encodedHref); + propPatchMethod = internalCreateOrUpdate(encodedHref); + status = propPatchMethod.getStatusCode(); + if (status == HttpStatus.SC_MULTI_STATUS) { + status = propPatchMethod.getResponseStatusCode(); + LOGGER.debug("Updated contact " + encodedHref); + } else { + LOGGER.warn("Unable to create or update contact " + status + ' ' + propPatchMethod.getStatusLine()); + } + } + + } else { + LOGGER.warn("Unable to create or update contact " + status + ' ' + propPatchMethod.getStatusLine()); + } ItemResult itemResult = new ItemResult(); // 440 means forbidden on Exchange if (status == 440) { @@ -803,12 +825,8 @@ public class DavExchangeSession extends ExchangeSession { return result; } - /** - * @inheritDoc - */ - @Override - protected ItemResult createOrUpdate(byte[] mimeContent) throws IOException { - PutMethod putmethod = new PutMethod(URIUtil.encodePath(getHref())); + protected PutMethod internalCreateOrUpdate(String encodedHref, byte[] mimeContent) throws IOException { + PutMethod putmethod = new PutMethod(encodedHref); putmethod.setRequestHeader("Translate", "f"); putmethod.setRequestHeader("Overwrite", "f"); if (etag != null) { @@ -819,27 +837,54 @@ public class DavExchangeSession extends ExchangeSession { } putmethod.setRequestHeader("Content-Type", "message/rfc822"); putmethod.setRequestEntity(new ByteArrayRequestEntity(mimeContent, "message/rfc822")); - int status; try { - status = httpClient.executeMethod(putmethod); - if (status == HttpURLConnection.HTTP_OK) { - LOGGER.debug("Updated event " + getHref()); - } else if (status == HttpURLConnection.HTTP_CREATED) { - LOGGER.warn("Overwritten event " + getHref()); - } else { - LOGGER.warn("Unable to create or update message " + status + ' ' + putmethod.getStatusLine()); - } + httpClient.executeMethod(putmethod); } finally { putmethod.releaseConnection(); } + return putmethod; + } + + /** + * @inheritDoc + */ + @Override + protected ItemResult createOrUpdate(byte[] mimeContent) throws IOException { + String encodedHref = URIUtil.encodePath(getHref()); + PutMethod putMethod = internalCreateOrUpdate(encodedHref, mimeContent); + int status = putMethod.getStatusCode(); + + if (status == HttpStatus.SC_OK) { + LOGGER.debug("Updated event " + encodedHref); + } else if (status == HttpStatus.SC_CREATED) { + LOGGER.warn("Overwritten event " + encodedHref); + } else if (status == HttpStatus.SC_NOT_FOUND) { + LOGGER.debug("Event not found at " + encodedHref + ", searching permanenturl by urlcompname"); + // failover, search item by urlcompname + MultiStatusResponse[] responses = searchItems(folderPath, EVENT_REQUEST_PROPERTIES, DavExchangeSession.this.equals("urlcompname", convertItemNameToEML(itemName)), FolderQueryTraversal.Shallow); + if (responses.length == 1) { + encodedHref = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "permanenturl"); + LOGGER.warn("Event found, permanenturl is " + encodedHref); + putMethod = internalCreateOrUpdate(encodedHref, mimeContent); + status = putMethod.getStatusCode(); + if (status == HttpStatus.SC_OK) { + LOGGER.debug("Updated event " + encodedHref); + } else { + LOGGER.warn("Unable to create or update event " + status + ' ' + putMethod.getStatusLine()); + } + } + } else { + LOGGER.warn("Unable to create or update event " + status + ' ' + putMethod.getStatusLine()); + } + ItemResult itemResult = new ItemResult(); // 440 means forbidden on Exchange if (status == 440) { status = HttpStatus.SC_FORBIDDEN; } itemResult.status = status; - if (putmethod.getResponseHeader("GetETag") != null) { - itemResult.etag = putmethod.getResponseHeader("GetETag").getValue(); + if (putMethod.getResponseHeader("GetETag") != null) { + itemResult.etag = putMethod.getResponseHeader("GetETag").getValue(); } // trigger activeSync push event, only if davmail.forceActiveSyncUpdate setting is true @@ -850,7 +895,7 @@ public class DavExchangeSession extends ExchangeSession { propertyList.add(Field.createDavProperty("contentclass", contentClass)); // ... but also set PR_INTERNET_CONTENT to preserve custom properties propertyList.add(Field.createDavProperty("internetContent", new String(Base64.encodeBase64(mimeContent)))); - PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(getHref()), propertyList); + PropPatchMethod propPatchMethod = new PropPatchMethod(encodedHref, propertyList); int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod); if (patchStatus != HttpStatus.SC_MULTI_STATUS) { LOGGER.warn("Unable to patch event to trigger activeSync push"); @@ -1201,6 +1246,7 @@ public class DavExchangeSession extends ExchangeSession { } protected static final DavPropertyNameSet EVENT_REQUEST_PROPERTIES_NAME_SET = new DavPropertyNameSet(); + static { for (String attribute : EVENT_REQUEST_PROPERTIES) { EVENT_REQUEST_PROPERTIES_NAME_SET.add(Field.getPropertyName(attribute)); @@ -1216,14 +1262,14 @@ public class DavExchangeSession extends ExchangeSession { try { responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(itemPath), 0, EVENT_REQUEST_PROPERTIES_NAME_SET); if (responses.length == 0) { - throw new HttpNotFoundException(itemPath+" not found"); + throw new HttpNotFoundException(itemPath + " not found"); } } catch (HttpNotFoundException e) { - LOGGER.debug(itemPath +" not found, searching by urlcompname"); + LOGGER.debug(itemPath + " not found, searching by urlcompname"); // failover: try to get event by displayname responses = searchItems(folderPath, EVENT_REQUEST_PROPERTIES, equals("urlcompname", emlItemName), FolderQueryTraversal.Shallow); if (responses.length == 0) { - throw new HttpNotFoundException(itemPath+" not found"); + throw new HttpNotFoundException(itemPath + " not found"); } } // build item @@ -1234,14 +1280,14 @@ public class DavExchangeSession extends ExchangeSession { List contacts = searchContacts(folderPath, CONTACT_ATTRIBUTES, equals("urlcompname", urlcompname)); if (contacts.isEmpty()) { LOGGER.warn("Item found, but unable to build contact"); - throw new HttpNotFoundException(itemPath+" not found"); + throw new HttpNotFoundException(itemPath + " not found"); } return contacts.get(0); } else if ("urn:content-classes:appointment".equals(contentClass) || "urn:content-classes:calendarmessage".equals(contentClass)) { return new Event(responses[0]); } else { - LOGGER.warn("wrong contentclass on item "+itemPath+": "+contentClass); + LOGGER.warn("wrong contentclass on item " + itemPath + ": " + contentClass); // return item anyway return new Event(responses[0]); } @@ -1362,7 +1408,7 @@ public class DavExchangeSession extends ExchangeSession { // get timezoneid from OWA settings userTimezone.timezoneId = getTimezoneIdFromExchange(); } - // without a timezoneId, use Exchange timezone + // without a timezoneId, use Exchange timezone if (userTimezone.timezoneId != null) { propertyList.add(Field.createDavProperty("timezoneid", userTimezone.timezoneId)); } diff --git a/src/java/davmail/util/StringUtil.java b/src/java/davmail/util/StringUtil.java index b5046ea3..ad7ab0cf 100644 --- a/src/java/davmail/util/StringUtil.java +++ b/src/java/davmail/util/StringUtil.java @@ -142,7 +142,7 @@ public final class StringUtil { public static String urlEncodeSlash(String name) { String result = name; if (name.indexOf("_xF8FF_") >= 0) { - result = F8FF_PATTERN.matcher(result).replaceAll(String.valueOf((char)0xF8FF)); + result = F8FF_PATTERN.matcher(result).replaceAll(String.valueOf((char) 0xF8FF)); } return result; } @@ -244,10 +244,16 @@ public final class StringUtil { return base64Value; } + /** + * Encode item name to get actual value stored in urlcompname MAPI property. + * + * @param value decoded value + * @return urlcompname encoded value + */ public static String encodeUrlcompname(String value) { String result = value; if (result.indexOf("_xF8FF_") >= 0) { - result = F8FF_PATTERN.matcher(result).replaceAll(String.valueOf((char)0xF8FF)); + result = F8FF_PATTERN.matcher(result).replaceAll(String.valueOf((char) 0xF8FF)); } if (result.indexOf('&') >= 0) { result = AMP_PATTERN.matcher(result).replaceAll("%26"); @@ -257,4 +263,20 @@ public final class StringUtil { } return result; } + + /** + * Urlencode plus sign in encoded href. + * '+' is decoded as ' ' by URIUtil.decode, the workaround is to force urlencoding to '%2B' first + * + * @param value encoded href + * @return encoded href + */ + public static String encodePlusSign(String value) { + String result = value; + if (result.indexOf('+') >= 0) { + result = PLUS_PATTERN.matcher(result).replaceAll("%2B"); + } + return result; + } + }