Caldav: major refactoring of event content handling and notifications
git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1322 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
parent
bba9c3616a
commit
bd687b813f
|
@ -39,8 +39,6 @@ import javax.mail.Address;
|
||||||
import javax.mail.MessagingException;
|
import javax.mail.MessagingException;
|
||||||
import javax.mail.internet.InternetAddress;
|
import javax.mail.internet.InternetAddress;
|
||||||
import javax.mail.internet.MimeMessage;
|
import javax.mail.internet.MimeMessage;
|
||||||
import javax.mail.internet.MimeMultipart;
|
|
||||||
import javax.mail.internet.MimePart;
|
|
||||||
import javax.mail.util.SharedByteArrayInputStream;
|
import javax.mail.util.SharedByteArrayInputStream;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.NoRouteToHostException;
|
import java.net.NoRouteToHostException;
|
||||||
|
@ -1821,15 +1819,15 @@ public abstract class ExchangeSession {
|
||||||
*/
|
*/
|
||||||
public abstract class Event extends Item {
|
public abstract class Event extends Item {
|
||||||
protected String contentClass;
|
protected String contentClass;
|
||||||
protected String itemBody;
|
protected VCalendar vCalendar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) {
|
public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) throws IOException {
|
||||||
super(folderPath, itemName, etag, noneMatch);
|
super(folderPath, itemName, etag, noneMatch);
|
||||||
this.contentClass = contentClass;
|
this.contentClass = contentClass;
|
||||||
this.itemBody = itemBody;
|
fixICS(itemBody.getBytes("UTF-8"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1843,116 +1841,36 @@ public abstract class ExchangeSession {
|
||||||
return "text/calendar;charset=UTF-8";
|
return "text/calendar;charset=UTF-8";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBody() throws IOException {
|
||||||
|
if (vCalendar == null) {
|
||||||
|
fixICS(getEventContent(), true);
|
||||||
|
}
|
||||||
|
return vCalendar.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load ICS content from MIME message input stream
|
* Retrieve item body from Exchange
|
||||||
*
|
*
|
||||||
* @param mimeInputStream mime message input stream
|
* @return item content
|
||||||
* @return mime message ics attachment body
|
* @throws HttpException on error
|
||||||
* @throws IOException on error
|
|
||||||
* @throws MessagingException on error
|
|
||||||
*/
|
*/
|
||||||
protected String getICS(InputStream mimeInputStream) throws IOException, MessagingException {
|
public abstract byte[] getEventContent() throws IOException;
|
||||||
String result;
|
|
||||||
MimeMessage mimeMessage = new MimeMessage(null, mimeInputStream);
|
|
||||||
Object mimeBody = mimeMessage.getContent();
|
|
||||||
MimePart bodyPart = null;
|
|
||||||
if (mimeBody instanceof MimeMultipart) {
|
|
||||||
bodyPart = getCalendarMimePart((MimeMultipart) mimeBody);
|
|
||||||
} else if (isCalendarContentType(mimeMessage.getContentType())) {
|
|
||||||
// no multipart, single body
|
|
||||||
bodyPart = mimeMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bodyPart != null) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
bodyPart.getDataHandler().writeTo(baos);
|
|
||||||
baos.close();
|
|
||||||
result = new String(baos.toByteArray(), "UTF-8");
|
|
||||||
} else {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
mimeMessage.writeTo(baos);
|
|
||||||
baos.close();
|
|
||||||
throw new DavMailException("EXCEPTION_INVALID_MESSAGE_CONTENT", new String(baos.toByteArray(), "UTF-8"));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static final String TEXT_CALENDAR = "text/calendar";
|
|
||||||
protected static final String APPLICATION_ICS = "application/ics";
|
|
||||||
|
|
||||||
protected boolean isCalendarContentType(String contentType) {
|
|
||||||
return TEXT_CALENDAR.regionMatches(true, 0, contentType, 0, TEXT_CALENDAR.length()) ||
|
|
||||||
APPLICATION_ICS.regionMatches(true, 0, contentType, 0, APPLICATION_ICS.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
if (isCalendarContentType(contentType)) {
|
|
||||||
bodyPart = (MimePart) multiPart.getBodyPart(i);
|
|
||||||
break;
|
|
||||||
} else if (contentType.startsWith("multipart")) {
|
|
||||||
Object content = multiPart.getBodyPart(i).getContent();
|
|
||||||
if (content instanceof MimeMultipart) {
|
|
||||||
bodyPart = getCalendarMimePart((MimeMultipart) content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bodyPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
int keyIndex = line.indexOf(';');
|
|
||||||
if (keyIndex == -1) {
|
|
||||||
keyIndex = valueIndex;
|
|
||||||
}
|
|
||||||
String dateValue = line.substring(valueIndex + 1, valueEndIndex);
|
|
||||||
String key = line.substring(0, Math.min(keyIndex, valueIndex));
|
|
||||||
return key + ";VALUE=DATE:" + dateValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String fixICS(String icsBody, boolean fromServer) throws IOException {
|
|
||||||
|
|
||||||
dumpIndex++;
|
|
||||||
dumpICS(icsBody, fromServer, false);
|
|
||||||
|
|
||||||
|
protected void fixICS(byte[] icsContent, boolean fromServer) throws IOException {
|
||||||
if (LOGGER.isDebugEnabled() && fromServer) {
|
if (LOGGER.isDebugEnabled() && fromServer) {
|
||||||
LOGGER.debug("Vcalendar body received from server:\n" +icsBody);
|
dumpIndex++;
|
||||||
|
String icsBody = new String(icsContent);
|
||||||
|
dumpICS(icsBody, fromServer, false);
|
||||||
|
LOGGER.debug("Vcalendar body received from server:\n" + icsBody);
|
||||||
}
|
}
|
||||||
VCalendar vCalendar = new VCalendar(icsBody, getEmail(), getVTimezone());
|
vCalendar = new VCalendar(icsContent, getEmail(), getVTimezone());
|
||||||
vCalendar.fixVCalendar(fromServer);
|
vCalendar.fixVCalendar(fromServer);
|
||||||
String resultString = vCalendar.toString();
|
|
||||||
if (LOGGER.isDebugEnabled() && !fromServer) {
|
if (LOGGER.isDebugEnabled() && !fromServer) {
|
||||||
LOGGER.debug("Fixed Vcalendar body to server:\n" +resultString);
|
String resultString = vCalendar.toString();
|
||||||
}
|
LOGGER.debug("Fixed Vcalendar body to server:\n" + resultString);
|
||||||
dumpICS(resultString, fromServer, true);
|
dumpICS(resultString, fromServer, true);
|
||||||
|
}
|
||||||
return resultString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void dumpICS(String icsBody, boolean fromServer, boolean after) {
|
protected void dumpICS(String icsBody, boolean fromServer, boolean after) {
|
||||||
|
@ -2020,126 +1938,6 @@ public abstract class ExchangeSession {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
String optionalAttendees;
|
|
||||||
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>();
|
|
||||||
HashSet<String> optionalAttendees = new HashSet<String>();
|
|
||||||
String organizer = null;
|
|
||||||
BufferedReader reader = null;
|
|
||||||
try {
|
|
||||||
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);
|
|
||||||
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
|
|
||||||
// return all attendees for user calendar folder, filter for notifications
|
|
||||||
&& (!isNotification
|
|
||||||
// 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))
|
|
||||||
))) {
|
|
||||||
if (line.indexOf("ROLE=OPT-PARTICIPANT") >= 0) {
|
|
||||||
optionalAttendees.add(value);
|
|
||||||
} else {
|
|
||||||
attendees.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (reader != null) {
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Participants participants = new Participants();
|
|
||||||
participants.attendees = StringUtil.join(attendees, ", ");
|
|
||||||
participants.optionalAttendees = StringUtil.join(optionalAttendees, ", ");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build Mime body for event or event message.
|
* Build Mime body for event or event message.
|
||||||
*
|
*
|
||||||
|
@ -2150,7 +1948,7 @@ public abstract class ExchangeSession {
|
||||||
String boundary = UUID.randomUUID().toString();
|
String boundary = UUID.randomUUID().toString();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
MimeOutputStreamWriter writer = new MimeOutputStreamWriter(baos);
|
MimeOutputStreamWriter writer = new MimeOutputStreamWriter(baos);
|
||||||
String method = getICSMethod(itemBody);
|
String method = vCalendar.getMethod();
|
||||||
|
|
||||||
writer.writeHeader("Content-Transfer-Encoding", "7bit");
|
writer.writeHeader("Content-Transfer-Encoding", "7bit");
|
||||||
writer.writeHeader("Content-class", contentClass);
|
writer.writeHeader("Content-class", contentClass);
|
||||||
|
@ -2158,53 +1956,54 @@ public abstract class ExchangeSession {
|
||||||
writer.writeHeader("Date", new Date());
|
writer.writeHeader("Date", new Date());
|
||||||
|
|
||||||
// Make sure invites have a proper subject line
|
// Make sure invites have a proper subject line
|
||||||
writer.writeHeader("Subject", getICSSummary(itemBody));
|
// TODO: get current user attendee status, i18n
|
||||||
|
String subject = vCalendar.getFirstVevent().getPropertyValue("SUMMARY");
|
||||||
|
if (subject == null) {
|
||||||
|
subject = BundleMessage.format("MEETING_REQUEST");
|
||||||
|
}
|
||||||
|
writer.writeHeader("Subject", subject);
|
||||||
|
|
||||||
if ("urn:content-classes:calendarmessage".equals(contentClass)) {
|
if ("urn:content-classes:calendarmessage".equals(contentClass)) {
|
||||||
// need to parse attendees and organizer to build recipients
|
// need to parse attendees and organizer to build recipients
|
||||||
Participants participants = getParticipants(true);
|
VCalendar.Recipients recipients = vCalendar.getRecipients(true);
|
||||||
if (email.equalsIgnoreCase(participants.organizer)) {
|
if (email.equalsIgnoreCase(recipients.organizer)) {
|
||||||
// current user is organizer => notify all
|
// current user is organizer => notify all
|
||||||
writer.writeHeader("To", participants.attendees);
|
writer.writeHeader("To", recipients.attendees);
|
||||||
writer.writeHeader("Cc", participants.optionalAttendees);
|
writer.writeHeader("Cc", recipients.optionalAttendees);
|
||||||
// do not send notification if no recipients found
|
// do not send notification if no recipients found
|
||||||
if (participants.attendees == null && participants.optionalAttendees == null) {
|
if (recipients.attendees == null && recipients.optionalAttendees == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// notify only organizer
|
// notify only organizer
|
||||||
writer.writeHeader("To", participants.organizer);
|
writer.writeHeader("To", recipients.organizer);
|
||||||
// do not send notification if no recipients found
|
// do not send notification if no recipients found
|
||||||
if (participants.organizer == null) {
|
if (recipients.organizer == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// need to parse attendees and organizer to build recipients
|
// need to parse attendees and organizer to build recipients
|
||||||
Participants participants = getParticipants(false);
|
VCalendar.Recipients recipients = vCalendar.getRecipients(false);
|
||||||
// storing appointment, full recipients header
|
// storing appointment, full recipients header
|
||||||
if (participants.attendees != null) {
|
if (recipients.attendees != null) {
|
||||||
writer.writeHeader("To", participants.attendees);
|
writer.writeHeader("To", recipients.attendees);
|
||||||
} else {
|
} else {
|
||||||
// use current user as attendee
|
// use current user as attendee
|
||||||
writer.writeHeader("To", email);
|
writer.writeHeader("To", email);
|
||||||
}
|
}
|
||||||
writer.writeHeader("Cc", participants.optionalAttendees);
|
writer.writeHeader("Cc", recipients.optionalAttendees);
|
||||||
|
|
||||||
if (participants.organizer != null) {
|
if (recipients.organizer != null) {
|
||||||
writer.writeHeader("From", participants.organizer);
|
writer.writeHeader("From", recipients.organizer);
|
||||||
} else {
|
} else {
|
||||||
writer.writeHeader("From", email);
|
writer.writeHeader("From", email);
|
||||||
}
|
}
|
||||||
// if not organizer, set REPLYTIME to force Outlook in attendee mode
|
// if not organizer, set REPLYTIME to force Outlook in attendee mode
|
||||||
if (participants.organizer != null && !email.equalsIgnoreCase(participants.organizer)) {
|
if (recipients.organizer != null && !email.equalsIgnoreCase(recipients.organizer)) {
|
||||||
if (itemBody.indexOf("METHOD:") < 0) {
|
if (method == null) {
|
||||||
itemBody = itemBody.replaceAll("BEGIN:VCALENDAR", "BEGIN:VCALENDAR\r\nMETHOD:REQUEST");
|
vCalendar.setPropertyValue("METHOD", "REQUEST");
|
||||||
}
|
|
||||||
if (itemBody.indexOf("X-MICROSOFT-CDO-REPLYTIME") < 0) {
|
|
||||||
itemBody = itemBody.replaceAll("END:VEVENT", "X-MICROSOFT-CDO-REPLYTIME:" +
|
|
||||||
getZuluDateFormat().format(new Date()) + "\r\nEND:VEVENT");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2218,9 +2017,9 @@ public abstract class ExchangeSession {
|
||||||
|
|
||||||
// Write a part of the message that contains the
|
// Write a part of the message that contains the
|
||||||
// ICS description so that invites contain the description text
|
// ICS description so that invites contain the description text
|
||||||
String description = getICSDescription(itemBody).replaceAll("\\\\[Nn]", "\r\n");
|
String description = vCalendar.getFirstVevent().getPropertyValue("DESCRIPTION");
|
||||||
|
|
||||||
if (description.length() > 0) {
|
if (description != null && description.length() > 0) {
|
||||||
writer.writeHeader("Content-Type", "text/plain;\r\n" +
|
writer.writeHeader("Content-Type", "text/plain;\r\n" +
|
||||||
"\tcharset=\"utf-8\"");
|
"\tcharset=\"utf-8\"");
|
||||||
writer.writeHeader("content-transfer-encoding", "8bit");
|
writer.writeHeader("content-transfer-encoding", "8bit");
|
||||||
|
@ -2238,7 +2037,7 @@ public abstract class ExchangeSession {
|
||||||
writer.writeHeader("Content-Transfer-Encoding", "8bit");
|
writer.writeHeader("Content-Transfer-Encoding", "8bit");
|
||||||
writer.writeLn();
|
writer.writeLn();
|
||||||
writer.flush();
|
writer.flush();
|
||||||
baos.write(fixICS(itemBody, false).getBytes("UTF-8"));
|
baos.write(vCalendar.toString().getBytes("UTF-8"));
|
||||||
writer.writeLn();
|
writer.writeLn();
|
||||||
writer.writeLn("------=_NextPart_" + boundary + "--");
|
writer.writeLn("------=_NextPart_" + boundary + "--");
|
||||||
writer.close();
|
writer.close();
|
||||||
|
@ -2251,21 +2050,7 @@ public abstract class ExchangeSession {
|
||||||
* @return action result
|
* @return action result
|
||||||
* @throws IOException on error
|
* @throws IOException on error
|
||||||
*/
|
*/
|
||||||
public ItemResult createOrUpdate() throws IOException {
|
public abstract ItemResult createOrUpdate() throws IOException;
|
||||||
byte[] mimeContent = createMimeContent();
|
|
||||||
ItemResult itemResult;
|
|
||||||
if (mimeContent != null) {
|
|
||||||
itemResult = createOrUpdate(mimeContent);
|
|
||||||
} else {
|
|
||||||
itemResult = new ItemResult();
|
|
||||||
itemResult.status = HttpStatus.SC_NO_CONTENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemResult;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract ItemResult createOrUpdate(byte[] mimeContent) throws IOException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2546,7 +2331,7 @@ public abstract class ExchangeSession {
|
||||||
properties.put("outlookmessageclass", "IPM.Contact");
|
properties.put("outlookmessageclass", "IPM.Contact");
|
||||||
|
|
||||||
VObject vcard = new VObject(new ICSBufferedReader(new StringReader(itemBody)));
|
VObject vcard = new VObject(new ICSBufferedReader(new StringReader(itemBody)));
|
||||||
for (VProperty property:vcard.getProperties()) {
|
for (VProperty property : vcard.getProperties()) {
|
||||||
if ("FN".equals(property.getKey())) {
|
if ("FN".equals(property.getKey())) {
|
||||||
properties.put("cn", property.getValue());
|
properties.put("cn", property.getValue());
|
||||||
properties.put("subject", property.getValue());
|
properties.put("subject", property.getValue());
|
||||||
|
@ -3220,21 +3005,6 @@ public abstract class ExchangeSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Timezone structure
|
|
||||||
*/
|
|
||||||
public static final class VTimezone {
|
|
||||||
/**
|
|
||||||
* Timezone iCalendar body
|
|
||||||
*/
|
|
||||||
public String timezoneBody;
|
|
||||||
/**
|
|
||||||
* Timezone id
|
|
||||||
*/
|
|
||||||
public String timezoneId;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected VObject vTimezone;
|
protected VObject vTimezone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,11 +19,13 @@
|
||||||
package davmail.exchange;
|
package davmail.exchange;
|
||||||
|
|
||||||
import davmail.Settings;
|
import davmail.Settings;
|
||||||
|
import davmail.util.StringUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
import java.util.Date;
|
||||||
import java.io.StringReader;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VCalendar object.
|
* VCalendar object.
|
||||||
|
@ -67,6 +69,18 @@ public class VCalendar extends VObject {
|
||||||
this(new ICSBufferedReader(new StringReader(vCalendarBody)), email, vTimezone);
|
this(new ICSBufferedReader(new StringReader(vCalendarBody)), email, vTimezone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create VCalendar object from string;
|
||||||
|
*
|
||||||
|
* @param vCalendarContent item content
|
||||||
|
* @param email current user email
|
||||||
|
* @param vTimezone user OWA timezone
|
||||||
|
* @throws IOException on error
|
||||||
|
*/
|
||||||
|
public VCalendar(byte[] vCalendarContent, String email, VObject vTimezone) throws IOException {
|
||||||
|
this(new ICSBufferedReader(new InputStreamReader(new ByteArrayInputStream(vCalendarContent), "UTF-8")), email, vTimezone);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addVObject(VObject vObject) {
|
protected void addVObject(VObject vObject) {
|
||||||
super.addVObject(vObject);
|
super.addVObject(vObject);
|
||||||
|
@ -83,28 +97,19 @@ public class VCalendar extends VObject {
|
||||||
return dtstart != null && dtstart.hasParam("VALUE", "DATE");
|
return dtstart != null && dtstart.hasParam("VALUE", "DATE");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean hasCdoAllDay(VObject vObject) {
|
|
||||||
return vObject.getProperty("X-MICROSOFT-CDO-ALLDAYEVENT") != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean hasCdoBusyStatus(VObject vObject) {
|
|
||||||
return vObject.getProperty("X-MICROSOFT-CDO-BUSYSTATUS") != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isCdoAllDay(VObject vObject) {
|
protected boolean isCdoAllDay(VObject vObject) {
|
||||||
return "TRUE".equals(vObject.getPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT"));
|
return "TRUE".equals(vObject.getPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isAppleiCal() {
|
protected String getEmailValue(VProperty property) {
|
||||||
return getPropertyValue("PRODID").contains("iCal");
|
if (property == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
String propertyValue = property.getValue();
|
||||||
protected String getOrganizer() {
|
if (propertyValue != null && (propertyValue.startsWith("MAILTO:") || propertyValue.startsWith("mailto:"))) {
|
||||||
String organizer = firstVevent.getPropertyValue("ORGANIZER");
|
return propertyValue.substring(7);
|
||||||
if (organizer.startsWith("MAILTO:")) {
|
|
||||||
return organizer.substring(7);
|
|
||||||
} else {
|
} else {
|
||||||
return organizer;
|
return propertyValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,23 +118,21 @@ public class VCalendar extends VObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void fixVCalendar(boolean fromServer) {
|
protected void fixVCalendar(boolean fromServer) {
|
||||||
// append missing method
|
|
||||||
if (getProperty("METHOD") == null) {
|
|
||||||
setPropertyValue("METHOD", "PUBLISH");
|
|
||||||
}
|
|
||||||
// iCal 4 global private flag
|
// iCal 4 global private flag
|
||||||
if (fromServer) {
|
if (fromServer) {
|
||||||
setPropertyValue("X-CALENDARSERVER-ACCESS", getCalendarServerAccess());
|
setPropertyValue("X-CALENDARSERVER-ACCESS", getCalendarServerAccess());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iCal 4 global X-CALENDARSERVER-ACCESS
|
||||||
String calendarServerAccess = getPropertyValue("X-CALENDARSERVER-ACCESS");
|
String calendarServerAccess = getPropertyValue("X-CALENDARSERVER-ACCESS");
|
||||||
|
String now = ExchangeSession.getZuluDateFormat().format(new Date());
|
||||||
|
|
||||||
// TODO: patch timezone for iPhone
|
|
||||||
// iterate over vObjects
|
// iterate over vObjects
|
||||||
for (VObject vObject : vObjects) {
|
for (VObject vObject : vObjects) {
|
||||||
if ("VEVENT".equals(vObject.type)) {
|
if ("VEVENT".equals(vObject.type)) {
|
||||||
if (calendarServerAccess != null) {
|
if (calendarServerAccess != null) {
|
||||||
vObject.setPropertyValue("CLASS", getEventClass(calendarServerAccess));
|
vObject.setPropertyValue("CLASS", getEventClass(calendarServerAccess));
|
||||||
|
// iCal 3, get X-CALENDARSERVER-ACCESS from local VEVENT
|
||||||
} else if (vObject.getPropertyValue("X-CALENDARSERVER-ACCESS") != null) {
|
} else if (vObject.getPropertyValue("X-CALENDARSERVER-ACCESS") != null) {
|
||||||
vObject.setPropertyValue("CLASS", getEventClass(vObject.getPropertyValue("X-CALENDARSERVER-ACCESS")));
|
vObject.setPropertyValue("CLASS", getEventClass(vObject.getPropertyValue("X-CALENDARSERVER-ACCESS")));
|
||||||
}
|
}
|
||||||
|
@ -143,17 +146,24 @@ public class VCalendar extends VObject {
|
||||||
setClientAllday(vObject.getProperty("DTSTART"));
|
setClientAllday(vObject.getProperty("DTSTART"));
|
||||||
setClientAllday(vObject.getProperty("DTEND"));
|
setClientAllday(vObject.getProperty("DTEND"));
|
||||||
}
|
}
|
||||||
|
String cdoBusyStatus = vObject.getPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS");
|
||||||
|
if (cdoBusyStatus != null) {
|
||||||
vObject.setPropertyValue("TRANSP",
|
vObject.setPropertyValue("TRANSP",
|
||||||
!"FREE".equals(vObject.getPropertyValue("X-MICROSOFT-CDO-BUSYSTATUS")) ? "OPAQUE" : "TRANSPARENT");
|
!"FREE".equals(cdoBusyStatus) ? "OPAQUE" : "TRANSPARENT");
|
||||||
|
}
|
||||||
|
|
||||||
// TODO splitExDate
|
|
||||||
// Apple iCal doesn't understand this key, and it's entourage
|
// Apple iCal doesn't understand this key, and it's entourage
|
||||||
// specific (i.e. not needed by any caldav client): strip it out
|
// specific (i.e. not needed by any caldav client): strip it out
|
||||||
vObject.removeProperty("X-ENTOURAGE_UUID");
|
vObject.removeProperty("X-ENTOURAGE_UUID");
|
||||||
|
|
||||||
|
splitExDate(vObject);
|
||||||
} else {
|
} else {
|
||||||
// add organizer line to all events created in Exchange for active sync
|
// add organizer line to all events created in Exchange for active sync
|
||||||
if (vObject.getPropertyValue("ORGANIZER") == null) {
|
String organizer = getEmailValue(vObject.getProperty("ORGANIZER"));
|
||||||
|
if (organizer == null) {
|
||||||
vObject.setPropertyValue("ORGANIZER", "MAILTO:" + email);
|
vObject.setPropertyValue("ORGANIZER", "MAILTO:" + email);
|
||||||
|
} else if (!email.equalsIgnoreCase(organizer) && vObject.getProperty("X-MICROSOFT-CDO-REPLYTIME") == null) {
|
||||||
|
vObject.setPropertyValue("X-MICROSOFT-CDO-REPLYTIME", now);
|
||||||
}
|
}
|
||||||
// set OWA allday flag
|
// set OWA allday flag
|
||||||
vObject.setPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT", isAllDay(vObject) ? "TRUE" : "FALSE");
|
vObject.setPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT", isAllDay(vObject) ? "TRUE" : "FALSE");
|
||||||
|
@ -169,15 +179,31 @@ public class VCalendar extends VObject {
|
||||||
|
|
||||||
fixAttendees(vObject, fromServer);
|
fixAttendees(vObject, fromServer);
|
||||||
|
|
||||||
// TODO handle BUSYSTATUS
|
|
||||||
|
|
||||||
fixAlarm(vObject, fromServer);
|
fixAlarm(vObject, fromServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setServerAllday(VProperty property) {
|
protected void splitExDate(VObject vObject) {
|
||||||
|
List<VProperty> exDateProperties = vObject.getProperties("EXDATE");
|
||||||
|
if (exDateProperties != null) {
|
||||||
|
for (VProperty property : exDateProperties) {
|
||||||
|
String value = property.getValue();
|
||||||
|
if (value.indexOf(',') >= 0) {
|
||||||
|
// split property
|
||||||
|
vObject.removeProperty(property);
|
||||||
|
for (String singleValue : value.split(",")) {
|
||||||
|
VProperty singleProperty = new VProperty("EXDATE", singleValue);
|
||||||
|
singleProperty.setParams(property.getParams());
|
||||||
|
vObject.addProperty(singleProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setServerAllday(VProperty property) {
|
||||||
// set TZID param
|
// set TZID param
|
||||||
if (!property.hasParam("TZID")) {
|
if (!property.hasParam("TZID")) {
|
||||||
property.addParam("TZID", vTimezone.getPropertyValue("TZID"));
|
property.addParam("TZID", vTimezone.getPropertyValue("TZID"));
|
||||||
|
@ -288,6 +314,7 @@ public class VCalendar extends VObject {
|
||||||
* Convert X-CALENDARSERVER-ACCESS to CLASS.
|
* Convert X-CALENDARSERVER-ACCESS to CLASS.
|
||||||
* see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt
|
* see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt
|
||||||
*
|
*
|
||||||
|
* @param calendarServerAccess X-CALENDARSERVER-ACCESS value
|
||||||
* @return CLASS value
|
* @return CLASS value
|
||||||
*/
|
*/
|
||||||
protected String getEventClass(String calendarServerAccess) {
|
protected String getEventClass(String calendarServerAccess) {
|
||||||
|
@ -303,6 +330,7 @@ public class VCalendar extends VObject {
|
||||||
/**
|
/**
|
||||||
* Convert CLASS to X-CALENDARSERVER-ACCESS.
|
* Convert CLASS to X-CALENDARSERVER-ACCESS.
|
||||||
* see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt *
|
* see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt *
|
||||||
|
*
|
||||||
* @return X-CALENDARSERVER-ACCESS value
|
* @return X-CALENDARSERVER-ACCESS value
|
||||||
*/
|
*/
|
||||||
protected String getCalendarServerAccess() {
|
protected String getCalendarServerAccess() {
|
||||||
|
@ -316,4 +344,54 @@ public class VCalendar extends VObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VObject getFirstVevent() {
|
||||||
|
return firstVevent;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Recipients {
|
||||||
|
String attendees;
|
||||||
|
String optionalAttendees;
|
||||||
|
String organizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Recipients getRecipients(boolean isNotification) {
|
||||||
|
|
||||||
|
HashSet<String> attendees = new HashSet<String>();
|
||||||
|
HashSet<String> optionalAttendees = new HashSet<String>();
|
||||||
|
|
||||||
|
// get recipients from first VEVENT
|
||||||
|
List<VProperty> attendeeProperties = firstVevent.getProperties("ATTENDEE");
|
||||||
|
if (attendeeProperties != null) {
|
||||||
|
for (VProperty property : attendeeProperties) {
|
||||||
|
// exclude current user and invalid values from recipients
|
||||||
|
// also exclude no action attendees
|
||||||
|
String attendeeEmail = getEmailValue(property);
|
||||||
|
if (!email.equalsIgnoreCase(attendeeEmail) && attendeeEmail.indexOf('@') >= 0
|
||||||
|
// return all attendees for user calendar folder, filter for notifications
|
||||||
|
&& (!isNotification
|
||||||
|
// notify attendee if reply explicitly requested
|
||||||
|
|| (property.hasParam("RSVP", "TRUE"))
|
||||||
|
|| (
|
||||||
|
// workaround for iCal bug: do not notify if reply explicitly not requested
|
||||||
|
!(property.hasParam("RSVP", "FALSE")) &&
|
||||||
|
((property.hasParam("PARTSTAT", "NEEDS-ACTION")
|
||||||
|
// need to include other PARTSTATs participants for CANCEL notifications
|
||||||
|
|| property.hasParam("PARTSTAT", "ACCEPTED")
|
||||||
|
|| property.hasParam("PARTSTAT", "DECLINED")
|
||||||
|
|| property.hasParam("PARTSTAT", "TENTATIVE")))
|
||||||
|
))) {
|
||||||
|
if (property.hasParam("ROLE", "OPT-PARTICIPANT")) {
|
||||||
|
optionalAttendees.add(attendeeEmail);
|
||||||
|
} else {
|
||||||
|
attendees.add(attendeeEmail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Recipients recipients = new Recipients();
|
||||||
|
recipients.organizer = getEmailValue(firstVevent.getProperty("ORGANIZER"));
|
||||||
|
recipients.attendees = StringUtil.join(attendees, ", ");
|
||||||
|
recipients.optionalAttendees = StringUtil.join(optionalAttendees, ", ");
|
||||||
|
return recipients;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,22 @@ public class VObject {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<VProperty> getProperties(String name) {
|
||||||
|
List<VProperty> result = null;
|
||||||
|
if (properties != null) {
|
||||||
|
for (VProperty property : properties) {
|
||||||
|
if (property.getKey().equalsIgnoreCase(name)) {
|
||||||
|
if (result == null) {
|
||||||
|
result = new ArrayList<VProperty>();
|
||||||
|
}
|
||||||
|
result.add(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPropertyValue(String name) {
|
public String getPropertyValue(String name) {
|
||||||
VProperty property = getProperty(name);
|
VProperty property = getProperty(name);
|
||||||
if (property != null) {
|
if (property != null) {
|
||||||
|
@ -184,11 +200,17 @@ public class VObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeProperty(String name) {
|
public void removeProperty(String name) {
|
||||||
if (vObjects != null) {
|
if (properties != null) {
|
||||||
VProperty property = getProperty(name);
|
VProperty property = getProperty(name);
|
||||||
if (property != null) {
|
if (property != null) {
|
||||||
vObjects.remove(property);
|
properties.remove(property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeProperty(VProperty property) {
|
||||||
|
if (properties != null) {
|
||||||
|
properties.remove(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class VProperty {
|
||||||
KEY, PARAM_NAME, PARAM_VALUE, QUOTED_PARAM_VALUE, VALUE, BACKSLASH
|
KEY, PARAM_NAME, PARAM_VALUE, QUOTED_PARAM_VALUE, VALUE, BACKSLASH
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class Param {
|
protected static class Param {
|
||||||
String name;
|
String name;
|
||||||
List<String> values;
|
List<String> values;
|
||||||
|
|
||||||
|
@ -53,6 +53,12 @@ public class VProperty {
|
||||||
protected List<Param> params;
|
protected List<Param> params;
|
||||||
protected List<String> values;
|
protected List<String> values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create VProperty for key and value.
|
||||||
|
*
|
||||||
|
* @param name property name
|
||||||
|
* @param value property value
|
||||||
|
*/
|
||||||
public VProperty(String name, String value) {
|
public VProperty(String name, String value) {
|
||||||
setKey(name);
|
setKey(name);
|
||||||
setValue(value);
|
setValue(value);
|
||||||
|
@ -197,6 +203,11 @@ public class VProperty {
|
||||||
return params != null && getParam(paramName) != null;
|
return params != null && getParam(paramName) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove param from property.
|
||||||
|
*
|
||||||
|
* @param paramName param name
|
||||||
|
*/
|
||||||
public void removeParam(String paramName) {
|
public void removeParam(String paramName) {
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
Param param = getParam(paramName);
|
Param param = getParam(paramName);
|
||||||
|
@ -220,7 +231,7 @@ public class VProperty {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addParam(String paramName, String paramValue) {
|
protected void addParam(String paramName, String paramValue) {
|
||||||
List paramValues = new ArrayList();
|
List<String> paramValues = new ArrayList<String>();
|
||||||
paramValues.add(paramValue);
|
paramValues.add(paramValue);
|
||||||
addParam(paramName, paramValues);
|
addParam(paramName, paramValues);
|
||||||
}
|
}
|
||||||
|
@ -249,6 +260,14 @@ public class VProperty {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<Param> getParams() {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setParams(List<Param> params) {
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
protected void setValue(String value) {
|
protected void setValue(String value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
values = null;
|
values = null;
|
||||||
|
@ -280,7 +299,7 @@ public class VProperty {
|
||||||
if (c == '\\') {
|
if (c == '\\') {
|
||||||
//noinspection AssignmentToForLoopParameter
|
//noinspection AssignmentToForLoopParameter
|
||||||
c = value.charAt(++i);
|
c = value.charAt(++i);
|
||||||
if (c == 'n') {
|
if (c == 'n' || c == 'N') {
|
||||||
c = '\n';
|
c = '\n';
|
||||||
} else if (c == 'r') {
|
} else if (c == 'r') {
|
||||||
c = '\r';
|
c = '\r';
|
||||||
|
|
|
@ -47,6 +47,9 @@ import org.apache.jackrabbit.webdav.property.DavPropertySet;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
import javax.mail.MessagingException;
|
import javax.mail.MessagingException;
|
||||||
|
import javax.mail.internet.MimeMessage;
|
||||||
|
import javax.mail.internet.MimeMultipart;
|
||||||
|
import javax.mail.internet.MimePart;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
@ -763,12 +766,72 @@ public class DavExchangeSession extends ExchangeSession {
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) {
|
public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) throws IOException {
|
||||||
super(folderPath, itemName, contentClass, itemBody, etag, noneMatch);
|
super(folderPath, itemName, contentClass, itemBody, etag, noneMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getICSFromInternetContentProperty() throws IOException, DavException, MessagingException {
|
protected static final String TEXT_CALENDAR = "text/calendar";
|
||||||
String result = null;
|
protected static final String APPLICATION_ICS = "application/ics";
|
||||||
|
|
||||||
|
protected boolean isCalendarContentType(String contentType) {
|
||||||
|
return TEXT_CALENDAR.regionMatches(true, 0, contentType, 0, TEXT_CALENDAR.length()) ||
|
||||||
|
APPLICATION_ICS.regionMatches(true, 0, contentType, 0, APPLICATION_ICS.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
if (isCalendarContentType(contentType)) {
|
||||||
|
bodyPart = (MimePart) multiPart.getBodyPart(i);
|
||||||
|
break;
|
||||||
|
} else if (contentType.startsWith("multipart")) {
|
||||||
|
Object content = multiPart.getBodyPart(i).getContent();
|
||||||
|
if (content instanceof MimeMultipart) {
|
||||||
|
bodyPart = getCalendarMimePart((MimeMultipart) content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bodyPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 byte[] getICS(InputStream mimeInputStream) throws IOException, MessagingException {
|
||||||
|
byte[] result;
|
||||||
|
MimeMessage mimeMessage = new MimeMessage(null, mimeInputStream);
|
||||||
|
Object mimeBody = mimeMessage.getContent();
|
||||||
|
MimePart bodyPart = null;
|
||||||
|
if (mimeBody instanceof MimeMultipart) {
|
||||||
|
bodyPart = getCalendarMimePart((MimeMultipart) mimeBody);
|
||||||
|
} else if (isCalendarContentType(mimeMessage.getContentType())) {
|
||||||
|
// no multipart, single body
|
||||||
|
bodyPart = mimeMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyPart != null) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
bodyPart.getDataHandler().writeTo(baos);
|
||||||
|
baos.close();
|
||||||
|
result = baos.toByteArray();
|
||||||
|
} else {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
mimeMessage.writeTo(baos);
|
||||||
|
baos.close();
|
||||||
|
throw new DavMailException("EXCEPTION_INVALID_MESSAGE_CONTENT", new String(baos.toByteArray(), "UTF-8"));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] getICSFromInternetContentProperty() throws IOException, DavException, MessagingException {
|
||||||
|
byte[] result = null;
|
||||||
// PropFind PR_INTERNET_CONTENT
|
// PropFind PR_INTERNET_CONTENT
|
||||||
DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
|
DavPropertyNameSet davPropertyNameSet = new DavPropertyNameSet();
|
||||||
davPropertyNameSet.add(Field.getPropertyName("internetContent"));
|
davPropertyNameSet.add(Field.getPropertyName("internetContent"));
|
||||||
|
@ -798,8 +861,8 @@ public class DavExchangeSession extends ExchangeSession {
|
||||||
* @throws HttpException on error
|
* @throws HttpException on error
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getBody() throws IOException {
|
public byte[] getEventContent() throws IOException {
|
||||||
String result;
|
byte[] result;
|
||||||
LOGGER.debug("Get event: " + permanentUrl);
|
LOGGER.debug("Get event: " + permanentUrl);
|
||||||
// try to get PR_INTERNET_CONTENT
|
// try to get PR_INTERNET_CONTENT
|
||||||
try {
|
try {
|
||||||
|
@ -822,7 +885,7 @@ public class DavExchangeSession extends ExchangeSession {
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
throw buildHttpException(e);
|
throw buildHttpException(e);
|
||||||
}
|
}
|
||||||
return fixICS(result, true);
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PutMethod internalCreateOrUpdate(String encodedHref, byte[] mimeContent) throws IOException {
|
protected PutMethod internalCreateOrUpdate(String encodedHref, byte[] mimeContent) throws IOException {
|
||||||
|
@ -849,7 +912,8 @@ public class DavExchangeSession extends ExchangeSession {
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected ItemResult createOrUpdate(byte[] mimeContent) throws IOException {
|
public ItemResult createOrUpdate() throws IOException {
|
||||||
|
byte[] mimeContent = createMimeContent();
|
||||||
String encodedHref = URIUtil.encodePath(getHref());
|
String encodedHref = URIUtil.encodePath(getHref());
|
||||||
PutMethod putMethod = internalCreateOrUpdate(encodedHref, mimeContent);
|
PutMethod putMethod = internalCreateOrUpdate(encodedHref, mimeContent);
|
||||||
int status = putMethod.getStatusCode();
|
int status = putMethod.getStatusCode();
|
||||||
|
|
|
@ -214,7 +214,7 @@ public class EwsExchangeSession extends ExchangeSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get item MIME content.
|
* Get item content.
|
||||||
*
|
*
|
||||||
* @param itemId EWS item id
|
* @param itemId EWS item id
|
||||||
* @return item content as byte array
|
* @return item content as byte array
|
||||||
|
@ -826,18 +826,14 @@ public class EwsExchangeSession extends ExchangeSession {
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
protected Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) {
|
protected Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) throws IOException {
|
||||||
super(folderPath, itemName, contentClass, itemBody, etag, noneMatch);
|
super(folderPath, itemName, contentClass, itemBody, etag, noneMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemResult createOrUpdate() throws IOException {
|
public ItemResult createOrUpdate() throws IOException {
|
||||||
return createOrUpdate(fixICS(itemBody, false).getBytes("UTF-8"));
|
byte[] itemContent = Base64.encodeBase64(vCalendar.toString().getBytes("UTF-8"));
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ItemResult createOrUpdate(byte[] mimeContent) throws IOException {
|
|
||||||
ItemResult itemResult = new ItemResult();
|
ItemResult itemResult = new ItemResult();
|
||||||
EWSMethod createOrUpdateItemMethod;
|
EWSMethod createOrUpdateItemMethod;
|
||||||
|
|
||||||
|
@ -865,7 +861,7 @@ public class EwsExchangeSession extends ExchangeSession {
|
||||||
|
|
||||||
if (currentItemId != null) {
|
if (currentItemId != null) {
|
||||||
Set<FieldUpdate> updates = new HashSet<FieldUpdate>();
|
Set<FieldUpdate> updates = new HashSet<FieldUpdate>();
|
||||||
updates.add(new FieldUpdate(Field.get("mimeContent"), String.valueOf(Base64.encodeBase64(mimeContent))));
|
updates.add(new FieldUpdate(Field.get("mimeContent"), String.valueOf(Base64.encodeBase64(itemContent))));
|
||||||
// update
|
// update
|
||||||
createOrUpdateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly,
|
createOrUpdateItemMethod = new UpdateItemMethod(MessageDisposition.SaveOnly,
|
||||||
ConflictResolution.AlwaysOverwrite,
|
ConflictResolution.AlwaysOverwrite,
|
||||||
|
@ -875,7 +871,7 @@ public class EwsExchangeSession extends ExchangeSession {
|
||||||
// create
|
// create
|
||||||
EWSMethod.Item newItem = new EWSMethod.Item();
|
EWSMethod.Item newItem = new EWSMethod.Item();
|
||||||
newItem.type = "CalendarItem";
|
newItem.type = "CalendarItem";
|
||||||
newItem.mimeContent = Base64.encodeBase64(mimeContent);
|
newItem.mimeContent = itemContent;
|
||||||
HashSet<FieldUpdate> updates = new HashSet<FieldUpdate>();
|
HashSet<FieldUpdate> updates = new HashSet<FieldUpdate>();
|
||||||
// force urlcompname
|
// force urlcompname
|
||||||
updates.add(Field.createFieldUpdate("urlcompname", convertItemNameToEML(itemName)));
|
updates.add(Field.createFieldUpdate("urlcompname", convertItemNameToEML(itemName)));
|
||||||
|
@ -908,16 +904,17 @@ public class EwsExchangeSession extends ExchangeSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBody() throws IOException {
|
public byte[] getEventContent() throws IOException {
|
||||||
String result;
|
byte[] content;
|
||||||
LOGGER.debug("Get event: " + permanentUrl);
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("Get event: " + folderPath + '/' + itemName);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
byte[] content = getContent(itemId);
|
content = getContent(itemId);
|
||||||
result = new String(content);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw buildHttpException(e);
|
throw buildHttpException(e);
|
||||||
}
|
}
|
||||||
return fixICS(result, true);
|
return content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ EXCEPTION_INVALID_DATE=Invalid date: {0}
|
||||||
EXCEPTION_INVALID_DATES=Invalid dates: {0}
|
EXCEPTION_INVALID_DATES=Invalid dates: {0}
|
||||||
EXCEPTION_INVALID_FOLDER_URL=Invalid folder URL: {0}
|
EXCEPTION_INVALID_FOLDER_URL=Invalid folder URL: {0}
|
||||||
EXCEPTION_INVALID_HEADER=Invalid header: {0}, HTTPS connection to an HTTP listener ?
|
EXCEPTION_INVALID_HEADER=Invalid header: {0}, HTTPS connection to an HTTP listener ?
|
||||||
EXCEPTION_INVALID_ICS_LINE=Invalid ICS line: {0}
|
|
||||||
EXCEPTION_INVALID_KEEPALIVE=Invalid Keep-Alive: {0}
|
EXCEPTION_INVALID_KEEPALIVE=Invalid Keep-Alive: {0}
|
||||||
EXCEPTION_INVALID_MAIL_PATH=Invalid mail path: {0}
|
EXCEPTION_INVALID_MAIL_PATH=Invalid mail path: {0}
|
||||||
EXCEPTION_INVALID_MESSAGE_CONTENT=Invalid message content: {0}
|
EXCEPTION_INVALID_MESSAGE_CONTENT=Invalid message content: {0}
|
||||||
|
|
|
@ -15,7 +15,6 @@ EXCEPTION_INVALID_DATE=Date invalide {0}
|
||||||
EXCEPTION_INVALID_DATES=Dates invalides : {0}
|
EXCEPTION_INVALID_DATES=Dates invalides : {0}
|
||||||
EXCEPTION_INVALID_FOLDER_URL=URL du dossier invalide : {0}
|
EXCEPTION_INVALID_FOLDER_URL=URL du dossier invalide : {0}
|
||||||
EXCEPTION_INVALID_HEADER=Entête invalide : {0}, connexion HTTPS sur le service HTTP ?
|
EXCEPTION_INVALID_HEADER=Entête invalide : {0}, connexion HTTPS sur le service HTTP ?
|
||||||
EXCEPTION_INVALID_ICS_LINE=Ligne ICS invalide : {0}
|
|
||||||
EXCEPTION_INVALID_KEEPALIVE=Keep-Alive invalide : {0}
|
EXCEPTION_INVALID_KEEPALIVE=Keep-Alive invalide : {0}
|
||||||
EXCEPTION_INVALID_MAIL_PATH=Chemin de messagerie invalide : {0}
|
EXCEPTION_INVALID_MAIL_PATH=Chemin de messagerie invalide : {0}
|
||||||
EXCEPTION_INVALID_MESSAGE_CONTENT=Contenu du message invalide : {0}
|
EXCEPTION_INVALID_MESSAGE_CONTENT=Contenu du message invalide : {0}
|
||||||
|
|
Loading…
Reference in New Issue