summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/modules/utils/calEmailUtils.jsm
blob: 5892ba569f994dc00602015c3597fd3f9d98547d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/* 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/. */

/**
 * Functions for processing email addresses and sending email
 */

// NOTE: This module should not be loaded directly, it is available when
// including calUtils.jsm under the cal.email namespace.

const EXPORTED_SYMBOLS = ["calemail"];

var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");

const lazy = {};
ChromeUtils.defineModuleGetter(lazy, "cal", "resource:///modules/calendar/calUtils.jsm");

var calemail = {
  /**
   * Convenience function to open the compose window pre-filled with the information from the
   * parameters. These parameters are mostly raw header fields, see #createRecipientList function
   * to create a recipient list string.
   *
   * @param {string} aRecipient - The email recipients string.
   * @param {string} aSubject - The email subject.
   * @param {string} aBody - The encoded email body text.
   * @param {nsIMsgIdentity} aIdentity - The email identity to use for sending
   */
  sendTo(aRecipient, aSubject, aBody, aIdentity) {
    let msgParams = Cc["@mozilla.org/messengercompose/composeparams;1"].createInstance(
      Ci.nsIMsgComposeParams
    );
    let composeFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(
      Ci.nsIMsgCompFields
    );

    composeFields.to = aRecipient;
    composeFields.subject = aSubject;
    composeFields.body = aBody;

    msgParams.type = Ci.nsIMsgCompType.New;
    msgParams.format = Ci.nsIMsgCompFormat.Default;
    msgParams.composeFields = composeFields;
    msgParams.identity = aIdentity;

    MailServices.compose.OpenComposeWindowWithParams(null, msgParams);
  },

  /**
   * Iterates all email identities and calls the passed function with identity and account.
   * If the called function returns false, iteration is stopped.
   *
   * @param {Function} aFunc - The function to be called for each identity and account
   */
  iterateIdentities(aFunc) {
    for (let account of MailServices.accounts.accounts) {
      for (let identity of account.identities) {
        if (!aFunc(identity, account)) {
          break;
        }
      }
    }
  },

  /**
   * Prepends a mailto: prefix to an email address like string
   *
   * @param  {string} aId     The string to prepend the prefix if not already there
   * @returns {string} The string with prefix
   */
  prependMailTo(aId) {
    return aId.replace(/^(?:mailto:)?(.*)@/i, "mailto:$1@");
  },

  /**
   * Removes an existing mailto: prefix from an attendee id
   *
   * @param  {string} aId     The string to remove the prefix from if any
   * @returns {string} The string without prefix
   */
  removeMailTo(aId) {
    return aId.replace(/^mailto:/i, "");
  },

  /**
   * Provides a string to use in email "to" header for given attendees
   *
   * @param  {calIAttendee[]} aAttendees          Array of calIAttendee's to check
   * @returns {string} Valid string to use in a 'to' header of an email
   */
  createRecipientList(aAttendees) {
    let cbEmail = function (aVal) {
      let email = calemail.getAttendeeEmail(aVal, true);
      if (!email.length) {
        lazy.cal.LOG("Dropping invalid recipient for email transport: " + aVal.toString());
      }
      return email;
    };
    return aAttendees
      .map(cbEmail)
      .filter(aVal => aVal.length > 0)
      .join(", ");
  },

  /**
   * Returns a wellformed email string like 'attendee@example.net',
   * 'Common Name <attendee@example.net>' or '"Name, Common" <attendee@example.net>'
   *
   * @param  {calIAttendee} aAttendee     The attendee to check
   * @param  {boolean} aIncludeCn         Whether or not to return also the CN if available
   * @returns {string} Valid email string or an empty string in case of error
   */
  getAttendeeEmail(aAttendee, aIncludeCn) {
    // If the recipient id is of type urn, we need to figure out the email address, otherwise
    // we fall back to the attendee id
    let email = aAttendee.id.match(/^urn:/i) ? aAttendee.getProperty("EMAIL") || "" : aAttendee.id;
    // Strip leading "mailto:" if it exists.
    email = email.replace(/^mailto:/i, "");
    // We add the CN if requested and available
    let commonName = aAttendee.commonName;
    if (aIncludeCn && email.length > 0 && commonName && commonName.length > 0) {
      if (commonName.match(/[,;]/)) {
        commonName = '"' + commonName + '"';
      }
      commonName = commonName + " <" + email + ">";
      if (calemail.validateRecipientList(commonName) == commonName) {
        email = commonName;
      }
    }
    return email;
  },

  /**
   * Returns a basically checked recipient list - malformed elements will be removed
   *
   * @param {string} aRecipients - A comma-seperated list of e-mail addresses
   * @returns {string} A validated comma-seperated list of e-mail addresses
   */
  validateRecipientList(aRecipients) {
    let compFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(
      Ci.nsIMsgCompFields
    );
    // Resolve the list considering also configured common names
    let members = compFields.splitRecipients(aRecipients, false);
    let list = [];
    let prefix = "";
    for (let member of members) {
      if (prefix != "") {
        // the previous member had no email address - this happens if a recipients CN
        // contains a ',' or ';' (splitRecipients(..) behaves wrongly here and produces an
        // additional member with only the first CN part of that recipient and no email
        // address while the next has the second part of the CN and the according email
        // address) - we still need to identify the original delimiter to append it to the
        // prefix
        let memberCnPart = member.match(/(.*) <.*>/);
        if (memberCnPart) {
          let pattern = new RegExp(prefix + "([;,] *)" + memberCnPart[1]);
          let delimiter = aRecipients.match(pattern);
          if (delimiter) {
            prefix = prefix + delimiter[1];
          }
        }
      }
      let parts = (prefix + member).match(/(.*)( <.*>)/);
      if (parts) {
        if (parts[2] == " <>") {
          // CN but no email address - we keep the CN part to prefix the next member's CN
          prefix = parts[1];
        } else {
          // CN with email address
          let commonName = parts[1].trim();
          // in case of any special characters in the CN string, we make sure to enclose
          // it with dquotes - simple spaces don't require dquotes
          if (commonName.match(/[-[\]{}()*+?.,;\\^$|#\f\n\r\t\v]/)) {
            commonName = '"' + commonName.replace(/\\"|"/, "").trim() + '"';
          }
          list.push(commonName + parts[2]);
          prefix = "";
        }
      } else if (member.length) {
        // email address only
        list.push(member);
        prefix = "";
      }
    }
    return list.join(", ");
  },

  /**
   * Check if the attendee object matches one of the addresses in the list. This
   * is useful to determine whether the current user acts as a delegate.
   *
   * @param {calIAttendee} aRefAttendee - The reference attendee object
   * @param {string[]} aAddresses - The list of addresses
   * @returns {boolean} True, if there is a match
   */
  attendeeMatchesAddresses(aRefAttendee, aAddresses) {
    let attId = aRefAttendee.id;
    if (!attId.match(/^mailto:/i)) {
      // Looks like its not a normal attendee, possibly urn:uuid:...
      // Try getting the email through the EMAIL property.
      let emailProp = aRefAttendee.getProperty("EMAIL");
      if (emailProp) {
        attId = emailProp;
      }
    }

    attId = attId.toLowerCase().replace(/^mailto:/, "");
    for (let address of aAddresses) {
      if (attId == address.toLowerCase().replace(/^mailto:/, "")) {
        return true;
      }
    }

    return false;
  },
};