This file is indexed.

/usr/lib/thunderbird-addons/extensions/{e2fda1a4-762b-4020-b5ad-a41df1933103}/components/lightningTextCalendarConverter.js is in xul-ext-lightning 1:24.4.0+build1-0ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
/* 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/. */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://calendar/modules/calUtils.jsm");
Components.utils.import("resource://calendar/modules/calXMLUtils.jsm");
Components.utils.import("resource://calendar/modules/calRecurrenceUtils.jsm");

function ltnMimeConverter() {
    this.wrappedJSObject = this;
}

const ltnMimeConverterClassID = Components.ID("{c70acb08-464e-4e55-899d-b2c84c5409fa}");
const ltnMimeConverterInterfaces = [Components.interfaces.nsISimpleMimeConverter];
ltnMimeConverter.prototype = {
    classID: ltnMimeConverterClassID,
    QueryInterface: XPCOMUtils.generateQI(ltnMimeConverterInterfaces),

    classInfo: XPCOMUtils.generateCI({
        classID: ltnMimeConverterClassID,
        contractID: "@mozilla.org/lightning/mime-converter;1",
        classDescription: "Lightning text/calendar handler",
        interfaces: ltnMimeConverterInterfaces
    }),

    /**
     * Append the text to node, converting contained URIs to <a> links.
     *
     * @param text      The text to convert.
     * @param node      The node to append the text to.
     */
    linkifyText: function linkifyText(text, node) {
        let doc = node.ownerDocument;
        let localText = text;

        // XXX This should be improved to also understand abbreviated urls, could be
        // extended to only linkify urls that have an internal protocol handler, or
        // have an external protocol handler that has an app assigned. The same
        // could be done for mailto links which are not handled here either.

        // XXX Ideally use mozITXTToHTMLConv here, but last time I tried it didn't work.

        while (localText.length) {
            let pos = localText.search(/(^|\s+)([a-zA-Z0-9]+):\/\/[^\s]+/);
            if (pos == -1) {
                node.appendChild(doc.createTextNode(localText));
                break;
            }
            pos += localText.substr(pos).match(/^\s*/)[0].length;
            let endPos = pos + localText.substr(pos).search(/([.!,<>(){}]+)?(\s+|$)/);
            let url = localText.substr(pos, endPos - pos);

            if (pos > 0) {
                node.appendChild(doc.createTextNode(localText.substr(0, pos)));
            }
            let a = doc.createElement("a");
            a.setAttribute("href", url);
            a.textContent = url;

            node.appendChild(a);

            localText = localText.substr(endPos);
        }
    },

    /**
     * Returns a header title for an ITIP item depending on the response method
     * @param       aItipItem  the event
     * @return string the header title
     */
    getItipHeader: function getItipHeader(aItipItem) {
        let header;

        if (aItipItem) {
            let item = aItipItem.getItemList({})[0];
            let summary = item.getProperty("SUMMARY") || "";
            let organizer = item.organizer;
            let organizerString = (organizer) ?
              (organizer.commonName || organizer.toString()) : "";

            switch (aItipItem.responseMethod) {
                case "REQUEST":
                    header = cal.calGetString("lightning",
                                              "itipRequestBody",
                                              [organizerString, summary],
                                              "lightning");
                    break;
                case "CANCEL":
                    header = cal.calGetString("lightning",
                                              "itipCancelBody",
                                              [organizerString, summary],
                                              "lightning");
                    break;
                case "REPLY": {
                    // This is a reply received from someone else, there should
                    // be just one attendee, the attendee that replied. If
                    // there is more than one attendee, just take the first so
                    // code doesn't break here.
                    let attendees = item.getAttendees({});
                    if (attendees && attendees.length >= 1) {
                        let sender = attendees[0];
                        let statusString = (sender.participationStatus == "DECLINED" ?
                                            "itipReplyBodyDecline" :
                                            "itipReplyBodyAccept");

                        header = cal.calGetString("lightning",
                                                  statusString,
                                                  [sender.toString()],
                                                  "lightning");
                    } else {
                        header = "";
                    }
                    break;
                }
            }
        }

        if (!header) {
            header = cal.calGetString("lightning", "imipHtml.header", null, "lightning");
        }

        return header;
    },

    /**
     * Returns the html representation of the event as a DOM document.
     *
     * @param event         The calIItemBase to parse into html.
     * @param aNewItipItem  The parsed itip item.
     * @return              The DOM document with values filled in.
     */
    createHtml: function createHtml(event, aNewItipItem) {
        // Creates HTML using the Node strings in the properties file
        let doc = cal.xml.parseFile("chrome://lightning/content/lightning-invitation.xhtml");
        let formatter = cal.getDateFormatter();

        let self = this;
        function field(field, contentText, linkify) {
            let descr = doc.getElementById("imipHtml-" + field + "-descr");
            if (descr) {
                let labelText = cal.calGetString("lightning", "imipHtml." + field, null, "lightning");
                descr.textContent = labelText;
            }

            if (contentText) {
                let content = doc.getElementById("imipHtml-" + field + "-content");
                doc.getElementById("imipHtml-" + field + "-row").hidden = false;
                if (linkify) {
                    self.linkifyText(contentText, content);
                } else {
                    content.textContent = contentText;
                }
            }
        }

        // Simple fields
        let headerDescr = doc.getElementById("imipHtml-header-descr");
        if (headerDescr) {
            headerDescr.textContent = this.getItipHeader(aNewItipItem);
        }

        field("summary", event.title);
        field("location", event.getProperty("LOCATION"));

        let dateString = formatter.formatItemInterval(event);

        if (event.recurrenceInfo) {
            let kDefaultTimezone = cal.calendarDefaultTimezone();
            let startDate =  event.startDate;
            let endDate = event.endDate;
            startDate = startDate ? startDate.getInTimezone(kDefaultTimezone) : null;
            endDate = endDate ? endDate.getInTimezone(kDefaultTimezone) : null;
            let repeatString = recurrenceRule2String(event.recurrenceInfo, startDate,
                                                     endDate, startDate.isDate);
            if (repeatString) {
                dateString = repeatString;
            }

            let formattedExDates = [];
            let modifiedOccurrences = [];
            function dateComptor(a,b) a.startDate.compare(b.startDate);

            // Show removed instances
            for each (let exc in event.recurrenceInfo.getRecurrenceItems({})) {
                if (exc instanceof Components.interfaces.calIRecurrenceDate) {
                    if (exc.isNegative) {
                        // This is an EXDATE
                        formattedExDates.push(formatter.formatDateTime(exc.date));
                    } else {
                        // This is an RDATE, close enough to a modified occurrence
                        let excItem = event.recurrenceInfo.getOccurrenceFor(exc.date);
                        cal.binaryInsert(modifiedOccurrences, excItem, dateComptor, true)
                    }
                }
            }
            if (formattedExDates.length > 0) {
                field("canceledOccurrences", formattedExDates.join("\n"));
            }

            // Show modified occurrences
            for each (let recurrenceId in event.recurrenceInfo.getExceptionIds({})) {
                let exc = event.recurrenceInfo.getExceptionFor(recurrenceId);
                let excLocation = exc.getProperty("LOCATION");

                // Only show modified occurrence if start, duration or location
                // has changed.
                if (exc.startDate.compare(exc.recurrenceId) != 0 ||
                    exc.duration.compare(event.duration) != 0 ||
                    excLocation != event.getProperty("LOCATION")) {
                    cal.binaryInsert(modifiedOccurrences, exc, dateComptor, true)
                }
            }

            function stringifyOcc(occ) {
                let formattedExc = formatter.formatItemInterval(occ);
                let occLocation = occ.getProperty("LOCATION");
                if (occLocation != event.getProperty("LOCATION")) {
                    let location = cal.calGetString("lightning", "imipHtml.newLocation", [occLocation], "lightning");
                    formattedExc += " (" + location + ")";
                }
                return formattedExc;
            }

            if (modifiedOccurrences.length > 0) {
                field("modifiedOccurrences", modifiedOccurrences.map(stringifyOcc).join("\n"));
            }
        }

        field("when", dateString);
        field("comment", event.getProperty("COMMENT"), true);

        // DESCRIPTION field
        let eventDescription = (event.getProperty("DESCRIPTION") || "")
                                    /* Remove the useless "Outlookism" squiggle. */
                                    .replace("*~*~*~*~*~*~*~*~*~*", "");
        field("description", eventDescription, true);

        // ATTENDEE and ORGANIZER fields
        let attendees = event.getAttendees({});
        let attendeeTemplate = doc.getElementById("attendee-template");
        let attendeeTable = doc.getElementById("attendee-table");
        let organizerTable = doc.getElementById("organizer-table");
        doc.getElementById("imipHtml-attendees-row").hidden = (attendees.length < 1);
        doc.getElementById("imipHtml-organizer-row").hidden = !event.organizer;

        function setupAttendee(attendee) {
            let row = attendeeTemplate.cloneNode(true);
            row.removeAttribute("id");
            row.removeAttribute("hidden");
            row.getElementsByClassName("status-icon")[0].setAttribute("status", attendee.participationStatus);
            row.getElementsByClassName("attendee-name")[0].textContent = attendee.toString();
            return row;
        }

        // Fill rows for attendees and organizer
        field("attendees");
        for each (let attendee in attendees) {
            attendeeTable.appendChild(setupAttendee(attendee));
        }

        field("organizer");
        if (event.organizer) {
            organizerTable.appendChild(setupAttendee(event.organizer));
        }

        return doc;
    },


    /* nsISimpleMimeConverter */

    uri: null,

    convertToHTML: function lmcCTH(contentType, data) {
        let parser = Components.classes["@mozilla.org/calendar/ics-parser;1"]
                               .createInstance(Components.interfaces.calIIcsParser);
        parser.parseString(data);
        let event = null;
        for each (let item in parser.getItems({})) {
            if (cal.isEvent(item)) {
                if (item.hasProperty("X-MOZ-FAKED-MASTER")) {
                    // if it's a faked master, take any overridden item to get a real occurrence:
                    let exc = item.recurrenceInfo.getExceptionFor(item.startDate);
                    cal.ASSERT(exc, "unexpected!");
                    if (exc) {
                        item = exc;
                    }
                }
                event = item;
                break;
            }
        }
        if (!event) {
            return '';
        }

        let itipItem = null;

        try {
            // this.uri is the message URL that we are processing.
            // We use it to get the nsMsgHeaderSink to store the calItipItem.
            if (this.uri) {
                let msgWindow = null;
                try {
                    let msgUrl = this.uri.QueryInterface(Components.interfaces.nsIMsgMailNewsUrl);
                    // msgWindow is optional in some scenarios
                    // (e.g. gloda in action, throws NS_ERROR_INVALID_POINTER then)
                    msgWindow = msgUrl.msgWindow;
                } catch (exc) {
                }
                if (msgWindow) {
                    itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
                                             .createInstance(Components.interfaces.calIItipItem);
                    itipItem.init(data);

                    let sinkProps = msgWindow.msgHeaderSink.properties;
                    sinkProps.setPropertyAsInterface("itipItem", itipItem);

                    // Notify the observer that the itipItem is available
                    Services.obs.notifyObservers(null, "onItipItemCreation", 0);
                }
            }
        } catch (e) {
            cal.ERROR("[ltnMimeConverter] convertToHTML: " + e);
        }

        // Create the HTML string for display
        return cal.xml.serializeDOM(this.createHtml(event, itipItem));
    }
};

var NSGetFactory = XPCOMUtils.generateNSGetFactory([ltnMimeConverter]);