Search in sources :

Example 16 with ICalTok

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);
    }
}
Also used : IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) Recurrence(com.zimbra.cs.mailbox.calendar.Recurrence) RdateExdate(com.zimbra.cs.mailbox.calendar.RdateExdate) ParsedDuration(com.zimbra.common.calendar.ParsedDuration) Element(com.zimbra.common.soap.Element) ArrayList(java.util.ArrayList) Period(com.zimbra.cs.mailbox.calendar.Period) ICalTok(com.zimbra.common.calendar.ZCalendar.ICalTok) IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) ZRecur(com.zimbra.cs.mailbox.calendar.ZRecur) ServiceException(com.zimbra.common.service.ServiceException) Iterator(java.util.Iterator) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone)

Example 17 with ICalTok

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;
}
Also used : ZMimeBodyPart(com.zimbra.common.zmime.ZMimeBodyPart) MimeBodyPart(javax.mail.internet.MimeBodyPart) BodyPart(javax.mail.BodyPart) ContentType(com.zimbra.common.mime.ContentType) ZMimeBodyPart(com.zimbra.common.zmime.ZMimeBodyPart) MessagingException(javax.mail.MessagingException) TNEFInputStream(net.freeutils.tnef.TNEFInputStream) InputStream(java.io.InputStream) TNEFInputStream(net.freeutils.tnef.TNEFInputStream) DefaultTnefToICalendar(com.zimbra.cs.util.tnef.DefaultTnefToICalendar) ICalTok(com.zimbra.common.calendar.ZCalendar.ICalTok) ZCalendar(com.zimbra.common.calendar.ZCalendar) ZVCalendar(com.zimbra.common.calendar.ZCalendar.ZVCalendar) StringWriter(java.io.StringWriter) ServiceException(com.zimbra.common.service.ServiceException) MimeMessage(javax.mail.internet.MimeMessage) ZMimeMultipart(com.zimbra.common.zmime.ZMimeMultipart) MimeMultipart(javax.mail.internet.MimeMultipart) TnefToICalendar(com.zimbra.cs.util.tnef.TnefToICalendar) DefaultTnefToICalendar(com.zimbra.cs.util.tnef.DefaultTnefToICalendar) ZMimeMultipart(com.zimbra.common.zmime.ZMimeMultipart) ZMimeBodyPart(com.zimbra.common.zmime.ZMimeBodyPart) MimeBodyPart(javax.mail.internet.MimeBodyPart) StringWriter(java.io.StringWriter) Writer(java.io.Writer)

Example 18 with ICalTok

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);
        }
    }
}
Also used : Account(com.zimbra.cs.account.Account) InviteChanges(com.zimbra.cs.mailbox.calendar.InviteChanges) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) ItemId(com.zimbra.cs.service.util.ItemId) Provisioning(com.zimbra.cs.account.Provisioning) MessagingException(javax.mail.MessagingException) NoSuchItemException(com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException) ServiceException(com.zimbra.common.service.ServiceException) IOException(java.io.IOException) ICalTok(com.zimbra.common.calendar.ZCalendar.ICalTok) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) ZProperty(com.zimbra.common.calendar.ZCalendar.ZProperty) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 19 with ICalTok

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);
                            }
                        }
                    }
                }
            }
        }
    }
}
Also used : AccessManager(com.zimbra.cs.account.AccessManager) Account(com.zimbra.cs.account.Account) ArrayList(java.util.ArrayList) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) ItemId(com.zimbra.cs.service.util.ItemId) ICalTok(com.zimbra.common.calendar.ZCalendar.ICalTok) RedoableOp(com.zimbra.cs.redolog.op.RedoableOp) RedoLogProvider(com.zimbra.cs.redolog.RedoLogProvider) ZMimeMessage(com.zimbra.common.zmime.ZMimeMessage) MimeMessage(javax.mail.internet.MimeMessage) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) CalendarResource(com.zimbra.cs.account.CalendarResource) HashSet(java.util.HashSet) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) NoSuchItemException(com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException) PrefCalendarApptVisibility(com.zimbra.common.account.ZAttrProvisioning.PrefCalendarApptVisibility) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Aggregations

ICalTok (com.zimbra.common.calendar.ZCalendar.ICalTok)19 ZComponent (com.zimbra.common.calendar.ZCalendar.ZComponent)9 ZProperty (com.zimbra.common.calendar.ZCalendar.ZProperty)9 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)8 ZParameter (com.zimbra.common.calendar.ZCalendar.ZParameter)7 ServiceException (com.zimbra.common.service.ServiceException)6 ArrayList (java.util.ArrayList)6 ParseException (java.text.ParseException)5 ICalTimeZone (com.zimbra.common.calendar.ICalTimeZone)4 ParsedDuration (com.zimbra.common.calendar.ParsedDuration)4 Account (com.zimbra.cs.account.Account)4 Invite (com.zimbra.cs.mailbox.calendar.Invite)4 ItemId (com.zimbra.cs.service.util.ItemId)4 MimeMessage (javax.mail.internet.MimeMessage)4 ZVCalendar (com.zimbra.common.calendar.ZCalendar.ZVCalendar)3 NoSuchItemException (com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException)3 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)3 IRecurrence (com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence)3 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)3 AccountAddressMatcher (com.zimbra.cs.util.AccountUtil.AccountAddressMatcher)3