2009-07-21 04:39:18 -04:00
|
|
|
/*
|
|
|
|
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
|
|
|
* Copyright (C) 2009 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.
|
|
|
|
*/
|
2006-12-12 18:57:24 -05:00
|
|
|
package davmail.exchange;
|
|
|
|
|
2009-04-27 19:03:58 -04:00
|
|
|
import davmail.BundleMessage;
|
2009-10-06 17:12:26 -04:00
|
|
|
import davmail.Settings;
|
2009-04-27 19:03:58 -04:00
|
|
|
import davmail.exception.DavMailAuthenticationException;
|
|
|
|
import davmail.exception.DavMailException;
|
2009-11-27 18:27:50 -05:00
|
|
|
import davmail.exception.HttpNotFoundException;
|
2008-11-03 20:47:10 -05:00
|
|
|
import davmail.http.DavGatewayHttpClientFacade;
|
2009-11-23 04:53:04 -05:00
|
|
|
import davmail.http.DavGatewayOTPPrompt;
|
2009-11-27 18:27:50 -05:00
|
|
|
import davmail.util.StringUtil;
|
2009-10-06 17:12:26 -04:00
|
|
|
import org.apache.commons.codec.DecoderException;
|
|
|
|
import org.apache.commons.codec.binary.Base64;
|
|
|
|
import org.apache.commons.codec.binary.Hex;
|
2008-11-30 13:05:36 -05:00
|
|
|
import org.apache.commons.httpclient.*;
|
2010-05-07 06:03:49 -04:00
|
|
|
import org.apache.commons.httpclient.methods.*;
|
2009-10-06 17:12:26 -04:00
|
|
|
import org.apache.commons.httpclient.params.HttpClientParams;
|
2006-12-12 18:57:24 -05:00
|
|
|
import org.apache.commons.httpclient.util.URIUtil;
|
2009-11-27 18:27:50 -05:00
|
|
|
import org.apache.jackrabbit.webdav.DavException;
|
|
|
|
import org.apache.jackrabbit.webdav.MultiStatus;
|
2009-04-01 11:51:12 -04:00
|
|
|
import org.apache.jackrabbit.webdav.MultiStatusResponse;
|
|
|
|
import org.apache.jackrabbit.webdav.client.methods.CopyMethod;
|
|
|
|
import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
|
2009-11-27 18:27:50 -05:00
|
|
|
import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
|
2009-04-01 11:51:12 -04:00
|
|
|
import org.apache.jackrabbit.webdav.client.methods.PropPatchMethod;
|
2009-04-01 17:31:44 -04:00
|
|
|
import org.apache.jackrabbit.webdav.property.*;
|
2009-04-01 11:51:12 -04:00
|
|
|
import org.apache.jackrabbit.webdav.xml.Namespace;
|
2007-04-26 06:29:11 -04:00
|
|
|
import org.apache.log4j.Logger;
|
2008-12-23 09:09:46 -05:00
|
|
|
import org.htmlcleaner.CommentToken;
|
2008-12-01 07:38:49 -05:00
|
|
|
import org.htmlcleaner.HtmlCleaner;
|
|
|
|
import org.htmlcleaner.TagNode;
|
2008-02-05 18:17:31 -05:00
|
|
|
|
2009-02-25 05:23:07 -05:00
|
|
|
import javax.mail.MessagingException;
|
|
|
|
import javax.mail.internet.MimeMessage;
|
|
|
|
import javax.mail.internet.MimeMultipart;
|
2009-03-04 01:53:24 -05:00
|
|
|
import javax.mail.internet.MimePart;
|
2010-04-23 09:42:37 -04:00
|
|
|
import javax.mail.util.SharedByteArrayInputStream;
|
2008-11-26 19:56:28 -05:00
|
|
|
import java.io.*;
|
2007-05-09 18:32:01 -04:00
|
|
|
import java.net.HttpURLConnection;
|
2009-10-06 17:12:26 -04:00
|
|
|
import java.net.NoRouteToHostException;
|
2009-05-05 16:05:57 -04:00
|
|
|
import java.net.UnknownHostException;
|
2006-12-12 18:57:24 -05:00
|
|
|
import java.text.ParseException;
|
|
|
|
import java.text.SimpleDateFormat;
|
2008-11-26 19:56:28 -05:00
|
|
|
import java.util.*;
|
2010-04-23 06:17:20 -04:00
|
|
|
import java.util.zip.GZIPInputStream;
|
2006-12-12 18:57:24 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Exchange session through Outlook Web Access (DAV)
|
2009-09-28 19:28:23 -04:00
|
|
|
*/
|
2010-06-02 04:46:46 -04:00
|
|
|
public abstract class ExchangeSession {
|
2007-05-09 18:32:01 -04:00
|
|
|
protected static final Logger LOGGER = Logger.getLogger("davmail.exchange.ExchangeSession");
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
|
|
|
* Reference GMT timezone to format dates
|
|
|
|
*/
|
2009-02-05 12:15:30 -05:00
|
|
|
public static final SimpleTimeZone GMT_TIMEZONE = new SimpleTimeZone(0, "GMT");
|
2009-01-15 08:19:24 -05:00
|
|
|
|
2009-11-23 04:53:04 -05:00
|
|
|
protected static final Set<String> USER_NAME_FIELDS = new HashSet<String>();
|
2009-11-25 16:58:25 -05:00
|
|
|
|
2009-11-23 04:53:04 -05:00
|
|
|
static {
|
|
|
|
USER_NAME_FIELDS.add("username");
|
|
|
|
USER_NAME_FIELDS.add("txtUserName");
|
|
|
|
USER_NAME_FIELDS.add("userid");
|
|
|
|
USER_NAME_FIELDS.add("SafeWordUser");
|
|
|
|
}
|
2009-11-25 16:58:25 -05:00
|
|
|
|
2009-11-23 04:53:04 -05:00
|
|
|
protected static final Set<String> PASSWORD_FIELDS = new HashSet<String>();
|
2009-11-25 16:58:25 -05:00
|
|
|
|
2009-11-23 04:53:04 -05:00
|
|
|
static {
|
|
|
|
PASSWORD_FIELDS.add("password");
|
|
|
|
PASSWORD_FIELDS.add("txtUserPass");
|
|
|
|
PASSWORD_FIELDS.add("pw");
|
|
|
|
PASSWORD_FIELDS.add("basicPassword");
|
|
|
|
}
|
2009-11-25 16:58:25 -05:00
|
|
|
|
2009-11-23 04:53:04 -05:00
|
|
|
protected static final Set<String> TOKEN_FIELDS = new HashSet<String>();
|
2009-11-25 16:58:25 -05:00
|
|
|
|
2009-11-23 04:53:04 -05:00
|
|
|
static {
|
|
|
|
TOKEN_FIELDS.add("SafeWordPassword");
|
|
|
|
}
|
|
|
|
|
2009-03-20 12:05:45 -04:00
|
|
|
protected static final int FREE_BUSY_INTERVAL = 15;
|
|
|
|
|
2010-06-08 06:44:47 -04:00
|
|
|
protected static final String PUBLIC_ROOT = "/public";
|
|
|
|
protected static final String CALENDAR = "calendar";
|
|
|
|
protected static final String CONTACTS = "contacts";
|
|
|
|
protected static final String INBOX = "INBOX";
|
|
|
|
protected static final String SENT = "Sent";
|
|
|
|
protected static final String DRAFTS = "Drafts";
|
|
|
|
protected static final String TRASH = "Trash";
|
|
|
|
protected static final String JUNK = "Junk";
|
|
|
|
protected static final String UNSENT = "Unsent Messages";
|
|
|
|
|
|
|
|
|
2010-05-07 04:30:12 -04:00
|
|
|
protected static final Namespace DAV = Namespace.getNamespace("DAV:");
|
2009-04-01 17:31:44 -04:00
|
|
|
protected static final Namespace URN_SCHEMAS_HTTPMAIL = Namespace.getNamespace("urn:schemas:httpmail:");
|
2009-11-12 16:32:20 -05:00
|
|
|
protected static final Namespace SCHEMAS_EXCHANGE = Namespace.getNamespace("http://schemas.microsoft.com/exchange/");
|
2009-04-01 17:31:44 -04:00
|
|
|
protected static final Namespace SCHEMAS_MAPI_PROPTAG = Namespace.getNamespace("http://schemas.microsoft.com/mapi/proptag/");
|
2010-05-06 15:26:44 -04:00
|
|
|
protected static final Namespace URN_SCHEMAS_CONTACTS = Namespace.getNamespace("urn:schemas:contacts:");
|
|
|
|
|
2009-04-01 17:31:44 -04:00
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
protected static final DavPropertyNameSet EVENT_REQUEST_PROPERTIES = new DavPropertyNameSet();
|
2008-12-09 08:24:17 -05:00
|
|
|
|
|
|
|
static {
|
2009-11-13 16:41:43 -05:00
|
|
|
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.create("permanenturl", SCHEMAS_EXCHANGE));
|
2009-04-01 11:51:12 -04:00
|
|
|
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.GETETAG);
|
2010-05-07 04:30:12 -04:00
|
|
|
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.create("contentclass", DAV));
|
2008-12-09 08:24:17 -05:00
|
|
|
}
|
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
protected static final DavPropertyNameSet WELL_KNOWN_FOLDERS = new DavPropertyNameSet();
|
2008-12-09 03:54:08 -05:00
|
|
|
|
|
|
|
static {
|
2009-04-01 17:31:44 -04:00
|
|
|
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("inbox", URN_SCHEMAS_HTTPMAIL));
|
|
|
|
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("deleteditems", URN_SCHEMAS_HTTPMAIL));
|
|
|
|
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("sentitems", URN_SCHEMAS_HTTPMAIL));
|
|
|
|
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("sendmsg", URN_SCHEMAS_HTTPMAIL));
|
|
|
|
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("drafts", URN_SCHEMAS_HTTPMAIL));
|
|
|
|
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("calendar", URN_SCHEMAS_HTTPMAIL));
|
2009-08-29 19:49:45 -04:00
|
|
|
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("contacts", URN_SCHEMAS_HTTPMAIL));
|
2010-06-08 06:44:47 -04:00
|
|
|
WELL_KNOWN_FOLDERS.add(DavPropertyName.create("outbox", URN_SCHEMAS_HTTPMAIL));
|
2008-12-09 03:54:08 -05:00
|
|
|
}
|
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
protected static final DavPropertyNameSet DISPLAY_NAME = new DavPropertyNameSet();
|
2009-03-11 08:39:14 -04:00
|
|
|
|
|
|
|
static {
|
2009-04-01 11:51:12 -04:00
|
|
|
DISPLAY_NAME.add(DavPropertyName.DISPLAYNAME);
|
2009-03-11 08:39:14 -04:00
|
|
|
}
|
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
protected static final DavPropertyNameSet FOLDER_PROPERTIES = new DavPropertyNameSet();
|
2009-03-18 13:26:33 -04:00
|
|
|
|
|
|
|
static {
|
2010-05-07 04:30:12 -04:00
|
|
|
FOLDER_PROPERTIES.add(DavPropertyName.create("contentclass", DAV));
|
2009-04-01 11:51:12 -04:00
|
|
|
FOLDER_PROPERTIES.add(DavPropertyName.create("hassubs"));
|
|
|
|
FOLDER_PROPERTIES.add(DavPropertyName.create("nosubs"));
|
2009-04-01 17:31:44 -04:00
|
|
|
FOLDER_PROPERTIES.add(DavPropertyName.create("unreadcount", URN_SCHEMAS_HTTPMAIL));
|
2009-04-01 11:51:12 -04:00
|
|
|
FOLDER_PROPERTIES.add(DavPropertyName.create("contenttag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")));
|
2010-04-01 05:12:44 -04:00
|
|
|
FOLDER_PROPERTIES.add(DavPropertyName.create("resourcetag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")));
|
2009-03-18 13:26:33 -04:00
|
|
|
}
|
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
protected static final DavPropertyNameSet CONTENT_TAG = new DavPropertyNameSet();
|
2009-03-18 08:09:03 -04:00
|
|
|
|
|
|
|
static {
|
2009-04-01 11:51:12 -04:00
|
|
|
CONTENT_TAG.add(DavPropertyName.create("contenttag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")));
|
2009-03-18 08:09:03 -04:00
|
|
|
}
|
|
|
|
|
2009-04-02 18:10:07 -04:00
|
|
|
protected static final DavPropertyNameSet RESOURCE_TAG = new DavPropertyNameSet();
|
|
|
|
|
|
|
|
static {
|
|
|
|
RESOURCE_TAG.add(DavPropertyName.create("resourcetag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")));
|
|
|
|
}
|
2009-03-11 08:39:14 -04:00
|
|
|
|
2009-08-29 16:20:44 -04:00
|
|
|
protected static final DavPropertyName DEFAULT_SCHEDULE_STATE_PROPERTY = DavPropertyName.create("schedule-state", Namespace.getNamespace("CALDAV:"));
|
|
|
|
protected DavPropertyName scheduleStateProperty = DEFAULT_SCHEDULE_STATE_PROPERTY;
|
2009-12-07 05:44:17 -05:00
|
|
|
protected static final DavPropertyName PR_INTERNET_CONTENT = DavPropertyName.create("x66590102", SCHEMAS_MAPI_PROPTAG);
|
2009-08-29 16:20:44 -04:00
|
|
|
|
2006-12-12 18:57:24 -05:00
|
|
|
/**
|
|
|
|
* Various standard mail boxes Urls
|
|
|
|
*/
|
2010-06-02 04:46:46 -04:00
|
|
|
protected String inboxUrl;
|
|
|
|
protected String deleteditemsUrl;
|
|
|
|
protected String sentitemsUrl;
|
|
|
|
protected String sendmsgUrl;
|
|
|
|
protected String draftsUrl;
|
|
|
|
protected String calendarUrl;
|
|
|
|
protected String contactsUrl;
|
2010-06-08 06:44:47 -04:00
|
|
|
protected String outboxUrl;
|
2010-06-02 04:46:46 -04:00
|
|
|
protected String publicFolderUrl;
|
2006-12-12 18:57:24 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Base user mailboxes path (used to select folder)
|
|
|
|
*/
|
2010-06-02 04:46:46 -04:00
|
|
|
protected String mailPath;
|
|
|
|
protected String email;
|
2009-03-13 07:11:40 -04:00
|
|
|
private String alias;
|
2010-06-02 04:46:46 -04:00
|
|
|
protected final HttpClient httpClient;
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2009-09-30 17:54:53 -04:00
|
|
|
private final String userName;
|
2009-10-06 17:12:26 -04:00
|
|
|
|
2009-04-16 18:20:30 -04:00
|
|
|
private boolean disableGalLookup;
|
2009-04-07 08:37:28 -04:00
|
|
|
private static final String YYYY_MM_DD_HH_MM_SS = "yyyy/MM/dd HH:mm:ss";
|
2009-04-11 08:27:10 -04:00
|
|
|
private static final String YYYYMMDD_T_HHMMSS_Z = "yyyyMMdd'T'HHmmss'Z'";
|
|
|
|
private static final String YYYY_MM_DD_T_HHMMSS_Z = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
2009-09-03 19:08:35 -04:00
|
|
|
private static final String YYYY_MM_DD_T_HHMMSS_SSS_Z = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
|
2008-12-23 10:20:50 -05:00
|
|
|
|
2009-09-08 04:05:05 -04:00
|
|
|
/**
|
|
|
|
* Logon form user name field, default is username.
|
|
|
|
*/
|
|
|
|
private String userNameInput = "username";
|
|
|
|
/**
|
|
|
|
* Logon form password field, default is password.
|
|
|
|
*/
|
|
|
|
private String passwordInput = "password";
|
|
|
|
|
2006-12-12 18:57:24 -05:00
|
|
|
/**
|
|
|
|
* Create an exchange session for the given URL.
|
2009-09-30 17:54:53 -04:00
|
|
|
* The session is established for given userName and password
|
2008-12-04 06:50:31 -05:00
|
|
|
*
|
2009-10-06 17:12:26 -04:00
|
|
|
* @param url Exchange url
|
2009-09-30 17:54:53 -04:00
|
|
|
* @param userName user login name
|
|
|
|
* @param password user password
|
2009-04-16 18:20:30 -04:00
|
|
|
* @throws IOException on error
|
2006-12-12 18:57:24 -05:00
|
|
|
*/
|
2010-05-18 07:50:00 -04:00
|
|
|
public ExchangeSession(String url, String userName, String password) throws IOException {
|
2009-09-30 17:54:53 -04:00
|
|
|
this.userName = userName;
|
2009-03-26 19:29:09 -04:00
|
|
|
try {
|
2009-09-30 17:54:53 -04:00
|
|
|
boolean isBasicAuthentication = isBasicAuthentication(url);
|
2009-03-26 19:29:09 -04:00
|
|
|
|
2009-09-30 17:54:53 -04:00
|
|
|
httpClient = DavGatewayHttpClientFacade.getInstance(url, userName, password);
|
2009-03-26 19:29:09 -04:00
|
|
|
|
|
|
|
// get webmail root url
|
|
|
|
// providing credentials
|
|
|
|
// manually follow redirect
|
2009-09-30 17:54:53 -04:00
|
|
|
HttpMethod method = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, url);
|
2009-03-26 19:29:09 -04:00
|
|
|
|
2009-04-25 06:31:33 -04:00
|
|
|
if (isBasicAuthentication) {
|
|
|
|
int status = method.getStatusCode();
|
2009-03-26 19:29:09 -04:00
|
|
|
|
2009-04-25 06:31:33 -04:00
|
|
|
if (status == HttpStatus.SC_UNAUTHORIZED) {
|
|
|
|
method.releaseConnection();
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED");
|
2009-04-25 06:31:33 -04:00
|
|
|
} else if (status != HttpStatus.SC_OK) {
|
|
|
|
method.releaseConnection();
|
|
|
|
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
2009-03-26 19:29:09 -04:00
|
|
|
}
|
2009-04-25 06:31:33 -04:00
|
|
|
} else {
|
2009-09-30 17:54:53 -04:00
|
|
|
method = formLogin(httpClient, method, userName, password);
|
2009-03-26 19:29:09 -04:00
|
|
|
}
|
|
|
|
|
2010-02-01 17:52:36 -05:00
|
|
|
// avoid 401 roundtrips, only if NTLM is disabled
|
|
|
|
if (!DavGatewayHttpClientFacade.hasNTLM(httpClient)) {
|
|
|
|
httpClient.getParams().setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true);
|
|
|
|
}
|
2009-11-01 16:58:04 -05:00
|
|
|
|
2010-06-02 04:46:46 -04:00
|
|
|
buildSessionInfo(method);
|
2009-03-26 19:29:09 -04:00
|
|
|
|
2009-04-27 19:03:58 -04:00
|
|
|
} catch (DavMailAuthenticationException exc) {
|
2010-05-04 04:35:40 -04:00
|
|
|
LOGGER.error(exc.getMessage());
|
2009-03-26 19:29:09 -04:00
|
|
|
throw exc;
|
2009-05-05 16:05:57 -04:00
|
|
|
} catch (UnknownHostException exc) {
|
2009-07-15 18:47:31 -04:00
|
|
|
BundleMessage message = new BundleMessage("EXCEPTION_CONNECT", exc.getClass().getName(), exc.getMessage());
|
2009-05-05 16:05:57 -04:00
|
|
|
ExchangeSession.LOGGER.error(message);
|
|
|
|
throw new DavMailException("EXCEPTION_DAVMAIL_CONFIGURATION", message);
|
2009-03-26 19:29:09 -04:00
|
|
|
} catch (IOException exc) {
|
2009-04-27 19:03:58 -04:00
|
|
|
LOGGER.error(BundleMessage.formatLog("EXCEPTION_EXCHANGE_LOGIN_FAILED", exc));
|
|
|
|
throw new DavMailException("EXCEPTION_EXCHANGE_LOGIN_FAILED", exc);
|
2009-03-26 19:29:09 -04:00
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
LOGGER.debug("Session " + this + " created");
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
|
|
|
|
2009-04-07 08:37:28 -04:00
|
|
|
protected String formatSearchDate(Date date) {
|
|
|
|
SimpleDateFormat dateFormatter = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS, Locale.ENGLISH);
|
2009-03-19 04:58:55 -04:00
|
|
|
dateFormatter.setTimeZone(GMT_TIMEZONE);
|
|
|
|
return dateFormatter.format(date);
|
|
|
|
}
|
|
|
|
|
2009-04-07 08:37:28 -04:00
|
|
|
protected SimpleDateFormat getZuluDateFormat() {
|
2009-04-11 08:27:10 -04:00
|
|
|
SimpleDateFormat dateFormat = new SimpleDateFormat(YYYYMMDD_T_HHMMSS_Z, Locale.ENGLISH);
|
2009-04-07 08:37:28 -04:00
|
|
|
dateFormat.setTimeZone(GMT_TIMEZONE);
|
|
|
|
return dateFormat;
|
|
|
|
}
|
|
|
|
|
2009-04-11 08:27:10 -04:00
|
|
|
protected SimpleDateFormat getExchangeZuluDateFormat() {
|
|
|
|
SimpleDateFormat dateFormat = new SimpleDateFormat(YYYY_MM_DD_T_HHMMSS_Z, Locale.ENGLISH);
|
|
|
|
dateFormat.setTimeZone(GMT_TIMEZONE);
|
|
|
|
return dateFormat;
|
2009-04-07 08:37:28 -04:00
|
|
|
}
|
|
|
|
|
2009-09-03 19:08:35 -04:00
|
|
|
protected SimpleDateFormat getExchangeZuluDateFormatMillisecond() {
|
|
|
|
SimpleDateFormat dateFormat = new SimpleDateFormat(YYYY_MM_DD_T_HHMMSS_SSS_Z, Locale.ENGLISH);
|
|
|
|
dateFormat.setTimeZone(GMT_TIMEZONE);
|
|
|
|
return dateFormat;
|
|
|
|
}
|
|
|
|
|
2009-04-07 08:37:28 -04:00
|
|
|
protected Date parseDate(String dateString) throws ParseException {
|
|
|
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
|
|
|
|
dateFormat.setTimeZone(GMT_TIMEZONE);
|
|
|
|
return dateFormat.parse(dateString);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
|
|
|
* Test if the session expired.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-28 02:39:27 -04:00
|
|
|
* @return true this session expired
|
|
|
|
* @throws NoRouteToHostException on error
|
2009-08-04 16:48:35 -04:00
|
|
|
* @throws UnknownHostException on error
|
2009-07-28 02:39:27 -04:00
|
|
|
*/
|
2009-05-15 08:38:24 -04:00
|
|
|
public boolean isExpired() throws NoRouteToHostException, UnknownHostException {
|
2008-12-04 06:50:31 -05:00
|
|
|
boolean isExpired = false;
|
|
|
|
try {
|
2009-03-18 13:26:33 -04:00
|
|
|
DavGatewayHttpClientFacade.executePropFindMethod(
|
|
|
|
httpClient, URIUtil.encodePath(inboxUrl), 0, DISPLAY_NAME);
|
2009-05-15 08:38:24 -04:00
|
|
|
} catch (UnknownHostException exc) {
|
|
|
|
throw exc;
|
|
|
|
} catch (NoRouteToHostException exc) {
|
|
|
|
throw exc;
|
2008-12-04 06:50:31 -05:00
|
|
|
} catch (IOException e) {
|
|
|
|
isExpired = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return isExpired;
|
|
|
|
}
|
|
|
|
|
2008-01-09 11:33:57 -05:00
|
|
|
/**
|
|
|
|
* Test authentication mode : form based or basic.
|
|
|
|
*
|
|
|
|
* @param url exchange base URL
|
|
|
|
* @return true if basic authentication detected
|
2009-04-16 18:20:30 -04:00
|
|
|
* @throws IOException unable to connect to exchange
|
2008-01-09 11:33:57 -05:00
|
|
|
*/
|
|
|
|
protected boolean isBasicAuthentication(String url) throws IOException {
|
2008-11-03 20:47:10 -05:00
|
|
|
return DavGatewayHttpClientFacade.getHttpStatus(url) == HttpStatus.SC_UNAUTHORIZED;
|
|
|
|
}
|
|
|
|
|
2009-09-02 12:05:03 -04:00
|
|
|
protected String getAbsoluteUri(HttpMethod method, String path) throws URIException {
|
|
|
|
URI uri = method.getURI();
|
|
|
|
if (path != null) {
|
2009-10-27 05:26:12 -04:00
|
|
|
// reset query string
|
|
|
|
uri.setQuery(null);
|
2009-09-02 12:05:03 -04:00
|
|
|
if (path.startsWith("/")) {
|
2009-09-02 12:37:33 -04:00
|
|
|
// path is absolute, replace method path
|
2009-09-02 12:05:03 -04:00
|
|
|
uri.setPath(path);
|
2009-10-29 13:19:00 -04:00
|
|
|
} else if (path.startsWith("http://") || path.startsWith("https://")) {
|
2009-10-25 18:24:53 -04:00
|
|
|
return path;
|
2009-02-27 06:45:38 -05:00
|
|
|
} else {
|
2009-09-02 12:37:33 -04:00
|
|
|
// relative path, build new path
|
2009-09-02 12:05:03 -04:00
|
|
|
String currentPath = method.getPath();
|
|
|
|
int end = currentPath.lastIndexOf('/');
|
|
|
|
if (end >= 0) {
|
|
|
|
uri.setPath(currentPath.substring(0, end + 1) + path);
|
|
|
|
} else {
|
|
|
|
throw new URIException(uri.getURI());
|
|
|
|
}
|
2008-12-19 07:27:33 -05:00
|
|
|
}
|
|
|
|
}
|
2009-09-02 12:05:03 -04:00
|
|
|
return uri.getURI();
|
2008-12-19 07:27:33 -05:00
|
|
|
}
|
|
|
|
|
2009-09-17 10:42:45 -04:00
|
|
|
protected String getScriptBasedFormURL(HttpMethod initmethod, String pathQuery) throws URIException {
|
|
|
|
URI initmethodURI = initmethod.getURI();
|
|
|
|
int queryIndex = pathQuery.indexOf('?');
|
|
|
|
if (queryIndex >= 0) {
|
|
|
|
if (queryIndex > 0) {
|
2009-09-28 19:28:23 -04:00
|
|
|
// update path
|
|
|
|
String newPath = pathQuery.substring(0, queryIndex);
|
|
|
|
if (newPath.startsWith("/")) {
|
|
|
|
// absolute path
|
|
|
|
initmethodURI.setPath(newPath);
|
|
|
|
} else {
|
|
|
|
String currentPath = initmethodURI.getPath();
|
|
|
|
int folderIndex = currentPath.lastIndexOf('/');
|
|
|
|
if (folderIndex >= 0) {
|
|
|
|
// replace relative path
|
|
|
|
initmethodURI.setPath(currentPath.substring(0, folderIndex + 1) + newPath);
|
|
|
|
} else {
|
|
|
|
// should not happen
|
|
|
|
initmethodURI.setPath('/' + newPath);
|
|
|
|
}
|
|
|
|
}
|
2009-09-17 10:42:45 -04:00
|
|
|
}
|
|
|
|
initmethodURI.setQuery(pathQuery.substring(queryIndex + 1));
|
|
|
|
}
|
|
|
|
return initmethodURI.getURI();
|
|
|
|
}
|
|
|
|
|
2008-11-03 20:47:10 -05:00
|
|
|
/**
|
|
|
|
* Try to find logon method path from logon form body.
|
|
|
|
*
|
2008-12-01 12:56:18 -05:00
|
|
|
* @param httpClient httpClient instance
|
2008-11-03 20:47:10 -05:00
|
|
|
* @param initmethod form body http method
|
2008-12-01 07:38:49 -05:00
|
|
|
* @return logon method
|
2009-04-16 18:20:30 -04:00
|
|
|
* @throws IOException on error
|
2008-11-03 20:47:10 -05:00
|
|
|
*/
|
2008-12-01 07:38:49 -05:00
|
|
|
protected PostMethod buildLogonMethod(HttpClient httpClient, HttpMethod initmethod) throws IOException {
|
|
|
|
|
|
|
|
PostMethod logonMethod = null;
|
|
|
|
|
|
|
|
// create an instance of HtmlCleaner
|
|
|
|
HtmlCleaner cleaner = new HtmlCleaner();
|
2008-11-03 20:47:10 -05:00
|
|
|
|
|
|
|
try {
|
2008-12-01 07:38:49 -05:00
|
|
|
TagNode node = cleaner.clean(initmethod.getResponseBodyAsStream());
|
2008-12-17 10:27:56 -05:00
|
|
|
List forms = node.getElementListByName("form", true);
|
2010-02-01 18:20:56 -05:00
|
|
|
TagNode logonForm = null;
|
|
|
|
// select form
|
2008-12-01 07:38:49 -05:00
|
|
|
if (forms.size() == 1) {
|
2010-02-01 18:20:56 -05:00
|
|
|
logonForm = (TagNode) forms.get(0);
|
|
|
|
} else if (forms.size() > 1) {
|
|
|
|
for (Object form : forms) {
|
|
|
|
if ("logonForm".equals(((TagNode) form).getAttributeByName("name"))) {
|
|
|
|
logonForm = ((TagNode) form);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (logonForm != null) {
|
|
|
|
String logonMethodPath = logonForm.getAttributeByName("action");
|
2008-12-01 07:38:49 -05:00
|
|
|
|
2009-09-02 12:05:03 -04:00
|
|
|
logonMethod = new PostMethod(getAbsoluteUri(initmethod, logonMethodPath));
|
2008-12-01 07:38:49 -05:00
|
|
|
|
2010-02-01 18:20:56 -05:00
|
|
|
List inputList = logonForm.getElementListByName("input", true);
|
2008-12-17 10:27:56 -05:00
|
|
|
for (Object input : inputList) {
|
|
|
|
String type = ((TagNode) input).getAttributeByName("type");
|
|
|
|
String name = ((TagNode) input).getAttributeByName("name");
|
|
|
|
String value = ((TagNode) input).getAttributeByName("value");
|
2009-11-02 18:15:12 -05:00
|
|
|
if ("hidden".equalsIgnoreCase(type) && name != null && value != null) {
|
2008-12-01 07:38:49 -05:00
|
|
|
logonMethod.addParameter(name, value);
|
|
|
|
}
|
2009-09-08 04:05:05 -04:00
|
|
|
// custom login form
|
2009-11-23 04:53:04 -05:00
|
|
|
if (USER_NAME_FIELDS.contains(name)) {
|
2009-11-01 17:27:23 -05:00
|
|
|
userNameInput = name;
|
2009-11-23 04:53:04 -05:00
|
|
|
} else if (PASSWORD_FIELDS.contains(name)) {
|
2009-11-01 17:27:23 -05:00
|
|
|
passwordInput = name;
|
2009-10-25 18:24:53 -04:00
|
|
|
} else if ("addr".equals(name)) {
|
|
|
|
// this is not a logon form but a redirect form
|
|
|
|
HttpMethod newInitMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, logonMethod);
|
|
|
|
logonMethod = buildLogonMethod(httpClient, newInitMethod);
|
2009-11-23 04:53:04 -05:00
|
|
|
} else if (TOKEN_FIELDS.contains(name)) {
|
|
|
|
// one time password, ask user
|
|
|
|
logonMethod.addParameter(name, DavGatewayOTPPrompt.getOneTimePassword());
|
2009-09-08 04:05:05 -04:00
|
|
|
}
|
2008-11-03 20:47:10 -05:00
|
|
|
}
|
2008-12-01 07:38:49 -05:00
|
|
|
} else {
|
2008-12-17 10:27:56 -05:00
|
|
|
List frameList = node.getElementListByName("frame", true);
|
2008-12-01 07:38:49 -05:00
|
|
|
if (frameList.size() == 1) {
|
2008-12-17 10:27:56 -05:00
|
|
|
String src = ((TagNode) frameList.get(0)).getAttributeByName("src");
|
2008-12-01 07:38:49 -05:00
|
|
|
if (src != null) {
|
|
|
|
LOGGER.debug("Frames detected in form page, try frame content");
|
|
|
|
initmethod.releaseConnection();
|
|
|
|
HttpMethod newInitMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, src);
|
|
|
|
logonMethod = buildLogonMethod(httpClient, newInitMethod);
|
2008-12-19 07:27:33 -05:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// another failover for script based logon forms (Exchange 2007)
|
|
|
|
List scriptList = node.getElementListByName("script", true);
|
|
|
|
for (Object script : scriptList) {
|
|
|
|
List contents = ((TagNode) script).getChildren();
|
|
|
|
for (Object content : contents) {
|
|
|
|
if (content instanceof CommentToken) {
|
|
|
|
String scriptValue = ((CommentToken) content).getCommentedContent();
|
2009-11-03 05:01:33 -05:00
|
|
|
String sUrl = StringUtil.getToken(scriptValue, "var a_sUrl = \"", "\"");
|
|
|
|
String sLgn = StringUtil.getToken(scriptValue, "var a_sLgn = \"", "\"");
|
|
|
|
if (sLgn == null) {
|
|
|
|
sLgn = StringUtil.getToken(scriptValue, "var a_sLgnQS = \"", "\"");
|
|
|
|
}
|
|
|
|
if (sUrl != null && sLgn != null) {
|
|
|
|
String src = getScriptBasedFormURL(initmethod, sLgn + sUrl);
|
|
|
|
LOGGER.debug("Detected script based logon, redirect to form at " + src);
|
|
|
|
HttpMethod newInitMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, src);
|
|
|
|
logonMethod = buildLogonMethod(httpClient, newInitMethod);
|
2008-12-19 07:27:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-12-01 07:38:49 -05:00
|
|
|
}
|
2008-11-03 20:47:10 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
2008-12-01 07:38:49 -05:00
|
|
|
LOGGER.error("Error parsing login form at " + initmethod.getURI());
|
2008-11-03 20:47:10 -05:00
|
|
|
} finally {
|
|
|
|
initmethod.releaseConnection();
|
|
|
|
}
|
|
|
|
|
2008-12-01 07:38:49 -05:00
|
|
|
return logonMethod;
|
|
|
|
}
|
2008-11-03 20:47:10 -05:00
|
|
|
|
2008-12-01 07:38:49 -05:00
|
|
|
protected HttpMethod formLogin(HttpClient httpClient, HttpMethod initmethod, String userName, String password) throws IOException {
|
|
|
|
LOGGER.debug("Form based authentication detected");
|
2008-12-02 05:20:46 -05:00
|
|
|
|
2008-12-01 07:38:49 -05:00
|
|
|
HttpMethod logonMethod = buildLogonMethod(httpClient, initmethod);
|
2009-11-01 16:58:04 -05:00
|
|
|
if (logonMethod == null) {
|
|
|
|
throw new DavMailException("EXCEPTION_AUTHENTICATION_FORM_NOT_FOUND", initmethod.getURI());
|
|
|
|
}
|
|
|
|
|
2009-11-01 17:27:23 -05:00
|
|
|
// make sure username and password fields are empty
|
|
|
|
((PostMethod) logonMethod).removeParameter(userNameInput);
|
|
|
|
((PostMethod) logonMethod).removeParameter(passwordInput);
|
2009-12-07 16:41:45 -05:00
|
|
|
((PostMethod) logonMethod).removeParameter("trusted");
|
|
|
|
((PostMethod) logonMethod).removeParameter("flags");
|
2009-09-08 04:05:05 -04:00
|
|
|
((PostMethod) logonMethod).addParameter(userNameInput, userName);
|
|
|
|
((PostMethod) logonMethod).addParameter(passwordInput, password);
|
2009-10-28 19:13:56 -04:00
|
|
|
((PostMethod) logonMethod).addParameter("trusted", "4");
|
2009-12-07 16:41:45 -05:00
|
|
|
((PostMethod) logonMethod).addParameter("flags", "4");
|
2008-12-01 07:38:49 -05:00
|
|
|
logonMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, logonMethod);
|
2009-04-25 06:31:33 -04:00
|
|
|
|
|
|
|
// test form based authentication
|
|
|
|
checkFormLoginQueryString(logonMethod);
|
2009-04-28 17:01:40 -04:00
|
|
|
|
2009-04-25 06:31:33 -04:00
|
|
|
// workaround for post logon script redirect
|
2009-11-01 16:58:04 -05:00
|
|
|
if (!isAuthenticated()) {
|
|
|
|
// try to get new method from script based redirection
|
2009-04-25 06:31:33 -04:00
|
|
|
logonMethod = buildLogonMethod(httpClient, logonMethod);
|
2009-11-01 16:58:04 -05:00
|
|
|
|
|
|
|
if (logonMethod != null) {
|
|
|
|
// if logonMethod is not null, try to follow redirection
|
|
|
|
logonMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, logonMethod);
|
|
|
|
checkFormLoginQueryString(logonMethod);
|
2009-11-23 04:53:04 -05:00
|
|
|
// also check cookies
|
|
|
|
if (!isAuthenticated()) {
|
|
|
|
throwAuthenticationFailed();
|
|
|
|
}
|
2009-11-01 16:58:04 -05:00
|
|
|
} else {
|
|
|
|
// authentication failed
|
|
|
|
throwAuthenticationFailed();
|
|
|
|
}
|
2009-04-25 06:31:33 -04:00
|
|
|
}
|
|
|
|
|
2008-12-01 07:38:49 -05:00
|
|
|
return logonMethod;
|
2008-11-03 20:47:10 -05:00
|
|
|
}
|
|
|
|
|
2009-11-01 16:58:04 -05:00
|
|
|
/**
|
|
|
|
* Look for session cookies.
|
|
|
|
*
|
|
|
|
* @return true if session cookies are available
|
|
|
|
*/
|
2009-11-02 04:36:51 -05:00
|
|
|
protected boolean isAuthenticated() {
|
2009-11-01 16:58:04 -05:00
|
|
|
boolean authenticated = false;
|
|
|
|
for (Cookie cookie : httpClient.getState().getCookies()) {
|
2009-11-18 06:13:06 -05:00
|
|
|
// Exchange 2003 cookies
|
|
|
|
if (cookie.getName().startsWith("cadata") || "sessionid".equals(cookie.getName())
|
|
|
|
// Exchange 2007 cookie
|
|
|
|
|| "UserContext".equals(cookie.getName())) {
|
2009-11-01 16:58:04 -05:00
|
|
|
authenticated = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return authenticated;
|
|
|
|
}
|
|
|
|
|
2009-04-27 19:03:58 -04:00
|
|
|
protected void checkFormLoginQueryString(HttpMethod logonMethod) throws DavMailAuthenticationException {
|
2009-04-28 17:01:40 -04:00
|
|
|
String queryString = logonMethod.getQueryString();
|
2009-04-25 06:31:33 -04:00
|
|
|
if (queryString != null && queryString.contains("reason=2")) {
|
|
|
|
logonMethod.releaseConnection();
|
2009-11-01 16:58:04 -05:00
|
|
|
throwAuthenticationFailed();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void throwAuthenticationFailed() throws DavMailAuthenticationException {
|
|
|
|
if (this.userName != null && this.userName.contains("\\")) {
|
|
|
|
throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED");
|
|
|
|
} else {
|
|
|
|
throw new DavMailAuthenticationException("EXCEPTION_AUTHENTICATION_FAILED_RETRY");
|
2009-04-25 06:31:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-02 04:46:46 -04:00
|
|
|
protected abstract void buildSessionInfo(HttpMethod method) throws DavMailException;
|
2008-12-22 19:14:41 -05:00
|
|
|
|
2009-04-01 17:31:44 -04:00
|
|
|
protected String getPropertyIfExists(DavPropertySet properties, String name, Namespace namespace) {
|
|
|
|
DavProperty property = properties.get(name, namespace);
|
|
|
|
if (property == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return (String) property.getValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-06 15:26:44 -04:00
|
|
|
protected String getPropertyIfExists(DavPropertySet properties, DavPropertyName davPropertyName, String defaultValue) {
|
|
|
|
String value = getPropertyIfExists(properties, davPropertyName);
|
|
|
|
if (value == null) {
|
|
|
|
return defaultValue;
|
|
|
|
} else {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-15 18:05:11 -04:00
|
|
|
protected String getPropertyIfExists(DavPropertySet properties, DavPropertyName davPropertyName) {
|
|
|
|
DavProperty property = properties.get(davPropertyName);
|
|
|
|
if (property == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return (String) property.getValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-01 17:31:44 -04:00
|
|
|
protected int getIntPropertyIfExists(DavPropertySet properties, String name, Namespace namespace) {
|
|
|
|
DavProperty property = properties.get(name, namespace);
|
|
|
|
if (property == null) {
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
return Integer.parseInt((String) property.getValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected long getLongPropertyIfExists(DavPropertySet properties, String name, Namespace namespace) {
|
|
|
|
DavProperty property = properties.get(name, namespace);
|
|
|
|
if (property == null) {
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
return Long.parseLong((String) property.getValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String getURIPropertyIfExists(DavPropertySet properties, String name, Namespace namespace) throws URIException {
|
|
|
|
DavProperty property = properties.get(name, namespace);
|
|
|
|
if (property == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return URIUtil.decode((String) property.getValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-12-12 18:57:24 -05:00
|
|
|
/**
|
|
|
|
* Create message in specified folder.
|
|
|
|
* Will overwrite an existing message with same subject in the same folder
|
2007-03-14 07:55:37 -04:00
|
|
|
*
|
2009-07-28 02:53:46 -04:00
|
|
|
* @param folderPath Exchange folder path
|
2009-02-09 05:12:09 -05:00
|
|
|
* @param messageName message name
|
|
|
|
* @param properties message properties (flags)
|
|
|
|
* @param messageBody mail body
|
2009-04-16 18:20:30 -04:00
|
|
|
* @throws IOException when unable to create message
|
2006-12-12 18:57:24 -05:00
|
|
|
*/
|
2009-07-28 02:53:46 -04:00
|
|
|
public void createMessage(String folderPath, String messageName, HashMap<String, String> properties, String messageBody) throws IOException {
|
|
|
|
String messageUrl = URIUtil.encodePathQuery(getFolderPath(folderPath) + '/' + messageName + ".EML");
|
2009-02-03 18:54:48 -05:00
|
|
|
PropPatchMethod patchMethod;
|
2009-02-03 11:13:00 -05:00
|
|
|
// create the message first as draft
|
2009-02-03 18:54:48 -05:00
|
|
|
if (properties.containsKey("draft")) {
|
2009-04-01 11:51:12 -04:00
|
|
|
patchMethod = new PropPatchMethod(messageUrl, buildProperties(properties));
|
2009-02-03 18:54:48 -05:00
|
|
|
try {
|
|
|
|
// update message with blind carbon copy and other flags
|
2009-03-18 13:26:33 -04:00
|
|
|
int statusCode = httpClient.executeMethod(patchMethod);
|
2009-02-03 18:54:48 -05:00
|
|
|
if (statusCode != HttpStatus.SC_MULTI_STATUS) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_UNABLE_TO_CREATE_MESSAGE", messageUrl, statusCode, ' ', patchMethod.getStatusLine());
|
2009-02-03 11:13:00 -05:00
|
|
|
}
|
|
|
|
|
2009-02-03 18:54:48 -05:00
|
|
|
} finally {
|
|
|
|
patchMethod.releaseConnection();
|
|
|
|
}
|
2009-02-03 11:13:00 -05:00
|
|
|
}
|
|
|
|
|
2006-12-12 18:57:24 -05:00
|
|
|
PutMethod putmethod = new PutMethod(messageUrl);
|
2008-11-26 19:56:28 -05:00
|
|
|
putmethod.setRequestHeader("Translate", "f");
|
|
|
|
try {
|
2008-11-29 09:24:12 -05:00
|
|
|
// use same encoding as client socket reader
|
2009-04-01 18:06:53 -04:00
|
|
|
putmethod.setRequestEntity(new ByteArrayRequestEntity(messageBody.getBytes()));
|
2009-03-18 13:26:33 -04:00
|
|
|
int code = httpClient.executeMethod(putmethod);
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2009-02-03 18:54:48 -05:00
|
|
|
if (code != HttpStatus.SC_OK && code != HttpStatus.SC_CREATED) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_UNABLE_TO_CREATE_MESSAGE", messageUrl, code, ' ', putmethod.getStatusLine());
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
putmethod.releaseConnection();
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
2008-12-09 05:10:38 -05:00
|
|
|
|
2009-02-03 18:54:48 -05:00
|
|
|
// add bcc and other properties
|
2009-04-16 17:16:40 -04:00
|
|
|
if (!properties.isEmpty()) {
|
2009-04-01 11:51:12 -04:00
|
|
|
patchMethod = new PropPatchMethod(messageUrl, buildProperties(properties));
|
2009-02-04 15:38:07 -05:00
|
|
|
try {
|
|
|
|
// update message with blind carbon copy and other flags
|
2009-03-18 13:26:33 -04:00
|
|
|
int statusCode = httpClient.executeMethod(patchMethod);
|
2009-02-04 15:38:07 -05:00
|
|
|
if (statusCode != HttpStatus.SC_MULTI_STATUS) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_UNABLE_TO_PATCH_MESSAGE", messageUrl, statusCode, ' ', patchMethod.getStatusLine());
|
2009-02-04 15:38:07 -05:00
|
|
|
}
|
2009-02-03 18:54:48 -05:00
|
|
|
|
2009-02-04 15:38:07 -05:00
|
|
|
} finally {
|
|
|
|
patchMethod.releaseConnection();
|
|
|
|
}
|
2009-02-03 18:54:48 -05:00
|
|
|
}
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
protected Message buildMessage(MultiStatusResponse responseEntity) throws URIException {
|
2006-12-12 18:57:24 -05:00
|
|
|
Message message = new Message();
|
|
|
|
message.messageUrl = URIUtil.decode(responseEntity.getHref());
|
2009-04-01 17:31:44 -04:00
|
|
|
DavPropertySet properties = responseEntity.getProperties(HttpStatus.SC_OK);
|
|
|
|
|
2009-11-13 13:07:37 -05:00
|
|
|
message.permanentUrl = getPropertyIfExists(properties, "permanenturl", SCHEMAS_EXCHANGE);
|
2009-04-01 17:31:44 -04:00
|
|
|
message.size = getIntPropertyIfExists(properties, "x0e080003", SCHEMAS_MAPI_PROPTAG);
|
2010-05-07 04:30:12 -04:00
|
|
|
message.uid = getPropertyIfExists(properties, "uid", DAV);
|
2009-04-01 17:31:44 -04:00
|
|
|
message.imapUid = getLongPropertyIfExists(properties, "x0e230003", SCHEMAS_MAPI_PROPTAG);
|
|
|
|
message.read = "1".equals(getPropertyIfExists(properties, "read", URN_SCHEMAS_HTTPMAIL));
|
|
|
|
message.junk = "1".equals(getPropertyIfExists(properties, "x10830003", SCHEMAS_MAPI_PROPTAG));
|
|
|
|
message.flagged = "2".equals(getPropertyIfExists(properties, "x10900003", SCHEMAS_MAPI_PROPTAG));
|
|
|
|
message.draft = "9".equals(getPropertyIfExists(properties, "x0E070003", SCHEMAS_MAPI_PROPTAG));
|
|
|
|
String x10810003 = getPropertyIfExists(properties, "x10810003", SCHEMAS_MAPI_PROPTAG);
|
|
|
|
message.answered = "102".equals(x10810003) || "103".equals(x10810003);
|
|
|
|
message.forwarded = "104".equals(x10810003);
|
|
|
|
message.date = getPropertyIfExists(properties, "date", Namespace.getNamespace("urn:schemas:mailheader:"));
|
2009-10-09 18:46:10 -04:00
|
|
|
message.deleted = "1".equals(getPropertyIfExists(properties, "deleted", Namespace.getNamespace("")));
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2009-12-16 17:12:54 -05:00
|
|
|
if (LOGGER.isDebugEnabled()) {
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
|
|
|
buffer.append("Message");
|
|
|
|
if (message.imapUid != 0) {
|
|
|
|
buffer.append(" IMAP uid: ").append(message.imapUid);
|
|
|
|
}
|
|
|
|
if (message.uid != null) {
|
|
|
|
buffer.append(" uid: ").append(message.uid);
|
|
|
|
}
|
|
|
|
buffer.append(" href: ").append(responseEntity.getHref()).append(" permanenturl:").append(message.permanentUrl);
|
|
|
|
LOGGER.debug(buffer.toString());
|
|
|
|
}
|
2006-12-12 18:57:24 -05:00
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
protected List<DavProperty> buildProperties(Map<String, String> properties) {
|
|
|
|
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
|
2009-02-03 18:54:48 -05:00
|
|
|
for (Map.Entry<String, String> entry : properties.entrySet()) {
|
|
|
|
if ("read".equals(entry.getKey())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("read", URN_SCHEMAS_HTTPMAIL), entry.getValue()));
|
2009-02-03 18:54:48 -05:00
|
|
|
} else if ("junk".equals(entry.getKey())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("x10830003", SCHEMAS_MAPI_PROPTAG), entry.getValue()));
|
2009-02-03 18:54:48 -05:00
|
|
|
} else if ("flagged".equals(entry.getKey())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("x10900003", SCHEMAS_MAPI_PROPTAG), entry.getValue()));
|
2009-02-03 18:54:48 -05:00
|
|
|
} else if ("answered".equals(entry.getKey())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("x10810003", SCHEMAS_MAPI_PROPTAG), entry.getValue()));
|
2009-02-04 19:18:29 -05:00
|
|
|
if ("102".equals(entry.getValue())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("x10800003", SCHEMAS_MAPI_PROPTAG), "261"));
|
2009-02-04 19:18:29 -05:00
|
|
|
}
|
|
|
|
} else if ("forwarded".equals(entry.getKey())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("x10810003", SCHEMAS_MAPI_PROPTAG), entry.getValue()));
|
2009-02-04 19:18:29 -05:00
|
|
|
if ("104".equals(entry.getValue())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("x10800003", SCHEMAS_MAPI_PROPTAG), "262"));
|
2009-02-04 19:18:29 -05:00
|
|
|
}
|
2009-02-03 18:54:48 -05:00
|
|
|
} else if ("bcc".equals(entry.getKey())) {
|
2009-04-01 11:51:12 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("bcc", Namespace.getNamespace("urn:schemas:mailheader:")), entry.getValue()));
|
2009-02-03 18:54:48 -05:00
|
|
|
} else if ("draft".equals(entry.getKey())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("x0E070003", SCHEMAS_MAPI_PROPTAG), entry.getValue()));
|
2009-02-04 18:06:30 -05:00
|
|
|
} else if ("deleted".equals(entry.getKey())) {
|
2009-10-09 18:46:10 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("_x0030_x8570", Namespace.getNamespace("http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/")), entry.getValue()));
|
2009-02-05 12:15:30 -05:00
|
|
|
} else if ("datereceived".equals(entry.getKey())) {
|
2009-04-01 17:31:44 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("datereceived", URN_SCHEMAS_HTTPMAIL), entry.getValue()));
|
2009-02-03 18:54:48 -05:00
|
|
|
}
|
|
|
|
}
|
2009-04-01 11:51:12 -04:00
|
|
|
return list;
|
2009-02-03 18:54:48 -05:00
|
|
|
}
|
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
|
|
|
* Update given properties on message.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
|
|
|
* @param message Exchange message
|
2009-07-28 02:39:27 -04:00
|
|
|
* @param properties Webdav properties map
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-01-26 18:51:08 -05:00
|
|
|
public void updateMessage(Message message, Map<String, String> properties) throws IOException {
|
2009-11-13 13:07:37 -05:00
|
|
|
PropPatchMethod patchMethod = new PropPatchMethod(message.permanentUrl, buildProperties(properties)) {
|
2009-10-16 04:58:04 -04:00
|
|
|
@Override
|
2009-10-09 18:46:10 -04:00
|
|
|
protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
|
|
|
|
// ignore response body, sometimes invalid with exchange mapi properties
|
|
|
|
}
|
|
|
|
};
|
2009-02-02 02:19:57 -05:00
|
|
|
try {
|
2009-03-18 13:26:33 -04:00
|
|
|
int statusCode = httpClient.executeMethod(patchMethod);
|
2009-02-02 02:19:57 -05:00
|
|
|
if (statusCode != HttpStatus.SC_MULTI_STATUS) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_UNABLE_TO_UPDATE_MESSAGE");
|
2009-02-02 02:19:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
patchMethod.releaseConnection();
|
|
|
|
}
|
2009-01-26 18:51:08 -05:00
|
|
|
}
|
2009-02-02 02:19:57 -05:00
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
|
|
|
* Return folder message list with id and size only (for POP3 listener).
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-28 02:39:27 -04:00
|
|
|
* @param folderName Exchange folder name
|
|
|
|
* @return folder message list
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-05-10 10:52:09 -04:00
|
|
|
public MessageList getAllMessageUidAndSize(String folderName) throws IOException {
|
2009-07-08 18:51:34 -04:00
|
|
|
return searchMessages(folderName, "\"DAV:uid\", \"http://schemas.microsoft.com/mapi/proptag/x0e080003\"", "");
|
2009-02-17 17:59:32 -05:00
|
|
|
}
|
2009-02-19 17:00:36 -05:00
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
|
|
|
* Search folder for messages matching conditions, with attributes needed by IMAP listener.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-28 02:39:27 -04:00
|
|
|
* @param folderName Exchange folder name
|
|
|
|
* @param conditions conditions string in Exchange SQL syntax
|
|
|
|
* @return message list
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-02-17 17:59:32 -05:00
|
|
|
public MessageList searchMessages(String folderName, String conditions) throws IOException {
|
2009-05-10 10:52:09 -04:00
|
|
|
return searchMessages(folderName, "\"DAV:uid\", \"http://schemas.microsoft.com/mapi/proptag/x0e080003\"" +
|
2009-03-20 12:05:45 -04:00
|
|
|
" ,\"http://schemas.microsoft.com/mapi/proptag/x0e230003\"" +
|
2009-02-03 09:38:05 -05:00
|
|
|
" ,\"http://schemas.microsoft.com/mapi/proptag/x10830003\", \"http://schemas.microsoft.com/mapi/proptag/x10900003\"" +
|
2009-02-03 18:54:48 -05:00
|
|
|
" ,\"http://schemas.microsoft.com/mapi/proptag/x0E070003\", \"http://schemas.microsoft.com/mapi/proptag/x10810003\"" +
|
2010-04-23 11:00:10 -04:00
|
|
|
" , \"urn:schemas:httpmail:read\" " +
|
2009-10-09 18:46:10 -04:00
|
|
|
" ,\"http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/0x8570\" as deleted, \"urn:schemas:mailheader:date\"", conditions);
|
2009-10-25 18:24:53 -04:00
|
|
|
}
|
2009-05-10 10:52:09 -04:00
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
|
|
|
* Search folder for messages matching conditions, with given attributes.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-28 02:39:27 -04:00
|
|
|
* @param folderName Exchange folder name
|
|
|
|
* @param attributes requested Webdav attributes
|
|
|
|
* @param conditions conditions string in Exchange SQL syntax
|
|
|
|
* @return message list
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-05-10 10:52:09 -04:00
|
|
|
public MessageList searchMessages(String folderName, String attributes, String conditions) throws IOException {
|
|
|
|
String folderUrl = getFolderPath(folderName);
|
|
|
|
MessageList messages = new MessageList();
|
2009-11-12 16:32:20 -05:00
|
|
|
StringBuilder searchRequest = new StringBuilder();
|
2009-11-13 13:07:37 -05:00
|
|
|
searchRequest.append("Select \"http://schemas.microsoft.com/exchange/permanenturl\"");
|
2009-11-12 16:32:20 -05:00
|
|
|
if (attributes != null && attributes.length() > 0) {
|
2009-11-13 13:07:37 -05:00
|
|
|
searchRequest.append(',').append(attributes);
|
2009-11-12 16:32:20 -05:00
|
|
|
}
|
|
|
|
searchRequest.append(" FROM Scope('SHALLOW TRAVERSAL OF \"").append(folderUrl).append("\"')\n")
|
|
|
|
.append(" WHERE \"DAV:ishidden\" = False AND \"DAV:isfolder\" = False\n");
|
2009-03-09 19:12:08 -04:00
|
|
|
if (conditions != null) {
|
2009-11-12 16:32:20 -05:00
|
|
|
searchRequest.append(conditions);
|
2009-03-09 19:12:08 -04:00
|
|
|
}
|
2009-11-12 16:32:20 -05:00
|
|
|
searchRequest.append(" ORDER BY \"urn:schemas:httpmail:date\" ASC");
|
2009-04-01 11:51:12 -04:00
|
|
|
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(
|
2009-11-12 16:32:20 -05:00
|
|
|
httpClient, URIUtil.encodePath(folderUrl), searchRequest.toString());
|
2008-12-01 12:56:18 -05:00
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
for (MultiStatusResponse response : responses) {
|
|
|
|
Message message = buildMessage(response);
|
2010-04-06 05:20:09 -04:00
|
|
|
message.messageList = messages;
|
2008-12-17 10:27:56 -05:00
|
|
|
messages.add(message);
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
2009-02-02 02:19:57 -05:00
|
|
|
Collections.sort(messages);
|
2006-12-12 18:57:24 -05:00
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
|
2010-06-07 17:17:07 -04:00
|
|
|
protected enum Operator {
|
|
|
|
Or, And, Not, IsEqualTo
|
|
|
|
}
|
|
|
|
|
|
|
|
protected abstract static class Condition {
|
|
|
|
public abstract void appendTo(StringBuilder buffer);
|
|
|
|
}
|
|
|
|
|
2010-06-07 18:30:23 -04:00
|
|
|
protected abstract static class AttributeCondition extends Condition {
|
2010-06-07 17:17:07 -04:00
|
|
|
protected String attributeName;
|
|
|
|
protected Operator operator;
|
|
|
|
protected String value;
|
|
|
|
|
|
|
|
protected AttributeCondition(String attributeName, Operator operator, String value) {
|
|
|
|
this.attributeName = attributeName;
|
|
|
|
this.operator = operator;
|
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-07 18:30:23 -04:00
|
|
|
protected abstract static class MultiCondition extends Condition {
|
2010-06-07 17:17:07 -04:00
|
|
|
protected Operator operator;
|
|
|
|
protected Condition[] conditions;
|
|
|
|
|
2010-06-07 18:30:23 -04:00
|
|
|
protected MultiCondition(Operator operator, Condition... conditions) {
|
2010-06-07 17:17:07 -04:00
|
|
|
this.operator = operator;
|
2010-06-07 18:30:23 -04:00
|
|
|
this.conditions = conditions;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected abstract static class NotCondition extends Condition {
|
|
|
|
protected Condition condition;
|
|
|
|
|
|
|
|
protected NotCondition(Condition condition) {
|
|
|
|
this.condition = condition;
|
2010-06-07 17:17:07 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-08 06:44:47 -04:00
|
|
|
protected abstract static class IsNullCondition extends Condition {
|
|
|
|
protected String attributeName;
|
|
|
|
|
|
|
|
protected IsNullCondition(String attributeName) {
|
|
|
|
this.attributeName = attributeName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public abstract Condition and(Condition... condition);
|
|
|
|
|
|
|
|
public abstract Condition or(Condition... condition);
|
2010-06-07 17:17:07 -04:00
|
|
|
|
2010-06-08 06:44:47 -04:00
|
|
|
public abstract Condition not(Condition condition);
|
2010-06-07 17:17:07 -04:00
|
|
|
|
2010-06-08 06:44:47 -04:00
|
|
|
public abstract Condition equals(String attributeName, String value);
|
2010-06-07 18:30:23 -04:00
|
|
|
|
2010-06-08 06:44:47 -04:00
|
|
|
public abstract Condition isNull(String attributeName);
|
2010-06-07 17:17:07 -04:00
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
2010-06-07 18:30:23 -04:00
|
|
|
* Search mail and generic folders under given folder.
|
|
|
|
* Exclude calendar and contacts folders
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-28 02:39:27 -04:00
|
|
|
* @param folderName Exchange folder name
|
2009-08-04 16:48:35 -04:00
|
|
|
* @param recursive deep search if true
|
2009-07-28 02:39:27 -04:00
|
|
|
* @return list of folders
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-01-23 08:01:46 -05:00
|
|
|
public List<Folder> getSubFolders(String folderName, boolean recursive) throws IOException {
|
2010-06-08 06:44:47 -04:00
|
|
|
return getSubFolders(folderName,
|
|
|
|
or(equals("folderclass", "IPF.Note"), isNull("folderclass")),
|
|
|
|
recursive);
|
2009-09-09 17:50:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search calendar folders under given folder.
|
|
|
|
*
|
|
|
|
* @param folderName Exchange folder name
|
|
|
|
* @param recursive deep search if true
|
|
|
|
* @return list of folders
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public List<Folder> getSubCalendarFolders(String folderName, boolean recursive) throws IOException {
|
2010-06-07 17:17:07 -04:00
|
|
|
return getSubFolders(folderName, equals("folderclass", "IPF.Appointment"), recursive);
|
2009-09-09 17:50:11 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search folders under given folder matching filter.
|
|
|
|
*
|
|
|
|
* @param folderName Exchange folder name
|
2010-06-07 17:17:07 -04:00
|
|
|
* @param condition search filter
|
2009-09-09 17:50:11 -04:00
|
|
|
* @param recursive deep search if true
|
|
|
|
* @return list of folders
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2010-06-07 17:17:07 -04:00
|
|
|
public abstract List<Folder> getSubFolders(String folderName, Condition condition, boolean recursive) throws IOException;
|
2009-01-22 19:59:41 -05:00
|
|
|
|
2007-02-07 06:32:44 -05:00
|
|
|
/**
|
|
|
|
* Delete oldest messages in trash.
|
2007-02-07 06:51:08 -05:00
|
|
|
* keepDelay is the number of days to keep messages in trash before delete
|
2007-03-14 07:55:37 -04:00
|
|
|
*
|
|
|
|
* @throws IOException when unable to purge messages
|
2007-02-07 06:32:44 -05:00
|
|
|
*/
|
2008-12-05 05:01:24 -05:00
|
|
|
public void purgeOldestTrashAndSentMessages() throws IOException {
|
2007-02-07 06:51:08 -05:00
|
|
|
int keepDelay = Settings.getIntProperty("davmail.keepDelay");
|
2008-12-05 05:01:24 -05:00
|
|
|
if (keepDelay != 0) {
|
|
|
|
purgeOldestFolderMessages(deleteditemsUrl, keepDelay);
|
|
|
|
}
|
|
|
|
// this is a new feature, default is : do nothing
|
|
|
|
int sentKeepDelay = Settings.getIntProperty("davmail.sentKeepDelay");
|
|
|
|
if (sentKeepDelay != 0) {
|
|
|
|
purgeOldestFolderMessages(sentitemsUrl, sentKeepDelay);
|
2007-02-07 06:51:08 -05:00
|
|
|
}
|
2008-12-05 05:01:24 -05:00
|
|
|
}
|
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
protected void purgeOldestFolderMessages(String folderUrl, int keepDelay) throws IOException {
|
2007-02-07 06:32:44 -05:00
|
|
|
Calendar cal = Calendar.getInstance();
|
|
|
|
cal.add(Calendar.DAY_OF_MONTH, -keepDelay);
|
2008-12-05 05:01:24 -05:00
|
|
|
LOGGER.debug("Delete messages in " + folderUrl + " since " + cal.getTime());
|
2008-12-05 04:29:44 -05:00
|
|
|
|
2008-12-17 10:27:56 -05:00
|
|
|
String searchRequest = "Select \"DAV:uid\"" +
|
2008-12-05 05:01:24 -05:00
|
|
|
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderUrl + "\"')\n" +
|
2008-12-05 04:29:44 -05:00
|
|
|
" WHERE \"DAV:isfolder\" = False\n" +
|
2009-04-07 08:37:28 -04:00
|
|
|
" AND \"DAV:getlastmodified\" < '" + formatSearchDate(cal.getTime()) + "'\n";
|
2009-04-01 11:51:12 -04:00
|
|
|
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(
|
2009-03-18 13:26:33 -04:00
|
|
|
httpClient, URIUtil.encodePath(folderUrl), searchRequest);
|
2008-12-05 04:29:44 -05:00
|
|
|
|
2009-04-01 11:51:12 -04:00
|
|
|
for (MultiStatusResponse response : responses) {
|
2009-04-15 12:15:00 -04:00
|
|
|
String messageUrl = URIUtil.decode(response.getHref());
|
2008-12-05 04:29:44 -05:00
|
|
|
|
2008-12-17 10:27:56 -05:00
|
|
|
LOGGER.debug("Delete " + messageUrl);
|
2009-04-15 12:15:00 -04:00
|
|
|
DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, URIUtil.encodePath(messageUrl));
|
2007-02-07 06:32:44 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
|
|
|
* Send message in reader to recipients.
|
|
|
|
* Detect visible recipients in message body to determine bcc recipients
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-28 02:39:27 -04:00
|
|
|
* @param recipients recipients list
|
2009-08-04 16:48:35 -04:00
|
|
|
* @param reader message stream
|
2009-07-28 02:39:27 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2008-12-09 05:10:38 -05:00
|
|
|
public void sendMessage(List<String> recipients, BufferedReader reader) throws IOException {
|
2006-12-12 18:57:24 -05:00
|
|
|
String line = reader.readLine();
|
2008-12-09 05:10:38 -05:00
|
|
|
StringBuilder mailBuffer = new StringBuilder();
|
|
|
|
StringBuilder recipientBuffer = new StringBuilder();
|
|
|
|
boolean inHeader = true;
|
|
|
|
boolean inRecipientHeader = false;
|
|
|
|
while (!".".equals(line)) {
|
2009-02-12 06:33:19 -05:00
|
|
|
mailBuffer.append(line).append((char) 13).append((char) 10);
|
2006-12-12 18:57:24 -05:00
|
|
|
line = reader.readLine();
|
2009-04-11 09:38:11 -04:00
|
|
|
// Exchange 2007 : skip From: header
|
|
|
|
if ((inHeader && line.length() >= 5)) {
|
|
|
|
String prefix = line.substring(0, 5).toLowerCase();
|
|
|
|
if ("from:".equals(prefix)) {
|
|
|
|
line = reader.readLine();
|
|
|
|
}
|
|
|
|
}
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2008-12-09 05:10:38 -05:00
|
|
|
if (inHeader && line.length() == 0) {
|
|
|
|
inHeader = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
inRecipientHeader = inRecipientHeader && line.startsWith(" ");
|
|
|
|
|
|
|
|
if ((inHeader && line.length() >= 3) || inRecipientHeader) {
|
|
|
|
String prefix = line.substring(0, 3).toLowerCase();
|
|
|
|
if ("to:".equals(prefix) || "cc:".equals(prefix) || inRecipientHeader) {
|
|
|
|
inRecipientHeader = true;
|
|
|
|
recipientBuffer.append(line);
|
2008-12-02 05:20:46 -05:00
|
|
|
}
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
2008-12-09 05:10:38 -05:00
|
|
|
}
|
|
|
|
// remove visible recipients from list
|
|
|
|
List<String> visibleRecipients = new ArrayList<String>();
|
|
|
|
for (String recipient : recipients) {
|
|
|
|
if (recipientBuffer.indexOf(recipient) >= 0) {
|
|
|
|
visibleRecipients.add(recipient);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
recipients.removeAll(visibleRecipients);
|
|
|
|
|
2009-04-16 17:16:40 -04:00
|
|
|
StringBuilder bccBuffer = new StringBuilder();
|
2008-12-09 05:10:38 -05:00
|
|
|
for (String recipient : recipients) {
|
|
|
|
if (bccBuffer.length() > 0) {
|
|
|
|
bccBuffer.append(',');
|
|
|
|
}
|
2009-04-17 06:49:04 -04:00
|
|
|
bccBuffer.append('<');
|
2008-12-09 05:10:38 -05:00
|
|
|
bccBuffer.append(recipient);
|
2009-04-17 06:49:04 -04:00
|
|
|
bccBuffer.append('>');
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
|
|
|
|
2009-02-03 18:54:48 -05:00
|
|
|
String bcc = bccBuffer.toString();
|
|
|
|
HashMap<String, String> properties = new HashMap<String, String>();
|
|
|
|
if (bcc.length() > 0) {
|
|
|
|
properties.put("bcc", bcc);
|
|
|
|
}
|
|
|
|
|
2008-12-19 07:27:33 -05:00
|
|
|
String messageName = UUID.randomUUID().toString();
|
|
|
|
|
2009-07-28 02:53:46 -04:00
|
|
|
createMessage("Drafts", messageName, properties, mailBuffer.toString());
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2009-04-23 16:53:22 -04:00
|
|
|
String tempUrl = draftsUrl + '/' + messageName + ".EML";
|
2009-04-01 11:51:12 -04:00
|
|
|
MoveMethod method = new MoveMethod(URIUtil.encodePath(tempUrl), URIUtil.encodePath(sendmsgUrl), true);
|
2009-03-18 13:26:33 -04:00
|
|
|
int status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
|
2009-03-18 18:19:28 -04:00
|
|
|
if (status != HttpStatus.SC_OK) {
|
|
|
|
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
|
|
|
}
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
|
|
|
|
2009-07-28 02:39:27 -04:00
|
|
|
/**
|
2009-07-28 02:53:46 -04:00
|
|
|
* Convert logical or relative folder path to absolute folder path.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-28 02:53:46 -04:00
|
|
|
* @param folderName folder name
|
|
|
|
* @return folder path
|
2009-07-28 02:39:27 -04:00
|
|
|
*/
|
2009-01-22 19:59:41 -05:00
|
|
|
public String getFolderPath(String folderName) {
|
|
|
|
String folderPath;
|
2010-06-08 06:44:47 -04:00
|
|
|
if (folderName.startsWith(INBOX)) {
|
|
|
|
folderPath = folderName.replaceFirst(INBOX, inboxUrl);
|
|
|
|
} else if (folderName.startsWith(TRASH)) {
|
|
|
|
folderPath = folderName.replaceFirst(TRASH, deleteditemsUrl);
|
|
|
|
} else if (folderName.startsWith(DRAFTS)) {
|
|
|
|
folderPath = folderName.replaceFirst(DRAFTS, draftsUrl);
|
|
|
|
} else if (folderName.startsWith(SENT)) {
|
|
|
|
folderPath = folderName.replaceFirst(SENT, sentitemsUrl);
|
|
|
|
} else if (folderName.startsWith(CALENDAR)) {
|
|
|
|
folderPath = folderName.replaceFirst(CALENDAR, calendarUrl);
|
|
|
|
} else if (folderName.startsWith(CONTACTS)) {
|
|
|
|
folderPath = folderName.replaceFirst(CONTACTS, contactsUrl);
|
2009-09-15 05:48:52 -04:00
|
|
|
} else if (folderName.startsWith("public")) {
|
2009-12-09 16:13:43 -05:00
|
|
|
folderPath = publicFolderUrl + folderName.substring("public".length());
|
2009-01-23 08:01:46 -05:00
|
|
|
// absolute folder path
|
2009-01-23 08:03:32 -05:00
|
|
|
} else if (folderName.startsWith("/")) {
|
2009-01-22 19:59:41 -05:00
|
|
|
folderPath = folderName;
|
|
|
|
} else {
|
|
|
|
folderPath = mailPath + folderName;
|
|
|
|
}
|
|
|
|
return folderPath;
|
|
|
|
}
|
|
|
|
|
2007-11-08 12:12:36 -05:00
|
|
|
/**
|
2009-07-28 02:53:46 -04:00
|
|
|
* Get folder object.
|
|
|
|
* Folder name can be logical names INBOX, Drafts, Trash or calendar,
|
|
|
|
* or a path relative to user base folder or absolute path.
|
2007-11-08 17:09:38 -05:00
|
|
|
*
|
2007-11-08 12:12:36 -05:00
|
|
|
* @param folderName folder name
|
|
|
|
* @return Folder object
|
2009-07-28 02:53:46 -04:00
|
|
|
* @throws IOException on error
|
2007-11-08 12:12:36 -05:00
|
|
|
*/
|
2010-06-07 05:07:31 -04:00
|
|
|
public abstract Folder getFolder(String folderName) throws IOException;
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2009-03-27 06:31:52 -04:00
|
|
|
/**
|
2009-07-28 02:53:46 -04:00
|
|
|
* Check folder ctag and reload messages as needed.
|
2009-03-27 06:31:52 -04:00
|
|
|
*
|
|
|
|
* @param currentFolder current folder
|
2009-12-01 05:14:59 -05:00
|
|
|
* @return true if folder changed
|
2009-03-27 06:31:52 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-12-01 05:14:59 -05:00
|
|
|
public boolean refreshFolder(Folder currentFolder) throws IOException {
|
2009-03-27 06:31:52 -04:00
|
|
|
Folder newFolder = getFolder(currentFolder.folderName);
|
2010-04-01 05:12:44 -04:00
|
|
|
if (currentFolder.ctag == null || !currentFolder.ctag.equals(newFolder.ctag)) {
|
2009-03-27 06:31:52 -04:00
|
|
|
if (LOGGER.isDebugEnabled()) {
|
2009-04-23 16:53:22 -04:00
|
|
|
LOGGER.debug("Contenttag changed on " + currentFolder.folderName + ' '
|
2010-04-01 05:12:44 -04:00
|
|
|
+ currentFolder.ctag + " => " + newFolder.ctag + ", reloading messages");
|
2009-03-27 06:31:52 -04:00
|
|
|
}
|
2009-12-01 05:14:59 -05:00
|
|
|
currentFolder.hasChildren = newFolder.hasChildren;
|
|
|
|
currentFolder.noInferiors = newFolder.noInferiors;
|
|
|
|
currentFolder.unreadCount = newFolder.unreadCount;
|
2010-04-01 05:12:44 -04:00
|
|
|
currentFolder.ctag = newFolder.ctag;
|
2009-12-01 05:14:59 -05:00
|
|
|
currentFolder.loadMessages();
|
|
|
|
return true;
|
2009-03-27 06:31:52 -04:00
|
|
|
} else {
|
2009-12-01 05:14:59 -05:00
|
|
|
return false;
|
2009-03-27 06:31:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
2009-10-27 19:32:00 -04:00
|
|
|
* Create Exchange message folder.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @param folderName logical folder name
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-10-27 19:32:00 -04:00
|
|
|
public void createMessageFolder(String folderName) throws IOException {
|
|
|
|
createFolder(folderName, "IPF.Note");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create Exchange calendar folder.
|
|
|
|
*
|
|
|
|
* @param folderName logical folder name
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public void createCalendarFolder(String folderName) throws IOException {
|
|
|
|
createFolder(folderName, "IPF.Appointment");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create Exchange folder with given folder class.
|
|
|
|
*
|
2009-10-29 13:19:00 -04:00
|
|
|
* @param folderName logical folder name
|
|
|
|
* @param folderClass folder class
|
2009-10-27 19:32:00 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public void createFolder(String folderName, String folderClass) throws IOException {
|
2009-01-23 08:01:46 -05:00
|
|
|
String folderPath = getFolderPath(folderName);
|
2009-04-01 11:51:12 -04:00
|
|
|
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
|
2009-10-27 05:26:12 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("outlookfolderclass", Namespace.getNamespace("http://schemas.microsoft.com/exchange/")), folderClass));
|
2009-07-31 02:21:46 -04:00
|
|
|
// standard MkColMethod does not take properties, override PropPatchMethod instead
|
2009-04-01 11:51:12 -04:00
|
|
|
PropPatchMethod method = new PropPatchMethod(URIUtil.encodePath(folderPath), list) {
|
2009-04-28 17:01:40 -04:00
|
|
|
@Override
|
|
|
|
public String getName() {
|
2009-01-23 08:01:46 -05:00
|
|
|
return "MKCOL";
|
|
|
|
}
|
|
|
|
};
|
2009-03-18 18:19:28 -04:00
|
|
|
int status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
|
2010-01-21 05:49:23 -05:00
|
|
|
// ok or already exists
|
2009-03-18 18:19:28 -04:00
|
|
|
if (status != HttpStatus.SC_MULTI_STATUS && status != HttpStatus.SC_METHOD_NOT_ALLOWED) {
|
|
|
|
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
2009-01-26 18:51:08 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Delete Exchange folder.
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param folderName logical folder name
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public void deleteFolder(String folderName) throws IOException {
|
|
|
|
DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, URIUtil.encodePath(getFolderPath(folderName)));
|
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Copy message to target folder
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
|
|
|
* @param message Exchange message
|
2009-07-31 02:21:46 -04:00
|
|
|
* @param targetFolder target folder
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public void copyMessage(Message message, String targetFolder) throws IOException {
|
2009-11-13 13:07:37 -05:00
|
|
|
String targetPath = URIUtil.encodePath(getFolderPath(targetFolder)) + '/' + UUID.randomUUID().toString();
|
|
|
|
CopyMethod method = new CopyMethod(message.permanentUrl, targetPath, false);
|
2009-07-31 02:21:46 -04:00
|
|
|
// allow rename if a message with the same name exists
|
2009-02-02 18:29:44 -05:00
|
|
|
method.addRequestHeader("Allow-Rename", "t");
|
|
|
|
try {
|
2009-03-18 13:26:33 -04:00
|
|
|
int statusCode = httpClient.executeMethod(method);
|
2009-02-02 18:29:44 -05:00
|
|
|
if (statusCode == HttpStatus.SC_PRECONDITION_FAILED) {
|
2009-07-31 02:21:46 -04:00
|
|
|
throw new DavMailException("EXCEPTION_UNABLE_TO_COPY_MESSAGE");
|
2009-02-02 18:29:44 -05:00
|
|
|
} else if (statusCode != HttpStatus.SC_CREATED) {
|
2009-03-18 13:26:33 -04:00
|
|
|
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
2009-02-02 18:29:44 -05:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
method.releaseConnection();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Move folder to target name.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @param folderName current folder name/path
|
|
|
|
* @param targetName target folder name/path
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-01-26 18:51:08 -05:00
|
|
|
public void moveFolder(String folderName, String targetName) throws IOException {
|
|
|
|
String folderPath = getFolderPath(folderName);
|
|
|
|
String targetPath = getFolderPath(targetName);
|
2009-07-31 02:21:46 -04:00
|
|
|
MoveMethod method = new MoveMethod(URIUtil.encodePath(folderPath), URIUtil.encodePath(targetPath), false);
|
2009-02-02 02:19:57 -05:00
|
|
|
try {
|
2009-03-18 13:26:33 -04:00
|
|
|
int statusCode = httpClient.executeMethod(method);
|
2009-02-02 02:19:57 -05:00
|
|
|
if (statusCode == HttpStatus.SC_PRECONDITION_FAILED) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_UNABLE_TO_MOVE_FOLDER");
|
2009-02-02 02:19:57 -05:00
|
|
|
} else if (statusCode != HttpStatus.SC_CREATED) {
|
2009-03-18 13:26:33 -04:00
|
|
|
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
2009-02-02 02:19:57 -05:00
|
|
|
}
|
2009-01-26 18:51:08 -05:00
|
|
|
} finally {
|
|
|
|
method.releaseConnection();
|
|
|
|
}
|
2009-01-23 08:01:46 -05:00
|
|
|
}
|
|
|
|
|
2009-11-12 16:32:20 -05:00
|
|
|
protected void moveToTrash(String encodedMessageUrl) throws IOException {
|
2009-11-13 13:07:37 -05:00
|
|
|
String destination = URIUtil.encodePath(deleteditemsUrl) + '/' + UUID.randomUUID().toString();
|
|
|
|
LOGGER.debug("Deleting : " + encodedMessageUrl + " to " + destination);
|
|
|
|
MoveMethod method = new MoveMethod(encodedMessageUrl, destination, false);
|
2009-03-05 05:26:20 -05:00
|
|
|
method.addRequestHeader("Allow-rename", "t");
|
|
|
|
|
2009-03-18 13:26:33 -04:00
|
|
|
int status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
|
2009-03-13 17:18:11 -04:00
|
|
|
// do not throw error if already deleted
|
|
|
|
if (status != HttpStatus.SC_CREATED && status != HttpStatus.SC_NOT_FOUND) {
|
2009-03-18 13:26:33 -04:00
|
|
|
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
2009-03-05 05:26:20 -05:00
|
|
|
}
|
|
|
|
if (method.getResponseHeader("Location") != null) {
|
|
|
|
destination = method.getResponseHeader("Location").getValue();
|
|
|
|
}
|
|
|
|
|
2009-03-18 13:26:33 -04:00
|
|
|
LOGGER.debug("Deleted to :" + destination);
|
2009-03-05 05:26:20 -05:00
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Exchange folder with IMAP properties
|
|
|
|
*/
|
2009-03-27 06:31:52 -04:00
|
|
|
public class Folder {
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Logical (IMAP) folder path.
|
|
|
|
*/
|
|
|
|
public String folderPath;
|
2010-04-01 05:12:44 -04:00
|
|
|
|
|
|
|
/**
|
2010-06-07 17:17:07 -04:00
|
|
|
* Folder class (PR_CONTAINER_CLASS).
|
2010-04-01 05:12:44 -04:00
|
|
|
*/
|
2010-06-07 17:17:07 -04:00
|
|
|
public String folderClass;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Folder unread message count.
|
|
|
|
*/
|
2006-12-12 18:57:24 -05:00
|
|
|
public int unreadCount;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* true if folder has subfolders (DAV:hassubs).
|
|
|
|
*/
|
2009-01-22 19:59:41 -05:00
|
|
|
public boolean hasChildren;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* true if folder has no subfolders (DAV:nosubs).
|
|
|
|
*/
|
2009-01-22 19:59:41 -05:00
|
|
|
public boolean noInferiors;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Requested folder name
|
|
|
|
*/
|
2009-01-23 06:20:20 -05:00
|
|
|
public String folderName;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Folder content tag (to detect folder content changes).
|
|
|
|
*/
|
2010-04-01 05:12:44 -04:00
|
|
|
public String ctag;
|
|
|
|
/**
|
|
|
|
* Folder etag (to detect folder object changes).
|
|
|
|
*/
|
|
|
|
public String etag;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Folder message list, empty before loadMessages call.
|
|
|
|
*/
|
2009-03-27 06:31:52 -04:00
|
|
|
public ExchangeSession.MessageList messages;
|
2010-01-05 05:06:47 -05:00
|
|
|
/**
|
2010-01-21 05:49:23 -05:00
|
|
|
* PermanentURL to UID map.
|
2010-01-05 05:06:47 -05:00
|
|
|
*/
|
2010-01-21 05:49:23 -05:00
|
|
|
private final HashMap<String, Long> uidUrlHashMap = new HashMap<String, Long>();
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Get IMAP folder flags.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @return folder flags in IMAP format
|
|
|
|
*/
|
2009-01-22 19:59:41 -05:00
|
|
|
public String getFlags() {
|
|
|
|
if (noInferiors) {
|
|
|
|
return "\\NoInferiors";
|
|
|
|
} else if (hasChildren) {
|
|
|
|
return "\\HasChildren";
|
|
|
|
} else {
|
|
|
|
return "\\HasNoChildren";
|
|
|
|
}
|
|
|
|
}
|
2009-03-27 06:31:52 -04:00
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Load folder messages.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-03-27 06:31:52 -04:00
|
|
|
public void loadMessages() throws IOException {
|
2010-01-21 05:49:23 -05:00
|
|
|
messages = ExchangeSession.this.searchMessages(folderPath, "");
|
|
|
|
fixUids(messages);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search messages in folder matching query.
|
|
|
|
*
|
|
|
|
* @param query search query
|
|
|
|
* @return message list
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public MessageList searchMessages(String query) throws IOException {
|
|
|
|
MessageList localMessages = ExchangeSession.this.searchMessages(folderName, query);
|
|
|
|
fixUids(localMessages);
|
|
|
|
return localMessages;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restore previous uids changed by a PROPPATCH (flag change).
|
|
|
|
*
|
|
|
|
* @param messages message list
|
|
|
|
*/
|
|
|
|
protected void fixUids(MessageList messages) {
|
|
|
|
boolean sortNeeded = false;
|
|
|
|
for (Message message : messages) {
|
|
|
|
if (uidUrlHashMap.containsKey(message.getPermanentUrl())) {
|
|
|
|
long previousUid = uidUrlHashMap.get(message.getPermanentUrl());
|
|
|
|
if (message.getImapUid() != previousUid) {
|
|
|
|
LOGGER.debug("Restoring IMAP uid " + message.getImapUid() + " -> " + previousUid + " for message " + message.getPermanentUrl() + " (" + message.messageUrl + ')');
|
|
|
|
message.setImapUid(previousUid);
|
|
|
|
sortNeeded = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// add message to uid map
|
|
|
|
uidUrlHashMap.put(message.getPermanentUrl(), message.getImapUid());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sortNeeded) {
|
|
|
|
Collections.sort(messages);
|
|
|
|
}
|
2009-03-27 06:31:52 -04:00
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Folder message count.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @return message count
|
|
|
|
*/
|
|
|
|
public int count() {
|
2009-03-27 06:31:52 -04:00
|
|
|
return messages.size();
|
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Compute IMAP uidnext.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
|
|
|
* @return max(messageuids)+1
|
2009-07-31 02:21:46 -04:00
|
|
|
*/
|
2009-03-27 06:31:52 -04:00
|
|
|
public long getUidNext() {
|
|
|
|
return messages.get(messages.size() - 1).getImapUid() + 1;
|
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Get message at index.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @param index message index
|
|
|
|
* @return message
|
|
|
|
*/
|
2009-03-27 06:31:52 -04:00
|
|
|
public Message get(int index) {
|
|
|
|
return messages.get(index);
|
|
|
|
}
|
2010-04-01 05:12:44 -04:00
|
|
|
|
|
|
|
/**
|
2010-04-22 17:50:16 -04:00
|
|
|
* Get current folder messages imap uids
|
2010-04-23 09:42:37 -04:00
|
|
|
*
|
2010-04-22 17:50:16 -04:00
|
|
|
* @return imap uid list
|
2010-04-01 05:12:44 -04:00
|
|
|
*/
|
2010-04-22 17:50:16 -04:00
|
|
|
public List<Long> getImapUidList() {
|
|
|
|
ArrayList<Long> imapUidList = new ArrayList<Long>();
|
|
|
|
for (ExchangeSession.Message message : messages) {
|
|
|
|
imapUidList.add(message.getImapUid());
|
|
|
|
}
|
|
|
|
return imapUidList;
|
|
|
|
}
|
|
|
|
|
2010-04-23 09:42:37 -04:00
|
|
|
/**
|
|
|
|
* Calendar folder flag.
|
|
|
|
*
|
|
|
|
* @return true if this is a calendar folder
|
|
|
|
*/
|
2010-04-01 05:12:44 -04:00
|
|
|
public boolean isCalendar() {
|
2010-06-07 17:17:07 -04:00
|
|
|
return "IPF.Appointment".equals(folderClass);
|
2010-04-01 05:12:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contact folder flag.
|
2010-04-06 05:20:09 -04:00
|
|
|
*
|
2010-04-01 05:12:44 -04:00
|
|
|
* @return true if this is a calendar folder
|
|
|
|
*/
|
|
|
|
public boolean isContact() {
|
2010-06-07 17:17:07 -04:00
|
|
|
return "IPF.Contact".equals(folderClass);
|
2010-04-01 05:12:44 -04:00
|
|
|
}
|
2010-04-22 17:50:16 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* drop cached message
|
|
|
|
*/
|
|
|
|
public void clearCache() {
|
2010-04-23 09:42:37 -04:00
|
|
|
messages.cachedMimeBody = null;
|
2010-04-22 17:50:16 -04:00
|
|
|
messages.cachedMimeMessage = null;
|
|
|
|
messages.cachedMessageImapUid = 0;
|
|
|
|
}
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Exchange message.
|
|
|
|
*/
|
2009-11-02 04:53:42 -05:00
|
|
|
public class Message implements Comparable<Message> {
|
2010-04-06 05:20:09 -04:00
|
|
|
/**
|
|
|
|
* enclosing message list
|
|
|
|
*/
|
|
|
|
protected MessageList messageList;
|
|
|
|
/**
|
|
|
|
* Message url.
|
|
|
|
*/
|
2009-07-31 02:21:46 -04:00
|
|
|
protected String messageUrl;
|
2010-04-06 05:20:09 -04:00
|
|
|
/**
|
|
|
|
* Message permanent url (does not change on message move).
|
|
|
|
*/
|
2009-11-13 13:07:37 -05:00
|
|
|
protected String permanentUrl;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Message uid.
|
|
|
|
*/
|
|
|
|
protected String uid;
|
|
|
|
/**
|
|
|
|
* Message IMAP uid, unique in folder (x0e230003).
|
|
|
|
*/
|
|
|
|
protected long imapUid;
|
|
|
|
/**
|
|
|
|
* MAPI message size.
|
|
|
|
*/
|
2006-12-12 18:57:24 -05:00
|
|
|
public int size;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Message date (urn:schemas:mailheader:date).
|
|
|
|
*/
|
2009-02-23 12:42:49 -05:00
|
|
|
public String date;
|
2009-07-31 02:21:46 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Message flag: read.
|
|
|
|
*/
|
2009-01-26 18:51:08 -05:00
|
|
|
public boolean read;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Message flag: deleted.
|
|
|
|
*/
|
2009-02-02 18:29:44 -05:00
|
|
|
public boolean deleted;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Message flag: junk.
|
|
|
|
*/
|
2009-02-03 09:38:05 -05:00
|
|
|
public boolean junk;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Message flag: flagged.
|
|
|
|
*/
|
2009-02-03 09:38:05 -05:00
|
|
|
public boolean flagged;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Message flag: draft.
|
|
|
|
*/
|
2009-02-03 11:13:00 -05:00
|
|
|
public boolean draft;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Message flag: answered.
|
|
|
|
*/
|
2009-02-03 18:54:48 -05:00
|
|
|
public boolean answered;
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Message flag: fowarded.
|
|
|
|
*/
|
2009-02-04 19:18:29 -05:00
|
|
|
public boolean forwarded;
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2010-04-23 09:42:37 -04:00
|
|
|
/**
|
|
|
|
* Unparsed message content.
|
|
|
|
*/
|
|
|
|
protected SharedByteArrayInputStream mimeBody;
|
|
|
|
|
2009-10-29 13:19:00 -04:00
|
|
|
/**
|
|
|
|
* Message content parsed in a MIME message.
|
|
|
|
*/
|
|
|
|
protected MimeMessage mimeMessage;
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* IMAP uid , unique in folder (x0e230003)
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @return IMAP uid
|
|
|
|
*/
|
2009-03-19 08:41:11 -04:00
|
|
|
public long getImapUid() {
|
|
|
|
return imapUid;
|
2009-02-02 02:19:57 -05:00
|
|
|
}
|
|
|
|
|
2010-01-21 05:49:23 -05:00
|
|
|
/**
|
|
|
|
* Set IMAP uid.
|
2010-02-01 18:20:56 -05:00
|
|
|
*
|
2010-01-21 05:49:23 -05:00
|
|
|
* @param imapUid new uid
|
|
|
|
*/
|
|
|
|
public void setImapUid(long imapUid) {
|
|
|
|
this.imapUid = imapUid;
|
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Exchange uid.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @return uid
|
|
|
|
*/
|
|
|
|
public String getUid() {
|
|
|
|
return uid;
|
|
|
|
}
|
|
|
|
|
2009-11-12 16:32:20 -05:00
|
|
|
/**
|
2009-11-17 18:11:08 -05:00
|
|
|
* Return permanent message url.
|
2009-11-18 06:13:06 -05:00
|
|
|
*
|
2009-11-17 18:11:08 -05:00
|
|
|
* @return permanent message url
|
2009-11-12 16:32:20 -05:00
|
|
|
*/
|
2010-01-21 05:49:23 -05:00
|
|
|
public String getPermanentUrl() {
|
2009-11-17 18:11:08 -05:00
|
|
|
return permanentUrl;
|
2009-11-12 16:32:20 -05:00
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Return message flags in IMAP format.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @return IMAP flags
|
|
|
|
*/
|
2009-02-02 18:29:44 -05:00
|
|
|
public String getImapFlags() {
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
|
|
|
if (read) {
|
|
|
|
buffer.append("\\Seen ");
|
|
|
|
}
|
|
|
|
if (deleted) {
|
2009-02-03 09:38:05 -05:00
|
|
|
buffer.append("\\Deleted ");
|
|
|
|
}
|
|
|
|
if (flagged) {
|
|
|
|
buffer.append("\\Flagged ");
|
|
|
|
}
|
|
|
|
if (junk) {
|
|
|
|
buffer.append("Junk ");
|
2009-02-02 18:29:44 -05:00
|
|
|
}
|
2009-02-03 11:13:00 -05:00
|
|
|
if (draft) {
|
|
|
|
buffer.append("\\Draft ");
|
|
|
|
}
|
2009-02-03 18:54:48 -05:00
|
|
|
if (answered) {
|
|
|
|
buffer.append("\\Answered ");
|
|
|
|
}
|
2009-02-04 19:18:29 -05:00
|
|
|
if (forwarded) {
|
|
|
|
buffer.append("$Forwarded ");
|
|
|
|
}
|
2009-02-03 09:38:05 -05:00
|
|
|
return buffer.toString().trim();
|
2009-02-02 18:29:44 -05:00
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Write MIME message to os
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2010-04-06 05:20:09 -04:00
|
|
|
* @param os output stream
|
2010-04-02 11:35:37 -04:00
|
|
|
* @param doubleDot replace '.' lines with '..' (POP protocol)
|
2009-07-31 02:21:46 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2010-04-02 11:35:37 -04:00
|
|
|
public void write(OutputStream os, boolean doubleDot) throws IOException {
|
2009-12-16 04:16:21 -05:00
|
|
|
try {
|
2010-04-02 11:35:37 -04:00
|
|
|
write(os, messageUrl, doubleDot);
|
2009-12-16 04:16:21 -05:00
|
|
|
} catch (HttpNotFoundException e) {
|
2009-12-16 17:12:54 -05:00
|
|
|
LOGGER.debug("Message not found at: " + messageUrl + ", retrying with permanenturl");
|
2010-04-02 11:35:37 -04:00
|
|
|
write(os, permanentUrl, doubleDot);
|
2009-12-16 04:16:21 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-04-23 06:17:20 -04:00
|
|
|
protected boolean isGzipEncoded(HttpMethod method) {
|
|
|
|
Header[] contentEncodingHeaders = method.getResponseHeaders("Content-Encoding");
|
|
|
|
if (contentEncodingHeaders != null) {
|
|
|
|
for (Header header : contentEncodingHeaders) {
|
|
|
|
if ("gzip".equals(header.getValue())) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-04-02 11:35:37 -04:00
|
|
|
protected void write(OutputStream os, String url, boolean doubleDot) throws IOException {
|
2009-12-16 04:16:21 -05:00
|
|
|
GetMethod method = new GetMethod(URIUtil.encodePath(url));
|
2009-09-17 17:40:47 -04:00
|
|
|
method.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
|
|
|
|
method.setRequestHeader("Translate", "f");
|
2010-04-23 06:17:20 -04:00
|
|
|
method.setRequestHeader("Accept-Encoding", "gzip");
|
2007-04-25 18:04:37 -04:00
|
|
|
BufferedReader reader = null;
|
2006-12-12 18:57:24 -05:00
|
|
|
try {
|
2010-03-24 05:59:23 -04:00
|
|
|
DavGatewayHttpClientFacade.executeGetMethod(httpClient, method, true);
|
2006-12-12 18:57:24 -05:00
|
|
|
|
2010-04-23 06:17:20 -04:00
|
|
|
if (isGzipEncoded(method)) {
|
|
|
|
reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(method.getResponseBodyAsStream())));
|
|
|
|
} else {
|
|
|
|
reader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
|
|
|
|
}
|
2008-02-05 18:17:31 -05:00
|
|
|
OutputStreamWriter isoWriter = new OutputStreamWriter(os);
|
|
|
|
String line;
|
|
|
|
while ((line = reader.readLine()) != null) {
|
2010-04-02 11:35:37 -04:00
|
|
|
if (doubleDot && ".".equals(line)) {
|
2008-02-05 18:17:31 -05:00
|
|
|
line = "..";
|
2008-02-14 09:20:37 -05:00
|
|
|
// patch text/calendar to include utf-8 encoding
|
|
|
|
} else if ("Content-Type: text/calendar;".equals(line)) {
|
2009-04-16 17:16:40 -04:00
|
|
|
StringBuilder headerBuffer = new StringBuilder();
|
2008-02-14 09:20:37 -05:00
|
|
|
headerBuffer.append(line);
|
|
|
|
while ((line = reader.readLine()) != null && line.startsWith("\t")) {
|
|
|
|
headerBuffer.append((char) 13);
|
|
|
|
headerBuffer.append((char) 10);
|
|
|
|
headerBuffer.append(line);
|
|
|
|
}
|
|
|
|
if (headerBuffer.indexOf("charset") < 0) {
|
|
|
|
headerBuffer.append(";charset=utf-8");
|
|
|
|
}
|
|
|
|
headerBuffer.append((char) 13);
|
|
|
|
headerBuffer.append((char) 10);
|
|
|
|
headerBuffer.append(line);
|
|
|
|
line = headerBuffer.toString();
|
2008-01-31 17:17:44 -05:00
|
|
|
}
|
2008-02-05 18:17:31 -05:00
|
|
|
isoWriter.write(line);
|
|
|
|
isoWriter.write((char) 13);
|
|
|
|
isoWriter.write((char) 10);
|
2007-04-25 18:04:37 -04:00
|
|
|
}
|
2008-02-05 18:17:31 -05:00
|
|
|
isoWriter.flush();
|
2010-05-11 08:52:15 -04:00
|
|
|
} catch (HttpException e) {
|
2009-12-09 16:13:43 -05:00
|
|
|
LOGGER.warn("Unable to retrieve message at: " + messageUrl);
|
2010-05-11 08:52:15 -04:00
|
|
|
if (Settings.getBooleanProperty("davmail.deleteBroken")
|
|
|
|
&& method.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
|
|
|
|
LOGGER.warn("Deleting broken message at: " + messageUrl + " permanentUrl: " + permanentUrl);
|
|
|
|
try {
|
|
|
|
this.delete();
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
LOGGER.warn("Unable to delete broken message at: " + permanentUrl);
|
|
|
|
}
|
|
|
|
}
|
2009-12-07 05:44:17 -05:00
|
|
|
throw e;
|
2007-04-25 18:04:37 -04:00
|
|
|
} finally {
|
|
|
|
if (reader != null) {
|
|
|
|
try {
|
|
|
|
reader.close();
|
|
|
|
} catch (IOException e) {
|
2008-02-05 18:17:31 -05:00
|
|
|
LOGGER.warn("Error closing message input stream", e);
|
2007-04-25 18:04:37 -04:00
|
|
|
}
|
|
|
|
}
|
2009-09-17 17:40:47 -04:00
|
|
|
method.releaseConnection();
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-29 18:45:17 -04:00
|
|
|
/**
|
|
|
|
* Load message content in a Mime message
|
2009-11-01 16:58:04 -05:00
|
|
|
*
|
|
|
|
* @throws IOException on error
|
2009-10-29 18:45:17 -04:00
|
|
|
* @throws MessagingException on error
|
|
|
|
*/
|
2010-04-23 09:42:37 -04:00
|
|
|
protected void loadMimeMessage() throws IOException, MessagingException {
|
2009-10-29 13:19:00 -04:00
|
|
|
if (mimeMessage == null) {
|
2010-04-06 05:20:09 -04:00
|
|
|
// try to get message content from cache
|
|
|
|
if (this.imapUid == messageList.cachedMessageImapUid) {
|
2010-04-23 09:42:37 -04:00
|
|
|
mimeBody = messageList.cachedMimeBody;
|
2010-04-06 05:20:09 -04:00
|
|
|
mimeMessage = messageList.cachedMimeMessage;
|
2010-04-23 09:42:37 -04:00
|
|
|
LOGGER.debug("Got message content for " + imapUid + " from cache");
|
2010-04-06 05:20:09 -04:00
|
|
|
} else {
|
|
|
|
// load message
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
write(baos, false);
|
2010-04-23 09:42:37 -04:00
|
|
|
// load and parse message
|
|
|
|
mimeBody = new SharedByteArrayInputStream(baos.toByteArray());
|
|
|
|
mimeMessage = new MimeMessage(null, mimeBody);
|
|
|
|
LOGGER.debug("Downloaded message content for " + imapUid + " (" + baos.size() + ')');
|
2010-04-06 05:20:09 -04:00
|
|
|
}
|
2009-10-29 13:19:00 -04:00
|
|
|
}
|
2010-04-23 09:42:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get message content as a Mime message.
|
|
|
|
*
|
|
|
|
* @return mime message
|
|
|
|
* @throws IOException on error
|
|
|
|
* @throws MessagingException on error
|
|
|
|
*/
|
|
|
|
public MimeMessage getMimeMessage() throws IOException, MessagingException {
|
|
|
|
loadMimeMessage();
|
2009-10-29 13:19:00 -04:00
|
|
|
return mimeMessage;
|
|
|
|
}
|
|
|
|
|
2010-04-23 09:42:37 -04:00
|
|
|
/**
|
|
|
|
* Get message body size.
|
2010-05-07 04:30:12 -04:00
|
|
|
*
|
2010-04-23 09:42:37 -04:00
|
|
|
* @return mime message size
|
2010-05-07 04:30:12 -04:00
|
|
|
* @throws IOException on error
|
2010-04-23 09:42:37 -04:00
|
|
|
* @throws MessagingException on error
|
|
|
|
*/
|
|
|
|
public int getMimeMessageSize() throws IOException, MessagingException {
|
|
|
|
loadMimeMessage();
|
|
|
|
mimeBody.reset();
|
|
|
|
return mimeBody.available();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get message body input stream.
|
2010-05-07 04:30:12 -04:00
|
|
|
*
|
2010-04-23 09:42:37 -04:00
|
|
|
* @return mime message InputStream
|
2010-05-07 04:30:12 -04:00
|
|
|
* @throws IOException on error
|
2010-04-23 09:42:37 -04:00
|
|
|
* @throws MessagingException on error
|
|
|
|
*/
|
|
|
|
public InputStream getRawInputStream() throws IOException, MessagingException {
|
|
|
|
loadMimeMessage();
|
|
|
|
mimeBody.reset();
|
|
|
|
return mimeBody;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-30 07:14:47 -04:00
|
|
|
/**
|
2010-04-06 05:20:09 -04:00
|
|
|
* Drop mime message to avoid keeping message content in memory,
|
|
|
|
* keep a single message in MessageList cache to handle chunked fetch.
|
2009-10-30 07:14:47 -04:00
|
|
|
*/
|
|
|
|
public void dropMimeMessage() {
|
2010-04-06 05:20:09 -04:00
|
|
|
// update single message cache
|
|
|
|
if (mimeMessage != null) {
|
|
|
|
messageList.cachedMessageImapUid = imapUid;
|
2010-04-23 09:42:37 -04:00
|
|
|
messageList.cachedMimeBody = mimeBody;
|
2010-04-06 05:20:09 -04:00
|
|
|
messageList.cachedMimeMessage = mimeMessage;
|
|
|
|
}
|
2009-10-30 07:14:47 -04:00
|
|
|
mimeMessage = null;
|
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Delete message.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2006-12-12 18:57:24 -05:00
|
|
|
public void delete() throws IOException {
|
2009-11-13 13:07:37 -05:00
|
|
|
DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, permanentUrl);
|
2009-02-03 16:02:33 -05:00
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Move message to trash, mark message read.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-02-03 16:02:33 -05:00
|
|
|
public void moveToTrash() throws IOException {
|
2009-02-05 11:28:54 -05:00
|
|
|
// mark message as read
|
2009-02-09 05:12:09 -05:00
|
|
|
HashMap<String, String> properties = new HashMap<String, String>();
|
2009-02-05 11:28:54 -05:00
|
|
|
properties.put("read", "1");
|
|
|
|
updateMessage(this, properties);
|
|
|
|
|
2009-11-13 13:07:37 -05:00
|
|
|
ExchangeSession.this.moveToTrash(permanentUrl);
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Comparator to sort messages by IMAP uid
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @param message other message
|
|
|
|
* @return imapUid comparison result
|
|
|
|
*/
|
2009-11-02 04:53:42 -05:00
|
|
|
public int compareTo(Message message) {
|
|
|
|
long compareValue = (imapUid - message.imapUid);
|
2009-03-03 10:59:06 -05:00
|
|
|
if (compareValue > 0) {
|
|
|
|
return 1;
|
|
|
|
} else if (compareValue < 0) {
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
2009-02-02 02:19:57 -05:00
|
|
|
}
|
2009-02-12 09:31:11 -05:00
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Override equals, compare IMAP uids
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @param message other message
|
|
|
|
* @return true if IMAP uids are equal
|
|
|
|
*/
|
2009-02-12 09:31:11 -05:00
|
|
|
@Override
|
|
|
|
public boolean equals(Object message) {
|
2009-04-16 17:16:40 -04:00
|
|
|
return message instanceof Message && imapUid == ((Message) message).imapUid;
|
2009-02-12 09:31:11 -05:00
|
|
|
}
|
2009-03-27 09:00:47 -04:00
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
|
|
|
* Override hashCode, return imapUid hashcode.
|
2009-08-04 16:48:35 -04:00
|
|
|
*
|
2009-07-31 02:21:46 -04:00
|
|
|
* @return imapUid hashcode
|
|
|
|
*/
|
2009-03-27 09:00:47 -04:00
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
2009-04-01 11:51:12 -04:00
|
|
|
return (int) (imapUid ^ (imapUid >>> 32));
|
2009-03-27 09:00:47 -04:00
|
|
|
}
|
2009-02-02 02:19:57 -05:00
|
|
|
}
|
|
|
|
|
2009-07-31 02:21:46 -04:00
|
|
|
/**
|
2010-04-06 05:20:09 -04:00
|
|
|
* Message list, includes a single messsage cache
|
2009-07-31 02:21:46 -04:00
|
|
|
*/
|
2009-08-04 16:48:35 -04:00
|
|
|
public static class MessageList extends ArrayList<Message> {
|
2010-04-06 05:20:09 -04:00
|
|
|
/**
|
|
|
|
* Cached message content parsed in a MIME message.
|
|
|
|
*/
|
2010-04-12 04:54:53 -04:00
|
|
|
protected transient MimeMessage cachedMimeMessage;
|
2010-04-06 05:20:09 -04:00
|
|
|
/**
|
|
|
|
* Cached message uid.
|
|
|
|
*/
|
2010-04-12 04:54:53 -04:00
|
|
|
protected transient long cachedMessageImapUid;
|
2010-04-23 09:42:37 -04:00
|
|
|
/**
|
|
|
|
* Cached unparsed message
|
|
|
|
*/
|
|
|
|
protected transient SharedByteArrayInputStream cachedMimeBody;
|
2010-04-06 05:20:09 -04:00
|
|
|
|
2009-08-04 16:48:35 -04:00
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
2010-05-06 15:26:44 -04:00
|
|
|
* Generic folder item.
|
2009-08-13 04:34:51 -04:00
|
|
|
*/
|
2010-05-06 15:26:44 -04:00
|
|
|
public abstract class Item {
|
2008-11-26 19:56:28 -05:00
|
|
|
protected String href;
|
2009-11-13 16:41:43 -05:00
|
|
|
protected String permanentUrl;
|
2010-04-12 04:54:53 -04:00
|
|
|
protected String displayName;
|
2008-11-26 19:56:28 -05:00
|
|
|
protected String etag;
|
2009-11-27 04:34:20 -05:00
|
|
|
protected String contentClass;
|
|
|
|
protected String noneMatch;
|
2010-05-07 04:30:12 -04:00
|
|
|
/**
|
|
|
|
* ICS content
|
|
|
|
*/
|
|
|
|
protected String itemBody;
|
2010-05-06 15:26:44 -04:00
|
|
|
|
|
|
|
/**
|
2010-05-07 04:30:12 -04:00
|
|
|
* Build item instance.
|
|
|
|
*
|
|
|
|
* @param messageUrl message url
|
|
|
|
* @param contentClass content class
|
|
|
|
* @param itemBody item body
|
|
|
|
* @param etag item etag
|
|
|
|
* @param noneMatch none match flag
|
2010-05-06 15:26:44 -04:00
|
|
|
*/
|
2010-05-07 04:30:12 -04:00
|
|
|
public Item(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
|
|
|
|
this.href = messageUrl;
|
|
|
|
this.contentClass = contentClass;
|
|
|
|
this.itemBody = itemBody;
|
|
|
|
this.etag = etag;
|
|
|
|
this.noneMatch = noneMatch;
|
2010-05-06 15:26:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return item content type
|
2010-05-07 04:30:12 -04:00
|
|
|
*
|
2010-05-06 15:26:44 -04:00
|
|
|
* @return content type
|
|
|
|
*/
|
|
|
|
public abstract String getContentType();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve item body from Exchange
|
2010-05-07 04:30:12 -04:00
|
|
|
*
|
2010-05-06 15:26:44 -04:00
|
|
|
* @return item body
|
|
|
|
* @throws HttpException on error
|
|
|
|
*/
|
|
|
|
public abstract String getBody() throws HttpException;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build Item instance from multistatusResponse info
|
2010-05-07 04:30:12 -04:00
|
|
|
*
|
2010-05-06 15:26:44 -04:00
|
|
|
* @param multiStatusResponse response
|
|
|
|
* @throws URIException on error
|
|
|
|
*/
|
|
|
|
public Item(MultiStatusResponse multiStatusResponse) throws URIException {
|
|
|
|
href = URIUtil.decode(multiStatusResponse.getHref());
|
|
|
|
permanentUrl = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "permanenturl", SCHEMAS_EXCHANGE);
|
2010-05-07 04:30:12 -04:00
|
|
|
etag = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "getetag", DAV);
|
|
|
|
displayName = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "displayname", DAV);
|
2010-05-06 15:26:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get event name (file name part in URL).
|
|
|
|
*
|
|
|
|
* @return event name
|
|
|
|
*/
|
|
|
|
public String getName() {
|
|
|
|
int index = href.lastIndexOf('/');
|
|
|
|
if (index >= 0) {
|
|
|
|
return href.substring(index + 1);
|
|
|
|
} else {
|
|
|
|
return href;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get event etag (last change tag).
|
|
|
|
*
|
|
|
|
* @return event etag
|
|
|
|
*/
|
|
|
|
public String getEtag() {
|
|
|
|
return etag;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected HttpException buildHttpException(Exception e) {
|
|
|
|
String message = "Unable to get event " + getName() + " at " + permanentUrl + ": " + e.getMessage();
|
|
|
|
LOGGER.warn(message);
|
|
|
|
return new HttpException(message);
|
|
|
|
}
|
|
|
|
}
|
2010-05-07 04:30:12 -04:00
|
|
|
|
2010-05-06 15:26:44 -04:00
|
|
|
/**
|
|
|
|
* Calendar event object
|
|
|
|
*/
|
|
|
|
public class Contact extends Item {
|
|
|
|
/**
|
|
|
|
* Build Contact instance from multistatusResponse info
|
2010-05-07 04:30:12 -04:00
|
|
|
*
|
2010-05-06 15:26:44 -04:00
|
|
|
* @param multiStatusResponse response
|
|
|
|
* @throws URIException on error
|
|
|
|
*/
|
|
|
|
public Contact(MultiStatusResponse multiStatusResponse) throws URIException {
|
|
|
|
super(multiStatusResponse);
|
|
|
|
}
|
|
|
|
|
2010-05-07 04:30:12 -04:00
|
|
|
/**
|
|
|
|
* {@inheritDoc}
|
|
|
|
*/
|
|
|
|
public Contact(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
|
|
|
|
super(messageUrl.endsWith(".vcf") ? messageUrl.substring(0, messageUrl.length() - 3) + "EML" : messageUrl, contentClass, itemBody, etag, noneMatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert EML extension to vcf.
|
|
|
|
*
|
|
|
|
* @return item name
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public String getName() {
|
|
|
|
String name = super.getName();
|
|
|
|
if (name.endsWith(".EML")) {
|
|
|
|
name = name.substring(0, name.length() - 3) + "vcf";
|
|
|
|
}
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute vcard uid from name.
|
|
|
|
*
|
|
|
|
* @return uid
|
|
|
|
* @throws URIException on error
|
|
|
|
*/
|
|
|
|
protected String getUid() throws URIException {
|
|
|
|
String uid = getName();
|
|
|
|
int dotIndex = uid.lastIndexOf('.');
|
|
|
|
if (dotIndex > 0) {
|
|
|
|
uid = uid.substring(0, dotIndex);
|
|
|
|
}
|
|
|
|
return URIUtil.encodePath(uid);
|
|
|
|
}
|
|
|
|
|
2010-05-06 15:26:44 -04:00
|
|
|
@Override
|
|
|
|
public String getContentType() {
|
|
|
|
return "text/vcard";
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getBody() throws HttpException {
|
|
|
|
// first retrieve contact details
|
|
|
|
String result = null;
|
|
|
|
|
|
|
|
PropFindMethod propFindMethod = null;
|
|
|
|
try {
|
|
|
|
propFindMethod = new PropFindMethod(URIUtil.encodePath(permanentUrl));
|
|
|
|
DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propFindMethod);
|
|
|
|
MultiStatus responses = propFindMethod.getResponseBodyAsMultiStatus();
|
|
|
|
if (responses.getResponses().length > 0) {
|
|
|
|
DavPropertySet properties = responses.getResponses()[0].getProperties(HttpStatus.SC_OK);
|
|
|
|
|
|
|
|
ICSBufferedWriter writer = new ICSBufferedWriter();
|
|
|
|
writer.writeLine("BEGIN:VCARD");
|
|
|
|
writer.writeLine("VERSION:3.0");
|
|
|
|
writer.write("UID:");
|
2010-05-07 04:30:12 -04:00
|
|
|
writer.writeLine(getUid());
|
2010-05-06 15:26:44 -04:00
|
|
|
writer.write("FN:");
|
|
|
|
writer.writeLine(getPropertyIfExists(properties, DavPropertyName.create("cn", URN_SCHEMAS_CONTACTS), ""));
|
|
|
|
// RFC 2426: Family Name, Given Name, Additional Names, Honorific Prefixes, and Honorific Suffixes
|
|
|
|
writer.write("N:");
|
|
|
|
writer.write(getPropertyIfExists(properties, DavPropertyName.create("sn", URN_SCHEMAS_CONTACTS), ""));
|
|
|
|
writer.write(";");
|
|
|
|
writer.write(getPropertyIfExists(properties, DavPropertyName.create("givenName", URN_SCHEMAS_CONTACTS), ""));
|
|
|
|
writer.write(";");
|
|
|
|
writer.writeLine(getPropertyIfExists(properties, DavPropertyName.create("middlename", URN_SCHEMAS_CONTACTS), ""));
|
|
|
|
|
|
|
|
writer.write("TEL;TYPE=cell:");
|
|
|
|
writer.writeLine(getPropertyIfExists(properties, DavPropertyName.create("mobile", URN_SCHEMAS_CONTACTS), ""));
|
2010-05-07 06:03:49 -04:00
|
|
|
writer.write("TEL;TYPE=work:");
|
|
|
|
writer.writeLine(getPropertyIfExists(properties, DavPropertyName.create("telephoneNumber", URN_SCHEMAS_CONTACTS), ""));
|
2010-05-06 15:26:44 -04:00
|
|
|
//writer.writeLine(getPropertyIfExists(properties, DavPropertyName.create("initials", URN_SCHEMAS_CONTACTS), ""));
|
|
|
|
|
|
|
|
// The structured type value corresponds, in sequence, to the post office box; the extended address;
|
|
|
|
// the street address; the locality (e.g., city); the region (e.g., state or province);
|
|
|
|
// the postal code; the country name
|
|
|
|
// ADR;TYPE=dom,home,postal,parcel:;;123 Main Street;Any Town;CA;91921-1234
|
|
|
|
writer.write("ADR;TYPE=home:;;");
|
|
|
|
writer.write(getPropertyIfExists(properties, DavPropertyName.create("homepostaladdress", URN_SCHEMAS_CONTACTS), ""));
|
|
|
|
writer.write(";;;");
|
|
|
|
writer.newLine();
|
|
|
|
writer.writeLine("END:VCARD");
|
|
|
|
result = writer.toString();
|
|
|
|
|
|
|
|
}
|
|
|
|
} catch (DavException e) {
|
|
|
|
throw buildHttpException(e);
|
|
|
|
} catch (IOException e) {
|
|
|
|
throw buildHttpException(e);
|
|
|
|
} finally {
|
|
|
|
if (propFindMethod != null) {
|
2010-05-07 04:30:12 -04:00
|
|
|
propFindMethod.releaseConnection();
|
2010-05-06 15:26:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
2010-05-07 04:30:12 -04:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
protected List<DavProperty> buildProperties() throws IOException {
|
|
|
|
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("contentclass", DAV), contentClass));
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("outlookmessageclass", SCHEMAS_EXCHANGE), "IPM.Contact"));
|
|
|
|
|
|
|
|
ICSBufferedReader reader = new ICSBufferedReader(new StringReader(itemBody));
|
|
|
|
String line;
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
int index = line.indexOf(':');
|
|
|
|
if (index >= 0) {
|
|
|
|
String key = line.substring(0, index);
|
|
|
|
String value = line.substring(index + 1);
|
|
|
|
if ("FN".equals(key)) {
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("cn", URN_SCHEMAS_CONTACTS), value));
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("subject", URN_SCHEMAS_HTTPMAIL), value));
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("fileas", URN_SCHEMAS_CONTACTS), value));
|
|
|
|
|
|
|
|
} else if ("N".equals(key)) {
|
|
|
|
String[] values = value.split(";");
|
|
|
|
if (values.length > 0) {
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("sn", URN_SCHEMAS_CONTACTS), values[0]));
|
|
|
|
}
|
|
|
|
if (values.length > 1) {
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("givenName", URN_SCHEMAS_CONTACTS), values[1]));
|
|
|
|
}
|
|
|
|
} else if ("TEL;TYPE=cell".equals(key)) {
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("mobile", URN_SCHEMAS_CONTACTS), value));
|
|
|
|
} else if ("TEL;TYPE=work".equals(key)) {
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("telephoneNumber", URN_SCHEMAS_CONTACTS), value));
|
|
|
|
} else if ("TEL;TYPE=home".equals(key)) {
|
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("homePhone", URN_SCHEMAS_CONTACTS), value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected ItemResult createOrUpdate() throws IOException {
|
|
|
|
int status = 0;
|
|
|
|
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), buildProperties());
|
|
|
|
propPatchMethod.setRequestHeader("Translate", "f");
|
|
|
|
if (etag != null) {
|
|
|
|
propPatchMethod.setRequestHeader("If-Match", etag);
|
|
|
|
}
|
|
|
|
if (noneMatch != null) {
|
|
|
|
propPatchMethod.setRequestHeader("If-None-Match", noneMatch);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
status = httpClient.executeMethod(propPatchMethod);
|
|
|
|
if (status == HttpStatus.SC_MULTI_STATUS) {
|
2010-05-07 06:03:49 -04:00
|
|
|
MultiStatus responses = propPatchMethod.getResponseBodyAsMultiStatus();
|
|
|
|
if (responses.getResponses().length > 0) {
|
|
|
|
status = responses.getResponses()[0].getStatus()[0].getStatusCode();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status == HttpStatus.SC_CREATED) {
|
|
|
|
LOGGER.debug("Created contact " + href);
|
2010-05-07 04:30:12 -04:00
|
|
|
} else {
|
2010-05-07 06:03:49 -04:00
|
|
|
LOGGER.debug("Updated contact " + href);
|
2010-05-07 04:30:12 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LOGGER.warn("Unable to create or update contact " + status + ' ' + propPatchMethod.getStatusLine());
|
|
|
|
}
|
2010-05-07 06:03:49 -04:00
|
|
|
} catch (DavException e) {
|
|
|
|
LOGGER.error("Error in item create or update", e);
|
|
|
|
throw new IOException(e);
|
2010-05-07 04:30:12 -04:00
|
|
|
} finally {
|
|
|
|
propPatchMethod.releaseConnection();
|
|
|
|
}
|
|
|
|
ItemResult itemResult = new ItemResult();
|
|
|
|
// 440 means forbidden on Exchange
|
|
|
|
if (status == 440) {
|
|
|
|
status = HttpStatus.SC_FORBIDDEN;
|
|
|
|
}
|
|
|
|
itemResult.status = status;
|
2010-05-07 06:03:49 -04:00
|
|
|
// need to retrieve new etag
|
|
|
|
HeadMethod headMethod = new HeadMethod(URIUtil.encodePath(href));
|
|
|
|
try {
|
|
|
|
httpClient.executeMethod(headMethod);
|
|
|
|
if (headMethod.getResponseHeader("ETag") != null) {
|
|
|
|
itemResult.etag = headMethod.getResponseHeader("ETag").getValue();
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
headMethod.releaseConnection();
|
2010-05-07 04:30:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return itemResult;
|
|
|
|
|
2010-05-06 15:26:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-05-07 04:30:12 -04:00
|
|
|
* Calendar event object.
|
2010-05-06 15:26:44 -04:00
|
|
|
*/
|
|
|
|
public class Event extends Item {
|
2009-11-27 04:34:20 -05:00
|
|
|
/**
|
2010-05-07 04:30:12 -04:00
|
|
|
* Build Event instance from response info.
|
|
|
|
*
|
2010-05-06 15:26:44 -04:00
|
|
|
* @param multiStatusResponse response
|
|
|
|
* @throws URIException on error
|
|
|
|
*/
|
|
|
|
public Event(MultiStatusResponse multiStatusResponse) throws URIException {
|
|
|
|
super(multiStatusResponse);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-05-07 04:30:12 -04:00
|
|
|
* {@inheritDoc}
|
2010-05-06 15:26:44 -04:00
|
|
|
*/
|
2010-05-07 04:30:12 -04:00
|
|
|
public Event(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
|
|
|
|
super(messageUrl, contentClass, itemBody, etag, noneMatch);
|
2010-05-06 15:26:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getContentType() {
|
|
|
|
return "text/calendar;charset=UTF-8";
|
|
|
|
}
|
|
|
|
|
2009-11-29 16:10:01 -05:00
|
|
|
protected boolean isCalendarContentType(String contentType) {
|
2009-12-07 05:44:17 -05:00
|
|
|
return contentType.startsWith("text/calendar") || contentType.startsWith("application/ics");
|
2009-11-29 16:10:01 -05:00
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
|
2009-03-04 13:20:35 -05:00
|
|
|
protected MimePart getCalendarMimePart(MimeMultipart multiPart) throws IOException, MessagingException {
|
|
|
|
MimePart bodyPart = null;
|
|
|
|
for (int i = 0; i < multiPart.getCount(); i++) {
|
|
|
|
String contentType = multiPart.getBodyPart(i).getContentType();
|
2009-11-29 16:10:01 -05:00
|
|
|
if (isCalendarContentType(contentType)) {
|
2009-04-16 17:52:17 -04:00
|
|
|
bodyPart = (MimePart) multiPart.getBodyPart(i);
|
2009-03-04 13:20:35 -05:00
|
|
|
break;
|
|
|
|
} else if (contentType.startsWith("multipart")) {
|
|
|
|
Object content = multiPart.getBodyPart(i).getContent();
|
|
|
|
if (content instanceof MimeMultipart) {
|
|
|
|
bodyPart = getCalendarMimePart((MimeMultipart) content);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bodyPart;
|
|
|
|
}
|
|
|
|
|
2009-11-27 18:27:50 -05:00
|
|
|
/**
|
|
|
|
* Load ICS content from MIME message input stream
|
|
|
|
*
|
|
|
|
* @param mimeInputStream mime message input stream
|
|
|
|
* @return mime message ics attachment body
|
|
|
|
* @throws IOException on error
|
|
|
|
* @throws MessagingException on error
|
|
|
|
*/
|
|
|
|
protected String getICS(InputStream mimeInputStream) throws IOException, MessagingException {
|
2009-12-07 05:44:17 -05:00
|
|
|
String result;
|
2009-11-27 18:27:50 -05:00
|
|
|
MimeMessage mimeMessage = new MimeMessage(null, mimeInputStream);
|
|
|
|
Object mimeBody = mimeMessage.getContent();
|
2009-11-29 16:10:01 -05:00
|
|
|
MimePart bodyPart = null;
|
2009-11-27 18:27:50 -05:00
|
|
|
if (mimeBody instanceof MimeMultipart) {
|
|
|
|
bodyPart = getCalendarMimePart((MimeMultipart) mimeBody);
|
2009-12-07 05:44:17 -05:00
|
|
|
} else if (isCalendarContentType(mimeMessage.getContentType())) {
|
2009-11-27 18:27:50 -05:00
|
|
|
// no multipart, single body
|
|
|
|
bodyPart = mimeMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bodyPart != null) {
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
bodyPart.getDataHandler().writeTo(baos);
|
|
|
|
baos.close();
|
|
|
|
result = fixICS(new String(baos.toByteArray(), "UTF-8"), true);
|
2009-11-29 16:10:01 -05:00
|
|
|
} else {
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
2010-04-15 08:56:55 -04:00
|
|
|
mimeMessage.writeTo(baos);
|
2009-11-29 16:10:01 -05:00
|
|
|
baos.close();
|
|
|
|
throw new DavMailException("EXCEPTION_INVALID_MESSAGE_CONTENT", new String(baos.toByteArray(), "UTF-8"));
|
2009-11-27 18:27:50 -05:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected String getICSFromInternetContentProperty() throws IOException, DavException, MessagingException {
|
|
|
|
String result = null;
|
|
|
|
// PropFind PR_INTERNET_CONTENT
|
|
|
|
DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
|
|
|
|
davPropertyNameSet.add(PR_INTERNET_CONTENT);
|
|
|
|
PropFindMethod propFindMethod = new PropFindMethod(URIUtil.encodePath(permanentUrl), davPropertyNameSet, 0);
|
|
|
|
try {
|
|
|
|
DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propFindMethod);
|
|
|
|
MultiStatus responses = propFindMethod.getResponseBodyAsMultiStatus();
|
|
|
|
if (responses.getResponses().length > 0) {
|
|
|
|
DavPropertySet properties = responses.getResponses()[0].getProperties(HttpStatus.SC_OK);
|
|
|
|
DavProperty property = properties.get(PR_INTERNET_CONTENT);
|
|
|
|
if (property != null) {
|
|
|
|
byte[] byteArray = Base64.decodeBase64(((String) property.getValue()).getBytes());
|
|
|
|
result = getICS(new ByteArrayInputStream(byteArray));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
propFindMethod.releaseConnection();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Load ICS content from Exchange server.
|
|
|
|
* User Translate: f header to get MIME event content and get ICS attachment from it
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @return ICS (iCalendar) event
|
2010-02-19 18:21:16 -05:00
|
|
|
* @throws HttpException on error
|
2009-08-13 04:34:51 -04:00
|
|
|
*/
|
2010-05-06 15:26:44 -04:00
|
|
|
@Override
|
|
|
|
public String getBody() throws HttpException {
|
2009-12-07 16:57:56 -05:00
|
|
|
String result;
|
2009-11-13 16:41:43 -05:00
|
|
|
LOGGER.debug("Get event: " + permanentUrl);
|
2009-11-27 18:27:50 -05:00
|
|
|
// try to get PR_INTERNET_CONTENT
|
2008-11-26 19:56:28 -05:00
|
|
|
try {
|
2009-11-27 18:27:50 -05:00
|
|
|
result = getICSFromInternetContentProperty();
|
|
|
|
if (result == null) {
|
|
|
|
GetMethod method = new GetMethod(permanentUrl);
|
|
|
|
method.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
|
|
|
|
method.setRequestHeader("Translate", "f");
|
|
|
|
try {
|
2010-01-27 16:54:30 -05:00
|
|
|
DavGatewayHttpClientFacade.executeGetMethod(httpClient, method, true);
|
2009-11-27 18:27:50 -05:00
|
|
|
result = getICS(method.getResponseBodyAsStream());
|
|
|
|
} finally {
|
|
|
|
method.releaseConnection();
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-11-27 18:27:50 -05:00
|
|
|
} catch (DavException e) {
|
2009-12-07 16:57:56 -05:00
|
|
|
throw buildHttpException(e);
|
2009-09-17 17:40:47 -04:00
|
|
|
} catch (IOException e) {
|
2009-12-07 16:57:56 -05:00
|
|
|
throw buildHttpException(e);
|
2009-02-25 05:23:07 -05:00
|
|
|
} catch (MessagingException e) {
|
2009-12-07 16:57:56 -05:00
|
|
|
throw buildHttpException(e);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-02-25 05:23:07 -05:00
|
|
|
return result;
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
2009-11-27 04:34:20 -05:00
|
|
|
protected String fixTimezoneId(String line, String validTimezoneId) {
|
|
|
|
return StringUtil.replaceToken(line, "TZID=", ":", validTimezoneId);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void splitExDate(ICSBufferedWriter result, String line) {
|
|
|
|
int cur = line.lastIndexOf(':') + 1;
|
|
|
|
String start = line.substring(0, cur);
|
|
|
|
|
|
|
|
for (int next = line.indexOf(',', cur); next != -1; next = line.indexOf(',', cur)) {
|
|
|
|
String val = line.substring(cur, next);
|
|
|
|
result.writeLine(start + val);
|
|
|
|
|
|
|
|
cur = next + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.writeLine(start + line.substring(cur));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String getAllDayLine(String line) throws IOException {
|
|
|
|
int valueIndex = line.lastIndexOf(':');
|
|
|
|
int valueEndIndex = line.lastIndexOf('T');
|
|
|
|
if (valueIndex < 0 || valueEndIndex < 0) {
|
|
|
|
throw new DavMailException("EXCEPTION_INVALID_ICS_LINE", line);
|
|
|
|
}
|
2010-04-15 08:56:55 -04:00
|
|
|
int keyIndex = line.indexOf(';');
|
|
|
|
if (keyIndex == -1) {
|
|
|
|
keyIndex = valueIndex;
|
|
|
|
}
|
2009-11-27 04:34:20 -05:00
|
|
|
String dateValue = line.substring(valueIndex + 1, valueEndIndex);
|
2010-03-19 06:32:29 -04:00
|
|
|
String key = line.substring(0, Math.min(keyIndex, valueIndex));
|
2009-11-27 04:34:20 -05:00
|
|
|
return key + ";VALUE=DATE:" + dateValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String fixICS(String icsBody, boolean fromServer) throws IOException {
|
|
|
|
// first pass : detect
|
|
|
|
class AllDayState {
|
|
|
|
boolean isAllDay;
|
|
|
|
boolean hasCdoAllDay;
|
|
|
|
boolean isCdoAllDay;
|
|
|
|
}
|
|
|
|
|
|
|
|
dumpIndex++;
|
|
|
|
dumpICS(icsBody, fromServer, false);
|
|
|
|
|
|
|
|
// Convert event class from and to iCal
|
|
|
|
// See https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt
|
|
|
|
boolean isAppleiCal = false;
|
|
|
|
boolean hasAttendee = false;
|
|
|
|
boolean hasCdoBusyStatus = false;
|
|
|
|
// detect ics event with empty timezone (all day from Lightning)
|
|
|
|
boolean hasTimezone = false;
|
|
|
|
String transp = null;
|
|
|
|
String validTimezoneId = null;
|
|
|
|
String eventClass = null;
|
|
|
|
String organizer = null;
|
|
|
|
String action = null;
|
|
|
|
boolean sound = false;
|
|
|
|
|
|
|
|
List<AllDayState> allDayStates = new ArrayList<AllDayState>();
|
|
|
|
AllDayState currentAllDayState = new AllDayState();
|
|
|
|
BufferedReader reader = null;
|
|
|
|
try {
|
|
|
|
reader = new ICSBufferedReader(new StringReader(icsBody));
|
|
|
|
String line;
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
int index = line.indexOf(':');
|
|
|
|
if (index >= 0) {
|
|
|
|
String key = line.substring(0, index);
|
|
|
|
String value = line.substring(index + 1);
|
|
|
|
if ("DTSTART;VALUE=DATE".equals(key)) {
|
|
|
|
currentAllDayState.isAllDay = true;
|
|
|
|
} else if ("X-MICROSOFT-CDO-ALLDAYEVENT".equals(key)) {
|
|
|
|
currentAllDayState.hasCdoAllDay = true;
|
|
|
|
currentAllDayState.isCdoAllDay = "TRUE".equals(value);
|
|
|
|
} else if ("END:VEVENT".equals(line)) {
|
|
|
|
allDayStates.add(currentAllDayState);
|
|
|
|
currentAllDayState = new AllDayState();
|
|
|
|
} else if ("PRODID".equals(key) && line.contains("iCal")) {
|
|
|
|
// detect iCal created events
|
|
|
|
isAppleiCal = true;
|
|
|
|
} else if (isAppleiCal && "X-CALENDARSERVER-ACCESS".equals(key)) {
|
|
|
|
eventClass = value;
|
|
|
|
} else if (!isAppleiCal && "CLASS".equals(key)) {
|
|
|
|
eventClass = value;
|
|
|
|
} else if ("ACTION".equals(key)) {
|
|
|
|
action = value;
|
|
|
|
} else if ("ATTACH;VALUES=URI".equals(key)) {
|
|
|
|
// This is a marker that this event has an alarm with sound
|
|
|
|
sound = true;
|
|
|
|
} else if (key.startsWith("ORGANIZER")) {
|
|
|
|
if (value.startsWith("MAILTO:")) {
|
|
|
|
organizer = value.substring(7);
|
|
|
|
} else {
|
|
|
|
organizer = value;
|
|
|
|
}
|
|
|
|
} else if (key.startsWith("ATTENDEE")) {
|
|
|
|
hasAttendee = true;
|
|
|
|
} else if ("TRANSP".equals(key)) {
|
|
|
|
transp = value;
|
|
|
|
} else if (line.startsWith("TZID:(GMT") ||
|
|
|
|
// additional test for Outlook created recurring events
|
|
|
|
line.startsWith("TZID:GMT ")) {
|
|
|
|
try {
|
|
|
|
validTimezoneId = ResourceBundle.getBundle("timezones").getString(value);
|
|
|
|
} catch (MissingResourceException mre) {
|
|
|
|
LOGGER.warn(new BundleMessage("LOG_INVALID_TIMEZONE", value));
|
|
|
|
}
|
|
|
|
} else if ("X-MICROSOFT-CDO-BUSYSTATUS".equals(key)) {
|
|
|
|
hasCdoBusyStatus = true;
|
|
|
|
} else if ("BEGIN:VTIMEZONE".equals(line)) {
|
|
|
|
hasTimezone = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (reader != null) {
|
|
|
|
reader.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// second pass : fix
|
|
|
|
int count = 0;
|
|
|
|
ICSBufferedWriter result = new ICSBufferedWriter();
|
|
|
|
try {
|
|
|
|
reader = new ICSBufferedReader(new StringReader(icsBody));
|
|
|
|
String line;
|
|
|
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
// remove empty properties
|
|
|
|
if ("CLASS:".equals(line) || "LOCATION:".equals(line)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// fix invalid exchange timezoneid
|
|
|
|
if (validTimezoneId != null && line.indexOf(";TZID=") >= 0) {
|
|
|
|
line = fixTimezoneId(line, validTimezoneId);
|
|
|
|
}
|
2010-06-04 13:38:15 -04:00
|
|
|
if (fromServer && line.startsWith("PRODID:") && eventClass != null) {
|
|
|
|
result.writeLine(line);
|
|
|
|
// set global calendarserver access for iCal 4
|
|
|
|
if ("PRIVATE".equalsIgnoreCase(eventClass)) {
|
|
|
|
result.writeLine("X-CALENDARSERVER-ACCESS:CONFIDENTIAL");
|
|
|
|
} else if ("CONFIDENTIAL".equalsIgnoreCase(eventClass)) {
|
|
|
|
result.writeLine("X-CALENDARSERVER-ACCESS:PRIVATE");
|
|
|
|
} else if (eventClass != null) {
|
|
|
|
result.writeLine("X-CALENDARSERVER-ACCESS:" + eventClass);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2009-11-27 04:34:20 -05:00
|
|
|
if (!fromServer && "BEGIN:VEVENT".equals(line) && !hasTimezone) {
|
|
|
|
result.write(ExchangeSession.this.getVTimezone().timezoneBody);
|
|
|
|
hasTimezone = true;
|
|
|
|
}
|
|
|
|
if (!fromServer && currentAllDayState.isAllDay && "X-MICROSOFT-CDO-ALLDAYEVENT:FALSE".equals(line)) {
|
|
|
|
line = "X-MICROSOFT-CDO-ALLDAYEVENT:TRUE";
|
|
|
|
} else if (!fromServer && "END:VEVENT".equals(line)) {
|
|
|
|
if (!hasCdoBusyStatus) {
|
|
|
|
result.writeLine("X-MICROSOFT-CDO-BUSYSTATUS:" + (!"TRANSPARENT".equals(transp) ? "BUSY" : "FREE"));
|
|
|
|
}
|
|
|
|
if (currentAllDayState.isAllDay && !currentAllDayState.hasCdoAllDay) {
|
|
|
|
result.writeLine("X-MICROSOFT-CDO-ALLDAYEVENT:TRUE");
|
|
|
|
}
|
|
|
|
// add organizer line to all events created in Exchange for active sync
|
|
|
|
if (organizer == null) {
|
|
|
|
result.writeLine("ORGANIZER:MAILTO:" + email);
|
|
|
|
}
|
2010-06-04 13:38:15 -04:00
|
|
|
if (isAppleiCal) {
|
|
|
|
if ("CONFIDENTIAL".equalsIgnoreCase(eventClass)) {
|
|
|
|
result.writeLine("CLASS:PRIVATE");
|
|
|
|
} else if ("PRIVATE".equalsIgnoreCase(eventClass)) {
|
|
|
|
result.writeLine("CLASS:CONFIDENTIAL");
|
|
|
|
} else {
|
|
|
|
result.writeLine("CLASS:" + eventClass);
|
|
|
|
}
|
|
|
|
}
|
2009-11-27 04:34:20 -05:00
|
|
|
} else if (!fromServer && line.startsWith("X-MICROSOFT-CDO-BUSYSTATUS:")) {
|
|
|
|
line = "X-MICROSOFT-CDO-BUSYSTATUS:" + (!"TRANSPARENT".equals(transp) ? "BUSY" : "FREE");
|
|
|
|
} else if (!fromServer && !currentAllDayState.isAllDay && "X-MICROSOFT-CDO-ALLDAYEVENT:TRUE".equals(line)) {
|
|
|
|
line = "X-MICROSOFT-CDO-ALLDAYEVENT:FALSE";
|
|
|
|
} else if (fromServer && currentAllDayState.isCdoAllDay && line.startsWith("DTSTART") && !line.startsWith("DTSTART;VALUE=DATE")) {
|
|
|
|
line = getAllDayLine(line);
|
|
|
|
} else if (fromServer && currentAllDayState.isCdoAllDay && line.startsWith("DTEND") && !line.startsWith("DTEND;VALUE=DATE")) {
|
|
|
|
line = getAllDayLine(line);
|
|
|
|
} else if (!fromServer && currentAllDayState.isAllDay && line.startsWith("DTSTART") && line.startsWith("DTSTART;VALUE=DATE")) {
|
|
|
|
line = "DTSTART;TZID=\"" + ExchangeSession.this.getVTimezone().timezoneId + "\":" + line.substring(19) + "T000000";
|
|
|
|
} else if (!fromServer && currentAllDayState.isAllDay && line.startsWith("DTEND") && line.startsWith("DTEND;VALUE=DATE")) {
|
|
|
|
line = "DTEND;TZID=\"" + ExchangeSession.this.getVTimezone().timezoneId + "\":" + line.substring(17) + "T000000";
|
|
|
|
} else if (line.startsWith("TZID:") && validTimezoneId != null) {
|
|
|
|
line = "TZID:" + validTimezoneId;
|
|
|
|
} else if ("BEGIN:VEVENT".equals(line)) {
|
|
|
|
currentAllDayState = allDayStates.get(count++);
|
2010-06-07 05:07:31 -04:00
|
|
|
// remove calendarserver access
|
2009-11-27 04:34:20 -05:00
|
|
|
} else if (line.startsWith("X-CALENDARSERVER-ACCESS:")) {
|
2010-06-04 13:38:15 -04:00
|
|
|
continue;
|
2009-11-27 04:34:20 -05:00
|
|
|
} else if (line.startsWith("EXDATE;TZID=") || line.startsWith("EXDATE:")) {
|
|
|
|
// Apple iCal doesn't support EXDATE with multiple exceptions
|
|
|
|
// on one line. Split into multiple EXDATE entries (which is
|
|
|
|
// also legal according to the caldav standard).
|
|
|
|
splitExDate(result, line);
|
|
|
|
continue;
|
|
|
|
} else if (line.startsWith("X-ENTOURAGE_UUID:")) {
|
|
|
|
// Apple iCal doesn't understand this key, and it's entourage
|
|
|
|
// specific (i.e. not needed by any caldav client): strip it out
|
|
|
|
continue;
|
|
|
|
} else if (fromServer && line.startsWith("ATTENDEE;")
|
|
|
|
&& (line.indexOf(email) >= 0)) {
|
|
|
|
// If this is coming from the server, strip out RSVP for this
|
|
|
|
// user as an attendee where the partstat is something other
|
|
|
|
// than PARTSTAT=NEEDS-ACTION since the RSVP confuses iCal4 into
|
|
|
|
// thinking the attendee has not replied
|
|
|
|
|
|
|
|
int rsvpSuffix = line.indexOf("RSVP=TRUE;");
|
|
|
|
int rsvpPrefix = line.indexOf(";RSVP=TRUE");
|
|
|
|
|
|
|
|
if (((rsvpSuffix >= 0) || (rsvpPrefix >= 0))
|
|
|
|
&& (line.indexOf("PARTSTAT=") >= 0)
|
|
|
|
&& (line.indexOf("PARTSTAT=NEEDS-ACTION") < 0)) {
|
|
|
|
|
|
|
|
// Strip out the "RSVP" line from the calendar entry
|
|
|
|
if (rsvpSuffix >= 0) {
|
|
|
|
line = line.substring(0, rsvpSuffix) + line.substring(rsvpSuffix + 10);
|
|
|
|
} else {
|
|
|
|
line = line.substring(0, rsvpPrefix) + line.substring(rsvpPrefix + 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
} else if (line.startsWith("ACTION:")) {
|
2009-12-20 05:52:53 -05:00
|
|
|
if (fromServer && "DISPLAY".equals(action)
|
|
|
|
// convert DISPLAY to AUDIO only if user defined an alarm sound
|
2009-12-21 08:01:49 -05:00
|
|
|
&& Settings.getProperty("davmail.caldavAlarmSound") != null) {
|
2009-12-20 05:52:53 -05:00
|
|
|
// Convert alarm to audio for iCal
|
2009-11-27 04:34:20 -05:00
|
|
|
result.writeLine("ACTION:AUDIO");
|
|
|
|
|
|
|
|
if (!sound) {
|
2009-12-20 05:52:53 -05:00
|
|
|
// Add defined sound into the audio alarm
|
|
|
|
result.writeLine("ATTACH;VALUE=URI:" + Settings.getProperty("davmail.caldavAlarmSound"));
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
} else if (!fromServer && "AUDIO".equals(action)) {
|
|
|
|
// Use the alarm action that exchange (and blackberry) understand
|
|
|
|
// (exchange and blackberry don't understand audio actions)
|
|
|
|
|
|
|
|
result.writeLine("ACTION:DISPLAY");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't recognize this type of action: pass it through
|
|
|
|
|
|
|
|
} else if (line.startsWith("CLASS:")) {
|
2010-06-04 13:38:15 -04:00
|
|
|
if (!fromServer && isAppleiCal) {
|
2009-11-27 04:34:20 -05:00
|
|
|
continue;
|
|
|
|
} else {
|
2010-06-04 13:38:15 -04:00
|
|
|
// still set calendarserver access inside event for iCal 3
|
2009-11-27 04:34:20 -05:00
|
|
|
if ("PRIVATE".equalsIgnoreCase(eventClass)) {
|
|
|
|
result.writeLine("X-CALENDARSERVER-ACCESS:CONFIDENTIAL");
|
|
|
|
} else if ("CONFIDENTIAL".equalsIgnoreCase(eventClass)) {
|
|
|
|
result.writeLine("X-CALENDARSERVER-ACCESS:PRIVATE");
|
|
|
|
} else {
|
|
|
|
result.writeLine("X-CALENDARSERVER-ACCESS:" + eventClass);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// remove organizer line if user is organizer for iPhone
|
|
|
|
} else if (fromServer && line.startsWith("ORGANIZER") && !hasAttendee) {
|
|
|
|
continue;
|
|
|
|
} else if (organizer != null && line.startsWith("ATTENDEE") && line.contains(organizer)) {
|
|
|
|
// Ignore organizer as attendee
|
|
|
|
continue;
|
|
|
|
} else if (!fromServer && line.startsWith("ATTENDEE")) {
|
|
|
|
line = replaceIcal4Principal(line);
|
|
|
|
}
|
|
|
|
|
|
|
|
result.writeLine(line);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
reader.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
String resultString = result.toString();
|
|
|
|
dumpICS(resultString, fromServer, true);
|
|
|
|
|
2010-03-21 16:40:14 -04:00
|
|
|
return resultString;
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
protected void dumpICS(String icsBody, boolean fromServer, boolean after) {
|
|
|
|
String logFileDirectory = Settings.getLogFileDirectory();
|
|
|
|
|
|
|
|
// additional setting to activate ICS dump (not available in GUI)
|
|
|
|
int dumpMax = Settings.getIntProperty("davmail.dumpICS");
|
|
|
|
if (dumpMax > 0) {
|
|
|
|
if (dumpIndex > dumpMax) {
|
|
|
|
// Delete the oldest dump file
|
|
|
|
final int oldest = dumpIndex - dumpMax;
|
|
|
|
try {
|
|
|
|
File[] oldestFiles = (new File(logFileDirectory)).listFiles(new FilenameFilter() {
|
|
|
|
public boolean accept(File dir, String name) {
|
|
|
|
if (name.endsWith(".ics")) {
|
|
|
|
int dashIndex = name.indexOf('-');
|
|
|
|
if (dashIndex > 0) {
|
|
|
|
try {
|
|
|
|
int fileIndex = Integer.parseInt(name.substring(0, dashIndex));
|
|
|
|
return fileIndex < oldest;
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
// ignore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
for (File file : oldestFiles) {
|
|
|
|
if (!file.delete()) {
|
|
|
|
LOGGER.warn("Unable to delete " + file.getAbsolutePath());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Exception ex) {
|
|
|
|
LOGGER.warn("Error deleting ics dump: " + ex.getMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StringBuilder filePath = new StringBuilder();
|
|
|
|
filePath.append(logFileDirectory).append('/')
|
|
|
|
.append(dumpIndex)
|
|
|
|
.append(after ? "-to" : "-from")
|
|
|
|
.append((after ^ fromServer) ? "-server" : "-client")
|
|
|
|
.append(".ics");
|
|
|
|
if ((icsBody != null) && (icsBody.length() > 0)) {
|
|
|
|
FileWriter fileWriter = null;
|
|
|
|
try {
|
|
|
|
fileWriter = new FileWriter(filePath.toString());
|
|
|
|
fileWriter.write(icsBody);
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.error(e);
|
|
|
|
} finally {
|
|
|
|
if (fileWriter != null) {
|
|
|
|
try {
|
|
|
|
fileWriter.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String getICSValue(String icsBody, String prefix, String defval) throws IOException {
|
|
|
|
// only return values in VEVENT section, not VALARM
|
|
|
|
Stack<String> sectionStack = new Stack<String>();
|
|
|
|
BufferedReader reader = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
reader = new ICSBufferedReader(new StringReader(icsBody));
|
|
|
|
String line;
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
if (line.startsWith("BEGIN:")) {
|
|
|
|
sectionStack.push(line);
|
|
|
|
} else if (line.startsWith("END:") && !sectionStack.isEmpty()) {
|
|
|
|
sectionStack.pop();
|
|
|
|
} else if (!sectionStack.isEmpty() && "BEGIN:VEVENT".equals(sectionStack.peek()) && line.startsWith(prefix)) {
|
|
|
|
return line.substring(prefix.length());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
if (reader != null) {
|
|
|
|
reader.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return defval;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String getICSSummary(String icsBody) throws IOException {
|
|
|
|
return getICSValue(icsBody, "SUMMARY:", BundleMessage.format("MEETING_REQUEST"));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String getICSDescription(String icsBody) throws IOException {
|
|
|
|
return getICSValue(icsBody, "DESCRIPTION:", "");
|
|
|
|
}
|
|
|
|
|
|
|
|
class Participants {
|
|
|
|
String attendees;
|
2009-12-24 10:33:26 -05:00
|
|
|
String optionalAttendees;
|
2009-11-27 04:34:20 -05:00
|
|
|
String organizer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse ics event for attendees and organizer.
|
|
|
|
* For notifications, only include attendees with RSVP=TRUE or PARTSTAT=NEEDS-ACTION
|
|
|
|
*
|
|
|
|
* @param isNotification get only notified attendees
|
|
|
|
* @return participants
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
protected Participants getParticipants(boolean isNotification) throws IOException {
|
|
|
|
HashSet<String> attendees = new HashSet<String>();
|
2009-12-24 10:33:26 -05:00
|
|
|
HashSet<String> optionalAttendees = new HashSet<String>();
|
2009-11-27 04:34:20 -05:00
|
|
|
String organizer = null;
|
|
|
|
BufferedReader reader = null;
|
|
|
|
try {
|
2010-05-07 04:30:12 -04:00
|
|
|
reader = new ICSBufferedReader(new StringReader(itemBody));
|
2009-11-27 04:34:20 -05:00
|
|
|
String line;
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
int index = line.indexOf(':');
|
|
|
|
if (index >= 0) {
|
|
|
|
String key = line.substring(0, index);
|
|
|
|
String value = line.substring(index + 1);
|
|
|
|
int semiColon = key.indexOf(';');
|
|
|
|
if (semiColon >= 0) {
|
|
|
|
key = key.substring(0, semiColon);
|
|
|
|
}
|
|
|
|
if ("ORGANIZER".equals(key) || "ATTENDEE".equals(key)) {
|
|
|
|
int colonIndex = value.indexOf(':');
|
|
|
|
if (colonIndex >= 0) {
|
|
|
|
value = value.substring(colonIndex + 1);
|
|
|
|
}
|
|
|
|
value = replaceIcal4Principal(value);
|
|
|
|
if ("ORGANIZER".equals(key)) {
|
|
|
|
organizer = value;
|
|
|
|
// exclude current user and invalid values from recipients
|
|
|
|
// also exclude no action attendees
|
|
|
|
} else if (!email.equalsIgnoreCase(value) && value.indexOf('@') >= 0
|
2010-05-14 06:52:50 -04:00
|
|
|
// return all attendees for user calendar folder, filter for notifications
|
2009-11-27 04:34:20 -05:00
|
|
|
&& (!isNotification
|
2010-05-14 06:52:50 -04:00
|
|
|
// notify attendee if reply explicitly requested
|
|
|
|
|| (line.indexOf("RSVP=TRUE") >= 0)
|
|
|
|
|| (
|
|
|
|
// workaround for iCal bug: do not notify if reply explicitly not requested
|
|
|
|
!(line.indexOf("RSVP=FALSE") >= 0) &&
|
|
|
|
((line.indexOf("PARTSTAT=NEEDS-ACTION") >= 0
|
|
|
|
// need to include other PARTSTATs participants for CANCEL notifications
|
|
|
|
|| line.indexOf("PARTSTAT=ACCEPTED") >= 0
|
|
|
|
|| line.indexOf("PARTSTAT=DECLINED") >= 0
|
|
|
|
|| line.indexOf("PARTSTAT=TENTATIVE") >= 0))
|
|
|
|
))) {
|
2009-12-24 10:33:26 -05:00
|
|
|
if (line.indexOf("ROLE=OPT-PARTICIPANT") >= 0) {
|
|
|
|
optionalAttendees.add(value);
|
|
|
|
} else {
|
|
|
|
attendees.add(value);
|
|
|
|
}
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (reader != null) {
|
|
|
|
reader.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Participants participants = new Participants();
|
2009-12-24 10:33:26 -05:00
|
|
|
participants.attendees = StringUtil.join(attendees, ", ");
|
|
|
|
participants.optionalAttendees = StringUtil.join(optionalAttendees, ", ");
|
2009-11-27 04:34:20 -05:00
|
|
|
participants.organizer = organizer;
|
|
|
|
return participants;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String getICSMethod(String icsBody) {
|
|
|
|
String icsMethod = StringUtil.getToken(icsBody, "METHOD:", "\r");
|
|
|
|
if (icsMethod == null) {
|
|
|
|
// default method is REQUEST
|
|
|
|
icsMethod = "REQUEST";
|
|
|
|
}
|
|
|
|
return icsMethod;
|
|
|
|
}
|
|
|
|
|
2010-05-07 04:30:12 -04:00
|
|
|
protected ItemResult createOrUpdate() throws IOException {
|
2009-11-27 04:34:20 -05:00
|
|
|
String boundary = UUID.randomUUID().toString();
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
2009-12-24 10:33:26 -05:00
|
|
|
MimeOutputStreamWriter writer = new MimeOutputStreamWriter(baos);
|
2009-11-27 04:34:20 -05:00
|
|
|
int status = 0;
|
|
|
|
PutMethod putmethod = new PutMethod(URIUtil.encodePath(href));
|
|
|
|
putmethod.setRequestHeader("Translate", "f");
|
|
|
|
putmethod.setRequestHeader("Overwrite", "f");
|
|
|
|
if (etag != null) {
|
|
|
|
putmethod.setRequestHeader("If-Match", etag);
|
|
|
|
}
|
|
|
|
if (noneMatch != null) {
|
|
|
|
putmethod.setRequestHeader("If-None-Match", noneMatch);
|
|
|
|
}
|
|
|
|
putmethod.setRequestHeader("Content-Type", "message/rfc822");
|
2010-05-07 04:30:12 -04:00
|
|
|
String method = getICSMethod(itemBody);
|
2009-11-27 04:34:20 -05:00
|
|
|
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("Content-Transfer-Encoding", "7bit");
|
|
|
|
writer.writeHeader("Content-class", contentClass);
|
2009-11-27 04:34:20 -05:00
|
|
|
// append date
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("Date", new Date());
|
|
|
|
|
|
|
|
// Make sure invites have a proper subject line
|
2010-05-07 04:30:12 -04:00
|
|
|
writer.writeHeader("Subject", getICSSummary(itemBody));
|
2009-12-24 10:33:26 -05:00
|
|
|
|
2009-11-27 04:34:20 -05:00
|
|
|
if ("urn:content-classes:calendarmessage".equals(contentClass)) {
|
|
|
|
// need to parse attendees and organizer to build recipients
|
|
|
|
Participants participants = getParticipants(true);
|
|
|
|
if (email.equalsIgnoreCase(participants.organizer)) {
|
|
|
|
// current user is organizer => notify all
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("To", participants.attendees);
|
|
|
|
writer.writeHeader("Cc", participants.optionalAttendees);
|
|
|
|
// do not send notification if no recipients found
|
2010-01-05 05:06:47 -05:00
|
|
|
if (participants.attendees == null && participants.optionalAttendees == null) {
|
|
|
|
status = HttpStatus.SC_NO_CONTENT;
|
|
|
|
}
|
2009-11-27 04:34:20 -05:00
|
|
|
} else {
|
|
|
|
// notify only organizer
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("To", participants.organizer);
|
|
|
|
// do not send notification if no recipients found
|
2010-01-05 05:06:47 -05:00
|
|
|
if (participants.organizer == null) {
|
|
|
|
status = HttpStatus.SC_NO_CONTENT;
|
|
|
|
}
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// need to parse attendees and organizer to build recipients
|
|
|
|
Participants participants = getParticipants(false);
|
|
|
|
// storing appointment, full recipients header
|
|
|
|
if (participants.attendees != null) {
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("To", participants.attendees);
|
2009-11-27 04:34:20 -05:00
|
|
|
} else {
|
2009-12-24 10:33:26 -05:00
|
|
|
// use current user as attendee
|
|
|
|
writer.writeHeader("To", email);
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("Cc", participants.optionalAttendees);
|
2009-11-27 04:34:20 -05:00
|
|
|
|
|
|
|
if (participants.organizer != null) {
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("From", participants.organizer);
|
2009-11-27 04:34:20 -05:00
|
|
|
} else {
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("From", email);
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
|
|
|
// if not organizer, set REPLYTIME to force Outlook in attendee mode
|
|
|
|
if (participants.organizer != null && !email.equalsIgnoreCase(participants.organizer)) {
|
2010-05-07 04:30:12 -04:00
|
|
|
if (itemBody.indexOf("METHOD:") < 0) {
|
|
|
|
itemBody = itemBody.replaceAll("BEGIN:VCALENDAR", "BEGIN:VCALENDAR\r\nMETHOD:REQUEST");
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
2010-05-07 04:30:12 -04:00
|
|
|
if (itemBody.indexOf("X-MICROSOFT-CDO-REPLYTIME") < 0) {
|
|
|
|
itemBody = itemBody.replaceAll("END:VEVENT", "X-MICROSOFT-CDO-REPLYTIME:" +
|
2009-11-27 04:34:20 -05:00
|
|
|
getZuluDateFormat().format(new Date()) + "\r\nEND:VEVENT");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("MIME-Version", "1.0");
|
2010-01-05 05:06:47 -05:00
|
|
|
writer.writeHeader("Content-Type", "multipart/alternative;\r\n" +
|
|
|
|
"\tboundary=\"----=_NextPart_" + boundary + '\"');
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeLn();
|
|
|
|
writer.writeLn("This is a multi-part message in MIME format.");
|
|
|
|
writer.writeLn();
|
2010-01-05 05:06:47 -05:00
|
|
|
writer.writeLn("------=_NextPart_" + boundary);
|
2009-11-27 04:34:20 -05:00
|
|
|
|
|
|
|
// Write a part of the message that contains the
|
|
|
|
// ICS description so that invites contain the description text
|
2010-05-07 04:30:12 -04:00
|
|
|
String description = getICSDescription(itemBody).replaceAll("\\\\[Nn]", "\r\n");
|
2009-11-27 04:34:20 -05:00
|
|
|
|
|
|
|
if (description.length() > 0) {
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("Content-Type", "text/plain;\r\n" +
|
|
|
|
"\tcharset=\"utf-8\"");
|
|
|
|
writer.writeHeader("content-transfer-encoding", "8bit");
|
|
|
|
writer.writeLn();
|
2009-11-27 04:34:20 -05:00
|
|
|
writer.flush();
|
|
|
|
baos.write(description.getBytes("UTF-8"));
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeLn();
|
2010-01-05 05:06:47 -05:00
|
|
|
writer.writeLn("------=_NextPart_" + boundary);
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeHeader("Content-class", contentClass);
|
2010-01-05 05:06:47 -05:00
|
|
|
writer.writeHeader("Content-Type", "text/calendar;\r\n" +
|
|
|
|
"\tmethod=" + method + ";\r\n" +
|
2009-12-24 10:33:26 -05:00
|
|
|
"\tcharset=\"utf-8\""
|
|
|
|
);
|
|
|
|
writer.writeHeader("Content-Transfer-Encoding", "8bit");
|
|
|
|
writer.writeLn();
|
2009-11-27 04:34:20 -05:00
|
|
|
writer.flush();
|
2010-05-07 04:30:12 -04:00
|
|
|
baos.write(fixICS(itemBody, false).getBytes("UTF-8"));
|
2009-12-24 10:33:26 -05:00
|
|
|
writer.writeLn();
|
2010-01-05 05:06:47 -05:00
|
|
|
writer.writeLn("------=_NextPart_" + boundary + "--");
|
2009-11-27 04:34:20 -05:00
|
|
|
writer.close();
|
|
|
|
putmethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray(), "message/rfc822"));
|
|
|
|
try {
|
|
|
|
if (status == 0) {
|
|
|
|
status = httpClient.executeMethod(putmethod);
|
|
|
|
if (status == HttpURLConnection.HTTP_OK) {
|
|
|
|
if (etag != null) {
|
|
|
|
LOGGER.debug("Updated event " + href);
|
|
|
|
} else {
|
|
|
|
LOGGER.warn("Overwritten event " + href);
|
|
|
|
}
|
|
|
|
} else if (status != HttpURLConnection.HTTP_CREATED) {
|
|
|
|
LOGGER.warn("Unable to create or update message " + status + ' ' + putmethod.getStatusLine());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
putmethod.releaseConnection();
|
|
|
|
}
|
2010-05-07 04:30:12 -04:00
|
|
|
ItemResult itemResult = new ItemResult();
|
2009-11-27 04:34:20 -05:00
|
|
|
// 440 means forbidden on Exchange
|
|
|
|
if (status == 440) {
|
|
|
|
status = HttpStatus.SC_FORBIDDEN;
|
|
|
|
}
|
2010-05-07 04:30:12 -04:00
|
|
|
itemResult.status = status;
|
2009-11-27 04:34:20 -05:00
|
|
|
if (putmethod.getResponseHeader("GetETag") != null) {
|
2010-05-07 04:30:12 -04:00
|
|
|
itemResult.etag = putmethod.getResponseHeader("GetETag").getValue();
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// trigger activeSync push event, only if davmail.forceActiveSyncUpdate setting is true
|
|
|
|
if ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED) &&
|
|
|
|
(Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) {
|
|
|
|
ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>();
|
2009-11-27 18:27:50 -05:00
|
|
|
// Set contentclass to make ActiveSync happy
|
2010-05-07 04:30:12 -04:00
|
|
|
propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", DAV), contentClass));
|
2009-11-27 18:27:50 -05:00
|
|
|
// ... but also set PR_INTERNET_CONTENT to preserve custom properties
|
|
|
|
propertyList.add(new DefaultDavProperty(PR_INTERNET_CONTENT, new String(Base64.encodeBase64(baos.toByteArray()))));
|
2009-11-27 04:34:20 -05:00
|
|
|
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), propertyList);
|
|
|
|
int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod);
|
|
|
|
if (patchStatus != HttpStatus.SC_MULTI_STATUS) {
|
|
|
|
LOGGER.warn("Unable to patch event to trigger activeSync push");
|
2009-11-27 18:27:50 -05:00
|
|
|
} else {
|
|
|
|
// need to retrieve new etag
|
2010-05-06 15:26:44 -04:00
|
|
|
Item newItem = getItem(href);
|
2010-05-07 04:30:12 -04:00
|
|
|
itemResult.etag = newItem.etag;
|
2009-11-27 04:34:20 -05:00
|
|
|
}
|
|
|
|
}
|
2010-05-07 04:30:12 -04:00
|
|
|
return itemResult;
|
2009-11-27 04:34:20 -05:00
|
|
|
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
2010-05-06 15:26:44 -04:00
|
|
|
/**
|
|
|
|
* Search contacts in provided folder.
|
|
|
|
*
|
|
|
|
* @param folderPath Exchange folder path
|
|
|
|
* @return list of contacts
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public List<Contact> getAllContacts(String folderPath) throws IOException {
|
|
|
|
|
|
|
|
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\", \"DAV:displayname\"" +
|
|
|
|
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
|
|
|
" WHERE \"DAV:contentclass\" = 'urn:content-classes:person'\n";
|
|
|
|
return getContacts(folderPath, searchQuery);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search contacts in provided folder matching the search query.
|
|
|
|
*
|
|
|
|
* @param folderPath Exchange folder path
|
|
|
|
* @param searchQuery Exchange search query
|
|
|
|
* @return list of contacts
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
protected List<Contact> getContacts(String folderPath, String searchQuery) throws IOException {
|
|
|
|
List<Contact> contacts = new ArrayList<Contact>();
|
|
|
|
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(httpClient, URIUtil.encodePath(folderPath), searchQuery);
|
|
|
|
for (MultiStatusResponse response : responses) {
|
|
|
|
contacts.add(new Contact(response));
|
|
|
|
}
|
|
|
|
return contacts;
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Search calendar messages in provided folder.
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param folderPath Exchange folder path
|
|
|
|
* @return list of calendar messages as Event objects
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-04-14 19:46:37 -04:00
|
|
|
public List<Event> getEventMessages(String folderPath) throws IOException {
|
2009-08-29 16:20:44 -04:00
|
|
|
List<Event> result;
|
|
|
|
try {
|
2010-03-29 06:45:10 -04:00
|
|
|
String scheduleStatePropertyName = scheduleStateProperty.getNamespace().getURI() + scheduleStateProperty.getName();
|
2010-04-12 04:54:53 -04:00
|
|
|
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\", \"urn:schemas:calendar:instancetype\", \"DAV:displayname\"" +
|
2009-08-29 16:20:44 -04:00
|
|
|
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
|
|
|
" WHERE \"DAV:contentclass\" = 'urn:content-classes:calendarmessage'\n" +
|
2010-03-30 16:46:57 -04:00
|
|
|
" AND (\"" + scheduleStatePropertyName + "\" IS NULL OR NOT \"" + scheduleStatePropertyName + "\" = 'CALDAV:schedule-processed')\n" +
|
2009-08-29 16:20:44 -04:00
|
|
|
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
|
|
|
|
result = getEvents(folderPath, searchQuery);
|
|
|
|
} catch (HttpException e) {
|
2009-10-16 04:58:04 -04:00
|
|
|
// failover to DAV:comment property on some Exchange servers
|
2009-08-29 16:20:44 -04:00
|
|
|
if (DEFAULT_SCHEDULE_STATE_PROPERTY.equals(scheduleStateProperty)) {
|
2010-05-07 04:30:12 -04:00
|
|
|
scheduleStateProperty = DavPropertyName.create("comment", DAV);
|
2009-08-29 16:20:44 -04:00
|
|
|
result = getEventMessages(folderPath);
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
2009-02-20 12:44:30 -05:00
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Search calendar events in provided folder.
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param folderPath Exchange folder path
|
|
|
|
* @return list of calendar events
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-04-14 19:46:37 -04:00
|
|
|
public List<Event> getAllEvents(String folderPath) throws IOException {
|
2010-04-14 16:01:51 -04:00
|
|
|
int caldavPastDelay = Settings.getIntProperty("davmail.caldavPastDelay");
|
2008-12-05 05:45:23 -05:00
|
|
|
String dateCondition = "";
|
2010-04-14 16:01:51 -04:00
|
|
|
if (caldavPastDelay != 0) {
|
2008-12-05 05:45:23 -05:00
|
|
|
Calendar cal = Calendar.getInstance();
|
|
|
|
cal.add(Calendar.DAY_OF_MONTH, -caldavPastDelay);
|
2009-04-07 08:37:28 -04:00
|
|
|
dateCondition = " AND \"urn:schemas:calendar:dtstart\" > '" + formatSearchDate(cal.getTime()) + "'\n";
|
2008-12-05 05:45:23 -05:00
|
|
|
}
|
|
|
|
|
2010-03-29 04:50:39 -04:00
|
|
|
String privateCondition = "";
|
2010-04-12 04:55:47 -04:00
|
|
|
if (isSharedFolder(folderPath)) {
|
2010-03-29 04:50:39 -04:00
|
|
|
LOGGER.debug("Shared or public calendar: exclude private events");
|
|
|
|
privateCondition = " AND \"http://schemas.microsoft.com/exchange/sensitivity\" = 0\n";
|
|
|
|
}
|
|
|
|
|
2010-04-12 04:54:53 -04:00
|
|
|
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\", \"urn:schemas:calendar:instancetype\", \"DAV:displayname\"" +
|
2009-04-14 19:46:37 -04:00
|
|
|
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
2009-03-22 15:22:27 -04:00
|
|
|
" WHERE (" +
|
2009-11-22 16:52:53 -05:00
|
|
|
// VTODO events have a null instancetype
|
|
|
|
" \"urn:schemas:calendar:instancetype\" is null OR" +
|
2009-03-22 15:22:27 -04:00
|
|
|
" \"urn:schemas:calendar:instancetype\" = 1\n" +
|
2009-01-08 05:35:06 -05:00
|
|
|
" OR (\"urn:schemas:calendar:instancetype\" = 0\n" +
|
2008-12-05 05:45:23 -05:00
|
|
|
dateCondition +
|
2009-01-08 05:35:06 -05:00
|
|
|
" )) AND \"DAV:contentclass\" = 'urn:content-classes:appointment'\n" +
|
2010-03-29 04:50:39 -04:00
|
|
|
privateCondition +
|
2009-02-20 12:44:30 -05:00
|
|
|
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
|
2009-04-14 19:46:37 -04:00
|
|
|
return getEvents(folderPath, searchQuery);
|
2009-02-20 12:44:30 -05:00
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Search calendar events or messages in provided folder matching the search query.
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
|
|
|
* @param folderPath Exchange folder path
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param searchQuery Exchange search query
|
|
|
|
* @return list of calendar messages as Event objects
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
protected List<Event> getEvents(String folderPath, String searchQuery) throws IOException {
|
2009-02-20 12:44:30 -05:00
|
|
|
List<Event> events = new ArrayList<Event>();
|
2009-08-13 04:34:51 -04:00
|
|
|
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(httpClient, URIUtil.encodePath(folderPath), searchQuery);
|
2009-04-01 11:51:12 -04:00
|
|
|
for (MultiStatusResponse response : responses) {
|
2009-12-21 08:01:49 -05:00
|
|
|
String instancetype = getPropertyIfExists(response.getProperties(HttpStatus.SC_OK), "instancetype", Namespace.getNamespace("urn:schemas:calendar:"));
|
2010-05-06 15:26:44 -04:00
|
|
|
Event event = new Event(response);
|
2010-03-29 04:50:39 -04:00
|
|
|
//noinspection VariableNotUsedInsideIf
|
2010-03-21 16:40:14 -04:00
|
|
|
if (instancetype == null) {
|
2010-01-14 04:46:48 -05:00
|
|
|
// check ics content
|
|
|
|
try {
|
2010-05-06 15:26:44 -04:00
|
|
|
event.getBody();
|
|
|
|
// getBody success => add event or task
|
2010-04-23 11:16:05 -04:00
|
|
|
events.add(event);
|
2010-02-19 18:21:16 -05:00
|
|
|
} catch (HttpException e) {
|
2010-01-14 04:46:48 -05:00
|
|
|
// invalid event: exclude from list
|
2010-04-23 09:42:37 -04:00
|
|
|
LOGGER.warn("Invalid event " + event.displayName + " found at " + response.getHref(), e);
|
2010-01-14 04:46:48 -05:00
|
|
|
}
|
2009-12-21 08:01:49 -05:00
|
|
|
} else {
|
|
|
|
events.add(event);
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
return events;
|
|
|
|
}
|
|
|
|
|
2010-05-07 06:03:49 -04:00
|
|
|
/**
|
|
|
|
* convert vcf extension to EML.
|
2010-05-11 08:52:15 -04:00
|
|
|
*
|
2010-05-07 06:03:49 -04:00
|
|
|
* @param itemName item name
|
|
|
|
* @return EML item name
|
|
|
|
*/
|
|
|
|
protected String convertItemNameToEML(String itemName) {
|
|
|
|
if (itemName.endsWith(".vcf")) {
|
|
|
|
return itemName.substring(0, itemName.length() - 3) + "EML";
|
|
|
|
} else {
|
|
|
|
return itemName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
2010-05-06 15:26:44 -04:00
|
|
|
* Get item named eventName in folder
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param folderPath Exchange folder path
|
2010-05-07 04:30:12 -04:00
|
|
|
* @param itemName event name
|
2009-08-13 04:34:51 -04:00
|
|
|
* @return event object
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2010-05-06 15:26:44 -04:00
|
|
|
public Item getItem(String folderPath, String itemName) throws IOException {
|
2010-05-12 05:16:20 -04:00
|
|
|
String itemPath = folderPath + '/' + convertItemNameToEML(itemName);
|
2010-05-06 15:26:44 -04:00
|
|
|
Item item;
|
2009-11-27 18:27:50 -05:00
|
|
|
try {
|
2010-05-12 05:16:20 -04:00
|
|
|
item = getItem(itemPath);
|
2009-11-27 18:27:50 -05:00
|
|
|
} catch (HttpNotFoundException hnfe) {
|
|
|
|
// failover for Exchange 2007 plus encoding issue
|
2010-05-07 06:03:49 -04:00
|
|
|
String decodedEventName = convertItemNameToEML(itemName).replaceAll("_xF8FF_", "/").replaceAll("_x003F_", "?").replaceAll("'", "''");
|
2010-05-13 08:35:09 -04:00
|
|
|
LOGGER.debug("Item not found at " + itemPath + ", search by displayname: '" + decodedEventName + '\'');
|
2009-11-27 18:27:50 -05:00
|
|
|
ExchangeSession.MessageList messages = searchMessages(folderPath, " AND \"DAV:displayname\"='" + decodedEventName + '\'');
|
|
|
|
if (!messages.isEmpty()) {
|
2010-05-06 15:26:44 -04:00
|
|
|
item = getItem(messages.get(0).getPermanentUrl());
|
2009-11-27 18:27:50 -05:00
|
|
|
} else {
|
|
|
|
throw hnfe;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-06 15:26:44 -04:00
|
|
|
return item;
|
2009-09-29 07:03:15 -04:00
|
|
|
}
|
|
|
|
|
2009-11-17 18:11:08 -05:00
|
|
|
/**
|
2010-05-06 15:26:44 -04:00
|
|
|
* Get item by url
|
2009-11-17 18:11:08 -05:00
|
|
|
*
|
2010-05-06 15:26:44 -04:00
|
|
|
* @param itemPath Event path
|
2009-11-17 18:11:08 -05:00
|
|
|
* @return event object
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2010-05-06 15:26:44 -04:00
|
|
|
public Item getItem(String itemPath) throws IOException {
|
|
|
|
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(itemPath), 0, EVENT_REQUEST_PROPERTIES);
|
2009-04-01 11:51:12 -04:00
|
|
|
if (responses.length == 0) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2010-05-07 04:30:12 -04:00
|
|
|
String contentClass = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK),
|
|
|
|
"contentclass", DAV);
|
2010-05-06 15:26:44 -04:00
|
|
|
if ("urn:content-classes:person".equals(contentClass)) {
|
|
|
|
return new Contact(responses[0]);
|
2010-05-13 08:35:09 -04:00
|
|
|
} else if ("urn:content-classes:appointment".equals(contentClass)
|
|
|
|
|| "urn:content-classes:calendarmessage".equals(contentClass)) {
|
2010-05-06 15:26:44 -04:00
|
|
|
return new Event(responses[0]);
|
|
|
|
} else {
|
|
|
|
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
|
|
|
|
}
|
2008-12-17 10:27:56 -05:00
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Delete event named eventName in folder
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param folderPath Exchange folder path
|
2010-05-11 08:52:15 -04:00
|
|
|
* @param itemName event name
|
2009-08-13 04:34:51 -04:00
|
|
|
* @return HTTP status
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2010-05-07 06:03:49 -04:00
|
|
|
public int deleteItem(String folderPath, String itemName) throws IOException {
|
|
|
|
String eventPath = URIUtil.encodePath(folderPath + '/' + convertItemNameToEML(itemName));
|
2009-08-13 04:34:51 -04:00
|
|
|
int status;
|
|
|
|
if (inboxUrl.endsWith(folderPath)) {
|
|
|
|
// do not delete calendar messages, mark read and processed
|
|
|
|
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
|
2009-08-29 16:20:44 -04:00
|
|
|
list.add(new DefaultDavProperty(scheduleStateProperty, "CALDAV:schedule-processed"));
|
2009-08-13 04:34:51 -04:00
|
|
|
list.add(new DefaultDavProperty(DavPropertyName.create("read", URN_SCHEMAS_HTTPMAIL), "1"));
|
|
|
|
PropPatchMethod patchMethod = new PropPatchMethod(eventPath, list);
|
|
|
|
DavGatewayHttpClientFacade.executeMethod(httpClient, patchMethod);
|
|
|
|
status = HttpStatus.SC_OK;
|
|
|
|
} else {
|
|
|
|
status = DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, eventPath);
|
|
|
|
}
|
|
|
|
return status;
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
2009-10-07 16:53:21 -04:00
|
|
|
private static int dumpIndex;
|
2009-09-02 06:33:54 -04:00
|
|
|
|
2009-09-14 17:16:15 -04:00
|
|
|
/**
|
|
|
|
* Replace iCal4 (Snow Leopard) principal paths with mailto expression
|
|
|
|
*
|
|
|
|
* @param value attendee value or ics line
|
|
|
|
* @return fixed value
|
|
|
|
*/
|
|
|
|
protected String replaceIcal4Principal(String value) {
|
|
|
|
if (value.contains("/principals/__uuids__/")) {
|
|
|
|
return value.replaceAll("/principals/__uuids__/([^/]*)__AT__([^/]*)/", "mailto:$1@$2");
|
|
|
|
} else {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Event result object to hold HTTP status and event etag from an event creation/update.
|
|
|
|
*/
|
2010-05-07 04:30:12 -04:00
|
|
|
public static class ItemResult {
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* HTTP status
|
|
|
|
*/
|
2009-02-11 20:33:49 -05:00
|
|
|
public int status;
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Event etag from response HTTP header
|
|
|
|
*/
|
2009-02-11 20:33:49 -05:00
|
|
|
public String etag;
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Build and send the MIME message for the provided ICS event.
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param icsBody event in iCalendar format
|
|
|
|
* @return HTTP status
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-02-09 05:12:09 -05:00
|
|
|
public int sendEvent(String icsBody) throws IOException {
|
2009-11-16 10:24:54 -05:00
|
|
|
String messageUrl = draftsUrl + '/' + UUID.randomUUID().toString() + ".EML";
|
2009-02-12 06:33:19 -05:00
|
|
|
int status = internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:calendarmessage", icsBody, null, null).status;
|
2009-02-09 05:12:09 -05:00
|
|
|
if (status != HttpStatus.SC_CREATED) {
|
|
|
|
return status;
|
|
|
|
} else {
|
2009-04-01 11:51:12 -04:00
|
|
|
MoveMethod method = new MoveMethod(URIUtil.encodePath(messageUrl), URIUtil.encodePath(sendmsgUrl), true);
|
2009-03-18 13:26:33 -04:00
|
|
|
status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
|
|
|
|
if (status != HttpStatus.SC_OK) {
|
|
|
|
throw DavGatewayHttpClientFacade.buildHttpException(method);
|
2009-02-09 05:12:09 -05:00
|
|
|
}
|
2009-03-18 13:26:33 -04:00
|
|
|
return status;
|
2009-02-09 05:12:09 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
2010-05-07 04:30:12 -04:00
|
|
|
* Create or update item (event or contact) on the Exchange server
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param folderPath Exchange folder path
|
2010-05-07 04:30:12 -04:00
|
|
|
* @param itemName event name
|
|
|
|
* @param itemBody event body in iCalendar format
|
2009-08-21 05:58:19 -04:00
|
|
|
* @param etag previous event etag to detect concurrent updates
|
|
|
|
* @param noneMatch if-none-match header value
|
2009-08-13 04:34:51 -04:00
|
|
|
* @return HTTP response event result (status and etag)
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2010-05-07 04:30:12 -04:00
|
|
|
public ItemResult createOrUpdateItem(String folderPath, String itemName, String itemBody, String etag, String noneMatch) throws IOException {
|
|
|
|
String messageUrl = folderPath + '/' + itemName;
|
|
|
|
if (itemBody.startsWith("BEGIN:VCALENDAR")) {
|
|
|
|
return internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:appointment", itemBody, etag, noneMatch);
|
|
|
|
} else if (itemBody.startsWith("BEGIN:VCARD")) {
|
|
|
|
return internalCreateOrUpdateContact(messageUrl, "urn:content-classes:person", itemBody, etag, noneMatch);
|
|
|
|
} else {
|
|
|
|
throw new IOException(BundleMessage.format("EXCEPTION_INVALID_MESSAGE_CONTENT", itemBody));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
|
|
|
Contact contact = new Contact(messageUrl, contentClass, icsBody, etag, noneMatch);
|
|
|
|
return contact.createOrUpdate();
|
2009-02-09 05:12:09 -05:00
|
|
|
}
|
|
|
|
|
2010-05-07 04:30:12 -04:00
|
|
|
protected ItemResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
|
|
|
Event event = new Event(messageUrl, contentClass, icsBody, etag, noneMatch);
|
2009-11-27 04:34:20 -05:00
|
|
|
return event.createOrUpdate();
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Get folder ctag (change tag).
|
|
|
|
* This flag changes whenever folder or folder content changes
|
|
|
|
*
|
2009-08-21 05:58:19 -04:00
|
|
|
* @param folderPath Exchange folder path
|
2009-08-13 04:34:51 -04:00
|
|
|
* @return folder ctag
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-04-15 18:05:11 -04:00
|
|
|
public String getFolderCtag(String folderPath) throws IOException {
|
|
|
|
return getFolderProperty(folderPath, CONTENT_TAG);
|
2009-03-03 06:09:07 -05:00
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Get folder resource tag.
|
|
|
|
* Same as etag for folders, changes when folder (not content) changes
|
|
|
|
*
|
|
|
|
* @param folderPath Exchange folder path
|
|
|
|
* @return folder resource tag
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-04-15 18:05:11 -04:00
|
|
|
public String getFolderResourceTag(String folderPath) throws IOException {
|
|
|
|
return getFolderProperty(folderPath, RESOURCE_TAG);
|
2009-03-03 06:09:07 -05:00
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
protected String getFolderProperty(String folderPath, DavPropertyNameSet davPropertyNameSet) throws IOException {
|
2009-04-15 18:05:11 -04:00
|
|
|
String result;
|
2009-04-01 11:51:12 -04:00
|
|
|
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(
|
2009-04-15 18:05:11 -04:00
|
|
|
httpClient, URIUtil.encodePath(folderPath), 0, davPropertyNameSet);
|
2009-04-01 11:51:12 -04:00
|
|
|
if (responses.length == 0) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_UNABLE_TO_GET_FOLDER", folderPath);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-04-01 19:23:14 -04:00
|
|
|
DavPropertySet properties = responses[0].getProperties(HttpStatus.SC_OK);
|
2009-04-15 18:05:11 -04:00
|
|
|
DavPropertyName davPropertyName = davPropertyNameSet.iterator().nextPropertyName();
|
|
|
|
result = getPropertyIfExists(properties, davPropertyName);
|
|
|
|
if (result == null) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_UNABLE_TO_GET_PROPERTY", davPropertyName);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-04-15 18:05:11 -04:00
|
|
|
return result;
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-01-08 04:58:31 -05:00
|
|
|
* Get current Exchange alias name from login name
|
|
|
|
*
|
|
|
|
* @return user name
|
|
|
|
*/
|
2009-02-24 06:53:02 -05:00
|
|
|
protected String getAliasFromLogin() {
|
2009-01-08 04:58:31 -05:00
|
|
|
// Exchange 2007 : userName is login without domain
|
2009-09-30 17:54:53 -04:00
|
|
|
String result = this.userName;
|
|
|
|
int index = result.indexOf('\\');
|
2009-01-08 04:58:31 -05:00
|
|
|
if (index >= 0) {
|
2009-09-30 17:54:53 -04:00
|
|
|
result = result.substring(index + 1);
|
2009-01-08 04:58:31 -05:00
|
|
|
}
|
2009-09-30 17:54:53 -04:00
|
|
|
return result;
|
2009-01-08 04:58:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get current Exchange alias name from mailbox name
|
2008-11-26 19:56:28 -05:00
|
|
|
*
|
|
|
|
* @return user name
|
|
|
|
*/
|
2009-05-07 04:45:00 -04:00
|
|
|
protected String getAliasFromMailPath() {
|
2008-12-25 17:16:24 -05:00
|
|
|
if (mailPath == null) {
|
2009-03-09 11:21:03 -04:00
|
|
|
return null;
|
2009-01-08 04:58:31 -05:00
|
|
|
}
|
2009-04-16 17:16:40 -04:00
|
|
|
int index = mailPath.lastIndexOf('/', mailPath.length() - 2);
|
2009-01-08 04:58:31 -05:00
|
|
|
if (index >= 0 && mailPath.endsWith("/")) {
|
|
|
|
return mailPath.substring(index + 1, mailPath.length() - 1);
|
2008-11-26 19:56:28 -05:00
|
|
|
} else {
|
2009-05-07 04:45:00 -04:00
|
|
|
LOGGER.warn(new BundleMessage("EXCEPTION_INVALID_MAIL_PATH", mailPath));
|
|
|
|
return null;
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Get user alias from mailbox display name over Webdav.
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @return user alias
|
|
|
|
*/
|
2009-05-07 04:45:00 -04:00
|
|
|
public String getAliasFromMailboxDisplayName() {
|
2009-03-11 08:39:14 -04:00
|
|
|
if (mailPath == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2009-05-07 04:45:00 -04:00
|
|
|
String displayName = null;
|
|
|
|
try {
|
|
|
|
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(
|
|
|
|
httpClient, URIUtil.encodePath(mailPath), 0, DISPLAY_NAME);
|
|
|
|
if (responses.length == 0) {
|
2009-09-20 10:10:54 -04:00
|
|
|
LOGGER.warn(new BundleMessage("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath));
|
2009-05-07 04:45:00 -04:00
|
|
|
} else {
|
2010-05-07 04:30:12 -04:00
|
|
|
displayName = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "displayname", DAV);
|
2009-05-07 04:45:00 -04:00
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
2009-09-20 10:10:54 -04:00
|
|
|
LOGGER.warn(new BundleMessage("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath));
|
2009-03-11 08:39:14 -04:00
|
|
|
}
|
2009-04-16 17:16:40 -04:00
|
|
|
return displayName;
|
2009-03-25 17:59:27 -04:00
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Build Caldav calendar path for principal and folder name.
|
|
|
|
* - prefix is current user mailbox path if principal is current user,
|
2009-08-21 05:58:19 -04:00
|
|
|
* else prefix is parent folder of current user mailbox path followed by principal
|
2009-08-13 04:34:51 -04:00
|
|
|
* - suffix according to well known folder names (internationalized on Exchange)
|
|
|
|
*
|
2009-08-21 05:58:19 -04:00
|
|
|
* @param principal calendar principal
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param folderName requested folder name
|
|
|
|
* @return Exchange folder path
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
2009-04-14 19:46:37 -04:00
|
|
|
public String buildCalendarPath(String principal, String folderName) throws IOException {
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
2009-09-10 04:06:15 -04:00
|
|
|
// other user calendar => replace principal folder name in mailPath
|
2010-04-14 16:08:33 -04:00
|
|
|
if (principal != null && !alias.equalsIgnoreCase(principal) && !email.equalsIgnoreCase(principal)) {
|
2009-10-06 17:12:26 -04:00
|
|
|
LOGGER.debug("Detected shared calendar path for principal " + principal + ", user principal is " + email);
|
2009-04-16 17:16:40 -04:00
|
|
|
int index = mailPath.lastIndexOf('/', mailPath.length() - 2);
|
2009-04-14 19:46:37 -04:00
|
|
|
if (index >= 0 && mailPath.endsWith("/")) {
|
2009-04-23 16:53:22 -04:00
|
|
|
buffer.append(mailPath.substring(0, index + 1)).append(principal).append('/');
|
2009-04-14 19:46:37 -04:00
|
|
|
} else {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_INVALID_MAIL_PATH", mailPath);
|
2009-04-14 19:46:37 -04:00
|
|
|
}
|
|
|
|
} else if (principal != null) {
|
|
|
|
buffer.append(mailPath);
|
|
|
|
}
|
2009-09-10 04:06:15 -04:00
|
|
|
|
2010-06-04 08:58:03 -04:00
|
|
|
if (folderName != null && (folderName.startsWith("calendar") || folderName.startsWith("contacts")
|
|
|
|
// OSX address book name
|
|
|
|
|| folderName.startsWith("addressbook"))) {
|
|
|
|
if (folderName.startsWith("calendar")) {
|
|
|
|
// replace 'calendar' folder name with i18n name
|
|
|
|
buffer.append(calendarUrl.substring(calendarUrl.lastIndexOf('/') + 1));
|
|
|
|
} else {
|
|
|
|
// replace 'contacts' folder name with i18n name
|
|
|
|
buffer.append(contactsUrl.substring(contactsUrl.lastIndexOf('/') + 1));
|
|
|
|
}
|
2009-09-10 04:06:15 -04:00
|
|
|
|
|
|
|
// sub calendar folder => append sub folder name
|
|
|
|
int index = folderName.indexOf('/');
|
|
|
|
if (index >= 0) {
|
2009-09-14 17:16:15 -04:00
|
|
|
buffer.append(folderName.substring(index));
|
2009-09-10 04:06:15 -04:00
|
|
|
}
|
|
|
|
// replace 'inbox' folder name with i18n name
|
2009-04-14 19:46:37 -04:00
|
|
|
} else if ("inbox".equals(folderName)) {
|
2009-04-16 17:16:40 -04:00
|
|
|
buffer.append(inboxUrl.substring(inboxUrl.lastIndexOf('/') + 1));
|
2009-09-10 04:06:15 -04:00
|
|
|
// append folder name without replace (public folder)
|
2009-04-14 19:46:37 -04:00
|
|
|
} else if (folderName != null && folderName.length() > 0) {
|
|
|
|
buffer.append(folderName);
|
|
|
|
}
|
|
|
|
return buffer.toString();
|
|
|
|
}
|
|
|
|
|
2010-03-29 04:50:39 -04:00
|
|
|
/**
|
|
|
|
* Test if folderPath is inside user mailbox.
|
|
|
|
*
|
|
|
|
* @param folderPath absolute folder path
|
|
|
|
* @return true if folderPath is a public or shared folder
|
|
|
|
*/
|
|
|
|
public boolean isSharedFolder(String folderPath) {
|
2010-04-14 16:08:33 -04:00
|
|
|
return !folderPath.toLowerCase().startsWith(mailPath.toLowerCase());
|
2010-03-29 04:50:39 -04:00
|
|
|
}
|
|
|
|
|
2009-04-23 17:34:04 -04:00
|
|
|
/**
|
|
|
|
* Build base path for cmd commands (galfind, gallookup).
|
|
|
|
* This does not work with freebusy, which requires /public/
|
2009-04-28 17:01:40 -04:00
|
|
|
*
|
2009-04-23 17:34:04 -04:00
|
|
|
* @return cmd base path
|
|
|
|
*/
|
|
|
|
public String getCmdBasePath() {
|
|
|
|
if (mailPath == null) {
|
2009-12-09 16:13:43 -05:00
|
|
|
if (publicFolderUrl == null) {
|
|
|
|
return "/public/";
|
|
|
|
} else {
|
|
|
|
return publicFolderUrl + '/';
|
|
|
|
}
|
2009-04-23 17:34:04 -04:00
|
|
|
} else {
|
|
|
|
return mailPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Get user email from global address list (galfind).
|
|
|
|
*
|
|
|
|
* @param alias user alias
|
|
|
|
* @return user email
|
|
|
|
*/
|
2009-05-07 04:45:00 -04:00
|
|
|
public String getEmail(String alias) {
|
2009-01-08 04:58:31 -05:00
|
|
|
String emailResult = null;
|
2009-03-09 11:21:03 -04:00
|
|
|
if (alias != null) {
|
2009-05-07 04:45:00 -04:00
|
|
|
GetMethod getMethod = null;
|
|
|
|
String path = null;
|
2009-03-09 11:21:03 -04:00
|
|
|
try {
|
2009-05-07 04:45:00 -04:00
|
|
|
path = getCmdBasePath() + "?Cmd=galfind&AN=" + URIUtil.encodeWithinQuery(alias);
|
|
|
|
getMethod = new GetMethod(path);
|
2009-10-16 04:58:04 -04:00
|
|
|
DavGatewayHttpClientFacade.executeGetMethod(httpClient, getMethod, true);
|
2009-03-09 11:21:03 -04:00
|
|
|
Map<String, Map<String, String>> results = XMLStreamUtil.getElementContentsAsMap(getMethod.getResponseBodyAsStream(), "item", "AN");
|
|
|
|
Map<String, String> result = results.get(alias.toLowerCase());
|
|
|
|
if (result != null) {
|
|
|
|
emailResult = result.get("EM");
|
|
|
|
}
|
2009-05-07 04:45:00 -04:00
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.debug("GET " + path + " failed: " + e + ' ' + e.getMessage());
|
2009-03-09 11:21:03 -04:00
|
|
|
} finally {
|
2009-05-07 04:45:00 -04:00
|
|
|
if (getMethod != null) {
|
|
|
|
getMethod.releaseConnection();
|
|
|
|
}
|
2009-03-09 11:21:03 -04:00
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-01-08 04:58:31 -05:00
|
|
|
return emailResult;
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Determine user email through various means.
|
|
|
|
*
|
2010-04-23 09:42:37 -04:00
|
|
|
* @param hostName Exchange server host name for last failover
|
2009-08-13 04:34:51 -04:00
|
|
|
*/
|
2010-04-20 11:01:22 -04:00
|
|
|
public void buildEmail(String hostName) {
|
2009-01-08 04:58:31 -05:00
|
|
|
// first try to get email from login name
|
2009-03-13 07:11:40 -04:00
|
|
|
alias = getAliasFromLogin();
|
|
|
|
email = getEmail(alias);
|
2009-01-08 04:58:31 -05:00
|
|
|
// failover: use mailbox name as alias
|
2008-11-26 19:56:28 -05:00
|
|
|
if (email == null) {
|
2009-03-13 07:11:40 -04:00
|
|
|
alias = getAliasFromMailPath();
|
|
|
|
email = getEmail(alias);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-03-11 08:39:14 -04:00
|
|
|
// another failover : get alias from mailPath display name
|
|
|
|
if (email == null) {
|
2009-03-13 07:11:40 -04:00
|
|
|
alias = getAliasFromMailboxDisplayName();
|
|
|
|
email = getEmail(alias);
|
2009-03-11 08:39:14 -04:00
|
|
|
}
|
2009-01-08 04:58:31 -05:00
|
|
|
if (email == null) {
|
2009-03-09 11:21:03 -04:00
|
|
|
// failover : get email from Exchange 2007 Options page
|
2010-04-20 11:01:22 -04:00
|
|
|
alias = getAliasFromOptions();
|
2009-03-13 07:11:40 -04:00
|
|
|
email = getEmail(alias);
|
2009-05-19 06:42:56 -04:00
|
|
|
// failover: get email from options
|
|
|
|
if (alias != null && email == null) {
|
2010-04-20 11:01:22 -04:00
|
|
|
email = getEmailFromOptions();
|
2009-05-19 06:42:56 -04:00
|
|
|
}
|
2009-03-09 11:21:03 -04:00
|
|
|
}
|
|
|
|
if (email == null) {
|
2009-04-08 10:51:15 -04:00
|
|
|
LOGGER.debug("Unable to get user email with alias " + getAliasFromLogin()
|
2009-03-09 11:21:03 -04:00
|
|
|
+ " or " + getAliasFromMailPath()
|
2010-04-20 11:01:22 -04:00
|
|
|
+ " or " + getAliasFromOptions()
|
2009-03-09 11:21:03 -04:00
|
|
|
);
|
2009-04-08 10:51:15 -04:00
|
|
|
// last failover: build email from domain name and mailbox display name
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
|
|
|
// most reliable alias
|
|
|
|
alias = getAliasFromMailboxDisplayName();
|
|
|
|
if (alias == null) {
|
|
|
|
alias = getAliasFromLogin();
|
|
|
|
}
|
|
|
|
if (alias != null) {
|
|
|
|
buffer.append(alias);
|
2009-12-21 10:02:16 -05:00
|
|
|
if (alias.indexOf('@') < 0) {
|
|
|
|
buffer.append('@');
|
|
|
|
int dotIndex = hostName.indexOf('.');
|
|
|
|
if (dotIndex >= 0) {
|
|
|
|
buffer.append(hostName.substring(dotIndex + 1));
|
|
|
|
}
|
2009-04-08 10:51:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
email = buffer.toString();
|
2009-03-09 11:21:03 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-30 18:13:39 -04:00
|
|
|
static final String MAILBOX_BASE = "/cn=";
|
2009-09-21 17:34:13 -04:00
|
|
|
|
2010-04-20 11:01:22 -04:00
|
|
|
protected String getAliasFromOptions() {
|
2009-03-09 11:21:03 -04:00
|
|
|
String result = null;
|
|
|
|
// get user mail URL from html body
|
|
|
|
BufferedReader optionsPageReader = null;
|
2010-04-20 11:01:22 -04:00
|
|
|
GetMethod optionsMethod = new GetMethod("/owa/?ae=Options&t=About");
|
2009-03-09 11:21:03 -04:00
|
|
|
try {
|
2009-10-16 04:58:04 -04:00
|
|
|
DavGatewayHttpClientFacade.executeGetMethod(httpClient, optionsMethod, false);
|
2009-03-09 11:21:03 -04:00
|
|
|
optionsPageReader = new BufferedReader(new InputStreamReader(optionsMethod.getResponseBodyAsStream()));
|
|
|
|
String line;
|
|
|
|
// find mailbox full name
|
|
|
|
//noinspection StatementWithEmptyBody
|
|
|
|
while ((line = optionsPageReader.readLine()) != null && line.toLowerCase().indexOf(MAILBOX_BASE) == -1) {
|
|
|
|
}
|
|
|
|
if (line != null) {
|
2010-03-30 18:13:39 -04:00
|
|
|
int start = line.toLowerCase().lastIndexOf(MAILBOX_BASE) + MAILBOX_BASE.length();
|
2009-04-16 17:16:40 -04:00
|
|
|
int end = line.indexOf('<', start);
|
2009-03-09 11:21:03 -04:00
|
|
|
result = line.substring(start, end);
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.error("Error parsing options page at " + optionsMethod.getPath());
|
|
|
|
} finally {
|
|
|
|
if (optionsPageReader != null) {
|
|
|
|
try {
|
|
|
|
optionsPageReader.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.error("Error parsing options page at " + optionsMethod.getPath());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
optionsMethod.releaseConnection();
|
2009-01-08 04:58:31 -05:00
|
|
|
}
|
2009-03-09 11:21:03 -04:00
|
|
|
|
|
|
|
return result;
|
2008-12-25 17:16:24 -05:00
|
|
|
}
|
|
|
|
|
2010-04-20 11:01:22 -04:00
|
|
|
protected String getEmailFromOptions() {
|
2009-05-19 06:42:56 -04:00
|
|
|
String result = null;
|
|
|
|
// get user mail URL from html body
|
|
|
|
BufferedReader optionsPageReader = null;
|
2010-04-20 11:01:22 -04:00
|
|
|
GetMethod optionsMethod = new GetMethod("/owa/?ae=Options&t=About");
|
2009-05-19 06:42:56 -04:00
|
|
|
try {
|
2009-10-16 04:58:04 -04:00
|
|
|
DavGatewayHttpClientFacade.executeGetMethod(httpClient, optionsMethod, false);
|
2009-05-19 06:42:56 -04:00
|
|
|
optionsPageReader = new BufferedReader(new InputStreamReader(optionsMethod.getResponseBodyAsStream()));
|
|
|
|
String line;
|
|
|
|
// find email
|
|
|
|
//noinspection StatementWithEmptyBody
|
|
|
|
while ((line = optionsPageReader.readLine()) != null
|
|
|
|
&& (line.indexOf('[') == -1
|
|
|
|
|| line.indexOf('@') == -1
|
|
|
|
|| line.indexOf(']') == -1)) {
|
|
|
|
}
|
|
|
|
if (line != null) {
|
|
|
|
int start = line.toLowerCase().indexOf('[') + 1;
|
|
|
|
int end = line.indexOf(']', start);
|
|
|
|
result = line.substring(start, end);
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.error("Error parsing options page at " + optionsMethod.getPath());
|
|
|
|
} finally {
|
|
|
|
if (optionsPageReader != null) {
|
|
|
|
try {
|
|
|
|
optionsPageReader.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.error("Error parsing options page at " + optionsMethod.getPath());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
optionsMethod.releaseConnection();
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2008-12-25 17:16:24 -05:00
|
|
|
/**
|
|
|
|
* Get current user email
|
|
|
|
*
|
|
|
|
* @return user email
|
|
|
|
*/
|
2009-02-24 06:53:02 -05:00
|
|
|
public String getEmail() {
|
2008-11-26 19:56:28 -05:00
|
|
|
return email;
|
|
|
|
}
|
|
|
|
|
2009-03-18 13:26:33 -04:00
|
|
|
/**
|
2009-03-13 07:11:40 -04:00
|
|
|
* Get current user alias
|
|
|
|
*
|
|
|
|
* @return user email
|
|
|
|
*/
|
|
|
|
public String getAlias() {
|
|
|
|
return alias;
|
|
|
|
}
|
|
|
|
|
2008-12-03 06:38:35 -05:00
|
|
|
/**
|
|
|
|
* Search users in global address book
|
|
|
|
*
|
2008-12-05 09:53:38 -05:00
|
|
|
* @param searchAttribute exchange search attribute
|
|
|
|
* @param searchValue search value
|
2008-12-03 06:38:35 -05:00
|
|
|
* @return List of users
|
2009-04-16 18:20:30 -04:00
|
|
|
* @throws IOException on error
|
2008-12-03 06:38:35 -05:00
|
|
|
*/
|
|
|
|
public Map<String, Map<String, String>> galFind(String searchAttribute, String searchValue) throws IOException {
|
2008-12-05 09:53:38 -05:00
|
|
|
Map<String, Map<String, String>> results;
|
2009-04-28 17:01:40 -04:00
|
|
|
GetMethod getMethod = new GetMethod(URIUtil.encodePathQuery(getCmdBasePath() + "?Cmd=galfind&" + searchAttribute + '=' + searchValue));
|
2008-12-03 06:38:35 -05:00
|
|
|
try {
|
2009-10-16 04:58:04 -04:00
|
|
|
DavGatewayHttpClientFacade.executeGetMethod(httpClient, getMethod, true);
|
2008-12-03 06:38:35 -05:00
|
|
|
results = XMLStreamUtil.getElementContentsAsMap(getMethod.getResponseBodyAsStream(), "item", "AN");
|
|
|
|
} finally {
|
|
|
|
getMethod.releaseConnection();
|
|
|
|
}
|
2009-04-23 16:53:22 -04:00
|
|
|
LOGGER.debug("galfind " + searchAttribute + '=' + searchValue + ": " + results.size() + " result(s)");
|
2008-12-03 06:38:35 -05:00
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2009-10-06 17:12:26 -04:00
|
|
|
/**
|
|
|
|
* Search users in contacts folder by uid.
|
|
|
|
*
|
|
|
|
* @param uid unique id
|
|
|
|
* @return List of users
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public Map<String, Map<String, String>> contactFindByUid(String uid) throws IOException {
|
|
|
|
return contactFind(DAV_UID_FILTER + uid + '\'');
|
|
|
|
}
|
|
|
|
|
|
|
|
static final String DAV_UID_FILTER = "\"DAV:uid\"='";
|
|
|
|
|
2009-08-29 19:49:45 -04:00
|
|
|
/**
|
|
|
|
* Search users in contacts folder
|
|
|
|
*
|
|
|
|
* @param searchFilter search filter
|
|
|
|
* @return List of users
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public Map<String, Map<String, String>> contactFind(String searchFilter) throws IOException {
|
2009-10-06 17:12:26 -04:00
|
|
|
// uid value in search filter (hex value)
|
|
|
|
String filterUid = null;
|
|
|
|
// base64 encoded uid value
|
|
|
|
String actualFilterUid = null;
|
|
|
|
|
|
|
|
// replace hex encoded uid with base64 uid
|
|
|
|
if (searchFilter != null) {
|
|
|
|
int uidStart = searchFilter.indexOf(DAV_UID_FILTER);
|
|
|
|
if (uidStart >= 0) {
|
|
|
|
int uidEnd = searchFilter.indexOf('\'', uidStart + DAV_UID_FILTER.length());
|
|
|
|
if (uidEnd >= 0) {
|
|
|
|
try {
|
|
|
|
filterUid = searchFilter.substring(uidStart + DAV_UID_FILTER.length(), uidEnd);
|
|
|
|
actualFilterUid = new String(Base64.encodeBase64(Hex.decodeHex(filterUid.toCharArray())));
|
|
|
|
searchFilter = searchFilter.substring(0, uidStart + DAV_UID_FILTER.length()) + actualFilterUid + searchFilter.substring(uidEnd);
|
|
|
|
} catch (DecoderException e) {
|
|
|
|
// ignore, this is not an hex uid
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-08-29 19:49:45 -04:00
|
|
|
StringBuilder searchRequest = new StringBuilder();
|
2009-09-03 19:08:35 -04:00
|
|
|
searchRequest.append("Select \"DAV:uid\", " +
|
|
|
|
"\"http://schemas.microsoft.com/exchange/extensionattribute1\"," +
|
|
|
|
"\"http://schemas.microsoft.com/exchange/extensionattribute2\"," +
|
|
|
|
"\"http://schemas.microsoft.com/exchange/extensionattribute3\"," +
|
|
|
|
"\"http://schemas.microsoft.com/exchange/extensionattribute4\"," +
|
|
|
|
"\"urn:schemas:contacts:bday\"," +
|
|
|
|
"\"urn:schemas:contacts:businesshomepage\"," +
|
|
|
|
"\"urn:schemas:contacts:c\"," +
|
|
|
|
"\"urn:schemas:contacts:cn\"," +
|
|
|
|
"\"urn:schemas:contacts:co\"," +
|
|
|
|
"\"urn:schemas:contacts:department\"," +
|
|
|
|
"\"urn:schemas:contacts:email1\"," +
|
|
|
|
"\"urn:schemas:contacts:email2\"," +
|
|
|
|
"\"urn:schemas:contacts:facsimiletelephonenumber\"," +
|
|
|
|
"\"urn:schemas:contacts:givenName\"," +
|
|
|
|
"\"urn:schemas:contacts:homeCity\"," +
|
|
|
|
"\"urn:schemas:contacts:homeCountry\"," +
|
|
|
|
"\"urn:schemas:contacts:homePhone\"," +
|
|
|
|
"\"urn:schemas:contacts:homePostalCode\"," +
|
|
|
|
"\"urn:schemas:contacts:homeState\"," +
|
|
|
|
"\"urn:schemas:contacts:homeStreet\"," +
|
|
|
|
"\"urn:schemas:contacts:l\"," +
|
|
|
|
"\"urn:schemas:contacts:manager\"," +
|
|
|
|
"\"urn:schemas:contacts:mobile\"," +
|
|
|
|
"\"urn:schemas:contacts:namesuffix\"," +
|
|
|
|
"\"urn:schemas:contacts:nickname\"," +
|
|
|
|
"\"urn:schemas:contacts:o\"," +
|
|
|
|
"\"urn:schemas:contacts:pager\"," +
|
|
|
|
"\"urn:schemas:contacts:personaltitle\"," +
|
|
|
|
"\"urn:schemas:contacts:postalcode\"," +
|
|
|
|
"\"urn:schemas:contacts:postofficebox\"," +
|
|
|
|
"\"urn:schemas:contacts:profession\"," +
|
|
|
|
"\"urn:schemas:contacts:roomnumber\"," +
|
|
|
|
"\"urn:schemas:contacts:secretarycn\"," +
|
|
|
|
"\"urn:schemas:contacts:sn\"," +
|
|
|
|
"\"urn:schemas:contacts:spousecn\"," +
|
|
|
|
"\"urn:schemas:contacts:st\"," +
|
|
|
|
"\"urn:schemas:contacts:street\"," +
|
|
|
|
"\"urn:schemas:contacts:telephoneNumber\"," +
|
|
|
|
"\"urn:schemas:contacts:title\"," +
|
|
|
|
"\"urn:schemas:httpmail:textdescription\"")
|
2009-09-07 08:24:50 -04:00
|
|
|
.append(" FROM Scope('SHALLOW TRAVERSAL OF \"").append(contactsUrl).append("\"')\n")
|
|
|
|
.append(" WHERE \"DAV:contentclass\" = 'urn:content-classes:person' \n");
|
2009-09-04 18:30:03 -04:00
|
|
|
if (searchFilter != null && searchFilter.length() > 0) {
|
2009-09-07 08:24:50 -04:00
|
|
|
searchRequest.append(" AND ").append(searchFilter);
|
2009-08-29 19:49:45 -04:00
|
|
|
}
|
2009-10-06 17:12:26 -04:00
|
|
|
LOGGER.debug("contactFind: " + searchRequest);
|
2009-08-29 19:49:45 -04:00
|
|
|
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(
|
|
|
|
httpClient, URIUtil.encodePath(contactsUrl), searchRequest.toString());
|
|
|
|
|
|
|
|
Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>();
|
|
|
|
Map<String, String> item;
|
|
|
|
for (MultiStatusResponse response : responses) {
|
2009-09-03 05:48:58 -04:00
|
|
|
item = new HashMap<String, String>();
|
|
|
|
|
2009-08-29 19:49:45 -04:00
|
|
|
DavPropertySet properties = response.getProperties(HttpStatus.SC_OK);
|
2009-09-03 05:48:58 -04:00
|
|
|
DavPropertyIterator propertiesIterator = properties.iterator();
|
|
|
|
while (propertiesIterator.hasNext()) {
|
|
|
|
DavProperty property = propertiesIterator.nextProperty();
|
|
|
|
String propertyName = property.getName().getName();
|
|
|
|
String propertyValue = (String) property.getValue();
|
2009-10-06 17:12:26 -04:00
|
|
|
if ("uid".equals(propertyName)) {
|
|
|
|
// uid is base64, reencode to hex
|
|
|
|
propertyValue = new String(Hex.encodeHex(Base64.decodeBase64(propertyValue.getBytes())));
|
|
|
|
// if actualFilterUid is not null, exclude non exact match
|
|
|
|
if (actualFilterUid != null && !filterUid.equals(propertyValue)) {
|
|
|
|
propertyValue = null;
|
|
|
|
}
|
|
|
|
} else if (propertyName.startsWith("email")) {
|
2009-09-03 05:48:58 -04:00
|
|
|
if (propertyValue != null && propertyValue.startsWith("\"")) {
|
|
|
|
int endIndex = propertyValue.indexOf('\"', 1);
|
|
|
|
if (endIndex > 0) {
|
|
|
|
propertyValue = propertyValue.substring(1, endIndex);
|
|
|
|
}
|
|
|
|
}
|
2009-09-03 19:08:35 -04:00
|
|
|
} else if ("bday".equals(propertyName)) {
|
|
|
|
SimpleDateFormat parser = getExchangeZuluDateFormatMillisecond();
|
|
|
|
try {
|
|
|
|
Calendar calendar = Calendar.getInstance();
|
|
|
|
calendar.setTime(parser.parse(propertyValue));
|
|
|
|
item.put("birthday", String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)));
|
2009-09-09 17:50:11 -04:00
|
|
|
item.put("birthmonth", String.valueOf(calendar.get(Calendar.MONTH) + 1));
|
2009-09-03 19:08:35 -04:00
|
|
|
item.put("birthyear", String.valueOf(calendar.get(Calendar.YEAR)));
|
|
|
|
propertyValue = null;
|
|
|
|
} catch (ParseException e) {
|
|
|
|
throw new IOException(e);
|
|
|
|
}
|
2009-09-10 04:47:01 -04:00
|
|
|
} else if ("textdescription".equals(propertyName) && " \n".equals(propertyValue)) {
|
|
|
|
propertyValue = null;
|
2009-09-03 19:08:35 -04:00
|
|
|
}
|
|
|
|
if (propertyValue != null && propertyValue.length() > 0) {
|
|
|
|
item.put(propertyName, propertyValue);
|
2009-08-29 19:49:45 -04:00
|
|
|
}
|
|
|
|
}
|
2009-10-06 17:12:26 -04:00
|
|
|
if (item.get("uid") != null) {
|
|
|
|
results.put(item.get("uid"), item);
|
|
|
|
}
|
2009-08-29 19:49:45 -04:00
|
|
|
}
|
|
|
|
|
2009-09-09 17:50:11 -04:00
|
|
|
LOGGER.debug("contactFind " + ((searchFilter == null) ? "" : searchFilter) + ": " + results.size() + " result(s)");
|
2009-08-29 19:49:45 -04:00
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Get extended address book information for person with gallookup.
|
|
|
|
* Does not work with Exchange 2007
|
|
|
|
*
|
|
|
|
* @param person person attributes map
|
|
|
|
*/
|
2008-12-03 06:38:35 -05:00
|
|
|
public void galLookup(Map<String, String> person) {
|
2008-12-23 10:20:50 -05:00
|
|
|
if (!disableGalLookup) {
|
|
|
|
GetMethod getMethod = null;
|
|
|
|
try {
|
2009-04-28 17:01:40 -04:00
|
|
|
getMethod = new GetMethod(URIUtil.encodePathQuery(getCmdBasePath() + "?Cmd=gallookup&ADDR=" + person.get("EM")));
|
2009-10-16 04:58:04 -04:00
|
|
|
DavGatewayHttpClientFacade.executeGetMethod(httpClient, getMethod, true);
|
2008-12-23 10:20:50 -05:00
|
|
|
Map<String, Map<String, String>> results = XMLStreamUtil.getElementContentsAsMap(getMethod.getResponseBodyAsStream(), "person", "alias");
|
|
|
|
// add detailed information
|
2009-04-16 17:16:40 -04:00
|
|
|
if (!results.isEmpty()) {
|
2009-02-09 08:57:10 -05:00
|
|
|
Map<String, String> fullperson = results.get(person.get("AN").toLowerCase());
|
2009-08-29 19:49:45 -04:00
|
|
|
if (fullperson != null) {
|
|
|
|
for (Map.Entry<String, String> entry : fullperson.entrySet()) {
|
|
|
|
person.put(entry.getKey(), entry.getValue());
|
|
|
|
}
|
2008-12-23 10:20:50 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.warn("Unable to gallookup person: " + person + ", disable GalLookup");
|
|
|
|
disableGalLookup = true;
|
|
|
|
} finally {
|
|
|
|
if (getMethod != null) {
|
|
|
|
getMethod.releaseConnection();
|
2008-12-03 06:38:35 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Get freebusy info for attendee between start and end date.
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
|
|
|
* @param attendee attendee email
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param startDateValue start date
|
2009-08-21 05:58:19 -04:00
|
|
|
* @param endDateValue end date
|
2009-08-13 04:34:51 -04:00
|
|
|
* @return FreeBusy info
|
|
|
|
* @throws IOException on error
|
|
|
|
*/
|
|
|
|
public FreeBusy getFreebusy(String attendee, String startDateValue, String endDateValue) throws IOException {
|
2009-09-14 17:16:15 -04:00
|
|
|
attendee = replaceIcal4Principal(attendee);
|
2009-11-16 17:49:52 -05:00
|
|
|
if (attendee.startsWith("mailto:") || attendee.startsWith("MAILTO:")) {
|
2008-11-26 19:56:28 -05:00
|
|
|
attendee = attendee.substring("mailto:".length());
|
|
|
|
}
|
|
|
|
|
2009-04-11 08:27:10 -04:00
|
|
|
SimpleDateFormat exchangeZuluDateFormat = getExchangeZuluDateFormat();
|
2009-04-07 08:37:28 -04:00
|
|
|
SimpleDateFormat icalDateFormat = getZuluDateFormat();
|
2008-11-26 19:56:28 -05:00
|
|
|
|
2009-09-30 17:54:53 -04:00
|
|
|
String freebusyUrl;
|
2008-12-01 12:56:18 -05:00
|
|
|
Date startDate;
|
|
|
|
Date endDate;
|
2008-11-26 19:56:28 -05:00
|
|
|
try {
|
|
|
|
if (startDateValue.length() == 8) {
|
2009-04-07 08:37:28 -04:00
|
|
|
startDate = parseDate(startDateValue);
|
2008-11-26 19:56:28 -05:00
|
|
|
} else {
|
2009-04-07 08:37:28 -04:00
|
|
|
startDate = icalDateFormat.parse(startDateValue);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
if (endDateValue.length() == 8) {
|
2009-04-07 08:37:28 -04:00
|
|
|
endDate = parseDate(endDateValue);
|
2008-11-26 19:56:28 -05:00
|
|
|
} else {
|
2009-04-07 08:37:28 -04:00
|
|
|
endDate = icalDateFormat.parse(endDateValue);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-12-09 16:13:43 -05:00
|
|
|
freebusyUrl = publicFolderUrl + "/?cmd=freebusy" +
|
2009-04-11 08:27:10 -04:00
|
|
|
"&start=" + exchangeZuluDateFormat.format(startDate) +
|
|
|
|
"&end=" + exchangeZuluDateFormat.format(endDate) +
|
2009-03-20 12:05:45 -04:00
|
|
|
"&interval=" + FREE_BUSY_INTERVAL +
|
2008-11-26 19:56:28 -05:00
|
|
|
"&u=SMTP:" + attendee;
|
|
|
|
} catch (ParseException e) {
|
2009-04-27 19:03:58 -04:00
|
|
|
throw new DavMailException("EXCEPTION_INVALID_DATES", e.getMessage());
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
2009-03-20 12:05:45 -04:00
|
|
|
FreeBusy freeBusy = null;
|
2009-09-30 17:54:53 -04:00
|
|
|
GetMethod getMethod = new GetMethod(freebusyUrl);
|
2008-11-26 19:56:28 -05:00
|
|
|
getMethod.setRequestHeader("Content-Type", "text/xml");
|
|
|
|
|
|
|
|
try {
|
2009-10-16 04:58:04 -04:00
|
|
|
DavGatewayHttpClientFacade.executeGetMethod(httpClient, getMethod, true);
|
2009-11-03 05:01:33 -05:00
|
|
|
String fbdata = StringUtil.getLastToken(getMethod.getResponseBodyAsString(), "<a:fbdata>", "</a:fbdata>");
|
|
|
|
if (fbdata != null) {
|
2009-04-07 08:37:28 -04:00
|
|
|
freeBusy = new FreeBusy(icalDateFormat, startDate, fbdata);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
getMethod.releaseConnection();
|
|
|
|
}
|
|
|
|
|
2009-06-10 12:46:58 -04:00
|
|
|
if (freeBusy != null && freeBusy.knownAttendee) {
|
2009-07-08 18:51:34 -04:00
|
|
|
return freeBusy;
|
2009-06-10 12:46:58 -04:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2009-03-20 12:05:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Exchange to iCalendar Free/Busy parser.
|
|
|
|
* Free time returns 0, Tentative returns 1, Busy returns 2, and Out of Office (OOF) returns 3
|
|
|
|
*/
|
|
|
|
public static final class FreeBusy {
|
2009-03-25 17:59:27 -04:00
|
|
|
final SimpleDateFormat icalParser;
|
2009-03-20 12:05:45 -04:00
|
|
|
boolean knownAttendee = true;
|
|
|
|
static final HashMap<Character, String> FBTYPES = new HashMap<Character, String>();
|
|
|
|
|
|
|
|
static {
|
|
|
|
FBTYPES.put('1', "BUSY-TENTATIVE");
|
|
|
|
FBTYPES.put('2', "BUSY");
|
|
|
|
FBTYPES.put('3', "BUSY-UNAVAILABLE");
|
|
|
|
}
|
|
|
|
|
2009-03-25 17:59:27 -04:00
|
|
|
final HashMap<String, StringBuilder> busyMap = new HashMap<String, StringBuilder>();
|
2009-03-20 12:05:45 -04:00
|
|
|
|
2009-04-23 18:10:10 -04:00
|
|
|
StringBuilder getBusyBuffer(char type) {
|
2009-03-20 12:05:45 -04:00
|
|
|
String fbType = FBTYPES.get(Character.valueOf(type));
|
|
|
|
StringBuilder buffer = busyMap.get(fbType);
|
|
|
|
if (buffer == null) {
|
|
|
|
buffer = new StringBuilder();
|
|
|
|
busyMap.put(fbType, buffer);
|
|
|
|
}
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
2009-04-23 18:10:10 -04:00
|
|
|
void startBusy(char type, Calendar currentCal) {
|
2009-03-20 12:05:45 -04:00
|
|
|
if (type == '4') {
|
|
|
|
knownAttendee = false;
|
|
|
|
} else if (type != '0') {
|
|
|
|
StringBuilder busyBuffer = getBusyBuffer(type);
|
|
|
|
if (busyBuffer.length() > 0) {
|
|
|
|
busyBuffer.append(',');
|
|
|
|
}
|
|
|
|
busyBuffer.append(icalParser.format(currentCal.getTime()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-23 18:10:10 -04:00
|
|
|
void endBusy(char type, Calendar currentCal) {
|
2009-03-20 12:05:45 -04:00
|
|
|
if (type != '0' && type != '4') {
|
|
|
|
getBusyBuffer(type).append('/').append(icalParser.format(currentCal.getTime()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-04-23 18:10:10 -04:00
|
|
|
FreeBusy(SimpleDateFormat icalParser, Date startDate, String fbdata) {
|
2009-03-20 12:05:45 -04:00
|
|
|
this.icalParser = icalParser;
|
|
|
|
if (fbdata.length() > 0) {
|
|
|
|
Calendar currentCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
|
|
|
currentCal.setTime(startDate);
|
|
|
|
|
|
|
|
startBusy(fbdata.charAt(0), currentCal);
|
|
|
|
for (int i = 1; i < fbdata.length() && knownAttendee; i++) {
|
|
|
|
currentCal.add(Calendar.MINUTE, FREE_BUSY_INTERVAL);
|
|
|
|
char previousState = fbdata.charAt(i - 1);
|
|
|
|
char currentState = fbdata.charAt(i);
|
|
|
|
if (previousState != currentState) {
|
|
|
|
endBusy(previousState, currentCal);
|
|
|
|
startBusy(currentState, currentCal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currentCal.add(Calendar.MINUTE, FREE_BUSY_INTERVAL);
|
|
|
|
endBusy(fbdata.charAt(fbdata.length() - 1), currentCal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-13 04:34:51 -04:00
|
|
|
/**
|
|
|
|
* Append freebusy information to buffer.
|
2009-08-21 05:58:19 -04:00
|
|
|
*
|
2009-08-13 04:34:51 -04:00
|
|
|
* @param buffer String buffer
|
|
|
|
*/
|
2009-03-20 12:05:45 -04:00
|
|
|
public void appendTo(StringBuilder buffer) {
|
|
|
|
for (Map.Entry<String, StringBuilder> entry : busyMap.entrySet()) {
|
|
|
|
buffer.append("FREEBUSY;FBTYPE=").append(entry.getKey())
|
2009-04-23 16:53:22 -04:00
|
|
|
.append(':').append(entry.getValue()).append((char) 13).append((char) 10);
|
2009-03-20 12:05:45 -04:00
|
|
|
}
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-08-21 05:58:19 -04:00
|
|
|
|
2009-11-02 17:56:08 -05:00
|
|
|
protected final class VTimezone {
|
|
|
|
private String timezoneBody;
|
|
|
|
private String timezoneId;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* create a fake event to get VTIMEZONE body
|
|
|
|
*/
|
|
|
|
private void load() {
|
|
|
|
try {
|
|
|
|
// create temporary folder
|
|
|
|
String folderPath = ExchangeSession.this.getFolderPath("davmailtemp");
|
|
|
|
ExchangeSession.this.createCalendarFolder(folderPath);
|
|
|
|
|
2009-11-09 17:09:15 -05:00
|
|
|
PostMethod postMethod = new PostMethod(URIUtil.encodePath(folderPath));
|
2009-11-02 17:56:08 -05:00
|
|
|
postMethod.addParameter("Cmd", "saveappt");
|
|
|
|
postMethod.addParameter("FORMTYPE", "appointment");
|
|
|
|
String fakeEventUrl = null;
|
|
|
|
try {
|
|
|
|
// create fake event
|
|
|
|
int statusCode = ExchangeSession.this.httpClient.executeMethod(postMethod);
|
|
|
|
if (statusCode == HttpStatus.SC_OK) {
|
|
|
|
fakeEventUrl = StringUtil.getToken(postMethod.getResponseBodyAsString(), "<span id=\"itemHREF\">", "</span>");
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
postMethod.releaseConnection();
|
|
|
|
}
|
2009-11-09 17:09:15 -05:00
|
|
|
// failover for Exchange 2007, use PROPPATCH with forced timezone
|
|
|
|
if (fakeEventUrl == null) {
|
|
|
|
ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>();
|
2010-05-07 04:30:12 -04:00
|
|
|
propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", DAV), "urn:content-classes:appointment"));
|
2009-11-09 17:09:15 -05:00
|
|
|
propertyList.add(new DefaultDavProperty(DavPropertyName.create("outlookmessageclass", Namespace.getNamespace("http://schemas.microsoft.com/exchange/")), "IPM.Appointment"));
|
|
|
|
propertyList.add(new DefaultDavProperty(DavPropertyName.create("instancetype", Namespace.getNamespace("urn:schemas:calendar:")), "0"));
|
|
|
|
// get forced timezone id from settings
|
|
|
|
timezoneId = Settings.getProperty("davmail.timezoneId");
|
|
|
|
if (timezoneId != null) {
|
|
|
|
propertyList.add(new DefaultDavProperty(DavPropertyName.create("timezoneid", Namespace.getNamespace("urn:schemas:calendar:")), timezoneId));
|
|
|
|
}
|
2009-11-12 16:32:20 -05:00
|
|
|
String patchMethodUrl = URIUtil.encodePath(folderPath) + '/' + UUID.randomUUID().toString() + ".EML";
|
2009-11-09 17:09:15 -05:00
|
|
|
PropPatchMethod patchMethod = new PropPatchMethod(URIUtil.encodePath(patchMethodUrl), propertyList);
|
|
|
|
try {
|
|
|
|
int statusCode = httpClient.executeMethod(patchMethod);
|
|
|
|
if (statusCode == HttpStatus.SC_MULTI_STATUS) {
|
|
|
|
fakeEventUrl = patchMethodUrl;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
patchMethod.releaseConnection();
|
|
|
|
}
|
|
|
|
}
|
2009-11-02 17:56:08 -05:00
|
|
|
if (fakeEventUrl != null) {
|
|
|
|
// get fake event body
|
|
|
|
GetMethod getMethod = new GetMethod(URIUtil.encodePath(fakeEventUrl));
|
|
|
|
getMethod.setRequestHeader("Translate", "f");
|
|
|
|
try {
|
|
|
|
ExchangeSession.this.httpClient.executeMethod(getMethod);
|
|
|
|
timezoneBody = "BEGIN:VTIMEZONE" +
|
|
|
|
StringUtil.getToken(getMethod.getResponseBodyAsString(), "BEGIN:VTIMEZONE", "END:VTIMEZONE") +
|
|
|
|
"END:VTIMEZONE\r\n";
|
|
|
|
timezoneId = StringUtil.getToken(timezoneBody, "TZID:", "\r\n");
|
|
|
|
} finally {
|
|
|
|
getMethod.releaseConnection();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete temporary folder
|
|
|
|
ExchangeSession.this.deleteFolder("davmailtemp");
|
|
|
|
} catch (IOException e) {
|
|
|
|
LOGGER.warn("Unable to get VTIMEZONE info: " + e, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected VTimezone vTimezone;
|
|
|
|
|
|
|
|
protected VTimezone getVTimezone() {
|
|
|
|
if (vTimezone == null) {
|
|
|
|
// need to load Timezone info from OWA
|
|
|
|
vTimezone = new VTimezone();
|
|
|
|
vTimezone.load();
|
|
|
|
}
|
|
|
|
return vTimezone;
|
|
|
|
}
|
|
|
|
|
2009-08-21 05:58:19 -04:00
|
|
|
/**
|
|
|
|
* Return internal HttpClient instance
|
2009-08-29 16:20:44 -04:00
|
|
|
*
|
2009-08-21 05:58:19 -04:00
|
|
|
* @return http client
|
|
|
|
*/
|
|
|
|
public HttpClient getHttpClient() {
|
|
|
|
return httpClient;
|
|
|
|
}
|
2006-12-12 18:57:24 -05:00
|
|
|
}
|