Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caldav: support context path when operating behind a reverse proxy #110

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/java/davmail/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public static void setDefaultSettings() {
SETTINGS.put("davmail.sentKeepDelay", "0");
SETTINGS.put("davmail.caldavPastDelay", "0");
SETTINGS.put("davmail.caldavAutoSchedule", Boolean.TRUE.toString());
SETTINGS.put("davmail.caldavContextPath", "");
SETTINGS.put("davmail.imapIdleDelay", "");
SETTINGS.put("davmail.folderSizeLimit", "");
SETTINGS.put("davmail.enableKeepAlive", Boolean.FALSE.toString());
Expand Down
42 changes: 29 additions & 13 deletions src/java/davmail/caldav/CaldavConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;


/**
* Handle a caldav connection.
Expand All @@ -66,6 +68,7 @@ public class CaldavConnection extends AbstractConnection {
*/
protected static final int MAX_KEEP_ALIVE_TIME = 300;
protected final Logger wireLogger = Logger.getLogger(this.getClass());
protected final String contextPath;

protected boolean closed;

Expand All @@ -79,11 +82,12 @@ public class CaldavConnection extends AbstractConnection {
ical_allowed_abs_path.clear('@');
}

static String encodePath(CaldavRequest request, String path) {
protected String encodePath(CaldavRequest request, String path) {
// restore the context path that was removed in #run()
if (request.isIcal5()) {
return URIUtil.encode(path, ical_allowed_abs_path);
return URIUtil.encode(contextPath + path, ical_allowed_abs_path);
} else {
return URIUtil.encodePath(path);
return URIUtil.encodePath(contextPath + path);
}
}

Expand All @@ -96,6 +100,10 @@ public CaldavConnection(Socket clientSocket) {
super(CaldavConnection.class.getSimpleName(), clientSocket, "UTF-8");
// set caldav logging to davmail logging level
wireLogger.setLevel(Settings.getLoggingLevel("davmail"));

// prevent trailing '/' in the context path, also for the root context
contextPath = Settings.getProperty("davmail.caldavContextPath", "")
.replaceFirst("/+$", "");
}

protected Map<String, String> parseHeaders() throws IOException {
Expand Down Expand Up @@ -150,6 +158,7 @@ protected void setSocketTimeout(String keepAliveValue) throws IOException {
public void run() {
String line;
StringTokenizer tokens;
final Pattern SLASHES_RE = Pattern.compile("/{2,}");

try {
while (!closed) {
Expand All @@ -163,6 +172,13 @@ public void run() {
Map<String, String> headers = parseHeaders();
String encodedPath = StringUtil.encodePlusSign(tokens.nextToken());
String path = URIUtil.decode(encodedPath);
path = SLASHES_RE.matcher(path).replaceAll("/");
if (!path.startsWith(contextPath + '/')) {
throw new DavMailException("EXCEPTION_CONTEXT_PATH_MISMATCH", path, contextPath);
}
// remove the context path; it will be restored in #encodePath(CaldavRequest, String)
// and in #sendWellKnown()
path = path.substring(contextPath.length());
String content = getContent(headers.get("content-length"));
setSocketTimeout(headers.get("keep-alive"));
// client requested connection close
Expand Down Expand Up @@ -504,9 +520,9 @@ public void appendFolderOrItem(CaldavResponse response, CaldavRequest request, E
}
if (request.hasProperty("owner")) {
if ("users".equals(request.getPathElement(1))) {
response.appendHrefProperty("D:owner", "/principals/users/" + request.getPathElement(2));
response.appendHrefProperty("D:owner", encodePath(request, "/principals/users/" + request.getPathElement(2)));
} else {
response.appendHrefProperty("D:owner", "/principals" + request.getPath());
response.appendHrefProperty("D:owner", encodePath(request, "/principals" + request.getPath()));
}
}
if (request.hasProperty("getcontenttype")) {
Expand Down Expand Up @@ -917,11 +933,11 @@ public void sendUserRoot(CaldavRequest request) throws IOException {
public void sendRoot(CaldavRequest request) throws IOException {
CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
response.startMultistatus();
response.startResponse("/");
response.startResponse(encodePath(request, "/"));
response.startPropstat();

if (request.hasProperty("principal-collection-set")) {
response.appendHrefProperty("D:principal-collection-set", "/principals/users/");
response.appendHrefProperty("D:principal-collection-set", encodePath(request, "/principals/users/"));
}
if (request.hasProperty("displayname")) {
response.appendProperty("D:displayname", "ROOT");
Expand All @@ -936,7 +952,7 @@ public void sendRoot(CaldavRequest request) throws IOException {
response.endResponse();
if (request.depth == 1) {
// iPhone workaround: send calendar subfolder
response.startResponse("/users/" + session.getEmail() + "/calendar");
response.startResponse(encodePath(request, "/users/" + session.getEmail() + "/calendar"));
response.startPropstat();
if (request.hasProperty("resourcetype")) {
response.appendProperty("D:resourcetype", "<D:collection/>" +
Expand All @@ -951,7 +967,7 @@ public void sendRoot(CaldavRequest request) throws IOException {
response.endPropStatOK();
response.endResponse();

response.startResponse("/users");
response.startResponse(encodePath(request, "/users"));
response.startPropstat();
if (request.hasProperty("displayname")) {
response.appendProperty("D:displayname", "users");
Expand All @@ -962,7 +978,7 @@ public void sendRoot(CaldavRequest request) throws IOException {
response.endPropStatOK();
response.endResponse();

response.startResponse("/principals");
response.startResponse(encodePath(request, "/principals"));
response.startPropstat();
if (request.hasProperty("displayname")) {
response.appendProperty("D:displayname", "principals");
Expand All @@ -986,7 +1002,7 @@ public void sendRoot(CaldavRequest request) throws IOException {
public void sendDirectory(CaldavRequest request) throws IOException {
CaldavResponse response = new CaldavResponse(HttpStatus.SC_MULTI_STATUS);
response.startMultistatus();
response.startResponse("/directory/");
response.startResponse(encodePath(request, "/directory/"));
response.startPropstat();
if (request.hasProperty("current-user-privilege-set")) {
response.appendProperty("D:current-user-privilege-set", "<D:privilege><D:read/></D:privilege>");
Expand All @@ -1004,7 +1020,7 @@ public void sendDirectory(CaldavRequest request) throws IOException {
*/
public void sendWellKnown() throws IOException {
HashMap<String, String> headers = new HashMap<>();
headers.put("Location", "/");
headers.put("Location", contextPath + "/");
sendHttpResponse(HttpStatus.SC_MOVED_PERMANENTLY, headers);
}

Expand Down Expand Up @@ -1067,7 +1083,7 @@ public void sendPrincipal(CaldavRequest request, String prefix, String principal
} else {
// public calendar, send root href as inbox url (always empty) for Lightning
if (request.isLightning() && request.hasProperty("schedule-inbox-URL")) {
response.appendHrefProperty("C:schedule-inbox-URL", "/");
response.appendHrefProperty("C:schedule-inbox-URL", encodePath(request, "/"));
}
// send user outbox
if (request.hasProperty("schedule-outbox-URL")) {
Expand Down
1 change: 1 addition & 0 deletions src/java/davmailmessages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ EXCEPTION_UNABLE_TO_UPDATE_MESSAGE=Unable to update message properties
EXCEPTION_CONNECT=Connect exception: {0} {1}
EXCEPTION_UNSUPPORTED_AUTHORIZATION_MODE=Unsupported authorization mode: {0}
EXCEPTION_UNSUPPORTED_VALUE=Unsupported value: {0}
EXCEPTION_CONTEXT_PATH_MISMATCH=Request {0} does not match context path {1}
LOG_CLIENT_CLOSED_CONNECTION=Client closed connection
LOG_CLOSE_CONNECTION_ON_TIMEOUT=Closing connection on timeout
LOG_CONNECTION_CLOSED=Connection closed
Expand Down
1 change: 1 addition & 0 deletions src/java/davmailmessages_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ EXCEPTION_UNABLE_TO_UPDATE_MESSAGE=Impossible de mettre
EXCEPTION_CONNECT=Exception lors de la connexion : {0} {1}
EXCEPTION_UNSUPPORTED_AUTHORIZATION_MODE=Mode d'authentification invalide : {0}
EXCEPTION_UNSUPPORTED_VALUE=Valeur non support�e : {0}
EXCEPTION_CONTEXT_PATH_MISMATCH=La demande {0} ne correspond pas au chemin de contexte {1}
LOG_CLIENT_CLOSED_CONNECTION=Connection ferm�e par le client
LOG_CLOSE_CONNECTION_ON_TIMEOUT=Connection ferm�e sur expiration
LOG_CONNECTION_CLOSED=Connection ferm�e
Expand Down
1 change: 1 addition & 0 deletions src/java/davmailmessages_it.properties
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ EXCEPTION_UNABLE_TO_UPDATE_MESSAGE=Impossibile aggiornare le propriet
EXCEPTION_CONNECT=Errore durante il collegamento: {0} {1}
EXCEPTION_UNSUPPORTED_AUTHORIZATION_MODE=Autenticazione utente non valido: {0}
EXCEPTION_UNSUPPORTED_VALUE=Valore non supportato: {0}
EXCEPTION_CONTEXT_PATH_MISMATCH=La richiesta {0} non corrisponde al percorso di contesto {1}
LOG_CLIENT_CLOSED_CONNECTION=Connessione chiusa dal client
LOG_CLOSE_CONNECTION_ON_TIMEOUT=Si � verificato un timeout di connessione
LOG_CONNECTION_CLOSED=Connessione chiusa
Expand Down