2008-11-26 19:56:28 -05:00
|
|
|
package davmail.caldav;
|
|
|
|
|
|
|
|
import davmail.AbstractConnection;
|
|
|
|
import davmail.Settings;
|
|
|
|
import davmail.exchange.ExchangeSession;
|
|
|
|
import davmail.exchange.ExchangeSessionFactory;
|
2009-02-09 05:12:09 -05:00
|
|
|
import davmail.exchange.ICSBufferedReader;
|
2008-11-26 19:56:28 -05:00
|
|
|
import davmail.tray.DavGatewayTray;
|
|
|
|
import org.apache.commons.httpclient.HttpException;
|
|
|
|
import org.apache.commons.httpclient.HttpStatus;
|
2008-12-19 08:01:14 -05:00
|
|
|
import org.apache.commons.httpclient.auth.AuthenticationException;
|
2009-02-09 05:12:09 -05:00
|
|
|
import org.apache.commons.httpclient.util.URIUtil;
|
2008-12-26 07:35:08 -05:00
|
|
|
import org.apache.log4j.Logger;
|
2008-11-26 19:56:28 -05:00
|
|
|
|
2008-11-29 09:27:33 -05:00
|
|
|
import javax.xml.stream.XMLInputFactory;
|
|
|
|
import javax.xml.stream.XMLStreamConstants;
|
|
|
|
import javax.xml.stream.XMLStreamException;
|
|
|
|
import javax.xml.stream.XMLStreamReader;
|
2009-03-03 06:09:07 -05:00
|
|
|
import java.io.*;
|
2008-11-26 19:56:28 -05:00
|
|
|
import java.net.Socket;
|
2008-12-05 03:47:54 -05:00
|
|
|
import java.net.SocketException;
|
2008-12-17 10:22:37 -05:00
|
|
|
import java.net.SocketTimeoutException;
|
2008-11-26 19:56:28 -05:00
|
|
|
import java.text.SimpleDateFormat;
|
2008-11-29 09:27:33 -05:00
|
|
|
import java.util.*;
|
2008-11-26 19:56:28 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a caldav connection.
|
|
|
|
*/
|
|
|
|
public class CaldavConnection extends AbstractConnection {
|
2009-02-24 06:53:02 -05:00
|
|
|
protected final Logger wireLogger = Logger.getLogger(this.getClass());
|
2008-12-26 07:35:08 -05:00
|
|
|
|
2008-11-26 19:56:28 -05:00
|
|
|
protected boolean closed = false;
|
|
|
|
|
|
|
|
// Initialize the streams and start the thread
|
|
|
|
public CaldavConnection(Socket clientSocket) {
|
2008-11-29 09:24:12 -05:00
|
|
|
super("CaldavConnection", clientSocket, "UTF-8");
|
2008-12-26 07:35:08 -05:00
|
|
|
wireLogger.setLevel(Settings.getLoggingLevel("httpclient.wire"));
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
protected Map<String, String> parseHeaders() throws IOException {
|
|
|
|
HashMap<String, String> headers = new HashMap<String, String>();
|
|
|
|
String line;
|
|
|
|
while ((line = readClient()) != null && line.length() > 0) {
|
|
|
|
int index = line.indexOf(':');
|
|
|
|
if (index <= 0) {
|
|
|
|
throw new IOException("Invalid header: " + line);
|
|
|
|
}
|
|
|
|
headers.put(line.substring(0, index).toLowerCase(), line.substring(index + 1).trim());
|
|
|
|
}
|
|
|
|
return headers;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String getContent(String contentLength) throws IOException {
|
|
|
|
if (contentLength == null || contentLength.length() == 0) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
int size;
|
|
|
|
try {
|
|
|
|
size = Integer.parseInt(contentLength);
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
throw new IOException("Invalid content length: " + contentLength);
|
|
|
|
}
|
|
|
|
char[] buffer = new char[size];
|
2009-02-11 20:33:49 -05:00
|
|
|
StringBuilder builder = new StringBuilder();
|
2009-02-12 05:10:23 -05:00
|
|
|
int actualSize = in.read(buffer);
|
|
|
|
builder.append(buffer, 0, actualSize);
|
|
|
|
if (actualSize < 0) {
|
|
|
|
throw new IOException("End of stream reached reading content");
|
|
|
|
}
|
2009-02-11 20:33:49 -05:00
|
|
|
// dirty hack to ensure full content read
|
|
|
|
// TODO : replace with a dedicated reader
|
|
|
|
while (builder.toString().getBytes("UTF-8").length < size) {
|
|
|
|
actualSize = in.read(buffer);
|
|
|
|
builder.append(buffer, 0, actualSize);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2009-02-12 05:10:23 -05:00
|
|
|
|
2009-02-11 20:33:49 -05:00
|
|
|
return builder.toString();
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void setSocketTimeout(String keepAliveValue) throws IOException {
|
2008-12-02 05:20:46 -05:00
|
|
|
if (keepAliveValue != null && keepAliveValue.length() > 0) {
|
2008-11-26 19:56:28 -05:00
|
|
|
int keepAlive;
|
|
|
|
try {
|
|
|
|
keepAlive = Integer.parseInt(keepAliveValue);
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
throw new IOException("Invalid Keep-Alive: " + keepAliveValue);
|
|
|
|
}
|
|
|
|
if (keepAlive > 300) {
|
|
|
|
keepAlive = 300;
|
|
|
|
}
|
|
|
|
client.setSoTimeout(keepAlive * 1000);
|
|
|
|
DavGatewayTray.debug("Set socket timeout to " + keepAlive + " seconds");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void run() {
|
|
|
|
String line;
|
|
|
|
StringTokenizer tokens;
|
|
|
|
|
|
|
|
try {
|
|
|
|
while (!closed) {
|
|
|
|
line = readClient();
|
|
|
|
// unable to read line, connection closed ?
|
|
|
|
if (line == null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
tokens = new StringTokenizer(line);
|
|
|
|
if (tokens.hasMoreTokens()) {
|
|
|
|
String command = tokens.nextToken();
|
|
|
|
Map<String, String> headers = parseHeaders();
|
|
|
|
if (tokens.hasMoreTokens()) {
|
|
|
|
String path = tokens.nextToken();
|
|
|
|
String content = getContent(headers.get("content-length"));
|
|
|
|
setSocketTimeout(headers.get("keep-alive"));
|
2009-02-11 20:33:49 -05:00
|
|
|
// client requested connection close
|
|
|
|
closed = "close".equals(headers.get("connection"));
|
2008-11-26 19:56:28 -05:00
|
|
|
if ("OPTIONS".equals(command)) {
|
|
|
|
sendOptions();
|
|
|
|
} else if (!headers.containsKey("authorization")) {
|
|
|
|
sendUnauthorized();
|
|
|
|
} else {
|
|
|
|
decodeCredentials(headers.get("authorization"));
|
|
|
|
// authenticate only once
|
|
|
|
if (session == null) {
|
2008-12-17 10:22:37 -05:00
|
|
|
// first check network connectivity
|
|
|
|
ExchangeSessionFactory.checkConfig();
|
2008-12-19 08:01:14 -05:00
|
|
|
try {
|
2008-12-23 16:34:39 -05:00
|
|
|
session = ExchangeSessionFactory.getInstance(userName, password);
|
2008-12-19 08:01:14 -05:00
|
|
|
} catch (AuthenticationException e) {
|
2009-01-09 09:59:54 -05:00
|
|
|
sendUnauthorized();
|
2008-12-19 08:01:14 -05:00
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
2008-12-30 07:05:41 -05:00
|
|
|
if (session != null) {
|
|
|
|
handleRequest(command, path, headers, content);
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sendErr(HttpStatus.SC_NOT_IMPLEMENTED, "Invalid URI");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
os.flush();
|
|
|
|
}
|
|
|
|
} catch (SocketTimeoutException e) {
|
|
|
|
DavGatewayTray.debug("Closing connection on timeout");
|
2008-12-05 03:47:54 -05:00
|
|
|
} catch (SocketException e) {
|
|
|
|
DavGatewayTray.debug("Connection closed");
|
2008-11-26 19:56:28 -05:00
|
|
|
} catch (IOException e) {
|
2009-03-13 17:18:11 -04:00
|
|
|
if (e instanceof HttpException) {
|
|
|
|
DavGatewayTray.error(((HttpException)e).getReasonCode()+" "+((HttpException)e).getReason(), e);
|
|
|
|
} else {
|
|
|
|
DavGatewayTray.error(e);
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
try {
|
2009-02-23 09:17:07 -05:00
|
|
|
sendErr(HttpStatus.SC_SERVICE_UNAVAILABLE, e);
|
2008-11-26 19:56:28 -05:00
|
|
|
} catch (IOException e2) {
|
|
|
|
DavGatewayTray.debug("Exception sending error to client", e2);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
DavGatewayTray.resetIcon();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected int getDepth(Map<String, String> headers) {
|
|
|
|
int result = 0;
|
|
|
|
String depthValue = headers.get("depth");
|
|
|
|
if (depthValue != null) {
|
|
|
|
try {
|
|
|
|
result = Integer.valueOf(depthValue);
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
DavGatewayTray.warn("Invalid depth value: " + depthValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void handleRequest(String command, String path, Map<String, String> headers, String body) throws IOException {
|
|
|
|
int depth = getDepth(headers);
|
2009-02-25 05:23:07 -05:00
|
|
|
String[] paths = path.replaceAll("//", "/").split("/");
|
2008-12-30 07:05:41 -05:00
|
|
|
|
2008-12-24 10:52:13 -05:00
|
|
|
// full debug trace
|
2008-12-26 07:35:08 -05:00
|
|
|
if (wireLogger.isDebugEnabled()) {
|
|
|
|
wireLogger.debug("Caldav command: " + command + " " + path + " depth: " + depth + "\n" + body);
|
|
|
|
}
|
2008-12-24 10:52:13 -05:00
|
|
|
|
2008-12-30 07:05:41 -05:00
|
|
|
CaldavRequest request = null;
|
|
|
|
if ("PROPFIND".equals(command) || "REPORT".equals(command)) {
|
|
|
|
request = new CaldavRequest(body);
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
if ("OPTIONS".equals(command)) {
|
|
|
|
sendOptions();
|
2008-12-30 07:05:41 -05:00
|
|
|
// redirect PROPFIND on / to current user principal
|
|
|
|
} else if ("PROPFIND".equals(command) && (paths.length == 0 || paths.length == 1)) {
|
|
|
|
sendRoot(request);
|
|
|
|
} else if ("GET".equals(command) && (paths.length == 0 || paths.length == 1)) {
|
|
|
|
sendGetRoot();
|
|
|
|
// return current user calendar
|
|
|
|
} else if ("calendar".equals(paths[1])) {
|
|
|
|
StringBuilder message = new StringBuilder();
|
|
|
|
message.append("/calendar no longer supported, recreate calendar with /users/")
|
|
|
|
.append(session.getEmail()).append("/calendar");
|
|
|
|
DavGatewayTray.error(message.toString());
|
|
|
|
sendErr(HttpStatus.SC_BAD_REQUEST, message.toString());
|
|
|
|
} else if ("user".equals(paths[1])) {
|
2008-12-30 07:46:57 -05:00
|
|
|
sendRedirect(headers, "/principals/users/" + session.getEmail());
|
2008-12-30 07:05:41 -05:00
|
|
|
// principal namespace
|
|
|
|
} else if ("PROPFIND".equals(command) && "principals".equals(paths[1]) && paths.length == 4 &&
|
|
|
|
"users".equals(paths[2])) {
|
|
|
|
sendPrincipal(request, paths[3]);
|
|
|
|
// send back principal on search
|
|
|
|
} else if ("REPORT".equals(command) && "principals".equals(paths[1]) && paths.length == 3 &&
|
|
|
|
"users".equals(paths[2])) {
|
|
|
|
sendPrincipal(request, session.getEmail());
|
|
|
|
// user root
|
|
|
|
} else if ("PROPFIND".equals(command) && "users".equals(paths[1]) && paths.length == 3) {
|
|
|
|
sendUserRoot(request, depth, paths[2]);
|
|
|
|
} else if ("PROPFIND".equals(command) && "users".equals(paths[1]) && paths.length == 4 && "inbox".equals(paths[3])) {
|
2009-02-20 12:44:30 -05:00
|
|
|
sendInbox(request, depth, paths[2]);
|
2008-12-30 07:05:41 -05:00
|
|
|
} else if ("REPORT".equals(command) && "users".equals(paths[1]) && paths.length == 4 && "inbox".equals(paths[3])) {
|
2009-02-25 05:23:07 -05:00
|
|
|
reportEvents(request, "INBOX");
|
2008-12-30 07:05:41 -05:00
|
|
|
} else if ("PROPFIND".equals(command) && "users".equals(paths[1]) && paths.length == 4 && "outbox".equals(paths[3])) {
|
|
|
|
sendOutbox(request, paths[2]);
|
|
|
|
} else if ("POST".equals(command) && "users".equals(paths[1]) && paths.length == 4 && "outbox".equals(paths[3])) {
|
2009-02-11 20:33:49 -05:00
|
|
|
if (body.indexOf("VFREEBUSY") >= 0) {
|
|
|
|
sendFreeBusy(body);
|
2009-02-09 05:12:09 -05:00
|
|
|
} else {
|
|
|
|
int status = session.sendEvent(body);
|
|
|
|
sendHttpResponse(status, true);
|
|
|
|
}
|
2008-12-30 07:05:41 -05:00
|
|
|
} else if ("PROPFIND".equals(command) && "users".equals(paths[1]) && paths.length == 4 && "calendar".equals(paths[3])) {
|
|
|
|
sendCalendar(request, depth, paths[2]);
|
2009-03-09 12:02:58 -04:00
|
|
|
} else if ("PROPPATCH".equals(command) && "users".equals(paths[1]) && paths.length == 4 && "calendar".equals(paths[3])) {
|
|
|
|
patchCalendar(request, depth, paths[2]);
|
2008-12-30 07:05:41 -05:00
|
|
|
} else if ("REPORT".equals(command) && "users".equals(paths[1]) && paths.length == 4 && "calendar".equals(paths[3])
|
|
|
|
// only current user for now
|
2009-01-08 04:58:31 -05:00
|
|
|
&& session.getEmail().equalsIgnoreCase(paths[2])) {
|
2009-02-25 05:23:07 -05:00
|
|
|
reportEvents(request, "calendar");
|
2008-12-30 07:05:41 -05:00
|
|
|
|
|
|
|
} else if ("PUT".equals(command) && "users".equals(paths[1]) && paths.length == 5 && "calendar".equals(paths[3])
|
|
|
|
// only current user for now
|
2009-01-08 04:58:31 -05:00
|
|
|
&& session.getEmail().equalsIgnoreCase(paths[2])) {
|
2008-11-26 19:56:28 -05:00
|
|
|
String etag = headers.get("if-match");
|
2009-02-11 20:33:49 -05:00
|
|
|
String noneMatch = headers.get("if-none-match");
|
2009-03-13 13:24:25 -04:00
|
|
|
ExchangeSession.EventResult eventResult = session.createOrUpdateEvent(paths[4].replaceAll("&", "&"), body, etag, noneMatch);
|
2009-02-11 20:33:49 -05:00
|
|
|
if (eventResult.etag != null) {
|
|
|
|
HashMap<String, String> responseHeaders = new HashMap<String, String>();
|
2009-03-05 11:56:04 -05:00
|
|
|
responseHeaders.put("ETag", eventResult.etag);
|
|
|
|
sendHttpResponse(eventResult.status, responseHeaders, null, "", true);
|
2009-02-11 20:33:49 -05:00
|
|
|
} else {
|
|
|
|
sendHttpResponse(eventResult.status, true);
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
|
2009-02-25 05:23:07 -05:00
|
|
|
} else if ("DELETE".equals(command) && "users".equals(paths[1]) && paths.length == 5
|
2008-12-30 07:05:41 -05:00
|
|
|
// only current user for now
|
2009-01-08 04:58:31 -05:00
|
|
|
&& session.getEmail().equalsIgnoreCase(paths[2])) {
|
2009-02-25 05:23:07 -05:00
|
|
|
if ("inbox".equals(paths[3])) {
|
|
|
|
paths[3] = "INBOX";
|
|
|
|
}
|
2009-03-13 17:18:11 -04:00
|
|
|
int status = session.deleteEvent(paths[3], paths[4].replaceAll("&", "&"));
|
2008-12-30 07:05:41 -05:00
|
|
|
sendHttpResponse(status, true);
|
|
|
|
} else if ("GET".equals(command) && "users".equals(paths[1]) && paths.length == 5 && "calendar".equals(paths[3])
|
|
|
|
// only current user for now
|
2009-01-08 04:58:31 -05:00
|
|
|
&& session.getEmail().equalsIgnoreCase(paths[2])) {
|
2009-03-13 13:24:25 -04:00
|
|
|
ExchangeSession.Event event = session.getEvent(paths[3], paths[4]);
|
2008-12-24 11:57:34 -05:00
|
|
|
sendHttpResponse(HttpStatus.SC_OK, null, "text/calendar;charset=UTF-8", event.getICS(), true);
|
|
|
|
|
2008-11-26 19:56:28 -05:00
|
|
|
} else {
|
2008-12-30 07:05:41 -05:00
|
|
|
StringBuilder message = new StringBuilder();
|
|
|
|
message.append("Unsupported request: ").append(command).append(" ").append(path);
|
|
|
|
message.append(" Depth: ").append(depth).append("\n").append(body);
|
|
|
|
DavGatewayTray.error(message.toString());
|
|
|
|
sendErr(HttpStatus.SC_BAD_REQUEST, message.toString());
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-03 06:09:07 -05:00
|
|
|
protected void appendEventsResponses(CaldavResponse response, CaldavRequest request, String path, List<ExchangeSession.Event> events) throws IOException {
|
2008-12-24 10:29:03 -05:00
|
|
|
int size = events.size();
|
|
|
|
int count = 0;
|
|
|
|
for (ExchangeSession.Event event : events) {
|
2008-12-25 17:51:57 -05:00
|
|
|
DavGatewayTray.debug("Retrieving event " + (++count) + "/" + size);
|
2009-03-03 06:09:07 -05:00
|
|
|
appendEventResponse(response, request, path, event);
|
2008-12-24 11:57:34 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-03 06:09:07 -05:00
|
|
|
protected void appendEventResponse(CaldavResponse response, CaldavRequest request, String path, ExchangeSession.Event event) throws IOException {
|
2009-03-04 12:21:15 -05:00
|
|
|
String eventPath = event.getPath().replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
2009-03-03 06:09:07 -05:00
|
|
|
response.startResponse("/users/" + session.getEmail() + "/" + path + "/" + URIUtil.encodeWithinQuery(eventPath));
|
|
|
|
response.startPropstat();
|
2008-12-24 11:57:34 -05:00
|
|
|
if (request.hasProperty("calendar-data")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendCalendarData(event.getICS());
|
2008-12-24 10:29:03 -05:00
|
|
|
}
|
2009-03-04 18:31:20 -05:00
|
|
|
if (request.hasProperty("getcontenttype")) {
|
|
|
|
response.appendProperty("D:getcontenttype", "text/calendar; component=vevent");
|
|
|
|
}
|
2008-12-24 11:57:34 -05:00
|
|
|
if (request.hasProperty("getetag")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:getetag", event.getEtag());
|
2008-12-24 11:57:34 -05:00
|
|
|
}
|
2008-12-30 09:32:19 -05:00
|
|
|
if (request.hasProperty("resourcetype")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:resourcetype");
|
2008-12-30 09:32:19 -05:00
|
|
|
}
|
2008-12-30 17:35:48 -05:00
|
|
|
if (request.hasProperty("displayname")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:displayname", eventPath);
|
2008-12-30 17:35:48 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endPropStatOK();
|
|
|
|
response.endResponse();
|
2008-12-24 10:29:03 -05:00
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
|
2009-03-03 06:09:07 -05:00
|
|
|
public void appendCalendar(CaldavResponse response, String principal, CaldavRequest request) throws IOException {
|
|
|
|
response.startResponse("/users/" + principal + "/calendar");
|
|
|
|
response.startPropstat();
|
2008-12-30 07:05:41 -05:00
|
|
|
|
|
|
|
if (request.hasProperty("resourcetype")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:resourcetype", "<D:collection/>" +
|
|
|
|
"<C:calendar xmlns:C=\"urn:ietf:params:xml:ns:caldav\"/>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
if (request.hasProperty("owner")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:owner", "<D:href>/principals/users/" + principal + "</D:href>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2009-03-04 18:31:20 -05:00
|
|
|
if (request.hasProperty("getcontenttype")) {
|
|
|
|
response.appendProperty("D:getcontenttype", "text/calendar; component=vevent");
|
|
|
|
}
|
2009-01-02 11:07:49 -05:00
|
|
|
if (request.hasProperty("getetag")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:getetag", session.getCalendarEtag());
|
2009-01-02 11:07:49 -05:00
|
|
|
}
|
2008-12-30 07:05:41 -05:00
|
|
|
if (request.hasProperty("getctag")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("CS:getctag", "CS=\"http://calendarserver.org/ns/\"",
|
|
|
|
base64Encode(session.getCalendarCtag()));
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2008-12-30 17:35:48 -05:00
|
|
|
if (request.hasProperty("displayname")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:displayname", "calendar");
|
2008-12-30 17:35:48 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endPropStatOK();
|
|
|
|
response.endResponse();
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
2009-03-03 06:09:07 -05:00
|
|
|
public void appendInbox(CaldavResponse response, String principal, CaldavRequest request) throws IOException {
|
|
|
|
response.startResponse("/users/" + principal + "/inbox");
|
|
|
|
response.startPropstat();
|
2008-12-30 07:05:41 -05:00
|
|
|
|
|
|
|
if (request.hasProperty("resourcetype")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:resourcetype", "<D:collection/>" +
|
|
|
|
"<C:schedule-inbox xmlns:C=\"urn:ietf:params:xml:ns:caldav\"/>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
if (request.hasProperty("getctag")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("CS:getctag", "CS=\"http://calendarserver.org/ns/\"",
|
|
|
|
base64Encode(session.getInboxCtag()));
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2008-12-30 17:35:48 -05:00
|
|
|
if (request.hasProperty("displayname")) {
|
2009-03-04 12:21:15 -05:00
|
|
|
response.appendProperty("D:displayname", "inbox");
|
2008-12-30 17:35:48 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endPropStatOK();
|
|
|
|
response.endResponse();
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
2009-03-03 06:09:07 -05:00
|
|
|
public void appendOutbox(CaldavResponse response, String principal, CaldavRequest request) throws IOException {
|
|
|
|
response.startResponse("/users/" + principal + "/outbox");
|
|
|
|
response.startPropstat();
|
2008-12-30 07:05:41 -05:00
|
|
|
|
|
|
|
if (request.hasProperty("resourcetype")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:resourcetype", "<D:collection/>" +
|
|
|
|
"<C:schedule-outbox xmlns:C=\"urn:ietf:params:xml:ns:caldav\"/>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
if (request.hasProperty("getctag")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("CS:getctag", "CS=\"http://calendarserver.org/ns/\"",
|
|
|
|
"0");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2008-12-30 17:35:48 -05:00
|
|
|
if (request.hasProperty("displayname")) {
|
2009-03-04 12:21:15 -05:00
|
|
|
response.appendProperty("D:displayname", "outbox");
|
2008-12-30 17:35:48 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endPropStatOK();
|
|
|
|
response.endResponse();
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendGetRoot() throws IOException {
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
|
|
|
buffer.append("Connected to DavMail<br/>");
|
|
|
|
buffer.append("UserName :").append(userName).append("<br/>");
|
|
|
|
buffer.append("Email :").append(session.getEmail()).append("<br/>");
|
|
|
|
sendHttpResponse(HttpStatus.SC_OK, null, "text/html;charset=UTF-8", buffer.toString(), true);
|
|
|
|
}
|
|
|
|
|
2009-02-20 12:44:30 -05:00
|
|
|
public void sendInbox(CaldavRequest request, int depth, String principal) throws IOException {
|
2009-03-03 06:09:07 -05:00
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startMultistatus();
|
|
|
|
appendInbox(response, principal, request);
|
2009-02-20 12:44:30 -05:00
|
|
|
if (depth == 1) {
|
|
|
|
DavGatewayTray.debug("Searching calendar messages...");
|
|
|
|
List<ExchangeSession.Event> events = session.getEventMessages();
|
|
|
|
DavGatewayTray.debug("Found " + events.size() + " calendar messages");
|
2009-03-03 06:09:07 -05:00
|
|
|
appendEventsResponses(response, request, "inbox", events);
|
2009-02-20 12:44:30 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endMultistatus();
|
|
|
|
sendHttpResponse(HttpStatus.SC_MULTI_STATUS, null, response, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendOutbox(CaldavRequest request, String principal) throws IOException {
|
2009-03-03 06:09:07 -05:00
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startMultistatus();
|
|
|
|
appendOutbox(response, principal, request);
|
|
|
|
response.endMultistatus();
|
|
|
|
sendHttpResponse(HttpStatus.SC_MULTI_STATUS, null, response, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendCalendar(CaldavRequest request, int depth, String principal) throws IOException {
|
2009-03-03 06:09:07 -05:00
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startMultistatus();
|
|
|
|
appendCalendar(response, principal, request);
|
2008-12-30 07:05:41 -05:00
|
|
|
if (depth == 1) {
|
2009-01-08 04:58:31 -05:00
|
|
|
DavGatewayTray.debug("Searching calendar events...");
|
|
|
|
List<ExchangeSession.Event> events = session.getAllEvents();
|
2009-02-11 20:33:49 -05:00
|
|
|
DavGatewayTray.debug("Found " + events.size() + " calendar events");
|
2009-03-03 06:09:07 -05:00
|
|
|
appendEventsResponses(response, request, "calendar", events);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endMultistatus();
|
|
|
|
sendHttpResponse(HttpStatus.SC_MULTI_STATUS, null, response, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
2009-03-09 12:02:58 -04:00
|
|
|
public void patchCalendar(CaldavRequest request, int depth, String principal) throws IOException {
|
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startMultistatus();
|
|
|
|
// just ignore calendar folder proppatch (color not supported in Exchange)
|
|
|
|
response.endMultistatus();
|
|
|
|
sendHttpResponse(HttpStatus.SC_MULTI_STATUS, null, response, true);
|
|
|
|
}
|
|
|
|
|
2008-12-30 07:05:41 -05:00
|
|
|
protected String getEventFileNameFromPath(String path) {
|
2009-02-25 05:23:07 -05:00
|
|
|
int index = path.lastIndexOf('/');
|
2008-12-30 07:05:41 -05:00
|
|
|
if (index < 0) {
|
|
|
|
return null;
|
|
|
|
} else {
|
2009-03-13 13:24:25 -04:00
|
|
|
return path.substring(index + 1).replaceAll("&", "&");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-02-25 05:23:07 -05:00
|
|
|
public void reportEvents(CaldavRequest request, String path) throws IOException {
|
2008-12-30 07:05:41 -05:00
|
|
|
List<ExchangeSession.Event> events;
|
|
|
|
List<String> notFound = new ArrayList<String>();
|
|
|
|
if (request.isMultiGet()) {
|
|
|
|
events = new ArrayList<ExchangeSession.Event>();
|
2009-01-08 05:35:43 -05:00
|
|
|
int count = 0;
|
|
|
|
int total = request.getHrefs().size();
|
2008-12-30 07:05:41 -05:00
|
|
|
for (String href : request.getHrefs()) {
|
2009-02-11 20:33:49 -05:00
|
|
|
DavGatewayTray.debug("Report event " + (++count) + "/" + total);
|
2008-12-30 07:05:41 -05:00
|
|
|
try {
|
|
|
|
String eventName = getEventFileNameFromPath(href);
|
|
|
|
if (eventName == null) {
|
|
|
|
notFound.add(href);
|
|
|
|
} else {
|
2009-03-13 13:24:25 -04:00
|
|
|
events.add(session.getEvent(path, eventName));
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
} catch (HttpException e) {
|
|
|
|
notFound.add(href);
|
|
|
|
}
|
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
} else if ("INBOX".equals(path)) {
|
2009-02-25 05:23:07 -05:00
|
|
|
events = session.getEventMessages();
|
2008-12-30 07:05:41 -05:00
|
|
|
} else {
|
2009-02-23 09:17:07 -05:00
|
|
|
events = session.getAllEvents();
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
2009-03-03 06:09:07 -05:00
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startMultistatus();
|
|
|
|
appendEventsResponses(response, request, path, events);
|
2008-12-30 07:05:41 -05:00
|
|
|
|
|
|
|
// send not found events errors
|
|
|
|
for (String href : notFound) {
|
2009-03-13 13:24:25 -04:00
|
|
|
response.startResponse(URIUtil.encodePath(href));
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendPropstatNotFound();
|
|
|
|
response.endResponse();
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endMultistatus();
|
2008-12-30 07:05:41 -05:00
|
|
|
|
2009-03-03 06:09:07 -05:00
|
|
|
sendHttpResponse(HttpStatus.SC_MULTI_STATUS, null, response, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendUserRoot(CaldavRequest request, int depth, String principal) throws IOException {
|
2009-03-03 06:09:07 -05:00
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startMultistatus();
|
|
|
|
response.startResponse("/users/" + principal);
|
|
|
|
response.startPropstat();
|
|
|
|
|
2008-12-30 07:05:41 -05:00
|
|
|
|
|
|
|
if (request.hasProperty("resourcetype")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:resourcetype", "<D:collection/>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2008-12-30 17:35:48 -05:00
|
|
|
if (request.hasProperty("displayname")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:displayname", principal);
|
2008-12-30 17:35:48 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endPropStatOK();
|
2008-12-30 07:05:41 -05:00
|
|
|
if (depth == 1) {
|
2009-03-03 06:09:07 -05:00
|
|
|
appendInbox(response, principal, request);
|
|
|
|
appendOutbox(response, principal, request);
|
|
|
|
appendCalendar(response, principal, request);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endResponse();
|
|
|
|
response.endMultistatus();
|
|
|
|
sendHttpResponse(HttpStatus.SC_MULTI_STATUS, null, response, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendRoot(CaldavRequest request) throws IOException {
|
2009-03-03 06:09:07 -05:00
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startMultistatus();
|
|
|
|
response.startResponse("/");
|
|
|
|
response.startPropstat();
|
|
|
|
|
2008-12-30 07:05:41 -05:00
|
|
|
if (request.hasProperty("principal-collection-set")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:principal-collection-set", "<D:href>/principals/users/" + session.getEmail() + "</D:href>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2008-12-30 17:35:48 -05:00
|
|
|
if (request.hasProperty("displayname")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:displayname", "ROOT");
|
2008-12-30 17:35:48 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endPropStatOK();
|
|
|
|
response.endResponse();
|
|
|
|
response.endMultistatus();
|
|
|
|
sendHttpResponse(HttpStatus.SC_MULTI_STATUS, null, response, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendPrincipal(CaldavRequest request, String principal) throws IOException {
|
2009-02-23 04:55:53 -05:00
|
|
|
// actual principal is email address
|
|
|
|
String actualPrincipal = principal;
|
|
|
|
if (userName.equals(principal)) {
|
|
|
|
actualPrincipal = session.getEmail();
|
|
|
|
}
|
|
|
|
|
2009-03-03 06:09:07 -05:00
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startMultistatus();
|
|
|
|
response.startResponse("/principals/users/" + principal);
|
|
|
|
response.startPropstat();
|
|
|
|
|
2008-12-30 07:05:41 -05:00
|
|
|
if (request.hasProperty("calendar-home-set")) {
|
2009-03-04 12:21:15 -05:00
|
|
|
response.appendProperty("C:calendar-home-set", "<D:href>/users/" + actualPrincipal + "/calendar</D:href>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (request.hasProperty("calendar-user-address-set")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("C:calendar-user-address-set", "<D:href>mailto:" + actualPrincipal + "</D:href>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (request.hasProperty("schedule-inbox-URL")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("C:schedule-inbox-URL", "<D:href>/users/" + actualPrincipal + "/inbox</D:href>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (request.hasProperty("schedule-outbox-URL")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("C:schedule-outbox-URL", "<D:href>/users/" + actualPrincipal + "/outbox</D:href>");
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
2008-12-30 17:35:48 -05:00
|
|
|
if (request.hasProperty("displayname")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:displayname", actualPrincipal);
|
2008-12-30 17:35:48 -05:00
|
|
|
}
|
2008-12-30 17:50:51 -05:00
|
|
|
if (request.hasProperty("resourcetype")) {
|
2009-03-03 06:09:07 -05:00
|
|
|
response.appendProperty("D:resourcetype", "<D:collection/><D:principal/>");
|
2009-01-19 09:44:02 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endPropStatOK();
|
|
|
|
response.endResponse();
|
|
|
|
response.endMultistatus();
|
|
|
|
sendHttpResponse(HttpStatus.SC_MULTI_STATUS, null, response, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendFreeBusy(String body) throws IOException {
|
2009-02-12 05:10:23 -05:00
|
|
|
HashMap<String, String> valueMap = new HashMap<String, String>();
|
|
|
|
ArrayList<String> attendees = new ArrayList<String>();
|
|
|
|
HashMap<String, String> attendeeKeyMap = new HashMap<String, String>();
|
2009-02-09 05:12:09 -05:00
|
|
|
ICSBufferedReader reader = new ICSBufferedReader(new StringReader(body));
|
2008-12-30 07:05:41 -05:00
|
|
|
String line;
|
2009-02-09 05:12:09 -05:00
|
|
|
String key;
|
2008-12-30 07:05:41 -05:00
|
|
|
while ((line = reader.readLine()) != null) {
|
2009-02-11 20:33:49 -05:00
|
|
|
int index = line.indexOf(':');
|
|
|
|
if (index <= 0) {
|
|
|
|
throw new IOException("Invalid request: " + body);
|
|
|
|
}
|
|
|
|
String fullkey = line.substring(0, index);
|
|
|
|
String value = line.substring(index + 1);
|
|
|
|
int semicolonIndex = fullkey.indexOf(";");
|
|
|
|
if (semicolonIndex > 0) {
|
|
|
|
key = fullkey.substring(0, semicolonIndex);
|
|
|
|
} else {
|
|
|
|
key = fullkey;
|
|
|
|
}
|
2009-02-12 05:10:23 -05:00
|
|
|
if ("ATTENDEE".equals(key)) {
|
|
|
|
attendees.add(value);
|
|
|
|
attendeeKeyMap.put(value, fullkey);
|
|
|
|
} else {
|
|
|
|
valueMap.put(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// get freebusy for each attendee
|
|
|
|
HashMap<String, String> freeBusyMap = new HashMap<String, String>();
|
|
|
|
for (String attendee : attendees) {
|
|
|
|
String freeBusy = session.getFreebusy(attendee, valueMap);
|
|
|
|
if (freeBusy != null) {
|
|
|
|
freeBusyMap.put(attendee, freeBusy);
|
|
|
|
}
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
2009-02-12 05:10:23 -05:00
|
|
|
if (!freeBusyMap.isEmpty()) {
|
2009-03-03 06:09:07 -05:00
|
|
|
CaldavResponse response = new CaldavResponse();
|
|
|
|
response.startScheduleResponse();
|
2009-02-09 05:12:09 -05:00
|
|
|
|
2009-02-12 05:10:23 -05:00
|
|
|
for (Map.Entry<String, String> entry : freeBusyMap.entrySet()) {
|
|
|
|
String attendee = entry.getKey();
|
2009-03-03 06:09:07 -05:00
|
|
|
response.startRecipientResponse(attendee);
|
|
|
|
|
|
|
|
StringBuilder ics = new StringBuilder();
|
|
|
|
ics.append("<C:calendar-data>BEGIN:VCALENDAR").append((char) 13).append((char) 10)
|
2009-02-12 05:10:23 -05:00
|
|
|
.append("VERSION:2.0").append((char) 13).append((char) 10)
|
|
|
|
.append("PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN").append((char) 13).append((char) 10)
|
|
|
|
.append("METHOD:REPLY").append((char) 13).append((char) 10)
|
|
|
|
.append("BEGIN:VFREEBUSY").append((char) 13).append((char) 10)
|
|
|
|
.append("DTSTAMP:").append(valueMap.get("DTSTAMP")).append("").append((char) 13).append((char) 10)
|
|
|
|
.append("ORGANIZER:").append(valueMap.get("ORGANIZER")).append("").append((char) 13).append((char) 10)
|
|
|
|
.append("DTSTART:").append(valueMap.get("DTSTART")).append("").append((char) 13).append((char) 10)
|
|
|
|
.append("DTEND:").append(valueMap.get("DTEND")).append("").append((char) 13).append((char) 10)
|
|
|
|
.append("UID:").append(valueMap.get("UID")).append("").append((char) 13).append((char) 10)
|
|
|
|
.append(attendeeKeyMap.get(attendee)).append(":").append(attendee).append("").append((char) 13).append((char) 10);
|
|
|
|
if (entry.getValue().length() > 0) {
|
2009-03-03 06:09:07 -05:00
|
|
|
ics.append("FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:").append(entry.getValue()).append("").append((char) 13).append((char) 10);
|
2009-02-12 05:10:23 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
ics.append("END:VFREEBUSY").append((char) 13).append((char) 10)
|
|
|
|
.append("END:VCALENDAR");
|
|
|
|
response.appendCalendarData(ics.toString());
|
|
|
|
response.endRecipientResponse();
|
|
|
|
|
2009-02-12 05:10:23 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
response.endScheduleResponse();
|
|
|
|
sendHttpResponse(HttpStatus.SC_OK, null, response, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
} else {
|
|
|
|
sendHttpResponse(HttpStatus.SC_NOT_FOUND, null, "text/plain", "Unknown recipient: " + valueMap.get("ATTENDEE"), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void sendRedirect(Map<String, String> headers, String path) throws IOException {
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
|
|
|
if (headers.get("host") != null) {
|
|
|
|
buffer.append("http://").append(headers.get("host"));
|
|
|
|
}
|
|
|
|
buffer.append(path);
|
|
|
|
Map<String, String> responseHeaders = new HashMap<String, String>();
|
|
|
|
responseHeaders.put("Location", buffer.toString());
|
2009-03-03 06:09:07 -05:00
|
|
|
sendHttpResponse(HttpStatus.SC_MOVED_PERMANENTLY, responseHeaders, true);
|
2008-12-30 07:05:41 -05:00
|
|
|
}
|
|
|
|
|
2008-12-30 17:35:48 -05:00
|
|
|
public void sendErr(int status, Exception e) throws IOException {
|
|
|
|
String message = e.getMessage();
|
|
|
|
if (message == null) {
|
|
|
|
message = e.toString();
|
|
|
|
}
|
|
|
|
sendErr(status, message);
|
|
|
|
}
|
|
|
|
|
2008-11-26 19:56:28 -05:00
|
|
|
public void sendErr(int status, String message) throws IOException {
|
|
|
|
sendHttpResponse(status, null, "text/plain;charset=UTF-8", message, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendOptions() throws IOException {
|
|
|
|
HashMap<String, String> headers = new HashMap<String, String>();
|
|
|
|
headers.put("Allow", "OPTIONS, GET, PROPFIND, PUT, POST");
|
2009-03-03 06:09:07 -05:00
|
|
|
sendHttpResponse(HttpStatus.SC_OK, headers, true);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendUnauthorized() throws IOException {
|
|
|
|
HashMap<String, String> headers = new HashMap<String, String>();
|
|
|
|
headers.put("WWW-Authenticate", "Basic realm=\"" + Settings.getProperty("davmail.url") + "\"");
|
2009-03-03 06:09:07 -05:00
|
|
|
sendHttpResponse(HttpStatus.SC_UNAUTHORIZED, headers, true);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendHttpResponse(int status, boolean keepAlive) throws IOException {
|
2009-03-03 06:09:07 -05:00
|
|
|
sendHttpResponse(status, null, null, (byte[]) null, keepAlive);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendHttpResponse(int status, Map<String, String> headers, boolean keepAlive) throws IOException {
|
|
|
|
sendHttpResponse(status, headers, null, (byte[]) null, keepAlive);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendHttpResponse(int status, Map<String, String> headers, CaldavResponse response, boolean keepAlive) throws IOException {
|
|
|
|
sendHttpResponse(status, headers, "text/xml;charset=UTF-8", response.getBytes(), keepAlive);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendHttpResponse(int status, Map<String, String> headers, String contentType, String content, boolean keepAlive) throws IOException {
|
2009-03-03 06:09:07 -05:00
|
|
|
sendHttpResponse(status, headers, contentType, content.getBytes("UTF-8"), keepAlive);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendHttpResponse(int status, Map<String, String> headers, String contentType, byte[] content, boolean keepAlive) throws IOException {
|
2008-11-26 19:56:28 -05:00
|
|
|
sendClient("HTTP/1.1 " + status + " " + HttpStatus.getStatusText(status));
|
|
|
|
sendClient("Server: DavMail Gateway");
|
2009-03-03 06:24:30 -05:00
|
|
|
sendClient("DAV: 1, 2, 3, access-control, calendar-access, ticket, calendar-schedule, calendarserver-private-events");
|
2008-11-26 19:56:28 -05:00
|
|
|
SimpleDateFormat formatter = new java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
|
|
|
|
sendClient("Date: " + formatter.format(new java.util.Date()));
|
|
|
|
if (headers != null) {
|
2008-12-17 10:22:37 -05:00
|
|
|
for (Map.Entry<String, String> header : headers.entrySet()) {
|
2008-12-02 05:20:46 -05:00
|
|
|
sendClient(header.getKey() + ": " + header.getValue());
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (contentType != null) {
|
|
|
|
sendClient("Content-Type: " + contentType);
|
|
|
|
}
|
2009-02-11 20:33:49 -05:00
|
|
|
closed = closed || !keepAlive;
|
|
|
|
sendClient("Connection: " + (closed ? "close" : "keep-alive"));
|
2009-03-03 06:09:07 -05:00
|
|
|
if (content != null && content.length > 0) {
|
|
|
|
sendClient("Content-Length: " + content.length);
|
2008-11-26 19:56:28 -05:00
|
|
|
} else {
|
|
|
|
sendClient("Content-Length: 0");
|
|
|
|
}
|
|
|
|
sendClient("");
|
2009-03-03 06:09:07 -05:00
|
|
|
if (content != null && content.length > 0) {
|
2008-12-24 10:52:13 -05:00
|
|
|
// full debug trace
|
2008-12-26 07:35:08 -05:00
|
|
|
if (wireLogger.isDebugEnabled()) {
|
2009-03-03 06:09:07 -05:00
|
|
|
wireLogger.debug("> " + new String(content, "UTF-8"));
|
2008-12-26 07:35:08 -05:00
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
sendClient(content);
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decode HTTP credentials
|
|
|
|
*
|
|
|
|
* @param authorization http authorization header value
|
|
|
|
* @throws java.io.IOException if invalid credentials
|
|
|
|
*/
|
|
|
|
protected void decodeCredentials(String authorization) throws IOException {
|
|
|
|
int index = authorization.indexOf(' ');
|
|
|
|
if (index > 0) {
|
|
|
|
String mode = authorization.substring(0, index).toLowerCase();
|
|
|
|
if (!"basic".equals(mode)) {
|
|
|
|
throw new IOException("Unsupported authorization mode: " + mode);
|
|
|
|
}
|
|
|
|
String encodedCredentials = authorization.substring(index + 1);
|
|
|
|
String decodedCredentials = base64Decode(encodedCredentials);
|
|
|
|
index = decodedCredentials.indexOf(':');
|
|
|
|
if (index > 0) {
|
|
|
|
userName = decodedCredentials.substring(0, index);
|
|
|
|
password = decodedCredentials.substring(index + 1);
|
|
|
|
} else {
|
|
|
|
throw new IOException("Invalid credentials");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new IOException("Invalid credentials");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2009-01-09 17:41:10 -05:00
|
|
|
protected static class CaldavRequest {
|
2009-02-24 06:53:02 -05:00
|
|
|
protected final HashSet<String> properties = new HashSet<String>();
|
2008-12-17 10:22:37 -05:00
|
|
|
protected HashSet<String> hrefs;
|
|
|
|
protected boolean isMultiGet;
|
|
|
|
|
|
|
|
public CaldavRequest(String body) throws IOException {
|
|
|
|
// parse body
|
|
|
|
XMLStreamReader streamReader = null;
|
|
|
|
try {
|
|
|
|
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
|
|
|
|
inputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
|
|
|
|
inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE);
|
|
|
|
|
|
|
|
streamReader = inputFactory.createXMLStreamReader(new StringReader(body));
|
|
|
|
boolean inElement = false;
|
|
|
|
boolean inProperties = false;
|
|
|
|
String currentElement = null;
|
|
|
|
while (streamReader.hasNext()) {
|
|
|
|
int event = streamReader.next();
|
|
|
|
if (event == XMLStreamConstants.START_ELEMENT) {
|
|
|
|
inElement = true;
|
|
|
|
currentElement = streamReader.getLocalName();
|
|
|
|
if ("prop".equals(currentElement)) {
|
|
|
|
inProperties = true;
|
|
|
|
} else if ("calendar-multiget".equals(currentElement)) {
|
|
|
|
isMultiGet = true;
|
|
|
|
} else if (inProperties) {
|
|
|
|
properties.add(currentElement);
|
|
|
|
}
|
|
|
|
} else if (event == XMLStreamConstants.END_ELEMENT) {
|
|
|
|
if ("prop".equals(currentElement)) {
|
|
|
|
inProperties = false;
|
|
|
|
}
|
|
|
|
} else if (event == XMLStreamConstants.CHARACTERS && inElement) {
|
|
|
|
if ("href".equals(currentElement)) {
|
|
|
|
if (hrefs == null) {
|
|
|
|
hrefs = new HashSet<String>();
|
|
|
|
}
|
2009-02-03 10:30:11 -05:00
|
|
|
hrefs.add(URIUtil.decode(streamReader.getText()));
|
2008-12-17 10:22:37 -05:00
|
|
|
}
|
|
|
|
inElement = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (XMLStreamException e) {
|
|
|
|
throw new IOException(e.getMessage());
|
|
|
|
} finally {
|
|
|
|
try {
|
|
|
|
if (streamReader != null) {
|
|
|
|
streamReader.close();
|
|
|
|
}
|
|
|
|
} catch (XMLStreamException e) {
|
|
|
|
DavGatewayTray.error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean hasProperty(String propertyName) {
|
|
|
|
return properties.contains(propertyName);
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isMultiGet() {
|
|
|
|
return isMultiGet && hrefs != null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Set<String> getHrefs() {
|
|
|
|
return hrefs;
|
|
|
|
}
|
|
|
|
}
|
2009-03-03 06:09:07 -05:00
|
|
|
|
|
|
|
protected static class CaldavResponse {
|
2009-03-04 17:05:26 -05:00
|
|
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
2009-03-03 06:09:07 -05:00
|
|
|
OutputStreamWriter writer;
|
|
|
|
|
|
|
|
public CaldavResponse() throws IOException {
|
|
|
|
writer = new OutputStreamWriter(baos, "UTF-8");
|
|
|
|
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void startMultistatus() throws IOException {
|
|
|
|
writer.write("<D:multistatus xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void startResponse(String href) throws IOException {
|
|
|
|
writer.write("<D:response>");
|
|
|
|
writer.write("<D:href>");
|
|
|
|
writer.write(href);
|
|
|
|
writer.write("</D:href>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void startPropstat() throws IOException {
|
|
|
|
writer.write("<D:propstat>");
|
|
|
|
writer.write("<D:prop>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void appendCalendarData(String ics) throws IOException {
|
|
|
|
if (ics != null && ics.length() > 0) {
|
|
|
|
ics = ics.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
|
|
writer.write("<C:calendar-data xmlns:C=\"urn:ietf:params:xml:ns:caldav\"");
|
|
|
|
writer.write(" C:content-type=\"text/calendar\" C:version=\"2.0\">");
|
|
|
|
writer.write(ics);
|
|
|
|
writer.write("</C:calendar-data>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void appendProperty(String propertyName) throws IOException {
|
|
|
|
appendProperty(propertyName, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void appendProperty(String propertyName, String propertyValue) throws IOException {
|
|
|
|
appendProperty(propertyName, null, propertyValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void appendProperty(String propertyName, String namespace, String propertyValue) throws IOException {
|
|
|
|
if (propertyValue != null) {
|
|
|
|
writer.write('<');
|
|
|
|
writer.write(propertyName);
|
|
|
|
if (namespace != null) {
|
|
|
|
writer.write(" xmlns:");
|
|
|
|
writer.write(namespace);
|
|
|
|
}
|
|
|
|
writer.write('>');
|
|
|
|
writer.write(propertyValue);
|
|
|
|
writer.write("</");
|
|
|
|
writer.write(propertyName);
|
|
|
|
writer.write('>');
|
|
|
|
} else {
|
|
|
|
writer.write('<');
|
|
|
|
writer.write(propertyName);
|
|
|
|
writer.write("/>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void endPropStatOK() throws IOException {
|
|
|
|
writer.write("</D:prop>");
|
|
|
|
writer.write("<D:status>HTTP/1.1 200 OK</D:status>");
|
|
|
|
writer.write("</D:propstat>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void appendPropstatNotFound() throws IOException {
|
|
|
|
writer.write("<D:propstat>");
|
|
|
|
writer.write("<D:status>HTTP/1.1 404 Not Found</D:status>");
|
|
|
|
writer.write("</D:propstat>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void endResponse() throws IOException {
|
|
|
|
writer.write("</D:response>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void endMultistatus() throws IOException {
|
|
|
|
writer.write("</D:multistatus>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void startScheduleResponse() throws IOException {
|
|
|
|
writer.write("<C:schedule-response xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void startRecipientResponse(String recipient) throws IOException {
|
|
|
|
writer.write("<C:response>");
|
|
|
|
writer.write("<C:recipient>");
|
|
|
|
writer.write("<D:href>");
|
|
|
|
writer.write(recipient);
|
|
|
|
writer.write("</D:href>");
|
|
|
|
writer.write("</C:recipient>");
|
|
|
|
writer.write("<C:request-status>2.0;Success</C:request-status>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void endRecipientResponse() throws IOException {
|
|
|
|
writer.write("</C:response>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void endScheduleResponse() throws IOException {
|
|
|
|
writer.write("</C:schedule-response>");
|
|
|
|
}
|
|
|
|
|
|
|
|
public void close() throws IOException {
|
|
|
|
try {
|
|
|
|
writer.close();
|
|
|
|
} finally {
|
|
|
|
baos.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public byte[] getBytes() throws IOException {
|
|
|
|
close();
|
|
|
|
return baos.toByteArray();
|
|
|
|
}
|
|
|
|
}
|
2008-11-26 19:56:28 -05:00
|
|
|
}
|
|
|
|
|