use of com.zimbra.common.calendar.ZCalendar.ICalTok in project zm-mailbox by Zimbra.
the class CalendarUtils method parseRecur.
static Recurrence.IRecurrence parseRecur(Element recurElt, TimeZoneMap invTzMap, ParsedDateTime dtStart, ParsedDateTime dtEnd, ParsedDuration dur, RecurId recurId) throws ServiceException {
if (dur == null && dtStart != null && dtEnd != null)
dur = dtEnd.difference(dtStart);
ArrayList<IRecurrence> addRules = new ArrayList<IRecurrence>();
ArrayList<IRecurrence> subRules = new ArrayList<IRecurrence>();
for (Iterator iter = recurElt.elementIterator(); iter.hasNext(); ) {
Element e = (Element) iter.next();
boolean exclude = false;
if (e.getName().equals(MailConstants.E_CAL_EXCLUDE)) {
exclude = true;
} else {
if (!e.getName().equals(MailConstants.E_CAL_ADD)) {
continue;
}
}
for (Iterator intIter = e.elementIterator(); intIter.hasNext(); ) {
Element intElt = (Element) intIter.next();
if (intElt.getName().equals(MailConstants.E_CAL_DATES)) {
// handle RDATE or EXDATE
String tzid = intElt.getAttribute(MailConstants.A_CAL_TIMEZONE, null);
ICalTimeZone tz = tzid != null ? invTzMap.lookupAndAdd(tzid) : null;
RdateExdate rexdate = new RdateExdate(exclude ? ICalTok.EXDATE : ICalTok.RDATE, tz);
ICalTok valueType = null;
for (Iterator<Element> dtvalIter = intElt.elementIterator(MailConstants.E_CAL_DATE_VAL); dtvalIter.hasNext(); ) {
ICalTok dtvalValueType = null;
Element dtvalElem = dtvalIter.next();
Element dtvalStartElem = dtvalElem.getElement(MailConstants.E_CAL_START_TIME);
String dtvalStartDateStr = dtvalStartElem.getAttribute(MailConstants.A_CAL_DATETIME);
ParsedDateTime dtvalStart = parseDateTime(dtvalElem.getName(), dtvalStartDateStr, tzid, invTzMap);
Element dtvalEndElem = dtvalElem.getOptionalElement(MailConstants.E_CAL_END_TIME);
Element dtvalDurElem = dtvalElem.getOptionalElement(MailConstants.E_CAL_DURATION);
if (dtvalEndElem == null && dtvalDurElem == null) {
if (dtvalStart.hasTime())
dtvalValueType = ICalTok.DATE_TIME;
else
dtvalValueType = ICalTok.DATE;
rexdate.addValue(dtvalStart);
} else {
dtvalValueType = ICalTok.PERIOD;
if (dtvalEndElem != null) {
String dtvalEndDateStr = dtvalEndElem.getAttribute(MailConstants.A_CAL_DATETIME);
ParsedDateTime dtvalEnd = parseDateTime(dtvalElem.getName(), dtvalEndDateStr, tzid, invTzMap);
Period p = new Period(dtvalStart, dtvalEnd);
rexdate.addValue(p);
} else {
ParsedDuration d = ParsedDuration.parse(dtvalDurElem);
Period p = new Period(dtvalStart, d);
rexdate.addValue(p);
}
}
if (valueType == null) {
valueType = dtvalValueType;
rexdate.setValueType(valueType);
} else if (valueType != dtvalValueType)
throw ServiceException.INVALID_REQUEST("Cannot mix different value types in a single <" + intElt.getName() + "> element", null);
}
Recurrence.SingleDates sd = new Recurrence.SingleDates(rexdate, dur);
if (exclude)
subRules.add(sd);
else
addRules.add(sd);
} else if (intElt.getName().equals(MailConstants.E_CAL_RULE)) {
// handle RRULE or EXRULE
// Turn XML into iCal RECUR string, which will then be
// parsed by ical4j Recur object.
StringBuilder recurBuf = new StringBuilder(100);
String freq = IcalXmlStrMap.sFreqMap.toIcal(intElt.getAttribute(MailConstants.A_CAL_RULE_FREQ));
recurBuf.append("FREQ=").append(freq);
for (Iterator ruleIter = intElt.elementIterator(); ruleIter.hasNext(); ) {
Element ruleElt = (Element) ruleIter.next();
String ruleEltName = ruleElt.getName();
if (ruleEltName.equals(MailConstants.E_CAL_RULE_UNTIL)) {
recurBuf.append(";UNTIL=");
String d = ruleElt.getAttribute(MailConstants.A_CAL_DATETIME);
recurBuf.append(d);
// (RFC2445 Section 4.3.10 Recurrence Rule)
if (d.indexOf("T") >= 0)
if (d.indexOf("Z") < 0)
recurBuf.append('Z');
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_COUNT)) {
int num = (int) ruleElt.getAttributeLong(MailConstants.A_CAL_RULE_COUNT_NUM, -1);
if (num > 0) {
recurBuf.append(";COUNT=").append(num);
} else {
throw ServiceException.INVALID_REQUEST("Expected positive num attribute in <recur> <rule> <count>", null);
}
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_INTERVAL)) {
String ival = ruleElt.getAttribute(MailConstants.A_CAL_RULE_INTERVAL_IVAL);
recurBuf.append(";INTERVAL=").append(ival);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYSECOND)) {
String list = ruleElt.getAttribute(MailConstants.A_CAL_RULE_BYSECOND_SECLIST);
recurBuf.append(";BYSECOND=").append(list);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYMINUTE)) {
String list = ruleElt.getAttribute(MailConstants.A_CAL_RULE_BYMINUTE_MINLIST);
recurBuf.append(";BYMINUTE=").append(list);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYHOUR)) {
String list = ruleElt.getAttribute(MailConstants.A_CAL_RULE_BYHOUR_HRLIST);
recurBuf.append(";BYHOUR=").append(list);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYDAY)) {
recurBuf.append(";BYDAY=");
int pos = 0;
for (Iterator bydayIter = ruleElt.elementIterator(MailConstants.E_CAL_RULE_BYDAY_WKDAY); bydayIter.hasNext(); pos++) {
Element wkdayElt = (Element) bydayIter.next();
if (pos > 0)
recurBuf.append(",");
String ordwk = wkdayElt.getAttribute(MailConstants.A_CAL_RULE_BYDAY_WKDAY_ORDWK, null);
if (ordwk != null)
recurBuf.append(ordwk);
String day = wkdayElt.getAttribute(MailConstants.A_CAL_RULE_DAY);
if (day == null || day.length() == 0)
throw ServiceException.INVALID_REQUEST("Missing " + MailConstants.A_CAL_RULE_DAY + " in <" + ruleEltName + ">", null);
recurBuf.append(day);
}
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYMONTHDAY)) {
String list = ruleElt.getAttribute(MailConstants.A_CAL_RULE_BYMONTHDAY_MODAYLIST);
recurBuf.append(";BYMONTHDAY=").append(list);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYYEARDAY)) {
String list = ruleElt.getAttribute(MailConstants.A_CAL_RULE_BYYEARDAY_YRDAYLIST);
recurBuf.append(";BYYEARDAY=").append(list);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYWEEKNO)) {
String list = ruleElt.getAttribute(MailConstants.A_CAL_RULE_BYWEEKNO_WKLIST);
recurBuf.append(";BYWEEKNO=").append(list);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYMONTH)) {
String list = ruleElt.getAttribute(MailConstants.A_CAL_RULE_BYMONTH_MOLIST);
recurBuf.append(";BYMONTH=").append(list);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_BYSETPOS)) {
String list = ruleElt.getAttribute(MailConstants.A_CAL_RULE_BYSETPOS_POSLIST);
recurBuf.append(";BYSETPOS=").append(list);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_WKST)) {
String day = ruleElt.getAttribute(MailConstants.A_CAL_RULE_DAY);
recurBuf.append(";WKST=").append(day);
} else if (ruleEltName.equals(MailConstants.E_CAL_RULE_XNAME)) {
String name = ruleElt.getAttribute(MailConstants.A_CAL_RULE_XNAME_NAME, null);
if (name != null) {
String value = ruleElt.getAttribute(MailConstants.A_CAL_RULE_XNAME_VALUE, "");
// TODO: Escape/unescape value according to
// "text" rule.
recurBuf.append(";").append(name).append("=").append(value);
}
}
}
try {
ZRecur recur = new ZRecur(recurBuf.toString(), invTzMap);
if (exclude) {
subRules.add(new Recurrence.SimpleRepeatingRule(dtStart, dur, recur, null));
} else {
addRules.add(new Recurrence.SimpleRepeatingRule(dtStart, dur, recur, null));
}
} catch (ServiceException ex) {
throw ServiceException.INVALID_REQUEST("Exception parsing <recur> <rule>", ex);
}
} else {
throw ServiceException.INVALID_REQUEST("Expected <date> or <rule> inside of " + e.getName() + ", got " + intElt.getName(), null);
}
}
// iterate inside <add> or <exclude>
}
if (recurId != null) {
return new Recurrence.ExceptionRule(recurId, dtStart, dur, null, addRules, subRules);
} else {
return new Recurrence.RecurrenceRule(dtStart, dur, null, addRules, subRules);
}
}
use of com.zimbra.common.calendar.ZCalendar.ICalTok in project zm-mailbox by Zimbra.
the class TnefConverter method expandTNEF.
/**
* Performs the TNEF->MIME conversion on any TNEF body parts that
* make up the given message.
* @throws ServiceException
*/
private MimeMultipart expandTNEF(MimeBodyPart bp) throws MessagingException, IOException {
if (!TNEFUtils.isTNEFMimeType(bp.getContentType()))
return null;
MimeMessage converted = null;
// convert TNEF to a MimeMessage and remove it from the parent
InputStream is = null;
try {
TNEFInputStream tnefis = new TNEFInputStream(is = bp.getInputStream());
converted = TNEFMime.convert(JMSession.getSession(), tnefis);
// XXX bburtin: nasty hack. Don't handle OOME since JTNEF can allocate a huge byte
// array when the TNEF file is malformed. See bug 42649.
// } catch (OutOfMemoryError e) {
// Zimbra.halt("Ran out of memory while expanding TNEF attachment", e);
} catch (Throwable t) {
ZimbraLog.extensions.warn("Conversion failed. TNEF attachment will not be expanded.", t);
return null;
} finally {
ByteUtil.closeStream(is);
}
Object convertedContent = converted.getContent();
if (!(convertedContent instanceof MimeMultipart)) {
ZimbraLog.extensions.debug("TNEF attachment doesn't contain valid MimeMultiPart");
return null;
}
MimeMultipart convertedMulti = (MimeMultipart) convertedContent;
// make sure that all the attachments are marked as attachments
for (int i = 0; i < convertedMulti.getCount(); i++) {
BodyPart subpart = convertedMulti.getBodyPart(i);
if (subpart.getHeader("Content-Disposition") == null)
subpart.setHeader("Content-Disposition", Part.ATTACHMENT);
}
// Create a MimeBodyPart for the converted data. Currently we're throwing
// away the top-level message because its content shows up as blank after
// the conversion.
MimeBodyPart convertedPart = new ZMimeBodyPart();
convertedPart.setContent(convertedMulti);
// If the TNEF object contains calendar data, create an iCalendar version.
MimeBodyPart icalPart = null;
if (DebugConfig.enableTnefToICalendarConversion) {
try {
TnefToICalendar calConverter = new DefaultTnefToICalendar();
ZCalendar.DefaultContentHandler icalHandler = new ZCalendar.DefaultContentHandler();
if (calConverter.convert(mMimeMessage, bp.getInputStream(), icalHandler)) {
if (icalHandler.getNumCals() > 0) {
List<ZVCalendar> cals = icalHandler.getCals();
Writer writer = new StringWriter(1024);
ICalTok method = null;
for (ZVCalendar cal : cals) {
cal.toICalendar(writer);
if (method == null)
method = cal.getMethod();
}
writer.close();
icalPart = new ZMimeBodyPart();
icalPart.setText(writer.toString());
ContentType ct = new ContentType(MimeConstants.CT_TEXT_CALENDAR);
ct.setCharset(MimeConstants.P_CHARSET_UTF8);
if (method != null)
ct.setParameter("method", method.toString());
icalPart.setHeader("Content-Type", ct.toString());
}
}
} catch (ServiceException e) {
throw new MessagingException("TNEF to iCalendar conversion failure: " + e.getMessage(), e);
} catch (Throwable t) {
//don't allow TNEF errors to crash server
ZimbraLog.extensions.warn("Failed to convert TNEF to iCal", t);
throw new MessagingException("TNEF to iCalendar conversion failure");
}
}
// create a multipart/alternative for the TNEF and its MIME version
MimeMultipart altMulti = new ZMimeMultipart("alternative");
// altMulti.addBodyPart(bp);
altMulti.addBodyPart(convertedPart);
if (icalPart != null)
altMulti.addBodyPart(icalPart);
return altMulti;
}
use of com.zimbra.common.calendar.ZCalendar.ICalTok in project zm-mailbox by Zimbra.
the class Message method getInfoForAssociatedCalendarItem.
/**
* Update {@code status.calItem} and {@code calendarItemInfos}
*/
private void getInfoForAssociatedCalendarItem(Account acct, Invite cur, String method, ParsedMessage pm, boolean applyToCalendar, ProcessInvitesStatus status) throws ServiceException {
boolean calItemIsNew = false;
boolean modifiedCalItem = false;
boolean success = false;
try {
InviteChanges invChanges = null;
// Look for organizer-provided change list.
ZProperty changesProp = cur.getXProperty(ICalTok.X_ZIMBRA_CHANGES.toString());
if (changesProp != null) {
invChanges = new InviteChanges(changesProp.getValue());
// Don't let the x-prop propagate further. This x-prop is used during transport only. Presence
// of this x-prop in the appointment object can confuse clients.
cur.removeXProp(ICalTok.X_ZIMBRA_CHANGES.toString());
}
if (!(status.intendedForMe || status.intendedForCalendarIManage)) {
// Not intended for me. Just save the invite detail in metadata.
CalendarItemInfo info = new CalendarItemInfo(CalendarItemInfo.CALITEM_ID_NONE, cur.getComponentNum(), cur, invChanges);
calendarItemInfos.add(info);
status.updatedMetadata = true;
success = true;
return;
}
OperationContext octxt = getMailbox().getOperationContext();
ICalTok methodTok = Invite.lookupMethod(method);
AccountAddressMatcher acctMatcher = status.getAcctMatcher();
cur.sanitize(true);
if (status.intendedForCalendarIManage) {
Provisioning prov = Provisioning.getInstance();
Account ownerAcct = prov.get(AccountBy.name, calendarIntendedFor);
com.zimbra.soap.mail.type.CalendarItemInfo cii = getMailbox().getRemoteCalItemByUID(ownerAcct, cur.getUid(), false, false);
CalendarItemInfo info;
if (cii == null) {
info = new CalendarItemInfo(CalendarItemInfo.CALITEM_ID_NONE, cur.getComponentNum(), cur, invChanges);
calendarItemInfos.add(info);
} else {
int calItemId;
String owner;
try {
ItemId iid = new ItemId(cii.getId(), (String) null);
calItemId = iid.getId();
owner = iid.getAccountId();
} catch (Exception e) {
calItemId = CalendarItemInfo.CALITEM_ID_NONE;
owner = null;
}
info = new CalendarItemInfo(calItemId, owner, cur.getComponentNum(), cur, invChanges);
calendarItemInfos.add(info);
}
status.updatedMetadata = true;
success = true;
return;
}
status.calItem = mMailbox.getCalendarItemByUid(octxt, cur.getUid());
if (applyToCalendar && // replies are handled elsewhere (in Mailbox.addMessage())
!ICalTok.REPLY.equals(methodTok) && !ICalTok.COUNTER.equals(methodTok) && !ICalTok.DECLINECOUNTER.equals(methodTok)) {
if (status.calItem == null) {
// Allow PUBLISH method as well depending on the preference.
if (ICalTok.REQUEST.equals(methodTok) || (ICalTok.PUBLISH.equals(methodTok) && getAccount().getBooleanAttr(Provisioning.A_zimbraPrefCalendarAllowPublishMethodInvite, false))) {
if (status.autoAddNew) {
if (mMailbox.getAccount().sameAccount(cur.getOrganizerAccount())) {
// Bug 100456 ZCO sends out invites before adding a calendar entry. If server adds
// Calendar entry and ZCO sees that before creating its entry, ZCO gets confused.
LOG.info("Mailbox %d Msg %d Don't create ORGANIZER calendar item for Invite %s", getMailboxId(), getId(), method);
} else {
int flags = 0;
// int flags = Flag.BITMASK_INDEXING_DEFERRED;
// mMailbox.incrementIndexDeferredCount(1);
int defaultFolder = cur.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
status.calItem = mMailbox.createCalendarItem(defaultFolder, flags, null, cur.getUid(), pm, cur, null);
calItemIsNew = true;
status.calItemFolderId = status.calItem.getFolderId();
}
}
} else {
LOG.info("Mailbox %d Message %d SKIPPING Invite %s b/c no CalendarItem could be found", getMailboxId(), getId(), method);
}
} else {
// bug 27887: Ignore when calendar request email somehow made a loop back to the
// organizer address. Necessary conditions are:
//
// 1) This mailbox is currently organizer.
// 2) ORGANIZER in the request is this mailbox.
// 3) User/COS preference doesn't explicitly allow it. (bug 29777)
//
// If ORGANIZER in the request is a different user this is an organizer change request,
// which should be allowed to happen.
boolean ignore = false;
Invite defInv = status.calItem.getDefaultInviteOrNull();
if (defInv != null && defInv.isOrganizer()) {
ZOrganizer org = cur.getOrganizer();
String orgAddress = org != null ? org.getAddress() : null;
if (acctMatcher.matches(orgAddress))
ignore = !acct.getBooleanAttr(Provisioning.A_zimbraPrefCalendarAllowCancelEmailToSelf, false);
}
if (ignore) {
ZimbraLog.calendar.info("Ignoring calendar request emailed from organizer to self, " + "possibly in a mail loop involving mailing lists and/or forwards; " + "calItemId=" + status.calItem.getId() + ", msgId=" + getId());
} else {
// the calendar item in the folder it's currently in.
if (status.addRevision)
status.calItem.snapshotRevision(false);
// If updating (but not canceling) an appointment in trash folder,
// use default calendar folder and discard all existing invites.
boolean discardExistingInvites = false;
int calFolderId = status.calItem.getFolderId();
if (!cur.isCancel() && status.calItem.inTrash()) {
discardExistingInvites = true;
if (status.calItem.getType() == MailItem.Type.TASK) {
calFolderId = Mailbox.ID_FOLDER_TASKS;
} else {
calFolderId = Mailbox.ID_FOLDER_CALENDAR;
}
}
// against the current appointment data.
if (invChanges == null && !cur.isCancel()) {
Invite prev = status.calItem.getInvite(cur.getRecurId());
if (prev == null) {
// If incoming invite is a brand-new exception instance, we have to compare
// against the series data, but adjusted to the RECURRENCE-ID of the instance.
Invite series = status.calItem.getInvite(null);
if (series != null)
prev = series.makeInstanceInvite(cur.getRecurId().getDt());
}
if (prev != null)
invChanges = new InviteChanges(prev, cur);
}
modifiedCalItem = status.calItem.processNewInvite(pm, cur, calFolderId, discardExistingInvites);
status.calItemFolderId = calFolderId;
status.calItem.getFolder().updateHighestMODSEQ();
}
}
}
int calItemId = status.calItem != null ? status.calItem.getId() : CalendarItemInfo.CALITEM_ID_NONE;
CalendarItemInfo info = new CalendarItemInfo(calItemId, cur.getComponentNum(), cur, invChanges);
calendarItemInfos.add(info);
status.updatedMetadata = true;
if (status.calItem != null && (calItemIsNew || modifiedCalItem)) {
mMailbox.index.add(status.calItem);
}
success = true;
} finally {
if (!success && status.calItem != null) {
// Error occurred and the calItem in memory may be out of sync with the database.
// Uncache it here, because the error will be ignored by this method's caller.
getMailbox().uncache(status.calItem);
}
}
}
use of com.zimbra.common.calendar.ZCalendar.ICalTok in project zm-mailbox by Zimbra.
the class Message method processInvitesAfterCreate.
/**
* This has to be done as a separate step, after the MailItem has been added, because of foreign key constraints on
* the CalendarItems table.
*/
private void processInvitesAfterCreate(String method, int folderId, boolean applyToCalendar, ParsedMessage pm, List<Invite> invites) throws ServiceException {
if (pm == null) {
throw ServiceException.INVALID_REQUEST("null ParsedMessage while processing invite in message " + mId, null);
}
Account acct = getAccount();
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(acct);
OperationContext octxt = getMailbox().getOperationContext();
ProcessInvitesStatus status = new ProcessInvitesStatus(acct, pm);
status.initAutoAddNew(octxt);
boolean isOrganizerMethod = Invite.isOrganizerMethod(method);
if (isOrganizerMethod && !invites.isEmpty() && status.intendedForMe) {
// Check if the sender is allowed to invite this user. Only do this for invite-type methods,
// namely REQUEST/PUBLISH/CANCEL/ADD/DECLINECOUNTER. REPLY/REFRESH/COUNTER don't undergo
// the check because they are not organizer-to-attendee methods.
String senderEmail;
Account senderAcct = null;
boolean onBehalfOf = false;
boolean canInvite;
AccessManager accessMgr = AccessManager.getInstance();
if (octxt != null && octxt.getAuthenticatedUser() != null) {
onBehalfOf = octxt.isDelegatedRequest(getMailbox());
senderAcct = octxt.getAuthenticatedUser();
senderEmail = senderAcct.getName();
canInvite = accessMgr.canDo(senderAcct, acct, User.R_invite, octxt.isUsingAdminPrivileges());
} else {
senderEmail = pm.getSenderEmail(false);
if (senderEmail != null) {
senderAcct = Provisioning.getInstance().get(AccountBy.name, senderEmail);
}
canInvite = accessMgr.canDo(senderEmail, acct, User.R_invite, false);
}
if (!canInvite) {
Invite invite = invites.get(0);
CalendarMailSender.handleInviteAutoDeclinedNotification(octxt, getMailbox(), acct, senderEmail, senderAcct, onBehalfOf, applyToCalendar, getId(), invite);
String inviteSender = senderEmail != null ? senderEmail : "unknown sender";
ZimbraLog.calendar.info("Calendar invite from %s to %s is not allowed", inviteSender, acct.getName());
// Turn off auto-add. We still have to run through the code below to save the invite's
// data in message's metadata.
status.autoAddNew = false;
}
}
// Override CLASS property if preference says to mark everything as private.
PrefCalendarApptVisibility prefClass = acct.getPrefCalendarApptVisibility();
boolean forcePrivateClass = prefClass != null && !prefClass.equals(PrefCalendarApptVisibility.public_);
// Ignore alarms set by organizer.
boolean allowOrganizerAlarm = DebugConfig.calendarAllowOrganizerSpecifiedAlarms;
if (calendarItemInfos == null) {
calendarItemInfos = new ArrayList<CalendarItemInfo>();
}
// properties.
if (invites.size() > 1) {
boolean hasSeries = false;
ZOrganizer seriesOrganizer = null;
boolean seriesIsOrganizer = false;
List<ZAttendee> seriesAttendees = null;
ParsedDateTime seriesDtStart = null;
// Get organizer and attendees from series VEVENT.
for (Invite inv : invites) {
if (!inv.hasRecurId()) {
hasSeries = true;
seriesOrganizer = inv.getOrganizer();
seriesIsOrganizer = inv.isOrganizer();
seriesAttendees = inv.getAttendees();
seriesDtStart = inv.getStartTime();
break;
}
}
if (hasSeries) {
for (Invite inv : invites) {
RecurId rid = inv.getRecurId();
if (rid != null) {
if (seriesOrganizer != null && !inv.hasOrganizer()) {
inv.setOrganizer(seriesOrganizer);
inv.setIsOrganizer(seriesIsOrganizer);
// exception instance to have no attendee.
if (!inv.hasOtherAttendees() && seriesAttendees != null) {
for (ZAttendee at : seriesAttendees) {
inv.addAttendee(at);
}
}
}
if (!inv.isAllDayEvent() && seriesDtStart != null) {
// Exchange can send invalid RECURRENCE-ID with HHMMSS set to 000000. Detect it and fix it up
// by copying the time from series DTSTART.
ParsedDateTime ridDt = rid.getDt();
if (ridDt != null && ridDt.hasZeroTime() && !seriesDtStart.hasZeroTime() && ridDt.sameTimeZone(seriesDtStart)) {
ParsedDateTime fixedDt = seriesDtStart.cloneWithNewDate(ridDt);
RecurId fixedRid = new RecurId(fixedDt, rid.getRange());
ZimbraLog.calendar.debug("Fixed up invalid RECURRENCE-ID with zero time; before=[%s], after=[%s]", rid, fixedRid);
inv.setRecurId(fixedRid);
}
}
// Exception instance invites shouldn't point to the same MIME part in the appointment blob
// as the series invite. If they do, we will lose the series attachment when a new exception
// instance update is received.
inv.setMailItemId(0);
}
}
}
}
// used to check if any invite is non-public
boolean publicInvites = true;
status.calItemFolderId = invites.size() > 0 && invites.get(0).isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
CalendarItem firstCalItem = null;
Set<String> calUidsSeen = new HashSet<String>();
for (Invite cur : invites) {
if (!cur.isPublic()) {
publicInvites = false;
}
// it's the correct organizer.
if (!cur.hasOrganizer() && cur.hasOtherAttendees()) {
String fromEmail = pm.getSenderEmail(true);
if (fromEmail != null) {
boolean dangerousSender = false;
// Is sender == recipient? If so, clear attendees.
if (status.intendedForAddress != null) {
if (status.intendedForAddress.equalsIgnoreCase(fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
cur.clearAttendees();
dangerousSender = true;
}
} else if (acctMatcher.matches(fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
cur.clearAttendees();
dangerousSender = true;
}
if (!dangerousSender) {
if (isOrganizerMethod = Invite.isOrganizerMethod(method)) {
// For organizer-originated methods, use email sender as default organizer.
ZOrganizer org = new ZOrganizer(fromEmail, null);
String senderEmail = pm.getSenderEmail(false);
if (senderEmail != null && !senderEmail.equalsIgnoreCase(fromEmail))
org.setSentBy(senderEmail);
cur.setOrganizer(org);
ZimbraLog.calendar.info("Got malformed invite that lists attendees without specifying an organizer. " + "Defaulting organizer to: " + org.toString());
} else {
// For attendee-originated methods, look up organizer from appointment on calendar.
// If appointment is not found, fall back to the intended-for address, then finally to self.
ZOrganizer org = null;
CalendarItem ci = mMailbox.getCalendarItemByUid(octxt, cur.getUid());
if (ci != null) {
Invite inv = ci.getInvite(cur.getRecurId());
if (inv == null) {
inv = ci.getDefaultInviteOrNull();
}
if (inv != null) {
org = inv.getOrganizer();
}
}
if (org == null) {
if (status.intendedForAddress != null) {
org = new ZOrganizer(status.intendedForAddress, null);
} else {
org = new ZOrganizer(acct.getName(), null);
}
}
cur.setOrganizer(org);
cur.setIsOrganizer(status.intendedForMe);
ZimbraLog.calendar.info("Got malformed reply missing organizer. Defaulting to " + org.toString());
}
}
}
}
cur.setLocalOnly(false);
status.initAddRevisionSetting(cur.getUid(), calUidsSeen);
// other than BUSY. And don't allow transparent meetings. This will prevent double booking in the future.
if (cur.isEvent() && (acct instanceof CalendarResource)) {
cur.setFreeBusy(IcalXmlStrMap.FBTYPE_BUSY);
cur.setTransparency(IcalXmlStrMap.TRANSP_OPAQUE);
}
if (forcePrivateClass) {
cur.setClassProp(IcalXmlStrMap.CLASS_PRIVATE);
cur.setClassPropSetByMe(true);
}
ICalTok methodTok = Invite.lookupMethod(method);
// Discard alarms set by organizer. Add a new one based on attendee's preferences.
if (!allowOrganizerAlarm) {
// only for non-cancel/non-declinecounter VEVENTs
if (cur.isEvent() && isOrganizerMethod && !cur.isCancel() && !ICalTok.DECLINECOUNTER.equals(methodTok))
Invite.setDefaultAlarm(cur, acct);
}
getInfoForAssociatedCalendarItem(acct, cur, method, pm, applyToCalendar, status);
if (firstCalItem == null) {
firstCalItem = status.calItem;
}
}
if (status.updatedMetadata) {
saveMetadata();
}
// Don't forward from a system account. (e.g. archiving, galsync, ham/spam)
if (applyToCalendar && !status.isForwardedInvite && status.intendedForMe && folderId != Mailbox.ID_FOLDER_SENT && !invites.isEmpty() && !acct.isIsSystemResource()) {
// Don't do the forwarding during redo playback.
RedoableOp redoPlayer = octxt != null ? octxt.getPlayer() : null;
RedoLogProvider redoProvider = RedoLogProvider.getInstance();
boolean needToForward = redoProvider.isMaster() && (redoPlayer == null || redoProvider.getRedoLogManager().getInCrashRecovery());
if (needToForward) {
String[] forwardTo = null;
if (isOrganizerMethod) {
forwardTo = acct.getPrefCalendarForwardInvitesTo();
} else {
// not the users listed in the zimbraPrefCalendarForwardInvitesTo preference.
if (firstCalItem != null) {
Invite invCalItem = firstCalItem.getInvite(invites.get(0).getRecurId());
if (invCalItem == null)
invCalItem = firstCalItem.getDefaultInviteOrNull();
if (invCalItem != null && invCalItem.isOrganizer()) {
ZOrganizer org = invCalItem.getOrganizer();
if (org.hasSentBy()) {
forwardTo = new String[] { org.getSentBy() };
}
}
}
}
Account senderAcct = null;
String senderEmail = pm.getSenderEmail(false);
if (senderEmail != null)
senderAcct = Provisioning.getInstance().get(AccountBy.name, senderEmail);
if (forwardTo != null && forwardTo.length > 0) {
// recipients to receive unfiltered message
List<String> rcptsUnfiltered = new ArrayList<String>();
// recipients to receive message filtered to remove private data
List<String> rcptsFiltered = new ArrayList<String>();
Folder calFolder = null;
try {
calFolder = getMailbox().getFolderById(status.calItemFolderId);
} catch (NoSuchItemException e) {
ZimbraLog.mailbox.warn("No such calendar folder (" + status.calItemFolderId + ") during invite auto-forwarding");
}
for (String fwd : forwardTo) {
if (fwd != null) {
fwd = fwd.trim();
}
if (StringUtil.isNullOrEmpty(fwd)) {
continue;
}
// Prevent forwarding to self.
if (acctMatcher.matches(fwd))
continue;
// Don't forward back to the sender. It's redundant and confusing.
Account rcptAcct = Provisioning.getInstance().get(AccountBy.name, fwd);
boolean rcptIsSender = false;
if (rcptAcct != null) {
if (senderAcct != null) {
rcptIsSender = rcptAcct.getId().equalsIgnoreCase(senderAcct.getId());
} else {
rcptIsSender = AccountUtil.addressMatchesAccount(rcptAcct, senderEmail);
}
} else {
if (senderAcct != null) {
rcptIsSender = AccountUtil.addressMatchesAccount(senderAcct, fwd);
} else {
rcptIsSender = fwd.equalsIgnoreCase(senderEmail);
}
}
if (rcptIsSender) {
ZimbraLog.calendar.info("Not auto-forwarding to " + fwd + " because it is the sender of this message");
continue;
}
if (publicInvites) {
rcptsUnfiltered.add(fwd);
} else {
boolean allowed = false;
if (calFolder != null && rcptAcct != null) {
allowed = calFolder.canAccess(ACL.RIGHT_PRIVATE, rcptAcct, false);
}
if (allowed) {
rcptsUnfiltered.add(fwd);
} else if (acct instanceof CalendarResource) {
// Forward filtered invite from calendar resource accounts only. Don't forward filtered
// invite from regular user account because the forwardee won't be able to accept/decline
// due to permission error.
rcptsFiltered.add(fwd);
}
}
}
if (!rcptsUnfiltered.isEmpty() || !rcptsFiltered.isEmpty()) {
MimeMessage mmOrig = pm.getMimeMessage();
if (mmOrig != null) {
String origSender = pm.getSenderEmail(false);
String forwarder = AccountUtil.getCanonicalAddress(acct);
if (!rcptsUnfiltered.isEmpty()) {
MimeMessage mm = CalendarMailSender.createForwardedInviteMessage(mmOrig, origSender, forwarder, rcptsUnfiltered.toArray(new String[0]));
if (mm != null) {
ItemId origMsgId = new ItemId(getMailbox(), getId());
CalendarMailSender.sendInviteAutoForwardMessage(octxt, getMailbox(), origMsgId, mm);
}
}
if (!rcptsFiltered.isEmpty()) {
MimeMessage mm = CalendarMailSender.createForwardedPrivateInviteMessage(acct, acct.getLocale(), method, invites, origSender, forwarder, rcptsFiltered.toArray(new String[0]));
if (mm != null) {
ItemId origMsgId = new ItemId(getMailbox(), getId());
CalendarMailSender.sendInviteAutoForwardMessage(octxt, getMailbox(), origMsgId, mm);
}
}
}
}
}
}
}
}
Aggregations