1
0
mirror of https://github.com/moparisthebest/davmail synced 2024-12-13 11:12:22 -05:00

Carddav: refactor VCard handling to merge with VCalendar code

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1316 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2010-07-29 16:17:46 +00:00
parent 1344e4c9bd
commit 3725a147c4
5 changed files with 449 additions and 243 deletions

View File

@ -2083,7 +2083,7 @@ public abstract class ExchangeSession {
result.writeLine("CLASS:PRIVATE"); result.writeLine("CLASS:PRIVATE");
} else if ("PRIVATE".equalsIgnoreCase(eventClass)) { } else if ("PRIVATE".equalsIgnoreCase(eventClass)) {
result.writeLine("CLASS:CONFIDENTIAL"); result.writeLine("CLASS:CONFIDENTIAL");
} else { } else if (eventClass != null) {
result.writeLine("CLASS:" + eventClass); result.writeLine("CLASS:" + eventClass);
} }
} }
@ -2786,9 +2786,8 @@ public abstract class ExchangeSession {
Map<String, String> properties = new HashMap<String, String>(); Map<String, String> properties = new HashMap<String, String>();
properties.put("outlookmessageclass", "IPM.Contact"); properties.put("outlookmessageclass", "IPM.Contact");
VCardReader reader = new VCardReader(new StringReader(itemBody)); VObject vcard = new VObject(new ICSBufferedReader(new StringReader(itemBody)));
VCardReader.Property property; for (VProperty property:vcard.getProperties()) {
while ((property = reader.readProperty()) != null) {
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());
@ -3331,6 +3330,7 @@ public abstract class ExchangeSession {
* @param end end date in Exchange zulu format * @param end end date in Exchange zulu format
* @param interval freebusy interval in minutes * @param interval freebusy interval in minutes
* @return freebusy data or null * @return freebusy data or null
* @throws IOException on error
*/ */
protected abstract String getFreeBusyData(String attendee, String start, String end, int interval) throws IOException; protected abstract String getFreeBusyData(String attendee, String start, String end, int interval) throws IOException;

View File

@ -1,238 +0,0 @@
/*
* 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.IOException;
import java.io.Reader;
import java.util.*;
/**
* VCARD reader.
*/
public class VCardReader extends ICSBufferedReader {
/**
* VCard property
*/
public static class Property {
protected String key;
protected Map<String, Set<String>> params;
protected List<String> values;
/**
* Property key, without optional parameters (e.g. TEL).
*
* @return key
*/
public String getKey() {
return key;
}
/**
* Property value.
*
* @return value
*/
public String getValue() {
if (values == null || values.isEmpty()) {
return null;
} else {
return values.get(0);
}
}
/**
* Property values.
*
* @return values
*/
public List<String> getValues() {
return values;
}
public boolean hasParam(String paramName, String paramValue) {
return params != null && params.containsKey(paramName) && params.get(paramName).contains(paramValue);
}
protected void addParam(String paramName, Set<String> paramValues) {
if (params == null) {
params = new HashMap<String, Set<String>>();
}
if (params.get(paramName) == null) {
params.put(paramName, paramValues);
} else {
params.get(paramName).addAll(paramValues);
}
}
protected void addValue(String value) {
if (values == null) {
values = new ArrayList<String>();
}
values.add(decodeValue(value));
}
protected String decodeValue(String value) {
if (value == null || (value.indexOf('\\') < 0 && value.indexOf(',') < 0)) {
return value;
} else {
// decode value
StringBuilder decodedValue = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '\\') {
//noinspection AssignmentToForLoopParameter
c = value.charAt(++i);
if (c == 'n') {
c = '\n';
} else if (c == 'r') {
c = '\r';
}
}
// iPhone encodes category separator
if (c == ',' &&
// multivalued properties
("N".equals(key) ||
"ADR".equals(key) ||
"CATEGORIES".equals(key) ||
"NICKNAME".equals(key)
)) {
// convert multiple values to multiline values (e.g. street)
c = '\n';
}
decodedValue.append(c);
}
return decodedValue.toString();
}
}
public void setKey(String key) {
int dotIndex = key.indexOf('.');
if (dotIndex < 0) {
this.key = key;
} else {
this.key = key.substring(dotIndex + 1);
}
}
}
/**
* Create a VCARD reader on the provided reader
*
* @param in input reader
* @throws IOException on error
*/
public VCardReader(Reader in) throws IOException {
super(in);
String firstLine = readLine();
if (firstLine == null || !"BEGIN:VCARD".equals(firstLine)) {
throw new IOException("Invalid VCard body: " + firstLine);
}
}
protected static enum State {
KEY, PARAM_NAME, PARAM_VALUE, QUOTED_PARAM_VALUE, VALUE, BACKSLASH
}
public Property readProperty() throws IOException {
Property property = null;
String line = readLine();
if (line != null && !"END:VCARD".equals(line)) {
property = new Property();
State state = State.KEY;
String paramName = null;
Set<String> paramValues = null;
int startIndex = 0;
for (int i = 0; i < line.length(); i++) {
char currentChar = line.charAt(i);
if (state == State.KEY) {
if (currentChar == ':') {
property.setKey(line.substring(startIndex, i));
state = State.VALUE;
startIndex = i + 1;
} else if (currentChar == ';') {
property.setKey(line.substring(startIndex, i));
state = State.PARAM_NAME;
startIndex = i + 1;
}
} else if (state == State.PARAM_NAME) {
if (currentChar == '=') {
paramName = line.substring(startIndex, i).toUpperCase();
state = State.PARAM_VALUE;
paramValues = new HashSet<String>();
startIndex = i + 1;
} else if (currentChar == ';') {
// param with no value
paramName = line.substring(startIndex, i).toUpperCase();
property.addParam(paramName, null);
state = State.PARAM_NAME;
startIndex = i + 1;
} else if (currentChar == ':') {
// param with no value
paramName = line.substring(startIndex, i).toUpperCase();
property.addParam(paramName, null);
state = State.VALUE;
startIndex = i + 1;
}
} else if (state == State.PARAM_VALUE) {
if (currentChar == '"') {
state = State.QUOTED_PARAM_VALUE;
startIndex = i + 1;
} else if (currentChar == ':') {
if (startIndex < i) {
paramValues.add(line.substring(startIndex, i).toLowerCase());
}
property.addParam(paramName, paramValues);
state = State.VALUE;
startIndex = i + 1;
} else if (currentChar == ';') {
if (startIndex < i) {
paramValues.add(line.substring(startIndex, i).toLowerCase());
}
property.addParam(paramName, paramValues);
state = State.PARAM_NAME;
startIndex = i + 1;
} else if (currentChar == ',') {
if (startIndex < i) {
paramValues.add(line.substring(startIndex, i).toLowerCase());
}
startIndex = i + 1;
}
} else if (state == State.QUOTED_PARAM_VALUE) {
if (currentChar == '"') {
state = State.PARAM_VALUE;
paramValues.add(line.substring(startIndex, i).toLowerCase());
startIndex = i + 1;
}
} else if (state == State.VALUE) {
if (currentChar == '\\') {
state = State.BACKSLASH;
} else if (currentChar == ';') {
property.addValue(line.substring(startIndex, i));
startIndex = i + 1;
}
} else if (state == State.BACKSLASH) {
state = State.VALUE;
}
}
property.addValue(line.substring(startIndex));
}
return property;
}
}

View File

@ -0,0 +1,126 @@
/*
* 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.util.ArrayList;
import java.util.List;
/**
* Base class for VCalendar, VTimezone, VEvent.
*/
public class VObject {
/**
* VObject properties
*/
ArrayList<VProperty> properties;
/**
* Inner VObjects (e.g. VEVENT, VALARM, ...)
*/
ArrayList<VObject> objects;
/**
* Object base name (VCALENDAR, VEVENT, VCARD...).
*/
public String type;
/**
* Create VObject with given type
*
* @param beginProperty first line property
* @param reader stream reader just after the BEGIN:TYPE line
* @throws IOException on error
*/
public VObject(VProperty beginProperty, BufferedReader reader) throws IOException {
if (!"BEGIN".equals(beginProperty.getKey())) {
throw new IOException("Invalid first line: " + beginProperty);
}
type = beginProperty.getValue();
String endLine = "END:" + type;
String line = reader.readLine();
while (line != null && !endLine.equals(line)) {
handleLine(line, reader);
line = reader.readLine();
}
if (line == null) {
throw new IOException("Unexpected end of stream");
}
}
/**
* Create VObject from reader.
*
* @param reader stream reader just after the BEGIN:TYPE line
* @throws IOException on error
*/
public VObject(BufferedReader reader) throws IOException {
this(new VProperty(reader.readLine()), reader);
}
protected void handleLine(String line, BufferedReader reader) throws IOException {
VProperty property = new VProperty(line);
// inner object
if ("BEGIN".equals(property.getKey())) {
addObject(new VObject(property, reader));
} else {
addProperty(property);
}
}
protected void addObject(VObject object) {
if (objects == null) {
objects = new ArrayList<VObject>();
}
objects.add(object);
}
protected void addProperty(VProperty property) {
if (properties == null) {
properties = new ArrayList<VProperty>();
}
properties.add(property);
}
public void writeTo(ICSBufferedWriter writer) {
writer.write("BEGIN:");
writer.writeLine(type);
if (properties != null) {
for (VProperty property : properties) {
writer.writeLine(property.toString());
}
}
if (objects != null) {
for (VObject object : objects) {
object.writeTo(writer);
}
}
writer.write("END:");
writer.writeLine(type);
}
public String toString() {
ICSBufferedWriter writer = new ICSBufferedWriter();
writeTo(writer);
return writer.toString();
}
public List<VProperty> getProperties() {
return properties;
}
}

View File

@ -0,0 +1,318 @@
/*
* 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.util.*;
/**
* VCard property
*/
public class VProperty {
protected static enum State {
KEY, PARAM_NAME, PARAM_VALUE, QUOTED_PARAM_VALUE, VALUE, BACKSLASH
}
protected class Param {
String name;
List<String> values;
public void addAll(List<String> paramValues) {
if (values == null) {
values = new ArrayList<String>();
}
values.addAll(paramValues);
}
}
protected String key;
protected List<Param> params;
protected List<String> values;
/**
* Create VProperty from line.
*
* @param line card line
*/
public VProperty(String line) {
if (line != null && !"END:VCARD".equals(line)) {
State state = State.KEY;
String paramName = null;
List<String> paramValues = null;
int startIndex = 0;
for (int i = 0; i < line.length(); i++) {
char currentChar = line.charAt(i);
if (state == State.KEY) {
if (currentChar == ':') {
setKey(line.substring(startIndex, i));
state = State.VALUE;
startIndex = i + 1;
} else if (currentChar == ';') {
setKey(line.substring(startIndex, i));
state = State.PARAM_NAME;
startIndex = i + 1;
}
} else if (state == State.PARAM_NAME) {
if (currentChar == '=') {
paramName = line.substring(startIndex, i).toUpperCase();
state = State.PARAM_VALUE;
paramValues = new ArrayList<String>();
startIndex = i + 1;
} else if (currentChar == ';') {
// param with no value
paramName = line.substring(startIndex, i).toUpperCase();
addParam(paramName, null);
state = State.PARAM_NAME;
startIndex = i + 1;
} else if (currentChar == ':') {
// param with no value
paramName = line.substring(startIndex, i).toUpperCase();
addParam(paramName, null);
state = State.VALUE;
startIndex = i + 1;
}
} else if (state == State.PARAM_VALUE) {
if (currentChar == '"') {
state = State.QUOTED_PARAM_VALUE;
startIndex = i + 1;
} else if (currentChar == ':') {
if (startIndex < i) {
paramValues.add(line.substring(startIndex, i));
}
addParam(paramName, paramValues);
state = State.VALUE;
startIndex = i + 1;
} else if (currentChar == ';') {
if (startIndex < i) {
paramValues.add(line.substring(startIndex, i));
}
addParam(paramName, paramValues);
state = State.PARAM_NAME;
startIndex = i + 1;
} else if (currentChar == ',') {
if (startIndex < i) {
paramValues.add(line.substring(startIndex, i));
}
startIndex = i + 1;
}
} else if (state == State.QUOTED_PARAM_VALUE) {
if (currentChar == '"') {
state = State.PARAM_VALUE;
paramValues.add(line.substring(startIndex, i));
startIndex = i + 1;
}
} else if (state == State.VALUE) {
if (currentChar == '\\') {
state = State.BACKSLASH;
} else if (currentChar == ';') {
addValue(line.substring(startIndex, i));
startIndex = i + 1;
}
} else if (state == State.BACKSLASH) {
state = State.VALUE;
}
}
addValue(line.substring(startIndex));
}
}
/**
* Property key, without optional parameters (e.g. TEL).
*
* @return key
*/
public String getKey() {
return key;
}
/**
* Property value.
*
* @return value
*/
public String getValue() {
if (values == null || values.isEmpty()) {
return null;
} else {
return values.get(0);
}
}
/**
* Property values.
*
* @return values
*/
public List<String> getValues() {
return values;
}
/**
* Test if the property has a param named paramName with given value.
*
* @param paramName param name
* @param paramValue param value
* @return true if property has param name and value
*/
public boolean hasParam(String paramName, String paramValue) {
return params != null && getParam(paramName) != null && containsIgnoreCase(getParam(paramName).values, paramValue);
}
protected boolean containsIgnoreCase(List<String> stringCollection, String value) {
for (String collectionValue : stringCollection) {
if (value.equalsIgnoreCase(collectionValue)) {
return true;
}
}
return false;
}
protected void addParam(String paramName, List<String> paramValues) {
if (params == null) {
params = new ArrayList<Param>();
}
Param currentParam = getParam(paramName);
if (currentParam == null) {
currentParam = new Param();
currentParam.name = paramName;
params.add(currentParam);
}
currentParam.addAll(paramValues);
}
protected Param getParam(String paramName) {
if (params != null) {
for (Param param:params) {
if (paramName.equals(param.name)) {
return param;
}
}
}
return null;
}
protected void addValue(String value) {
if (values == null) {
values = new ArrayList<String>();
}
values.add(decodeValue(value));
}
protected String decodeValue(String value) {
if (value == null || (value.indexOf('\\') < 0 && value.indexOf(',') < 0)) {
return value;
} else {
// decode value
StringBuilder decodedValue = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '\\') {
//noinspection AssignmentToForLoopParameter
c = value.charAt(++i);
if (c == 'n') {
c = '\n';
} else if (c == 'r') {
c = '\r';
}
}
// iPhone encodes category separator
if (c == ',' &&
// multivalued properties
("N".equals(key) ||
"ADR".equals(key) ||
"CATEGORIES".equals(key) ||
"NICKNAME".equals(key)
)) {
// convert multiple values to multiline values (e.g. street)
c = '\n';
}
decodedValue.append(c);
}
return decodedValue.toString();
}
}
/**
* Set property key.
*
* @param key property key
*/
public void setKey(String key) {
int dotIndex = key.indexOf('.');
if (dotIndex < 0) {
this.key = key;
} else {
this.key = key.substring(dotIndex + 1);
}
}
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append(key);
if (params != null) {
for (Param param : params) {
buffer.append(';').append(param.name);
if (param.values != null) {
buffer.append('=');
for (String value : param.values) {
appendParamValue(buffer, value);
}
}
}
}
buffer.append(':');
if (values != null) {
boolean firstValue = true;
for (String value : values) {
if (firstValue) {
firstValue = false;
} else {
buffer.append(';');
}
appendMultilineEncodedValue(buffer, value);
}
}
return buffer.toString();
}
protected void appendParamValue(StringBuilder buffer, String value) {
if (Math.max(value.indexOf(';'), value.indexOf(',')) >= 0) {
buffer.append('"').append(value).append('"');
} else {
buffer.append(value);
}
}
/**
* Append and encode \n to \\n in value.
*
* @param buffer line buffer
* @param value value
*/
protected void appendMultilineEncodedValue(StringBuilder buffer, String value) {
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '\n') {
buffer.append("\\n");
} else {
buffer.append(value.charAt(i));
}
}
}
}

View File

@ -258,7 +258,7 @@ public class TestExchangeSessionContact extends AbstractExchangeSessionTestCase
assertNull(contact.get("private")); assertNull(contact.get("private"));
assertNull(contact.get("haspicture")); assertTrue(contact.get("haspicture") == null || "false".equals(contact.get("haspicture")));
assertNull(session.getContactPhoto(contact)); assertNull(session.getContactPhoto(contact));
} }