![JAR search and dependency download from the Maven repository](/logo.png)
org.bedework.caldav.bwserver.BwSysIntfImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bw-calendar-engine-caldav Show documentation
Show all versions of bw-calendar-engine-caldav Show documentation
Main calendar engine code for bedework
/* ********************************************************************
Licensed to Jasig under one or more contributor license
agreements. See the NOTICE file distributed with this work
for additional information regarding copyright ownership.
Jasig licenses this file to you under the Apache License,
Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a
copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.bedework.caldav.bwserver;
import org.bedework.access.AccessPrincipal;
import org.bedework.access.Acl;
import org.bedework.access.CurrentAccess;
import org.bedework.access.PrivilegeDefs;
import org.bedework.access.WhoDefs;
import org.bedework.base.exc.BedeworkAccessException;
import org.bedework.base.exc.BedeworkException;
import org.bedework.base.exc.BedeworkForbidden;
import org.bedework.base.exc.persist.BedeworkStaleStateException;
import org.bedework.base.response.GetEntityResponse;
import org.bedework.base.response.Response;
import org.bedework.caldav.server.CalDAVCollection;
import org.bedework.caldav.server.CalDAVEvent;
import org.bedework.caldav.server.CalDAVResource;
import org.bedework.caldav.server.CalDavHeaders;
import org.bedework.caldav.server.PropertyHandler;
import org.bedework.caldav.server.PropertyHandler.PropertyType;
import org.bedework.caldav.server.SysIntfReader;
import org.bedework.caldav.server.SysiIcalendar;
import org.bedework.caldav.server.sysinterface.CalPrincipalInfo;
import org.bedework.caldav.server.sysinterface.RetrievalMode;
import org.bedework.caldav.server.sysinterface.SysIntf;
import org.bedework.caldav.server.sysinterface.SysIntf.SynchReportData.SynchReportDataItem;
import org.bedework.caldav.util.TimeRange;
import org.bedework.caldav.util.filter.FilterBase;
import org.bedework.caldav.util.notifications.NotificationType;
import org.bedework.caldav.util.sharing.InviteReplyType;
import org.bedework.caldav.util.sharing.InviteType;
import org.bedework.caldav.util.sharing.ShareResultType;
import org.bedework.caldav.util.sharing.ShareType;
import org.bedework.caldav.util.sharing.UserType;
import org.bedework.calfacade.BwCalendar;
import org.bedework.calfacade.BwCategory;
import org.bedework.calfacade.BwDateTime;
import org.bedework.calfacade.BwEvent;
import org.bedework.calfacade.BwEventObj;
import org.bedework.calfacade.BwEventProxy;
import org.bedework.calfacade.BwOrganizer;
import org.bedework.calfacade.BwPrincipal;
import org.bedework.calfacade.BwPrincipalInfo;
import org.bedework.calfacade.BwResource;
import org.bedework.calfacade.BwString;
import org.bedework.calfacade.BwVersion;
import org.bedework.calfacade.BwXproperty;
import org.bedework.calfacade.CollectionAliases;
import org.bedework.calfacade.CollectionInfo;
import org.bedework.calfacade.RecurringRetrievalMode;
import org.bedework.calfacade.RecurringRetrievalMode.Rmode;
import org.bedework.calfacade.ScheduleResult;
import org.bedework.calfacade.ScheduleResult.ScheduleRecipientResult;
import org.bedework.calfacade.configs.AuthProperties;
import org.bedework.calfacade.configs.BasicSystemProperties;
import org.bedework.calfacade.configs.Configurations;
import org.bedework.calfacade.configs.NotificationProperties;
import org.bedework.calfacade.configs.SystemProperties;
import org.bedework.calfacade.exc.CalFacadeErrorCode;
import org.bedework.calfacade.exc.CalFacadeInvalidSynctoken;
import org.bedework.calfacade.filter.RetrieveList;
import org.bedework.calfacade.filter.SfpTokenizer;
import org.bedework.calfacade.filter.SimpleFilterParser;
import org.bedework.calfacade.filter.SimpleFilterParser.ParseResult;
import org.bedework.calfacade.ifs.Directories;
import org.bedework.calfacade.indexing.BwIndexer.DeletedState;
import org.bedework.calfacade.svc.BwPreferences;
import org.bedework.calfacade.svc.BwPreferences.CategoryMapping;
import org.bedework.calfacade.svc.BwPreferences.CategoryMappings;
import org.bedework.calfacade.svc.CalSvcIPars;
import org.bedework.calfacade.svc.EventInfo;
import org.bedework.calfacade.svc.SharingReplyResult;
import org.bedework.calfacade.util.CategoryMapInfo;
import org.bedework.calsvci.CalSvcFactoryDefault;
import org.bedework.calsvci.CalSvcI;
import org.bedework.calsvci.CalendarsI;
import org.bedework.calsvci.SynchReport;
import org.bedework.calsvci.SynchReportItem;
import org.bedework.convert.IcalTranslator;
import org.bedework.convert.Icalendar;
import org.bedework.convert.ical.IcalMalformedException;
import org.bedework.convert.ical.VFreeUtil;
import org.bedework.convert.jcal.JcalTranslator;
import org.bedework.convert.jscal.JSCalTranslator;
import org.bedework.convert.xcal.XmlTranslator;
import org.bedework.jsforj.model.JSGroup;
import org.bedework.sysevents.events.HttpEvent;
import org.bedework.sysevents.events.HttpOutEvent;
import org.bedework.sysevents.events.SysEventBase.SysCode;
import org.bedework.util.calendar.IcalDefs;
import org.bedework.util.calendar.IcalendarUtil;
import org.bedework.util.calendar.ScheduleMethods;
import org.bedework.util.calendar.XcalUtil;
import org.bedework.util.logging.BwLogger;
import org.bedework.util.logging.Logged;
import org.bedework.util.misc.Util;
import org.bedework.util.xml.XmlEmit;
import org.bedework.util.xml.tagdefs.CaldavTags;
import org.bedework.util.xml.tagdefs.WebdavTags;
import org.bedework.util.xml.tagdefs.XcalTags;
import org.bedework.webdav.servlet.shared.PrincipalPropertySearch;
import org.bedework.webdav.servlet.shared.UrlHandler;
import org.bedework.webdav.servlet.shared.WdCollection;
import org.bedework.webdav.servlet.shared.WdEntity;
import org.bedework.webdav.servlet.shared.WebdavBadRequest;
import org.bedework.webdav.servlet.shared.WebdavException;
import org.bedework.webdav.servlet.shared.WebdavForbidden;
import org.bedework.webdav.servlet.shared.WebdavNotFound;
import org.bedework.webdav.servlet.shared.WebdavNsNode.PropertyTagEntry;
import org.bedework.webdav.servlet.shared.WebdavProperty;
import org.bedework.webdav.servlet.shared.WebdavUnauthorized;
import ietf.params.xml.ns.caldav.ExpandType;
import ietf.params.xml.ns.caldav.LimitRecurrenceSetType;
import ietf.params.xml.ns.icalendar_2.IcalendarType;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.TimeZone;
import net.fortuna.ical4j.model.component.VFreeBusy;
import org.apache.james.jdkim.api.JDKIM;
import org.oasis_open.docs.ws_calendar.ns.soap.ComponentSelectionType;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import static org.bedework.base.response.Response.Status.limitExceeded;
import static org.bedework.base.response.Response.Status.noAccess;
import static org.bedework.base.response.Response.Status.ok;
/** Bedework implementation of SysIntf.
*
* @author Mike Douglass douglm at rpi.edu
*/
public class BwSysIntfImpl implements Logged, SysIntf {
private boolean bedeworkExtensionsEnabled;
protected BwPrincipal> currentPrincipal;
private CalPrincipalInfo principalInfo;
private BwPreferences prefs;
private static byte[] key;
private static final Object keyPairLock = new Object();
/* These two set after a call to getSvci()
*/
private IcalTranslator trans;
private XmlTranslator xmlTrans;
private JcalTranslator jcalTrans;
private JSCalTranslator jscalTrans;
private CalSvcI svci;
private UrlHandler urlHandler;
private AuthProperties authProperties;
private SystemProperties sysProperties;
private long reqInTime;
private boolean calWs;
private boolean synchWs;
private static Configurations configs;
static {
try {
configs = CalSvcFactoryDefault.getSystemConfig();
} catch (final Throwable t) {
t.printStackTrace();
}
}
final static List roMethods = Arrays.asList("GET", "REPORT", "PROPFIND");
private final static Set readOnlyMethods =
new TreeSet<>(roMethods);
private static class HttpReqInfo {
private final HttpServletRequest req;
HttpReqInfo(final HttpServletRequest req) {
this.req = req;
}
String runAs() {
if (req == null) {
return null;
}
return CalDavHeaders.getRunAs(req);
}
boolean readOnly() {
return readOnlyMethods.contains(req.getMethod());
}
String clientId() {
if (req == null) {
return null;
}
return CalDavHeaders.getClientId(req);
}
String principalToken() {
if (req == null) {
return null;
}
return req.getHeader("X-BEDEWORK-PT");
}
String notePr() {
if (req == null) {
return null;
}
return req.getHeader("X-BEDEWORK-NOTEPR");
}
String note() {
if (req == null) {
return null;
}
return req.getHeader("X-BEDEWORK-NOTE");
}
String socketToken() {
if (req == null) {
return null;
}
return req.getHeader("X-BEDEWORK-SOCKETTKN");
}
String socketPr() {
if (req == null) {
return null;
}
return req.getHeader("X-BEDEWORK-SOCKETPR");
}
String bedeworkExtensions() {
if (req == null) {
return null;
}
return req.getHeader("X-BEDEWORK-EXTENSIONS");
}
}
@Override
public String init(final HttpServletRequest req,
final String account,
final boolean service,
final boolean calWs,
final boolean synchWs,
final boolean notifyWs,
final boolean socketWs,
final String opaqueData) {
try {
this.calWs = calWs;
this.synchWs = synchWs;
final HttpReqInfo reqi = new HttpReqInfo(req);
principalInfo = null; // In case we reinit
if (req != null) {
urlHandler = new UrlHandler(req, !calWs);
} else {
urlHandler = new UrlHandler("", null, true);
}
//final HttpSession session = req.getSession();
//final ServletContext sc = session.getServletContext();
//final String appName = sc.getInitParameter("bwappname");
//if ((appName == null) || (appName.length() == 0)) {
// throw new WebdavException("bwappname is not set in web.xml");
//}
// Notification service calling?
final String id;
final String notePr = reqi.notePr();
if (notifyWs) {
id = doNoteHeader(reqi.note(), notePr);
} else if (socketWs) {
final String tkn = reqi.socketToken();
if ((tkn == null) ||
!tkn.equals(getSystemProperties().getSocketToken())) {
throw new WebdavForbidden();
}
id = reqi.socketPr();
} else {
id = account;
}
doBedeworkExtensions(reqi.bedeworkExtensions());
/* Find the mbean and get the config */
// ObjectName mbeanName = new ObjectName(CalDAVConf.getServiceName(appName));
// Call to set up ThreadLocal variables
boolean publicAdmin = false;
boolean adminCreateEprops = false;
String calSuite = null;
if (!notifyWs && (opaqueData != null)) {
final String[] vals = opaqueData.split("\t");
for (final String val: vals) {
final int pos = val.indexOf("=");
if (pos < 0) {
continue;
}
final String rhs = val.substring(pos + 1);
if (val.startsWith("public-admin=")) {
publicAdmin = Boolean.parseBoolean(rhs);
continue;
}
if (val.startsWith("adminCreateEprops=")) {
adminCreateEprops = Boolean.parseBoolean(rhs);
continue;
}
if (val.startsWith("calsuite=")) {
calSuite = rhs;
}
}
}
getSvci(id,
reqi.runAs(),
service,
publicAdmin,
calSuite,
reqi.clientId(),
adminCreateEprops,
reqi.readOnly());
authProperties = svci.getAuthProperties();
sysProperties = configs.getSystemProperties();
svci.postNotification(new HttpEvent(SysCode.CALDAV_IN));
reqInTime = System.currentTimeMillis();
currentPrincipal = svci.getUsersHandler().getUser(id);
if (notifyWs && (notePr != null)) {
final String principalToken = reqi.principalToken();
if (principalToken == null) {
throw new WebdavUnauthorized();
}
final BwPreferences prefs = svci.getPrefsHandler().get();
if ((prefs == null) ||
(prefs.getNotificationToken() == null) ||
!principalToken.equals(prefs.getNotificationToken())) {
throw new WebdavUnauthorized();
}
}
return id;
} catch (final WebdavException we) {
throw we;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public boolean testMode() {
return sysProperties.getTestMode();
}
@Override
public boolean bedeworkExtensionsEnabled() {
return bedeworkExtensionsEnabled;
}
@Override
public AuthProperties getAuthProperties() {
return authProperties;
}
@Override
public SystemProperties getSystemProperties() {
if (sysProperties == null) {
sysProperties = configs.getSystemProperties();
}
return sysProperties;
}
@Override
public JDKIM getJDKIM() {
return getSvci().getJDKIM();
}
@Override
public boolean allowsSyncReport(final WdCollection> col) {
if (col == null) {
return false;
}
/* This - or any target if an alias - cannot be filtered at the moment. */
final BwCalendar bwcol = ((BwCalDAVCollection)col).getCol();
if (bwcol.getFilterExpr() != null) {
return false;
}
BwCalendar leaf = bwcol;
while (leaf.getInternalAlias()) {
leaf = resolveAlias(leaf, false);
if ((leaf == null) || // bad alias
(leaf.getFilterExpr() != null)) {
return false;
}
}
/* This must be a collection which is either a user home or below. */
final String[] els = bwcol.getPath().split("/");
// First element should be "" for the leading "/"
if ((els.length < 3) ||
(!"".equals(els[0])) ||
(els[1] == null) ||
(els[2] == null) ||
(els[2].isEmpty())) {
return false;
}
return els[1].equals(BasicSystemProperties.userCalendarRoot);
}
@Override
public String getDefaultContentType() {
if (calWs) {
return XcalTags.mimetype;
}
return "text/calendar";
}
@Override
public String getNotificationURL() {
final CalPrincipalInfo cpi =
getCalPrincipalInfo(currentPrincipal);
if (cpi == null) {
return null;
}
return cpi.notificationsPath;
}
@Override
public AccessPrincipal getPrincipal() {
return currentPrincipal;
}
@Override
public byte[] getPublicKey(final String domain,
final String service) {
try {
if (key == null) {
synchronized (keyPairLock) {
key = svci.getPublicKey(domain, service);
}
}
return key;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
private static class MyPropertyHandler extends PropertyHandler {
private final static HashMap propertyNames =
new HashMap<>();
@Override
public Map getPropertyNames() {
return propertyNames;
}
}
@Override
public PropertyHandler getPropertyHandler(final PropertyType ptype) {
return new MyPropertyHandler();
}
@Override
public UrlHandler getUrlHandler() {
return urlHandler;
}
@Override
public boolean isPrincipal(final String val) {
return getSvci().getDirectories().isPrincipal(val);
}
@Override
public AccessPrincipal getPrincipalForUser(final String account) {
try {
final Directories dir = getSvci().getDirectories();
return dir.getPrincipal(dir.makePrincipalUri(account, WhoDefs.whoTypeUser));
} catch (final BedeworkException be) {
if (be.getMessage().equals(CalFacadeErrorCode.principalNotFound)) {
throw new WebdavNotFound(account);
}
throw new WebdavException(be);
}
}
@Override
public AccessPrincipal getPrincipal(final String href) {
try {
return getSvci().getDirectories().getPrincipal(href);
} catch (final BedeworkException be) {
if (be.getMessage().equals(CalFacadeErrorCode.principalNotFound)) {
throw new WebdavNotFound(href);
}
throw new WebdavException(be);
}
}
@Override
public String makeHref(final String id, final int whoType) {
return getUrlHandler().prefix(
getSvci().getDirectories().makePrincipalUri(id,
whoType));
// return getUrlPrefix() + getSvci().getDirectories().makePrincipalUri(id, whoType);
}
@Override
public CollectiongetGroups(final String rootUrl,
final String principalUrl) {
try {
return getSvci().getDirectories().getGroups(rootUrl,
principalUrl);
} catch (final BedeworkException be) {
throw new WebdavException(be);
}
}
@Override
public AccessPrincipal caladdrToPrincipal(final String caladdr) {
// XXX This needs to work for groups.
return getSvci().getDirectories().caladdrToPrincipal(caladdr);
}
@Override
public String principalToCaladdr(final AccessPrincipal principal) {
try {
return getSvci().getDirectories().principalToCaladdr(principal);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
boolean updateQuota(final AccessPrincipal principal,
final long inc) {
try {
final BwPrincipal> p =
getSvci().getUsersHandler()
.getPrincipal(principal.getPrincipalRef());
if (p == null) {
return false; // No quota - fail
}
if (p.getKind() != WhoDefs.whoTypeUser) {
// XXX Cannot handle this yet
return false; // No quota - fail
}
final BwPreferences prefs = getPrefs();
final long used = prefs.getQuotaUsed() + inc;
prefs.setQuotaUsed(used);
getSvci().getUsersHandler().update(p);
return (inc < 0) || // Decreasing usage - let it pass
(used <= p.getQuota());
} catch (final BedeworkException be) {
throw new WebdavException(be);
}
}
@Override
public CalPrincipalInfo getCalPrincipalInfo(final AccessPrincipal principal) {
if (principal == null) {
return null;
}
final boolean thisPrincipal = principal.equals(getSvci().getPrincipal());
if (thisPrincipal && (principalInfo != null)) {
return principalInfo;
}
final BwPrincipal> p =
getSvci().getUsersHandler().getPrincipal(
principal.getPrincipalRef());
if (p == null) {
return null;
}
if (p.getKind() != WhoDefs.whoTypeUser) {
// XXX Cannot handle this yet
return null;
}
// SCHEDULE - just get home path and get default cal from user prefs.
final String userHomePath = Util.buildPath(true,
getSvci().getPrincipalInfo().getCalendarHomePath(p));
final String defaultCalendarPath =
Util.buildPath(true, userHomePath, "/",
BasicSystemProperties.userDefaultCalendar);
final String inboxPath =
Util.buildPath(true, userHomePath, "/",
BasicSystemProperties.userInbox);
final String outboxPath =
Util.buildPath(true, userHomePath, "/",
BasicSystemProperties.userOutbox);
final String notificationsPath =
Util.buildPath(true, userHomePath, "/",
BasicSystemProperties.defaultNotificationsName);
final CalPrincipalInfo pi =
new CalPrincipalInfo(p,
null,
null,
userHomePath,
defaultCalendarPath,
inboxPath,
outboxPath,
notificationsPath,
p.getQuota());
if (thisPrincipal) {
principalInfo = pi;
}
return pi;
}
private CalPrincipalInfo getCalPrincipalInfo(final BwPrincipalInfo pi) {
try {
// SCHEDULE - just get home path and get default cal from user prefs.
String userHomePath = Util.buildPath(false, "/",
BasicSystemProperties.userCalendarRoot);
if (pi.getPrincipalHref() == null) {
return new CalPrincipalInfo(null,
pi.getCard(),
pi.getCardStr(),
null, // userHomePath,
null, // defaultCalendarPath,
null, // inboxPath,
null, // outboxPath,
null, // notificationsPath,
0);
}
final BwPrincipal> p =
getSvci().getDirectories()
.getPrincipal(pi.getPrincipalHref());
if (pi.getPrincipalHref().startsWith(BwPrincipal.userPrincipalRoot)) {
userHomePath = Util.buildPath(true, userHomePath,
pi.getPrincipalHref().
substring(BwPrincipal.userPrincipalRoot.length()));
} else {
userHomePath = Util.buildPath(true, userHomePath,
pi.getPrincipalHref());
}
final String defaultCalendarPath =
Util.buildPath(true, userHomePath, "/",
BasicSystemProperties.userDefaultCalendar);
final String inboxPath =
Util.buildPath(true, userHomePath, "/",
BasicSystemProperties.userInbox);
final String outboxPath =
Util.buildPath(true, userHomePath, "/",
BasicSystemProperties.userOutbox);
final String notificationsPath =
Util.buildPath(true, userHomePath, "/",
BasicSystemProperties.defaultNotificationsName);
return new CalPrincipalInfo(p,
pi.getCard(),
pi.getCardStr(),
userHomePath,
defaultCalendarPath,
inboxPath,
outboxPath,
notificationsPath,
0);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public Collection getPrincipalCollectionSet(final String resourceUri) {
try {
final ArrayList al = new ArrayList<>();
al.add(BwPrincipal.principalRoot);
return al;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public Collection getPrincipals(
String resourceUri,
final PrincipalPropertySearch pps) {
List principals = null;
if (pps.applyToPrincipalCollectionSet) {
/* I believe it's valid (if unhelpful) to return nothing
*/
return new ArrayList<>();
}
if (!resourceUri.endsWith("/")) {
resourceUri += "/";
}
final String proot = BwPrincipal.principalRoot;
if (!resourceUri.equals(proot)) {
return new ArrayList<>();
}
/* If we don't support any of the properties in the searches we don't match.
*
* Currently we only support calendarUserAddressSet or calendarHomeSet.
*
* For calendarUserAddressSet the value to match must be a valid CUA
*
* For calendarHomeSet it must be a valid home uri
*/
final List props = new ArrayList<>();
String cutype = null;
for (final WebdavProperty prop: pps.props) {
if (debug()) {
debug("Try to match " + prop);
}
final String pval = prop.getPval();
if (CaldavTags.calendarUserAddressSet.equals(prop.getTag())) {
principals = and(principals,
getCalPrincipalInfo(caladdrToPrincipal(pval)));
} else if (CaldavTags.calendarHomeSet.equals(prop.getTag())) {
final String path = getUrlHandler().unprefix(pval);
final CalDAVCollection> col = getCollection(path);
if (col != null) {
principals = and(principals, getCalPrincipalInfo(col.getOwner()));
}
} else if (CaldavTags.calendarUserType.equals(prop.getTag())) {
cutype = pval;
} else if (WebdavTags.displayname.equals(prop.getTag())) {
// Store for directory search
props.add(prop);
}
}
if (!props.isEmpty()) {
// Directory search
if (principals == null) {
principals = new ArrayList<>();
}
final var pis =
getSvci().getDirectories().find(props,
pps.pr.props,
cutype);
if (pis != null) {
for (final BwPrincipalInfo pi: pis.principals()) {
principals.add(getCalPrincipalInfo(pi));
}
}
}
if (principals == null) {
return new ArrayList<>();
}
return principals;
}
private List and(final List pis,
final CalPrincipalInfo pi) {
if (pis == null) {
final List newPis = new ArrayList<>();
newPis.add(pi);
return newPis;
}
if (pis.isEmpty()) {
return pis;
}
for (final CalPrincipalInfo listPi: pis) {
if (pi.principal.equals(listPi.principal)) {
if (pis.size() == 1) {
return pis;
}
final List newPis = new ArrayList<>();
newPis.add(pi);
return newPis;
}
}
return new ArrayList<>();
}
@Override
public boolean validPrincipal(final String account) {
try {
return getSvci().getDirectories().validPrincipal(account);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
/* ====================================================================
* Notifications
* ==================================================================== */
public boolean subscribeNotification(final String principalHref,
final String action,
final List emails) {
final boolean add = "add".equals(action);
final boolean remove = "remove".equals(action);
if (!add && !remove) {
return false;
}
try {
if (remove) {
svci.getNotificationsHandler().unsubscribe(principalHref, emails);
return true;
}
if (Util.isEmpty(emails)) {
return false;
}
svci.getNotificationsHandler().subscribe(principalHref, emails);
return true;
} catch (final BedeworkException be) {
throw new WebdavException(be);
}
}
@Override
public boolean sendNotification(final String href,
final NotificationType val) {
final AccessPrincipal pr = caladdrToPrincipal(href);
if (pr == null) {
return false;
}
try {
return svci.getNotificationsHandler()
.send((BwPrincipal>)pr, val);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void removeNotification(final String href,
final NotificationType val) {
try {
svci.getNotificationsHandler().remove(href, val);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public List getNotifications() {
try {
return prefix(svci.getNotificationsHandler().getAll());
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public List getNotifications(final String href,
final QName type) {
try {
return prefix(svci.getNotificationsHandler().getMatching(href, type));
} catch (final WebdavException wde) {
throw wde;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
private List prefix(final List notes) {
for (final NotificationType n: notes) {
n.getNotification().prefixHrefs(getUrlHandler());
}
return notes;
}
@Override
public ShareResultType share(final CalDAVCollection> col,
final ShareType share) {
try {
return svci.getSharingHandler().share(unwrap(col), share);
} catch (final BedeworkForbidden bf) {
throw new WebdavForbidden(bf.getMessage());
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public String sharingReply(final CalDAVCollection> col,
final InviteReplyType reply) {
try {
final SharingReplyResult rr = svci.getSharingHandler()
.reply(unwrap(col), reply);
if ((rr == null) || !rr.getOk()) {
return null;
}
return getUrlHandler().prefix(rr.getSharedAs().href());
} catch (final BedeworkForbidden bf) {
throw new WebdavForbidden(bf.getMessage());
} catch (final BedeworkException e) {
throw new WebdavException(e);
}
}
@Override
public InviteType getInviteStatus(final CalDAVCollection> col) {
if (col == null) {
return null;
}
try {
final InviteType inv = svci.getSharingHandler()
.getInviteStatus(unwrap(col));
if (inv == null) {
return null;
}
final UrlHandler uh = getUrlHandler();
for (final UserType u: inv.getUsers()) {
u.setHref(uh.prefix(u.getHref()));
}
return inv;
} catch (final BedeworkForbidden bf) {
throw new WebdavForbidden(bf.getMessage());
} catch (final BedeworkException t) {
throw new WebdavException(t);
}
}
/* ====================================================================
* Scheduling
* ==================================================================== */
@Override
public Collection getFreebusySet() {
try {
final Collection cals = svci.getScheduler()
.getFreebusySet();
final Collection hrefs = new ArrayList<>();
if (cals == null) {
return hrefs;
}
for (final BwCalendar cal: cals) {
hrefs.add(getUrlHandler().prefix(cal.getPath()));
//hrefs.add(getUrlPrefix() + cal.getPath());
}
return hrefs;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public Collection schedule(final CalDAVEvent> ev) {
final BwEvent event = getEvent(ev);
event.setOwnerHref(currentPrincipal.getPrincipalRef());
final var sched = getSvci().getScheduler();
final var evinfo = getEvinfo(ev);
if (Icalendar.itipReplyMethodType(event.getScheduleMethod())) {
return checkStatus(sched.scheduleResponse(evinfo, null));
}
return checkStatus(sched.schedule(evinfo,
null, null,
true, null)); // iSchedule
}
/* ==============================================================
* Events
* ============================================================== */
@Override
public Collection> addEvent(final CalDAVEvent> ev,
final boolean noInvites,
final boolean rollbackOnError) {
try {
/* Is the event a scheduling object? */
final EventInfo ei = getEvinfo(ev);
mapCategories(ei);
final EventInfo.UpdateResult resp =
getSvci().getEventsHandler().add(ei, noInvites,
false, // scheduling - inbox
false, // autocreate
rollbackOnError);
if (resp.isOk()) {
final Collection bwevs = resp.failedOverrides;
if (bwevs == null) {
return null;
}
final Collection> evs = new ArrayList<>();
for (final BwEvent bwev: bwevs) {
evs.add(new BwCalDAVEvent(this, new EventInfo(bwev)));
}
return evs;
}
if (resp.getStatus() == noAccess) {
throw new WebdavForbidden();
}
if (resp.getStatus() == limitExceeded) {
if (CalFacadeErrorCode.schedulingTooManyAttendees
.equals(resp.getMessage())) {
throw new WebdavForbidden(
CaldavTags.maxAttendeesPerInstance,
ev.getParentPath());
}
if (CalFacadeErrorCode.invalidOverride
.equals(resp.getMessage())) {
throw new WebdavForbidden(CaldavTags.validCalendarData,
ev.getParentPath());
}
throw new WebdavForbidden(resp.getMessage());
}
if (CalFacadeErrorCode.duplicateGuid.equals(resp.getMessage())) {
throw new WebdavForbidden(CaldavTags.noUidConflict,
ev.getParentPath());
}
if (CalFacadeErrorCode.duplicateName.equals(resp.getMessage())) {
throw new WebdavForbidden(CaldavTags.noUidConflict,
ev.getParentPath());
}
if (resp.getException() != null) {
throw new WebdavException(resp.getException());
}
throw new WebdavForbidden(resp.toString());
} catch (final WebdavException we) {
throw we;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void reindexEvent(final CalDAVEvent> event) {
try {
final EventInfo ei = getEvinfo(event);
getSvci().getEventsHandler().reindex(ei);
} catch (final Throwable t) {
error(t);
}
}
@Override
public void updateEvent(final CalDAVEvent> event) {
try {
final EventInfo ei = getEvinfo(event);
handleUpdateResult(getSvci().getEventsHandler().update(ei, false));
} catch (final WebdavException we) {
throw we;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public UpdateResult updateEvent(final CalDAVEvent> event,
final List updates) {
try {
final EventInfo ei = getEvinfo(event);
if (updates == null) {
return new UpdateResult("No updates");
}
final CategoryMapInfo cm = getCatMapping();
final UpdateResult ur =
new BwUpdates(getPrincipal().getPrincipalRef(),
cm).
updateEvent(ei, updates,
getSvci().getIcalCallback());
if (!ur.getOk()) {
return ur;
}
handleUpdateResult(getSvci().getEventsHandler().update(ei, false));
return ur;
} catch (final WebdavException we) {
throw we;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
private void handleUpdateResult(final EventInfo.UpdateResult ur) {
if (ur.getStatus() == ok) {
return;
}
if (ur.getStatus() == noAccess) {
throw new WebdavForbidden();
}
if (ur.getException() != null) {
if (ur.getException() instanceof final BedeworkException be) {
if (CalFacadeErrorCode.duplicateGuid
.equals(be.getMessage())) {
throw new WebdavBadRequest("Duplicate-guid");
}
if (be instanceof final BedeworkForbidden bf) {
throw new WebdavForbidden(bf.getQname(), bf.getMessage());
}
}
throw new WebdavException(ur.getException());
}
throw new WebdavException(ur.getMessage());
}
@Override
public Collection> getEvents(final CalDAVCollection> col,
final FilterBase filter,
final List retrieveList,
final RetrievalMode recurRetrieval) {
try {
/* Limit the results to just this collection by adding an ANDed filter */
final SimpleFilterParser sfp = getSvci().getFilterParser();
final String expr = "(colPath='" + SfpTokenizer.escapeQuotes(col.getPath()) + "')";
final ParseResult pr = sfp.parse(expr, true, null);
if (!pr.ok) {
throw new WebdavBadRequest("Failed to reference collection " +
col.getPath() +
": message was " + pr.message);
}
final FilterBase f = FilterBase.addAndChild(filter,
pr.filter);
final Collection bwevs =
getSvci().getEventsHandler().getEvents(null,// Collection
f,
null, // start
null, // end
RetrieveList.getRetrieveList(retrieveList),
DeletedState.noDeleted,
getRrm(recurRetrieval));
if (bwevs == null) {
return null;
}
final Collection> evs = new ArrayList<>();
for (final EventInfo ei: bwevs) {
if (recurRetrieval != null) {
ei.getEvent().setForceUTC(recurRetrieval.getExpand() != null);
}
evs.add(new BwCalDAVEvent(this, ei));
}
return evs;
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final BedeworkException be) {
if (CalFacadeErrorCode.unknownProperty.equals(be.getMessage())) {
throw new WebdavBadRequest("Unknown property " + be.getExtra());
}
throw new WebdavException(be);
} catch (final WebdavException wde) {
throw wde;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public CalDAVEvent> getEvent(final CalDAVCollection> col,
final String val) {
try {
if ((col == null) || (val == null)) {
throw new WebdavBadRequest();
}
final EventInfo ei =
getSvci().getEventsHandler().get(unwrap(col),
val, null, null);
if (ei == null) {
return null;
}
return new BwCalDAVEvent(this, ei);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void deleteEvent(final CalDAVEvent> ev,
final boolean scheduleReply) {
if (ev == null) {
return;
}
final Response resp = getSvci().getEventsHandler()
.delete(getEvinfo(ev), scheduleReply);
if (!resp.isOk()) {
throw new WebdavException("Failed delete:" + resp);
}
}
@Override
public Collection requestFreeBusy(
final CalDAVEvent> val,
final boolean iSchedule) {
final BwEvent ev = getEvent(val);
if (currentPrincipal != null) {
ev.setOwnerHref(currentPrincipal.getPrincipalRef());
}
final var sched = getSvci().getScheduler();
final var evinfo = getEvinfo(val);
if (Icalendar.itipReplyMethodType(ev.getScheduleMethod())) {
return checkStatus(sched.scheduleResponse(evinfo, null));
}
return checkStatus(sched.schedule(evinfo,
null, null, iSchedule, null));
}
@Override
public void getSpecialFreeBusy(final String cua,
final Set recipients,
final String originator,
final TimeRange tr,
final Writer wtr) {
final BwOrganizer org = new BwOrganizer();
org.setOrganizerUri(cua);
final BwEvent ev = new BwEventObj();
ev.setDtstart(getBwDt(tr.getStart()));
ev.setDtend(getBwDt(tr.getEnd()));
ev.setEntityType(IcalDefs.entityTypeFreeAndBusy);
ev.setScheduleMethod(ScheduleMethods.methodTypeRequest);
ev.setRecipients(recipients);
ev.setOriginator(originator);
ev.setOrganizer(org);
final Collection srrs = requestFreeBusy(
new BwCalDAVEvent(this, new EventInfo(ev)), false);
for (final SchedRecipientResult srr: srrs) {
// We expect one only
final BwCalDAVEvent rfb = (BwCalDAVEvent)srr.freeBusy;
if (rfb != null) {
rfb.getEv().setOrganizer(org);
try {
final VFreeBusy vfreeBusy = VFreeUtil.toVFreeBusy(rfb.getEv());
final net.fortuna.ical4j.model.Calendar ical =
IcalendarUtil.newIcal(
ScheduleMethods.methodTypeReply,
BwVersion.prodId);
ical.getComponents().add(vfreeBusy);
IcalendarUtil.writeCalendar(ical, wtr);
} catch (final Throwable t) {
if (debug()) {
error(t);
}
throw new WebdavException(t);
}
}
}
}
@Override
public CalDAVEvent> getFreeBusy(final CalDAVCollection> col,
final int depth,
final TimeRange timeRange) {
try {
final BwCalendar bwCol = unwrap(col);
final int calType = bwCol.getCalType();
if (!bwCol.getCollectionInfo().allowFreeBusy) {
throw new WebdavForbidden(WebdavTags.supportedReport);
}
final Collection cals = new ArrayList<>();
if (calType == BwCalendar.calTypeCalendarCollection) {
cals.add(bwCol);
} else if (depth != 0) { /* Cannot return anything for 0 */
/* Make new cal object with just calendar collections as children */
for (final BwCalendar ch: getSvci().getCalendarsHandler()
.getChildren(bwCol)) {
// For depth 1 we only add calendar collections
if ((depth > 1) ||
(ch.getCalType() == BwCalendar.calTypeCalendarCollection)) {
cals.add(ch);
}
}
}
final AccessPrincipal owner = col.getOwner();
final String orgUri;
if (owner instanceof final BwPrincipal> powner) {
orgUri = getSvci().getDirectories()
.principalToCaladdr(powner);
} else {
final BwPrincipal> p = BwPrincipal.makeUserPrincipal();
p.setAccount(owner.getAccount());
orgUri = getSvci().getDirectories().principalToCaladdr(p);
}
final BwOrganizer org = new BwOrganizer();
org.setOrganizerUri(orgUri);
final BwEvent fb;
if (cals.isEmpty()) {
// Return an empty object
fb = new BwEventObj();
fb.setEntityType(IcalDefs.entityTypeFreeAndBusy);
fb.setDtstart(getBwDt(timeRange.getStart()));
fb.setDtend(getBwDt(timeRange.getEnd()));
} else {
fb = getSvci().getScheduler().getFreeBusy(cals,
currentPrincipal,
getBwDt(timeRange.getStart()),
getBwDt(timeRange.getEnd()),
org,
null, // uid
null);
}
final EventInfo ei = new EventInfo(fb);
return new BwCalDAVEvent(this, ei);
} catch (final WebdavException wde) {
throw wde;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public CurrentAccess checkAccess(final WdEntity> ent,
final int desiredAccess,
final boolean returnResult) {
try {
if (ent instanceof CalDAVCollection) {
return getSvci().checkAccess(unwrap((CalDAVCollection>)ent),
desiredAccess, returnResult);
}
if (ent instanceof CalDAVEvent) {
return getSvci().checkAccess(getEvent((CalDAVEvent>)ent),
desiredAccess, returnResult);
}
if (ent instanceof CalDAVResource) {
return getSvci().checkAccess(getRsrc((CalDAVResource>)ent),
desiredAccess, returnResult);
}
throw new WebdavBadRequest();
} catch (final BedeworkException be) {
throw new WebdavException(be);
}
}
@Override
public void updateAccess(final CalDAVEvent> ev,
final Acl acl) throws WebdavException{
try {
getSvci().changeAccess(getEvent(ev), acl.getAces(), true);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
/* ====================================================================
* Collections
* ==================================================================== */
@Override
public CalDAVCollection> newCollectionObject(final boolean isCalendarCollection,
final String parentPath) {
final BwCalendar col = new BwCalendar();
if (isCalendarCollection) {
col.setCalType(BwCalendar.calTypeCalendarCollection);
} else {
col.setCalType(BwCalendar.calTypeFolder);
}
col.setColPath(parentPath);
col.setOwnerHref(currentPrincipal.getPrincipalRef());
return new BwCalDAVCollection(this, col);
}
@Override
public void updateAccess(final CalDAVCollection> col,
final Acl acl) {
try {
getSvci().changeAccess(unwrap(col), acl.getAces(), true);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public int makeCollection(final CalDAVCollection> col) {
final BwCalendar bwCol = unwrap(col);
try {
getSvci().getCalendarsHandler().add(bwCol, bwCol.getColPath());
return HttpServletResponse.SC_CREATED;
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final BedeworkException be) {
final String msg = be.getMessage();
if (CalFacadeErrorCode.duplicateCalendar.equals(msg)) {
throw new WebdavForbidden(WebdavTags.resourceMustBeNull);
}
if (CalFacadeErrorCode.illegalCalendarCreation.equals(msg)) {
throw new WebdavForbidden();
}
throw new WebdavException(be);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void copyMove(final CalDAVCollection> from,
final CalDAVCollection> to,
final boolean copy,
final boolean overwrite) {
try {
final BwCalendar bwFrom = unwrap(from);
final BwCalendar bwTo = unwrap(to);
if (!copy) {
/* Move the from collection to the new location "to".
* If the parent calendar is the same in both cases, this is just a rename.
*/
if ((bwFrom.getColPath() == null) || (bwTo.getColPath() == null)) {
throw new WebdavForbidden("Cannot move root");
}
if (bwFrom.getColPath().equals(bwTo.getColPath())) {
// Rename
getSvci().getCalendarsHandler().rename(bwFrom, to.getName());
return;
}
}
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
throw new WebdavException("unimplemented");
}
@Override
public boolean copyMove(final CalDAVEvent> from,
final CalDAVCollection> to,
final String name,
final boolean copy,
final boolean overwrite) {
final Response resp = getSvci().getEventsHandler()
.copyMoveNamed(getEvinfo(from),
unwrap(to), name,
copy, overwrite, false);
if (resp.getException() != null) {
throw new WebdavException(resp.getException());
}
if (resp.isOk()) {
if ("created".equals(resp.getMessage())) {
return true;
}
return false;
}
if (resp.getStatus() == Response.Status.forbidden) {
throw new WebdavForbidden(resp.getMessage());
}
throw new WebdavException("Unexpected response from copymove: " +
resp);
}
@Override
public CalDAVCollection> getCollection(final String path) {
try {
final BwCalendar col = getSvci().getCalendarsHandler().get(path);
if (col == null) {
return null;
}
getSvci().getCalendarsHandler().resolveAlias(col, true, false);
return new BwCalDAVCollection(this, col);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void updateCollection(final CalDAVCollection> col) {
try {
getSvci().getCalendarsHandler().update(unwrap(col));
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final BedeworkException be) {
if (CalFacadeErrorCode.duplicateGuid.equals(be.getMessage())) {
throw new WebdavBadRequest("Duplicate-guid");
}
throw new WebdavException(be);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void deleteCollection(final CalDAVCollection> col,
final boolean sendSchedulingMessage) {
try {
getSvci().getCalendarsHandler().delete(unwrap(col), true,
sendSchedulingMessage);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final BedeworkException be) {
final String msg = be.getMessage();
if (CalFacadeErrorCode.cannotDeleteDefaultCalendar.equals(msg) ||
CalFacadeErrorCode.cannotDeleteCalendarRoot.equals(msg)) {
throw new WebdavForbidden();
} else {
throw new WebdavException(be);
}
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public Collection> getCollections(final CalDAVCollection> col) {
try {
final BwCalendar bwCol = unwrap(col);
boolean isUserHome = false;
List provisionedTypes = null;
/* Is this the calendar home? If so we have to ensure all
provisioned collections exist */
if (getPrincipal() != null) {
final String userHomePath =
Util.buildPath(true,
getSvci().getPrincipalInfo()
.getCalendarHomePath(
getPrincipal()));
if (Util.buildPath(true, bwCol.getPath())
.equals(userHomePath)) {
isUserHome = true;
provisionedTypes = new ArrayList<>();
for (final CollectionInfo ci:
BwCalendar.getAllCollectionInfo()) {
if (ci.provision) {
provisionedTypes.add(ci.collectionType);
}
}
}
}
final CalendarsI ci = getSvci().getCalendarsHandler();
final Collection bwch = ci.getChildren(bwCol);
final Collection> ch = new ArrayList<>();
if (bwch == null) {
return ch;
}
for (final BwCalendar c: bwch) {
if (bedeworkExtensionsEnabled() || !c.getName().startsWith(".")) {
ci.resolveAlias(c, true, false);
ch.add(new BwCalDAVCollection(this, c));
}
if (isUserHome && !c.getAlias()) {
provisionedTypes.remove(Integer.valueOf(c.getCalType()));
}
}
if (isUserHome && !provisionedTypes.isEmpty()) {
// Need to add some
for (final int colType: provisionedTypes) {
final BwCalendar pcol =
ci.getSpecial(currentPrincipal, colType,
true, PrivilegeDefs.privAny);
ch.add(new BwCalDAVCollection(this, pcol));
}
}
return ch;
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
/** Attempt to get calendar referenced by the alias. For an internal alias
* the result will also be set in the aliasTarget property of the parameter.
*
* @param col to resolve
* @param resolveSubAlias - if true and the alias points to an alias, resolve
* down to a non-alias.
* @return BwCalendar
*/
BwCalendar resolveAlias(final BwCalendar col,
final boolean resolveSubAlias) {
try {
return getSvci().getCalendarsHandler().resolveAlias(col, resolveSubAlias, false);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
/* ====================================================================
* Files
* ==================================================================== */
@Override
public CalDAVResource> newResourceObject(final String parentPath) {
final CalDAVResource> r = new BwCalDAVResource(this,
null);
r.setParentPath(parentPath);
r.setOwner(currentPrincipal);
return r;
}
@Override
public void putFile(final CalDAVCollection> coll,
final CalDAVResource> val) {
try {
final BwResource rsrc = getRsrc(val);
rsrc.setColPath(coll.getPath());
getSvci().getResourcesHandler().save(rsrc,
false);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public CalDAVResource> getFile(final CalDAVCollection> coll,
final String name) {
try {
final BwResource rsrc = getSvci().getResourcesHandler().get(
Util.buildPath(false, coll.getPath(), "/", name));
if (rsrc == null) {
return null;
}
return new BwCalDAVResource(this,
rsrc);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void getFileContent(final CalDAVResource> val) {
try {
getSvci().getResourcesHandler().getContent(getRsrc(val));
} catch (final Throwable t) {
throw new RuntimeException(t);
}
}
@Override
public Collection> getFiles(final CalDAVCollection> coll) {
try {
final Collection bwrs =
getSvci().getResourcesHandler().getAll(coll.getPath());
if (bwrs == null) {
return null;
}
final Collection> rs =
new ArrayList<>();
for (final BwResource r: bwrs) {
rs.add(new BwCalDAVResource(this, r));
}
return rs;
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void updateFile(final CalDAVResource> val,
final boolean updateContent) {
try {
getSvci().getResourcesHandler().update(getRsrc(val), updateContent);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public void deleteFile(final CalDAVResource> val) {
try {
updateQuota(val.getOwner(), -val.getQuotaSize());
getSvci().getResourcesHandler().delete(Util.buildPath(false, val.getParentPath(),
"/",
val.getName()));
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public boolean copyMoveFile(final CalDAVResource> from,
final String toPath,
final String name,
final boolean copy,
final boolean overwrite) {
try {
return getSvci().getResourcesHandler().copyMove(getRsrc(from),
toPath, name,
copy,
overwrite);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public String getSyncToken(final CalDAVCollection> col) {
try {
final BwCalendar bwcol = ((BwCalDAVCollection)col).getCol();
String path = col.getPath();
if (bwcol.getInternalAlias()) {
path = bwcol.getAliasTarget().getPath();
}
return "data:," + getSvci().getCalendarsHandler().getSyncToken(path);
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public SynchReportData getSyncReport(final String path,
final String token,
final int limit,
final boolean recurse) {
try {
String syncToken = null;
if (token != null) {
if (!token.startsWith("data:,")) {
throw new WebdavForbidden(WebdavTags.validSyncToken, token);
}
syncToken = token.substring(6);
}
/* We managed to embed a bad sync token in the system - if it's length 16
* treat it as null
*/
if ((syncToken != null) && (syncToken.length() == 16)) {
syncToken = null; // Force a full reload
}
final SynchReport sr =
getSvci().getSynchReport(path, syncToken, limit, recurse);
if (sr == null) {
return null;
}
final SynchReportData srd = new SynchReportData();
srd.tokenValid = sr.getTokenValid();
if (!sr.getTokenValid()) {
return srd;
}
srd.items = new ArrayList<>();
srd.token = "data:," + sr.getToken();
srd.truncated = sr.getTruncated();
for (final SynchReportItem sri: sr.getItems()) {
final SynchReportDataItem srdi;
if (sri.getEvent() != null) {
srdi = new SynchReportDataItem(sri.getVpath(),
new BwCalDAVEvent(this,
sri.getEvent()),
sri.getToken());
} else if (sri.getResource() != null) {
srdi = new SynchReportDataItem(sri.getVpath(),
new BwCalDAVResource(this,
sri.getResource()),
sri.getToken());
} else if (sri.getCol() != null) {
srdi = new SynchReportDataItem(sri.getVpath(),
new BwCalDAVCollection(this,
sri.getCol()),
sri.getToken(),
sri.getCanSync());
} else {
throw new RuntimeException("Unhandled sync report item type");
}
srd.items.add(srdi);
}
return srd;
} catch (final BedeworkAccessException ignored) {
throw new WebdavForbidden();
} catch (final CalFacadeInvalidSynctoken cist) {
throw new WebdavBadRequest(WebdavTags.validSyncToken);
} catch (final WebdavException we) {
throw we;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public Calendar toCalendar(final CalDAVEvent> ev,
final boolean incSchedMethod) {
try {
int meth = ScheduleMethods.methodTypeNone;
if (incSchedMethod) {
meth = getEvent(ev).getScheduleMethod();
}
return trans.toIcal(getEvinfo(ev), meth);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public IcalendarType toIcalendar(final CalDAVEvent> ev,
final boolean incSchedMethod,
final IcalendarType pattern) {
try {
int meth = ScheduleMethods.methodTypeNone;
if (incSchedMethod) {
meth = getEvent(ev).getScheduleMethod();
}
return getXmlTrans().toXMLIcalendar(getEvinfo(ev), meth, pattern,
synchWs);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public String toJcal(final CalDAVEvent> ev,
final boolean incSchedMethod) {
try {
int meth = ScheduleMethods.methodTypeNone;
if (incSchedMethod) {
meth = getEvent(ev).getScheduleMethod();
}
return getJcalTrans().toJcal(getEvinfo(ev), meth);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public String toIcalString(final Calendar cal,
final String contentType) {
try {
String ctype = null;
if (contentType != null) {
final String[] contentTypePars = contentType.split(";");
ctype = contentTypePars[0];
}
if (ctype == null) {
throw new WebdavException("Null content type");
}
if (ctype.equals("text/calendar")) {
return IcalendarUtil.toIcalString(cal);
}
if (ctype.equals("application/calendar+json")) {
return JcalTranslator.toJcal(cal);
}
throw new WebdavException("Unhandled content type" + contentType);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public String writeCalendar(final Collection> evs,
final MethodEmitted method,
final XmlEmit xml,
final Writer wtr,
final String contentType) {
try {
final Collection bwevs = new ArrayList<>();
int meth = ScheduleMethods.methodTypeNone;
if (method == MethodEmitted.publish) {
meth = ScheduleMethods.methodTypePublish;
}
for (final CalDAVEvent> cde: evs) {
final BwCalDAVEvent bcde = (BwCalDAVEvent)cde;
if (method == MethodEmitted.eventMethod) {
meth = getEvent(bcde).getScheduleMethod();
}
bwevs.add(bcde.getEvinfo());
}
String ctype = contentType;
if ((ctype == null) ||
(!ctype.equals("text/calendar") &&
!ctype.equals("application/calendar+json") &&
!ctype.equals("application/jscalendar+json") &&
!ctype.equals(XcalTags.mimetype))) {
ctype = getDefaultContentType();
}
switch (ctype) {
case "text/calendar":
final Calendar ical = trans.toIcal(bwevs, meth);
if (xml == null) {
IcalendarUtil.writeCalendar(ical, wtr);
} else {
xml.cdataValue(toIcalString(ical, "text/calendar"));
}
break;
case "application/calendar+json":
if (xml == null) {
getJcalTrans().writeJcal(bwevs, meth, wtr);
} else {
final StringWriter sw = new StringWriter();
getJcalTrans().writeJcal(bwevs, meth, sw);
xml.cdataValue(sw.toString());
}
break;
case "application/jscalendar+json":
final JSGroup grp = getJScalTrans().toJScal(bwevs, meth);
if (xml == null) {
JSCalTranslator.writeJSCalendar(grp, wtr);
} else {
final StringWriter sw = new StringWriter();
JSCalTranslator.writeJSCalendar(grp, sw);
xml.cdataValue(sw.toString());
}
break;
case XcalTags.mimetype:
final XmlEmit x;
if (xml == null) {
x = new XmlEmit();
x.startEmit(wtr);
} else {
x = xml;
}
getXmlTrans().writeXmlCalendar(bwevs, meth, x);
break;
}
return ctype;
} catch (final WebdavException we) {
throw we;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public SysiIcalendar fromIcal(final CalDAVCollection> col,
final Reader rdr,
final String contentType,
final IcalResultType rtype,
final boolean mergeAttendees) {
getSvci(); // Ensure open
boolean rollback = true;
final IcalTranslator trans = getTrans(contentType);
/* (CALDAV:supported-calendar-data) */
if (trans == null) {
if (debug()) {
debug("Bad content type: " + contentType);
}
throw new WebdavForbidden(CaldavTags.supportedCalendarData,
"Bad content type: " + contentType);
}
try {
BwCalendar bwcol = null;
if (col != null) {
bwcol = unwrap(col);
}
final Icalendar ic =
trans.fromIcal(bwcol, new SysIntfReader(rdr),
contentType,
mergeAttendees);
if (rtype == IcalResultType.OneComponent) {
if (ic.getComponents().size() != 1) {
throw new WebdavForbidden(CaldavTags.validCalendarObjectResource);
}
} else if (rtype == IcalResultType.TimeZone) {
if (ic.getTimeZones().size() != 1) {
throw new WebdavForbidden("Expected one timezone");
}
}
final SysiIcalendar sic = new BwSysiIcalendar(this,
col,
ic);
rollback = false;
return sic;
} catch (final IcalMalformedException ime) {
throw new WebdavForbidden(ime, CaldavTags.validCalendarData);
} catch (final BedeworkException be) {
if (CalFacadeErrorCode.unknownTimezone.equals(
be.getDetailMessage())) {
throw new WebdavForbidden(CaldavTags.validTimezone,
be.getMessage());
}
if (debug()) {
error(be);
}
throw new WebdavForbidden(CaldavTags.validCalendarObjectResource,
be.getMessage());
} catch (final WebdavException wde) {
if (debug()) {
error(wde);
}
throw wde;
} catch (final Throwable t) {
if (debug()) {
error(t);
}
// Assume bad data in some way
throw new WebdavForbidden(CaldavTags.validCalendarObjectResource,
t.getMessage());
} finally {
if (rollback) {
getSvci().rollbackTransaction();
}
}
}
@Override
public SysiIcalendar fromIcal(final CalDAVCollection> col,
final IcalendarType ical,
final IcalResultType rtype) {
getSvci(); // Ensure open
boolean rollback = true;
try {
BwCalendar bwcol = null;
if (col != null) {
bwcol = unwrap(col.resolveAlias(true));
}
final Icalendar ic = trans.fromIcal(bwcol,
ical);
if (rtype == IcalResultType.OneComponent) {
if (ic.getComponents().size() != 1) {
throw new WebdavBadRequest(CaldavTags.validCalendarObjectResource);
}
} else if (rtype == IcalResultType.TimeZone) {
if (ic.getTimeZones().size() != 1) {
throw new WebdavBadRequest("Expected one timezone");
}
}
final SysiIcalendar sic = new BwSysiIcalendar(this, col,
ic);
rollback = false;
return sic;
} catch (final WebdavException wde) {
throw wde;
} catch (final IcalMalformedException ime) {
throw new WebdavForbidden(CaldavTags.validCalendarData,
ime.getMessage());
} catch (final Throwable t) {
if (debug()) {
error(t);
}
// Assume bad data in some way
throw new WebdavForbidden(CaldavTags.validCalendarObjectResource,
t.getMessage());
} finally {
if (rollback) {
getSvci().rollbackTransaction();
}
}
}
@Override
public String toStringTzCalendar(final String tzid) {
try {
return IcalendarUtil.toStringTzCalendar(
tzid,
BwVersion.prodId);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
@Override
public String tzidFromTzdef(final String val) {
try {
getSvci(); // Ensure open
final StringReader sr = new StringReader(val);
final Icalendar ic = trans.fromIcal(null, sr);
if ((ic == null) ||
(ic.size() != 0) || // No components other than timezones
(ic.getTimeZones().size() != 1)) {
if (debug()) {
debug("Not single timezone");
}
throw new WebdavForbidden(CaldavTags.calendarTimezone, "Not single timezone");
}
/* This should be the only timezone ion the Calendar object
*/
final TimeZone tz = ic.getTimeZones().iterator().next().tz;
return tz.getID();
} catch (final BedeworkException be) {
throw new WebdavException(be);
}
}
private static final String ValidateAlarmPrefix =
"""
BEGIN:VCALENDAR
VERSION:2.0
PRODID:bedework-validate
BEGIN:VEVENT
DTSTART:20101231T230000
DTEND:20110101T010000
SUMMARY:Just checking
UID:1234
DTSTAMP:20101125T112600
""";
private static final String ValidateAlarmSuffix =
"""
END:VEVENT
END:VCALENDAR
""";
/** Validate an alarm component
*
* @param val alarm component as string
* @return boolean false for failure
*/
@Override
public boolean validateAlarm(final String val) {
try {
getSvci(); // Ensure open
final StringReader sr = new StringReader(ValidateAlarmPrefix +
val +
ValidateAlarmSuffix);
final Icalendar ic = trans.fromIcal(null, sr);
if ((ic == null) ||
(ic.getEventInfo() == null)) {
if (debug()) {
debug("Not single event");
}
return false;
}
/* There should be alarms in the Calendar object
*/
final EventInfo ei = ic.getEventInfo();
final BwEvent ev = ei.getEvent();
return ((ev.getAlarms() != null) &&
!ev.getAlarms().isEmpty());
} catch (final BedeworkException be) {
if (debug()) {
error(be);
}
return false;
}
}
@Override
public void rollback() {
svci.rollbackTransaction();
}
@Override
public void close() {
close(svci);
}
/* ====================================================================
* Private methods
* ==================================================================== */
private CategoryMapInfo getCatMapping() {
final BwPreferences prefs = getPrefs();
final CategoryMappings catMaps;
if (prefs == null) {
return new CategoryMapInfo(null, null);
}
final GetEntityResponse ger =
prefs.readCategoryMappings();
if (!ger.isOk()) {
// Should emit warning
return new CategoryMapInfo(null, null);
}
catMaps = ger.getEntity();
final Set topicalAreas;
if (catMaps == null) {
return new CategoryMapInfo(null, null);
}
topicalAreas = new TreeSet<>();
// We'll need all the topical areas for the calsuite
final BwCalendar home =
getSvci().getCalendarsHandler().get(getSvci().getPrincipalInfo().getCalendarHomePath());
if (home == null) {
throw new RuntimeException("No home directory");
}
final Collection children = getSvci().
getCalendarsHandler().getChildren(home);
for (final BwCalendar child: children) {
if (!child.getIsTopicalArea()) {
continue;
}
final GetEntityResponse geca =
getSvci().getCalendarsHandler().getAliasInfo(child);
if (!geca.isOk()) {
throw new RuntimeException("Failed to get alias info: " +
geca.getMessage());
}
final CollectionAliases ca = geca.getEntity();
if (ca.getInvalidAlias() != null) {
continue;
}
topicalAreas.add(child);
}
return new CategoryMapInfo(catMaps, topicalAreas);
}
private void mapCategories(final EventInfo ei) {
final CategoryMapInfo cm = getCatMapping();
if (cm.getNoMapping()) {
return;
}
final BwEvent ev = ei.getEvent();
final List toRemove = new ArrayList<>();
for (final BwCategory cat: ev.getCategories()) {
final CategoryMapping catMap =
cm.findMapping(cat.getWordVal());
if (catMap == null) {
// Not a candidate
continue;
}
toRemove.add(cat);
if (catMap.isTopicalArea()) {
final BwCalendar mapTo = cm.getTopicalArea(catMap);
if (mapTo == null) {
// Should warn
continue;
}
// Add an x-prop to define the alias. Categories will be added by realias.
final BwCalendar aliasTarget = mapTo.getAliasTarget();
final BwXproperty xp = BwXproperty.makeBwAlias(
mapTo.getName(),
mapTo.getAliasUri().substring(
BwCalendar.internalAliasUriPrefix.length()),
aliasTarget.getPath(),
mapTo.getPath());
ev.addXproperty(xp);
} else {
// Add a category
final BwCategory newCat = BwCategory.makeCategory();
newCat.setWord(new BwString(null, catMap.getTo()));
getSvci().getCategoriesHandler()
.ensureExists(newCat, ev.getOwnerHref());
}
}
for (final BwCategory cat: toRemove) {
ev.getCategories().remove(cat);
}
if (!Util.isEmpty(ei.getOverrides())) {
for (final EventInfo oei: ei.getOverrides()) {
mapCategories(oei);
}
}
}
private String doNoteHeader(final String hdr,
final String account) {
if (hdr == null) {
return account;
}
try {
final String[] hparts = hdr.split(":");
if (hparts.length != 2) {
throw new WebdavBadRequest();
}
final String id = hparts[0];
final NotificationProperties nprops = configs.getNotificationProps();
final String token = hparts[1];
if (id == null) {
throw new WebdavBadRequest();
}
if (!id.equals(nprops.getNotifierId()) ||
(token == null) ||
!token.equals(nprops.getNotifierToken())) {
throw new WebdavBadRequest();
}
if (account != null) {
return account;
}
return id;
} catch (final WebdavException wde) {
throw wde;
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
private void doBedeworkExtensions(final String hdr) {
if (hdr == null) {
return;
}
bedeworkExtensionsEnabled = Boolean.parseBoolean(hdr);
}
/**
* @param sr schedule result
* @return recipient results
*/
private Collection checkStatus(final ScheduleResult sr) {
final String errorCode;
final var exc = sr.getException();
if (exc != null) {
errorCode = sr.getException().getMessage();
} else {
errorCode = null;
}
if ((errorCode == null) ||
(errorCode.equals(CalFacadeErrorCode.schedulingNoRecipients))) {
final Collection srrs = new ArrayList<>();
for (final ScheduleRecipientResult bwsrr: sr.recipientResults.values()) {
final SchedRecipientResult srr = new SchedRecipientResult();
srr.recipient = bwsrr.recipient;
srr.status = bwsrr.getStatus();
if (bwsrr.freeBusy != null) {
srr.freeBusy = new BwCalDAVEvent(this, new EventInfo(bwsrr.freeBusy));
}
srrs.add(srr);
}
return srrs;
}
switch (errorCode) {
case CalFacadeErrorCode.schedulingBadMethod ->
throw new WebdavForbidden(CaldavTags.validCalendarData,
"Bad METHOD");
case CalFacadeErrorCode.schedulingBadAttendees ->
throw new WebdavForbidden(CaldavTags.attendeeAllowed,
"Bad attendees");
case CalFacadeErrorCode.schedulingAttendeeAccessDisallowed ->
throw new WebdavForbidden(CaldavTags.attendeeAllowed,
"attendeeAccessDisallowed");
}
throw new WebdavForbidden(errorCode);
}
private BwCalendar unwrap(final CalDAVCollection> col) {
if (col == null) {
return null;
}
if (!(col instanceof BwCalDAVCollection)) {
throw new RuntimeException("Unknown implementation of BwCalDAVCollection" +
col.getClass());
}
return ((BwCalDAVCollection)col).getCol();
}
private EventInfo getEvinfo(final CalDAVEvent> ev) {
if (ev == null) {
return null;
}
return ((BwCalDAVEvent)ev).getEvinfo();
}
private BwEvent getEvent(final CalDAVEvent> ev) {
if (ev == null) {
return null;
}
return ((BwCalDAVEvent)ev).getEv();
}
private BwResource getRsrc(final CalDAVResource> rsrc) {
if (rsrc == null) {
return null;
}
return ((BwCalDAVResource)rsrc).getRsrc();
}
/**
* @return CalSvcI session object
* @throws RuntimeException on fatal error
*/
private CalSvcI getSvci() {
if (!svci.isOpen()) {
try {
svci.open();
svci.beginTransaction();
} catch (final Throwable t) {
throw new RuntimeException(t);
}
}
return svci;
}
@SuppressWarnings("UnusedReturnValue")
private CalSvcI getSvci(final String account,
final String runAs,
final boolean service,
final boolean publicAdmin,
final String calSuite,
final String clientId,
final boolean allowCreateEprops,
final boolean readonly) {
try {
/* account is what we authenticated with.
* user, if non-null, is the user calendar we want to access.
*/
final boolean possibleSuperUser =
"root".equals(account) || // allow SuperUser
"admin".equals(account);
String runAsUser = null;
String clientIdent = null;
if (possibleSuperUser) {
runAsUser = runAs;
clientIdent = clientId;
}
final CalSvcIPars pars =
CalSvcIPars.getCaldavPars("bwcaldav",
account,
runAsUser,
clientIdent,
possibleSuperUser, // allow SuperUser
service,
publicAdmin,
calSuite,
allowCreateEprops,
readonly);
svci = new CalSvcFactoryDefault().getSvc(
getClass().getClassLoader(), pars);
svci.open();
svci.beginTransaction();
trans = new IcalTranslator(svci.getIcalCallback());
} catch (final Throwable t) {
throw new WebdavException(t);
}
return svci;
}
private XmlTranslator getXmlTrans() {
if (xmlTrans == null) {
xmlTrans = new XmlTranslator(svci.getIcalCallback());
}
return xmlTrans;
}
private JcalTranslator getJcalTrans() {
if (jcalTrans == null) {
jcalTrans = new JcalTranslator(svci.getIcalCallback());
}
return jcalTrans;
}
private JSCalTranslator getJScalTrans() {
if (jscalTrans == null) {
jscalTrans = new JSCalTranslator(svci.getIcalCallback());
}
return jscalTrans;
}
private IcalTranslator getTrans(final String contentType) {
if (contentType == null) {
return trans;
}
return switch (contentType) {
case "text/calendar" -> trans;
case "application/calendar+json" -> getJcalTrans();
case "application/calendar+xml" -> getXmlTrans();
case "application/jscalendar+json" -> getJScalTrans();
default -> throw new RuntimeException(
"Unsupported content type: " +
contentType);
};
}
private void close(final CalSvcI svci) {
if ((svci == null) || !svci.isOpen()) {
return;
}
try {
svci.endTransaction();
final long reqTime = System.currentTimeMillis() - reqInTime;
svci.postNotification(new HttpOutEvent(SysCode.CALDAV_OUT, reqTime));
} catch (final Throwable t) {
try {
svci.close();
} catch (final Throwable ignored) {
}
if (t instanceof BedeworkStaleStateException) {
throw new WebdavException(HttpServletResponse.SC_CONFLICT);
}
throw new WebdavException(t);
}
try {
svci.close();
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
private RecurringRetrievalMode getRrm(final RetrievalMode rm) {
if (rm == null) {
return RecurringRetrievalMode.overrides;
}
try {
if (rm.getExpand() != null) {
/* expand with time range */
final ExpandType ex = rm.getExpand();
final DateTime s = new DateTime(
XcalUtil.getIcalFormatDateTime(ex.getStart()));
final DateTime e = new DateTime(
XcalUtil.getIcalFormatDateTime(ex.getEnd()));
return new RecurringRetrievalMode(Rmode.expanded,
getBwDt(s),
getBwDt(e));
}
if (rm.getLimitRecurrenceSet() != null) {
/* Only return master event and overrides in range */
final LimitRecurrenceSetType l = rm.getLimitRecurrenceSet();
final DateTime s = new DateTime(
XcalUtil.getIcalFormatDateTime(l.getStart()));
final DateTime e = new DateTime(
XcalUtil.getIcalFormatDateTime(l.getEnd()));
return new RecurringRetrievalMode(Rmode.overrides,
getBwDt(s),
getBwDt(e));
}
} catch (final Throwable t) {
throw new WebdavBadRequest(CaldavTags.validFilter, "Invalid time-range");
}
/* Return master + overrides */
return RecurringRetrievalMode.overrides;
}
private BwDateTime getBwDt(final DateTime dt) {
try {
if (dt == null) {
return null;
}
return BwDateTime.makeBwDateTime(false, dt.toString(), null);
} catch (final Throwable t) {
throw new WebdavException(t);
}
}
private BwPreferences getPrefs() {
try {
if (prefs == null) {
prefs = getSvci().getPrefsHandler().get();
}
} catch (final Throwable t) {
throw new WebdavException(t);
}
return prefs;
}
/* ==============================================================
* Logged methods
* ============================================================== */
private final BwLogger logger = new BwLogger();
@Override
public BwLogger getLogger() {
if ((logger.getLoggedClass() == null) && (logger.getLoggedName() == null)) {
logger.setLoggedClass(getClass());
}
return logger;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy