diff options
Diffstat (limited to 'comm/calendar/base/modules/utils/calProviderUtils.jsm')
-rw-r--r-- | comm/calendar/base/modules/utils/calProviderUtils.jsm | 907 |
1 files changed, 907 insertions, 0 deletions
diff --git a/comm/calendar/base/modules/utils/calProviderUtils.jsm b/comm/calendar/base/modules/utils/calProviderUtils.jsm new file mode 100644 index 0000000000..5e78aad37b --- /dev/null +++ b/comm/calendar/base/modules/utils/calProviderUtils.jsm @@ -0,0 +1,907 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); +var { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); + +/** + * Helpers and base class for calendar providers + */ + +// NOTE: This module should not be loaded directly, it is available when +// including calUtils.jsm under the cal.provider namespace. + +const EXPORTED_SYMBOLS = ["calprovider"]; + +const lazy = {}; +XPCOMUtils.defineLazyModuleGetters(lazy, { + cal: "resource:///modules/calendar/calUtils.jsm", + CalPeriod: "resource:///modules/CalPeriod.jsm", + CalReadableStreamFactory: "resource:///modules/CalReadableStreamFactory.jsm", +}); + +var calprovider = { + /** + * Prepare HTTP channel with standard request headers and upload data/content-type if needed. + * + * @param {nsIURI} aUri - The channel URI, only used for a new channel. + * @param {nsIInputStream | string} aUploadData - Data to be uploaded, if any. If a string, + * it will be converted to an nsIInputStream. + * @param {string} aContentType - Value for Content-Type header, if any. + * @param {nsIInterfaceRequestor} aNotificationCallbacks - Typically a CalDavRequestBase which + * implements nsIInterfaceRequestor and nsIChannelEventSink, and provides access to the + * calICalendar associated with the channel. + * @param {nsIChannel} [aExistingChannel] - An existing channel to modify (optional). + * @param {boolean} [aForceNewAuth=false] - If true, use a new user context to avoid cached + * authentication (see code comments). Optional, ignored if aExistingChannel is passed. + * @returns {nsIChannel} - The prepared channel. + */ + prepHttpChannel( + aUri, + aUploadData, + aContentType, + aNotificationCallbacks, + aExistingChannel = null, + aForceNewAuth = false + ) { + let originAttributes = {}; + + // The current nsIHttpChannel implementation separates connections only + // by hosts, which causes issues with cookies and password caching for + // two or more simultaneous connections to the same host and different + // authenticated users. This can be solved by providing the additional + // userContextId, which also separates connections (a.k.a. containers). + // Connections for userA @ server1 and userA @ server2 can exist in the + // same container, as nsIHttpChannel will separate them. Connections + // for userA @ server1 and userB @ server1 however must be placed into + // different containers. It is therefore sufficient to add individual + // userContextIds per username. + + if (aForceNewAuth) { + // A random "username" that won't be the same as any existing one. + // The value is not used for any other reason, so a UUID will do. + originAttributes.userContextId = lazy.cal.auth.containerMap.getUserContextIdForUsername( + lazy.cal.getUUID() + ); + } else if (!aExistingChannel) { + try { + // Use a try/catch because there may not be a calICalendar interface. + // For example, when there is no calendar associated with a request, + // as in calendar detection. + let calendar = aNotificationCallbacks.getInterface(Ci.calICalendar); + if (calendar && calendar.getProperty("capabilities.username.supported") === true) { + originAttributes.userContextId = lazy.cal.auth.containerMap.getUserContextIdForUsername( + calendar.getProperty("username") + ); + } + } catch (e) { + if (e.result != Cr.NS_ERROR_NO_INTERFACE) { + throw e; + } + } + } + + // We cannot use a system principal here since the connection setup will fail if + // same-site cookie protection is enabled in TB and server-side. + let principal = aExistingChannel + ? null + : Services.scriptSecurityManager.createContentPrincipal(aUri, originAttributes); + let channel = + aExistingChannel || + Services.io.newChannelFromURI( + aUri, + null, + principal, + null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER + ); + let httpchannel = channel.QueryInterface(Ci.nsIHttpChannel); + + httpchannel.setRequestHeader("Accept", "text/xml", false); + httpchannel.setRequestHeader("Accept-Charset", "utf-8,*;q=0.1", false); + httpchannel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; + httpchannel.notificationCallbacks = aNotificationCallbacks; + + if (aUploadData) { + httpchannel = httpchannel.QueryInterface(Ci.nsIUploadChannel); + let stream; + if (aUploadData instanceof Ci.nsIInputStream) { + // Make sure the stream is reset + stream = aUploadData.QueryInterface(Ci.nsISeekableStream); + stream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + } else { + // Otherwise its something that should be a string, convert it. + stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setUTF8Data(aUploadData, aUploadData.length); + } + + httpchannel.setUploadStream(stream, aContentType, -1); + } + + return httpchannel; + }, + + /** + * Send prepared HTTP request asynchronously + * + * @param {nsIStreamLoader} aStreamLoader - Stream loader for request + * @param {nsIChannel} aChannel - Channel for request + * @param {nsIStreamLoaderObserver} aListener - Listener for method completion + */ + sendHttpRequest(aStreamLoader, aChannel, aListener) { + aStreamLoader.init(aListener); + aChannel.asyncOpen(aStreamLoader); + }, + + /** + * Shortcut to create an nsIStreamLoader + * + * @returns {nsIStreamLoader} A fresh streamloader + */ + createStreamLoader() { + return Cc["@mozilla.org/network/stream-loader;1"].createInstance(Ci.nsIStreamLoader); + }, + + /** + * getInterface method for providers. This should be called in the context of + * the respective provider, i.e + * + * return cal.provider.InterfaceRequestor_getInterface.apply(this, arguments); + * + * or + * ... + * getInterface: cal.provider.InterfaceRequestor_getInterface, + * ... + * + * NOTE: If the server only provides one realm for all calendars, be sure that + * the |this| object implements calICalendar. In this case the calendar name + * will be appended to the realm. If you need that feature disabled, see the + * capabilities section of calICalendar.idl + * + * @param {nsIIDRef} aIID - The interface ID to return + * @returns {nsISupports} The requested interface + */ + InterfaceRequestor_getInterface(aIID) { + try { + return this.QueryInterface(aIID); + } catch (e) { + // Support Auth Prompt Interfaces + if (aIID.equals(Ci.nsIAuthPrompt2)) { + if (!this.calAuthPrompt) { + this.calAuthPrompt = new lazy.cal.auth.Prompt(); + } + return this.calAuthPrompt; + } else if (aIID.equals(Ci.nsIAuthPromptProvider) || aIID.equals(Ci.nsIPrompt)) { + return Services.ww.getNewPrompter(null); + } + throw e; + } + }, + + /** + * Bad Certificate Handler for Network Requests. Shows the Network Exception + * Dialog if a certificate Problem occurs. + */ + BadCertHandler: class { + /** + * @param {calICalendar} [calendar] - A calendar associated with the request, may be null. + */ + constructor(calendar) { + this.calendar = calendar; + this.timer = null; + } + + notifyCertProblem(secInfo, targetSite) { + // Unfortunately we can't pass js objects using the window watcher, so + // we'll just take the first available calendar window. We also need to + // do this on a timer so that the modal window doesn't block the + // network request. + let calWindow = lazy.cal.window.getCalendarWindow(); + + let timerCallback = { + calendar: this.calendar, + notify(timer) { + let params = { + exceptionAdded: false, + securityInfo: secInfo, + prefetchCert: true, + location: targetSite, + }; + calWindow.openDialog( + "chrome://pippki/content/exceptionDialog.xhtml", + "", + "chrome,centerscreen,modal", + params + ); + if (this.calendar && this.calendar.canRefresh && params.exceptionAdded) { + // Refresh the calendar if the exception certificate was added + this.calendar.refresh(); + } + }, + }; + this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this.timer.initWithCallback(timerCallback, 0, Ci.nsITimer.TYPE_ONE_SHOT); + return true; + } + }, + + /** + * Check for bad server certificates on SSL/TLS connections. + * + * @param {nsIRequest} request - request from the Stream loader. + * @param {number} status - A Components.results result. + * @param {calICalendar} [calendar] - A calendar associated with the request, may be null. + */ + checkBadCertStatus(request, status, calendar) { + let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"].getService( + Ci.nsINSSErrorsService + ); + let isCertError = false; + try { + let errorType = nssErrorsService.getErrorClass(status); + if (errorType == Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) { + isCertError = true; + } + } catch (e) { + // nsINSSErrorsService.getErrorClass throws if given a non-TLS, non-cert error, so ignore this. + } + + if (isCertError && request.securityInfo) { + let secInfo = request.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + let badCertHandler = new calprovider.BadCertHandler(calendar); + badCertHandler.notifyCertProblem(secInfo, request.originalURI.displayHostPort); + } + }, + + /** + * Freebusy interval implementation. All parameters are optional. + * + * @param aCalId The calendar id to set up with. + * @param aFreeBusyType The type from calIFreeBusyInterval. + * @param aStart The start of the interval. + * @param aEnd The end of the interval. + * @returns The fresh calIFreeBusyInterval. + */ + FreeBusyInterval: class { + QueryInterface() { + return ChromeUtils.generateQI(["calIFreeBusyInterval"]); + } + + constructor(aCalId, aFreeBusyType, aStart, aEnd) { + this.calId = aCalId; + this.interval = new lazy.CalPeriod(); + this.interval.start = aStart; + this.interval.end = aEnd; + + this.freeBusyType = aFreeBusyType || Ci.calIFreeBusyInterval.UNKNOWN; + } + }, + + /** + * Gets the iTIP/iMIP transport if the passed calendar has configured email. + * + * @param {calICalendar} aCalendar - The calendar to get the transport for + * @returns {?calIItipTransport} The email transport, or null if no identity configured + */ + getImipTransport(aCalendar) { + // assure an identity is configured for the calendar + if (aCalendar && aCalendar.getProperty("imip.identity")) { + return this.defaultImipTransport; + } + return null; + }, + + /** + * Gets the configured identity and account of a particular calendar instance, or null. + * + * @param {calICalendar} aCalendar - Calendar instance + * @param {?object} outAccount - Optional out value for account + * @returns {nsIMsgIdentity} The configured identity + */ + getEmailIdentityOfCalendar(aCalendar, outAccount) { + lazy.cal.ASSERT(aCalendar, "no calendar!", Cr.NS_ERROR_INVALID_ARG); + let key = aCalendar.getProperty("imip.identity.key"); + if (key === null) { + // take default account/identity: + let findIdentity = function (account) { + if (account && account.identities.length) { + return account.defaultIdentity || account.identities[0]; + } + return null; + }; + + let foundAccount = MailServices.accounts.defaultAccount; + let foundIdentity = findIdentity(foundAccount); + + if (!foundAccount || !foundIdentity) { + for (let account of MailServices.accounts.accounts) { + let identity = findIdentity(account); + + if (account && identity) { + foundAccount = account; + foundIdentity = identity; + break; + } + } + } + + if (outAccount) { + outAccount.value = foundIdentity ? foundAccount : null; + } + return foundIdentity; + } + if (key.length == 0) { + // i.e. "None" + return null; + } + let identity = null; + lazy.cal.email.iterateIdentities((identity_, account) => { + if (identity_.key == key) { + identity = identity_; + if (outAccount) { + outAccount.value = account; + } + } + return identity_.key != key; + }); + + if (!identity) { + // dangling identity: + lazy.cal.WARN( + "Calendar " + + (aCalendar.uri ? aCalendar.uri.spec : aCalendar.id) + + " has a dangling E-Mail identity configured." + ); + } + return identity; + }, + + /** + * Opens the calendar conflict dialog + * + * @param {string} aMode - The conflict mode, either "modify" or "delete" + * @param {calIItemBase} aItem - The item to raise a conflict for + * @returns {boolean} True, if the item should be overwritten + */ + promptOverwrite(aMode, aItem) { + let window = lazy.cal.window.getCalendarWindow(); + let args = { + item: aItem, + mode: aMode, + overwrite: false, + }; + + window.openDialog( + "chrome://calendar/content/calendar-conflicts-dialog.xhtml", + "calendarConflictsDialog", + "chrome,titlebar,modal", + args + ); + + return args.overwrite; + }, + + /** + * Gets the calendar directory, defaults to <profile-dir>/calendar-data + * + * @returns {nsIFile} The calendar-data directory as nsIFile + */ + getCalendarDirectory() { + if (calprovider.getCalendarDirectory.mDir === undefined) { + let dir = Services.dirsvc.get("ProfD", Ci.nsIFile); + dir.append("calendar-data"); + if (!dir.exists()) { + try { + dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o700); + } catch (exc) { + lazy.cal.ASSERT(false, exc); + throw exc; + } + } + calprovider.getCalendarDirectory.mDir = dir; + } + return calprovider.getCalendarDirectory.mDir.clone(); + }, + + /** + * Base prototype to be used implementing a calICalendar. + */ + BaseClass: class { + /** + * The transient properties that are not pesisted to storage + */ + static get mTransientProperties() { + return { + "cache.uncachedCalendar": true, + currentStatus: true, + "itip.transport": true, + "imip.identity": true, + "imip.account": true, + "imip.identity.disabled": true, + organizerId: true, + organizerCN: true, + }; + } + + QueryInterface = ChromeUtils.generateQI(["calICalendar", "calISchedulingSupport"]); + + /** + * Initialize the base class, this should be migrated to an ES6 constructor once all + * subclasses are also es6 classes. Call this from the constructor. + */ + initProviderBase() { + this.wrappedJSObject = this; + this.mID = null; + this.mUri = null; + this.mACLEntry = null; + this.mBatchCount = 0; + this.transientProperties = false; + this.mObservers = new lazy.cal.data.ObserverSet(Ci.calIObserver); + this.mProperties = {}; + this.mProperties.currentStatus = Cr.NS_OK; + } + + /** + * Returns the calIObservers for this calendar + */ + get observers() { + return this.mObservers; + } + + // attribute AUTF8String id; + get id() { + return this.mID; + } + set id(aValue) { + if (this.mID) { + throw Components.Exception("", Cr.NS_ERROR_ALREADY_INITIALIZED); + } + this.mID = aValue; + + // make all properties persistent that have been set so far: + for (let aName in this.mProperties) { + if (!this.constructor.mTransientProperties[aName]) { + let value = this.mProperties[aName]; + if (value !== null) { + lazy.cal.manager.setCalendarPref_(this, aName, value); + } + } + } + } + + // attribute AUTF8String name; + get name() { + return this.getProperty("name"); + } + set name(aValue) { + this.setProperty("name", aValue); + } + + // readonly attribute calICalendarACLManager aclManager; + get aclManager() { + const defaultACLProviderClass = "@mozilla.org/calendar/acl-manager;1?type=default"; + let providerClass = this.getProperty("aclManagerClass"); + if (!providerClass || !Cc[providerClass]) { + providerClass = defaultACLProviderClass; + } + return Cc[providerClass].getService(Ci.calICalendarACLManager); + } + + // readonly attribute calICalendarACLEntry aclEntry; + get aclEntry() { + return this.mACLEntry; + } + + // attribute calICalendar superCalendar; + get superCalendar() { + // If we have a superCalendar, check this calendar for a superCalendar. + // This will make sure the topmost calendar is returned + return this.mSuperCalendar ? this.mSuperCalendar.superCalendar : this; + } + set superCalendar(val) { + this.mSuperCalendar = val; + } + + // attribute nsIURI uri; + get uri() { + return this.mUri; + } + set uri(aValue) { + this.mUri = aValue; + } + + // attribute boolean readOnly; + get readOnly() { + return this.getProperty("readOnly"); + } + set readOnly(aValue) { + this.setProperty("readOnly", aValue); + } + + // readonly attribute boolean canRefresh; + get canRefresh() { + return false; + } + + // void startBatch(); + startBatch() { + if (this.mBatchCount++ == 0) { + this.mObservers.notify("onStartBatch", [this]); + } + } + + // void endBatch(); + endBatch() { + if (this.mBatchCount > 0) { + if (--this.mBatchCount == 0) { + this.mObservers.notify("onEndBatch", [this]); + } + } else { + lazy.cal.ASSERT(this.mBatchCount > 0, "unexpected endBatch!"); + } + } + + /** + * Implementation of calICalendar.getItems(). This should be overridden by + * all child classes. + * + * @param {number} itemFilter + * @param {number} count + * @param {calIDateTime} rangeStart + * @param {calIDateTime} rangeEnd + * + * @returns {ReadableStream<calIItemBase>} + */ + getItems(itemFilter, count, rangeStart, rangeEnd) { + return lazy.CalReadableStreamFactory.createEmptyReadableStream(); + } + + /** + * Implementation of calICalendar.getItemsAsArray(). + * + * @param {number} itemFilter + * @param {number} count + * @param {calIDateTime} rangeStart + * @param {calIDateTime} rangeEnd + * + * @returns {calIItemBase[]} + */ + async getItemsAsArray(itemFilter, count, rangeStart, rangeEnd) { + return lazy.cal.iterate.streamToArray(this.getItems(itemFilter, count, rangeStart, rangeEnd)); + } + + /** + * Notifies the given listener for onOperationComplete, ignoring (but logging) any + * exceptions that occur. If no listener is passed the function is a no-op. + * + * @param {?calIOperationListener} aListener - The listener to notify + * @param {number} aStatus - A Components.results result + * @param {number} aOperationType - The operation type component + * @param {string} aId - The item id + * @param {*} aDetail - The item detail for the listener + */ + notifyPureOperationComplete(aListener, aStatus, aOperationType, aId, aDetail) { + if (aListener) { + try { + aListener.onOperationComplete(this.superCalendar, aStatus, aOperationType, aId, aDetail); + } catch (exc) { + lazy.cal.ERROR(exc); + } + } + } + + /** + * Notifies the given listener for onOperationComplete, also setting various calendar status + * variables and notifying about the error. + * + * @param {?calIOperationListener} aListener - The listener to notify + * @param {number} aStatus - A Components.results result + * @param {number} aOperationType - The operation type component + * @param {string} aId - The item id + * @param {*} aDetail - The item detail for the listener + * @param {string} aExtraMessage - An extra message to pass to notifyError + */ + notifyOperationComplete(aListener, aStatus, aOperationType, aId, aDetail, aExtraMessage) { + this.notifyPureOperationComplete(aListener, aStatus, aOperationType, aId, aDetail); + + if (aStatus == Ci.calIErrors.OPERATION_CANCELLED) { + return; // cancellation doesn't change current status, no notification + } + if (Components.isSuccessCode(aStatus)) { + this.setProperty("currentStatus", aStatus); + } else { + if (aDetail instanceof Ci.nsIException) { + this.notifyError(aDetail); // will set currentStatus + } else { + this.notifyError(aStatus, aDetail); // will set currentStatus + } + this.notifyError( + aOperationType == Ci.calIOperationListener.GET + ? Ci.calIErrors.READ_FAILED + : Ci.calIErrors.MODIFICATION_FAILED, + aExtraMessage || "" + ); + } + } + + /** + * Notify observers using the onError notification with a readable error message + * + * @param {number | nsIException} aErrNo The error number from Components.results, or + * the exception which contains the error number + * @param {?string} aMessage - The message to show for the error + */ + notifyError(aErrNo, aMessage = null) { + if (aErrNo == Ci.calIErrors.OPERATION_CANCELLED) { + return; // cancellation doesn't change current status, no notification + } + if (aErrNo instanceof Ci.nsIException) { + if (!aMessage) { + aMessage = aErrNo.message; + } + aErrNo = aErrNo.result; + } + this.setProperty("currentStatus", aErrNo); + this.observers.notify("onError", [this.superCalendar, aErrNo, aMessage]); + } + + // nsIVariant getProperty(in AUTF8String aName); + getProperty(aName) { + switch (aName) { + case "itip.transport": // iTIP/iMIP default: + return calprovider.getImipTransport(this); + case "itip.notify-replies": // iTIP/iMIP default: + return Services.prefs.getBoolPref("calendar.itip.notify-replies", false); + // temporary hack to get the uncached calendar instance: + case "cache.uncachedCalendar": + return this; + } + + let ret = this.mProperties[aName]; + if (ret === undefined) { + ret = null; + switch (aName) { + case "imip.identity": // we want to cache the identity object a little, because + // it is heavily used by the invitation checks + ret = calprovider.getEmailIdentityOfCalendar(this); + break; + case "imip.account": { + let outAccount = {}; + if (calprovider.getEmailIdentityOfCalendar(this, outAccount)) { + ret = outAccount.value; + } + break; + } + case "organizerId": { + // itip/imip default: derived out of imip.identity + let identity = this.getProperty("imip.identity"); + ret = identity ? "mailto:" + identity.QueryInterface(Ci.nsIMsgIdentity).email : null; + break; + } + case "organizerCN": { + // itip/imip default: derived out of imip.identity + let identity = this.getProperty("imip.identity"); + ret = identity ? identity.QueryInterface(Ci.nsIMsgIdentity).fullName : null; + break; + } + } + if ( + ret === null && + !this.constructor.mTransientProperties[aName] && + !this.transientProperties + ) { + if (this.id) { + ret = lazy.cal.manager.getCalendarPref_(this, aName); + } + switch (aName) { + case "suppressAlarms": + if (this.getProperty("capabilities.alarms.popup.supported") === false) { + // If popup alarms are not supported, + // automatically suppress alarms + ret = true; + } + break; + } + } + this.mProperties[aName] = ret; + } + return ret; + } + + // void setProperty(in AUTF8String aName, in nsIVariant aValue); + setProperty(aName, aValue) { + let oldValue = this.getProperty(aName); + if (oldValue != aValue) { + this.mProperties[aName] = aValue; + switch (aName) { + case "imip.identity.key": // invalidate identity and account object if key is set: + delete this.mProperties["imip.identity"]; + delete this.mProperties["imip.account"]; + delete this.mProperties.organizerId; + delete this.mProperties.organizerCN; + break; + } + if (!this.transientProperties && !this.constructor.mTransientProperties[aName] && this.id) { + lazy.cal.manager.setCalendarPref_(this, aName, aValue); + } + this.mObservers.notify("onPropertyChanged", [this.superCalendar, aName, aValue, oldValue]); + } + return aValue; + } + + // void deleteProperty(in AUTF8String aName); + deleteProperty(aName) { + this.mObservers.notify("onPropertyDeleting", [this.superCalendar, aName]); + delete this.mProperties[aName]; + lazy.cal.manager.deleteCalendarPref_(this, aName); + } + + // calIOperation refresh + refresh() { + return null; + } + + // void addObserver( in calIObserver observer ); + addObserver(aObserver) { + this.mObservers.add(aObserver); + } + + // void removeObserver( in calIObserver observer ); + removeObserver(aObserver) { + this.mObservers.delete(aObserver); + } + + // calISchedulingSupport: Implementation corresponding to our iTIP/iMIP support + isInvitation(aItem) { + if (!this.mACLEntry || !this.mACLEntry.hasAccessControl) { + // No ACL support - fallback to the old method + let id = aItem.getProperty("X-MOZ-INVITED-ATTENDEE") || this.getProperty("organizerId"); + if (id) { + let org = aItem.organizer; + if (!org || !org.id || org.id.toLowerCase() == id.toLowerCase()) { + return false; + } + return aItem.getAttendeeById(id) != null; + } + return false; + } + + let org = aItem.organizer; + if (!org || !org.id) { + // HACK + // if we don't have an organizer, this is perhaps because it's an exception + // to a recurring event. We check the parent item. + if (aItem.parentItem) { + org = aItem.parentItem.organizer; + if (!org || !org.id) { + return false; + } + } else { + return false; + } + } + + // We check if : + // - the organizer of the event is NOT within the owner's identities of this calendar + // - if the one of the owner's identities of this calendar is in the attendees + let ownerIdentities = this.mACLEntry.getOwnerIdentities(); + for (let i = 0; i < ownerIdentities.length; i++) { + let identity = "mailto:" + ownerIdentities[i].email.toLowerCase(); + if (org.id.toLowerCase() == identity) { + return false; + } + + if (aItem.getAttendeeById(identity) != null) { + return true; + } + } + + return false; + } + + // calIAttendee getInvitedAttendee(in calIItemBase aItem); + getInvitedAttendee(aItem) { + let id = this.getProperty("organizerId"); + let attendee = id ? aItem.getAttendeeById(id) : null; + + if (!attendee && this.mACLEntry && this.mACLEntry.hasAccessControl) { + let ownerIdentities = this.mACLEntry.getOwnerIdentities(); + if (ownerIdentities.length > 0) { + let identity; + for (let i = 0; !attendee && i < ownerIdentities.length; i++) { + identity = "mailto:" + ownerIdentities[i].email.toLowerCase(); + attendee = aItem.getAttendeeById(identity); + } + } + } + + return attendee; + } + + // boolean canNotify(in AUTF8String aMethod, in calIItemBase aItem); + canNotify(aMethod, aItem) { + return false; // use outbound iTIP for all + } + }, + + // Provider Registration + + /** + * Register a provider. + * + * @param {calICalendarProvider} provider - The provider object. + */ + register(provider) { + this.providers.set(provider.type, provider); + }, + + /** + * Unregister a provider. + * + * @param {string} type - The type of the provider to unregister. + * @returns {boolean} True if the provider was unregistered, false if + * it was not registered in the first place. + */ + unregister(type) { + return this.providers.delete(type); + }, + + /** + * Get a provider by its type property, e.g. "ics", "caldav". + * + * @param {string} type - Type of the provider to get. + * @returns {calICalendarProvider | undefined} Provider or undefined if none + * is registered for the type. + */ + byType(type) { + return this.providers.get(type); + }, + + /** + * The built-in "ics" provider. + * + * @type {calICalendarProvider} + */ + get ics() { + return this.byType("ics"); + }, + + /** + * The built-in "caldav" provider. + * + * @type {calICalendarProvider} + */ + get caldav() { + return this.byType("caldav"); + }, +}; + +// Initialize `cal.provider.providers` with the built-in providers. +XPCOMUtils.defineLazyGetter(calprovider, "providers", () => { + const { CalICSProvider } = ChromeUtils.import("resource:///modules/CalICSProvider.jsm"); + const { CalDavProvider } = ChromeUtils.import("resource:///modules/CalDavProvider.jsm"); + return new Map([ + ["ics", CalICSProvider], + ["caldav", CalDavProvider], + ]); +}); + +// This is the transport returned by getImipTransport(). +XPCOMUtils.defineLazyGetter(calprovider, "defaultImipTransport", () => { + const { CalItipEmailTransport } = ChromeUtils.import( + "resource:///modules/CalItipEmailTransport.jsm" + ); + return CalItipEmailTransport.createInstance(); +}); + +// Set up the `cal.provider.detection` module. +XPCOMUtils.defineLazyModuleGetter( + calprovider, + "detection", + "resource:///modules/calendar/utils/calProviderDetectionUtils.jsm", + "calproviderdetection" +); |