Caldav: start new VCalendar fixICS implementation
git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1318 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
parent
2b92a0946e
commit
3edbd2c44c
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
||||||
|
* Copyright (C) 2010 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.
|
||||||
|
*/
|
||||||
|
package davmail.exchange;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VCalendar object.
|
||||||
|
*/
|
||||||
|
public class VCalendar extends VObject {
|
||||||
|
protected VObject firstVevent;
|
||||||
|
protected VObject vTimezone;
|
||||||
|
protected String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create VCalendar object from reader;
|
||||||
|
*
|
||||||
|
* @param reader stream reader
|
||||||
|
* @param email current user email
|
||||||
|
* @throws IOException on error
|
||||||
|
*/
|
||||||
|
public VCalendar(BufferedReader reader, String email) throws IOException {
|
||||||
|
super(reader);
|
||||||
|
if (!"VCALENDAR".equals(type)) {
|
||||||
|
throw new IOException("Invalid type: " + type);
|
||||||
|
}
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create VCalendar object from reader;
|
||||||
|
*
|
||||||
|
* @param vCalendarBody item body
|
||||||
|
* @param email current user email
|
||||||
|
* @throws IOException on error
|
||||||
|
*/
|
||||||
|
public VCalendar(String vCalendarBody, String email) throws IOException {
|
||||||
|
this(new ICSBufferedReader(new StringReader(vCalendarBody)), email);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addVObject(VObject vObject) {
|
||||||
|
super.addVObject(vObject);
|
||||||
|
if (firstVevent == null && "VEVENT".equals(vObject.type)) {
|
||||||
|
firstVevent = vObject;
|
||||||
|
}
|
||||||
|
if ("VTIMEZONE".equals(vObject.type)) {
|
||||||
|
vTimezone = vObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isAllDay(VObject vObject) {
|
||||||
|
VProperty dtstart = vObject.getProperty("DTSTART");
|
||||||
|
return dtstart != null && dtstart.hasParam("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) {
|
||||||
|
return "TRUE".equals(vObject.getPropertyValue("X-MICROSOFT-CDO-ALLDAYEVENT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isAppleiCal() {
|
||||||
|
return getPropertyValue("PRODID").contains("iCal");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getOrganizer() {
|
||||||
|
String organizer = firstVevent.getPropertyValue("ORGANIZER");
|
||||||
|
if (organizer.startsWith("MAILTO:")) {
|
||||||
|
return organizer.substring(7);
|
||||||
|
} else {
|
||||||
|
return organizer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getMethod() {
|
||||||
|
return getPropertyValue("METHOD");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fixVCalendar(boolean fromServer) {
|
||||||
|
// append missing method
|
||||||
|
if (getProperty("METHOD") == null) {
|
||||||
|
setPropertyValue("METHOD", "PUBLISH");
|
||||||
|
}
|
||||||
|
// iCal 4 global private flag
|
||||||
|
if (fromServer) {
|
||||||
|
setPropertyValue("X-CALENDARSERVER-ACCESS", getCalendarServerAccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
String calendarServerAccess = getPropertyValue("X-CALENDARSERVER-ACCESS");
|
||||||
|
|
||||||
|
// TODO: patch timezone for iPhone
|
||||||
|
// iterate over vObjects
|
||||||
|
for (VObject vObject : vObjects) {
|
||||||
|
if ("VEVENT".equals(vObject.type)) {
|
||||||
|
if (calendarServerAccess != null) {
|
||||||
|
setPropertyValue("CLASS", getEventClass(calendarServerAccess));
|
||||||
|
} else if (vObject.getPropertyValue("X-CALENDARSERVER-ACCESS") != null){
|
||||||
|
setPropertyValue("CLASS", getEventClass(vObject.getPropertyValue("X-CALENDARSERVER-ACCESS")));
|
||||||
|
}
|
||||||
|
// add organizer line to all events created in Exchange for active sync
|
||||||
|
if (vObject.getPropertyValue("ORGANIZER") == null) {
|
||||||
|
vObject.setPropertyValue("ORGANIZER", "MAILTO:"+email);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert X-CALENDARSERVER-ACCESS to CLASS.
|
||||||
|
*
|
||||||
|
* @return CLASS value
|
||||||
|
*/
|
||||||
|
protected String getEventClass(String calendarServerAccess) {
|
||||||
|
if ("PRIVATE".equals(calendarServerAccess)) {
|
||||||
|
return "CONFIDENTIAL";
|
||||||
|
} else if ("CONFIDENTIAL".equals(calendarServerAccess) || "RESTRICTED".equals(calendarServerAccess)) {
|
||||||
|
return "PRIVATE";
|
||||||
|
} else {
|
||||||
|
return calendarServerAccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert CLASS to X-CALENDARSERVER-ACCESS.
|
||||||
|
*
|
||||||
|
* @return X-CALENDARSERVER-ACCESS value
|
||||||
|
*/
|
||||||
|
protected String getCalendarServerAccess() {
|
||||||
|
String eventClass = firstVevent.getPropertyValue("CLASS");
|
||||||
|
if ("PRIVATE".equals(eventClass)) {
|
||||||
|
return "CONFIDENTIAL";
|
||||||
|
} else if ("CONFIDENTIAL".equals(eventClass)) {
|
||||||
|
return "PRIVATE";
|
||||||
|
} else {
|
||||||
|
return eventClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ public class VObject {
|
||||||
/**
|
/**
|
||||||
* Inner VObjects (e.g. VEVENT, VALARM, ...)
|
* Inner VObjects (e.g. VEVENT, VALARM, ...)
|
||||||
*/
|
*/
|
||||||
ArrayList<VObject> objects;
|
ArrayList<VObject> vObjects;
|
||||||
/**
|
/**
|
||||||
* Object base name (VCALENDAR, VEVENT, VCARD...).
|
* Object base name (VCALENDAR, VEVENT, VCARD...).
|
||||||
*/
|
*/
|
||||||
|
@ -77,26 +77,33 @@ public class VObject {
|
||||||
VProperty property = new VProperty(line);
|
VProperty property = new VProperty(line);
|
||||||
// inner object
|
// inner object
|
||||||
if ("BEGIN".equals(property.getKey())) {
|
if ("BEGIN".equals(property.getKey())) {
|
||||||
addObject(new VObject(property, reader));
|
addVObject(new VObject(property, reader));
|
||||||
} else {
|
} else {
|
||||||
addProperty(property);
|
addProperty(property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addObject(VObject object) {
|
protected void addVObject(VObject vObject) {
|
||||||
if (objects == null) {
|
if (vObjects == null) {
|
||||||
objects = new ArrayList<VObject>();
|
vObjects = new ArrayList<VObject>();
|
||||||
}
|
}
|
||||||
objects.add(object);
|
vObjects.add(vObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addProperty(VProperty property) {
|
protected void addProperty(VProperty property) {
|
||||||
if (properties == null) {
|
if (property.getValue() != null) {
|
||||||
properties = new ArrayList<VProperty>();
|
if (properties == null) {
|
||||||
|
properties = new ArrayList<VProperty>();
|
||||||
|
}
|
||||||
|
properties.add(property);
|
||||||
}
|
}
|
||||||
properties.add(property);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write VObject to writer.
|
||||||
|
*
|
||||||
|
* @param writer buffered writer
|
||||||
|
*/
|
||||||
public void writeTo(ICSBufferedWriter writer) {
|
public void writeTo(ICSBufferedWriter writer) {
|
||||||
writer.write("BEGIN:");
|
writer.write("BEGIN:");
|
||||||
writer.writeLine(type);
|
writer.writeLine(type);
|
||||||
|
@ -105,8 +112,8 @@ public class VObject {
|
||||||
writer.writeLine(property.toString());
|
writer.writeLine(property.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (objects != null) {
|
if (vObjects != null) {
|
||||||
for (VObject object : objects) {
|
for (VObject object : vObjects) {
|
||||||
object.writeTo(writer);
|
object.writeTo(writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +127,44 @@ public class VObject {
|
||||||
return writer.toString();
|
return writer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get VObject properties
|
||||||
|
*
|
||||||
|
* @return properties
|
||||||
|
*/
|
||||||
public List<VProperty> getProperties() {
|
public List<VProperty> getProperties() {
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VProperty getProperty(String name) {
|
||||||
|
if (properties != null) {
|
||||||
|
for (VProperty property : properties) {
|
||||||
|
if (property.getKey().equalsIgnoreCase(name)) {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPropertyValue(String name) {
|
||||||
|
VProperty property = getProperty(name);
|
||||||
|
if (property != null) {
|
||||||
|
return property.getValue();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPropertyValue(String name, String value) {
|
||||||
|
VProperty property = getProperty(name);
|
||||||
|
if (property == null) {
|
||||||
|
property = new VProperty(name, value);
|
||||||
|
addProperty(property);
|
||||||
|
} else {
|
||||||
|
property.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
*/
|
*/
|
||||||
package davmail.exchange;
|
package davmail.exchange;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VCard property
|
* VCard property
|
||||||
|
@ -44,6 +45,11 @@ public class VProperty {
|
||||||
protected List<Param> params;
|
protected List<Param> params;
|
||||||
protected List<String> values;
|
protected List<String> values;
|
||||||
|
|
||||||
|
public VProperty(String name, String value) {
|
||||||
|
setKey(name);
|
||||||
|
setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create VProperty from line.
|
* Create VProperty from line.
|
||||||
*
|
*
|
||||||
|
@ -173,6 +179,16 @@ public class VProperty {
|
||||||
return params != null && getParam(paramName) != null && containsIgnoreCase(getParam(paramName).values, paramValue);
|
return params != null && getParam(paramName) != null && containsIgnoreCase(getParam(paramName).values, paramValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the property has a param named paramName.
|
||||||
|
*
|
||||||
|
* @param paramName param name
|
||||||
|
* @return true if property has param name
|
||||||
|
*/
|
||||||
|
public boolean hasParam(String paramName) {
|
||||||
|
return params != null && getParam(paramName) != null;
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean containsIgnoreCase(List<String> stringCollection, String value) {
|
protected boolean containsIgnoreCase(List<String> stringCollection, String value) {
|
||||||
for (String collectionValue : stringCollection) {
|
for (String collectionValue : stringCollection) {
|
||||||
if (value.equalsIgnoreCase(collectionValue)) {
|
if (value.equalsIgnoreCase(collectionValue)) {
|
||||||
|
@ -197,7 +213,7 @@ public class VProperty {
|
||||||
|
|
||||||
protected Param getParam(String paramName) {
|
protected Param getParam(String paramName) {
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
for (Param param:params) {
|
for (Param param : params) {
|
||||||
if (paramName.equals(param.name)) {
|
if (paramName.equals(param.name)) {
|
||||||
return param;
|
return param;
|
||||||
}
|
}
|
||||||
|
@ -206,6 +222,19 @@ public class VProperty {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setValue(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
values = null;
|
||||||
|
} else {
|
||||||
|
if (values == null) {
|
||||||
|
values = new ArrayList<String>();
|
||||||
|
} else {
|
||||||
|
values.clear();
|
||||||
|
}
|
||||||
|
values.add(decodeValue(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void addValue(String value) {
|
protected void addValue(String value) {
|
||||||
if (values == null) {
|
if (values == null) {
|
||||||
values = new ArrayList<String>();
|
values = new ArrayList<String>();
|
||||||
|
|
|
@ -0,0 +1,479 @@
|
||||||
|
/*
|
||||||
|
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
|
||||||
|
* Copyright (C) 2010 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.
|
||||||
|
*/
|
||||||
|
package davmail.exchange;
|
||||||
|
|
||||||
|
import davmail.Settings;
|
||||||
|
import davmail.exception.DavMailException;
|
||||||
|
import davmail.util.StringUtil;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.MissingResourceException;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test ExchangeSession event conversion.
|
||||||
|
*/
|
||||||
|
public class TestExchangeSessionEvent extends TestCase {
|
||||||
|
String email = "user@company.com";
|
||||||
|
|
||||||
|
/*
|
||||||
|
X-CALENDARSERVER-ACCESS conversion
|
||||||
|
see http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-privateevents.txt
|
||||||
|
|
||||||
|
X-CALENDARSERVER-ACCESS -> CLASS
|
||||||
|
NONE -> NONE
|
||||||
|
PUBLIC -> PUBLIC
|
||||||
|
PRIVATE -> CONFIDENTIAL
|
||||||
|
CONFIDENTIAL -> PRIVATE
|
||||||
|
RESTRICTED -> PRIVATE
|
||||||
|
|
||||||
|
CLASS -> X-CALENDARSERVER-ACCESS
|
||||||
|
NONE -> NONE
|
||||||
|
PUBLIC -> PUBLIC
|
||||||
|
PRIVATE -> CONFIDENTIAL
|
||||||
|
CONFIDENTIAL -> PRIVATE
|
||||||
|
|
||||||
|
iCal 3 sends X-CALENDARSERVER-ACCESS inside VEVENT, iCal 4 uses global X-CALENDARSERVER-ACCESS
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 fixTimezoneId(String line, String validTimezoneId) {
|
||||||
|
return StringUtil.replaceToken(line, "TZID=", ":", validTimezoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String replaceIcal4Principal(String value) {
|
||||||
|
if (value.contains("/principals/__uuids__/")) {
|
||||||
|
return value.replaceAll("/principals/__uuids__/([^/]*)__AT__([^/]*)/", "mailto:$1@$2");
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 oldfixICS(String icsBody, boolean fromServer) throws IOException {
|
||||||
|
// first pass : detect
|
||||||
|
class AllDayState {
|
||||||
|
boolean isAllDay;
|
||||||
|
boolean hasCdoAllDay;
|
||||||
|
boolean isCdoAllDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
String method = 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;
|
||||||
|
} else if ("METHOD".equals(key)) {
|
||||||
|
method = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
if (!fromServer && "BEGIN:VCALENDAR".equals(line) && method == null) {
|
||||||
|
result.writeLine(line);
|
||||||
|
// append missing method
|
||||||
|
if (method == null) {
|
||||||
|
result.writeLine("METHOD:PUBLISH");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (!fromServer && "BEGIN:VEVENT".equals(line) && !hasTimezone) {
|
||||||
|
result.write("BEGIN:VTIMEZONE\nFAKE:FAKE\nEND:VTIMEZONE\n");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (isAppleiCal) {
|
||||||
|
if ("CONFIDENTIAL".equalsIgnoreCase(eventClass)) {
|
||||||
|
result.writeLine("CLASS:PRIVATE");
|
||||||
|
} else if ("PRIVATE".equalsIgnoreCase(eventClass)) {
|
||||||
|
result.writeLine("CLASS:CONFIDENTIAL");
|
||||||
|
} else if (eventClass != null) {
|
||||||
|
result.writeLine("CLASS:" + eventClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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=\"" + "OWATZID" + "\":" + line.substring(19) + "T000000";
|
||||||
|
} else if (!fromServer && currentAllDayState.isAllDay && line.startsWith("DTEND") && line.startsWith("DTEND;VALUE=DATE")) {
|
||||||
|
line = "DTEND;TZID=\"" + "OWATZID" + "\":" + line.substring(17) + "T000000";
|
||||||
|
} else if (line.startsWith("TZID:") && validTimezoneId != null) {
|
||||||
|
line = "TZID:" + validTimezoneId;
|
||||||
|
} else if ("BEGIN:VEVENT".equals(line)) {
|
||||||
|
currentAllDayState = allDayStates.get(count++);
|
||||||
|
// remove calendarserver access
|
||||||
|
} else if (line.startsWith("X-CALENDARSERVER-ACCESS:")) {
|
||||||
|
continue;
|
||||||
|
} 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:")) {
|
||||||
|
if (fromServer && "DISPLAY".equals(action)
|
||||||
|
// convert DISPLAY to AUDIO only if user defined an alarm sound
|
||||||
|
&& Settings.getProperty("davmail.caldavAlarmSound") != null) {
|
||||||
|
// Convert alarm to audio for iCal
|
||||||
|
result.writeLine("ACTION:AUDIO");
|
||||||
|
|
||||||
|
if (!sound) {
|
||||||
|
// Add defined sound into the audio alarm
|
||||||
|
result.writeLine("ATTACH;VALUE=URI:" + Settings.getProperty("davmail.caldavAlarmSound"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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:")) {
|
||||||
|
if (!fromServer && isAppleiCal) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// still set calendarserver access inside event for iCal 3
|
||||||
|
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();
|
||||||
|
|
||||||
|
return resultString;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String fixICS(String icsBody, boolean fromServer) throws IOException {
|
||||||
|
VCalendar vCalendar = new VCalendar(icsBody, email);
|
||||||
|
vCalendar.fixVCalendar(fromServer);
|
||||||
|
return vCalendar.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNoClass() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
String toClient = fixICS(itemBody, true);
|
||||||
|
System.out.println(toServer);
|
||||||
|
System.out.println(toClient);
|
||||||
|
assertTrue(toServer.indexOf("CLASS") < 0);
|
||||||
|
assertTrue(toClient.indexOf("CLASS") < 0);
|
||||||
|
assertTrue(toClient.indexOf("X-CALENDARSERVER-ACCESS") < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPublicClass() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"CLASS:PUBLIC\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
String toClient = fixICS(itemBody, true);
|
||||||
|
System.out.println(toServer);
|
||||||
|
System.out.println(toClient);
|
||||||
|
assertTrue(toServer.indexOf("CLASS:PUBLIC") >= 0);
|
||||||
|
assertTrue(toClient.indexOf("CLASS:PUBLIC") >= 0);
|
||||||
|
assertTrue(toClient.indexOf("X-CALENDARSERVER-ACCESS:PUBLIC") >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPrivateClass() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"CLASS:PRIVATE\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
String toClient = fixICS(itemBody, true);
|
||||||
|
System.out.println(toServer);
|
||||||
|
System.out.println(toClient);
|
||||||
|
assertTrue(toServer.indexOf("CLASS:PRIVATE") >= 0);
|
||||||
|
assertTrue(toClient.indexOf("CLASS:PRIVATE") >= 0);
|
||||||
|
assertTrue(toClient.indexOf("X-CALENDARSERVER-ACCESS:CONFIDENTIAL") >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConfidentialClass() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"CLASS:CONFIDENTIAL\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
String toClient = fixICS(itemBody, true);
|
||||||
|
System.out.println(toServer);
|
||||||
|
System.out.println(toClient);
|
||||||
|
assertTrue(toServer.indexOf("CLASS:CONFIDENTIAL") >= 0);
|
||||||
|
assertTrue(toClient.indexOf("CLASS:CONFIDENTIAL") >= 0);
|
||||||
|
assertTrue(toClient.indexOf("X-CALENDARSERVER-ACCESS:PRIVATE") >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCalendarServerAccessPrivate() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"PRODID:-//Apple Inc.//iCal 4.0.3//EN\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"X-CALENDARSERVER-ACCESS:PRIVATE\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
System.out.println(toServer);
|
||||||
|
assertTrue(toServer.indexOf("CLASS:CONFIDENTIAL") >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCalendarServerAccessConfidential() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"PRODID:-//Apple Inc.//iCal 4.0.3//EN\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"X-CALENDARSERVER-ACCESS:CONFIDENTIAL\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
System.out.println(toServer);
|
||||||
|
assertTrue(toServer.indexOf("CLASS:PRIVATE") >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCalendarServerAccessPublic() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"PRODID:-//Apple Inc.//iCal 4.0.3//EN\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"X-CALENDARSERVER-ACCESS:PUBLIC\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
System.out.println(toServer);
|
||||||
|
assertTrue(toServer.indexOf("CLASS:PUBLIC") >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCalendarServerAccessNone() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"PRODID:-//Apple Inc.//iCal 4.0.3//EN\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
System.out.println(toServer);
|
||||||
|
assertFalse(toServer.contains("CLASS"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNoOrganizer() throws IOException {
|
||||||
|
String itemBody = "BEGIN:VCALENDAR\n" +
|
||||||
|
"BEGIN:VEVENT\n" +
|
||||||
|
"END:VEVENT\n" +
|
||||||
|
"END:VCALENDAR";
|
||||||
|
String toServer = fixICS(itemBody, false);
|
||||||
|
System.out.println(toServer);
|
||||||
|
assertTrue(toServer.contains("ORGANIZER"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue