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
This commit is contained in:
mguessan 2010-07-27 09:34:04 +00:00
parent 60066db7be
commit 2a9c62ec14
3 changed files with 119 additions and 66 deletions

View File

@ -131,7 +131,7 @@ public class CaldavConnection extends AbstractConnection {
tokens = new StringTokenizer(line);
String command = tokens.nextToken();
Map<String, String> 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())));
}
}
}

View File

@ -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<ExchangeSession.Contact> 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));
}

View File

@ -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;
}
}