use of com.zimbra.cs.mailbox.Mailbox.AddInviteData in project zm-mailbox by Zimbra.
the class ImportAppointments method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Mailbox mbox = getRequestedMailbox(zsc);
OperationContext octxt = getOperationContext(zsc, context);
String folder = request.getAttribute(MailConstants.A_FOLDER, DEFAULT_FOLDER_ID);
ItemId iidFolder = new ItemId(folder, zsc);
String ct = request.getAttribute(MailConstants.A_CONTENT_TYPE);
if (!ct.equalsIgnoreCase("ics") && !ct.equalsIgnoreCase(MimeConstants.CT_TEXT_CALENDAR))
throw ServiceException.INVALID_REQUEST("unsupported content type: " + ct, null);
Element content = request.getElement(MailConstants.E_CONTENT);
List<Upload> uploads = null;
InputStream is = null;
String partStr = null;
String attachment = content.getAttribute(MailConstants.A_ATTACHMENT_ID, null);
String messageId = content.getAttribute(MailConstants.A_MESSAGE_ID, null);
if (attachment != null && messageId != null) {
throw ServiceException.INVALID_REQUEST("use either aid or mid but not both", null);
}
SourceSpecMethod sourceSpecMethod;
if (messageId != null) {
sourceSpecMethod = SourceSpecMethod.MSG_PART;
} else if (attachment != null) {
sourceSpecMethod = SourceSpecMethod.ATTACH_ID;
} else {
sourceSpecMethod = SourceSpecMethod.INLINE_TEXT;
}
try {
if (SourceSpecMethod.MSG_PART.equals(sourceSpecMethod)) {
// Get content from part of existing message.
ItemId iid = new ItemId(messageId, zsc);
String part = content.getAttribute(MailConstants.A_PART);
partStr = CreateContact.fetchItemPart(zsc, octxt, mbox, iid, part, null, MimeConstants.P_CHARSET_UTF8);
is = new ByteArrayInputStream(partStr.getBytes(MimeConstants.P_CHARSET_UTF8));
}
// if the "target item" is remote, we will need to proxy the request
ItemId iidTarget = getProxyTarget(zsc, octxt, iidFolder, true);
if (iidTarget != null) {
ZimbraLog.misc.info("Proxying ImportAppointments - folder=" + folder + ", acct=" + iidTarget.getAccountId());
if (SourceSpecMethod.MSG_PART.equals(sourceSpecMethod)) {
/* Bug 77131 change specification method to something that will work in the proxied context */
content.addAttribute(MailConstants.A_MESSAGE_ID, (String) null);
content.addAttribute(MailConstants.A_PART, (String) null);
Upload up = FileUploadServlet.saveUpload(is, "upload.ics", MimeConstants.CT_TEXT_CALENDAR, zsc.getAuthtokenAccountId());
content.addAttribute(MailConstants.A_ATTACHMENT_ID, up.getId());
}
return proxyRequest(request, context, iidFolder, iidTarget);
}
if (SourceSpecMethod.ATTACH_ID.equals(sourceSpecMethod)) {
is = parseUploadedContent(zsc, attachment, uploads = new ArrayList<Upload>());
} else if (SourceSpecMethod.INLINE_TEXT.equals(sourceSpecMethod)) {
// Convert LF to CRLF because the XML parser normalizes element text to LF.
String text = StringUtil.lfToCrlf(content.getText());
is = new ByteArrayInputStream(text.getBytes(MimeConstants.P_CHARSET_UTF8));
}
List<ZVCalendar> icals = ZCalendarBuilder.buildMulti(is, MimeConstants.P_CHARSET_UTF8);
is.close();
is = null;
List<Invite> invites = Invite.createFromCalendar(mbox.getAccount(), null, icals, true, true, null);
Set<String> uidsSeen = new HashSet<String>();
StringBuilder ids = new StringBuilder();
for (Invite inv : invites) {
// handle missing UIDs on remote calendars by generating them as needed
String uid = inv.getUid();
if (uid == null) {
uid = LdapUtil.generateUUID();
inv.setUid(uid);
}
boolean addRevision;
if (!uidsSeen.contains(uid)) {
addRevision = true;
uidsSeen.add(uid);
} else {
addRevision = false;
}
// and add the invite to the calendar!
try {
AddInviteData aid = mbox.addInvite(octxt, inv, iidFolder.getId(), false, addRevision);
if (aid != null) {
if (ids.length() > 0)
ids.append(",");
ids.append(aid.calItemId).append("-").append(aid.invId);
}
} catch (ServiceException e) {
ZimbraLog.calendar.warn("Skipping bad iCalendar object during import: uid=" + inv.getUid(), e);
}
}
Element response = zsc.createElement(MailConstants.IMPORT_APPOINTMENTS_RESPONSE);
Element cn = response.addElement(MailConstants.E_APPOINTMENT);
cn.addAttribute(MailConstants.A_IDS, ids.toString());
cn.addAttribute(MailConstants.A_NUM, invites.size());
return response;
} catch (IOException e) {
throw MailServiceException.UNABLE_TO_IMPORT_APPOINTMENTS(e.getMessage(), e);
} finally {
if (is != null)
try {
is.close();
} catch (IOException e) {
}
if (attachment != null)
FileUploadServlet.deleteUploads(uploads);
}
}
use of com.zimbra.cs.mailbox.Mailbox.AddInviteData in project zm-mailbox by Zimbra.
the class GetMsgTest method testHandle.
@Test
public void testHandle() throws Exception {
Account acct1 = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com");
Account acct2 = Provisioning.getInstance().get(Key.AccountBy.name, "test2@zimbra.com");
Mailbox mbox1 = MailboxManager.getInstance().getMailboxByAccount(acct1);
Folder calendarFolder = mbox1.getCalendarFolders(null, SortBy.NONE).get(0);
String fragment = "Some message";
ZVCalendar calendar = new ZVCalendar();
calendar.addDescription(desc, null);
ZComponent comp = new ZComponent("VEVENT");
calendar.addComponent(comp);
Invite invite = MailboxTestUtil.generateInvite(acct1, fragment, calendar);
ICalTimeZone ical = invite.getTimeZoneMap().getLocalTimeZone();
long utc = 5 * 60 * 60 * 1000;
ParsedDateTime s = ParsedDateTime.fromUTCTime(System.currentTimeMillis() + utc, ical);
ParsedDateTime e = ParsedDateTime.fromUTCTime(System.currentTimeMillis() + (30 * 60 * 1000) + utc, ical);
invite.setDtStart(s);
invite.setDtEnd(e);
invite.setPriority("5");
invite.setClassProp("PRI");
invite.setOrganizer(new ZOrganizer("test@zimbra.com", null));
invite.setUid(UUID.randomUUID().toString());
invite.setMethod("REQUEST");
invite.setName("Testing");
invite.setFreeBusy("B");
invite.setIsOrganizer(true);
invite.setItemType(MailItem.Type.APPOINTMENT);
invite.setUid(UUID.randomUUID().toString());
AddInviteData inviteData = mbox1.addInvite(null, invite, calendarFolder.getId());
calendarFolder = mbox1.getCalendarFolders(null, SortBy.NONE).get(0);
Element request = new Element.XMLElement("GetCalendarItem");
Element action = request.addElement(MailConstants.E_MSG);
action.addAttribute(MailConstants.A_ID, acct1.getId() + ":" + inviteData.calItemId + "-" + inviteData.invId);
action.addAttribute(MailConstants.A_WANT_HTML, "1");
action.addAttribute(MailConstants.A_NEED_EXP, "1");
Element response = new GetMsg().handle(request, ServiceTestUtil.getRequestContext(acct1));
Element organizer = response.getElement("m").getElement("inv").getElement("comp").getElement("or");
String organizerString = organizer.prettyPrint();
assertTrue(organizerString.contains("a=\"test@zimbra.com\" url=\"test@zimbra.com\""));
mbox1.grantAccess(null, 10, acct2.getId(), ACL.GRANTEE_USER, ACL.RIGHT_READ, null);
request = new Element.XMLElement("CreateMountPoint");
Element link = request.addElement("link");
link.addAttribute("f", "#");
link.addAttribute("reminder", 0);
link.addAttribute("name", "sharedcal");
link.addAttribute("path", "/Calendar");
link.addAttribute("owner", "test@zimbra.com");
link.addAttribute("l", 10);
link.addAttribute("view", "appoinment");
response = new CreateMountpoint().handle(request, ServiceTestUtil.getRequestContext(acct2));
String mptId = response.getElement("link").getAttribute("id");
request = new Element.XMLElement("GetMsgRequest");
action = request.addElement(MailConstants.E_MSG);
action.addAttribute(MailConstants.A_ID, acct1.getId() + ":" + inviteData.calItemId + "-" + mptId);
action.addAttribute(MailConstants.A_WANT_HTML, "1");
action.addAttribute(MailConstants.A_NEED_EXP, "1");
response = new GetMsg().handle(request, ServiceTestUtil.getRequestContext(acct2, acct1));
organizerString = response.getElement("m").prettyPrint();
assertTrue(!organizerString.contains("a=\"test@zimbra.com\" url=\"test@zimbra.com\""));
request = new Element.XMLElement("FolderAction");
action = request.addElement("action");
action.addAttribute("id", mptId);
action.addAttribute("op", "delete");
response = new FolderAction().handle(request, ServiceTestUtil.getRequestContext(acct2));
mbox1.revokeAccess(null, 10, acct2.getId());
}
use of com.zimbra.cs.mailbox.Mailbox.AddInviteData in project zm-mailbox by Zimbra.
the class CalendarRequest method sendCalendarMessageInternal.
/**
* Send an iCalendar email message and optionally create/update/cancel
* corresponding appointment/invite in sender's calendar.
* @param zsc
* @param apptFolderId
* @param acct
* @param mbox
* @param csd
* @param response
* @param updateOwnAppointment if true, corresponding change is made to
* sender's calendar
* @return
* @throws ServiceException
*/
private static Element sendCalendarMessageInternal(ZimbraSoapContext zsc, OperationContext octxt, int apptFolderId, Account acct, Mailbox mbox, CalSendData csd, Element response, boolean updateOwnAppointment, boolean forceSend, MailSendQueue sendQueue) throws ServiceException {
boolean onBehalfOf = isOnBehalfOfRequest(zsc);
boolean notifyOwner = onBehalfOf && acct.getBooleanAttr(Provisioning.A_zimbraPrefCalendarNotifyDelegatedChanges, false);
if (notifyOwner) {
try {
InternetAddress addr = AccountUtil.getFriendlyEmailAddress(acct);
csd.mMm.addRecipient(javax.mail.Message.RecipientType.TO, addr);
} catch (MessagingException e) {
throw ServiceException.FAILURE("count not add calendar owner to recipient list", e);
}
}
// in a non-delegated request.
if (!onBehalfOf) {
String[] aliases = acct.getMailAlias();
String[] addrs;
if (aliases != null && aliases.length > 0) {
addrs = new String[aliases.length + 1];
addrs[0] = acct.getAttr(Provisioning.A_mail);
for (int i = 0; i < aliases.length; i++) {
addrs[i + 1] = aliases[i];
}
} else {
addrs = new String[1];
addrs[0] = acct.getAttr(Provisioning.A_mail);
}
try {
Mime.removeRecipients(csd.mMm, addrs);
} catch (MessagingException e) {
}
}
ParsedMessage pm = new ParsedMessage(csd.mMm, false);
if (csd.mInvite.getFragment() == null || csd.mInvite.getFragment().equals("")) {
csd.mInvite.setFragment(pm.getFragment(acct.getLocale()));
}
boolean willNotify = false;
if (!csd.mDontNotifyAttendees) {
try {
Address[] rcpts = csd.mMm.getAllRecipients();
willNotify = rcpts != null && rcpts.length > 0;
} catch (MessagingException e) {
throw ServiceException.FAILURE("Checking recipients of outgoing msg ", e);
}
}
// Validate the addresses first.
if (!csd.mInvite.isCancel() && !forceSend && willNotify) {
try {
MailUtil.validateRcptAddresses(JMSession.getSmtpSession(mbox.getAccount()), csd.mMm.getAllRecipients());
} catch (MessagingException mex) {
if (mex instanceof SendFailedException) {
SendFailedException sfex = (SendFailedException) mex;
throw MailServiceException.SEND_ABORTED_ADDRESS_FAILURE("invalid addresses", sfex, sfex.getInvalidAddresses(), sfex.getValidUnsentAddresses());
}
}
}
AddInviteData aid = null;
File tempMmFile = null;
boolean queued = false;
try {
if (willNotify) {
// Write out the MimeMessage to a temp file and create a new MimeMessage from the file.
// If we don't do this, we get into trouble during modify appointment call. If the blob
// is bigger than the streaming threshold (e.g. appointment has a big attachment), the
// MimeMessage object is attached to the current blob file. But the Mailbox.addInvite()
// call below updates the blob to a new mod_content (hence new path). The attached blob
// thus having been deleted, the MainSender.sendMimeMessage() call that follows will attempt
// to read from a non-existent file and fail. We can avoid this situation by writing the
// to-be-emailed mime message to a temp file, thus detaching it from the appointment's
// current blob file. This is inefficient, but safe.
OutputStream os = null;
InputStream is = null;
try {
tempMmFile = File.createTempFile("zcal", "tmp");
os = new FileOutputStream(tempMmFile);
csd.mMm.writeTo(os);
ByteUtil.closeStream(os);
os = null;
is = new ZSharedFileInputStream(tempMmFile);
csd.mMm = new FixedMimeMessage(JMSession.getSmtpSession(acct), is);
} catch (IOException e) {
if (tempMmFile != null)
tempMmFile.delete();
throw ServiceException.FAILURE("error creating calendar message content", e);
} catch (MessagingException e) {
if (tempMmFile != null)
tempMmFile.delete();
throw ServiceException.FAILURE("error creating calendar message content", e);
} finally {
ByteUtil.closeStream(os);
ByteUtil.closeStream(is);
}
}
// because email send will delete uploaded attachments as a side-effect.
if (updateOwnAppointment) {
aid = mbox.addInvite(octxt, csd.mInvite, apptFolderId, pm);
}
// Next, notify any attendees.
if (willNotify) {
MailSendQueueEntry entry = new MailSendQueueEntry(octxt, mbox, csd, tempMmFile);
sendQueue.add(entry);
queued = true;
}
} finally {
// Delete the temp file if it wasn't queued.
if (tempMmFile != null && !queued) {
tempMmFile.delete();
}
}
if (updateOwnAppointment && response != null && aid != null) {
csd.mAddInvData = aid;
ItemIdFormatter ifmt = new ItemIdFormatter(zsc);
String id = ifmt.formatItemId(aid.calItemId);
response.addAttribute(MailConstants.A_CAL_ID, id);
if (csd.mInvite.isEvent())
// for backward compat
response.addAttribute(MailConstants.A_APPT_ID_DEPRECATE_ME, id);
response.addAttribute(MailConstants.A_CAL_INV_ID, ifmt.formatItemId(aid.calItemId, aid.invId));
if (Invite.isOrganizerMethod(csd.mInvite.getMethod())) {
response.addAttribute(MailConstants.A_MODIFIED_SEQUENCE, aid.modSeq);
response.addAttribute(MailConstants.A_REVISION, aid.rev);
}
}
return response;
}
use of com.zimbra.cs.mailbox.Mailbox.AddInviteData in project zm-mailbox by Zimbra.
the class AddCalendarItemInvite method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Account acct = getRequestedAccount(zsc);
Mailbox mbox = getRequestedMailbox(zsc);
OperationContext octxt = getOperationContext(zsc, context);
AddInviteParser parser = new AddInviteParser();
SetCalendarItemData scid = SetCalendarItem.getSetCalendarItemData(zsc, octxt, acct, mbox, request, parser);
Invite inv = scid.invite;
CalendarItem calItem = mbox.getCalendarItemByUid(octxt, inv.getUid());
int folderId = inv.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
if (calItem != null) {
int f = calItem.getFolderId();
if (f != Mailbox.ID_FOLDER_TRASH && f != Mailbox.ID_FOLDER_SPAM)
folderId = f;
}
// it's the correct organizer.
if (!inv.hasOrganizer() && inv.hasOtherAttendees()) {
if (scid.message == null) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
inv.clearAttendees();
} else {
String fromEmail = scid.message.getSenderEmail(true);
if (fromEmail != null) {
boolean dangerousSender = false;
// Is sender == recipient? If so, clear attendees.
String intendedForAddress;
try {
intendedForAddress = scid.message.getMimeMessage().getHeader(CalendarMailSender.X_ZIMBRA_CALENDAR_INTENDED_FOR, null);
} catch (MessagingException e) {
throw ServiceException.FAILURE("error parsing message", e);
}
if (intendedForAddress != null && intendedForAddress.length() > 0) {
if (intendedForAddress.equalsIgnoreCase(fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
inv.clearAttendees();
dangerousSender = true;
}
} else if (AccountUtil.addressMatchesAccount(acct, fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
inv.clearAttendees();
dangerousSender = true;
}
if (!dangerousSender) {
ZOrganizer org = new ZOrganizer(fromEmail, null);
String senderEmail = scid.message.getSenderEmail(false);
if (senderEmail != null && !senderEmail.equalsIgnoreCase(fromEmail))
org.setSentBy(senderEmail);
inv.setOrganizer(org);
ZimbraLog.calendar.info("Got malformed invite that lists attendees without specifying an organizer. " + "Defaulting organizer to: " + org.toString());
}
}
}
}
// trace logging
String calItemIdStr = calItem != null ? Integer.toString(calItem.getId()) : "(new)";
if (!inv.hasRecurId())
ZimbraLog.calendar.info("<AddCalendarItemInvite> id=%s, folderId=%d, subject=\"%s\", UID=%s", calItemIdStr, folderId, inv.isPublic() ? inv.getName() : "(private)", inv.getUid());
else
ZimbraLog.calendar.info("<AddCalendarItemInvite> id=%s, folderId=%d, subject=\"%s\", UID=%s, recurId=%s", calItemIdStr, folderId, inv.isPublic() ? inv.getName() : "(private)", inv.getUid(), inv.getRecurId().getDtZ());
Element response = getResponseElement(zsc);
if (calItem != null) {
// If the calendar item already has the invite, no need to add again.
RecurId rid = scid.invite.getRecurId();
Invite matchingInv = calItem.getInvite(rid);
if (matchingInv != null && matchingInv.isSameOrNewerVersion(scid.invite)) {
response.addAttribute(MailConstants.A_CAL_ID, calItem.getId());
response.addAttribute(MailConstants.A_CAL_INV_ID, matchingInv.getMailItemId());
response.addAttribute(MailConstants.A_CAL_COMPONENT_NUM, matchingInv.getComponentNum());
return response;
}
}
AddInviteData aid = mbox.addInvite(octxt, inv, folderId, scid.message, false, false, true);
if (aid != null) {
calItem = mbox.getCalendarItemById(octxt, aid.calItemId);
if (calItem != null) {
Invite[] invs = calItem.getInvites(aid.invId);
if (invs != null && invs.length > 0) {
response.addAttribute(MailConstants.A_CAL_ID, aid.calItemId);
response.addAttribute(MailConstants.A_CAL_INV_ID, aid.invId);
response.addAttribute(MailConstants.A_CAL_COMPONENT_NUM, invs[0].getComponentNum());
}
}
}
return response;
}
use of com.zimbra.cs.mailbox.Mailbox.AddInviteData in project zm-mailbox by Zimbra.
the class SendInviteReply method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Mailbox mbox = getRequestedMailbox(zsc);
Account acct = getRequestedAccount(zsc);
Account authAcct = getAuthenticatedAccount(zsc);
boolean isAdmin = zsc.isUsingAdminPrivileges();
OperationContext octxt = getOperationContext(zsc, context);
boolean onBehalfOf = isOnBehalfOfRequest(zsc);
ItemId iid = new ItemId(request.getAttribute(MailConstants.A_ID), zsc);
int compNum = (int) request.getAttributeLong(MailConstants.A_CAL_COMPONENT_NUM);
String verbStr = request.getAttribute(MailConstants.A_VERB);
Verb verb = CalendarMailSender.parseVerb(verbStr);
boolean isDecline = CalendarMailSender.VERB_DECLINE.equals(verb);
boolean updateOrg = request.getAttributeBool(MailConstants.A_CAL_UPDATE_ORGANIZER, true);
// Get the identity/persona being used in the reply. It is set at the request level, but
// let's also look for it in the <m> child element too, because that is the precedent in
// the SendMsg request. For SendInviteReply we have to insist it at request level because
// <m> is an optional element.
String identityId = request.getAttribute(MailConstants.A_IDENTITY_ID, null);
if (identityId == null) {
Element msgElem = request.getOptionalElement(MailConstants.E_MSG);
if (msgElem != null)
identityId = msgElem.getAttribute(MailConstants.A_IDENTITY_ID, null);
}
Element response = getResponseElement(zsc);
boolean intendedForMe = true;
Invite oldInv = null;
int calItemId;
int inviteMsgId;
CalendarItem calItem = null;
boolean wasInTrash = false;
// calendar item (id="aaaa-nnnn") --- work in both cases
if (iid.hasSubpart()) {
// directly accepting the calendar item
calItemId = iid.getId();
inviteMsgId = iid.getSubpartId();
calItem = safeGetCalendarItemById(mbox, octxt, iid.getId());
if (calItem == null)
throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
oldInv = calItem.getInvite(inviteMsgId, compNum);
} else {
// accepting the message: go find the calendar item and then the invite
inviteMsgId = iid.getId();
Message msg = mbox.getMessageById(octxt, inviteMsgId);
Message.CalendarItemInfo info = msg.getCalendarItemInfo(compNum);
if (info == null)
throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
String intendedFor = msg.getCalendarIntendedFor();
Account intendedAcct = null;
if (intendedFor != null) {
try {
InternetAddress intendedForAddr = new JavaMailInternetAddress(intendedFor);
intendedAcct = Provisioning.getInstance().get(AccountBy.name, intendedForAddr.getAddress());
} catch (AddressException e) {
throw ServiceException.INVALID_REQUEST("The intended account " + intendedFor + " is invalid", e);
}
if (intendedAcct == null) {
throw ServiceException.INVALID_REQUEST("The intended account " + intendedFor + " was not found", null);
}
// Special case: intended account = me.
if (intendedAcct.equals(mbox.getAccount()))
intendedAcct = null;
else
intendedForMe = false;
}
if (intendedAcct != null) {
// trace logging: let's just indicate we're replying to a remote appointment
ZimbraLog.calendar.info("<SendInviteReply> (remote mbox) id=%s, verb=%s, notifyOrg=%s", new ItemIdFormatter(zsc).formatItemId(iid), verb.toString(), Boolean.toString(updateOrg));
// Replying to a remote appointment
calItem = null;
calItemId = 0;
ZMailbox zmbx = getRemoteZMailbox(octxt, authAcct, intendedAcct);
// Try to add the appointment to remote mailbox.
AddInviteResult addInviteResult = sendAddInvite(zmbx, octxt, msg);
if (addInviteResult == null)
throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
// Forward the reply request.
remoteSendInviteReply(zmbx, request, addInviteResult);
} else {
// Replying to a local appointment
if (info.getInvite() != null) {
calItem = mbox.getCalendarItemByUid(octxt, info.getInvite().getUid());
wasInTrash = calItem != null && calItem.inTrash();
if (calItem != null && !wasInTrash) {
Invite newInv = info.getInvite();
// If appointment exists, check if our invite has been outdated.
Invite curr = calItem.getInvite(newInv.getRecurId());
if (curr != null && !newInv.isSameOrNewerVersion(curr))
throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
}
Invite inv = info.getInvite().newCopy();
Invite.setDefaultAlarm(inv, acct);
inv.setMailItemId(inviteMsgId);
// (TODO: Is it better to delete the existing appointment/instance when declining?)
if (calItem != null || !isDecline) {
// Add the invite. This will either create or update the appointment.
int folder;
boolean untrashing = wasInTrash && !isDecline;
if (calItem == null || untrashing) {
// If appointment/task doesn't exist, create in default folder.
// If it exists but is in Trash and is not a decline, move it out of Trash.
// If it's in trash and we're declining, leave it in trash.
folder = inv.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
} else {
folder = calItem.getFolderId();
}
ParsedMessage pm = new ParsedMessage(msg.getMimeMessage(false), false);
AddInviteData aid = mbox.addInvite(octxt, inv, folder, pm, false, untrashing, true);
if (aid == null)
throw ServiceException.FAILURE("Could not create/update calendar item", null);
calItemId = aid.calItemId;
// Refetch updated item.
calItem = safeGetCalendarItemById(mbox, octxt, aid.calItemId);
if (calItem == null)
throw ServiceException.FAILURE("Could not refetch created/updated calendar item", null);
} else {
calItemId = 0;
}
oldInv = inv;
} else if (info.calItemCreated()) {
// legacy case (before we added Invite info to Message metadata)
calItem = safeGetCalendarItemById(mbox, octxt, info.getCalendarItemId());
if (calItem == null)
throw ServiceException.FAILURE("Missing invite data", null);
// Must be for this mailbox
calItemId = info.getCalendarItemId().getId();
wasInTrash = calItem.inTrash();
oldInv = calItem.getInvite(inviteMsgId, compNum);
} else {
throw ServiceException.FAILURE("Missing invite data", null);
}
}
}
if (intendedForMe) {
if (oldInv == null)
throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
if (calItem != null && (mbox.getEffectivePermissions(octxt, calItemId, MailItem.Type.UNKNOWN) & ACL.RIGHT_ACTION) == 0) {
throw ServiceException.PERM_DENIED("You do not have ACTION rights for CalendarItem " + calItemId);
}
// check if invite organizer requested rsvp or not
updateOrg = updateOrg && oldInv.getRsvp();
// Don't allow creating/editing a private appointment on behalf of another user,
// unless that other user is a calendar resource.
boolean allowPrivateAccess = calItem != null ? calItem.allowPrivateAccess(authAcct, isAdmin) : true;
boolean isCalendarResource = acct instanceof CalendarResource;
if (!allowPrivateAccess && !oldInv.isPublic() && !isCalendarResource)
throw ServiceException.PERM_DENIED("Cannot reply to a private appointment/task on behalf of another user");
// see if there is a specific Exception being referenced by this reply...
Element exc = request.getOptionalElement(MailConstants.E_CAL_EXCEPTION_ID);
ParsedDateTime exceptDt = null;
if (exc != null) {
TimeZoneMap tzmap = oldInv.getTimeZoneMap();
Element tzElem = request.getOptionalElement(MailConstants.E_CAL_TZ);
ICalTimeZone tz = null;
if (tzElem != null) {
tz = CalendarUtils.parseTzElement(tzElem);
tzmap.add(tz);
}
exceptDt = CalendarUtils.parseDateTime(exc, tzmap);
} else if (oldInv.hasRecurId()) {
exceptDt = oldInv.getRecurId().getDt();
}
// trace logging
String calItemIdStr = calItem != null ? Integer.toString(calItem.getId()) : "none";
String folderIdStr = calItem != null ? Integer.toString(calItem.getFolderId()) : "none";
if (exceptDt == null)
ZimbraLog.calendar.info("<SendInviteReply> id=%s, folderId=%s, verb=%s, notifyOrg=%s, subject=\"%s\", UID=%s", calItemIdStr, folderIdStr, verb.toString(), Boolean.toString(updateOrg), oldInv.isPublic() ? oldInv.getName() : "(private)", oldInv.getUid());
else
ZimbraLog.calendar.info("<SendInviteReply> id=%s, folderId=%s, verb=%s, notifyOrg=%s, subject=\"%s\", UID=%s, recurId=%s", calItemIdStr, folderIdStr, verb.toString(), Boolean.toString(updateOrg), oldInv.isPublic() ? oldInv.getName() : "(private)", oldInv.getUid(), exceptDt.getUtcString());
// exception instance first. Then reply to it.
if (calItem != null && oldInv.isRecurrence() && exceptDt != null) {
Invite localException = oldInv.makeInstanceInvite(exceptDt);
long now = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
localException.setDtStamp(now);
String partStat = verb.getXmlPartStat();
localException.setPartStat(partStat);
ZAttendee at = localException.getMatchingAttendee(acct, identityId);
if (at != null)
at.setPartStat(partStat);
// Carry over the MimeMessage/ParsedMessage to preserve any attachments.
MimeMessage mmInv = calItem.getSubpartMessage(oldInv.getMailItemId());
ParsedMessage pm = mmInv != null ? new ParsedMessage(mmInv, false) : null;
int folder;
boolean untrashing = wasInTrash && !isDecline;
if (untrashing) {
// If it exists but is in Trash and is not a decline, move it out of Trash.
// If it's in trash and we're declining, leave it in trash.
folder = localException.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
} else {
folder = calItem.getFolderId();
}
mbox.addInvite(octxt, localException, folder, pm, true, untrashing, true);
// Refetch the updated calendar item and set oldInv to refetched local exception instance.
calItem = safeGetCalendarItemById(mbox, octxt, calItemId);
if (calItem == null)
throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
oldInv = calItem.getInvite(new RecurId(exceptDt, RecurId.RANGE_NONE));
}
if (updateOrg && oldInv.hasOrganizer()) {
Locale locale;
Account organizer = oldInv.getOrganizerAccount();
if (organizer != null)
locale = organizer.getLocale();
else
locale = !onBehalfOf ? acct.getLocale() : authAcct.getLocale();
String subject;
if (!allowPrivateAccess && !oldInv.isPublic())
subject = L10nUtil.getMessage(MsgKey.calendarSubjectWithheld, locale);
else
subject = oldInv.getName();
String replySubject = CalendarMailSender.getReplySubject(verb, subject, locale);
CalSendData csd = new CalSendData();
csd.mOrigId = new ItemId(mbox, oldInv.getMailItemId());
csd.mReplyType = MailSender.MSGTYPE_REPLY;
csd.mInvite = CalendarMailSender.replyToInvite(acct, identityId, authAcct, onBehalfOf, allowPrivateAccess, oldInv, verb, replySubject, exceptDt);
ZVCalendar iCal = csd.mInvite.newToICalendar(true);
ParseMimeMessage.MimeMessageData parsedMessageData = new ParseMimeMessage.MimeMessageData();
// did they specify a custom <m> message? If so, then we don't have to build one...
Element msgElem = request.getOptionalElement(MailConstants.E_MSG);
if (msgElem != null) {
String text = ParseMimeMessage.getTextPlainContent(msgElem);
String html = ParseMimeMessage.getTextHtmlContent(msgElem);
iCal.addDescription(text, html);
MimeBodyPart[] mbps = new MimeBodyPart[1];
mbps[0] = CalendarMailSender.makeICalIntoMimePart(iCal);
// the <inv> element is *NOT* allowed -- we always build it manually
// based on the params to the <SendInviteReply> and stick it in the
// mbps (additionalParts) parameter...
csd.mMm = ParseMimeMessage.parseMimeMsgSoap(zsc, octxt, mbox, msgElem, mbps, ParseMimeMessage.NO_INV_ALLOWED_PARSER, parsedMessageData);
} else {
// build a default "Accepted" response
if (!(acct instanceof CalendarResource)) {
csd.mMm = CalendarMailSender.createDefaultReply(acct, identityId, authAcct, identityId, isAdmin, onBehalfOf, calItem, oldInv, null, replySubject, verb, null, iCal);
} else {
// different template for calendar resources
RecurId rid = oldInv.getRecurId();
ParsedDateTime ridDt = rid != null ? rid.getDt() : null;
Invite replyInv = CalendarMailSender.replyToInvite(acct, authAcct, onBehalfOf, allowPrivateAccess, oldInv, verb, replySubject, ridDt);
MimeMessage mmInv = calItem.getSubpartMessage(oldInv.getMailItemId());
csd.mMm = CalendarMailSender.createResourceAutoReply(octxt, identityId, identityId, mbox, verb, false, null, calItem, oldInv, new Invite[] { replyInv }, mmInv, true);
}
}
int apptFolderId;
if (calItem != null)
apptFolderId = calItem.getFolderId();
else
apptFolderId = oldInv.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
MailSendQueue sendQueue = new MailSendQueue();
try {
sendCalendarMessage(zsc, octxt, apptFolderId, acct, mbox, csd, response, sendQueue);
} finally {
sendQueue.send();
}
}
RecurId recurId = null;
if (exceptDt != null) {
recurId = new RecurId(exceptDt, RecurId.RANGE_NONE);
}
ZAttendee me = oldInv.getMatchingAttendee(acct);
String cnStr = null;
String addressStr = acct.getName();
String role = IcalXmlStrMap.ROLE_OPT_PARTICIPANT;
int seqNo = oldInv.getSeqNo();
long dtStamp = oldInv.getDTStamp();
if (me != null) {
if (me.hasCn()) {
cnStr = me.getCn();
}
addressStr = me.getAddress();
if (me.hasRole()) {
role = me.getRole();
}
}
if (calItem != null)
mbox.modifyPartStat(octxt, calItemId, recurId, cnStr, addressStr, null, role, verb.getXmlPartStat(), Boolean.FALSE, seqNo, dtStamp);
}
// move the invite to the Trash if the user wants it
if (deleteInviteOnReply(acct)) {
try {
if (onBehalfOf) {
// HACK: Run the move in the context of the organizer
// mailbox because the authenticated account doesn't
// have rights on Inbox and Trash folders.
octxt = new OperationContext(mbox);
}
mbox.move(octxt, inviteMsgId, MailItem.Type.MESSAGE, Mailbox.ID_FOLDER_TRASH);
} catch (MailServiceException.NoSuchItemException nsie) {
ZimbraLog.calendar.debug("can't move nonexistent invite to Trash: " + inviteMsgId);
}
}
return response;
}
Aggregations