summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/addrbook/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/addrbook/src
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--comm/mailnews/addrbook/src/AbAutoCompleteMyDomain.jsm69
-rw-r--r--comm/mailnews/addrbook/src/AbAutoCompleteSearch.jsm608
-rw-r--r--comm/mailnews/addrbook/src/AbLDAPAttributeMap.jsm219
-rw-r--r--comm/mailnews/addrbook/src/AbLDAPAutoCompleteSearch.jsm364
-rw-r--r--comm/mailnews/addrbook/src/components.conf129
-rw-r--r--comm/mailnews/addrbook/src/moz.build49
-rw-r--r--comm/mailnews/addrbook/src/nsAbAddressCollector.cpp281
-rw-r--r--comm/mailnews/addrbook/src/nsAbAddressCollector.h42
-rw-r--r--comm/mailnews/addrbook/src/nsAbBooleanExpression.cpp98
-rw-r--r--comm/mailnews/addrbook/src/nsAbBooleanExpression.h41
-rw-r--r--comm/mailnews/addrbook/src/nsAbCardProperty.cpp1004
-rw-r--r--comm/mailnews/addrbook/src/nsAbCardProperty.h63
-rw-r--r--comm/mailnews/addrbook/src/nsAbDirProperty.cpp573
-rw-r--r--comm/mailnews/addrbook/src/nsAbDirProperty.h62
-rw-r--r--comm/mailnews/addrbook/src/nsAbDirectoryQuery.cpp421
-rw-r--r--comm/mailnews/addrbook/src/nsAbDirectoryQuery.h96
-rw-r--r--comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp25
-rw-r--r--comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h26
-rw-r--r--comm/mailnews/addrbook/src/nsAbLDIFService.cpp787
-rw-r--r--comm/mailnews/addrbook/src/nsAbLDIFService.h37
-rw-r--r--comm/mailnews/addrbook/src/nsAbOSXCard.h51
-rw-r--r--comm/mailnews/addrbook/src/nsAbOSXCard.mm353
-rw-r--r--comm/mailnews/addrbook/src/nsAbOSXDirectory.h119
-rw-r--r--comm/mailnews/addrbook/src/nsAbOSXDirectory.mm911
-rw-r--r--comm/mailnews/addrbook/src/nsAbOSXUtils.h30
-rw-r--r--comm/mailnews/addrbook/src/nsAbOSXUtils.mm107
-rw-r--r--comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp1418
-rw-r--r--comm/mailnews/addrbook/src/nsAbOutlookDirectory.h181
-rw-r--r--comm/mailnews/addrbook/src/nsAbOutlookInterface.cpp38
-rw-r--r--comm/mailnews/addrbook/src/nsAbOutlookInterface.h21
-rw-r--r--comm/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp293
-rw-r--r--comm/mailnews/addrbook/src/nsAbQueryStringToExpression.h38
-rw-r--r--comm/mailnews/addrbook/src/nsAbWinHelper.cpp1491
-rw-r--r--comm/mailnews/addrbook/src/nsAbWinHelper.h183
-rw-r--r--comm/mailnews/addrbook/src/nsLDAPURL.cpp591
-rw-r--r--comm/mailnews/addrbook/src/nsLDAPURL.h95
-rw-r--r--comm/mailnews/addrbook/src/nsMapiAddressBook.cpp163
-rw-r--r--comm/mailnews/addrbook/src/nsMapiAddressBook.h52
38 files changed, 11129 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/src/AbAutoCompleteMyDomain.jsm b/comm/mailnews/addrbook/src/AbAutoCompleteMyDomain.jsm
new file mode 100644
index 0000000000..08a2654d03
--- /dev/null
+++ b/comm/mailnews/addrbook/src/AbAutoCompleteMyDomain.jsm
@@ -0,0 +1,69 @@
+/* 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 EXPORTED_SYMBOLS = ["AbAutoCompleteMyDomain"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function AbAutoCompleteMyDomain() {}
+
+AbAutoCompleteMyDomain.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteSearch"]),
+
+ cachedIdKey: "",
+ cachedIdentity: null,
+
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+ startSearch(aString, aSearchParam, aResult, aListener) {
+ let params = aSearchParam ? JSON.parse(aSearchParam) : {};
+ let applicable =
+ "type" in params && this.applicableHeaders.has(params.type);
+ const ACR = Ci.nsIAutoCompleteResult;
+ var address = null;
+ if (applicable && aString && !aString.includes(",")) {
+ if ("idKey" in params && params.idKey != this.cachedIdKey) {
+ this.cachedIdentity = MailServices.accounts.getIdentity(params.idKey);
+ this.cachedIdKey = params.idKey;
+ }
+ if (this.cachedIdentity.autocompleteToMyDomain) {
+ address = aString.includes("@")
+ ? aString
+ : this.cachedIdentity.email.replace(/[^@]*/, aString);
+ }
+ }
+
+ var result = {
+ searchString: aString,
+ searchResult: address ? ACR.RESULT_SUCCESS : ACR.RESULT_FAILURE,
+ defaultIndex: -1,
+ errorDescription: null,
+ matchCount: address ? 1 : 0,
+ getValueAt() {
+ return address;
+ },
+ getLabelAt() {
+ return this.getValueAt();
+ },
+ getCommentAt() {
+ return null;
+ },
+ getStyleAt() {
+ return "default-match";
+ },
+ getImageAt() {
+ return null;
+ },
+ getFinalCompleteValueAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+ removeValueAt() {},
+ };
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch() {},
+};
diff --git a/comm/mailnews/addrbook/src/AbAutoCompleteSearch.jsm b/comm/mailnews/addrbook/src/AbAutoCompleteSearch.jsm
new file mode 100644
index 0000000000..8beff4f670
--- /dev/null
+++ b/comm/mailnews/addrbook/src/AbAutoCompleteSearch.jsm
@@ -0,0 +1,608 @@
+/* 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 EXPORTED_SYMBOLS = ["AbAutoCompleteSearch"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var {
+ getSearchTokens,
+ getModelQuery,
+ modelQueryHasUserValue,
+ generateQueryURI,
+} = ChromeUtils.import("resource:///modules/ABQueryUtils.jsm");
+
+var ACR = Ci.nsIAutoCompleteResult;
+var nsIAbAutoCompleteResult = Ci.nsIAbAutoCompleteResult;
+
+var MAX_ASYNC_RESULTS = 100;
+
+function nsAbAutoCompleteResult(aSearchString) {
+ // Can't create this in the prototype as we'd get the same array for
+ // all instances
+ this.asyncDirectories = [];
+ this._searchResults = []; // final results
+ this.searchString = aSearchString;
+ this._collectedValues = new Map(); // temporary unsorted results
+ // Get model query from pref; this will return mail.addr_book.autocompletequery.format.phonetic
+ // if mail.addr_book.show_phonetic_fields == true
+ this.modelQuery = getModelQuery("mail.addr_book.autocompletequery.format");
+ // check if the currently active model query has been modified by user
+ this._modelQueryHasUserValue = modelQueryHasUserValue(
+ "mail.addr_book.autocompletequery.format"
+ );
+}
+
+nsAbAutoCompleteResult.prototype = {
+ _searchResults: null,
+
+ // nsIAutoCompleteResult
+
+ searchString: null,
+ searchResult: ACR.RESULT_NOMATCH,
+ defaultIndex: -1,
+ errorDescription: null,
+
+ get matchCount() {
+ return this._searchResults.length;
+ },
+
+ getValueAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getLabelAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ getCommentAt(aIndex) {
+ return this._searchResults[aIndex].comment;
+ },
+
+ getStyleAt(aIndex) {
+ return "local-abook";
+ },
+
+ getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt(aRowIndex, aRemoveFromDB) {},
+
+ // nsIAbAutoCompleteResult
+
+ getCardAt(aIndex) {
+ return this._searchResults[aIndex].card;
+ },
+
+ getEmailToUse(aIndex) {
+ return this._searchResults[aIndex].emailToUse;
+ },
+
+ isCompleteResult(aIndex) {
+ return this._searchResults[aIndex].isCompleteResult;
+ },
+
+ modelQuery: null,
+ asyncDirectories: null,
+
+ // nsISupports
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIAutoCompleteResult",
+ "nsIAbAutoCompleteResult",
+ ]),
+};
+
+function AbAutoCompleteSearch() {}
+
+AbAutoCompleteSearch.prototype = {
+ // This is set from a preference,
+ // 0 = no comment column, 1 = name of address book this card came from
+ // Other numbers currently unused (hence default to zero)
+ _commentColumn: 0,
+ _parser: MailServices.headerParser,
+ _abManager: MailServices.ab,
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+ _result: null,
+
+ // Private methods
+
+ /**
+ * Returns the popularity index for a given card. This takes account of a
+ * translation bug whereby Thunderbird 2 stores its values in mork as
+ * hexadecimal, and Thunderbird 3 stores as decimal.
+ *
+ * @param {nsIAbDirectory} aDirectory - The directory that the card is in.
+ * @param {nsIAbCard} aCard - The card to return the popularity index for.
+ */
+ _getPopularityIndex(aDirectory, aCard) {
+ let popularityValue = aCard.getProperty("PopularityIndex", "0");
+ let popularityIndex = parseInt(popularityValue);
+
+ // If we haven't parsed it the first time round, parse it as hexadecimal
+ // and repair so that we don't have to keep repairing.
+ if (isNaN(popularityIndex)) {
+ popularityIndex = parseInt(popularityValue, 16);
+
+ // If its still NaN, just give up, we shouldn't ever get here.
+ if (isNaN(popularityIndex)) {
+ popularityIndex = 0;
+ }
+
+ // Now store this change so that we're not changing it each time around.
+ if (!aDirectory.readOnly) {
+ aCard.setProperty("PopularityIndex", popularityIndex);
+ try {
+ aDirectory.modifyCard(aCard);
+ } catch (ex) {
+ console.error(ex);
+ }
+ }
+ }
+ return popularityIndex;
+ },
+
+ /**
+ * Gets the score of the (full) address, given the search input. We want
+ * results that match the beginning of a "word" in the result to score better
+ * than a result that matches only in the middle of the word.
+ *
+ * @param {nsIAbCard} aCard - The card whose score is being decided.
+ * @param {string} aAddress - Full lower-cased address, including display
+ * name and address.
+ * @param {string} aSearchString - Search string provided by user.
+ * @returns {integer} a score; a higher score is better than a lower one.
+ */
+ _getScore(aCard, aAddress, aSearchString) {
+ const BEST = 100;
+
+ // We will firstly check if the search term provided by the user
+ // is the nick name for the card or at least in the beginning of it.
+ let nick = aCard.getProperty("NickName", "").toLocaleLowerCase();
+ aSearchString = aSearchString.toLocaleLowerCase();
+ if (nick == aSearchString) {
+ return BEST + 1;
+ }
+ if (nick.indexOf(aSearchString) == 0) {
+ return BEST;
+ }
+
+ // We'll do this case-insensitively and ignore the domain.
+ let atIdx = aAddress.lastIndexOf("@");
+ if (atIdx != -1) {
+ // mail lists don't have an @
+ aAddress = aAddress.substr(0, atIdx);
+ }
+ let idx = aAddress.indexOf(aSearchString);
+ if (idx == 0) {
+ return BEST;
+ }
+ if (idx == -1) {
+ return 0;
+ }
+
+ // We want to treat firstname, lastname and word boundary(ish) parts of
+ // the email address the same. E.g. for "John Doe (:xx) <jd.who@example.com>"
+ // all of these should score the same: "John", "Doe", "xx",
+ // ":xx", "jd", "who".
+ let prevCh = aAddress.charAt(idx - 1);
+ if (/[ :."'(\-_<&]/.test(prevCh)) {
+ return BEST;
+ }
+
+ // The match was inside a word -> we don't care about the position.
+ return 0;
+ },
+
+ /**
+ * Searches cards in the given directory. If a card is matched (and isn't
+ * a mailing list) then the function will add a result for each email address
+ * that exists.
+ *
+ * @param {string} searchQuery - The boolean search query to use.
+ * @param {string} searchString - The original search string.
+ * @param {nsIAbDirectory} directory - An nsIAbDirectory to search.
+ * @param {nsIAbAutoCompleteResult} result - The result element to append
+ * results to.
+ */
+ _searchCards(searchQuery, searchString, directory, result) {
+ // Cache this values to save going through xpconnect each time
+ let commentColumn = this._commentColumn == 1 ? directory.dirName : "";
+
+ if (searchQuery[0] == "?") {
+ searchQuery = searchQuery.substring(1);
+ }
+ return new Promise(resolve => {
+ directory.search(searchQuery, searchString, {
+ onSearchFoundCard: card => {
+ if (card.isMailList) {
+ this._addToResult(commentColumn, directory, card, "", true, result);
+ } else {
+ let first = true;
+ for (let emailAddress of card.emailAddresses) {
+ this._addToResult(
+ commentColumn,
+ directory,
+ card,
+ emailAddress,
+ first,
+ result
+ );
+ first = false;
+ }
+ }
+ },
+ onSearchFinished(status, complete, secInfo, location) {
+ resolve();
+ },
+ });
+ });
+ },
+
+ /**
+ * Checks the parent card and email address of an autocomplete results entry
+ * from a previous result against the search parameters to see if that entry
+ * should still be included in the narrowed-down result.
+ *
+ * @param {nsIAbCard} aCard - The card to check.
+ * @param {string} aEmailToUse - The email address to check against.
+ * @param {string[]} aSearchWords - Words in the multi word search string.
+ * @returns {boolean} True if the card matches the search parameters,
+ * false otherwise.
+ */
+ _checkEntry(aCard, aEmailToUse, aSearchWords) {
+ // Joining values of many fields in a single string so that a single
+ // search query can be fired on all of them at once. Separating them
+ // using spaces so that field1=> "abc" and field2=> "def" on joining
+ // shouldn't return true on search for "bcd".
+ // Note: This should be constructed from model query pref using
+ // getModelQuery("mail.addr_book.autocompletequery.format")
+ // but for now we hard-code the default value equivalent of the pref here
+ // or else bail out before and reconstruct the full c++ query if the pref
+ // has been customized (modelQueryHasUserValue), so that we won't get here.
+ let cumulativeFieldText =
+ aCard.displayName +
+ " " +
+ aCard.firstName +
+ " " +
+ aCard.lastName +
+ " " +
+ aEmailToUse +
+ " " +
+ aCard.getProperty("NickName", "");
+ if (aCard.isMailList) {
+ cumulativeFieldText += " " + aCard.getProperty("Notes", "");
+ }
+ cumulativeFieldText = cumulativeFieldText.toLocaleLowerCase();
+
+ return aSearchWords.every(String.prototype.includes, cumulativeFieldText);
+ },
+
+ /**
+ * Checks to see if an emailAddress (name/address) is a duplicate of an
+ * existing entry already in the results. If the emailAddress is found, it
+ * will remove the existing element if the popularity of the new card is
+ * higher than the previous card.
+ *
+ * @param {nsIAbDirectory} directory - The directory that the card is in.
+ * @param {nsIAbCard} card - The card that could be a duplicate.
+ * @param {string} lcEmailAddress - The emailAddress (name/address
+ * combination) to check for duplicates against. Lowercased.
+ * @param {nsIAbAutoCompleteResult} currentResults - The current results list.
+ */
+ _checkDuplicate(directory, card, lcEmailAddress, currentResults) {
+ let existingResult = currentResults._collectedValues.get(lcEmailAddress);
+ if (!existingResult) {
+ return false;
+ }
+
+ let popIndex = this._getPopularityIndex(directory, card);
+ // It's a duplicate, is the new one more popular?
+ if (popIndex > existingResult.popularity) {
+ // Yes it is, so delete this element, return false and allow
+ // _addToResult to sort the new element into the correct place.
+ currentResults._collectedValues.delete(lcEmailAddress);
+ return false;
+ }
+ // Not more popular, but still a duplicate. Return true and _addToResult
+ // will just forget about it.
+ return true;
+ },
+
+ /**
+ * Adds a card to the results list if it isn't a duplicate. The function will
+ * order the results by popularity.
+ *
+ * @param {string} commentColumn - The text to be displayed in the comment
+ * column (if any).
+ * @param {nsIAbDirectory} directory - The directory that the card is in.
+ * @param {nsIAbCard} card - The card being added to the results.
+ * @param {string} emailToUse - The email address from the card that should
+ * be used for this result.
+ * @param {boolean} isPrimaryEmail - Is the emailToUse the primary email?
+ * Set to true if it is the case. For mailing lists set it to true.
+ * @param {nsIAbAutoCompleteResult} result - The result to add the new entry to.
+ */
+ _addToResult(
+ commentColumn,
+ directory,
+ card,
+ emailToUse,
+ isPrimaryEmail,
+ result
+ ) {
+ let mbox = this._parser.makeMailboxObject(
+ card.displayName,
+ card.isMailList
+ ? card.getProperty("Notes", "") || card.displayName
+ : emailToUse
+ );
+ if (!mbox.email) {
+ return;
+ }
+
+ let emailAddress = mbox.toString();
+ let lcEmailAddress = emailAddress.toLocaleLowerCase();
+
+ // If it is a duplicate, then just return and don't add it. The
+ // _checkDuplicate function deals with it all for us.
+ if (this._checkDuplicate(directory, card, lcEmailAddress, result)) {
+ return;
+ }
+
+ result._collectedValues.set(lcEmailAddress, {
+ value: emailAddress,
+ comment: commentColumn,
+ card,
+ isPrimaryEmail,
+ emailToUse,
+ isCompleteResult: true,
+ popularity: this._getPopularityIndex(directory, card),
+ score: this._getScore(card, lcEmailAddress, result.searchString),
+ });
+ },
+
+ // nsIAutoCompleteSearch
+
+ /**
+ * Starts a search based on the given parameters.
+ *
+ * @see nsIAutoCompleteSearch for parameter details.
+ *
+ * It is expected that aSearchParam contains the identity (if any) to use
+ * for determining if an address book should be autocompleted against.
+ */
+ async startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) {
+ let params = aSearchParam ? JSON.parse(aSearchParam) : {};
+ var result = new nsAbAutoCompleteResult(aSearchString);
+ if ("type" in params && !this.applicableHeaders.has(params.type)) {
+ result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ let fullString = aSearchString && aSearchString.trim().toLocaleLowerCase();
+
+ // If the search string is empty, or the user hasn't enabled autocomplete,
+ // then just return no matches or the result ignored.
+ if (!fullString) {
+ result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ // Array of all the terms from the fullString search query
+ // (separated on the basis of spaces or exact terms on the
+ // basis of quotes).
+ let searchWords = getSearchTokens(fullString);
+
+ // Find out about the comment column
+ this._commentColumn = Services.prefs.getIntPref(
+ "mail.autoComplete.commentColumn",
+ 0
+ );
+
+ let asyncDirectories = [];
+
+ if (
+ aPreviousResult instanceof nsIAbAutoCompleteResult &&
+ aSearchString.startsWith(aPreviousResult.searchString) &&
+ aPreviousResult.searchResult == ACR.RESULT_SUCCESS &&
+ !result._modelQueryHasUserValue &&
+ result.modelQuery == aPreviousResult.modelQuery
+ ) {
+ // We have successful previous matches, and model query has not changed since
+ // previous search, therefore just iterate through the list of previous result
+ // entries and reduce as appropriate (via _checkEntry function).
+ // Test for model query change is required: when reverting back from custom to
+ // default query, result._modelQueryHasUserValue==false, but we must bail out.
+ // Todo: However, if autocomplete model query has been customized, we fall
+ // back to using the full query again instead of reducing result list in js;
+ // The full query might be less performant as it's fired against entire AB,
+ // so we should try morphing the query for js. We can't use the _checkEntry
+ // js query yet because it is hardcoded (mimic default model query).
+ // At least we now allow users to customize their autocomplete model query...
+ for (let i = 0; i < aPreviousResult.matchCount; ++i) {
+ if (aPreviousResult.isCompleteResult(i)) {
+ let card = aPreviousResult.getCardAt(i);
+ let email = aPreviousResult.getEmailToUse(i);
+ if (this._checkEntry(card, email, searchWords)) {
+ // Add matches into the results array. We re-sort as needed later.
+ result._searchResults.push({
+ value: aPreviousResult.getValueAt(i),
+ comment: aPreviousResult.getCommentAt(i),
+ card,
+ isPrimaryEmail: card.primaryEmail == email,
+ emailToUse: email,
+ isCompleteResult: true,
+ popularity: parseInt(card.getProperty("PopularityIndex", "0")),
+ score: this._getScore(
+ card,
+ aPreviousResult.getValueAt(i).toLocaleLowerCase(),
+ fullString
+ ),
+ });
+ }
+ }
+ }
+
+ asyncDirectories = aPreviousResult.asyncDirectories;
+ } else {
+ // Construct the search query from pref; using a query means we can
+ // optimise on running the search through c++ which is better for string
+ // comparisons (_checkEntry is relatively slow).
+ // When user's fullstring search expression is a multiword query, search
+ // for each word separately so that each result contains all the words
+ // from the fullstring in the fields of the addressbook card
+ // (see bug 558931 for explanations).
+ // Use helper method to split up search query to multi-word search
+ // query against multiple fields.
+ let searchWords = getSearchTokens(fullString);
+ let searchQuery = generateQueryURI(result.modelQuery, searchWords);
+
+ // Now do the searching
+ // We're not going to bother searching sub-directories, currently the
+ // architecture forces all cards that are in mailing lists to be in ABs as
+ // well, therefore by searching sub-directories (aka mailing lists) we're
+ // just going to find duplicates.
+ for (let dir of this._abManager.directories) {
+ // A failure in one address book should no break the whole search.
+ try {
+ if (dir.useForAutocomplete("idKey" in params ? params.idKey : null)) {
+ await this._searchCards(searchQuery, aSearchString, dir, result);
+ } else if (dir.dirType == Ci.nsIAbManager.ASYNC_DIRECTORY_TYPE) {
+ asyncDirectories.push(dir);
+ }
+ } catch (ex) {
+ console.error(
+ new Components.Exception(
+ `Exception thrown by ${dir.URI}: ${ex.message}`,
+ ex
+ )
+ );
+ }
+ }
+
+ result._searchResults = [...result._collectedValues.values()];
+ // Make sure a result with direct email match will be the one used.
+ for (let sr of result._searchResults) {
+ if (sr.emailToUse == fullString.replace(/.*<(.+@.+)>$/, "$1")) {
+ sr.score = 100;
+ }
+ }
+ }
+
+ // Sort the results. Scoring may have changed so do it even if this is
+ // just filtered previous results. Only local results are sorted,
+ // because the autocomplete widget doesn't let us alter the order of
+ // results that have already been notified.
+ result._searchResults.sort(function (a, b) {
+ // Order by 1) descending score, then 2) descending popularity,
+ // then 3) any emails that actually match the search string,
+ // 4) primary email before secondary for the same card, then
+ // 5) by emails sorted alphabetically.
+ return (
+ b.score - a.score ||
+ b.popularity - a.popularity ||
+ (b.emailToUse.includes(aSearchString) &&
+ !a.emailToUse.includes(aSearchString)
+ ? 1
+ : 0) ||
+ (a.card == b.card && a.isPrimaryEmail ? -1 : 0) ||
+ a.value.localeCompare(b.value)
+ );
+ });
+
+ if (result.matchCount) {
+ result.searchResult = ACR.RESULT_SUCCESS;
+ result.defaultIndex = 0;
+ }
+
+ if (!asyncDirectories.length) {
+ // We're done. Just return our result immediately.
+ aListener.onSearchResult(this, result);
+ return;
+ }
+
+ // Let the widget know the sync results we have so far.
+ result.searchResult = result.matchCount
+ ? ACR.RESULT_SUCCESS_ONGOING
+ : ACR.RESULT_NOMATCH_ONGOING;
+ aListener.onSearchResult(this, result);
+
+ // Start searching our asynchronous autocomplete directories.
+ this._result = result;
+ let searches = new Set();
+ for (let dir of asyncDirectories) {
+ let comment = this._commentColumn == 1 ? dir.dirName : "";
+ let cards = [];
+ let searchListener = {
+ onSearchFoundCard: card => {
+ cards.push(card);
+ },
+ onSearchFinished: (status, isCompleteResult, secInfo, location) => {
+ if (this._result != result) {
+ // The search was aborted, so give up.
+ return;
+ }
+ searches.delete(searchListener);
+ if (cards.length) {
+ // Avoid overwhelming the UI with excessive results.
+ if (cards.length > MAX_ASYNC_RESULTS) {
+ cards.length = MAX_ASYNC_RESULTS;
+ isCompleteResult = false;
+ }
+ // We can't guarantee to score the extension's results accurately so
+ // we assume that the extension has sorted the results appropriately
+ for (let card of cards) {
+ let emailToUse = card.primaryEmail;
+ let value = MailServices.headerParser
+ .makeMailboxObject(card.displayName, emailToUse)
+ .toString();
+ result._searchResults.push({
+ value,
+ comment,
+ card,
+ emailToUse,
+ isCompleteResult,
+ });
+ }
+ if (!isCompleteResult) {
+ // Next time perform a full search again to get better results.
+ result.asyncDirectories.push(dir);
+ }
+ }
+ if (result._searchResults.length) {
+ result.searchResult = searches.size
+ ? ACR.RESULT_SUCCESS_ONGOING
+ : ACR.RESULT_SUCCESS;
+ result.defaultIndex = 0;
+ } else {
+ result.searchResult = searches.size
+ ? ACR.RESULT_NOMATCH_ONGOING
+ : ACR.RESULT_NOMATCH;
+ }
+ aListener.onSearchResult(this, result);
+ },
+ };
+ // Keep track of the pending searches so that we know when we've finished.
+ searches.add(searchListener);
+ dir.search(null, aSearchString, searchListener);
+ }
+ },
+
+ stopSearch() {
+ this._result = null;
+ },
+
+ // nsISupports
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteSearch"]),
+};
diff --git a/comm/mailnews/addrbook/src/AbLDAPAttributeMap.jsm b/comm/mailnews/addrbook/src/AbLDAPAttributeMap.jsm
new file mode 100644
index 0000000000..dae7b97630
--- /dev/null
+++ b/comm/mailnews/addrbook/src/AbLDAPAttributeMap.jsm
@@ -0,0 +1,219 @@
+/* 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 EXPORTED_SYMBOLS = ["AbLDAPAttributeMap", "AbLDAPAttributeMapService"];
+
+function AbLDAPAttributeMap() {
+ this.mPropertyMap = {};
+ this.mAttrMap = {};
+}
+
+AbLDAPAttributeMap.prototype = {
+ getAttributeList(aProperty) {
+ if (!(aProperty in this.mPropertyMap)) {
+ return null;
+ }
+
+ // return the joined list
+ return this.mPropertyMap[aProperty].join(",");
+ },
+
+ getAttributes(aProperty) {
+ // fail if no entry for this
+ if (!(aProperty in this.mPropertyMap)) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+ return this.mPropertyMap[aProperty];
+ },
+
+ getFirstAttribute(aProperty) {
+ // fail if no entry for this
+ if (!(aProperty in this.mPropertyMap)) {
+ return null;
+ }
+
+ return this.mPropertyMap[aProperty][0]?.replace(/\[(\d+)\]$/, "");
+ },
+
+ setAttributeList(aProperty, aAttributeList, aAllowInconsistencies) {
+ var attrs = aAttributeList.split(",");
+
+ // check to make sure this call won't allow multiple mappings to be
+ // created, if requested
+ if (!aAllowInconsistencies) {
+ for (var attr of attrs) {
+ if (attr in this.mAttrMap && this.mAttrMap[attr] != aProperty) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+ }
+ }
+
+ // delete any attr mappings created by the existing property map entry
+ if (aProperty in this.mPropertyMap) {
+ for (attr of this.mPropertyMap[aProperty]) {
+ delete this.mAttrMap[attr];
+ }
+ }
+
+ // add these attrs to the attrmap
+ for (attr of attrs) {
+ this.mAttrMap[attr] = aProperty;
+ }
+
+ // add them to the property map
+ this.mPropertyMap[aProperty] = attrs;
+ },
+
+ getProperty(aAttribute) {
+ if (!(aAttribute in this.mAttrMap)) {
+ return null;
+ }
+
+ return this.mAttrMap[aAttribute];
+ },
+
+ getAllCardAttributes() {
+ var attrs = [];
+ for (let attrArray of Object.entries(this.mPropertyMap)) {
+ for (let attrName of attrArray) {
+ attrName = attrName.toString().replace(/\[(\d+)\]$/, "");
+ if (attrs.includes(attrName)) {
+ continue;
+ }
+ attrs.push(attrName);
+ }
+ }
+
+ if (!attrs.length) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+
+ return attrs.join(",");
+ },
+
+ getAllCardProperties() {
+ var props = [];
+ for (var prop in this.mPropertyMap) {
+ props.push(prop);
+ }
+ return props;
+ },
+
+ setFromPrefs(aPrefBranchName) {
+ // get the right pref branch
+ let branch = Services.prefs.getBranch(aPrefBranchName + ".");
+
+ // get the list of children
+ var children = branch.getChildList("");
+
+ // do the actual sets
+ for (var child of children) {
+ this.setAttributeList(child, branch.getCharPref(child), true);
+ }
+
+ // ensure that everything is kosher
+ this.checkState();
+ },
+
+ setCardPropertiesFromLDAPMessage(aMessage, aCard) {
+ var cardValueWasSet = false;
+
+ var msgAttrs = aMessage.getAttributes();
+
+ // downcase the array for comparison
+ function toLower(a) {
+ return a.toLowerCase();
+ }
+ msgAttrs = msgAttrs.map(toLower);
+
+ // deal with each addressbook property
+ for (var prop in this.mPropertyMap) {
+ // go through the list of possible attrs in precedence order
+ for (var attr of this.mPropertyMap[prop]) {
+ attr = attr.toLowerCase();
+ // allow an index in attr
+ let valueIndex = 0;
+ const valueIndexMatch = /^(.+)\[(\d+)\]$/.exec(attr);
+ if (valueIndexMatch !== null) {
+ attr = valueIndexMatch[1];
+ valueIndex = parseInt(valueIndexMatch[2]);
+ }
+
+ // find the first attr that exists in this message
+ if (msgAttrs.includes(attr)) {
+ try {
+ var values = aMessage.getValues(attr);
+ // strip out the optional label from the labeledURI
+ if (attr == "labeleduri" && values[valueIndex]) {
+ var index = values[valueIndex].indexOf(" ");
+ if (index != -1) {
+ values[valueIndex] = values[valueIndex].substring(0, index);
+ }
+ }
+ aCard.setProperty(prop, values[valueIndex]);
+
+ cardValueWasSet = true;
+ break;
+ } catch (ex) {
+ // ignore any errors getting message values or setting card values
+ }
+ }
+ }
+ }
+
+ if (!cardValueWasSet) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+ },
+
+ checkState() {
+ var attrsSeen = [];
+
+ for (var prop in this.mPropertyMap) {
+ let attrArray = this.mPropertyMap[prop];
+ for (var attr of attrArray) {
+ // multiple attributes that mapped to the empty string are permitted
+ if (!attr.length) {
+ continue;
+ }
+
+ // if we've seen this before, there's a problem
+ if (attrsSeen.includes(attr)) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+
+ // remember that we've seen it now
+ attrsSeen.push(attr);
+ }
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAbLDAPAttributeMap"]),
+};
+
+function AbLDAPAttributeMapService() {}
+
+AbLDAPAttributeMapService.prototype = {
+ mAttrMaps: {},
+
+ getMapForPrefBranch(aPrefBranchName) {
+ // if we've already got this map, return it
+ if (aPrefBranchName in this.mAttrMaps) {
+ return this.mAttrMaps[aPrefBranchName];
+ }
+
+ // otherwise, try and create it
+ var attrMap = new AbLDAPAttributeMap();
+ attrMap.setFromPrefs("ldap_2.servers.default.attrmap");
+ attrMap.setFromPrefs(aPrefBranchName + ".attrmap");
+
+ // cache
+ this.mAttrMaps[aPrefBranchName] = attrMap;
+
+ // and return
+ return attrMap;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAbLDAPAttributeMapService"]),
+};
diff --git a/comm/mailnews/addrbook/src/AbLDAPAutoCompleteSearch.jsm b/comm/mailnews/addrbook/src/AbLDAPAutoCompleteSearch.jsm
new file mode 100644
index 0000000000..8df1c0f71e
--- /dev/null
+++ b/comm/mailnews/addrbook/src/AbLDAPAutoCompleteSearch.jsm
@@ -0,0 +1,364 @@
+/* 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 EXPORTED_SYMBOLS = ["AbLDAPAutoCompleteSearch"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var ACR = Ci.nsIAutoCompleteResult;
+
+// nsAbLDAPAutoCompleteResult
+// Derived from nsIAbAutoCompleteResult, provides a LDAP specific result
+// implementation.
+
+function nsAbLDAPAutoCompleteResult(aSearchString) {
+ // Can't create this in the prototype as we'd get the same array for
+ // all instances
+ this._searchResults = [];
+ this.searchString = aSearchString;
+}
+
+nsAbLDAPAutoCompleteResult.prototype = {
+ _searchResults: null,
+ _commentColumn: "",
+
+ // nsIAutoCompleteResult
+
+ searchString: null,
+ searchResult: ACR.RESULT_NOMATCH,
+ defaultIndex: -1,
+ errorDescription: null,
+
+ get matchCount() {
+ return this._searchResults.length;
+ },
+
+ getLabelAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ getValueAt(aIndex) {
+ return this._searchResults[aIndex].value;
+ },
+
+ getCommentAt(aIndex) {
+ return this._commentColumn;
+ },
+
+ getStyleAt(aIndex) {
+ return this.searchResult == ACR.RESULT_FAILURE
+ ? "remote-err"
+ : "remote-abook";
+ },
+
+ getImageAt(aIndex) {
+ return "";
+ },
+
+ getFinalCompleteValueAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt(aRowIndex, aRemoveFromDB) {},
+
+ // nsIAbAutoCompleteResult
+
+ getCardAt(aIndex) {
+ return this._searchResults[aIndex].card;
+ },
+
+ // nsISupports
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIAutoCompleteResult",
+ "nsIAbAutoCompleteResult",
+ ]),
+};
+
+function AbLDAPAutoCompleteSearch() {
+ Services.obs.addObserver(this, "quit-application");
+ this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+}
+
+AbLDAPAutoCompleteSearch.prototype = {
+ // A short-lived LDAP directory cache.
+ // To avoid recreating components as the user completes, we maintain the most
+ // recently used address book, nsAbLDAPDirectoryQuery and search context.
+ // However the cache is discarded if it has not been used for a minute.
+ // This is done to avoid problems with LDAP sessions timing out and hanging.
+ _query: null,
+ _book: null,
+ _attributes: null,
+ _context: -1,
+ _timer: null,
+
+ // The current search result.
+ _result: null,
+ // The listener to pass back results to.
+ _listener: null,
+
+ _parser: MailServices.headerParser,
+
+ applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+ // Private methods
+
+ _checkDuplicate(card, emailAddress) {
+ var lcEmailAddress = emailAddress.toLocaleLowerCase();
+
+ return this._result._searchResults.some(function (result) {
+ return result.value.toLocaleLowerCase() == lcEmailAddress;
+ });
+ },
+
+ _addToResult(card, address) {
+ let mbox = this._parser.makeMailboxObject(
+ card.displayName,
+ card.isMailList
+ ? card.getProperty("Notes", "") || card.displayName
+ : address
+ );
+ if (!mbox.email) {
+ return;
+ }
+
+ let emailAddress = mbox.toString();
+
+ // If it is a duplicate, then just return and don't add it. The
+ // _checkDuplicate function deals with it all for us.
+ if (this._checkDuplicate(card, emailAddress)) {
+ return;
+ }
+
+ // Find out where to insert the card.
+ var insertPosition = 0;
+
+ // Next sort on full address
+ while (
+ insertPosition < this._result._searchResults.length &&
+ emailAddress > this._result._searchResults[insertPosition].value
+ ) {
+ ++insertPosition;
+ }
+
+ this._result._searchResults.splice(insertPosition, 0, {
+ value: emailAddress,
+ card,
+ });
+ },
+
+ // nsIObserver
+
+ observe(subject, topic, data) {
+ if (topic == "quit-application") {
+ Services.obs.removeObserver(this, "quit-application");
+ } else if (topic != "timer-callback") {
+ return;
+ }
+
+ // Force the individual query items to null, so that the memory
+ // gets collected straight away.
+ this.stopSearch();
+ this._book = null;
+ this._context = -1;
+ this._query = null;
+ this._attributes = null;
+ },
+
+ // nsIAutoCompleteSearch
+
+ startSearch(aSearchString, aParam, aPreviousResult, aListener) {
+ let params = JSON.parse(aParam) || {};
+ let applicable =
+ !("type" in params) || this.applicableHeaders.has(params.type);
+
+ this._result = new nsAbLDAPAutoCompleteResult(aSearchString);
+ aSearchString = aSearchString.toLocaleLowerCase();
+
+ // If the search string isn't value, or contains a comma, or the user
+ // hasn't enabled autocomplete, then just return no matches / or the
+ // result ignored.
+ // The comma check is so that we don't autocomplete against the user
+ // entering multiple addresses.
+ if (!applicable || !aSearchString || aSearchString.includes(",")) {
+ this._result.searchResult = ACR.RESULT_IGNORED;
+ aListener.onSearchResult(this, this._result);
+ return;
+ }
+
+ // The rules here: If the current identity has a directoryServer set, then
+ // use that, otherwise, try the global preference instead.
+ var acDirURI = null;
+ var identity;
+
+ if ("idKey" in params) {
+ try {
+ identity = MailServices.accounts.getIdentity(params.idKey);
+ } catch (ex) {
+ console.error(
+ "Couldn't get specified identity, " +
+ "falling back to global settings"
+ );
+ }
+ }
+
+ // Does the current identity override the global preference?
+ if (identity && identity.overrideGlobalPref) {
+ acDirURI = identity.directoryServer;
+ } else if (Services.prefs.getBoolPref("ldap_2.autoComplete.useDirectory")) {
+ // Try the global one
+ acDirURI = Services.prefs.getCharPref(
+ "ldap_2.autoComplete.directoryServer"
+ );
+ }
+
+ if (!acDirURI || Services.io.offline) {
+ // No directory to search or we are offline, send a no match and return.
+ aListener.onSearchResult(this, this._result);
+ return;
+ }
+
+ this.stopSearch();
+
+ // If we don't already have a cached query for this URI, build a new one.
+ acDirURI = "moz-abldapdirectory://" + acDirURI;
+ if (!this._book || this._book.URI != acDirURI) {
+ this._query = Cc[
+ "@mozilla.org/addressbook/ldap-directory-query;1"
+ ].createInstance(Ci.nsIAbDirectoryQuery);
+ this._book = MailServices.ab
+ .getDirectory(acDirURI)
+ .QueryInterface(Ci.nsIAbLDAPDirectory);
+
+ // Create a minimal map just for the display name and primary email.
+ this._attributes = Cc[
+ "@mozilla.org/addressbook/ldap-attribute-map;1"
+ ].createInstance(Ci.nsIAbLDAPAttributeMap);
+ this._attributes.setAttributeList(
+ "DisplayName",
+ this._book.attributeMap.getAttributeList("DisplayName", {}),
+ true
+ );
+ this._attributes.setAttributeList(
+ "PrimaryEmail",
+ this._book.attributeMap.getAttributeList("PrimaryEmail", {}),
+ true
+ );
+ this._attributes.setAttributeList(
+ "SecondEmail",
+ this._book.attributeMap.getAttributeList("SecondEmail", {}),
+ true
+ );
+ }
+
+ this._result._commentColumn = this._book.dirName;
+ this._listener = aListener;
+ this._timer.init(this, 60000, Ci.nsITimer.TYPE_ONE_SHOT);
+
+ var args = Cc[
+ "@mozilla.org/addressbook/directory/query-arguments;1"
+ ].createInstance(Ci.nsIAbDirectoryQueryArguments);
+
+ var filterTemplate = this._book.getStringValue(
+ "autoComplete.filterTemplate",
+ ""
+ );
+
+ // Use default value when preference is not set or it contains empty string
+ if (!filterTemplate) {
+ filterTemplate =
+ "(|(cn=*%v1*%v2-*)(mail=*%v*)(givenName=*%v1*)(sn=*%v*))";
+ }
+
+ // Create filter from filter template and search string
+ var ldapSvc = Cc["@mozilla.org/network/ldap-service;1"].getService(
+ Ci.nsILDAPService
+ );
+ var filter = ldapSvc.createFilter(
+ 1024,
+ filterTemplate,
+ "",
+ "",
+ "",
+ aSearchString
+ );
+ if (!filter) {
+ throw new Error(
+ "Filter string is empty, check if filterTemplate variable is valid in prefs.js."
+ );
+ }
+ args.typeSpecificArg = this._attributes;
+ args.querySubDirectories = true;
+ args.filter = filter;
+
+ // Start the actual search
+ this._context = this._query.doQuery(
+ this._book,
+ args,
+ this,
+ this._book.maxHits,
+ 0
+ );
+ },
+
+ stopSearch() {
+ if (this._listener) {
+ this._query.stopQuery(this._context);
+ this._listener = null;
+ }
+ },
+
+ // nsIAbDirSearchListener
+
+ onSearchFinished(status, complete, secInfo, location) {
+ if (!this._listener) {
+ return;
+ }
+
+ if (status == Cr.NS_OK) {
+ if (this._result.matchCount) {
+ this._result.searchResult = ACR.RESULT_SUCCESS;
+ this._result.defaultIndex = 0;
+ } else {
+ this._result.searchResult = ACR.RESULT_NOMATCH;
+ }
+ } else {
+ this._result.searchResult = ACR.RESULT_FAILURE;
+ this._result.defaultIndex = 0;
+ }
+ // const long queryResultStopped = 2;
+ // const long queryResultError = 3;
+ this._listener.onSearchResult(this, this._result);
+ this._listener = null;
+ },
+
+ onSearchFoundCard(aCard) {
+ if (!this._listener) {
+ return;
+ }
+
+ for (let emailAddress of aCard.emailAddresses) {
+ this._addToResult(aCard, emailAddress);
+ }
+
+ /* XXX autocomplete doesn't expect you to rearrange while searching
+ if (this._result.matchCount) {
+ this._result.searchResult = ACR.RESULT_SUCCESS_ONGOING;
+ } else {
+ this._result.searchResult = ACR.RESULT_NOMATCH_ONGOING;
+ }
+ this._listener.onSearchResult(this, this._result);
+ */
+ },
+
+ // nsISupports
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsIAutoCompleteSearch",
+ "nsIAbDirSearchListener",
+ ]),
+};
diff --git a/comm/mailnews/addrbook/src/components.conf b/comm/mailnews/addrbook/src/components.conf
new file mode 100644
index 0000000000..622fba5951
--- /dev/null
+++ b/comm/mailnews/addrbook/src/components.conf
@@ -0,0 +1,129 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ "cid": "{5b259db2-e451-4de9-8a6f-cfba91402973}",
+ "contract_ids": ["@mozilla.org/autocomplete/search;1?name=mydomain"],
+ "jsm": "resource:///modules/AbAutoCompleteMyDomain.jsm",
+ "constructor": "AbAutoCompleteMyDomain",
+ },
+ {
+ "cid": "{2f946df9-114c-41fe-8899-81f10daf4f0c}",
+ "contract_ids": ["@mozilla.org/autocomplete/search;1?name=addrbook"],
+ "jsm": "resource:///modules/AbAutoCompleteSearch.jsm",
+ "constructor": "AbAutoCompleteSearch",
+ },
+ {
+ "cid": "{127b341a-bdda-4270-85e1-edff569a9b85}",
+ "contract_ids": ["@mozilla.org/addressbook/ldap-attribute-map;1"],
+ "jsm": "resource:///modules/AbLDAPAttributeMap.jsm",
+ "constructor": "AbLDAPAttributeMap",
+ },
+ {
+ "cid": "{4ed7d5e1-8800-40da-9e78-c4f509d7ac5e}",
+ "contract_ids": ["@mozilla.org/addressbook/ldap-attribute-map-service;1"],
+ "jsm": "resource:///modules/AbLDAPAttributeMap.jsm",
+ "constructor": "AbLDAPAttributeMapService",
+ },
+ {
+ "cid": "{227e6482-fe9f-441f-9b7d-7b60375e7449}",
+ "contract_ids": ["@mozilla.org/autocomplete/search;1?name=ldap"],
+ "jsm": "resource:///modules/AbLDAPAutoCompleteSearch.jsm",
+ "constructor": "AbLDAPAutoCompleteSearch",
+ },
+ {
+ "cid": "{cb7c67f8-0053-4072-89e9-501cbd1b35ab}",
+ "contract_ids": ["@mozilla.org/network/ldap-url;1"],
+ "type": "nsLDAPURL",
+ "headers": ["/comm/mailnews/addrbook/src/nsLDAPURL.h"],
+ },
+ {
+ "cid": "{2b722171-2cea-11d3-9e0b-00a0c92b5f0d}",
+ "contract_ids": ["@mozilla.org/addressbook/cardproperty;1"],
+ "type": "nsAbCardProperty",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbCardProperty.h"],
+ },
+ {
+ "cid": "{6fd8ec67-3965-11d3-a316-001083003d0c}",
+ "contract_ids": ["@mozilla.org/addressbook/directoryproperty;1"],
+ "type": "nsAbDirProperty",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbDirProperty.h"],
+ },
+ {
+ "cid": "{e7702d5a-99d8-4648-bab7-919ea29f30b6}",
+ "contract_ids": ["@mozilla.org/addressbook/services/addressCollector;1"],
+ "type": "nsAbAddressCollector",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbAddressCollector.h"],
+ },
+ {
+ "cid": "{f7dc2aeb-8e62-4750-965c-24b9e09ed8d2}",
+ "contract_ids": ["@mozilla.org/addressbook/directory/query-arguments;1"],
+ "type": "nsAbDirectoryQueryArguments",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbDirectoryQuery.h"],
+ },
+ {
+ "cid": "{ca1944a9-527e-4c77-895d-d0466dd41cf5}",
+ "contract_ids": ["@mozilla.org/boolean-expression/condition-string;1"],
+ "type": "nsAbBooleanConditionString",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbBooleanExpression.h"],
+ },
+ {
+ "cid": "{2c2e75c8-6f56-4a50-af1c-72af5d0e8d41}",
+ "contract_ids": ["@mozilla.org/boolean-expression/n-peer;1"],
+ "type": "nsAbBooleanExpression",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbBooleanExpression.h"],
+ },
+ {
+ "cid": "{e162e335-541b-43b4-aaea-fe591e240caf}",
+ "contract_ids": ["@mozilla.org/addressbook/directory-query/proxy;1"],
+ "type": "nsAbDirectoryQueryProxy",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h"],
+ },
+ {
+ "cid": "{db6f46da-8de3-478d-b539-801398656cf6}",
+ "contract_ids": ["@mozilla.org/addressbook/abldifservice;1"],
+ "type": "nsAbLDIFService",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbLDIFService.h"],
+ },
+]
+
+if buildconfig.substs["OS_ARCH"] == "Darwin":
+ Classes += [
+ {
+ "cid": "{83781cc6-c682-11d6-bdeb-0005024967b8}",
+ "contract_ids": [
+ "@mozilla.org/addressbook/directory;1?type=moz-abosxdirectory"
+ ],
+ "type": "nsAbOSXDirectory",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbOSXDirectory.h"],
+ },
+ {
+ "cid": "{89bbf582-c682-11d6-bc9d-0005024967b8}",
+ "contract_ids": ["@mozilla.org/addressbook/directory;1?type=moz-abosxcard"],
+ "type": "nsAbOSXCard",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbOSXCard.h"],
+ },
+ ]
+
+if buildconfig.substs["OS_ARCH"] == "WINNT" and buildconfig.substs["MOZ_MAPI_SUPPORT"]:
+ Classes += [
+ {
+ "cid": "{9cc57822-0599-4c47-a399-1c6fa185a05c}",
+ "contract_ids": [
+ "@mozilla.org/addressbook/directory;1?type=moz-aboutlookdirectory"
+ ],
+ "type": "nsAbOutlookDirectory",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbOutlookDirectory.h"],
+ },
+ {
+ "cid": "{558ccc0f-2681-4dac-a066-debd8d26faf6}",
+ "contract_ids": ["@mozilla.org/addressbook/outlookinterface;1"],
+ "type": "nsAbOutlookInterface",
+ "headers": ["/comm/mailnews/addrbook/src/nsAbOutlookInterface.h"],
+ },
+ ]
diff --git a/comm/mailnews/addrbook/src/moz.build b/comm/mailnews/addrbook/src/moz.build
new file mode 100644
index 0000000000..f7fbe7c58f
--- /dev/null
+++ b/comm/mailnews/addrbook/src/moz.build
@@ -0,0 +1,49 @@
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS += [
+ "nsAbDirProperty.h",
+]
+
+SOURCES += [
+ "nsAbAddressCollector.cpp",
+ "nsAbBooleanExpression.cpp",
+ "nsAbCardProperty.cpp",
+ "nsAbDirectoryQuery.cpp",
+ "nsAbDirectoryQueryProxy.cpp",
+ "nsAbDirProperty.cpp",
+ "nsAbLDIFService.cpp",
+ "nsAbQueryStringToExpression.cpp",
+ "nsLDAPURL.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["MOZ_MAPI_SUPPORT"]:
+ SOURCES += [
+ "nsAbOutlookDirectory.cpp",
+ "nsAbOutlookInterface.cpp",
+ "nsAbWinHelper.cpp",
+ "nsMapiAddressBook.cpp",
+ ]
+ LOCAL_INCLUDES += ["/comm/mailnews/mapi/include"]
+
+if CONFIG["OS_ARCH"] == "Darwin":
+ SOURCES += [
+ "nsAbOSXCard.mm",
+ "nsAbOSXDirectory.mm",
+ "nsAbOSXUtils.mm",
+ ]
+
+EXTRA_JS_MODULES += [
+ "AbAutoCompleteMyDomain.jsm",
+ "AbAutoCompleteSearch.jsm",
+ "AbLDAPAttributeMap.jsm",
+ "AbLDAPAutoCompleteSearch.jsm",
+]
+
+FINAL_LIBRARY = "mail"
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
diff --git a/comm/mailnews/addrbook/src/nsAbAddressCollector.cpp b/comm/mailnews/addrbook/src/nsAbAddressCollector.cpp
new file mode 100644
index 0000000000..09018daf96
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbAddressCollector.cpp
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "msgCore.h" // for pre-compiled headers
+#include "nsISimpleEnumerator.h"
+
+#include "nsIAbCard.h"
+#include "nsAbAddressCollector.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsString.h"
+#include "prmem.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAbManager.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+NS_IMPL_ISUPPORTS(nsAbAddressCollector, nsIAbAddressCollector, nsIObserver)
+
+#define PREF_MAIL_COLLECT_ADDRESSBOOK "mail.collect_addressbook"
+
+nsAbAddressCollector::nsAbAddressCollector() {}
+
+nsAbAddressCollector::~nsAbAddressCollector() {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranchInt(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ pPrefBranchInt->RemoveObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this);
+}
+
+/**
+ * Returns the first card found with the specified email address. This
+ * returns an already addrefed pointer to the card if the card is found.
+ */
+already_AddRefed<nsIAbCard> nsAbAddressCollector::GetCardForAddress(
+ const char* aProperty, const nsACString& aEmailAddress,
+ nsIAbDirectory** aDirectory) {
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsTArray<RefPtr<nsIAbDirectory>> directories;
+ rv = abManager->GetDirectories(directories);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIAbCard> result;
+ uint32_t count = directories.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ // Some implementations may return NS_ERROR_NOT_IMPLEMENTED here,
+ // so just catch the value and continue.
+ if (NS_FAILED(directories[i]->GetCardFromProperty(
+ aProperty, aEmailAddress, false, getter_AddRefs(result)))) {
+ continue;
+ }
+
+ if (result) {
+ if (aDirectory) directories[i].forget(aDirectory);
+ return result.forget();
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsAbAddressCollector::CollectAddress(const nsACString& aAddresses,
+ bool aCreateCard) {
+ // If we've not got a valid directory, no point in going any further
+ if (!mDirectory) return NS_OK;
+
+ // note that we're now setting the whole recipient list,
+ // not just the pretty name of the first recipient.
+ nsTArray<nsCString> names;
+ nsTArray<nsCString> addresses;
+ ExtractAllAddresses(EncodedHeader(aAddresses), UTF16ArrayAdapter<>(names),
+ UTF16ArrayAdapter<>(addresses));
+ uint32_t numAddresses = names.Length();
+
+ for (uint32_t i = 0; i < numAddresses; i++) {
+ // Don't allow collection of addresses with no email address, it makes
+ // no sense. Whilst we should never get here in most normal cases, we
+ // should still be careful.
+ if (addresses[i].IsEmpty()) continue;
+
+ CollectSingleAddress(addresses[i], names[i], aCreateCard, false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbAddressCollector::CollectSingleAddress(const nsACString& aEmail,
+ const nsACString& aDisplayName,
+ bool aCreateCard,
+ bool aSkipCheckExisting) {
+ if (!mDirectory) return NS_OK;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbDirectory> originDirectory;
+ nsCOMPtr<nsIAbCard> card;
+ if (!aSkipCheckExisting) {
+ card = GetCardForAddress(kPriEmailProperty, aEmail,
+ getter_AddRefs(originDirectory));
+
+ // If a card has aEmail, but it's the secondary address, we don't want to
+ // update any properties, so just return.
+ if (!card) {
+ card = GetCardForAddress(k2ndEmailProperty, aEmail,
+ getter_AddRefs(originDirectory));
+ if (card) return NS_OK;
+ }
+ }
+
+ if (!card && (aCreateCard || aSkipCheckExisting)) {
+ card = do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv);
+ if (NS_SUCCEEDED(rv) && card) {
+ // Set up the fields for the new card.
+ SetNamesForCard(card, aDisplayName);
+ AutoCollectScreenName(card, aEmail);
+
+ if (NS_SUCCEEDED(card->SetPrimaryEmail(NS_ConvertUTF8toUTF16(aEmail)))) {
+ nsCOMPtr<nsIAbCard> addedCard;
+ rv = mDirectory->AddCard(card, getter_AddRefs(addedCard));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add card");
+ }
+ }
+ } else if (card && originDirectory) {
+ // It could be that the origin directory is read-only, so don't try and
+ // write to it if it is.
+ bool readOnly;
+ rv = originDirectory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (readOnly) return NS_OK;
+
+ // address is already in the AB, so update the names
+ bool modifiedCard = false;
+
+ nsString displayName;
+ card->GetDisplayName(displayName);
+ // If we already have a display name, don't set the names on the card.
+ if (displayName.IsEmpty() && !aDisplayName.IsEmpty())
+ modifiedCard = SetNamesForCard(card, aDisplayName);
+
+ if (modifiedCard) originDirectory->ModifyCard(card);
+ }
+
+ return NS_OK;
+}
+
+// Works out the screen name to put on the card for some well-known addresses
+void nsAbAddressCollector::AutoCollectScreenName(nsIAbCard* aCard,
+ const nsACString& aEmail) {
+ if (!aCard) return;
+
+ int32_t atPos = aEmail.FindChar('@');
+ if (atPos == -1) return;
+
+ const nsACString& domain = Substring(aEmail, atPos + 1);
+
+ if (domain.IsEmpty()) return;
+ // username in
+ // username@aol.com (America Online)
+ // username@cs.com (Compuserve)
+ // username@netscape.net (Netscape webmail)
+ // are all AIM screennames. autocollect that info.
+ if (domain.EqualsLiteral("aol.com") || domain.EqualsLiteral("cs.com") ||
+ domain.EqualsLiteral("netscape.net"))
+ aCard->SetPropertyAsAUTF8String(kScreenNameProperty,
+ Substring(aEmail, 0, atPos));
+ else if (domain.EqualsLiteral("gmail.com") ||
+ domain.EqualsLiteral("googlemail.com"))
+ aCard->SetPropertyAsAUTF8String(kGtalkProperty,
+ Substring(aEmail, 0, atPos));
+}
+
+// Returns true if the card was modified successfully.
+bool nsAbAddressCollector::SetNamesForCard(nsIAbCard* aSenderCard,
+ const nsACString& aFullName) {
+ nsCString firstName;
+ nsCString lastName;
+ bool modifiedCard = false;
+
+ if (NS_SUCCEEDED(
+ aSenderCard->SetDisplayName(NS_ConvertUTF8toUTF16(aFullName))))
+ modifiedCard = true;
+
+ // Now split up the full name.
+ SplitFullName(nsCString(aFullName), firstName, lastName);
+
+ if (!firstName.IsEmpty() &&
+ NS_SUCCEEDED(aSenderCard->SetFirstName(NS_ConvertUTF8toUTF16(firstName))))
+ modifiedCard = true;
+
+ if (!lastName.IsEmpty() &&
+ NS_SUCCEEDED(aSenderCard->SetLastName(NS_ConvertUTF8toUTF16(lastName))))
+ modifiedCard = true;
+
+ if (modifiedCard) aSenderCard->SetPropertyAsBool("PreferDisplayName", false);
+
+ return modifiedCard;
+}
+
+// Splits the first and last name based on the space between them.
+void nsAbAddressCollector::SplitFullName(const nsCString& aFullName,
+ nsCString& aFirstName,
+ nsCString& aLastName) {
+ int index = aFullName.RFindChar(' ');
+ if (index != -1) {
+ aLastName = Substring(aFullName, index + 1);
+ aFirstName = Substring(aFullName, 0, index);
+ }
+}
+
+// Observes the collected address book pref in case it changes.
+NS_IMETHODIMP
+nsAbAddressCollector::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ if (!prefBranch) {
+ NS_ASSERTION(prefBranch, "failed to get prefs");
+ return NS_OK;
+ }
+
+ SetUpAbFromPrefs(prefBranch);
+ return NS_OK;
+}
+
+// Initialises the collector with the required items.
+nsresult nsAbAddressCollector::Init(void) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = prefBranch->AddObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetUpAbFromPrefs(prefBranch);
+ return NS_OK;
+}
+
+// Performs the necessary changes to set up the collector for the specified
+// collected address book.
+void nsAbAddressCollector::SetUpAbFromPrefs(nsIPrefBranch* aPrefBranch) {
+ nsCString abURI;
+ aPrefBranch->GetCharPref(PREF_MAIL_COLLECT_ADDRESSBOOK, abURI);
+
+ if (abURI.IsEmpty()) abURI.AssignLiteral(kPersonalAddressbookUri);
+
+ if (abURI == mABURI) return;
+
+ mDirectory = nullptr;
+ mABURI = abURI;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = abManager->GetDirectory(mABURI, getter_AddRefs(mDirectory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ bool readOnly;
+ rv = mDirectory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // If the directory is read-only, we can't write to it, so just blank it out
+ // here, and warn because we shouldn't hit this (UI is wrong).
+ if (readOnly) {
+ NS_ERROR(
+ "Address Collection book preferences is set to a read-only book. "
+ "Address collection will not take place.");
+ mDirectory = nullptr;
+ }
+}
diff --git a/comm/mailnews/addrbook/src/nsAbAddressCollector.h b/comm/mailnews/addrbook/src/nsAbAddressCollector.h
new file mode 100644
index 0000000000..a2bfcaf802
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbAddressCollector.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _nsAbAddressCollector_H_
+#define _nsAbAddressCollector_H_
+
+#include "nsIAbAddressCollector.h"
+#include "nsCOMPtr.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbCard.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+
+class nsIPrefBranch;
+
+class nsAbAddressCollector : public nsIAbAddressCollector, public nsIObserver {
+ public:
+ nsAbAddressCollector();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABADDRESSCOLLECTOR
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ private:
+ virtual ~nsAbAddressCollector();
+ already_AddRefed<nsIAbCard> GetCardForAddress(const char* aProperty,
+ const nsACString& aEmailAddress,
+ nsIAbDirectory** aDirectory);
+ void AutoCollectScreenName(nsIAbCard* aCard, const nsACString& aEmail);
+ bool SetNamesForCard(nsIAbCard* aSenderCard, const nsACString& aFullName);
+ void SplitFullName(const nsCString& aFullName, nsCString& aFirstName,
+ nsCString& aLastName);
+ void SetUpAbFromPrefs(nsIPrefBranch* aPrefBranch);
+ nsCOMPtr<nsIAbDirectory> mDirectory;
+ nsCString mABURI;
+};
+
+#endif // _nsAbAddressCollector_H_
diff --git a/comm/mailnews/addrbook/src/nsAbBooleanExpression.cpp b/comm/mailnews/addrbook/src/nsAbBooleanExpression.cpp
new file mode 100644
index 0000000000..434c8756e6
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbBooleanExpression.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAbBooleanExpression.h"
+#include "nsComponentManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAbBooleanConditionString, nsIAbBooleanConditionString)
+
+nsAbBooleanConditionString::nsAbBooleanConditionString()
+ : mCondition(nsIAbBooleanConditionTypes::Exists) {}
+
+nsAbBooleanConditionString::~nsAbBooleanConditionString() {}
+
+/* attribute nsAbBooleanConditionType condition; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetCondition(
+ nsAbBooleanConditionType* aCondition) {
+ if (!aCondition) return NS_ERROR_NULL_POINTER;
+
+ *aCondition = mCondition;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetCondition(
+ nsAbBooleanConditionType aCondition) {
+ mCondition = aCondition;
+
+ return NS_OK;
+}
+
+/* attribute string name; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetName(char** aName) {
+ if (!aName) return NS_ERROR_NULL_POINTER;
+
+ *aName = mName.IsEmpty() ? 0 : ToNewCString(mName);
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetName(const char* aName) {
+ if (!aName) return NS_ERROR_NULL_POINTER;
+
+ mName = aName;
+
+ return NS_OK;
+}
+
+/* attribute wstring value; */
+NS_IMETHODIMP nsAbBooleanConditionString::GetValue(char16_t** aValue) {
+ if (!aValue) return NS_ERROR_NULL_POINTER;
+
+ *aValue = ToNewUnicode(mValue);
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanConditionString::SetValue(const char16_t* aValue) {
+ if (!aValue) return NS_ERROR_NULL_POINTER;
+
+ mValue = aValue;
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbBooleanExpression, nsIAbBooleanExpression)
+
+nsAbBooleanExpression::nsAbBooleanExpression()
+ : mOperation(nsIAbBooleanOperationTypes::AND) {}
+
+nsAbBooleanExpression::~nsAbBooleanExpression() {}
+
+/* attribute nsAbBooleanOperationType operation; */
+NS_IMETHODIMP nsAbBooleanExpression::GetOperation(
+ nsAbBooleanOperationType* aOperation) {
+ if (!aOperation) return NS_ERROR_NULL_POINTER;
+
+ *aOperation = mOperation;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbBooleanExpression::SetOperation(
+ nsAbBooleanOperationType aOperation) {
+ mOperation = aOperation;
+
+ return NS_OK;
+}
+
+/* attribute Array<nsISupports> expressions; */
+NS_IMETHODIMP nsAbBooleanExpression::GetExpressions(
+ nsTArray<RefPtr<nsISupports>>& aExpressions) {
+ aExpressions = mExpressions.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbBooleanExpression::SetExpressions(
+ const nsTArray<RefPtr<nsISupports>>& aExpressions) {
+ mExpressions = aExpressions.Clone();
+ return NS_OK;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbBooleanExpression.h b/comm/mailnews/addrbook/src/nsAbBooleanExpression.h
new file mode 100644
index 0000000000..c739e0c2df
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbBooleanExpression.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsAbBooleanExpression_h__
+#define nsAbBooleanExpression_h__
+
+#include "nsIAbBooleanExpression.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIArray.h"
+
+class nsAbBooleanConditionString : public nsIAbBooleanConditionString {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANCONDITIONSTRING
+
+ nsAbBooleanConditionString();
+
+ protected:
+ virtual ~nsAbBooleanConditionString();
+ nsAbBooleanConditionType mCondition;
+ nsCString mName;
+ nsString mValue;
+};
+
+class nsAbBooleanExpression : public nsIAbBooleanExpression {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANEXPRESSION
+
+ nsAbBooleanExpression();
+
+ protected:
+ virtual ~nsAbBooleanExpression();
+ nsAbBooleanOperationType mOperation;
+ nsTArray<RefPtr<nsISupports>> mExpressions;
+};
+
+#endif
diff --git a/comm/mailnews/addrbook/src/nsAbCardProperty.cpp b/comm/mailnews/addrbook/src/nsAbCardProperty.cpp
new file mode 100644
index 0000000000..cc34655afd
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbCardProperty.cpp
@@ -0,0 +1,1004 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAbCardProperty.h"
+#include "nsIPrefService.h"
+#include "nsIAbDirectory.h"
+#include "plbase64.h"
+#include "nsIStringBundle.h"
+#include "plstr.h"
+#include "nsMsgUtils.h"
+#include "nsINetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsIAbManager.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIMsgVCardService.h"
+#include "nsVariant.h"
+#include "nsIProperty.h"
+#include "nsCOMArray.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Components.h"
+using namespace mozilla;
+
+#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst"
+
+const char sAddrbookProperties[] =
+ "chrome://messenger/locale/addressbook/addressBook.properties";
+
+enum EAppendType {
+ eAppendLine,
+ eAppendLabel,
+ eAppendCityStateZip,
+ eAppendUndefined
+};
+
+struct AppendItem {
+ const char* mColumn;
+ const char* mLabel;
+ EAppendType mAppendType;
+};
+
+static const AppendItem NAME_ATTRS_ARRAY[] = {
+ {kDisplayNameProperty, "propertyDisplayName", eAppendLabel},
+ {kNicknameProperty, "propertyNickname", eAppendLabel},
+ {kPriEmailProperty, "", eAppendLine},
+ {k2ndEmailProperty, "", eAppendLine}};
+
+static const AppendItem PHONE_ATTRS_ARRAY[] = {
+ {kWorkPhoneProperty, "propertyWork", eAppendLabel},
+ {kHomePhoneProperty, "propertyHome", eAppendLabel},
+ {kFaxProperty, "propertyFax", eAppendLabel},
+ {kPagerProperty, "propertyPager", eAppendLabel},
+ {kCellularProperty, "propertyCellular", eAppendLabel}};
+
+static const AppendItem HOME_ATTRS_ARRAY[] = {
+ {kHomeAddressProperty, "", eAppendLine},
+ {kHomeAddress2Property, "", eAppendLine},
+ {kHomeCityProperty, "", eAppendCityStateZip},
+ {kHomeCountryProperty, "", eAppendLine},
+ {kHomeWebPageProperty, "", eAppendLine}};
+
+static const AppendItem WORK_ATTRS_ARRAY[] = {
+ {kJobTitleProperty, "", eAppendLine},
+ {kDepartmentProperty, "", eAppendLine},
+ {kCompanyProperty, "", eAppendLine},
+ {kWorkAddressProperty, "", eAppendLine},
+ {kWorkAddress2Property, "", eAppendLine},
+ {kWorkCityProperty, "", eAppendCityStateZip},
+ {kWorkCountryProperty, "", eAppendLine},
+ {kWorkWebPageProperty, "", eAppendLine}};
+
+static const AppendItem CUSTOM_ATTRS_ARRAY[] = {
+ {kCustom1Property, "propertyCustom1", eAppendLabel},
+ {kCustom2Property, "propertyCustom2", eAppendLabel},
+ {kCustom3Property, "propertyCustom3", eAppendLabel},
+ {kCustom4Property, "propertyCustom4", eAppendLabel},
+ {kNotesProperty, "", eAppendLine}};
+
+static const AppendItem CHAT_ATTRS_ARRAY[] = {
+ {kGtalkProperty, "propertyGtalk", eAppendLabel},
+ {kAIMProperty, "propertyAIM", eAppendLabel},
+ {kYahooProperty, "propertyYahoo", eAppendLabel},
+ {kSkypeProperty, "propertySkype", eAppendLabel},
+ {kQQProperty, "propertyQQ", eAppendLabel},
+ {kMSNProperty, "propertyMSN", eAppendLabel},
+ {kICQProperty, "propertyICQ", eAppendLabel},
+ {kXMPPProperty, "propertyXMPP", eAppendLabel},
+ {kIRCProperty, "propertyIRC", eAppendLabel}};
+
+nsAbCardProperty::nsAbCardProperty() : m_IsMailList(false) {
+ // Initialize some default properties
+ SetPropertyAsUint32(kPopularityIndexProperty, 0);
+ // Uninitialized...
+ SetPropertyAsUint32(kLastModifiedDateProperty, 0);
+}
+
+nsAbCardProperty::~nsAbCardProperty(void) {}
+
+NS_IMPL_ISUPPORTS(nsAbCardProperty, nsIAbCard)
+
+NS_IMETHODIMP nsAbCardProperty::GetDirectoryUID(nsACString& dirUID) {
+ dirUID = m_directoryUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetDirectoryUID(const nsACString& aDirUID) {
+ m_directoryUID = aDirUID;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsAbCardProperty::GetIsMailList(bool* aIsMailList) {
+ *aIsMailList = m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetIsMailList(bool aIsMailList) {
+ m_IsMailList = aIsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetMailListURI(char** aMailListURI) {
+ if (aMailListURI) {
+ *aMailListURI = ToNewCString(m_MailListURI);
+ return (*aMailListURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ } else
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetMailListURI(const char* aMailListURI) {
+ if (aMailListURI) {
+ m_MailListURI = aMailListURI;
+ return NS_OK;
+ } else
+ return NS_ERROR_NULL_POINTER;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Property bag portion of nsAbCardProperty
+///////////////////////////////////////////////////////////////////////////////
+
+class nsAbSimpleProperty final : public nsIProperty {
+ public:
+ nsAbSimpleProperty(const nsACString& aName, nsIVariant* aValue)
+ : mName(aName), mValue(aValue) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTY
+ protected:
+ ~nsAbSimpleProperty() {}
+ nsCString mName;
+ nsCOMPtr<nsIVariant> mValue;
+};
+
+NS_IMPL_ISUPPORTS(nsAbSimpleProperty, nsIProperty)
+
+NS_IMETHODIMP
+nsAbSimpleProperty::GetName(nsAString& aName) {
+ aName.Assign(NS_ConvertUTF8toUTF16(mName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbSimpleProperty::GetValue(nsIVariant** aValue) {
+ NS_IF_ADDREF(*aValue = mValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetProperties(
+ nsTArray<RefPtr<nsIProperty>>& props) {
+ props.ClearAndRetainStorage();
+ props.SetCapacity(m_properties.Count());
+ for (auto iter = m_properties.Iter(); !iter.Done(); iter.Next()) {
+ props.AppendElement(new nsAbSimpleProperty(iter.Key(), iter.UserData()));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetProperty(const nsACString& name,
+ nsIVariant* defaultValue,
+ nsIVariant** value) {
+ if (!m_properties.Get(name, value)) NS_ADDREF(*value = defaultValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsAString(const char* name,
+ nsAString& value) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant))
+ ? variant->GetAsAString(value)
+ : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsAUTF8String(const char* name,
+ nsACString& value) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant))
+ ? variant->GetAsAUTF8String(value)
+ : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsUint32(const char* name,
+ uint32_t* value) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant))
+ ? variant->GetAsUint32(value)
+ : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPropertyAsBool(const char* name,
+ bool defaultValue,
+ bool* value) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ *value = defaultValue;
+
+ nsCOMPtr<nsIVariant> variant;
+ return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant))
+ ? variant->GetAsBool(value)
+ : NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetProperty(const nsACString& name,
+ nsIVariant* value) {
+ m_properties.InsertOrUpdate(name, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsAString(const char* name,
+ const nsAString& value) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsAString(value);
+ m_properties.InsertOrUpdate(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsAUTF8String(
+ const char* name, const nsACString& value) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsAUTF8String(value);
+ m_properties.InsertOrUpdate(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsUint32(const char* name,
+ uint32_t value) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsUint32(value);
+ m_properties.InsertOrUpdate(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPropertyAsBool(const char* name,
+ bool value) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ variant->SetAsBool(value);
+ m_properties.InsertOrUpdate(nsDependentCString(name), variant);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::DeleteProperty(const nsACString& name) {
+ m_properties.Remove(name);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetSupportsVCard(bool* aSupportsVCard) {
+ *aSupportsVCard = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetVCardProperties(
+ JS::MutableHandle<JS::Value> properties) {
+ properties.setNull();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetUID(nsACString& uid) {
+ nsAutoString aString;
+ nsresult rv = GetPropertyAsAString(kUIDProperty, aString);
+ if (NS_SUCCEEDED(rv)) {
+ uid = NS_ConvertUTF16toUTF8(aString);
+ return rv;
+ }
+
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ mozilla::components::UUIDGenerator::Service();
+ NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
+
+ nsID id;
+ rv = uuidgen->GenerateUUIDInPlace(&id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char idString[NSID_LENGTH];
+ id.ToProvidedString(idString);
+
+ uid.AppendASCII(idString + 1, NSID_LENGTH - 3);
+ return SetUID(uid);
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetUID(const nsACString& aUID) {
+ nsAutoString aString;
+ nsresult rv = GetPropertyAsAString(kUIDProperty, aString);
+ if (NS_SUCCEEDED(rv)) {
+ if (!aString.Equals(NS_ConvertUTF8toUTF16(aUID))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ rv = SetPropertyAsAString(kUIDProperty, NS_ConvertUTF8toUTF16(aUID));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_directoryUID.IsEmpty()) {
+ // This card's not in a directory.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory = nullptr;
+ rv =
+ abManager->GetDirectoryFromUID(m_directoryUID, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!directory) {
+ // This card claims to be in a directory, but we can't find it.
+ return NS_OK;
+ }
+
+ bool readOnly;
+ rv = directory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (readOnly) {
+ // The directory is read-only.
+ return NS_OK;
+ }
+
+ // Save the new UID so we can use it again in the future.
+ return directory->ModifyCard(this);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetFirstName(nsAString& aString) {
+ nsresult rv = GetPropertyAsAString(kFirstNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetFirstName(const nsAString& aString) {
+ return SetPropertyAsAString(kFirstNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetLastName(nsAString& aString) {
+ nsresult rv = GetPropertyAsAString(kLastNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetLastName(const nsAString& aString) {
+ return SetPropertyAsAString(kLastNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetDisplayName(nsAString& aString) {
+ nsresult rv = GetPropertyAsAString(kDisplayNameProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetDisplayName(const nsAString& aString) {
+ return SetPropertyAsAString(kDisplayNameProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPrimaryEmail(nsAString& aString) {
+ nsresult rv = GetPropertyAsAString(kPriEmailProperty, aString);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ aString.Truncate();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAbCardProperty::SetPrimaryEmail(const nsAString& aString) {
+ return SetPropertyAsAString(kPriEmailProperty, aString);
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetEmailAddresses(
+ nsTArray<nsString>& aEmailAddresses) {
+ aEmailAddresses.Clear();
+
+ nsresult rv;
+ nsString emailAddress;
+
+ rv = GetPropertyAsAString(kPriEmailProperty, emailAddress);
+ if (rv != NS_ERROR_NOT_AVAILABLE && !emailAddress.IsEmpty()) {
+ aEmailAddresses.AppendElement(emailAddress);
+ }
+
+ rv = GetPropertyAsAString(k2ndEmailProperty, emailAddress);
+ if (rv != NS_ERROR_NOT_AVAILABLE && !emailAddress.IsEmpty()) {
+ aEmailAddresses.AppendElement(emailAddress);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::HasEmailAddress(const nsACString& aEmailAddress,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+
+ nsCString emailAddress;
+ nsresult rv = GetPropertyAsAUTF8String(kPriEmailProperty, emailAddress);
+ if (rv != NS_ERROR_NOT_AVAILABLE &&
+ emailAddress.Equals(aEmailAddress, nsCaseInsensitiveCStringComparator)) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ rv = GetPropertyAsAUTF8String(k2ndEmailProperty, emailAddress);
+ if (rv != NS_ERROR_NOT_AVAILABLE &&
+ emailAddress.Equals(aEmailAddress, nsCaseInsensitiveCStringComparator))
+ *aResult = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GetPhotoURL(nsAString& aPhotoURL) {
+ aPhotoURL.Truncate();
+ return NS_OK;
+}
+
+// This function may be overridden by derived classes for
+// nsAb*Card specific implementations.
+NS_IMETHODIMP nsAbCardProperty::Copy(nsIAbCard* srcCard) {
+ NS_ENSURE_ARG_POINTER(srcCard);
+
+ nsTArray<RefPtr<nsIProperty>> properties;
+ nsresult rv = srcCard->GetProperties(properties);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIProperty* property : properties) {
+ nsAutoString name;
+ property->GetName(name);
+ nsCOMPtr<nsIVariant> value;
+ property->GetValue(getter_AddRefs(value));
+
+ SetProperty(NS_ConvertUTF16toUTF8(name), value);
+ }
+
+ bool isMailList;
+ srcCard->GetIsMailList(&isMailList);
+ SetIsMailList(isMailList);
+
+ nsCString mailListURI;
+ srcCard->GetMailListURI(getter_Copies(mailListURI));
+ SetMailListURI(mailListURI.get());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::Equals(nsIAbCard* card, bool* result) {
+ *result = (card == this);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The following methods are other views of a card
+////////////////////////////////////////////////////////////////////////////////
+
+// XXX: Use the category manager instead of this file to implement these
+NS_IMETHODIMP nsAbCardProperty::TranslateTo(const nsACString& type,
+ nsACString& result) {
+ if (type.EqualsLiteral("base64xml")) {
+ return ConvertToBase64EncodedXML(result);
+ } else if (type.EqualsLiteral("xml")) {
+ nsString utf16String;
+ nsresult rv = ConvertToXMLPrintData(utf16String);
+ NS_ENSURE_SUCCESS(rv, rv);
+ result = NS_ConvertUTF16toUTF8(utf16String);
+ return NS_OK;
+ } else if (type.EqualsLiteral("vcard")) {
+ return ConvertToEscapedVCard(result);
+ }
+
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+nsresult nsAbCardProperty::ConvertToEscapedVCard(nsACString& aResult) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgVCardService> vCardService =
+ do_GetService("@mozilla.org/addressbook/msgvcardservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString result;
+ rv = vCardService->AbCardToEscapedVCard(this, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult = NS_ConvertUTF16toUTF8(result);
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::ConvertToBase64EncodedXML(nsACString& result) {
+ nsresult rv;
+ nsString xmlStr;
+
+ xmlStr.AppendLiteral(
+ "<?xml version=\"1.0\"?>\n"
+ "<?xml-stylesheet type=\"text/css\" "
+ "href=\"chrome://messagebody/skin/abPrint.css\"?>\n"
+ "<directory>\n");
+
+ // Get Address Book string and set it as title of XML document
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::components::StringBundle::Service();
+ if (stringBundleService) {
+ rv = stringBundleService->CreateBundle(sAddrbookProperties,
+ getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsString addrBook;
+ rv = bundle->GetStringFromName("addressBook", addrBook);
+ if (NS_SUCCEEDED(rv)) {
+ xmlStr.AppendLiteral("<title xmlns=\"http://www.w3.org/1999/xhtml\">");
+ xmlStr.Append(addrBook);
+ xmlStr.AppendLiteral("</title>\n");
+ }
+ }
+ }
+
+ nsString xmlSubstr;
+ rv = ConvertToXMLPrintData(xmlSubstr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ xmlStr.Append(xmlSubstr);
+ xmlStr.AppendLiteral("</directory>\n");
+
+ char* tmpRes =
+ PL_Base64Encode(NS_ConvertUTF16toUTF8(xmlStr).get(), 0, nullptr);
+ result.Assign(tmpRes);
+ PR_Free(tmpRes);
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::ConvertToXMLPrintData(nsAString& aXMLSubstr) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t generatedNameFormat;
+ rv = prefBranch->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST,
+ &generatedNameFormat);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = stringBundleService->CreateBundle(sAddrbookProperties,
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString generatedName;
+ rv = GenerateName(generatedNameFormat, bundle, generatedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozITXTToHTMLConv> conv =
+ do_CreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString xmlStr;
+ xmlStr.SetLength(
+ 4096); // to reduce allocations. should be enough for most cards
+ xmlStr.AssignLiteral("<GeneratedName>\n");
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ if (!generatedName.IsEmpty()) {
+ rv = conv->ScanTXT(generatedName, mozITXTToHTMLConv::kEntities, safeText);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (safeText.IsEmpty()) {
+ nsAutoString primaryEmail;
+ GetPrimaryEmail(primaryEmail);
+
+ // use ScanTXT to convert < > & to safe values.
+ rv = conv->ScanTXT(primaryEmail, mozITXTToHTMLConv::kEntities, safeText);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ xmlStr.Append(safeText);
+
+ xmlStr.AppendLiteral(
+ "</GeneratedName>\n"
+ "<table><tr><td>");
+
+ rv = AppendSection(NAME_ATTRS_ARRAY,
+ sizeof(NAME_ATTRS_ARRAY) / sizeof(AppendItem),
+ EmptyString(), bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("</td></tr><tr><td>");
+
+ rv = AppendSection(PHONE_ATTRS_ARRAY,
+ sizeof(PHONE_ATTRS_ARRAY) / sizeof(AppendItem),
+ u"headingPhone"_ns, bundle, conv, xmlStr);
+
+ if (!m_IsMailList) {
+ rv = AppendSection(CUSTOM_ATTRS_ARRAY,
+ sizeof(CUSTOM_ATTRS_ARRAY) / sizeof(AppendItem),
+ u"headingOther"_ns, bundle, conv, xmlStr);
+ rv = AppendSection(CHAT_ATTRS_ARRAY,
+ sizeof(CHAT_ATTRS_ARRAY) / sizeof(AppendItem),
+ u"headingChat"_ns, bundle, conv, xmlStr);
+ } else {
+ rv = AppendSection(CUSTOM_ATTRS_ARRAY,
+ sizeof(CUSTOM_ATTRS_ARRAY) / sizeof(AppendItem),
+ u"headingDescription"_ns, bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("<section><sectiontitle>");
+
+ nsString headingAddresses;
+ rv = bundle->GetStringFromName("headingAddresses", headingAddresses);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ xmlStr.Append(headingAddresses);
+ xmlStr.AppendLiteral("</sectiontitle>");
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> mailList = nullptr;
+ rv = abManager->GetDirectory(m_MailListURI, getter_AddRefs(mailList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIAbCard>> mailListAddresses;
+ rv = mailList->GetChildCards(mailListAddresses);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIAbCard* listCard : mailListAddresses) {
+ xmlStr.AppendLiteral("<PrimaryEmail>\n");
+
+ nsAutoString displayName;
+ rv = listCard->GetDisplayName(displayName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ rv = conv->ScanTXT(displayName, mozITXTToHTMLConv::kEntities, safeText);
+ NS_ENSURE_SUCCESS(rv, rv);
+ xmlStr.Append(safeText);
+
+ xmlStr.AppendLiteral(" &lt;");
+
+ nsAutoString primaryEmail;
+ listCard->GetPrimaryEmail(primaryEmail);
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText2;
+ rv = conv->ScanTXT(primaryEmail, mozITXTToHTMLConv::kEntities, safeText2);
+ NS_ENSURE_SUCCESS(rv, rv);
+ xmlStr.Append(safeText2);
+
+ xmlStr.AppendLiteral("&gt;</PrimaryEmail>\n");
+ }
+ xmlStr.AppendLiteral("</section>");
+ }
+
+ xmlStr.AppendLiteral("</td><td>");
+
+ rv = AppendSection(HOME_ATTRS_ARRAY,
+ sizeof(HOME_ATTRS_ARRAY) / sizeof(AppendItem),
+ u"headingHome"_ns, bundle, conv, xmlStr);
+ rv = AppendSection(WORK_ATTRS_ARRAY,
+ sizeof(WORK_ATTRS_ARRAY) / sizeof(AppendItem),
+ u"headingWork"_ns, bundle, conv, xmlStr);
+
+ xmlStr.AppendLiteral("</td></tr></table>");
+
+ aXMLSubstr = xmlStr;
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendSection(
+ const AppendItem* aArray, int16_t aCount, const nsString& aHeading,
+ nsIStringBundle* aBundle, mozITXTToHTMLConv* aConv, nsString& aResult) {
+ nsresult rv = NS_OK;
+
+ aResult.AppendLiteral("<section>");
+
+ nsString attrValue;
+ bool sectionIsEmpty = true;
+
+ int16_t i = 0;
+ for (i = 0; i < aCount; i++) {
+ rv = GetPropertyAsAString(aArray[i].mColumn, attrValue);
+ if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) sectionIsEmpty = false;
+ }
+
+ if (!sectionIsEmpty && !aHeading.IsEmpty()) {
+ nsString heading;
+ rv = aBundle->GetStringFromName(NS_ConvertUTF16toUTF8(aHeading).get(),
+ heading);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.AppendLiteral("<sectiontitle>");
+ aResult.Append(heading);
+ aResult.AppendLiteral("</sectiontitle>");
+ }
+
+ for (i = 0; i < aCount; i++) {
+ switch (aArray[i].mAppendType) {
+ case eAppendLine:
+ rv = AppendLine(aArray[i], aConv, aResult);
+ break;
+ case eAppendLabel:
+ rv = AppendLabel(aArray[i], aBundle, aConv, aResult);
+ break;
+ case eAppendCityStateZip:
+ rv = AppendCityStateZip(aArray[i], aBundle, aConv, aResult);
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("append item failed");
+ break;
+ }
+ }
+ aResult.AppendLiteral("</section>");
+
+ return rv;
+}
+
+nsresult nsAbCardProperty::AppendLine(const AppendItem& aItem,
+ mozITXTToHTMLConv* aConv,
+ nsString& aResult) {
+ NS_ENSURE_ARG_POINTER(aConv);
+
+ nsString attrValue;
+ nsresult rv = GetPropertyAsAString(aItem.mColumn, attrValue);
+
+ if (NS_FAILED(rv) || attrValue.IsEmpty()) return NS_OK;
+
+ aResult.Append(char16_t('<'));
+ aResult.Append(NS_ConvertUTF8toUTF16(aItem.mColumn));
+ aResult.Append(char16_t('>'));
+
+ // use ScanTXT to convert < > & to safe values.
+ nsString safeText;
+ rv = aConv->ScanTXT(attrValue, mozITXTToHTMLConv::kEntities, safeText);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aResult.Append(safeText);
+
+ aResult.AppendLiteral("</");
+ aResult.Append(NS_ConvertUTF8toUTF16(aItem.mColumn));
+ aResult.Append(char16_t('>'));
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendLabel(const AppendItem& aItem,
+ nsIStringBundle* aBundle,
+ mozITXTToHTMLConv* aConv,
+ nsString& aResult) {
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ nsresult rv;
+ nsString label, attrValue;
+
+ rv = GetPropertyAsAString(aItem.mColumn, attrValue);
+
+ if (NS_FAILED(rv) || attrValue.IsEmpty()) return NS_OK;
+
+ rv = aBundle->GetStringFromName(aItem.mLabel, label);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.AppendLiteral("<labelrow><label>");
+
+ aResult.Append(label);
+ aResult.AppendLiteral(": </label>");
+
+ rv = AppendLine(aItem, aConv, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.AppendLiteral("</labelrow>");
+
+ return NS_OK;
+}
+
+nsresult nsAbCardProperty::AppendCityStateZip(const AppendItem& aItem,
+ nsIStringBundle* aBundle,
+ mozITXTToHTMLConv* aConv,
+ nsString& aResult) {
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ nsresult rv;
+ AppendItem item;
+ const char *statePropName, *zipPropName;
+
+ if (strcmp(aItem.mColumn, kHomeCityProperty) == 0) {
+ statePropName = kHomeStateProperty;
+ zipPropName = kHomeZipCodeProperty;
+ } else {
+ statePropName = kWorkStateProperty;
+ zipPropName = kWorkZipCodeProperty;
+ }
+
+ nsAutoString cityResult, stateResult, zipResult;
+
+ rv = AppendLine(aItem, aConv, cityResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ item.mColumn = statePropName;
+ item.mLabel = "";
+ item.mAppendType = eAppendUndefined;
+
+ rv = AppendLine(item, aConv, stateResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ item.mColumn = zipPropName;
+
+ rv = AppendLine(item, aConv, zipResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString formattedString;
+
+ if (!cityResult.IsEmpty() && !stateResult.IsEmpty() && !zipResult.IsEmpty()) {
+ AutoTArray<nsString, 3> formatStrings = {cityResult, stateResult,
+ zipResult};
+ rv = aBundle->FormatStringFromName("cityAndStateAndZip", formatStrings,
+ formattedString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (!cityResult.IsEmpty() && !stateResult.IsEmpty() &&
+ zipResult.IsEmpty()) {
+ AutoTArray<nsString, 2> formatStrings = {cityResult, stateResult};
+ rv = aBundle->FormatStringFromName("cityAndStateNoZip", formatStrings,
+ formattedString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if ((!cityResult.IsEmpty() && stateResult.IsEmpty() &&
+ !zipResult.IsEmpty()) ||
+ (cityResult.IsEmpty() && !stateResult.IsEmpty() &&
+ !zipResult.IsEmpty())) {
+ AutoTArray<nsString, 2> formatStrings = {
+ cityResult.IsEmpty() ? stateResult : cityResult, zipResult};
+ rv = aBundle->FormatStringFromName("cityOrStateAndZip", formatStrings,
+ formattedString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ if (!cityResult.IsEmpty())
+ formattedString = cityResult;
+ else if (!stateResult.IsEmpty())
+ formattedString = stateResult;
+ else
+ formattedString = zipResult;
+ }
+
+ aResult.Append(formattedString);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GenerateName(int32_t aGenerateFormat,
+ nsIStringBundle* aBundle,
+ nsAString& aResult) {
+ aResult.Truncate();
+
+ // Cache the first and last names
+ nsAutoString firstName, lastName;
+ GetFirstName(firstName);
+ GetLastName(lastName);
+
+ // No need to check for aBundle present straight away, only do that if we're
+ // actually going to use it.
+ if (aGenerateFormat == GENERATE_DISPLAY_NAME)
+ GetDisplayName(aResult);
+ else if (lastName.IsEmpty())
+ aResult = firstName;
+ else if (firstName.IsEmpty())
+ aResult = lastName;
+ else {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundle> bundle(aBundle);
+ if (!bundle) {
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED);
+
+ rv = stringBundleService->CreateBundle(sAddrbookProperties,
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsString result;
+
+ if (aGenerateFormat == GENERATE_LAST_FIRST_ORDER) {
+ AutoTArray<nsString, 2> stringParams = {lastName, firstName};
+
+ rv =
+ bundle->FormatStringFromName("lastFirstFormat", stringParams, result);
+ } else {
+ AutoTArray<nsString, 2> stringParams = {firstName, lastName};
+
+ rv =
+ bundle->FormatStringFromName("firstLastFormat", stringParams, result);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.Assign(result);
+ }
+
+ if (aResult.IsEmpty()) {
+ // The normal names have failed, does this card have a company name? If so,
+ // use that instead, because that is likely to be more meaningful than an
+ // email address.
+ //
+ // If this errors, the string isn't found and we'll fall into the next
+ // check.
+ (void)GetPropertyAsAString(kCompanyProperty, aResult);
+ }
+
+ if (aResult.IsEmpty()) {
+ // see bug #211078
+ // if there is no generated name at this point
+ // use the userid from the email address
+ // it is better than nothing.
+ GetPrimaryEmail(aResult);
+ int32_t index = aResult.FindChar('@');
+ if (index != -1) aResult.SetLength(index);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GeneratePhoneticName(bool aLastNameFirst,
+ nsAString& aResult) {
+ nsAutoString firstName, lastName;
+ GetPropertyAsAString(kPhoneticFirstNameProperty, firstName);
+ GetPropertyAsAString(kPhoneticLastNameProperty, lastName);
+
+ if (aLastNameFirst) {
+ aResult = lastName;
+ aResult += firstName;
+ } else {
+ aResult = firstName;
+ aResult += lastName;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbCardProperty::GenerateChatName(nsAString& aResult) {
+ aResult.Truncate();
+
+#define CHECK_CHAT_PROPERTY(aProtocol) \
+ if (NS_SUCCEEDED(GetPropertyAsAString(k##aProtocol##Property, aResult)) && \
+ !aResult.IsEmpty()) \
+ return NS_OK
+ CHECK_CHAT_PROPERTY(Gtalk);
+ CHECK_CHAT_PROPERTY(AIM);
+ CHECK_CHAT_PROPERTY(Yahoo);
+ CHECK_CHAT_PROPERTY(Skype);
+ CHECK_CHAT_PROPERTY(QQ);
+ CHECK_CHAT_PROPERTY(MSN);
+ CHECK_CHAT_PROPERTY(ICQ);
+ CHECK_CHAT_PROPERTY(XMPP);
+ CHECK_CHAT_PROPERTY(IRC);
+ return NS_OK;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbCardProperty.h b/comm/mailnews/addrbook/src/nsAbCardProperty.h
new file mode 100644
index 0000000000..a724b592a6
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbCardProperty.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Person Card Property
+
+*********************************************************************************************************/
+
+#ifndef nsAbCardProperty_h__
+#define nsAbCardProperty_h__
+
+#include "nsIAbCard.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#include "nsInterfaceHashtable.h"
+#include "nsIVariant.h"
+
+class nsIStringBundle;
+class mozITXTToHTMLConv;
+struct AppendItem;
+
+/*
+ * Address Book Card Property
+ */
+
+class nsAbCardProperty : public nsIAbCard {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABCARD
+
+ nsAbCardProperty();
+
+ protected:
+ virtual ~nsAbCardProperty();
+ bool m_IsMailList;
+ nsCString m_MailListURI;
+
+ // Store most of the properties here
+ nsInterfaceHashtable<nsCStringHashKey, nsIVariant> m_properties;
+
+ nsCString m_directoryUID;
+
+ private:
+ nsresult AppendSection(const AppendItem* aArray, int16_t aCount,
+ const nsString& aHeading, nsIStringBundle* aBundle,
+ mozITXTToHTMLConv* aConv, nsString& aResult);
+ nsresult AppendLine(const AppendItem& aItem, mozITXTToHTMLConv* aConv,
+ nsString& aResult);
+ nsresult AppendLabel(const AppendItem& aItem, nsIStringBundle* aBundle,
+ mozITXTToHTMLConv* aConv, nsString& aResult);
+ nsresult AppendCityStateZip(const AppendItem& aItem, nsIStringBundle* aBundle,
+ mozITXTToHTMLConv* aConv, nsString& aResult);
+
+ nsresult ConvertToBase64EncodedXML(nsACString& result);
+ nsresult ConvertToXMLPrintData(nsAString& result);
+ nsresult ConvertToEscapedVCard(nsACString& result);
+};
+
+#endif
diff --git a/comm/mailnews/addrbook/src/nsAbDirProperty.cpp b/comm/mailnews/addrbook/src/nsAbDirProperty.cpp
new file mode 100644
index 0000000000..67860e424d
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbDirProperty.cpp
@@ -0,0 +1,573 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAbDirProperty.h"
+#include "nsIAbCard.h"
+#include "nsIPrefService.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "prmem.h"
+#include "nsIAbManager.h"
+#include "nsArrayUtils.h"
+#include "nsIUUIDGenerator.h"
+#include "mozilla/Components.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "mozilla/dom/Promise.h"
+
+using mozilla::ErrorResult;
+using mozilla::dom::Promise;
+using namespace mozilla;
+
+// From nsDirPrefs
+#define kDefaultPosition 1
+
+nsAbDirProperty::nsAbDirProperty(void)
+ : m_LastModifiedDate(0), mIsValidURI(false) {
+ m_IsMailList = false;
+ mUID = EmptyCString();
+}
+
+nsAbDirProperty::~nsAbDirProperty(void) {
+#if 0
+ // this code causes a regression #138647
+ // don't turn it on until you figure it out
+ if (m_AddressList) {
+ uint32_t count;
+ nsresult rv;
+ rv = m_AddressList->GetLength(&count);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed");
+ int32_t i;
+ for (i = count - 1; i >= 0; i--)
+ m_AddressList->RemoveElementAt(i);
+ }
+#endif
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirProperty, nsIAbDirectory, nsISupportsWeakReference)
+
+NS_IMETHODIMP nsAbDirProperty::GetPropertiesChromeURI(nsACString& aResult) {
+ aResult.AssignLiteral(
+ "chrome://messenger/content/addressbook/abAddressBookNameDialog.xhtml");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirName(nsAString& aDirName) {
+ if (m_DirPrefId.IsEmpty()) {
+ aDirName = m_ListDirName;
+ return NS_OK;
+ }
+
+ nsCString dirName;
+ nsresult rv = GetLocalizedStringValue("description", EmptyCString(), dirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // In TB 2 only some prefs had chrome:// URIs. We had code in place that would
+ // only get the localized string pref for the particular address books that
+ // were built-in.
+ // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set,
+ // non-locked pref value if it is a chrome:// URI and will get the string
+ // value at that chrome URI. This breaks extensions/autoconfig that want to
+ // set default pref values and allow users to change directory names.
+ //
+ // Now we have to support this, and so if for whatever reason we fail to get
+ // the localized version, then we try and get the non-localized version
+ // instead. If the string value is empty, then we'll just get the empty value
+ // back here.
+ if (dirName.IsEmpty()) {
+ rv = GetStringValue("description", EmptyCString(), dirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ CopyUTF8toUTF16(dirName, aDirName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetDirName(const nsAString& aDirName) {
+ if (m_DirPrefId.IsEmpty()) {
+ m_ListDirName = aDirName;
+ return NS_OK;
+ }
+
+ // Store the old value.
+ nsString oldDirName;
+ nsresult rv = GetDirName(oldDirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Save the new value
+ rv = SetLocalizedStringValue("description", NS_ConvertUTF16toUTF8(aDirName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService("@mozilla.org/abmanager;1", &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ // We inherit from nsIAbDirectory, so this static cast should be safe.
+ observerService->NotifyObservers(static_cast<nsIAbDirectory*>(this),
+ "addrbook-directory-updated", u"DirName");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirType(int32_t* aDirType) {
+ return GetIntValue("dirType", nsIAbManager::LDAP_DIRECTORY_TYPE, aDirType);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetFileName(nsACString& aFileName) {
+ return GetStringValue("filename", EmptyCString(), aFileName);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetUID(nsACString& aUID) {
+ nsresult rv = NS_OK;
+ if (!mUID.IsEmpty()) {
+ aUID = mUID;
+ return rv;
+ }
+ if (!m_IsMailList) {
+ rv = GetStringValue("uid", EmptyCString(), aUID);
+ if (!aUID.IsEmpty()) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ mozilla::components::UUIDGenerator::Service();
+ NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
+
+ nsID id;
+ rv = uuidgen->GenerateUUIDInPlace(&id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char idString[NSID_LENGTH];
+ id.ToProvidedString(idString);
+
+ aUID.AppendASCII(idString + 1, NSID_LENGTH - 3);
+ return SetUID(aUID);
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetUID(const nsACString& aUID) {
+ mUID = aUID;
+ if (m_IsMailList) {
+ return NS_OK;
+ }
+ return SetStringValue("uid", aUID);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetURI(nsACString& aURI) {
+ // XXX Should we complete this for Mailing Lists?
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetPosition(int32_t* aPosition) {
+ return GetIntValue("position", kDefaultPosition, aPosition);
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetLastModifiedDate(
+ uint32_t* aLastModifiedDate) {
+ NS_ENSURE_ARG_POINTER(aLastModifiedDate);
+ *aLastModifiedDate = m_LastModifiedDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetLastModifiedDate(uint32_t aLastModifiedDate) {
+ if (aLastModifiedDate) {
+ m_LastModifiedDate = aLastModifiedDate;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetListNickName(nsAString& aListNickName) {
+ aListNickName = m_ListNickName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetListNickName(const nsAString& aListNickName) {
+ m_ListNickName = aListNickName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDescription(nsAString& aDescription) {
+ aDescription = m_Description;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetDescription(const nsAString& aDescription) {
+ m_Description = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsMailList(bool* aIsMailList) {
+ *aIsMailList = m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetIsMailList(bool aIsMailList) {
+ m_IsMailList = aIsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::CopyMailList(nsIAbDirectory* srcList) {
+ SetIsMailList(true);
+
+ nsString str;
+ srcList->GetDirName(str);
+ SetDirName(str);
+ srcList->GetListNickName(str);
+ SetListNickName(str);
+ srcList->GetDescription(str);
+ SetDescription(str);
+
+ nsAutoCString uid;
+ srcList->GetUID(uid);
+ SetUID(uid);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::Init(const char* aURI) {
+ mURI = aURI;
+ mIsValidURI = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::CleanUp(JSContext* cx, Promise** retval) {
+ nsIGlobalObject* globalObject =
+ xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx));
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ promise->MaybeResolveWithUndefined();
+ promise.forget(retval);
+
+ return NS_OK;
+}
+
+// nsIAbDirectory NOT IMPLEMENTED methods
+NS_IMETHODIMP
+nsAbDirProperty::GetChildNodes(nsTArray<RefPtr<nsIAbDirectory>>& childList) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::GetChildCardCount(uint32_t* count) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::GetChildCards(nsTArray<RefPtr<nsIAbCard>>& childCards) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::DeleteDirectory(nsIAbDirectory* directory) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::HasCard(nsIAbCard* cards, bool* hasCard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::HasDirectory(nsIAbDirectory* dir, bool* hasDir) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::HasMailListWithName(const nsAString& aName, bool* aHasList) {
+ NS_ENSURE_ARG_POINTER(aHasList);
+
+ *aHasList = false;
+ nsCOMPtr<nsIAbDirectory> aDir;
+ nsresult rv = GetMailListFromName(aName, getter_AddRefs(aDir));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDir) {
+ *aHasList = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::AddMailList(nsIAbDirectory* list,
+ nsIAbDirectory** addedList) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::EditMailListToDatabase(nsIAbCard* listCard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::AddCard(nsIAbCard* childCard,
+ nsIAbCard** addedCard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::ModifyCard(nsIAbCard* aModifiedCard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::DeleteCards(
+ const nsTArray<RefPtr<nsIAbCard>>& aCards) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::DropCard(nsIAbCard* childCard,
+ bool needToCopyCard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::CardForEmailAddress(
+ const nsACString& aEmailAddress, nsIAbCard** aAbCard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetCardFromProperty(const char* aProperty,
+ const nsACString& aValue,
+ bool caseSensitive,
+ nsIAbCard** result) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetCardsFromProperty(
+ const char* aProperty, const nsACString& aValue, bool caseSensitive,
+ nsTArray<RefPtr<nsIAbCard>>& result) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsAbDirProperty::GetMailListFromName(const nsAString& aName,
+ nsIAbDirectory** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+ bool supportsLists = false;
+ nsresult rv = GetSupportsMailingLists(&supportsLists);
+ if (NS_FAILED(rv) || !supportsLists) return NS_OK;
+
+ if (m_IsMailList) return NS_OK;
+
+ if (!m_AddressList) {
+ nsresult rv;
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t listCount = 0;
+ rv = m_AddressList->GetLength(&listCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < listCount; i++) {
+ nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(m_AddressList, i, &rv));
+ if (NS_SUCCEEDED(rv) && listDir) {
+ nsAutoString listName;
+ rv = listDir->GetDirName(listName);
+ if (NS_SUCCEEDED(rv) && listName.Equals(aName)) {
+ listDir.forget(aResult);
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetSupportsMailingLists(
+ bool* aSupportsMailingsLists) {
+ NS_ENSURE_ARG_POINTER(aSupportsMailingsLists);
+ // We don't currently support nested mailing lists, so only return true if
+ // we're not a mailing list.
+ *aSupportsMailingsLists = !m_IsMailList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetReadOnly(bool* aReadOnly) {
+ NS_ENSURE_ARG_POINTER(aReadOnly);
+ // Default is that we are writable. Any implementation that is read-only must
+ // override this method.
+ *aReadOnly = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsRemote(bool* aIsRemote) {
+ NS_ENSURE_ARG_POINTER(aIsRemote);
+ *aIsRemote = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIsSecure(bool* aIsSecure) {
+ NS_ENSURE_ARG_POINTER(aIsSecure);
+ *aIsSecure = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::UseForAutocomplete(
+ const nsACString& aIdentityKey, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Is local autocomplete enabled?
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = prefBranch->GetBoolPref("mail.enable_autocomplete", aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If autocomplete is generally enabled, check if it has been disabled
+ // explicitly for this directory.
+ if (*aResult) {
+ (void)GetBoolValue("enable_autocomplete", true, aResult);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetDirPrefId(nsACString& aDirPrefId) {
+ aDirPrefId = m_DirPrefId;
+ return NS_OK;
+}
+
+nsresult nsAbDirProperty::InitDirectoryPrefs() {
+ if (m_DirPrefId.IsEmpty()) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefService(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString realPrefId(m_DirPrefId);
+ realPrefId.Append('.');
+
+ return prefService->GetBranch(realPrefId.get(),
+ getter_AddRefs(m_DirectoryPrefs));
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetIntValue(const char* aName,
+ int32_t aDefaultValue,
+ int32_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (NS_FAILED(m_DirectoryPrefs->GetIntPref(aName, aResult)))
+ *aResult = aDefaultValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetBoolValue(const char* aName,
+ bool aDefaultValue, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (NS_FAILED(m_DirectoryPrefs->GetBoolPref(aName, aResult)))
+ *aResult = aDefaultValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::GetStringValue(const char* aName,
+ const nsACString& aDefaultValue,
+ nsACString& aResult) {
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString value;
+
+ /* unfortunately, there may be some prefs out there which look like (null) */
+ if (NS_SUCCEEDED(m_DirectoryPrefs->GetCharPref(aName, value)) &&
+ !value.EqualsLiteral("(null"))
+ aResult = value;
+ else
+ aResult = aDefaultValue;
+
+ return NS_OK;
+}
+/*
+ * Get localized unicode string pref from properties file, convert into an
+ * UTF8 string since address book prefs store as UTF8 strings. So far there
+ * are 2 default prefs stored in addressbook.properties.
+ * "ldap_2.servers.pab.description"
+ * "ldap_2.servers.history.description"
+ */
+NS_IMETHODIMP nsAbDirProperty::GetLocalizedStringValue(
+ const char* aName, const nsACString& aDefaultValue, nsACString& aResult) {
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsString wvalue;
+ nsCOMPtr<nsIPrefLocalizedString> locStr;
+
+ nsresult rv = m_DirectoryPrefs->GetComplexValue(
+ aName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(locStr));
+ if (NS_SUCCEEDED(rv)) {
+ rv = locStr->ToString(getter_Copies(wvalue));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (wvalue.IsEmpty())
+ aResult = aDefaultValue;
+ else
+ CopyUTF16toUTF8(wvalue, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetIntValue(const char* aName, int32_t aValue) {
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetIntPref(aName, aValue);
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetBoolValue(const char* aName, bool aValue) {
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetBoolPref(aName, aValue);
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetStringValue(const char* aName,
+ const nsACString& aValue) {
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return m_DirectoryPrefs->SetCharPref(aName, aValue);
+}
+
+NS_IMETHODIMP nsAbDirProperty::SetLocalizedStringValue(
+ const char* aName, const nsACString& aValue) {
+ if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs()))
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefLocalizedString> locStr(
+ do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = locStr->SetData(NS_ConvertUTF8toUTF16(aValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return m_DirectoryPrefs->SetComplexValue(
+ aName, NS_GET_IID(nsIPrefLocalizedString), locStr);
+}
+
+NS_IMETHODIMP nsAbDirProperty::Search(const nsAString& query,
+ const nsAString& searchString,
+ nsIAbDirSearchListener* listener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbDirProperty.h b/comm/mailnews/addrbook/src/nsAbDirProperty.h
new file mode 100644
index 0000000000..cf3bd59b68
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbDirProperty.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/********************************************************************************************************
+
+ Interface for representing Address Book Directory
+
+*********************************************************************************************************/
+
+#ifndef nsAbDirProperty_h__
+#define nsAbDirProperty_h__
+
+#include "nsIAbDirectory.h" /* include the interface we are going to support */
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIPrefBranch.h"
+#include "nsIMutableArray.h"
+#include "nsWeakReference.h"
+
+/*
+ * Address Book Directory
+ */
+
+class nsAbDirProperty : public nsIAbDirectory, public nsSupportsWeakReference {
+ public:
+ nsAbDirProperty(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRECTORY
+
+ protected:
+ virtual ~nsAbDirProperty(void);
+
+ /**
+ * Initialise the directory prefs for this branch
+ */
+ nsresult InitDirectoryPrefs();
+
+ uint32_t m_LastModifiedDate;
+
+ nsString m_ListDirName;
+ nsString m_ListName;
+ nsString m_ListNickName;
+ nsString m_Description;
+ bool m_IsMailList;
+
+ nsCString mURI;
+ nsCString mUID;
+ bool mIsValidURI;
+
+ /*
+ * Note that any derived implementations should ensure that this item
+ * (m_DirPrefId) is correctly initialised correctly
+ */
+ nsCString m_DirPrefId; // ie,"ldap_2.servers.pab"
+
+ nsCOMPtr<nsIPrefBranch> m_DirectoryPrefs;
+ nsCOMPtr<nsIMutableArray> m_AddressList;
+};
+#endif
diff --git a/comm/mailnews/addrbook/src/nsAbDirectoryQuery.cpp b/comm/mailnews/addrbook/src/nsAbDirectoryQuery.cpp
new file mode 100644
index 0000000000..f0f5d18b0c
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbDirectoryQuery.cpp
@@ -0,0 +1,421 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIAbCard.h"
+#include "nsAbDirectoryQuery.h"
+#include "nsAbDirectoryQueryProxy.h"
+#include "nsAbBooleanExpression.h"
+#include "nsComponentManagerUtils.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsISimpleEnumerator.h"
+#include "nsMsgUtils.h"
+#include "nsQueryObject.h"
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQuerySimpleBooleanExpression,
+ nsIAbBooleanExpression)
+
+nsAbDirectoryQuerySimpleBooleanExpression::
+ nsAbDirectoryQuerySimpleBooleanExpression()
+ : mOperation(nsIAbBooleanOperationTypes::AND) {}
+
+nsAbDirectoryQuerySimpleBooleanExpression::
+ ~nsAbDirectoryQuerySimpleBooleanExpression() {}
+
+/* attribute nsAbBooleanOperationType operation; */
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::GetOperation(
+ nsAbBooleanOperationType* aOperation) {
+ if (!aOperation) return NS_ERROR_NULL_POINTER;
+
+ *aOperation = mOperation;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::SetOperation(
+ nsAbBooleanOperationType aOperation) {
+ if (aOperation != nsIAbBooleanOperationTypes::AND &&
+ aOperation != nsIAbBooleanOperationTypes::OR)
+ return NS_ERROR_FAILURE;
+
+ mOperation = aOperation;
+
+ return NS_OK;
+}
+
+/* attribute Array<nsISupports> expressions; */
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::GetExpressions(
+ nsTArray<RefPtr<nsISupports>>& aExpressions) {
+ aExpressions = mExpressions.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::SetExpressions(
+ const nsTArray<RefPtr<nsISupports>>& aExpressions) {
+ // Ensure all the items are of the right type.
+ nsresult rv;
+ nsCOMPtr<nsIAbBooleanConditionString> queryExpression;
+ for (auto expression : aExpressions) {
+ queryExpression = do_QueryInterface(expression, &rv);
+ if (NS_FAILED(rv)) return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Values ok, so we can just save and return.
+ mExpressions = aExpressions.Clone();
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryArguments, nsIAbDirectoryQueryArguments)
+
+nsAbDirectoryQueryArguments::nsAbDirectoryQueryArguments()
+ : mQuerySubDirectories(true) {}
+
+nsAbDirectoryQueryArguments::~nsAbDirectoryQueryArguments() {}
+
+/* attribute nsISupports matchItems; */
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetExpression(
+ nsISupports** aExpression) {
+ if (!aExpression) return NS_ERROR_NULL_POINTER;
+
+ NS_IF_ADDREF(*aExpression = mExpression);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetExpression(
+ nsISupports* aExpression) {
+ mExpression = aExpression;
+ return NS_OK;
+}
+
+/* attribute boolean querySubDirectories; */
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetQuerySubDirectories(
+ bool* aQuerySubDirectories) {
+ NS_ENSURE_ARG_POINTER(aQuerySubDirectories);
+ *aQuerySubDirectories = mQuerySubDirectories;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetQuerySubDirectories(
+ bool aQuerySubDirectories) {
+ mQuerySubDirectories = aQuerySubDirectories;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetTypeSpecificArg(
+ nsISupports** aArg) {
+ NS_ENSURE_ARG_POINTER(aArg);
+
+ NS_IF_ADDREF(*aArg = mTypeSpecificArg);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetTypeSpecificArg(
+ nsISupports* aArg) {
+ mTypeSpecificArg = aArg;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::GetFilter(nsACString& aFilter) {
+ aFilter.Assign(mFilter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbDirectoryQueryArguments::SetFilter(
+ const nsACString& aFilter) {
+ mFilter.Assign(aFilter);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryPropertyValue,
+ nsIAbDirectoryQueryPropertyValue)
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue() {}
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue(
+ const char* aName, const char16_t* aValue) {
+ mName = aName;
+ mValue = aValue;
+}
+
+nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue(
+ const char* aName, nsISupports* aValueISupports) {
+ mName = aName;
+ mValueISupports = aValueISupports;
+}
+
+nsAbDirectoryQueryPropertyValue::~nsAbDirectoryQueryPropertyValue() {}
+
+/* read only attribute string name; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetName(char** aName) {
+ *aName = mName.IsEmpty() ? 0 : ToNewCString(mName);
+
+ return NS_OK;
+}
+
+/* read only attribute wstring value; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetValue(char16_t** aValue) {
+ *aValue = ToNewUnicode(mValue);
+ if (!(*aValue))
+ return NS_ERROR_OUT_OF_MEMORY;
+ else
+ return NS_OK;
+}
+
+/* readonly attribute nsISupports valueISupports; */
+NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetValueISupports(
+ nsISupports** aValueISupports) {
+ if (!mValueISupports) return NS_ERROR_NULL_POINTER;
+
+ NS_IF_ADDREF(*aValueISupports = mValueISupports);
+ return NS_OK;
+}
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsAbDirectoryQuery, nsIAbDirectoryQuery)
+
+nsAbDirectoryQuery::nsAbDirectoryQuery() {}
+
+nsAbDirectoryQuery::~nsAbDirectoryQuery() {}
+
+NS_IMETHODIMP nsAbDirectoryQuery::DoQuery(
+ nsIAbDirectory* aDirectory, nsIAbDirectoryQueryArguments* arguments,
+ nsIAbDirSearchListener* listener, int32_t resultLimit, int32_t timeOut,
+ int32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ nsCOMPtr<nsISupports> supportsExpression;
+ nsresult rv = arguments->GetExpression(getter_AddRefs(supportsExpression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression(
+ do_QueryInterface(supportsExpression, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool doSubDirectories;
+ rv = arguments->GetQuerySubDirectories(&doSubDirectories);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = query(aDirectory, expression, listener, doSubDirectories, &resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = listener->OnSearchFinished(rv, true, nullptr, ""_ns);
+
+ *_retval = 0;
+ return rv;
+}
+
+/* void stopQuery (in long contextID); */
+NS_IMETHODIMP nsAbDirectoryQuery::StopQuery(int32_t contextID) { return NS_OK; }
+
+nsresult nsAbDirectoryQuery::query(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit) {
+ if (*resultLimit == 0) return NS_OK;
+
+ nsresult rv = queryCards(directory, expression, listener, resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*resultLimit != 0 && doSubDirectories) {
+ rv = queryChildren(directory, expression, listener, doSubDirectories,
+ resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult nsAbDirectoryQuery::queryChildren(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories,
+ int32_t* resultLimit) {
+ nsTArray<RefPtr<nsIAbDirectory>> subDirectories;
+ nsresult rv = directory->GetChildNodes(subDirectories);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIAbDirectory* subDirectory : subDirectories) {
+ rv = query(subDirectory, expression, listener, doSubDirectories,
+ resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::queryCards(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit) {
+ nsTArray<RefPtr<nsIAbCard>> cards;
+ nsresult rv = directory->GetChildCards(cards);
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIAbCard* card : cards) {
+ rv = matchCard(card, expression, listener, resultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*resultLimit == 0) return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::matchCard(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ int32_t* resultLimit) {
+ bool matchFound = false;
+ nsresult rv = matchCardExpression(card, expression, &matchFound);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matchFound) {
+ (*resultLimit)--;
+ rv = listener->OnSearchFoundCard(card);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult nsAbDirectoryQuery::matchCardExpression(
+ nsIAbCard* card, nsIAbBooleanExpression* expression, bool* result) {
+ nsAbBooleanOperationType operation;
+ nsresult rv = expression->GetOperation(&operation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsISupports>> childExpressions;
+ rv = expression->GetExpressions(childExpressions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count = childExpressions.Length();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (operation == nsIAbBooleanOperationTypes::NOT && count > 1)
+ return NS_ERROR_FAILURE;
+
+ bool value = *result = false;
+ nsCOMPtr<nsIAbBooleanConditionString> childCondition;
+ nsCOMPtr<nsIAbBooleanExpression> childExpression;
+
+ for (uint32_t i = 0; i < count; i++) {
+ childCondition = do_QueryObject(childExpressions[i], &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = matchCardCondition(card, childCondition, &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ childExpression = do_QueryObject(childExpressions[i], &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = matchCardExpression(card, childExpression, &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else
+ return NS_ERROR_FAILURE;
+ }
+ if (operation == nsIAbBooleanOperationTypes::OR && value)
+ break;
+ else if (operation == nsIAbBooleanOperationTypes::AND && !value)
+ break;
+ else if (operation == nsIAbBooleanOperationTypes::NOT)
+ value = !value;
+ }
+ *result = value;
+
+ return NS_OK;
+}
+
+nsresult nsAbDirectoryQuery::matchCardCondition(
+ nsIAbCard* card, nsIAbBooleanConditionString* condition, bool* matchFound) {
+ nsAbBooleanConditionType conditionType;
+ nsresult rv = condition->GetCondition(&conditionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString name;
+ rv = condition->GetName(getter_Copies(name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (name.Equals("card:nsIAbCard")) {
+ *matchFound = (conditionType == nsIAbBooleanConditionTypes::Exists);
+ return NS_OK;
+ }
+
+ nsString matchValue;
+ rv = condition->GetValue(getter_Copies(matchValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (name.EqualsLiteral("IsMailList")) {
+ bool isMailList;
+ rv = card->GetIsMailList(&isMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only equals is supported.
+ if (conditionType != nsIAbBooleanConditionTypes::Is)
+ return NS_ERROR_FAILURE;
+
+ *matchFound = isMailList ? matchValue.EqualsLiteral("TRUE")
+ : matchValue.EqualsLiteral("FALSE");
+ return NS_OK;
+ }
+
+ nsString value;
+ (void)card->GetPropertyAsAString(name.get(), value);
+
+ if (value.IsEmpty()) {
+ *matchFound = (conditionType == nsIAbBooleanConditionTypes::DoesNotExist)
+ ? true
+ : false;
+ return NS_OK;
+ }
+
+ /* TODO
+ * What about allowing choice between case insensitive
+ * and case sensitive comparisons?
+ *
+ */
+ switch (conditionType) {
+ case nsIAbBooleanConditionTypes::Exists:
+ *matchFound = true;
+ break;
+ case nsIAbBooleanConditionTypes::Contains:
+ *matchFound = CaseInsensitiveFindInReadable(matchValue, value);
+ break;
+ case nsIAbBooleanConditionTypes::DoesNotContain:
+ *matchFound = !CaseInsensitiveFindInReadable(matchValue, value);
+ break;
+ case nsIAbBooleanConditionTypes::Is:
+ *matchFound = value.Equals(matchValue, nsCaseInsensitiveStringComparator);
+ break;
+ case nsIAbBooleanConditionTypes::IsNot:
+ *matchFound =
+ !value.Equals(matchValue, nsCaseInsensitiveStringComparator);
+ break;
+ case nsIAbBooleanConditionTypes::BeginsWith:
+ *matchFound = StringBeginsWith(value, matchValue,
+ nsCaseInsensitiveStringComparator);
+ break;
+ case nsIAbBooleanConditionTypes::LessThan:
+ *matchFound =
+ Compare(value, matchValue, nsCaseInsensitiveStringComparator) < 0;
+ break;
+ case nsIAbBooleanConditionTypes::GreaterThan:
+ *matchFound =
+ Compare(value, matchValue, nsCaseInsensitiveStringComparator) > 0;
+ break;
+ case nsIAbBooleanConditionTypes::EndsWith:
+ *matchFound =
+ StringEndsWith(value, matchValue, nsCaseInsensitiveStringComparator);
+ break;
+ case nsIAbBooleanConditionTypes::SoundsLike:
+ case nsIAbBooleanConditionTypes::RegExp:
+ *matchFound = false;
+ break;
+ default:
+ *matchFound = false;
+ }
+
+ return rv;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbDirectoryQuery.h b/comm/mailnews/addrbook/src/nsAbDirectoryQuery.h
new file mode 100644
index 0000000000..2c0e7b0654
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbDirectoryQuery.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsAbDirectoryQuery_h__
+#define nsAbDirectoryQuery_h__
+
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirectory.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIArray.h"
+#include "nsIAbBooleanExpression.h"
+
+class nsAbDirectoryQuerySimpleBooleanExpression
+ : public nsIAbBooleanExpression {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABBOOLEANEXPRESSION
+
+ nsAbDirectoryQuerySimpleBooleanExpression();
+
+ private:
+ virtual ~nsAbDirectoryQuerySimpleBooleanExpression();
+
+ public:
+ nsTArray<RefPtr<nsISupports>> mExpressions;
+ nsAbBooleanOperationType mOperation;
+};
+
+class nsAbDirectoryQueryArguments : public nsIAbDirectoryQueryArguments {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERYARGUMENTS
+
+ nsAbDirectoryQueryArguments();
+
+ private:
+ virtual ~nsAbDirectoryQueryArguments();
+
+ protected:
+ nsCOMPtr<nsISupports> mExpression;
+ nsCOMPtr<nsISupports> mTypeSpecificArg;
+ bool mQuerySubDirectories;
+ nsCString mFilter;
+};
+
+class nsAbDirectoryQueryPropertyValue
+ : public nsIAbDirectoryQueryPropertyValue {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERYPROPERTYVALUE
+
+ nsAbDirectoryQueryPropertyValue();
+ nsAbDirectoryQueryPropertyValue(const char* aName, const char16_t* aValue);
+ nsAbDirectoryQueryPropertyValue(const char* aName,
+ nsISupports* aValueISupports);
+
+ protected:
+ virtual ~nsAbDirectoryQueryPropertyValue();
+ nsCString mName;
+ nsString mValue;
+ nsCOMPtr<nsISupports> mValueISupports;
+};
+
+class nsAbDirectoryQuery : public nsIAbDirectoryQuery {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABDIRECTORYQUERY
+
+ nsAbDirectoryQuery();
+
+ protected:
+ virtual ~nsAbDirectoryQuery();
+ nsresult query(nsIAbDirectory* directory, nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener, bool doSubDirectories,
+ int32_t* resultLimit);
+ nsresult queryChildren(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener,
+ bool doSubDirectories, int32_t* resultLimit);
+ nsresult queryCards(nsIAbDirectory* directory,
+ nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener, int32_t* resultLimit);
+ nsresult matchCard(nsIAbCard* card, nsIAbBooleanExpression* expression,
+ nsIAbDirSearchListener* listener, int32_t* resultLimit);
+ nsresult matchCardExpression(nsIAbCard* card,
+ nsIAbBooleanExpression* expression,
+ bool* result);
+ nsresult matchCardCondition(nsIAbCard* card,
+ nsIAbBooleanConditionString* condition,
+ bool* matchFound);
+};
+
+#endif
diff --git a/comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp b/comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp
new file mode 100644
index 0000000000..984746b82a
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAbDirectoryQuery.h"
+#include "nsAbDirectoryQueryProxy.h"
+
+NS_IMPL_ISUPPORTS(nsAbDirectoryQueryProxy, nsIAbDirectoryQueryProxy,
+ nsIAbDirectoryQuery)
+
+nsAbDirectoryQueryProxy::nsAbDirectoryQueryProxy() : mInitiated(false) {}
+
+nsAbDirectoryQueryProxy::~nsAbDirectoryQueryProxy() {}
+
+/* void initiate (in nsIAbDirectory directory); */
+NS_IMETHODIMP nsAbDirectoryQueryProxy::Initiate() {
+ if (mInitiated) return NS_OK;
+
+ mDirectoryQuery = new nsAbDirectoryQuery();
+
+ mInitiated = true;
+
+ return NS_OK;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h b/comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h
new file mode 100644
index 0000000000..542d58e6e3
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsAbDirectoryQueryProxy_h__
+#define nsAbDirectoryQueryProxy_h__
+
+#include "nsIAbDirectoryQueryProxy.h"
+#include "nsCOMPtr.h"
+
+class nsAbDirectoryQueryProxy : public nsIAbDirectoryQueryProxy {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSIABDIRECTORYQUERY(mDirectoryQuery->)
+ NS_DECL_NSIABDIRECTORYQUERYPROXY
+
+ nsAbDirectoryQueryProxy();
+
+ protected:
+ virtual ~nsAbDirectoryQueryProxy();
+ bool mInitiated;
+ nsCOMPtr<nsIAbDirectoryQuery> mDirectoryQuery;
+};
+
+#endif
diff --git a/comm/mailnews/addrbook/src/nsAbLDIFService.cpp b/comm/mailnews/addrbook/src/nsAbLDIFService.cpp
new file mode 100644
index 0000000000..2d40bec9b2
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbLDIFService.cpp
@@ -0,0 +1,787 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsIAbDirectory.h"
+#include "nsIAbCard.h"
+#include "nsString.h"
+#include "nsAbLDIFService.h"
+#include "nsIFile.h"
+#include "nsILineInputStream.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsISeekableStream.h"
+#include "mdb.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "prprf.h"
+#include "nsCRTGlue.h"
+#include "nsTArray.h"
+#include "nsIComponentManager.h"
+
+#include <ctype.h>
+
+NS_IMPL_ISUPPORTS(nsAbLDIFService, nsIAbLDIFService)
+
+// If we get a line longer than 32K it's just toooooo bad!
+#define kTextAddressBufferSz (64 * 1024)
+
+nsAbLDIFService::nsAbLDIFService() {
+ mStoreLocAsHome = false;
+ mLFCount = 0;
+ mCRCount = 0;
+}
+
+nsAbLDIFService::~nsAbLDIFService() {}
+
+#define RIGHT2 0x03
+#define RIGHT4 0x0f
+#define CONTINUED_LINE_MARKER '\001'
+
+// XXX TODO fix me
+// use the NSPR base64 library. see plbase64.h
+// see bug #145367
+static unsigned char b642nib[0x80] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
+ 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24,
+ 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+ 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+NS_IMETHODIMP nsAbLDIFService::ImportLDIFFile(nsIAbDirectory* aDirectory,
+ nsIFile* aSrc,
+ bool aStoreLocAsHome,
+ uint32_t* aProgress) {
+ NS_ENSURE_ARG_POINTER(aSrc);
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ mStoreLocAsHome = aStoreLocAsHome;
+
+ char buf[1024];
+ char* pBuf = &buf[0];
+ int32_t startPos = 0;
+ uint32_t len = 0;
+ nsTArray<int32_t> listPosArray; // where each list/group starts in ldif file
+ nsTArray<int32_t> listSizeArray; // size of the list/group info
+ int32_t savedStartPos = 0;
+ int32_t filePos = 0;
+ uint64_t bytesLeft = 0;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize the parser for a run...
+ mLdifLine.Truncate();
+
+ while (NS_SUCCEEDED(inputStream->Available(&bytesLeft)) && bytesLeft > 0) {
+ if (NS_SUCCEEDED(inputStream->Read(pBuf, sizeof(buf), &len)) && len > 0) {
+ startPos = 0;
+
+ while (NS_SUCCEEDED(GetLdifStringRecord(buf, len, startPos))) {
+ if (mLdifLine.Find("groupOfNames") == -1)
+ AddLdifRowToDatabase(aDirectory, false);
+ else {
+ // keep file position for mailing list
+ listPosArray.AppendElement(savedStartPos);
+ listSizeArray.AppendElement(filePos + startPos - savedStartPos);
+ ClearLdifRecordBuffer();
+ }
+ savedStartPos = filePos + startPos;
+ }
+ filePos += len;
+ if (aProgress) *aProgress = (uint32_t)filePos;
+ }
+ }
+ // last row
+ if (!mLdifLine.IsEmpty() && mLdifLine.Find("groupOfNames") == -1)
+ AddLdifRowToDatabase(aDirectory, false);
+
+ // mail Lists
+ int32_t i, pos;
+ uint32_t size;
+ int32_t listTotal = listPosArray.Length();
+ char* listBuf;
+ ClearLdifRecordBuffer(); // make sure the buffer is clean
+
+ nsCOMPtr<nsISeekableStream> seekableStream =
+ do_QueryInterface(inputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < listTotal; i++) {
+ pos = listPosArray[i];
+ size = listSizeArray[i];
+ if (NS_SUCCEEDED(
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, pos))) {
+ // Allocate enough space for the lists/groups as the size varies.
+ listBuf = (char*)PR_Malloc(size);
+ if (!listBuf) continue;
+ if (NS_SUCCEEDED(inputStream->Read(listBuf, size, &len)) && len > 0) {
+ startPos = 0;
+
+ while (NS_SUCCEEDED(GetLdifStringRecord(listBuf, len, startPos))) {
+ if (mLdifLine.Find("groupOfNames") != -1) {
+ AddLdifRowToDatabase(aDirectory, true);
+ if (NS_SUCCEEDED(
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0)))
+ break;
+ }
+ }
+ }
+ PR_FREEIF(listBuf);
+ }
+ }
+
+ rv = inputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+/*
+ * str_parse_line - takes a line of the form "type:[:] value" and splits it
+ * into components "type" and "value". if a double colon separates type from
+ * value, then value is encoded in base 64, and parse_line un-decodes it
+ * (in place) before returning.
+ * in LDIF, non-ASCII data is treated as base64 encoded UTF-8
+ */
+
+nsresult nsAbLDIFService::str_parse_line(char* line, char** type, char** value,
+ int* vlen) const {
+ char *p, *s, *d, *byte, *stop;
+ char nib;
+ int i, b64;
+
+ /* skip any leading space */
+ while (isspace(*line)) {
+ line++;
+ }
+ *type = line;
+
+ for (s = line; *s && *s != ':'; s++)
+ ; /* NULL */
+ if (*s == '\0') {
+ return NS_ERROR_FAILURE;
+ }
+
+ /* trim any space between type and : */
+ for (p = s - 1; p > line && isspace(*p); p--) {
+ *p = '\0';
+ }
+ *s++ = '\0';
+
+ /* check for double : - indicates base 64 encoded value */
+ if (*s == ':') {
+ s++;
+ b64 = 1;
+ /* single : - normally encoded value */
+ } else {
+ b64 = 0;
+ }
+
+ /* skip space between : and value */
+ while (isspace(*s)) {
+ s++;
+ }
+
+ /* if no value is present, error out */
+ if (*s == '\0') {
+ return NS_ERROR_FAILURE;
+ }
+
+ /* check for continued line markers that should be deleted */
+ for (p = s, d = s; *p; p++) {
+ if (*p != CONTINUED_LINE_MARKER) *d++ = *p;
+ }
+ *d = '\0';
+
+ *value = s;
+ if (b64) {
+ stop = PL_strchr(s, '\0');
+ byte = s;
+ for (p = s, *vlen = 0; p < stop; p += 4, *vlen += 3) {
+ for (i = 0; i < 3; i++) {
+ if (p[i] != '=' && (p[i] & 0x80 || b642nib[p[i] & 0x7f] > 0x3f)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ /* first digit */
+ nib = b642nib[p[0] & 0x7f];
+ byte[0] = nib << 2;
+ /* second digit */
+ nib = b642nib[p[1] & 0x7f];
+ byte[0] |= nib >> 4;
+ byte[1] = (nib & RIGHT4) << 4;
+ /* third digit */
+ if (p[2] == '=') {
+ *vlen += 1;
+ break;
+ }
+ nib = b642nib[p[2] & 0x7f];
+ byte[1] |= nib >> 2;
+ byte[2] = (nib & RIGHT2) << 6;
+ /* fourth digit */
+ if (p[3] == '=') {
+ *vlen += 2;
+ break;
+ }
+ nib = b642nib[p[3] & 0x7f];
+ byte[2] |= nib;
+
+ byte += 3;
+ }
+ s[*vlen] = '\0';
+ } else {
+ *vlen = (int)(d - s);
+ }
+ return NS_OK;
+}
+
+/*
+ * str_getline - return the next "line" (minus newline) of input from a
+ * string buffer of lines separated by newlines, terminated by \n\n
+ * or \0. this routine handles continued lines, bundling them into
+ * a single big line before returning. if a line begins with a white
+ * space character, it is a continuation of the previous line. the white
+ * space character (nb: only one char), and preceding newline are changed
+ * into CONTINUED_LINE_MARKER chars, to be deleted later by the
+ * str_parse_line() routine above.
+ *
+ * it takes a pointer to a pointer to the buffer on the first call,
+ * which it updates and must be supplied on subsequent calls.
+ */
+
+char* nsAbLDIFService::str_getline(char** next) const {
+ char* lineStr;
+ char c;
+
+ if (*next == nullptr || **next == '\n' || **next == '\0') {
+ return (nullptr);
+ }
+
+ lineStr = *next;
+ while ((*next = PL_strchr(*next, '\n')) != NULL) {
+ c = *(*next + 1);
+ if (isspace(c) && c != '\n') {
+ **next = CONTINUED_LINE_MARKER;
+ *(*next + 1) = CONTINUED_LINE_MARKER;
+ } else {
+ *(*next)++ = '\0';
+ break;
+ }
+ }
+
+ return (lineStr);
+}
+
+nsresult nsAbLDIFService::GetLdifStringRecord(char* buf, int32_t len,
+ int32_t& stopPos) {
+ for (; stopPos < len; stopPos++) {
+ char c = buf[stopPos];
+
+ if (c == 0xA) {
+ mLFCount++;
+ } else if (c == 0xD) {
+ mCRCount++;
+ } else {
+ if (mLFCount == 0 && mCRCount == 0)
+ mLdifLine.Append(c);
+ else if ((mLFCount > 1) || (mCRCount > 2 && mLFCount) ||
+ (!mLFCount && mCRCount > 1)) {
+ return NS_OK;
+ } else if ((mLFCount == 1 || mCRCount == 1)) {
+ mLdifLine.Append('\n');
+ mLdifLine.Append(c);
+ mLFCount = 0;
+ mCRCount = 0;
+ }
+ }
+ }
+
+ if (((stopPos == len) && (mLFCount > 1)) || (mCRCount > 2 && mLFCount) ||
+ (!mLFCount && mCRCount > 1))
+ return NS_OK;
+
+ return NS_ERROR_FAILURE;
+}
+
+void nsAbLDIFService::AddLdifRowToDatabase(nsIAbDirectory* aDirectory,
+ bool bIsList) {
+ if (!aDirectory) {
+ return;
+ }
+
+ // If no data to process then reset CR/LF counters and return.
+ if (mLdifLine.IsEmpty()) {
+ mLFCount = 0;
+ mCRCount = 0;
+ return;
+ }
+
+ nsCOMPtr<nsIAbCard> newCard =
+ do_CreateInstance("@mozilla.org/addressbook/cardproperty;1");
+ nsTArray<nsCString> members;
+
+ char* cursor = ToNewCString(mLdifLine);
+ char* saveCursor = cursor; /* keep for deleting */
+ char* line = 0;
+ char* typeSlot = 0;
+ char* valueSlot = 0;
+ int length = 0; // the length of an ldif attribute
+ while ((line = str_getline(&cursor)) != nullptr) {
+ if (NS_SUCCEEDED(str_parse_line(line, &typeSlot, &valueSlot, &length))) {
+ nsAutoCString colType(typeSlot);
+ nsAutoCString column(valueSlot);
+
+ // 4.x exports attributes like "givenname",
+ // mozilla does "givenName" to be compliant with RFC 2798
+ ToLowerCase(colType);
+
+ if (colType.EqualsLiteral("member") ||
+ colType.EqualsLiteral("uniquemember")) {
+ members.AppendElement(column);
+ } else {
+ AddLdifColToDatabase(aDirectory, newCard, colType, column, bIsList);
+ }
+ } else
+ continue; // parse error: continue with next loop iteration
+ }
+ free(saveCursor);
+
+ if (bIsList) {
+ nsCOMPtr<nsIAbDirectory> newList =
+ do_CreateInstance("@mozilla.org/addressbook/directoryproperty;1");
+ newList->SetIsMailList(true);
+
+ nsAutoString temp;
+ newCard->GetDisplayName(temp);
+ newList->SetDirName(temp);
+ temp.Truncate();
+ newCard->GetPropertyAsAString(kNicknameProperty, temp);
+ newList->SetListNickName(temp);
+ temp.Truncate();
+ newCard->GetPropertyAsAString(kNotesProperty, temp);
+ newList->SetDescription(temp);
+
+ nsIAbDirectory* outList;
+ nsresult rv = aDirectory->AddMailList(newList, &outList);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ int32_t count = members.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ nsAutoCString email;
+ int32_t emailPos = members[i].Find("mail=");
+ emailPos += strlen("mail=");
+ email = Substring(members[i], emailPos);
+
+ nsCOMPtr<nsIAbCard> emailCard;
+ aDirectory->CardForEmailAddress(email, getter_AddRefs(emailCard));
+ if (emailCard) {
+ nsIAbCard* outCard;
+ outList->AddCard(emailCard, &outCard);
+ }
+ }
+ } else {
+ nsIAbCard* outCard;
+ aDirectory->AddCard(newCard, &outCard);
+ }
+
+ // Clear buffer for next record
+ ClearLdifRecordBuffer();
+}
+
+void nsAbLDIFService::AddLdifColToDatabase(nsIAbDirectory* aDirectory,
+ nsIAbCard* newCard,
+ nsCString colType, nsCString column,
+ bool bIsList) {
+ nsString value = NS_ConvertUTF8toUTF16(column);
+
+ char firstByte = colType.get()[0];
+ switch (firstByte) {
+ case 'b':
+ if (colType.EqualsLiteral("birthyear"))
+ newCard->SetPropertyAsAString(kBirthYearProperty, value);
+ else if (colType.EqualsLiteral("birthmonth"))
+ newCard->SetPropertyAsAString(kBirthMonthProperty, value);
+ else if (colType.EqualsLiteral("birthday"))
+ newCard->SetPropertyAsAString(kBirthDayProperty, value);
+ break; // 'b'
+
+ case 'c':
+ if (colType.EqualsLiteral("cn") || colType.EqualsLiteral("commonname")) {
+ newCard->SetDisplayName(value);
+ } else if (colType.EqualsLiteral("c") ||
+ colType.EqualsLiteral("countryname")) {
+ if (mStoreLocAsHome)
+ newCard->SetPropertyAsAString(kHomeCountryProperty, value);
+ else
+ newCard->SetPropertyAsAString(kWorkCountryProperty, value);
+ }
+
+ else if (colType.EqualsLiteral("cellphone"))
+ newCard->SetPropertyAsAString(kCellularProperty, value);
+
+ else if (colType.EqualsLiteral("carphone"))
+ newCard->SetPropertyAsAString(kCellularProperty, value);
+
+ else if (colType.EqualsLiteral("custom1"))
+ newCard->SetPropertyAsAString(kCustom1Property, value);
+
+ else if (colType.EqualsLiteral("custom2"))
+ newCard->SetPropertyAsAString(kCustom2Property, value);
+
+ else if (colType.EqualsLiteral("custom3"))
+ newCard->SetPropertyAsAString(kCustom3Property, value);
+
+ else if (colType.EqualsLiteral("custom4"))
+ newCard->SetPropertyAsAString(kCustom4Property, value);
+
+ else if (colType.EqualsLiteral("company"))
+ newCard->SetPropertyAsAString(kCompanyProperty, value);
+ break; // 'c'
+
+ case 'd':
+ if (colType.EqualsLiteral("description"))
+ newCard->SetPropertyAsAString(kNotesProperty, value);
+
+ else if (colType.EqualsLiteral("department"))
+ newCard->SetPropertyAsAString(kDepartmentProperty, value);
+
+ else if (colType.EqualsLiteral("displayname"))
+ newCard->SetDisplayName(value);
+ break; // 'd'
+
+ case 'f':
+
+ if (colType.EqualsLiteral("fax") ||
+ colType.EqualsLiteral("facsimiletelephonenumber"))
+ newCard->SetPropertyAsAString(kFaxProperty, value);
+ break; // 'f'
+
+ case 'g':
+ if (colType.EqualsLiteral("givenname")) newCard->SetFirstName(value);
+ break; // 'g'
+
+ case 'h':
+ if (colType.EqualsLiteral("homephone"))
+ newCard->SetPropertyAsAString(kHomePhoneProperty, value);
+
+ else if (colType.EqualsLiteral("homestreet"))
+ newCard->SetPropertyAsAString(kHomeAddressProperty, value);
+
+ else if (colType.EqualsLiteral("homeurl"))
+ newCard->SetPropertyAsAString(kHomeWebPageProperty, value);
+ break; // 'h'
+
+ case 'l':
+ if (colType.EqualsLiteral("l") || colType.EqualsLiteral("locality")) {
+ if (mStoreLocAsHome)
+ newCard->SetPropertyAsAString(kHomeCityProperty, value);
+ else
+ newCard->SetPropertyAsAString(kWorkCityProperty, value);
+ }
+ // labeledURI contains a URI and, optionally, a label
+ // This will remove the label and place the URI as the work URL
+ else if (colType.EqualsLiteral("labeleduri")) {
+ int32_t index = column.FindChar(' ');
+ if (index != -1) column.SetLength(index);
+
+ newCard->SetPropertyAsAString(kWorkWebPageProperty,
+ NS_ConvertUTF8toUTF16(column));
+ }
+
+ break; // 'l'
+
+ case 'm':
+ if (colType.EqualsLiteral("mail"))
+ newCard->SetPrimaryEmail(value);
+
+ else if (colType.EqualsLiteral("mobile"))
+ newCard->SetPropertyAsAString(kCellularProperty, value);
+
+ else if (colType.EqualsLiteral("mozilla_aimscreenname"))
+ newCard->SetPropertyAsAString(kAIMProperty, value);
+
+ else if (colType.EqualsLiteral("mozillacustom1"))
+ newCard->SetPropertyAsAString(kCustom1Property, value);
+
+ else if (colType.EqualsLiteral("mozillacustom2"))
+ newCard->SetPropertyAsAString(kCustom2Property, value);
+
+ else if (colType.EqualsLiteral("mozillacustom3"))
+ newCard->SetPropertyAsAString(kCustom3Property, value);
+
+ else if (colType.EqualsLiteral("mozillacustom4"))
+ newCard->SetPropertyAsAString(kCustom4Property, value);
+
+ else if (colType.EqualsLiteral("mozillahomecountryname"))
+ newCard->SetPropertyAsAString(kHomeCountryProperty, value);
+
+ else if (colType.EqualsLiteral("mozillahomelocalityname"))
+ newCard->SetPropertyAsAString(kHomeCityProperty, value);
+
+ else if (colType.EqualsLiteral("mozillahomestate"))
+ newCard->SetPropertyAsAString(kHomeStateProperty, value);
+
+ else if (colType.EqualsLiteral("mozillahomestreet"))
+ newCard->SetPropertyAsAString(kHomeAddressProperty, value);
+
+ else if (colType.EqualsLiteral("mozillahomestreet2"))
+ newCard->SetPropertyAsAString(kHomeAddress2Property, value);
+
+ else if (colType.EqualsLiteral("mozillahomepostalcode"))
+ newCard->SetPropertyAsAString(kHomeZipCodeProperty, value);
+
+ else if (colType.EqualsLiteral("mozillahomeurl"))
+ newCard->SetPropertyAsAString(kHomeWebPageProperty, value);
+
+ else if (colType.EqualsLiteral("mozillanickname"))
+ newCard->SetPropertyAsAString(kNicknameProperty, value);
+
+ else if (colType.EqualsLiteral("mozillasecondemail"))
+ newCard->SetPropertyAsAString(k2ndEmailProperty, value);
+
+ else if (colType.EqualsLiteral("mozillaworkstreet2"))
+ newCard->SetPropertyAsAString(kWorkAddress2Property, value);
+
+ else if (colType.EqualsLiteral("mozillaworkurl"))
+ newCard->SetPropertyAsAString(kWorkWebPageProperty, value);
+
+ break; // 'm'
+
+ case 'n':
+ if (colType.EqualsLiteral("notes"))
+ newCard->SetPropertyAsAString(kNotesProperty, value);
+
+ else if (colType.EqualsLiteral("nscpaimscreenname") ||
+ colType.EqualsLiteral("nsaimid"))
+ newCard->SetPropertyAsAString(kAIMProperty, value);
+
+ break; // 'n'
+
+ case 'o':
+ if (colType.EqualsLiteral("objectclass"))
+ break;
+
+ else if (colType.EqualsLiteral("ou") || colType.EqualsLiteral("orgunit"))
+ newCard->SetPropertyAsAString(kDepartmentProperty, value);
+
+ else if (colType.EqualsLiteral("o")) // organization
+ newCard->SetPropertyAsAString(kCompanyProperty, value);
+
+ break; // 'o'
+
+ case 'p':
+ if (colType.EqualsLiteral("postalcode")) {
+ if (mStoreLocAsHome)
+ newCard->SetPropertyAsAString(kHomeZipCodeProperty, value);
+ else
+ newCard->SetPropertyAsAString(kWorkZipCodeProperty, value);
+ }
+
+ else if (colType.EqualsLiteral("postofficebox")) {
+ nsAutoCString workAddr1, workAddr2;
+ SplitCRLFAddressField(column, workAddr1, workAddr2);
+ newCard->SetPropertyAsAString(kWorkAddressProperty,
+ NS_ConvertUTF8toUTF16(workAddr1));
+ newCard->SetPropertyAsAString(kWorkAddress2Property,
+ NS_ConvertUTF8toUTF16(workAddr2));
+ } else if (colType.EqualsLiteral("pager") ||
+ colType.EqualsLiteral("pagerphone"))
+ newCard->SetPropertyAsAString(kPagerProperty, value);
+
+ break; // 'p'
+
+ case 'r':
+ if (colType.EqualsLiteral("region")) {
+ newCard->SetPropertyAsAString(kWorkStateProperty, value);
+ }
+
+ break; // 'r'
+
+ case 's':
+ if (colType.EqualsLiteral("sn") || colType.EqualsLiteral("surname"))
+ newCard->SetPropertyAsAString(kLastNameProperty, value);
+
+ else if (colType.EqualsLiteral("street"))
+ newCard->SetPropertyAsAString(kWorkAddressProperty, value);
+
+ else if (colType.EqualsLiteral("streetaddress")) {
+ nsAutoCString addr1, addr2;
+ SplitCRLFAddressField(column, addr1, addr2);
+ if (mStoreLocAsHome) {
+ newCard->SetPropertyAsAString(kHomeAddressProperty,
+ NS_ConvertUTF8toUTF16(addr1));
+ newCard->SetPropertyAsAString(kHomeAddress2Property,
+ NS_ConvertUTF8toUTF16(addr2));
+ } else {
+ newCard->SetPropertyAsAString(kWorkAddressProperty,
+ NS_ConvertUTF8toUTF16(addr1));
+ newCard->SetPropertyAsAString(kWorkAddress2Property,
+ NS_ConvertUTF8toUTF16(addr2));
+ }
+ } else if (colType.EqualsLiteral("st")) {
+ if (mStoreLocAsHome)
+ newCard->SetPropertyAsAString(kHomeStateProperty, value);
+ else
+ newCard->SetPropertyAsAString(kWorkStateProperty, value);
+ }
+
+ break; // 's'
+
+ case 't':
+ if (colType.EqualsLiteral("title"))
+ newCard->SetPropertyAsAString(kJobTitleProperty, value);
+
+ else if (colType.EqualsLiteral("telephonenumber")) {
+ newCard->SetPropertyAsAString(kWorkPhoneProperty, value);
+ }
+
+ break; // 't'
+
+ case 'w':
+ if (colType.EqualsLiteral("workurl"))
+ newCard->SetPropertyAsAString(kWorkWebPageProperty, value);
+
+ break; // 'w'
+
+ case 'x':
+ if (colType.EqualsLiteral("xmozillanickname")) {
+ newCard->SetPropertyAsAString(kNicknameProperty, value);
+ }
+
+ break; // 'x'
+
+ case 'z':
+ if (colType.EqualsLiteral("zip")) // alias for postalcode
+ {
+ if (mStoreLocAsHome)
+ newCard->SetPropertyAsAString(kHomeZipCodeProperty, value);
+ else
+ newCard->SetPropertyAsAString(kWorkZipCodeProperty, value);
+ }
+
+ break; // 'z'
+
+ default:
+ break; // default
+ }
+}
+
+void nsAbLDIFService::ClearLdifRecordBuffer() {
+ if (!mLdifLine.IsEmpty()) {
+ mLdifLine.Truncate();
+ mLFCount = 0;
+ mCRCount = 0;
+ }
+}
+
+// Some common ldif fields, it an ldif file has NONE of these entries
+// then it is most likely NOT an ldif file!
+static const char* const sLDIFFields[] = {"objectclass", "sn", "dn", "cn",
+ "givenName", "mail", nullptr};
+#define kMaxLDIFLen 14
+
+// Count total number of legal ldif fields and records in the first 100 lines of
+// the file and if the average legal ldif field is 3 or higher than it's a valid
+// ldif file.
+NS_IMETHODIMP nsAbLDIFService::IsLDIFFile(nsIFile* pSrc, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(pSrc);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = false;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIInputStream> fileStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), pSrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILineInputStream> lineInputStream(
+ do_QueryInterface(fileStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t lineLen = 0;
+ int32_t lineCount = 0;
+ int32_t ldifFields = 0; // total number of legal ldif fields.
+ char field[kMaxLDIFLen];
+ int32_t fLen = 0;
+ const char* pChar;
+ int32_t recCount = 0; // total number of records.
+ int32_t i;
+ bool gotLDIF = false;
+ bool more = true;
+ nsCString line;
+
+ while (more && NS_SUCCEEDED(rv) && (lineCount < 100)) {
+ rv = lineInputStream->ReadLine(line, &more);
+
+ if (NS_SUCCEEDED(rv) && more) {
+ pChar = line.get();
+ lineLen = line.Length();
+ if (!lineLen && gotLDIF) {
+ recCount++;
+ gotLDIF = false;
+ }
+
+ if (lineLen && (*pChar != ' ') && (*pChar != '\t')) {
+ fLen = 0;
+
+ while (lineLen && (fLen < (kMaxLDIFLen - 1)) && (*pChar != ':')) {
+ field[fLen] = *pChar;
+ pChar++;
+ fLen++;
+ lineLen--;
+ }
+
+ field[fLen] = 0;
+
+ if (lineLen && (*pChar == ':') && (fLen < (kMaxLDIFLen - 1))) {
+ // see if this is an ldif field (case insensitive)?
+ i = 0;
+ while (sLDIFFields[i]) {
+ if (!PL_strcasecmp(sLDIFFields[i], field)) {
+ ldifFields++;
+ gotLDIF = true;
+ break;
+ }
+ i++;
+ }
+ }
+ }
+ }
+ lineCount++;
+ }
+
+ // If we just saw ldif address, increment recCount.
+ if (gotLDIF) recCount++;
+
+ rv = fileStream->Close();
+
+ if (recCount > 1) ldifFields /= recCount;
+
+ // If the average field number >= 3 then it's a good ldif file.
+ if (ldifFields >= 3) {
+ *_retval = true;
+ }
+
+ return rv;
+}
+
+void nsAbLDIFService::SplitCRLFAddressField(nsCString& inputAddress,
+ nsCString& outputLine1,
+ nsCString& outputLine2) const {
+ int32_t crlfPos = inputAddress.Find("\r\n");
+ if (crlfPos != -1) {
+ outputLine1 = Substring(inputAddress, 0, crlfPos);
+ outputLine2 = Substring(inputAddress, crlfPos + 2);
+ } else
+ outputLine1.Assign(inputAddress);
+}
diff --git a/comm/mailnews/addrbook/src/nsAbLDIFService.h b/comm/mailnews/addrbook/src/nsAbLDIFService.h
new file mode 100644
index 0000000000..84d892fdff
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbLDIFService.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#ifndef __nsAbLDIFService_h
+#define __nsAbLDIFService_h
+
+#include "nsIAbLDIFService.h"
+#include "nsCOMPtr.h"
+
+class nsAbLDIFService : public nsIAbLDIFService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABLDIFSERVICE
+
+ nsAbLDIFService();
+
+ private:
+ virtual ~nsAbLDIFService();
+ nsresult str_parse_line(char* line, char** type, char** value,
+ int* vlen) const;
+ char* str_getline(char** next) const;
+ nsresult GetLdifStringRecord(char* buf, int32_t len, int32_t& stopPos);
+ void AddLdifRowToDatabase(nsIAbDirectory* aDirectory, bool aIsList);
+ void AddLdifColToDatabase(nsIAbDirectory* aDirectory, nsIAbCard* newCard,
+ nsCString colType, nsCString column, bool bIsList);
+ void ClearLdifRecordBuffer();
+ void SplitCRLFAddressField(nsCString& inputAddress, nsCString& outputLine1,
+ nsCString& outputLine2) const;
+
+ bool mStoreLocAsHome;
+ nsCString mLdifLine;
+ int32_t mLFCount;
+ int32_t mCRCount;
+};
+
+#endif
diff --git a/comm/mailnews/addrbook/src/nsAbOSXCard.h b/comm/mailnews/addrbook/src/nsAbOSXCard.h
new file mode 100644
index 0000000000..2c0650fd64
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOSXCard.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsAbOSXCard_h___
+#define nsAbOSXCard_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbCardProperty.h"
+
+#define NS_ABOSXCARD_URI_PREFIX "moz-abosxcard://"
+
+#define NS_IABOSXCARD_IID \
+ { \
+ 0xa7e5b697, 0x772d, 0x4fb5, { \
+ 0x81, 0x16, 0x23, 0xb7, 0x5a, 0xac, 0x94, 0x56 \
+ } \
+ }
+
+class nsIAbOSXCard : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IABOSXCARD_IID)
+
+ virtual nsresult Init(const char* aUri) = 0;
+ virtual nsresult Update(bool aNotify) = 0;
+ virtual nsresult GetURI(nsACString& aURI) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIAbOSXCard, NS_IABOSXCARD_IID)
+
+class nsAbOSXCard : public nsAbCardProperty, public nsIAbOSXCard {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsresult Update(bool aNotify) override;
+ nsresult GetURI(nsACString& aURI) override;
+ nsresult Init(const char* aUri) override;
+ NS_IMETHOD GetUID(nsACString& uid) override;
+ NS_IMETHOD SetUID(const nsACString& aUID) override;
+ // this is needed so nsAbOSXUtils.mm can get at nsAbCardProperty
+ friend class nsAbOSXUtils;
+
+ private:
+ nsCString mURI;
+ nsCString mUID;
+
+ virtual ~nsAbOSXCard() {}
+};
+
+#endif // nsAbOSXCard_h___
diff --git a/comm/mailnews/addrbook/src/nsAbOSXCard.mm b/comm/mailnews/addrbook/src/nsAbOSXCard.mm
new file mode 100644
index 0000000000..ab77242490
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOSXCard.mm
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAbOSXCard.h"
+#include "nsAbOSXDirectory.h"
+#include "nsAbOSXUtils.h"
+#include "nsIAbManager.h"
+#include "nsObjCExceptions.h"
+#include "nsServiceManagerUtils.h"
+
+#include <AddressBook/AddressBook.h>
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOSXCard, nsAbCardProperty, nsIAbOSXCard)
+
+#ifdef DEBUG
+static ABPropertyType GetPropertType(ABRecord* aCard, NSString* aProperty) {
+ ABPropertyType propertyType = kABErrorInProperty;
+ if ([aCard isKindOfClass:[ABPerson class]])
+ propertyType = [ABPerson typeOfProperty:aProperty];
+ else if ([aCard isKindOfClass:[ABGroup class]])
+ propertyType = [ABGroup typeOfProperty:aProperty];
+ return propertyType;
+}
+#endif
+
+static void SetStringProperty(nsAbOSXCard* aCard, const nsString& aValue, const char* aMemberName,
+ bool aNotify, nsIAbManager* aAbManager) {
+ nsString oldValue;
+ nsresult rv = aCard->GetPropertyAsAString(aMemberName, oldValue);
+ if (NS_FAILED(rv)) oldValue.Truncate();
+
+ if (!aNotify) {
+ aCard->SetPropertyAsAString(aMemberName, aValue);
+ } else if (!oldValue.Equals(aValue)) {
+ aCard->SetPropertyAsAString(aMemberName, aValue);
+ }
+}
+
+static void SetStringProperty(nsAbOSXCard* aCard, NSString* aValue, const char* aMemberName,
+ bool aNotify, nsIAbManager* aAbManager) {
+ nsAutoString value;
+ if (aValue) AppendToString(aValue, value);
+
+ SetStringProperty(aCard, value, aMemberName, aNotify, aAbManager);
+}
+
+static void MapStringProperty(nsAbOSXCard* aCard, ABRecord* aOSXCard, NSString* aProperty,
+ const char* aMemberName, bool aNotify, nsIAbManager* aAbManager) {
+ NS_ASSERTION(aProperty, "This is bad! You asked for an unresolved symbol.");
+ NS_ASSERTION(GetPropertType(aOSXCard, aProperty) == kABStringProperty, "Wrong type!");
+
+ SetStringProperty(aCard, [aOSXCard valueForProperty:aProperty], aMemberName, aNotify, aAbManager);
+}
+
+static ABMutableMultiValue* GetMultiValue(ABRecord* aCard, NSString* aProperty) {
+ NS_ASSERTION(aProperty, "This is bad! You asked for an unresolved symbol.");
+ NS_ASSERTION(GetPropertType(aCard, aProperty) & kABMultiValueMask, "Wrong type!");
+
+ return [aCard valueForProperty:aProperty];
+}
+
+static void MapDate(nsAbOSXCard* aCard, NSDate* aDate, const char* aYearPropName,
+ const char* aMonthPropName, const char* aDayPropName, bool aNotify,
+ nsIAbManager* aAbManager) {
+ // XXX Should we pass a format and timezone?
+ NSCalendarDate* date = [aDate dateWithCalendarFormat:nil timeZone:nil];
+
+ nsAutoString value;
+ value.AppendInt(static_cast<int32_t>([date yearOfCommonEra]));
+ SetStringProperty(aCard, value, aYearPropName, aNotify, aAbManager);
+ value.Truncate();
+ value.AppendInt(static_cast<int32_t>([date monthOfYear]));
+ SetStringProperty(aCard, value, aMonthPropName, aNotify, aAbManager);
+ value.Truncate();
+ value.AppendInt(static_cast<int32_t>([date dayOfMonth]));
+ SetStringProperty(aCard, value, aDayPropName, aNotify, aAbManager);
+}
+
+static bool MapMultiValue(nsAbOSXCard* aCard, ABRecord* aOSXCard, const nsAbOSXPropertyMap& aMap,
+ bool aNotify, nsIAbManager* aAbManager) {
+ ABMultiValue* value = GetMultiValue(aOSXCard, aMap.mOSXProperty);
+ if (value) {
+ unsigned int j;
+ unsigned int count = [value count];
+ for (j = 0; j < count; ++j) {
+ if ([[value labelAtIndex:j] isEqualToString:aMap.mOSXLabel]) {
+ NSString* stringValue = (aMap.mOSXKey) ? [[value valueAtIndex:j] objectForKey:aMap.mOSXKey]
+ : [value valueAtIndex:j];
+
+ SetStringProperty(aCard, stringValue, aMap.mPropertyName, aNotify, aAbManager);
+
+ return true;
+ }
+ }
+ }
+ // String wasn't found, set value of card to empty if it was set previously
+ SetStringProperty(aCard, EmptyString(), aMap.mPropertyName, aNotify, aAbManager);
+
+ return false;
+}
+
+// Maps Address Book's instant messenger name to the corresponding nsIAbCard field name.
+static const char* InstantMessengerFieldName(NSString* aInstantMessengerName) {
+ if ([aInstantMessengerName isEqualToString:@"AIMInstant"]) {
+ return "_AimScreenName";
+ }
+ if ([aInstantMessengerName isEqualToString:@"GoogleTalkInstant"]) {
+ return "_GoogleTalk";
+ }
+ if ([aInstantMessengerName isEqualToString:@"ICQInstant"]) {
+ return "_ICQ";
+ }
+ if ([aInstantMessengerName isEqualToString:@"JabberInstant"]) {
+ return "_JabberId";
+ }
+ if ([aInstantMessengerName isEqualToString:@"MSNInstant"]) {
+ return "_MSN";
+ }
+ if ([aInstantMessengerName isEqualToString:@"QQInstant"]) {
+ return "_QQ";
+ }
+ if ([aInstantMessengerName isEqualToString:@"SkypeInstant"]) {
+ return "_Skype";
+ }
+ if ([aInstantMessengerName isEqualToString:@"YahooInstant"]) {
+ return "_Yahoo";
+ }
+
+ // Fall back to AIM for everything else.
+ // We don't have nsIAbCard fields for FacebookInstant and GaduGaduInstant.
+ return "_AimScreenName";
+}
+
+nsresult nsAbOSXCard::Init(const char* aUri) {
+ if (strncmp(aUri, NS_ABOSXCARD_URI_PREFIX, sizeof(NS_ABOSXCARD_URI_PREFIX) - 1) != 0)
+ return NS_ERROR_FAILURE;
+
+ mURI = aUri;
+
+ // Extract the UID part.
+ mUID = Substring(mURI, 16, mURI.Length());
+ // Now make sure we don't use the `:ABPerson` on the end, so that
+ // we don't expose it to extensions etc.
+ int32_t pos = mUID.RFindChar(':');
+ if (pos != kNotFound) {
+ mUID = Substring(mUID, 0, pos);
+ }
+ // Also lower case so that we match other UIDs generated by the address book.
+ ToLowerCase(mUID);
+
+ return Update(false);
+}
+
+nsresult nsAbOSXCard::GetURI(nsACString& aURI) {
+ if (mURI.IsEmpty()) return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOSXCard::GetUID(nsACString& uid) {
+ uid = mUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOSXCard::SetUID(const nsACString& aUID) {
+ // The UIDs are obtained from the OS X contacts and cannot be changed.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsAbOSXCard::Update(bool aNotify) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ ABAddressBook* addressBook = [ABAddressBook sharedAddressBook];
+
+ const char* uid = &((mURI.get())[16]);
+ ABRecord* card = [addressBook recordForUniqueId:[NSString stringWithUTF8String:uid]];
+ NS_ENSURE_TRUE(card, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIAbManager> abManager;
+ nsresult rv;
+ if (aNotify) {
+ abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if ([card isKindOfClass:[ABGroup class]]) {
+ m_IsMailList = true;
+ m_MailListURI.AssignLiteral(NS_ABOSXDIRECTORY_URI_PREFIX);
+ m_MailListURI.Append(uid);
+ MapStringProperty(this, card, kABGroupNameProperty, "DisplayName", aNotify, abManager);
+ MapStringProperty(this, card, kABGroupNameProperty, "LastName", aNotify, abManager);
+
+ return NS_OK;
+ }
+
+ bool foundHome = false, foundWork = false;
+
+ uint32_t i;
+ for (i = 0; i < nsAbOSXUtils::kPropertyMapSize; ++i) {
+ const nsAbOSXPropertyMap& propertyMap = nsAbOSXUtils::kPropertyMap[i];
+ if (!propertyMap.mOSXProperty) continue;
+
+ if (propertyMap.mOSXLabel) {
+ if (MapMultiValue(this, card, propertyMap, aNotify, abManager) &&
+ propertyMap.mOSXProperty == kABAddressProperty) {
+ if (propertyMap.mOSXLabel == kABAddressHomeLabel)
+ foundHome = true;
+ else
+ foundWork = true;
+ }
+ } else {
+ MapStringProperty(this, card, propertyMap.mOSXProperty, propertyMap.mPropertyName, aNotify,
+ abManager);
+ }
+ }
+
+ int flags = 0;
+ if (kABPersonFlags) flags = [[card valueForProperty:kABPersonFlags] intValue];
+
+#define SET_STRING(_value, _name, _notify, _session) \
+ SetStringProperty(this, _value, #_name, _notify, _session)
+
+ // If kABShowAsCompany is set we use the company name as display name.
+ if (kABPersonFlags && (flags & kABShowAsCompany)) {
+ nsString company;
+ nsresult rv = GetPropertyAsAString(kCompanyProperty, company);
+ if (NS_FAILED(rv)) company.Truncate();
+ SET_STRING(company, DisplayName, aNotify, abManager);
+ } else {
+ // Use the order used in the OS X address book to set DisplayName.
+ int order = kABPersonFlags && (flags & kABNameOrderingMask);
+ if (kABPersonFlags && (order == kABDefaultNameOrdering)) {
+ order = [addressBook defaultNameOrdering];
+ }
+
+ nsAutoString displayName, tempName;
+ if (kABPersonFlags && (order == kABFirstNameFirst)) {
+ GetFirstName(tempName);
+ displayName.Append(tempName);
+
+ GetLastName(tempName);
+
+ // Only append a space if the last name and the first name are not empty
+ if (!tempName.IsEmpty() && !displayName.IsEmpty()) displayName.Append(' ');
+
+ displayName.Append(tempName);
+ } else {
+ GetLastName(tempName);
+ displayName.Append(tempName);
+
+ GetFirstName(tempName);
+
+ // Only append a space if the last name and the first name are not empty
+ if (!tempName.IsEmpty() && !displayName.IsEmpty()) displayName.Append(' ');
+
+ displayName.Append(tempName);
+ }
+ SET_STRING(displayName, DisplayName, aNotify, abManager);
+ }
+
+ ABMultiValue* value = GetMultiValue(card, kABEmailProperty);
+ if (value) {
+ unsigned int count = [value count];
+ if (count > 0) {
+ unsigned int j = [value indexForIdentifier:[value primaryIdentifier]];
+
+ if (j < count) SET_STRING([value valueAtIndex:j], PrimaryEmail, aNotify, abManager);
+
+ // If j is 0 (first in the list) we want the second in the list
+ // (index 1), if j is anything else we want the first in the list
+ // (index 0).
+ j = (j == 0);
+ if (j < count) SET_STRING([value valueAtIndex:j], SecondEmail, aNotify, abManager);
+ }
+ }
+
+ // We map the first home address we can find and the first work address
+ // we can find. If we find none, we map the primary address to the home
+ // address.
+ if (!foundHome && !foundWork) {
+ value = GetMultiValue(card, kABAddressProperty);
+ if (value) {
+ unsigned int count = [value count];
+ unsigned int j = [value indexForIdentifier:[value primaryIdentifier]];
+
+ if (j < count) {
+ NSDictionary* address = [value valueAtIndex:j];
+ if (address) {
+ SET_STRING([address objectForKey:kABAddressStreetKey], HomeAddress, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressCityKey], HomeCity, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressStateKey], HomeState, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressZIPKey], HomeZipCode, aNotify, abManager);
+ SET_STRING([address objectForKey:kABAddressCountryKey], HomeCountry, aNotify, abManager);
+ }
+ }
+ }
+ }
+ // This was kABAIMInstantProperty previously, but it was deprecated in OS X 10.7.
+ value = GetMultiValue(card, kABInstantMessageProperty);
+ if (value) {
+ unsigned int count = [value count];
+ for (size_t i = 0; i < count; i++) {
+ id imValue = [value valueAtIndex:i];
+ // Depending on the macOS version, imValue can be an NSString or an NSDictionary.
+ if ([imValue isKindOfClass:[NSString class]]) {
+ if (i == [value indexForIdentifier:[value primaryIdentifier]]) {
+ SET_STRING(imValue, _AimScreenName, aNotify, abManager);
+ }
+ } else if ([imValue isKindOfClass:[NSDictionary class]]) {
+ NSString* instantMessageService = [imValue objectForKey:@"InstantMessageService"];
+ const char* fieldName = InstantMessengerFieldName(instantMessageService);
+ NSString* userName = [imValue objectForKey:@"InstantMessageUsername"];
+ SetStringProperty(this, userName, fieldName, aNotify, abManager);
+ }
+ }
+ }
+
+#define MAP_DATE(_date, _name, _notify, _session) \
+ MapDate(this, _date, #_name "Year", #_name "Month", #_name "Day", _notify, _session)
+
+ NSDate* date = [card valueForProperty:kABBirthdayProperty];
+ if (date) MAP_DATE(date, Birth, aNotify, abManager);
+
+ if (kABOtherDatesProperty) {
+ value = GetMultiValue(card, kABOtherDatesProperty);
+ if (value) {
+ unsigned int j, count = [value count];
+ for (j = 0; j < count; ++j) {
+ if ([[value labelAtIndex:j] isEqualToString:kABAnniversaryLabel]) {
+ date = [value valueAtIndex:j];
+ if (date) {
+ MAP_DATE(date, Anniversary, aNotify, abManager);
+
+ break;
+ }
+ }
+ }
+ }
+ }
+#undef MAP_DATE
+#undef SET_STRING
+
+ date = [card valueForProperty:kABModificationDateProperty];
+ if (date) SetPropertyAsUint32("LastModifiedDate", uint32_t([date timeIntervalSince1970]));
+ // XXX No way to notify about this?
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
diff --git a/comm/mailnews/addrbook/src/nsAbOSXDirectory.h b/comm/mailnews/addrbook/src/nsAbOSXDirectory.h
new file mode 100644
index 0000000000..3d5b0384a9
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOSXDirectory.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsAbOSXDirectory_h___
+#define nsAbOSXDirectory_h___
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include "nsAbDirProperty.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsIMutableArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsAbOSXCard.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+class nsIAbManager;
+class nsIAbBooleanExpression;
+
+#define NS_ABOSXDIRECTORY_URI_PREFIX "moz-abosxdirectory://"
+
+#define NS_IABOSXDIRECTORY_IID \
+ { \
+ 0x87ee4bd9, 0x8552, 0x498f, { \
+ 0x80, 0x85, 0x34, 0xf0, 0x2a, 0xbb, 0x56, 0x16 \
+ } \
+ }
+
+class nsIAbOSXDirectory : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IABOSXDIRECTORY_IID)
+
+ virtual nsresult AssertChildNodes() = 0;
+ virtual nsresult Update() = 0;
+ virtual nsresult AssertDirectory(nsIAbManager* aManager,
+ nsIAbDirectory* aDirectory) = 0;
+ virtual nsresult AssertCard(nsIAbManager* aManager, nsIAbCard* aCard) = 0;
+ virtual nsresult UnassertCard(nsIAbManager* aManager, nsIAbCard* aCard,
+ nsIMutableArray* aCardList) = 0;
+ virtual nsresult UnassertDirectory(nsIAbManager* aManager,
+ nsIAbDirectory* aDirectory) = 0;
+ virtual nsresult DeleteUid(const nsACString& aUid) = 0;
+ virtual nsresult GetURI(nsACString& aURI) = 0;
+ virtual nsresult Init(const char* aUri) = 0;
+ virtual nsresult GetCardByUri(const nsACString& aUri,
+ nsIAbOSXCard** aResult) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIAbOSXDirectory, NS_IABOSXDIRECTORY_IID)
+
+class nsAbOSXDirectory final : public nsAbDirProperty,
+ public nsIAbOSXDirectory {
+ public:
+ nsAbOSXDirectory();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIAbOSXDirectory method
+ NS_IMETHOD Init(const char* aUri) override;
+
+ // nsAbDirProperty methods
+ NS_IMETHOD GetReadOnly(bool* aReadOnly) override;
+ NS_IMETHOD GetChildCardCount(uint32_t* aCount) override;
+ NS_IMETHOD GetChildCards(nsTArray<RefPtr<nsIAbCard>>& result) override;
+ NS_IMETHOD GetChildNodes(nsTArray<RefPtr<nsIAbDirectory>>& result) override;
+ NS_IMETHOD HasCard(nsIAbCard* aCard, bool* aHasCard) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory* aDirectory,
+ bool* aHasDirectory) override;
+ NS_IMETHOD GetURI(nsACString& aURI) override;
+ NS_IMETHOD GetCardFromProperty(const char* aProperty,
+ const nsACString& aValue, bool caseSensitive,
+ nsIAbCard** aResult) override;
+ NS_IMETHOD GetCardsFromProperty(
+ const char* aProperty, const nsACString& aValue, bool aCaseSensitive,
+ nsTArray<RefPtr<nsIAbCard>>& aResult) override;
+ NS_IMETHOD CardForEmailAddress(const nsACString& aEmailAddress,
+ nsIAbCard** aResult) override;
+ NS_IMETHOD Search(const nsAString& query, const nsAString& searchString,
+ nsIAbDirSearchListener* listener) override;
+
+ // nsIAbOSXDirectory
+ nsresult AssertChildNodes() override;
+ nsresult AssertDirectory(nsIAbManager* aManager,
+ nsIAbDirectory* aDirectory) override;
+ nsresult AssertCard(nsIAbManager* aManager, nsIAbCard* aCard) override;
+ nsresult UnassertCard(nsIAbManager* aManager, nsIAbCard* aCard,
+ nsIMutableArray* aCardList) override;
+ nsresult UnassertDirectory(nsIAbManager* aManager,
+ nsIAbDirectory* aDirectory) override;
+
+ nsresult Update() override;
+
+ nsresult DeleteUid(const nsACString& aUid) override;
+
+ nsresult GetCardByUri(const nsACString& aUri,
+ nsIAbOSXCard** aResult) override;
+
+ nsresult GetRootOSXDirectory(nsIAbOSXDirectory** aResult);
+
+ private:
+ ~nsAbOSXDirectory();
+
+ // This is a list of nsIAbCards, kept separate from m_AddressList because:
+ // - nsIAbDirectory items that are mailing lists, must keep a list of
+ // nsIAbCards in m_AddressList, however
+ // - nsIAbDirectory items that are address books, must keep a list of
+ // nsIAbDirectory (i.e. mailing lists) in m_AddressList, AND no nsIAbCards.
+ //
+ // This wasn't too bad for mork, as that just gets a list from its database,
+ // but because we store our own copy of the list, we must store a separate
+ // list of nsIAbCards here. nsIMutableArray is used, because then it is
+ // interchangeable with m_AddressList.
+ nsCOMPtr<nsIMutableArray> mCardList;
+ nsInterfaceHashtable<nsCStringHashKey, nsIAbOSXCard> mCardStore;
+ nsCOMPtr<nsIAbOSXDirectory> mCacheTopLevelOSXAb;
+};
+
+#endif // nsAbOSXDirectory_h___
diff --git a/comm/mailnews/addrbook/src/nsAbOSXDirectory.mm b/comm/mailnews/addrbook/src/nsAbOSXDirectory.mm
new file mode 100644
index 0000000000..fe29a8a0d2
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOSXDirectory.mm
@@ -0,0 +1,911 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAbOSXDirectory.h"
+#include "nsAbOSXCard.h"
+#include "nsAbOSXUtils.h"
+#include "nsAbQueryStringToExpression.h"
+#include "nsCOMArray.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIAbDirectoryQueryProxy.h"
+#include "nsIAbManager.h"
+#include "nsObjCExceptions.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIAbBooleanExpression.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+
+#include <AddressBook/AddressBook.h>
+
+#define kABDeletedRecords (kABDeletedRecords ? kABDeletedRecords : @"ABDeletedRecords")
+#define kABUpdatedRecords (kABUpdatedRecords ? kABUpdatedRecords : @"ABUpdatedRecords")
+#define kABInsertedRecords (kABInsertedRecords ? kABInsertedRecords : @"ABInsertedRecords")
+
+static nsresult GetOrCreateGroup(NSString* aUid, nsIAbDirectory** aResult) {
+ NS_ASSERTION(aUid, "No UID for group!.");
+
+ nsAutoCString uri(NS_ABOSXDIRECTORY_URI_PREFIX);
+ AppendToCString(aUid, uri);
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = directory);
+ return NS_OK;
+}
+
+static nsresult GetCard(ABRecord* aRecord, nsIAbCard** aResult, nsIAbOSXDirectory* osxDirectory) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ NSString* uid = [aRecord uniqueId];
+ NS_ASSERTION(uid, "No UID for card!.");
+ if (!uid) return NS_ERROR_FAILURE;
+
+ nsAutoCString uri(NS_ABOSXCARD_URI_PREFIX);
+ AppendToCString(uid, uri);
+ nsCOMPtr<nsIAbOSXCard> osxCard;
+ nsresult rv = osxDirectory->GetCardByUri(uri, getter_AddRefs(osxCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card = do_QueryInterface(osxCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = card);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+static nsresult CreateCard(ABRecord* aRecord, nsIAbCard** aResult) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ NSString* uid = [aRecord uniqueId];
+ NS_ASSERTION(uid, "No UID for card!.");
+ if (!uid) return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbOSXCard> osxCard =
+ do_CreateInstance("@mozilla.org/addressbook/directory;1?type=moz-abosxcard", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri(NS_ABOSXCARD_URI_PREFIX);
+ AppendToCString(uid, uri);
+
+ rv = osxCard->Init(uri.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card = do_QueryInterface(osxCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aResult = card);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+static nsresult Sync(NSString* aUid) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ ABAddressBook* addressBook = [ABAddressBook sharedAddressBook];
+ ABRecord* card = [addressBook recordForUniqueId:aUid];
+ if ([card isKindOfClass:[ABGroup class]]) {
+ nsCOMPtr<nsIAbDirectory> directory;
+ GetOrCreateGroup(aUid, getter_AddRefs(directory));
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory = do_QueryInterface(directory);
+
+ if (osxDirectory) {
+ osxDirectory->Update();
+ }
+ } else {
+ nsCOMPtr<nsIAbCard> abCard;
+ nsresult rv;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(nsLiteralCString(NS_ABOSXDIRECTORY_URI_PREFIX "/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory = do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCard(card, getter_AddRefs(abCard), osxDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(abCard);
+ osxCard->Update(true);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+@interface ABChangedMonitor : NSObject
+- (void)ABChanged:(NSNotification*)aNotification;
+@end
+
+@implementation ABChangedMonitor
+- (void)ABChanged:(NSNotification*)aNotification {
+ NSDictionary* changes = [aNotification userInfo];
+
+ nsresult rv;
+ NSArray* inserted = [changes objectForKey:kABInsertedRecords];
+
+ if (inserted) {
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(nsLiteralCString(NS_ABOSXDIRECTORY_URI_PREFIX "/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory = do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ unsigned int i, count = [inserted count];
+ for (i = 0; i < count; ++i) {
+ ABAddressBook* addressBook = [ABAddressBook sharedAddressBook];
+ ABRecord* card = [addressBook recordForUniqueId:[inserted objectAtIndex:i]];
+ if ([card isKindOfClass:[ABGroup class]]) {
+ nsCOMPtr<nsIAbDirectory> directory;
+ GetOrCreateGroup([inserted objectAtIndex:i], getter_AddRefs(directory));
+
+ rv = osxDirectory->AssertDirectory(abManager, directory);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ } else {
+ nsCOMPtr<nsIAbCard> abCard;
+ // Construct a card
+ nsresult rv = CreateCard(card, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = osxDirectory->AssertCard(abManager, abCard);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+ }
+
+ NSArray* updated = [changes objectForKey:kABUpdatedRecords];
+ if (updated) {
+ unsigned int i, count = [updated count];
+ for (i = 0; i < count; ++i) {
+ NSString* uid = [updated objectAtIndex:i];
+ Sync(uid);
+ }
+ }
+
+ NSArray* deleted = [changes objectForKey:kABDeletedRecords];
+ if (deleted) {
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(nsLiteralCString(NS_ABOSXDIRECTORY_URI_PREFIX "/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory = do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ unsigned int i, count = [deleted count];
+ for (i = 0; i < count; ++i) {
+ NSString* deletedUid = [deleted objectAtIndex:i];
+
+ nsAutoCString uid;
+ AppendToCString(deletedUid, uid);
+
+ rv = osxDirectory->DeleteUid(uid);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+
+ if (!inserted && !updated && !deleted) {
+ // XXX This is supposed to mean "everything was updated", but we get
+ // this whenever something has changed, so not sure what to do.
+ }
+}
+@end
+
+static uint32_t sObserverCount = 0;
+static ABChangedMonitor* sObserver = nullptr;
+
+nsAbOSXDirectory::nsAbOSXDirectory() {}
+
+nsAbOSXDirectory::~nsAbOSXDirectory() {
+ if (--sObserverCount == 0) {
+ [[NSNotificationCenter defaultCenter] removeObserver:sObserver];
+ [sObserver release];
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOSXDirectory, nsAbDirProperty, nsIAbOSXDirectory)
+
+NS_IMETHODIMP
+nsAbOSXDirectory::Init(const char* aUri) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ nsresult rv;
+ rv = nsAbDirProperty::Init(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ABAddressBook* addressBook = [ABAddressBook sharedAddressBook];
+ if (sObserverCount == 0) {
+ sObserver = [[ABChangedMonitor alloc] init];
+ [[NSNotificationCenter defaultCenter] addObserver:(ABChangedMonitor*)sObserver
+ selector:@selector(ABChanged:)
+ name:kABDatabaseChangedExternallyNotification
+ object:nil];
+ }
+ ++sObserverCount;
+
+ NSArray* cards;
+ nsCOMPtr<nsIMutableArray> cardList;
+ bool isRootOSXDirectory = false;
+
+ if (mURI.Length() <= sizeof(NS_ABOSXDIRECTORY_URI_PREFIX)) {
+ isRootOSXDirectory = true;
+
+ m_DirPrefId.AssignLiteral("ldap_2.servers.osx");
+
+ cards = [[addressBook people] arrayByAddingObjectsFromArray:[addressBook groups]];
+ if (!mCardList)
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ else
+ rv = mCardList->Clear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ cardList = mCardList;
+ } else {
+ nsAutoCString uid(Substring(mURI, sizeof(NS_ABOSXDIRECTORY_URI_PREFIX) - 1));
+ ABRecord* card = [addressBook recordForUniqueId:[NSString stringWithUTF8String:uid.get()]];
+ NS_ASSERTION([card isKindOfClass:[ABGroup class]], "Huh.");
+
+ m_IsMailList = true;
+ AppendToString([card valueForProperty:kABGroupNameProperty], m_ListDirName);
+
+ ABGroup* group = (ABGroup*)[addressBook
+ recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURI, 21)).get()]];
+ cards = [[group members] arrayByAddingObjectsFromArray:[group subgroups]];
+
+ if (!m_AddressList)
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ else
+ rv = m_AddressList->Clear();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ cardList = m_AddressList;
+ }
+
+ unsigned int nbCards = [cards count];
+ nsCOMPtr<nsIAbCard> card;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
+ if (!isRootOSXDirectory) {
+ rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (unsigned int i = 0; i < nbCards; ++i) {
+ // If we're a Group, it's likely that the cards we're going
+ // to create were already created in the root nsAbOSXDirectory,
+ if (!isRootOSXDirectory)
+ rv = GetCard([cards objectAtIndex:i], getter_AddRefs(card), rootOSXDirectory);
+ else {
+ // If we're not a Group, that means we're the root nsAbOSXDirectory,
+ // which means we have to create the cards from scratch.
+ rv = CreateCard([cards objectAtIndex:i], getter_AddRefs(card));
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We're going to want to tell the AB Manager that we've added some cards
+ // so that they show up in the address book views.
+ AssertCard(abManager, card);
+ }
+
+ if (isRootOSXDirectory) {
+ AssertChildNodes();
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetURI(nsACString& aURI) {
+ if (mURI.IsEmpty()) return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetReadOnly(bool* aReadOnly) {
+ NS_ENSURE_ARG_POINTER(aReadOnly);
+
+ *aReadOnly = true;
+ return NS_OK;
+}
+
+static bool CheckRedundantCards(nsIAbManager* aManager, nsIAbDirectory* aDirectory,
+ nsIAbCard* aCard, NSMutableArray* aCardList) {
+ nsresult rv;
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(aCard, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString uri;
+ rv = osxCard->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, false);
+ NSString* uid = [NSString stringWithUTF8String:(uri.get() + 21)];
+
+ unsigned int i, count = [aCardList count];
+ for (i = 0; i < count; ++i) {
+ if ([[[aCardList objectAtIndex:i] uniqueId] isEqualToString:uid]) {
+ [aCardList removeObjectAtIndex:i];
+ break;
+ }
+ }
+
+ if (i == count) {
+ return true;
+ }
+
+ return false;
+}
+
+nsresult nsAbOSXDirectory::GetRootOSXDirectory(nsIAbOSXDirectory** aResult) {
+ if (!mCacheTopLevelOSXAb) {
+ // Attempt to get card from the toplevel directories
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(nsLiteralCString(NS_ABOSXDIRECTORY_URI_PREFIX "/"),
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbOSXDirectory> osxDirectory = do_QueryInterface(directory, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCacheTopLevelOSXAb = osxDirectory;
+ }
+
+ NS_IF_ADDREF(*aResult = mCacheTopLevelOSXAb);
+ return NS_OK;
+}
+
+nsresult nsAbOSXDirectory::Update() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ABAddressBook* addressBook = [ABAddressBook sharedAddressBook];
+ // Due to the horrible way the address book code works wrt mailing lists
+ // we have to use a different list depending on what we are. This pointer
+ // holds a reference to that list.
+ nsIMutableArray* cardList;
+ NSArray *groups, *cards;
+ if (m_IsMailList) {
+ ABGroup* group = (ABGroup*)[addressBook
+ recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURI, 21)).get()]];
+ groups = nil;
+ cards = [[group members] arrayByAddingObjectsFromArray:[group subgroups]];
+
+ if (!m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // For mailing lists, store the cards in m_AddressList
+ cardList = m_AddressList;
+ } else {
+ groups = [addressBook groups];
+ cards = [[addressBook people] arrayByAddingObjectsFromArray:groups];
+
+ if (!mCardList) {
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // For directories, store the cards in mCardList
+ cardList = mCardList;
+ }
+
+ NSMutableArray* mutableArray = [NSMutableArray arrayWithArray:cards];
+ uint32_t addressCount;
+ rv = cardList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--) {
+ nsCOMPtr<nsIAbCard> card(do_QueryElementAt(cardList, addressCount, &rv));
+ if (NS_FAILED(rv)) break;
+
+ if (CheckRedundantCards(abManager, this, card, mutableArray))
+ cardList->RemoveElementAt(addressCount);
+ }
+
+ NSEnumerator* enumerator = [mutableArray objectEnumerator];
+ ABRecord* card;
+ nsCOMPtr<nsIAbCard> abCard;
+ nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory;
+ rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while ((card = [enumerator nextObject])) {
+ rv = GetCard(card, getter_AddRefs(abCard), rootOSXDirectory);
+ if (NS_FAILED(rv)) rv = CreateCard(card, getter_AddRefs(abCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ AssertCard(abManager, abCard);
+ }
+
+ card = (ABRecord*)[addressBook
+ recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURI, 21)).get()]];
+ NSString* stringValue = [card valueForProperty:kABGroupNameProperty];
+ if (![stringValue isEqualToString:WrapString(m_ListDirName)]) {
+ nsAutoString oldValue(m_ListDirName);
+ AssignToString(stringValue, m_ListDirName);
+ }
+
+ if (groups) {
+ mutableArray = [NSMutableArray arrayWithArray:groups];
+ nsCOMPtr<nsIAbDirectory> directory;
+ // It is ok to use m_AddressList here as only top-level directories have
+ // groups, and they will be in m_AddressList
+ if (m_AddressList) {
+ rv = m_AddressList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--) {
+ directory = do_QueryElementAt(m_AddressList, addressCount, &rv);
+ if (NS_FAILED(rv)) continue;
+
+ nsAutoCString uri;
+ directory->GetURI(uri);
+ uri.Cut(0, 21);
+ NSString* uid = [NSString stringWithUTF8String:uri.get()];
+
+ unsigned int j, arrayCount = [mutableArray count];
+ for (j = 0; j < arrayCount; ++j) {
+ if ([[[mutableArray objectAtIndex:j] uniqueId] isEqualToString:uid]) {
+ [mutableArray removeObjectAtIndex:j];
+ break;
+ }
+ }
+
+ if (j == arrayCount) {
+ UnassertDirectory(abManager, directory);
+ }
+ }
+ }
+
+ enumerator = [mutableArray objectEnumerator];
+ while ((card = [enumerator nextObject])) {
+ rv = GetOrCreateGroup([card uniqueId], getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AssertDirectory(abManager, directory);
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+nsresult nsAbOSXDirectory::AssertChildNodes() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ // Mailing lists can't have childnodes.
+ if (m_IsMailList) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NSArray* groups = [[ABAddressBook sharedAddressBook] groups];
+
+ unsigned int i, count = [groups count];
+
+ if (count > 0 && !m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ for (i = 0; i < count; ++i) {
+ rv = GetOrCreateGroup([[groups objectAtIndex:i] uniqueId], getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AssertDirectory(abManager, directory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+nsresult nsAbOSXDirectory::AssertDirectory(nsIAbManager* aManager, nsIAbDirectory* aDirectory) {
+ uint32_t pos;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ // We already have this directory, so no point in adding it again.
+ return NS_OK;
+
+ nsresult rv;
+ if (!m_AddressList) {
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = m_AddressList->AppendElement(aDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsAbOSXDirectory::AssertCard(nsIAbManager* aManager, nsIAbCard* aCard) {
+ nsAutoCString ourUID;
+ GetUID(ourUID);
+ aCard->SetDirectoryUID(ourUID);
+
+ nsresult rv =
+ m_IsMailList ? m_AddressList->AppendElement(aCard) : mCardList->AppendElement(aCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the card's URI and add it to our card store
+ nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(aCard, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString uri;
+ rv = osxCard->GetURI(uri);
+
+ nsCOMPtr<nsIAbOSXCard> retrievedCard;
+ if (!mCardStore.Get(uri, getter_AddRefs(retrievedCard))) mCardStore.InsertOrUpdate(uri, osxCard);
+
+ return NS_OK;
+}
+
+nsresult nsAbOSXDirectory::UnassertCard(nsIAbManager* aManager, nsIAbCard* aCard,
+ nsIMutableArray* aCardList) {
+ uint32_t pos;
+ if (NS_SUCCEEDED(aCardList->IndexOf(0, aCard, &pos))) {
+ nsresult rv = aCardList->RemoveElementAt(pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAbOSXDirectory::UnassertDirectory(nsIAbManager* aManager, nsIAbDirectory* aDirectory) {
+ NS_ENSURE_TRUE(m_AddressList, NS_ERROR_NULL_POINTER);
+
+ uint32_t pos;
+ if (NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos))) {
+ nsresult rv = m_AddressList->RemoveElementAt(pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOSXDirectory::GetChildNodes(nsTArray<RefPtr<nsIAbDirectory>>& aNodes) {
+ aNodes.Clear();
+ // Mailing lists don't have childnodes.
+ if (m_IsMailList || !m_AddressList) {
+ return NS_OK;
+ }
+
+ uint32_t count = 0;
+ nsresult rv = m_AddressList->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aNodes.SetCapacity(count);
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbDirectory> dir = do_QueryElementAt(m_AddressList, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aNodes.AppendElement(&*dir);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetChildCardCount(uint32_t* aCount) {
+ nsIMutableArray* srcCards = m_IsMailList ? m_AddressList : mCardList;
+ return srcCards->GetLength(aCount);
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetChildCards(nsTArray<RefPtr<nsIAbCard>>& aCards) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+ aCards.Clear();
+
+ // Not a search, so just return the appropriate list of items.
+ nsIMutableArray* srcCards = m_IsMailList ? m_AddressList : mCardList;
+ uint32_t count = 0;
+ nsresult rv = srcCards->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aCards.SetCapacity(count);
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbCard> card = do_QueryElementAt(srcCards, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aCards.AppendElement(&*card);
+ }
+ return NS_OK;
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+/* Recursive method that searches for a child card by URI. If it cannot find
+ * it within this directory, it checks all subfolders.
+ */
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardByUri(const nsACString& aUri, nsIAbOSXCard** aResult) {
+ nsCOMPtr<nsIAbOSXCard> osxCard;
+
+ // Base Case
+ if (mCardStore.Get(aUri, getter_AddRefs(osxCard))) {
+ NS_IF_ADDREF(*aResult = osxCard);
+ return NS_OK;
+ }
+ // Search children
+ nsTArray<RefPtr<nsIAbDirectory>> children;
+ nsresult rv = this->GetChildNodes(children);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIAbDirectory* dir : children) {
+ nsCOMPtr<nsIAbOSXDirectory> childDirectory = do_QueryInterface(dir, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = childDirectory->GetCardByUri(aUri, getter_AddRefs(osxCard));
+ if (NS_SUCCEEDED(rv)) {
+ NS_IF_ADDREF(*aResult = osxCard);
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardFromProperty(const char* aProperty, const nsACString& aValue,
+ bool aCaseSensitive, nsIAbCard** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aValue.IsEmpty()) return NS_OK;
+
+ nsIMutableArray* list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list) return NS_OK;
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card;
+ nsAutoCString cardValue;
+
+ for (uint32_t i = 0; i < length && !*aResult; ++i) {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = card->GetPropertyAsAUTF8String(aProperty, cardValue);
+ if (NS_SUCCEEDED(rv)) {
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue)
+ : cardValue.Equals(aValue, nsCaseInsensitiveCStringComparator);
+ if (equal) NS_IF_ADDREF(*aResult = card);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::GetCardsFromProperty(const char* aProperty, const nsACString& aValue,
+ bool aCaseSensitive, nsTArray<RefPtr<nsIAbCard>>& aResult) {
+ aResult.Clear();
+ if (aValue.IsEmpty()) return NS_OK;
+
+ nsIMutableArray* list = m_IsMailList ? m_AddressList : mCardList;
+ if (!list) return NS_OK;
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aResult.SetCapacity(length);
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIAbCard> card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString cardValue;
+ rv = card->GetPropertyAsAUTF8String(aProperty, cardValue);
+ if (NS_SUCCEEDED(rv)) {
+ bool equal = aCaseSensitive ? cardValue.Equals(aValue)
+ : cardValue.Equals(aValue, nsCaseInsensitiveCStringComparator);
+ if (equal) aResult.AppendElement(&*card);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::CardForEmailAddress(const nsACString& aEmailAddress, nsIAbCard** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aEmailAddress.IsEmpty()) return NS_OK;
+
+ nsIMutableArray* list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list) return NS_OK;
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card;
+
+ for (uint32_t i = 0; i < length && !*aResult; ++i) {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ bool hasEmailAddress = false;
+
+ rv = card->HasEmailAddress(aEmailAddress, &hasEmailAddress);
+ if (NS_SUCCEEDED(rv) && hasEmailAddress) NS_IF_ADDREF(*aResult = card);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::HasCard(nsIAbCard* aCard, bool* aHasCard) {
+ NS_ENSURE_ARG_POINTER(aCard);
+ NS_ENSURE_ARG_POINTER(aHasCard);
+
+ nsresult rv = NS_OK;
+ uint32_t index;
+ if (m_IsMailList) {
+ if (m_AddressList) rv = m_AddressList->IndexOf(0, aCard, &index);
+ } else if (mCardList)
+ rv = mCardList->IndexOf(0, aCard, &index);
+
+ *aHasCard = NS_SUCCEEDED(rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::HasDirectory(nsIAbDirectory* aDirectory, bool* aHasDirectory) {
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_ENSURE_ARG_POINTER(aHasDirectory);
+
+ *aHasDirectory = false;
+
+ uint32_t pos;
+ if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ *aHasDirectory = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAbOSXDirectory::Search(const nsAString& query, const nsAString& searchString,
+ nsIAbDirSearchListener* listener) {
+ nsresult rv;
+
+ nsCOMPtr<nsIAbBooleanExpression> expression;
+ rv = nsAbQueryStringToExpression::Convert(NS_ConvertUTF16toUTF8(query),
+ getter_AddRefs(expression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments =
+ do_CreateInstance("@mozilla.org/addressbook/directory/query-arguments;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = arguments->SetExpression(expression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't search the subdirectories. If the current directory is a mailing
+ // list, it won't have any subdirectories. If the current directory is an
+ // addressbook, searching both it and the subdirectories (the mailing
+ // lists), will yield duplicate results because every entry in a mailing
+ // list will be an entry in the parent addressbook.
+ rv = arguments->SetQuerySubDirectories(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initiate the proxy query with the no query directory
+ nsCOMPtr<nsIAbDirectoryQueryProxy> queryProxy =
+ do_CreateInstance("@mozilla.org/addressbook/directory-query/proxy;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = queryProxy->Initiate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t context = 0;
+ rv = queryProxy->DoQuery(this, arguments, listener, -1, 0, &context);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsAbOSXDirectory::DeleteUid(const nsACString& aUid) {
+ if (!m_AddressList) return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // At this stage we don't know if aUid represents a card or group. The OS X
+ // interfaces don't give us chance to find out, so we have to go through
+ // our lists to find it.
+
+ // First, we'll see if its in the group list as it is likely to be shorter.
+
+ // See if this item is in our address list
+ uint32_t addressCount;
+ rv = m_AddressList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri(NS_ABOSXDIRECTORY_URI_PREFIX);
+ uri.Append(aUid);
+
+ // Iterate backwards in case we remove something
+ while (addressCount--) {
+ nsCOMPtr<nsISupports> abItem(do_QueryElementAt(m_AddressList, addressCount, &rv));
+ if (NS_FAILED(rv)) continue;
+
+ nsCOMPtr<nsIAbDirectory> directory(do_QueryInterface(abItem, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString dirUri;
+ directory->GetURI(dirUri);
+ if (uri.Equals(dirUri)) return UnassertDirectory(abManager, directory);
+ } else {
+ nsCOMPtr<nsIAbOSXCard> osxCard(do_QueryInterface(abItem, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString cardUri;
+ osxCard->GetURI(cardUri);
+ if (uri.Equals(cardUri)) {
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(osxCard, &rv));
+ if (NS_SUCCEEDED(rv)) return UnassertCard(abManager, card, m_AddressList);
+ }
+ }
+ }
+ }
+
+ // Second, see if it is one of the cards.
+ if (!mCardList) return NS_ERROR_FAILURE;
+
+ uri = NS_ABOSXCARD_URI_PREFIX;
+ uri.Append(aUid);
+
+ rv = mCardList->GetLength(&addressCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (addressCount--) {
+ nsCOMPtr<nsIAbOSXCard> osxCard(do_QueryElementAt(mCardList, addressCount, &rv));
+ if (NS_FAILED(rv)) continue;
+
+ nsAutoCString cardUri;
+ osxCard->GetURI(cardUri);
+
+ if (uri.Equals(cardUri)) {
+ nsCOMPtr<nsIAbCard> card(do_QueryInterface(osxCard, &rv));
+ if (NS_SUCCEEDED(rv)) return UnassertCard(abManager, card, mCardList);
+ }
+ }
+ return NS_OK;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbOSXUtils.h b/comm/mailnews/addrbook/src/nsAbOSXUtils.h
new file mode 100644
index 0000000000..42a57f45f6
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOSXUtils.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsAbOSXUtils_h___
+#define nsAbOSXUtils_h___
+
+#include <Foundation/NSString.h>
+#include "nsString.h"
+
+NSString* WrapString(const nsString& aString);
+void AppendToString(const NSString* aString, nsString& aResult);
+void AssignToString(const NSString* aString, nsString& aResult);
+void AppendToCString(const NSString* aString, nsCString& aResult);
+
+struct nsAbOSXPropertyMap {
+ NSString* const mOSXProperty;
+ NSString* const mOSXLabel;
+ NSString* const mOSXKey;
+ const char* mPropertyName;
+};
+
+class nsAbOSXUtils {
+ public:
+ static const nsAbOSXPropertyMap kPropertyMap[];
+ static const uint32_t kPropertyMapSize;
+};
+
+#endif // nsAbOSXUtils_h___
diff --git a/comm/mailnews/addrbook/src/nsAbOSXUtils.mm b/comm/mailnews/addrbook/src/nsAbOSXUtils.mm
new file mode 100644
index 0000000000..5c6f7b2b8d
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOSXUtils.mm
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAbOSXUtils.h"
+#include "nsString.h"
+#include "nsAbOSXCard.h"
+#include "nsMemory.h"
+#include "mozilla/ArrayUtils.h"
+using namespace mozilla;
+
+#include <AddressBook/AddressBook.h>
+#define kABDepartmentProperty (kABDepartmentProperty ? kABDepartmentProperty : @"ABDepartment")
+
+NSString* WrapString(const nsString& aString) {
+ unichar* chars = reinterpret_cast<unichar*>(const_cast<char16_t*>(aString.get()));
+
+ return [NSString stringWithCharacters:chars length:aString.Length()];
+}
+
+void AppendToString(const NSString* aString, nsString& aResult) {
+ if (aString) {
+ const char* chars = [aString UTF8String];
+ if (chars) {
+ aResult.Append(NS_ConvertUTF8toUTF16(chars));
+ }
+ }
+}
+
+void AssignToString(const NSString* aString, nsString& aResult) {
+ if (aString) {
+ const char* chars = [aString UTF8String];
+ if (chars) CopyUTF8toUTF16(nsDependentCString(chars), aResult);
+ }
+}
+
+void AppendToCString(const NSString* aString, nsCString& aResult) {
+ if (aString) {
+ const char* chars = [aString UTF8String];
+ if (chars) {
+ aResult.Append(chars);
+ }
+ }
+}
+
+// Some properties can't be easily mapped back and forth.
+#define DONT_MAP(moz_name, osx_property, osx_label, osx_key)
+
+#define DEFINE_PROPERTY(moz_name, osx_property, osx_label, osx_key) \
+ {osx_property, osx_label, osx_key, #moz_name},
+
+// clang-format off
+const nsAbOSXPropertyMap nsAbOSXUtils::kPropertyMap[] = {
+ DEFINE_PROPERTY(FirstName, kABFirstNameProperty, nil, nil)
+ DEFINE_PROPERTY(LastName, kABLastNameProperty, nil, nil)
+ DONT_MAP("DisplayName", nil, nil, nil)
+ DEFINE_PROPERTY(PhoneticFirstName, kABFirstNamePhoneticProperty, nil, nil)
+ DEFINE_PROPERTY(PhoneticLastName, kABLastNamePhoneticProperty, nil, nil)
+ DEFINE_PROPERTY(NickName, kABNicknameProperty, nil, nil)
+ DONT_MAP(PrimaryEmail, kABEmailProperty, nil, nil)
+ DONT_MAP(SecondEmail, kABEmailProperty, nil, nil)
+ DEFINE_PROPERTY(WorkPhone, kABPhoneProperty, kABPhoneWorkLabel, nil)
+ DEFINE_PROPERTY(HomePhone, kABPhoneProperty, kABPhoneHomeLabel, nil)
+ DEFINE_PROPERTY(FaxNumber, kABPhoneProperty, kABPhoneWorkFAXLabel, nil)
+ DEFINE_PROPERTY(PagerNumber, kABPhoneProperty, kABPhonePagerLabel, nil)
+ DEFINE_PROPERTY(CellularNumber, kABPhoneProperty, kABPhoneMobileLabel, nil)
+ DEFINE_PROPERTY(HomeAddress, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressStreetKey)
+ DEFINE_PROPERTY(HomeCity, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressCityKey)
+ DEFINE_PROPERTY(HomeState, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressStateKey)
+ DEFINE_PROPERTY(HomeZipCode, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressZIPKey)
+ DEFINE_PROPERTY(HomeCountry, kABAddressProperty, kABAddressHomeLabel,
+ kABAddressCountryKey)
+ DEFINE_PROPERTY(WorkAddress, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressStreetKey)
+ DEFINE_PROPERTY(WorkCity, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressCityKey)
+ DEFINE_PROPERTY(WorkState, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressStateKey)
+ DEFINE_PROPERTY(WorkZipCode, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressZIPKey)
+ DEFINE_PROPERTY(WorkCountry, kABAddressProperty, kABAddressWorkLabel,
+ kABAddressCountryKey)
+ DEFINE_PROPERTY(JobTitle, kABJobTitleProperty, nil, nil)
+ DEFINE_PROPERTY(Department, kABDepartmentProperty, nil, nil)
+ DEFINE_PROPERTY(Company, kABOrganizationProperty, nil, nil)
+ // This was kABAIMInstantProperty previously, but it was deprecated in OS X 10.7.
+ DONT_MAP(_AimScreenName, kABInstantMessageProperty, nil, nil)
+ DEFINE_PROPERTY(WebPage1, kABHomePageProperty, nil, nil)
+ DONT_MAP(WebPage2, kABHomePageProperty, nil, nil)
+ DONT_MAP(BirthYear, "birthyear", nil, nil)
+ DONT_MAP(BirthMonth, "birthmonth", nil, nil)
+ DONT_MAP(BirthDay, "birthday", nil, nil)
+ DONT_MAP(Custom1, "custom1", nil, nil)
+ DONT_MAP(Custom2, "custom2", nil, nil)
+ DONT_MAP(Custom3, "custom3", nil, nil)
+ DONT_MAP(Custom4, "custom4", nil, nil)
+ DEFINE_PROPERTY(Note, kABNoteProperty, nil, nil)
+ DONT_MAP("LastModifiedDate", modifytimestamp, nil, nil)
+};
+// clang-format on
+
+const uint32_t nsAbOSXUtils::kPropertyMapSize = ArrayLength(nsAbOSXUtils::kPropertyMap);
diff --git a/comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp b/comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp
new file mode 100644
index 0000000000..77568a62f2
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp
@@ -0,0 +1,1418 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsAbOutlookDirectory.h"
+#include "nsAbWinHelper.h"
+
+#include "nsString.h"
+#include "nsAbDirectoryQuery.h"
+#include "nsIAbBooleanExpression.h"
+#include "nsIAbManager.h"
+#include "nsAbQueryStringToExpression.h"
+#include "nsEnumeratorUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsCRTGlue.h"
+#include "nsArrayUtils.h"
+#include "nsMsgUtils.h"
+#include "nsQueryObject.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "mozilla/JSONStringWriteFuncs.h"
+
+#define PRINT_TO_CONSOLE 0
+#if PRINT_TO_CONSOLE
+# define PRINTF(args) printf args
+#else
+static mozilla::LazyLogModule gAbOutlookDirectoryLog("AbOutlookDirectory");
+# define PRINTF(args) \
+ MOZ_LOG(gAbOutlookDirectoryLog, mozilla::LogLevel::Debug, args)
+#endif
+
+nsAbOutlookDirectory::nsAbOutlookDirectory(void)
+ : nsAbDirProperty(),
+ mDirEntry(nullptr),
+ mCurrentQueryId(0),
+ mSearchContext(-1) {
+ mDirEntry = new nsMapiEntry;
+}
+
+nsAbOutlookDirectory::~nsAbOutlookDirectory(void) {
+ if (mDirEntry) {
+ delete mDirEntry;
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAbOutlookDirectory, nsAbDirProperty,
+ nsIAbDirectoryQuery, nsIAbDirSearchListener)
+
+NS_IMETHODIMP nsAbOutlookDirectory::Init(const char* aUri) {
+ nsresult rv = nsAbDirProperty::Init(aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString entry;
+ makeEntryIdFromURI(kOutlookDirectoryScheme, mURI.get(), entry);
+ nsAbWinHelperGuard mapiAddBook;
+ nsAutoString unichars;
+ ULONG objectType = 0;
+
+ if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE;
+
+ mDirEntry->Assign(entry);
+ if (!mapiAddBook->GetPropertyLong(*mDirEntry, PR_OBJECT_TYPE, objectType)) {
+ PRINTF(("Cannot get type.\n"));
+ return NS_ERROR_FAILURE;
+ }
+ if (!mapiAddBook->GetPropertyUString(*mDirEntry, PR_DISPLAY_NAME_W,
+ unichars)) {
+ PRINTF(("Cannot get name.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (objectType == MAPI_DISTLIST) {
+ m_IsMailList = true;
+ SetDirName(unichars);
+ // For a mailing list, we get all the cards into our member variable.
+ rv = GetCards(m_AddressList, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ m_IsMailList = false;
+ if (unichars.IsEmpty()) {
+ SetDirName(u"Outlook"_ns);
+ } else {
+ SetDirName(unichars);
+ }
+ // First, get the mailing lists, then the cards.
+ rv = GetNodes(m_AddressList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCards(mCardList, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+// nsIAbDirectory methods
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetDirType(int32_t* aDirType) {
+ NS_ENSURE_ARG_POINTER(aDirType);
+ *aDirType = nsIAbManager::MAPI_DIRECTORY_TYPE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetURI(nsACString& aURI) {
+ if (mURI.IsEmpty()) return NS_ERROR_NOT_INITIALIZED;
+
+ aURI = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetChildNodes(
+ nsTArray<RefPtr<nsIAbDirectory>>& aNodes) {
+ aNodes.Clear();
+ // Mailing lists don't have childnodes.
+ if (m_IsMailList || !m_AddressList) {
+ return NS_OK;
+ }
+
+ uint32_t count = 0;
+ nsresult rv = m_AddressList->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aNodes.SetCapacity(count);
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbDirectory> dir = do_QueryElementAt(m_AddressList, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aNodes.AppendElement(&*dir);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetChildCardCount(uint32_t* aCount) {
+ nsIMutableArray* srcCards = m_IsMailList ? m_AddressList : mCardList;
+ return srcCards->GetLength(aCount);
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::GetChildCards(
+ nsTArray<RefPtr<nsIAbCard>>& aCards) {
+ aCards.Clear();
+
+ // Not a search, so just return the appropriate list of items.
+ nsIMutableArray* srcCards = m_IsMailList ? m_AddressList : mCardList;
+ uint32_t count = 0;
+ nsresult rv = srcCards->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aCards.SetCapacity(count);
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIAbCard> card = do_QueryElementAt(srcCards, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aCards.AppendElement(&*card);
+ }
+ return NS_OK;
+}
+
+// This is an exact copy of nsAbOSXDirectory::HasCard().
+NS_IMETHODIMP nsAbOutlookDirectory::HasCard(nsIAbCard* aCard, bool* aHasCard) {
+ NS_ENSURE_ARG_POINTER(aCard);
+ NS_ENSURE_ARG_POINTER(aHasCard);
+
+ nsresult rv = NS_OK;
+ uint32_t index;
+ if (m_IsMailList) {
+ if (m_AddressList) rv = m_AddressList->IndexOf(0, aCard, &index);
+ } else if (mCardList)
+ rv = mCardList->IndexOf(0, aCard, &index);
+
+ *aHasCard = NS_SUCCEEDED(rv);
+
+ return NS_OK;
+}
+
+// This is an exact copy of nsAbOSXDirectory::HasDirectory().
+NS_IMETHODIMP nsAbOutlookDirectory::HasDirectory(nsIAbDirectory* aDirectory,
+ bool* aHasDirectory) {
+ NS_ENSURE_ARG_POINTER(aDirectory);
+ NS_ENSURE_ARG_POINTER(aHasDirectory);
+
+ *aHasDirectory = false;
+
+ uint32_t pos;
+ if (m_AddressList &&
+ NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ *aHasDirectory = true;
+
+ return NS_OK;
+}
+
+// This is an exact copy of nsAbOSXDirectory::CardForEmailAddress().
+NS_IMETHODIMP
+nsAbOutlookDirectory::CardForEmailAddress(const nsACString& aEmailAddress,
+ nsIAbCard** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (aEmailAddress.IsEmpty()) return NS_OK;
+
+ nsIMutableArray* list = m_IsMailList ? m_AddressList : mCardList;
+
+ if (!list) return NS_OK;
+
+ uint32_t length;
+ nsresult rv = list->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> card;
+
+ for (uint32_t i = 0; i < length && !*aResult; ++i) {
+ card = do_QueryElementAt(list, i, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ bool hasEmailAddress = false;
+
+ rv = card->HasEmailAddress(aEmailAddress, &hasEmailAddress);
+ if (NS_SUCCEEDED(rv) && hasEmailAddress) NS_IF_ADDREF(*aResult = card);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsAbOutlookDirectory::ExtractCardEntry(nsIAbCard* aCard,
+ nsCString& aEntry) {
+ aEntry.Truncate();
+
+ nsCString uri;
+ aCard->GetPropertyAsAUTF8String("OutlookEntryURI", uri);
+
+ // If we don't have a URI, uri will be empty. makeEntryIdFromURI doesn't set
+ // aEntry to anything if uri is empty, so it will be truncated, allowing us
+ // to accept cards not initialized by us.
+ makeEntryIdFromURI(kOutlookCardScheme, uri.get(), aEntry);
+ return NS_OK;
+}
+
+nsresult nsAbOutlookDirectory::ExtractDirectoryEntry(nsIAbDirectory* aDirectory,
+ nsCString& aEntry) {
+ aEntry.Truncate();
+ nsCString uri;
+ nsresult rv = aDirectory->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), aEntry);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DeleteCards(
+ const nsTArray<RefPtr<nsIAbCard>>& aCards) {
+ nsresult retCode = NS_OK;
+ nsAbWinHelperGuard mapiAddBook;
+
+ if (!mapiAddBook->IsOK()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString cardEntryString;
+ nsMapiEntry cardEntry;
+
+ for (auto card : aCards) {
+ retCode = ExtractCardEntry(card, cardEntryString);
+ if (NS_SUCCEEDED(retCode) && !cardEntryString.IsEmpty()) {
+ cardEntry.Assign(cardEntryString);
+ bool success = false;
+ if (m_IsMailList) {
+ nsAutoCString uri(mURI);
+ // Trim off the mailing list entry ID from the mailing list URI
+ // to get the top-level directory entry ID.
+ nsAutoCString topEntryString;
+ int32_t slashPos = uri.RFindChar('/');
+ uri.SetLength(slashPos);
+ makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), topEntryString);
+ nsMapiEntry topDirEntry;
+ topDirEntry.Assign(topEntryString);
+ success =
+ mapiAddBook->DeleteEntryfromDL(topDirEntry, *mDirEntry, cardEntry);
+ } else {
+ success = mapiAddBook->DeleteEntry(*mDirEntry, cardEntry);
+ }
+ if (!success) {
+ PRINTF(("Cannot delete card %s.\n", cardEntryString.get()));
+ } else {
+ if (m_IsMailList) {
+ // It appears that removing a card from a mailing list makes
+ // our list go stale, so refresh it.
+ m_AddressList->Clear();
+ GetCards(m_AddressList, nullptr);
+ } else if (mCardList) {
+ uint32_t pos;
+ if (NS_SUCCEEDED(mCardList->IndexOf(0, card, &pos)))
+ mCardList->RemoveElementAt(pos);
+ }
+ retCode = NotifyItemDeletion(card, true);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ card->SetDirectoryUID(EmptyCString());
+ }
+ } else {
+ PRINTF(("Card doesn't belong in this directory.\n"));
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DeleteDirectory(
+ nsIAbDirectory* aDirectory) {
+ if (!aDirectory) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsresult retCode = NS_OK;
+ nsAbWinHelperGuard mapiAddBook;
+ nsAutoCString dirEntryString;
+
+ if (!mapiAddBook->IsOK()) {
+ return NS_ERROR_FAILURE;
+ }
+ retCode = ExtractDirectoryEntry(aDirectory, dirEntryString);
+ if (NS_SUCCEEDED(retCode) && !dirEntryString.IsEmpty()) {
+ nsMapiEntry directoryEntry;
+
+ directoryEntry.Assign(dirEntryString);
+ if (!mapiAddBook->DeleteEntry(*mDirEntry, directoryEntry)) {
+ PRINTF(("Cannot delete directory %s.\n", dirEntryString.get()));
+ } else {
+ uint32_t pos;
+ if (m_AddressList &&
+ NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos)))
+ m_AddressList->RemoveElementAt(pos);
+
+ // Iterate over the cards of the directory to find the one
+ // representing the mailing list and also remove it.
+ if (mCardList) {
+ nsAutoCString listUID;
+ aDirectory->GetUID(listUID);
+
+ uint32_t nbCards = 0;
+ nsresult rv = mCardList->GetLength(&nbCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < nbCards; i++) {
+ nsCOMPtr<nsIAbCard> card = do_QueryElementAt(mCardList, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString cardUID;
+ rv = card->GetUID(cardUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (cardUID.Equals(listUID)) {
+ mCardList->RemoveElementAt(i);
+ break;
+ }
+ }
+ }
+ retCode = NotifyItemDeletion(aDirectory, false);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ }
+ } else {
+ PRINTF(("Directory doesn't belong to this folder.\n"));
+ }
+ return retCode;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::AddCard(nsIAbCard* aCard,
+ nsIAbCard** aNewCard) {
+ NS_ENSURE_ARG_POINTER(aCard);
+ NS_ENSURE_ARG_POINTER(aNewCard);
+
+ *aNewCard = nullptr;
+ nsresult retCode = NS_OK;
+ nsAbWinHelperGuard mapiAddBook;
+ nsMapiEntry newEntry;
+ nsAutoCString cardEntryString;
+ bool isNewCard = false;
+ nsCOMPtr<nsIAbDirectory> topDir;
+
+ if (!mapiAddBook->IsOK()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString ourUID;
+ if (!m_IsMailList) {
+ // We're not dealing with a mailing list, so just create a new entry.
+ if (!mapiAddBook->CreateEntry(*mDirEntry, newEntry)) {
+ return NS_ERROR_FAILURE;
+ }
+ isNewCard = true;
+ GetUID(ourUID);
+ } else {
+ nsAutoCString dirURI(mURI);
+ // Trim off the mailing list entry ID from the mailing list URI
+ // to get the top-level directory entry ID.
+ nsAutoCString topEntryString;
+ int32_t slashPos = dirURI.RFindChar('/');
+ dirURI.SetLength(slashPos);
+ makeEntryIdFromURI(kOutlookDirectoryScheme, dirURI.get(), topEntryString);
+ nsMapiEntry topDirEntry;
+ topDirEntry.Assign(topEntryString);
+
+ // Add a card to a mailing list. We distinguish two cases:
+ // If there is already an Outlook card, we can just add it.
+ // If none exists, we need to create it first. Outlook has an option
+ // that allows a creation of a mailing list member solely in the list
+ // but we don't support this for now to avoid more MAPI complication.
+ retCode = ExtractCardEntry(aCard, cardEntryString);
+ if (NS_SUCCEEDED(retCode) && !cardEntryString.IsEmpty()) {
+ newEntry.Assign(cardEntryString);
+ } else {
+ if (!mapiAddBook->CreateEntry(topDirEntry, newEntry)) {
+ return NS_ERROR_FAILURE;
+ }
+ isNewCard = true;
+ }
+ nsAutoString display;
+ nsAutoString email;
+ aCard->GetDisplayName(display);
+ aCard->GetPrimaryEmail(email);
+ if (!mapiAddBook->AddEntryToDL(topDirEntry, *mDirEntry, newEntry,
+ display.get(), email.get())) {
+ return NS_ERROR_FAILURE;
+ }
+ // The UID of the card is the top directory's UID.
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &retCode));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ retCode = abManager->GetDirectory(dirURI, getter_AddRefs(topDir));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ topDir->GetUID(ourUID);
+ }
+
+ newEntry.ToString(cardEntryString);
+ nsAutoCString cardURI(kOutlookCardScheme);
+ cardURI.Append(cardEntryString);
+
+ nsCOMPtr<nsIAbCard> newCard;
+ retCode = OutlookCardForURI(cardURI, getter_AddRefs(newCard));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ // Make sure the card has a UID before setting its directory UID.
+ // This is a bit of a hack. If we get the UID of the card before setting its
+ // directory UID, we can avoid an unwanted `ModifyCard()` call inside
+ // `nsAbCardProperty::SetUID()`.
+ nsCString dummy;
+ newCard->GetUID(dummy);
+ newCard->SetDirectoryUID(ourUID);
+
+ if (isNewCard) {
+ retCode = newCard->Copy(aCard);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ // Set a decent display name of the card. This needs to be set
+ // on the card and not on the related contact via `SetPropertiesUString()`.
+ nsAutoString displayName;
+ newCard->GetDisplayName(displayName);
+ mapiAddBook->SetPropertyUString(newEntry, PR_DISPLAY_NAME_W,
+ displayName.get());
+
+ if (m_IsMailList) {
+ // Observed behavior for a new card in a mailing list is that
+ // Outlook returns __MailUser__ as first name. That value was
+ // previously set as display name when creating the bare card.
+ nsAutoString firstName;
+ newCard->GetFirstName(firstName);
+ if (StringBeginsWith(firstName,
+ NS_LITERAL_STRING_FROM_CSTRING(kDummyDisplayName))) {
+ newCard->SetFirstName(EmptyString());
+ }
+ }
+
+ retCode = ModifyCardInternal(newCard, true);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ }
+
+ if (m_IsMailList) {
+ m_AddressList->AppendElement(newCard);
+ if (isNewCard) {
+ // Add the new card to the cards of the top directory as well.
+ nsAbOutlookDirectory* topDirOL =
+ static_cast<nsAbOutlookDirectory*>(topDir.get());
+ topDirOL->mCardList->AppendElement(newCard);
+ }
+ } else {
+ mCardList->AppendElement(newCard);
+ }
+
+ NotifyItemAddition(newCard, true);
+
+ newCard.forget(aNewCard);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DropCard(nsIAbCard* aData,
+ bool needToCopyCard) {
+ nsCOMPtr<nsIAbCard> addedCard;
+ return AddCard(aData, getter_AddRefs(addedCard));
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::AddMailList(nsIAbDirectory* aMailList,
+ nsIAbDirectory** addedList) {
+ NS_ENSURE_ARG_POINTER(aMailList);
+ NS_ENSURE_ARG_POINTER(addedList);
+ if (m_IsMailList) return NS_OK;
+
+ nsAbWinHelperGuard mapiAddBook;
+ nsMapiEntry newEntry;
+ nsAutoCString newEntryString;
+
+ if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE;
+
+ nsAutoString name;
+ nsresult rv = aMailList->GetDirName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mapiAddBook->CreateDistList(*mDirEntry, newEntry, name.get()))
+ return NS_ERROR_FAILURE;
+
+ newEntry.ToString(newEntryString);
+ nsAutoCString uri(mURI);
+ nsAutoCString topEntryString;
+ makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), topEntryString);
+ uri.Append('/');
+ uri.Append(newEntryString);
+
+ RefPtr<nsAbOutlookDirectory> directory = new nsAbOutlookDirectory;
+
+ // We will later need the URI of the parent directory, so store it here.
+ directory->mParentEntryId = topEntryString;
+
+ // Light-weight initialisation. `nsAbOutlookDirectory::Init()` will get
+ // the object type wrong since we don't have cards yet and scan for cards
+ // which we don't have yet.
+ rv = directory->nsAbDirProperty::Init(uri.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ directory->mDirEntry->Assign(newEntryString);
+ directory->m_IsMailList = true;
+ directory->m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> newList = do_QueryObject(directory);
+
+ rv = newList->CopyMailList(aMailList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Also create a card to match the list.
+ // That needs to happen before the notification.
+ nsAutoCString cardURI(kOutlookCardScheme);
+ cardURI.Append(newEntryString);
+ nsCOMPtr<nsIAbCard> newCard =
+ do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newCard->SetPropertyAsAUTF8String("OutlookEntryURI", cardURI);
+
+ // This is a bit of a hack. If we set the UID of the card before setting its
+ // directory UID, we can avoid an unwanted `ModifyCard()` call inside
+ // `nsAbCardProperty::SetUID()`.
+ nsAutoCString listUID;
+ newList->GetUID(listUID);
+ newCard->SetUID(listUID);
+ nsAutoCString ourUID;
+ GetUID(ourUID);
+ newCard->SetDirectoryUID(ourUID);
+ newCard->SetIsMailList(true);
+ newCard->SetMailListURI(uri.get());
+ newCard->SetDisplayName(name);
+ newCard->SetLastName(name);
+
+ mCardList->AppendElement(newCard);
+ m_AddressList->AppendElement(newList);
+
+ NotifyItemAddition(newList, false);
+
+ newList.forget(addedList);
+ return rv;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::EditMailListToDatabase(
+ nsIAbCard* listCard) {
+ nsresult rv;
+ nsString name;
+ nsAbWinHelperGuard mapiAddBook;
+
+ if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE;
+
+ rv = GetDirName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mapiAddBook->SetPropertyUString(*mDirEntry, PR_DISPLAY_NAME_W,
+ name.get()))
+ return NS_ERROR_FAILURE;
+
+ // Iterate over the cards of the parent directory to find the one
+ // representing the mailing list and also change its name.
+ nsAutoCString uri(mURI);
+ // Trim off the mailing list entry ID from the mailing list URI
+ // to get the top-level directory entry ID.
+ nsAutoCString topEntryString;
+ int32_t slashPos = uri.RFindChar('/');
+ uri.SetLength(slashPos);
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIAbDirectory> parent;
+ rv = abManager->GetDirectory(uri, getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString listUID;
+ GetUID(listUID);
+
+ uint32_t nbCards = 0;
+ nsAbOutlookDirectory* olDir =
+ static_cast<nsAbOutlookDirectory*>(parent.get());
+ rv = olDir->mCardList->GetLength(&nbCards);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < nbCards; i++) {
+ nsCOMPtr<nsIAbCard> card = do_QueryElementAt(olDir->mCardList, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString cardUID;
+ rv = card->GetUID(cardUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (cardUID.Equals(listUID)) {
+ card->SetDisplayName(name);
+ break;
+ }
+ }
+
+ nsAutoCString dirUID;
+ if (listCard) {
+ // For mailing list cards, we use the UID of the top level directory.
+ listCard->GetDirectoryUID(dirUID);
+ NotifyItemModification(listCard, true, dirUID.get());
+ }
+ nsCOMPtr<nsIAbDirectory> dir = do_QueryObject(this);
+ // Use the UID of the parent.
+ parent->GetUID(dirUID);
+ NotifyItemModification(dir, false, dirUID.get());
+ return NS_OK;
+}
+
+static nsresult FindPrimaryEmailCondition(nsIAbBooleanExpression* aLevel,
+ nsAString& value) {
+ if (!aLevel) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsresult retCode = NS_OK;
+ nsTArray<RefPtr<nsISupports>> expressions;
+
+ retCode = aLevel->GetExpressions(expressions);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ for (uint32_t i = 0; i < expressions.Length(); ++i) {
+ RefPtr<nsIAbBooleanConditionString> condition =
+ do_QueryObject(expressions[i], &retCode);
+ if (NS_SUCCEEDED(retCode)) {
+ nsCString name;
+ retCode = condition->GetName(getter_Copies(name));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ if (name.EqualsLiteral("PrimaryEmail")) {
+ // We found a leaf in the boolean expression tree that compares
+ // "PrimaryEmail". So return the value and be done.
+ retCode = condition->GetValue(getter_Copies(value));
+ return retCode;
+ }
+ continue;
+ }
+
+ RefPtr<nsIAbBooleanExpression> subExpression =
+ do_QueryObject(expressions[i], &retCode);
+ if (NS_SUCCEEDED(retCode)) {
+ // Recurse into the sub-tree.
+ retCode = FindPrimaryEmailCondition(subExpression, value);
+ // If we found our leaf there, we're done.
+ if (NS_SUCCEEDED(retCode)) return retCode;
+ }
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+static nsresult GetConditionValue(nsIAbDirectoryQueryArguments* aArguments,
+ nsAString& value) {
+ if (!aArguments) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsresult retCode = NS_OK;
+
+ nsCOMPtr<nsISupports> supports;
+ retCode = aArguments->GetExpression(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ nsCOMPtr<nsIAbBooleanExpression> booleanQuery =
+ do_QueryInterface(supports, &retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ // Outlook can only query the PR_ANR property. So get its value from the
+ // PrimaryEmail condition.
+ retCode = FindPrimaryEmailCondition(booleanQuery, value);
+ return retCode;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::DoQuery(
+ nsIAbDirectory* aDirectory, nsIAbDirectoryQueryArguments* aArguments,
+ nsIAbDirSearchListener* aListener, int32_t aResultLimit, int32_t aTimeout,
+ int32_t* aReturnValue) {
+ if (!aArguments || !aListener || !aReturnValue) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // The only thing we can search here is PR_ANR. All other properties are
+ // skipped. Note that PR_ANR also searches in the recipient's name and
+ // e-mail address.
+ // https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/address-book-restrictions
+ // states:
+ // Ambiguous name restrictions are property restrictions using the PR_ANR
+ // property to match recipient names with entries in address book containers.
+
+ SRestriction restriction;
+ SPropValue val;
+ restriction.rt = RES_PROPERTY;
+ restriction.res.resProperty.relop = RELOP_EQ;
+ restriction.res.resProperty.ulPropTag = PR_ANR_W;
+ restriction.res.resProperty.lpProp = &val;
+ restriction.res.resProperty.lpProp->ulPropTag = PR_ANR_W;
+
+ nsAutoString value;
+ nsresult rv = GetConditionValue(aArguments, value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ restriction.res.resProperty.lpProp->Value.lpszW = value.get();
+
+ rv = ExecuteQuery(&restriction, aListener, aResultLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aReturnValue = ++mCurrentQueryId;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::StopQuery(int32_t aContext) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::Search(const nsAString& query,
+ const nsAString& searchString,
+ nsIAbDirSearchListener* listener) {
+ nsresult retCode = NS_OK;
+
+ // Note the following: We get a rather complicated query passed here from
+ // preference mail.addr_book.quicksearchquery.format.
+ // Outlook address book search only allows search by PR_ANR, which is a fuzzy
+ // Ambiguous Name Restriction search.
+
+ retCode = StopSearch();
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ nsCOMPtr<nsIAbBooleanExpression> expression;
+
+ nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(
+ "@mozilla.org/addressbook/directory/query-arguments;1", &retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = nsAbQueryStringToExpression::Convert(NS_ConvertUTF16toUTF8(query),
+ getter_AddRefs(expression));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ retCode = arguments->SetExpression(expression);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = arguments->SetQuerySubDirectories(true);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ return DoQuery(this, arguments, listener, -1, 0, &mSearchContext);
+}
+
+nsresult nsAbOutlookDirectory::StopSearch(void) {
+ return StopQuery(mSearchContext);
+}
+
+// nsIAbDirSearchListener
+NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFinished(
+ nsresult status, bool complete, nsITransportSecurityInfo* secInfo,
+ nsACString const& location) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFoundCard(nsIAbCard* aCard) {
+ mCardList->AppendElement(aCard);
+ return NS_OK;
+}
+
+nsresult nsAbOutlookDirectory::ExecuteQuery(SRestriction* aRestriction,
+ nsIAbDirSearchListener* aListener,
+ int32_t aResultLimit)
+
+{
+ if (!aListener) return NS_ERROR_NULL_POINTER;
+
+ nsresult retCode = NS_OK;
+
+ nsCOMPtr<nsIMutableArray> resultsArray(
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &retCode));
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ retCode = GetCards(resultsArray, aRestriction);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ uint32_t nbResults = 0;
+ retCode = resultsArray->GetLength(&nbResults);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ if (aResultLimit > 0 && nbResults > static_cast<uint32_t>(aResultLimit)) {
+ nbResults = static_cast<uint32_t>(aResultLimit);
+ }
+
+ uint32_t i = 0;
+ nsCOMPtr<nsIAbCard> card;
+
+ for (i = 0; i < nbResults; ++i) {
+ card = do_QueryElementAt(resultsArray, i, &retCode);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+
+ aListener->OnSearchFoundCard(card);
+ }
+
+ aListener->OnSearchFinished(NS_OK, true, nullptr, ""_ns);
+ return retCode;
+}
+
+// This function expects the aCards array to already be created.
+nsresult nsAbOutlookDirectory::GetCards(nsIMutableArray* aCards,
+ SRestriction* aRestriction) {
+ nsAbWinHelperGuard mapiAddBook;
+
+ if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE;
+
+ nsMapiEntryArray cardEntries;
+ LPSRestriction restriction = (LPSRestriction)aRestriction;
+
+ if (!mapiAddBook->GetCards(*mDirEntry, restriction, cardEntries)) {
+ PRINTF(("Cannot get cards.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ nsAutoCString ourUID;
+ if (m_IsMailList) {
+ // Look up the parent directory (top-level directory) in the
+ // AddrBookManager. That relies on the fact that the top-level
+ // directory is already in its map before being initialised.
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString dirURI(kOutlookDirectoryScheme);
+ dirURI.Append(mParentEntryId);
+ nsCOMPtr<nsIAbDirectory> owningDir;
+ rv = abManager->GetDirectory(dirURI, getter_AddRefs(owningDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ owningDir->GetUID(ourUID);
+ } else {
+ GetUID(ourUID);
+ }
+
+ rv = NS_OK;
+
+ for (ULONG card = 0; card < cardEntries.mNbEntries; ++card) {
+ nsAutoCString cardEntryString;
+ nsAutoCString cardURI(kOutlookCardScheme);
+ nsCOMPtr<nsIAbCard> childCard;
+ cardEntries.mEntries[card].ToString(cardEntryString);
+ cardURI.Append(cardEntryString);
+
+ rv = OutlookCardForURI(cardURI, getter_AddRefs(childCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Make sure the card has a UID before setting its directory UID.
+ // This is a bit of a hack. If we get the UID of the card before setting its
+ // directory UID, we can avoid an unwanted `ModifyCard()` call inside
+ // `nsAbCardProperty::SetUID()`.
+ nsCString dummy;
+ childCard->GetUID(dummy);
+ childCard->SetDirectoryUID(ourUID);
+
+ aCards->AppendElement(childCard);
+ }
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::GetNodes(nsIMutableArray* aNodes) {
+ NS_ENSURE_ARG_POINTER(aNodes);
+
+ nsAbWinHelperGuard mapiAddBook;
+ nsMapiEntryArray nodeEntries;
+
+ if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE;
+
+ if (!mapiAddBook->GetNodes(*mDirEntry, nodeEntries)) {
+ PRINTF(("Cannot get nodes.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString topEntryString;
+ mDirEntry->ToString(topEntryString);
+
+ for (ULONG node = 0; node < nodeEntries.mNbEntries; ++node) {
+ nsAutoCString dirEntryString;
+ nsAutoCString uri(kOutlookDirectoryScheme);
+ uri.Append(topEntryString);
+ uri.Append('/');
+ nodeEntries.mEntries[node].ToString(dirEntryString);
+ uri.Append(dirEntryString);
+
+ RefPtr<nsAbOutlookDirectory> directory = new nsAbOutlookDirectory;
+
+ // We will later need the URI of the parent directory, so store it here.
+ directory->mParentEntryId = topEntryString;
+ directory->Init(uri.get());
+
+ nsCOMPtr<nsIAbDirectory> dir = do_QueryObject(directory);
+ aNodes->AppendElement(dir);
+ }
+ return rv;
+}
+
+nsresult nsAbOutlookDirectory::commonNotification(
+ nsISupports* aItem, const char* aTopic, const char* aNotificationUID) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ // `dirUID` needs to stay in scope until the end of the function.
+ nsAutoCString dirUID;
+ if (!aNotificationUID) {
+ // Use the UID of the directory.
+ GetUID(dirUID);
+ aNotificationUID = dirUID.get();
+ }
+
+ observerService->NotifyObservers(
+ aItem, aTopic, NS_ConvertUTF8toUTF16(aNotificationUID).get());
+ return NS_OK;
+}
+
+nsresult nsAbOutlookDirectory::NotifyItemDeletion(
+ nsISupports* aItem, bool aIsCard, const char* aNotificationUID) {
+ const char* topic;
+ if (aIsCard) {
+ topic = m_IsMailList ? "addrbook-list-member-removed"
+ : "addrbook-contact-deleted";
+ } else {
+ topic = "addrbook-list-deleted";
+ }
+ return commonNotification(aItem, topic, aNotificationUID);
+}
+
+nsresult nsAbOutlookDirectory::NotifyItemAddition(
+ nsISupports* aItem, bool aIsCard, const char* aNotificationUID) {
+ const char* topic;
+ if (aIsCard) {
+ topic = m_IsMailList ? "addrbook-list-member-added"
+ : "addrbook-contact-created";
+ } else {
+ topic = "addrbook-list-created";
+ }
+ return commonNotification(aItem, topic, aNotificationUID);
+}
+
+nsresult nsAbOutlookDirectory::NotifyItemModification(
+ nsISupports* aItem, bool aIsCard, const char* aNotificationUID) {
+ return commonNotification(
+ aItem, aIsCard ? "addrbook-contact-updated" : "addrbook-list-updated",
+ aNotificationUID);
+}
+
+nsresult nsAbOutlookDirectory::NotifyCardPropertyChanges(nsIAbCard* aOld,
+ nsIAbCard* aNew) {
+ mozilla::JSONStringWriteFunc<nsCString> jsonString;
+ mozilla::JSONWriter w(jsonString);
+ w.Start();
+ w.StartObjectElement();
+ bool somethingChanged = false;
+ for (uint32_t i = 0; i < sizeof(CardStringProperties) / sizeof(char*); i++) {
+ nsAutoCString oldValue;
+ nsAutoCString newValue;
+ aOld->GetPropertyAsAUTF8String(CardStringProperties[i], oldValue);
+ aNew->GetPropertyAsAUTF8String(CardStringProperties[i], newValue);
+
+ if (!oldValue.Equals(newValue)) {
+ somethingChanged = true;
+ w.StartObjectProperty(mozilla::MakeStringSpan(CardStringProperties[i]));
+ if (oldValue.IsEmpty()) {
+ w.NullProperty("oldValue");
+ } else {
+ w.StringProperty("oldValue", mozilla::MakeStringSpan(oldValue.get()));
+ }
+ if (newValue.IsEmpty()) {
+ w.NullProperty("newValue");
+ } else {
+ w.StringProperty("newValue", mozilla::MakeStringSpan(newValue.get()));
+ }
+ w.EndObject();
+ }
+ }
+
+ for (uint32_t i = 0; i < sizeof(CardIntProperties) / sizeof(char*); i++) {
+ uint32_t oldValue = 0;
+ uint32_t newValue = 0;
+ aOld->GetPropertyAsUint32(CardIntProperties[i], &oldValue);
+ aNew->GetPropertyAsUint32(CardIntProperties[i], &newValue);
+
+ if (oldValue != newValue) {
+ somethingChanged = true;
+ w.StartObjectProperty(mozilla::MakeStringSpan(CardIntProperties[i]));
+ if (oldValue == 0) {
+ w.NullProperty("oldValue");
+ } else {
+ w.IntProperty("oldValue", oldValue);
+ }
+ if (newValue == 0) {
+ w.NullProperty("newValue");
+ } else {
+ w.IntProperty("newValue", newValue);
+ }
+ w.EndObject();
+ }
+ }
+ w.EndObject();
+ w.End();
+
+#if PRINT_TO_CONSOLE
+ printf("%s", jsonString.StringCRef().get());
+#endif
+
+ if (somethingChanged) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->NotifyObservers(
+ aNew, "addrbook-contact-properties-updated",
+ NS_ConvertUTF8toUTF16(jsonString.StringCRef()).get());
+ }
+ return NS_OK;
+}
+
+static void UnicodeToWord(const char16_t* aUnicode, WORD& aWord) {
+ aWord = 0;
+ if (aUnicode == nullptr || *aUnicode == 0) {
+ return;
+ }
+ nsresult errorCode = NS_OK;
+ nsAutoString unichar(aUnicode);
+
+ aWord = static_cast<WORD>(unichar.ToInteger(&errorCode));
+ if (NS_FAILED(errorCode)) {
+ PRINTF(("Error conversion string %S: %08x.\n", (wchar_t*)(unichar.get()),
+ errorCode));
+ }
+}
+
+#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst"
+
+NS_IMETHODIMP nsAbOutlookDirectory::ModifyCard(nsIAbCard* aModifiedCard) {
+ return ModifyCardInternal(aModifiedCard, false);
+}
+
+nsresult nsAbOutlookDirectory::ModifyCardInternal(nsIAbCard* aModifiedCard,
+ bool aIsAddition) {
+ NS_ENSURE_ARG_POINTER(aModifiedCard);
+
+ nsString* properties = nullptr;
+ nsAutoString utility;
+ nsAbWinHelperGuard mapiAddBook;
+
+ if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE;
+
+ nsCString cardEntryString;
+ nsresult retCode = ExtractCardEntry(aModifiedCard, cardEntryString);
+ NS_ENSURE_SUCCESS(retCode, retCode);
+ // If we don't have the card entry, we can't work.
+ if (cardEntryString.IsEmpty()) return NS_ERROR_FAILURE;
+
+ nsMapiEntry cardEntry;
+ cardEntry.Assign(cardEntryString);
+
+ // Get the existing card.
+ nsCString uri;
+ nsCOMPtr<nsIAbCard> oldCard;
+ aModifiedCard->GetPropertyAsAUTF8String("OutlookEntryURI", uri);
+ // If the following fails, we didn't get the old card, not fatal.
+ OutlookCardForURI(uri, getter_AddRefs(oldCard));
+
+ // First, all the standard properties in one go
+ properties = new nsString[index_LastProp];
+ if (!properties) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aModifiedCard->GetFirstName(properties[index_FirstName]);
+ aModifiedCard->GetLastName(properties[index_LastName]);
+ // This triple search for something to put in the name
+ // is because in the case of a mailing list edition in
+ // Mozilla, the display name will not be provided, and
+ // MAPI doesn't allow that, so we fall back on an optional
+ // name, and when all fails, on the email address.
+ aModifiedCard->GetDisplayName(properties[index_DisplayName]);
+ if (properties[index_DisplayName].IsEmpty()) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t format;
+ rv = prefBranch->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &format);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aModifiedCard->GenerateName(format, nullptr,
+ properties[index_DisplayName]);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (properties[index_DisplayName].IsEmpty()) {
+ aModifiedCard->GetPrimaryEmail(properties[index_DisplayName]);
+ }
+ }
+
+ nsMapiEntry dirEntry;
+ if (m_IsMailList) {
+ nsAutoCString uri(mURI);
+ // Trim off the mailing list entry ID from the mailing list URI
+ // to get the top-level directory entry ID.
+ nsAutoCString topEntryString;
+ int32_t slashPos = uri.RFindChar('/');
+ uri.SetLength(slashPos);
+ makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), topEntryString);
+ dirEntry.Assign(topEntryString);
+ } else {
+ dirEntry.Assign(mDirEntry->mByteCount, mDirEntry->mEntryId);
+ }
+
+ aModifiedCard->SetDisplayName(properties[index_DisplayName]);
+ aModifiedCard->GetPropertyAsAString(kNicknameProperty,
+ properties[index_NickName]);
+ aModifiedCard->GetPropertyAsAString(kWorkPhoneProperty,
+ properties[index_WorkPhoneNumber]);
+ aModifiedCard->GetPropertyAsAString(kHomePhoneProperty,
+ properties[index_HomePhoneNumber]);
+ aModifiedCard->GetPropertyAsAString(kFaxProperty,
+ properties[index_WorkFaxNumber]);
+ aModifiedCard->GetPropertyAsAString(kPagerProperty,
+ properties[index_PagerNumber]);
+ aModifiedCard->GetPropertyAsAString(kCellularProperty,
+ properties[index_MobileNumber]);
+ aModifiedCard->GetPropertyAsAString(kHomeCityProperty,
+ properties[index_HomeCity]);
+ aModifiedCard->GetPropertyAsAString(kHomeStateProperty,
+ properties[index_HomeState]);
+ aModifiedCard->GetPropertyAsAString(kHomeZipCodeProperty,
+ properties[index_HomeZip]);
+ aModifiedCard->GetPropertyAsAString(kHomeCountryProperty,
+ properties[index_HomeCountry]);
+ aModifiedCard->GetPropertyAsAString(kWorkCityProperty,
+ properties[index_WorkCity]);
+ aModifiedCard->GetPropertyAsAString(kWorkStateProperty,
+ properties[index_WorkState]);
+ aModifiedCard->GetPropertyAsAString(kWorkZipCodeProperty,
+ properties[index_WorkZip]);
+ aModifiedCard->GetPropertyAsAString(kWorkCountryProperty,
+ properties[index_WorkCountry]);
+ aModifiedCard->GetPropertyAsAString(kJobTitleProperty,
+ properties[index_JobTitle]);
+ aModifiedCard->GetPropertyAsAString(kDepartmentProperty,
+ properties[index_Department]);
+ aModifiedCard->GetPropertyAsAString(kCompanyProperty,
+ properties[index_Company]);
+ aModifiedCard->GetPropertyAsAString(kWorkWebPageProperty,
+ properties[index_WorkWebPage]);
+ aModifiedCard->GetPropertyAsAString(kHomeWebPageProperty,
+ properties[index_HomeWebPage]);
+ aModifiedCard->GetPropertyAsAString(kNotesProperty, properties[index_Notes]);
+ if (!mapiAddBook->SetPropertiesUString(dirEntry, cardEntry,
+ OutlookCardMAPIProps, index_LastProp,
+ properties)) {
+ PRINTF(("Cannot set general properties.\n"));
+ }
+
+ delete[] properties;
+ nsString unichar;
+ nsString unichar2;
+ WORD year = 0;
+ WORD month = 0;
+ WORD day = 0;
+
+ aModifiedCard->GetPrimaryEmail(unichar);
+ if (!mapiAddBook->SetPropertyUString(cardEntry, PR_EMAIL_ADDRESS_W,
+ unichar.get())) {
+ PRINTF(("Cannot set primary email.\n"));
+ }
+ aModifiedCard->GetPropertyAsAString(kHomeAddressProperty, unichar);
+ aModifiedCard->GetPropertyAsAString(kHomeAddress2Property, unichar2);
+
+ utility.Assign(unichar.get());
+ if (!utility.IsEmpty()) utility.AppendLiteral("\r\n");
+
+ utility.Append(unichar2.get());
+ if (!mapiAddBook->SetPropertyUString(cardEntry, PR_HOME_ADDRESS_STREET_W,
+ utility.get())) {
+ PRINTF(("Cannot set home address.\n"));
+ }
+
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kWorkAddressProperty, unichar);
+ unichar2.Truncate();
+ aModifiedCard->GetPropertyAsAString(kWorkAddress2Property, unichar2);
+
+ utility.Assign(unichar.get());
+ if (!utility.IsEmpty()) utility.AppendLiteral("\r\n");
+
+ utility.Append(unichar2.get());
+ if (!mapiAddBook->SetPropertyUString(cardEntry, PR_BUSINESS_ADDRESS_STREET_W,
+ utility.get())) {
+ PRINTF(("Cannot set work address.\n"));
+ }
+
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthYearProperty, unichar);
+ UnicodeToWord(unichar.get(), year);
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthMonthProperty, unichar);
+ UnicodeToWord(unichar.get(), month);
+ unichar.Truncate();
+ aModifiedCard->GetPropertyAsAString(kBirthDayProperty, unichar);
+ UnicodeToWord(unichar.get(), day);
+ if (!mapiAddBook->SetPropertyDate(dirEntry, cardEntry, true, PR_BIRTHDAY,
+ year, month, day)) {
+ PRINTF(("Cannot set date.\n"));
+ }
+
+ if (!aIsAddition) {
+ NotifyItemModification(aModifiedCard, true);
+ if (oldCard) NotifyCardPropertyChanges(oldCard, aModifiedCard);
+ }
+
+ return retCode;
+}
+
+static void splitString(nsString& aSource, nsString& aTarget) {
+ aTarget.Truncate();
+ int32_t offset = aSource.FindChar('\n');
+
+ if (offset >= 0) {
+ const char16_t* source = aSource.get() + offset + 1;
+ while (*source) {
+ if (*source == '\n' || *source == '\r')
+ aTarget.Append(char16_t(' '));
+ else
+ aTarget.Append(*source);
+ ++source;
+ }
+ int32_t offsetCR = aSource.FindChar('\r');
+ aSource.SetLength(offsetCR >= 0 ? offsetCR : offset);
+ }
+}
+
+nsresult nsAbOutlookDirectory::OutlookCardForURI(const nsACString& aUri,
+ nsIAbCard** newCard) {
+ NS_ENSURE_ARG_POINTER(newCard);
+
+ nsAutoCString cardEntryString;
+ makeEntryIdFromURI(kOutlookCardScheme, PromiseFlatCString(aUri).get(),
+ cardEntryString);
+
+ nsAbWinHelperGuard mapiAddBook;
+ if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbCard> card =
+ do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ card->SetPropertyAsAUTF8String("OutlookEntryURI", aUri);
+
+ nsMapiEntry cardEntry;
+ cardEntry.Assign(cardEntryString);
+
+ nsString unichars[index_LastProp];
+ bool success[index_LastProp];
+
+ nsMapiEntry dirEntry;
+ if (m_IsMailList) {
+ nsAutoCString uri(mURI);
+ // Trim off the mailing list entry ID from the mailing list URI
+ // to get the top-level directory entry ID.
+ nsAutoCString topEntryString;
+ int32_t slashPos = uri.RFindChar('/');
+ uri.SetLength(slashPos);
+ makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), topEntryString);
+ dirEntry.Assign(topEntryString);
+ } else {
+ dirEntry.Assign(mDirEntry->mByteCount, mDirEntry->mEntryId);
+ }
+
+ if (mapiAddBook->GetPropertiesUString(dirEntry, cardEntry,
+ OutlookCardMAPIProps, index_LastProp,
+ unichars, success)) {
+ if (success[index_FirstName]) card->SetFirstName(unichars[index_FirstName]);
+ if (success[index_LastName]) card->SetLastName(unichars[index_LastName]);
+ if (success[index_DisplayName])
+ card->SetDisplayName(unichars[index_DisplayName]);
+
+#define SETPROP(name, index) \
+ if (success[index]) card->SetPropertyAsAString(name, unichars[index])
+ SETPROP(kNicknameProperty, index_NickName);
+ SETPROP(kWorkPhoneProperty, index_WorkPhoneNumber);
+ SETPROP(kHomePhoneProperty, index_HomePhoneNumber);
+ SETPROP(kFaxProperty, index_WorkFaxNumber);
+ SETPROP(kPagerProperty, index_PagerNumber);
+ SETPROP(kCellularProperty, index_MobileNumber);
+ SETPROP(kHomeCityProperty, index_HomeCity);
+ SETPROP(kHomeStateProperty, index_HomeState);
+ SETPROP(kHomeZipCodeProperty, index_HomeZip);
+ SETPROP(kHomeCountryProperty, index_HomeCountry);
+ SETPROP(kWorkCityProperty, index_WorkCity);
+ SETPROP(kWorkStateProperty, index_WorkState);
+ SETPROP(kWorkZipCodeProperty, index_WorkZip);
+ SETPROP(kWorkCountryProperty, index_WorkCountry);
+ SETPROP(kJobTitleProperty, index_JobTitle);
+ SETPROP(kDepartmentProperty, index_Department);
+ SETPROP(kCompanyProperty, index_Company);
+ SETPROP(kWorkWebPageProperty, index_WorkWebPage);
+ SETPROP(kHomeWebPageProperty, index_HomeWebPage);
+ SETPROP(kNotesProperty, index_Notes);
+ }
+
+ ULONG cardType = 0;
+ if (mapiAddBook->GetPropertyLong(cardEntry, PR_OBJECT_TYPE, cardType)) {
+ card->SetIsMailList(cardType == MAPI_DISTLIST);
+ if (cardType == MAPI_DISTLIST) {
+ nsCString dirEntryString;
+ mDirEntry->ToString(dirEntryString);
+ nsAutoCString uri(kOutlookDirectoryScheme);
+ uri.Append(dirEntryString);
+ uri.Append('/');
+ nsCString originalUID;
+ AlignListEntryStringAndGetUID(cardEntryString, originalUID);
+ uri.Append(cardEntryString);
+ card->SetMailListURI(uri.get());
+ if (!originalUID.IsEmpty()) card->SetUID(originalUID);
+
+ // In case the display is by "First Last" or "Last, First", give the card
+ // a name, otherwise nothing is displayed.
+ if (success[index_DisplayName])
+ card->SetLastName(unichars[index_DisplayName]);
+ }
+ }
+
+ nsAutoString unichar;
+ nsAutoString unicharBis;
+ if (mapiAddBook->GetPropertyUString(cardEntry, PR_EMAIL_ADDRESS_W, unichar)) {
+ card->SetPrimaryEmail(unichar);
+ }
+ if (mapiAddBook->GetPropertyUString(cardEntry, PR_HOME_ADDRESS_STREET_W,
+ unichar)) {
+ splitString(unichar, unicharBis);
+ card->SetPropertyAsAString(kHomeAddressProperty, unichar);
+ card->SetPropertyAsAString(kHomeAddress2Property, unicharBis);
+ }
+ if (mapiAddBook->GetPropertyUString(cardEntry, PR_BUSINESS_ADDRESS_STREET_W,
+ unichar)) {
+ splitString(unichar, unicharBis);
+ card->SetPropertyAsAString(kWorkAddressProperty, unichar);
+ card->SetPropertyAsAString(kWorkAddress2Property, unicharBis);
+ }
+
+ WORD year = 0, month = 0, day = 0;
+ if (mapiAddBook->GetPropertyDate(dirEntry, cardEntry, true, PR_BIRTHDAY, year,
+ month, day)) {
+ card->SetPropertyAsUint32(kBirthYearProperty, year);
+ card->SetPropertyAsUint32(kBirthMonthProperty, month);
+ card->SetPropertyAsUint32(kBirthDayProperty, day);
+ }
+
+ card.forget(newCard);
+ return NS_OK;
+}
+
+void nsAbOutlookDirectory::AlignListEntryStringAndGetUID(
+ nsCString& aEntryString, nsCString& aOriginalUID) {
+ // Sadly when scanning for cards and finding a distribution list, the
+ // entry ID is different to the entry ID returned when scanning the top level
+ // directory for distribution lists. We make the adjustment here.
+ // We also retrieve the original UID from the mailing list.
+ nsAbWinHelperGuard mapiAddBook;
+ if (!mapiAddBook->IsOK()) return;
+
+ uint32_t nbLists = 0;
+ nsresult rv = m_AddressList->GetLength(&nbLists);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ for (uint32_t i = 0; i < nbLists; i++) {
+ nsCOMPtr<nsIAbDirectory> list = do_QueryElementAt(m_AddressList, i, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Get URI and extract entry ID.
+ nsAutoCString listURI;
+ list->GetURI(listURI);
+ int ind = listURI.RFindChar('/');
+ listURI = Substring(listURI, ind + 1);
+
+ if (aEntryString.Equals(listURI)) {
+ list->GetUID(aOriginalUID);
+ return;
+ }
+ if (mapiAddBook->CompareEntryIDs(aEntryString, listURI)) {
+ PRINTF(("Entry ID for mailing list replaced:\nWas: %s\nNow: %s\n",
+ aEntryString.get(), listURI.get()));
+ aEntryString = listURI;
+ list->GetUID(aOriginalUID);
+ return;
+ }
+ }
+ PRINTF(("Entry ID for mailing list not found.\n"));
+}
diff --git a/comm/mailnews/addrbook/src/nsAbOutlookDirectory.h b/comm/mailnews/addrbook/src/nsAbOutlookDirectory.h
new file mode 100644
index 0000000000..203f82df4a
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOutlookDirectory.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#ifndef nsAbOutlookDirectory_h___
+#define nsAbOutlookDirectory_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIAbCard.h"
+#include "nsAbDirProperty.h"
+#include "nsIAbDirectoryQuery.h"
+#include "nsIAbDirSearchListener.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIMutableArray.h"
+#include "nsAbWinHelper.h"
+
+struct nsMapiEntry;
+
+class nsAbOutlookDirectory : public nsAbDirProperty, // nsIAbDirectory
+ public nsIAbDirectoryQuery,
+ public nsIAbDirSearchListener {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIABDIRSEARCHLISTENER
+
+ nsAbOutlookDirectory(void);
+
+ // nsAbDirProperty methods
+ NS_IMETHOD GetDirType(int32_t* aDirType) override;
+ NS_IMETHOD GetURI(nsACString& aURI) override;
+ NS_IMETHOD GetChildCardCount(uint32_t* aCount) override;
+ NS_IMETHOD GetChildCards(nsTArray<RefPtr<nsIAbCard>>& result) override;
+ NS_IMETHOD GetChildNodes(nsTArray<RefPtr<nsIAbDirectory>>& result) override;
+ NS_IMETHOD HasCard(nsIAbCard* aCard, bool* aHasCard) override;
+ NS_IMETHOD HasDirectory(nsIAbDirectory* aDirectory,
+ bool* aHasDirectory) override;
+ NS_IMETHOD DeleteCards(const nsTArray<RefPtr<nsIAbCard>>& aCards) override;
+ NS_IMETHOD DeleteDirectory(nsIAbDirectory* aDirectory) override;
+ NS_IMETHOD AddCard(nsIAbCard* aData, nsIAbCard** addedCard) override;
+ NS_IMETHOD ModifyCard(nsIAbCard* aModifiedCard) override;
+ NS_IMETHOD DropCard(nsIAbCard* aData, bool needToCopyCard) override;
+ NS_IMETHOD AddMailList(nsIAbDirectory* aMailList,
+ nsIAbDirectory** addedList) override;
+ NS_IMETHOD EditMailListToDatabase(nsIAbCard* listCard) override;
+ NS_IMETHOD CardForEmailAddress(const nsACString& aEmailAddress,
+ nsIAbCard** aResult) override;
+
+ // nsAbDirProperty method
+ NS_IMETHOD Init(const char* aUri) override;
+ // nsIAbDirectoryQuery methods
+ NS_DECL_NSIABDIRECTORYQUERY
+ // Perform a MAPI query.
+ nsresult ExecuteQuery(SRestriction* aRestriction,
+ nsIAbDirSearchListener* aListener,
+ int32_t aResultLimit);
+ NS_IMETHOD Search(const nsAString& query, const nsAString& searchString,
+ nsIAbDirSearchListener* listener) override;
+
+ protected:
+ nsresult StopSearch();
+ nsresult ExtractCardEntry(nsIAbCard* aCard, nsCString& aEntry);
+ nsresult ExtractDirectoryEntry(nsIAbDirectory* aDirectory, nsCString& aEntry);
+ void AlignListEntryStringAndGetUID(nsCString& aEntryString,
+ nsCString& aOriginalUID);
+
+ // Retrieve hierarchy as cards, with an optional restriction
+ nsresult GetCards(nsIMutableArray* aCards, SRestriction* aRestriction);
+ // Retrieve hierarchy as directories
+ nsresult GetNodes(nsIMutableArray* aNodes);
+ nsresult ModifyCardInternal(nsIAbCard* aModifiedCard, bool aIsAddition);
+ // Notification for the UI.
+ nsresult NotifyItemDeletion(nsISupports* aItem, bool aIsCard,
+ const char* aNotificationUID = nullptr);
+ nsresult NotifyItemAddition(nsISupports* aItem, bool aIsCard,
+ const char* aNotificationUID = nullptr);
+ nsresult NotifyItemModification(nsISupports* aItem, bool aIsCard,
+ const char* aNotificationUID = nullptr);
+ nsresult NotifyCardPropertyChanges(nsIAbCard* aOld, nsIAbCard* aNew);
+ nsresult commonNotification(nsISupports* aItem, const char* aTopic,
+ const char* aNotificationUID);
+ // Utility to produce a card from a URI.
+ nsresult OutlookCardForURI(const nsACString& aUri, nsIAbCard** card);
+
+ nsMapiEntry* mDirEntry;
+ // Keep track of context ID to be passed back from `DoQuery()`.
+ int32_t mCurrentQueryId;
+ // Data for the search interfaces
+ int32_t mSearchContext;
+
+ private:
+ virtual ~nsAbOutlookDirectory(void);
+ nsCString mParentEntryId;
+
+ // This is totally quirky. `m_AddressList` is defined in
+ // class nsAbDirProperty to hold a list of mailing lists,
+ // but there is no member to hold a list of cards.
+ // It gets worse: For mailing lists, `m_AddressList` holds the
+ // list of cards.
+ // So we'll do it as the Mac AB does and define a member for it.
+ // nsIMutableArray is used, because then it is interchangeable with
+ // `m_AddressList`.
+ nsCOMPtr<nsIMutableArray> mCardList;
+};
+
+enum {
+ index_DisplayName = 0,
+ index_FirstName,
+ index_LastName,
+ index_NickName,
+ index_WorkPhoneNumber,
+ index_HomePhoneNumber,
+ index_WorkFaxNumber,
+ index_PagerNumber,
+ index_MobileNumber,
+ index_HomeCity,
+ index_HomeState,
+ index_HomeZip,
+ index_HomeCountry,
+ index_WorkCity,
+ index_WorkState,
+ index_WorkZip,
+ index_WorkCountry,
+ index_JobTitle,
+ index_Department,
+ index_Company,
+ index_WorkWebPage,
+ index_HomeWebPage,
+ index_Notes,
+ index_LastProp
+};
+
+// The following properties are retrieved from the contact associated
+// with the address book entry. Email not available on contact,
+// the contact has three named email properties.
+static const ULONG OutlookCardMAPIProps[] = {
+ PR_DISPLAY_NAME_W,
+ PR_GIVEN_NAME_W,
+ PR_SURNAME_W,
+ PR_NICKNAME_W,
+ PR_BUSINESS_TELEPHONE_NUMBER_W,
+ PR_HOME_TELEPHONE_NUMBER_W,
+ PR_BUSINESS_FAX_NUMBER_W,
+ PR_PAGER_TELEPHONE_NUMBER_W,
+ PR_MOBILE_TELEPHONE_NUMBER_W,
+ PR_HOME_ADDRESS_CITY_W,
+ PR_HOME_ADDRESS_STATE_OR_PROVINCE_W,
+ PR_HOME_ADDRESS_POSTAL_CODE_W,
+ PR_HOME_ADDRESS_COUNTRY_W,
+ PR_BUSINESS_ADDRESS_CITY_W,
+ PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W,
+ PR_BUSINESS_ADDRESS_POSTAL_CODE_W,
+ PR_BUSINESS_ADDRESS_COUNTRY_W,
+ PR_TITLE_W,
+ PR_DEPARTMENT_NAME_W,
+ PR_COMPANY_NAME_W,
+ PR_BUSINESS_HOME_PAGE_W,
+ PR_PERSONAL_HOME_PAGE_W,
+ PR_BODY_W};
+
+static const char* CardStringProperties[] = {
+ kFirstNameProperty, kLastNameProperty, kDisplayNameProperty,
+ kNicknameProperty, kPriEmailProperty,
+
+ kHomeAddressProperty, kHomeAddress2Property, kHomeCityProperty,
+ kHomeStateProperty, kHomeZipCodeProperty, kHomeCountryProperty,
+ kHomeWebPageProperty,
+
+ kWorkAddressProperty, kWorkAddress2Property, kWorkCityProperty,
+ kWorkStateProperty, kWorkZipCodeProperty, kWorkCountryProperty,
+ kWorkWebPageProperty,
+
+ kHomePhoneProperty, kWorkPhoneProperty, kFaxProperty,
+ kPagerProperty, kCellularProperty,
+
+ kJobTitleProperty, kDepartmentProperty, kCompanyProperty,
+ kNotesProperty};
+
+static const char* CardIntProperties[] = {
+ kBirthYearProperty, kBirthMonthProperty, kBirthDayProperty};
+
+#endif // nsAbOutlookDirectory_h___
diff --git a/comm/mailnews/addrbook/src/nsAbOutlookInterface.cpp b/comm/mailnews/addrbook/src/nsAbOutlookInterface.cpp
new file mode 100644
index 0000000000..7000c90317
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOutlookInterface.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsAbOutlookInterface.h"
+#include "nsAbWinHelper.h"
+#include "nsComponentManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsAbOutlookInterface, nsIAbOutlookInterface)
+
+nsAbOutlookInterface::nsAbOutlookInterface(void) {}
+
+nsAbOutlookInterface::~nsAbOutlookInterface(void) {}
+
+NS_IMETHODIMP
+nsAbOutlookInterface::GetFolderURIs(const nsACString& aURI,
+ nsTArray<nsCString>& uris) {
+ uris.Clear();
+ nsresult rv = NS_OK;
+
+ nsAbWinHelperGuard mapiAddBook;
+ nsMapiEntryArray folders;
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mapiAddBook->IsOK() || !mapiAddBook->GetFolders(folders)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uris.SetCapacity(folders.mNbEntries);
+
+ for (ULONG i = 0; i < folders.mNbEntries; ++i) {
+ nsAutoCString entryId;
+ nsAutoCString uri(kOutlookDirectoryScheme);
+ folders.mEntries[i].ToString(entryId);
+ uri.Append(entryId);
+ uris.AppendElement(uri);
+ }
+ return NS_OK;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbOutlookInterface.h b/comm/mailnews/addrbook/src/nsAbOutlookInterface.h
new file mode 100644
index 0000000000..fd51f83516
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbOutlookInterface.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+#ifndef nsAbOutlookInterface_h___
+#define nsAbOutlookInterface_h___
+
+#include "nsIAbOutlookInterface.h"
+
+class nsAbOutlookInterface : public nsIAbOutlookInterface {
+ public:
+ nsAbOutlookInterface(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTLOOKINTERFACE
+
+ private:
+ virtual ~nsAbOutlookInterface(void);
+};
+
+#endif // nsAbOutlookInterface_h___
diff --git a/comm/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp b/comm/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp
new file mode 100644
index 0000000000..bf5e158de3
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp
@@ -0,0 +1,293 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAbQueryStringToExpression.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsITextToSubURI.h"
+#include "nsAbBooleanExpression.h"
+#include "plstr.h"
+
+/**
+ * This code parses the query expression passed in as an addressbook URI.
+ * The expression takes the form:
+ * (BOOL1(FIELD1,OP1,VALUE1)..(FIELDn,OPn,VALUEn)(BOOL2(FIELD1,OP1,VALUE1)...)...)
+ *
+ * BOOLn A boolean operator joining subsequent terms delimited by ().
+ * For possible values see CreateBooleanExpression().
+ * FIELDn An addressbook card data field.
+ * OPn An operator for the search term.
+ * For possible values see CreateBooleanConditionString().
+ * VALUEn The value to be matched in the FIELDn via the OPn operator.
+ * The value must be URL encoded by the caller, if it contains any
+ * special characters including '(' and ')'.
+ */
+nsresult nsAbQueryStringToExpression::Convert(
+ const nsACString& aQueryString, nsIAbBooleanExpression** expression) {
+ nsresult rv;
+
+ nsAutoCString q(aQueryString);
+ q.StripWhitespace();
+ const char* queryChars = q.get();
+
+ nsCOMPtr<nsISupports> s;
+ rv = ParseExpression(&queryChars, getter_AddRefs(s));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Case: Not end of string
+ if (*queryChars != 0) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbBooleanExpression> e(do_QueryInterface(s, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ e.forget(expression);
+ return rv;
+}
+
+nsresult nsAbQueryStringToExpression::ParseExpression(
+ const char** index, nsISupports** expression) {
+ nsresult rv;
+
+ if (**index == '?') {
+ (*index)++;
+ }
+
+ if (**index != '(') return NS_ERROR_FAILURE;
+
+ const char* indexBracket = *index + 1;
+ while (*indexBracket && *indexBracket != '(' && *indexBracket != ')')
+ indexBracket++;
+
+ // Case: End of string
+ if (*indexBracket == 0) return NS_ERROR_FAILURE;
+
+ // Case: "((" or "()"
+ if (indexBracket == *index + 1) {
+ return NS_ERROR_FAILURE;
+ }
+ // Case: "(*("
+ else if (*indexBracket == '(') {
+ // printf ("Case: (*(: %s\n", *index);
+
+ nsCString operation;
+ rv = ParseOperationEntry(*index, indexBracket, getter_Copies(operation));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbBooleanExpression> e;
+ rv = CreateBooleanExpression(operation.get(), getter_AddRefs(e));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Case: "(*)(*)....(*))"
+ *index = indexBracket;
+ rv = ParseExpressions(index, e);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ e.forget(expression);
+ }
+ // Case" "(*)"
+ else if (*indexBracket == ')') {
+ // printf ("Case: (*): %s\n", *index);
+
+ nsCOMPtr<nsIAbBooleanConditionString> conditionString;
+ rv = ParseCondition(index, indexBracket, getter_AddRefs(conditionString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ conditionString.forget(expression);
+ }
+
+ if (**index != ')') return NS_ERROR_FAILURE;
+
+ (*index)++;
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseExpressions(
+ const char** index, nsIAbBooleanExpression* expression) {
+ nsresult rv;
+ nsTArray<RefPtr<nsISupports>> expressions;
+
+ // Case: ")(*)(*)....(*))"
+ // printf ("Case: )(*)(*)....(*)): %s\n", *index);
+ while (**index == '(') {
+ nsCOMPtr<nsISupports> childExpression;
+ rv = ParseExpression(index, getter_AddRefs(childExpression));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ expressions.AppendElement(childExpression);
+ }
+
+ if (**index == 0) return NS_ERROR_FAILURE;
+
+ // Case: "))"
+ // printf ("Case: )): %s\n", *index);
+
+ if (**index != ')') return NS_ERROR_FAILURE;
+
+ expression->SetExpressions(expressions);
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseCondition(
+ const char** index, const char* indexBracketClose,
+ nsIAbBooleanConditionString** conditionString) {
+ nsresult rv;
+
+ (*index)++;
+
+ nsCString entries[3];
+ for (int i = 0; i < 3; i++) {
+ rv = ParseConditionEntry(index, indexBracketClose,
+ getter_Copies(entries[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*index == indexBracketClose) break;
+ }
+
+ if (*index != indexBracketClose) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAbBooleanConditionString> c;
+ rv = CreateBooleanConditionString(entries[0].get(), entries[1].get(),
+ entries[2].get(), getter_AddRefs(c));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ c.forget(conditionString);
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseConditionEntry(
+ const char** index, const char* indexBracketClose, char** entry) {
+ const char* indexDeliminator = *index;
+ while (indexDeliminator != indexBracketClose && *indexDeliminator != ',')
+ indexDeliminator++;
+
+ int entryLength = indexDeliminator - *index;
+ if (entryLength)
+ *entry = PL_strndup(*index, entryLength);
+ else
+ *entry = 0;
+
+ if (indexDeliminator != indexBracketClose)
+ *index = indexDeliminator + 1;
+ else
+ *index = indexDeliminator;
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::ParseOperationEntry(
+ const char* indexBracketOpen1, const char* indexBracketOpen2,
+ char** operation) {
+ int operationLength = indexBracketOpen2 - indexBracketOpen1 - 1;
+ if (operationLength)
+ *operation = PL_strndup(indexBracketOpen1 + 1, operationLength);
+ else
+ *operation = 0;
+
+ return NS_OK;
+}
+
+nsresult nsAbQueryStringToExpression::CreateBooleanExpression(
+ const char* operation, nsIAbBooleanExpression** expression) {
+ nsAbBooleanOperationType op;
+ if (PL_strcasecmp(operation, "and") == 0)
+ op = nsIAbBooleanOperationTypes::AND;
+ else if (PL_strcasecmp(operation, "or") == 0)
+ op = nsIAbBooleanOperationTypes::OR;
+ else if (PL_strcasecmp(operation, "not") == 0)
+ op = nsIAbBooleanOperationTypes::NOT;
+ else
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbBooleanExpression> expr =
+ do_CreateInstance("@mozilla.org/boolean-expression/n-peer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = expr->SetOperation(op);
+ expr.forget(expression);
+ return rv;
+}
+
+nsresult nsAbQueryStringToExpression::CreateBooleanConditionString(
+ const char* attribute, const char* condition, const char* value,
+ nsIAbBooleanConditionString** conditionString) {
+ if (attribute == 0 || condition == 0 || value == 0) return NS_ERROR_FAILURE;
+
+ nsAbBooleanConditionType c;
+
+ if (PL_strcasecmp(condition, "=") == 0)
+ c = nsIAbBooleanConditionTypes::Is;
+ else if (PL_strcasecmp(condition, "!=") == 0)
+ c = nsIAbBooleanConditionTypes::IsNot;
+ else if (PL_strcasecmp(condition, "lt") == 0)
+ c = nsIAbBooleanConditionTypes::LessThan;
+ else if (PL_strcasecmp(condition, "gt") == 0)
+ c = nsIAbBooleanConditionTypes::GreaterThan;
+ else if (PL_strcasecmp(condition, "bw") == 0)
+ c = nsIAbBooleanConditionTypes::BeginsWith;
+ else if (PL_strcasecmp(condition, "ew") == 0)
+ c = nsIAbBooleanConditionTypes::EndsWith;
+ else if (PL_strcasecmp(condition, "c") == 0)
+ c = nsIAbBooleanConditionTypes::Contains;
+ else if (PL_strcasecmp(condition, "!c") == 0)
+ c = nsIAbBooleanConditionTypes::DoesNotContain;
+ else if (PL_strcasecmp(condition, "~=") == 0)
+ c = nsIAbBooleanConditionTypes::SoundsLike;
+ else if (PL_strcasecmp(condition, "regex") == 0)
+ c = nsIAbBooleanConditionTypes::RegExp;
+ else if (PL_strcasecmp(condition, "ex") == 0)
+ c = nsIAbBooleanConditionTypes::Exists;
+ else if (PL_strcasecmp(condition, "!ex") == 0)
+ c = nsIAbBooleanConditionTypes::DoesNotExist;
+ else
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbBooleanConditionString> cs = do_CreateInstance(
+ "@mozilla.org/boolean-expression/condition-string;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cs->SetCondition(c);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsString attributeUCS2;
+ nsString valueUCS2;
+
+ rv = textToSubURI->UnEscapeAndConvert(
+ "UTF-8"_ns, nsDependentCString(attribute), attributeUCS2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = textToSubURI->UnEscapeAndConvert("UTF-8"_ns, nsDependentCString(value),
+ valueUCS2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF16toUTF8 attributeUTF8(attributeUCS2);
+
+ rv = cs->SetName(attributeUTF8.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cs->SetValue(valueUCS2.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_ConvertUTF8toUTF16 valueUCS2(value);
+
+ rv = cs->SetName(attribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cs->SetValue(valueUCS2.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ cs.forget(conditionString);
+ return NS_OK;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbQueryStringToExpression.h b/comm/mailnews/addrbook/src/nsAbQueryStringToExpression.h
new file mode 100644
index 0000000000..acab014278
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbQueryStringToExpression.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsAbQueryStringToExpression_h__
+#define nsAbQueryStringToExpression_h__
+
+#include "nsIAbBooleanExpression.h"
+
+class nsAbQueryStringToExpression {
+ public:
+ static nsresult Convert(const nsACString& aQueryString,
+ nsIAbBooleanExpression** expression);
+
+ protected:
+ static nsresult ParseExpression(const char** index, nsISupports** expression);
+ static nsresult ParseExpressions(const char** index,
+ nsIAbBooleanExpression* expression);
+ static nsresult ParseCondition(const char** index,
+ const char* indexBracketClose,
+ nsIAbBooleanConditionString** conditionString);
+
+ static nsresult ParseConditionEntry(const char** index,
+ const char* indexBracketClose,
+ char** entry);
+ static nsresult ParseOperationEntry(const char* indexBracketOpen1,
+ const char* indexBracketOpen2,
+ char** operation);
+
+ static nsresult CreateBooleanExpression(const char* operation,
+ nsIAbBooleanExpression** expression);
+ static nsresult CreateBooleanConditionString(
+ const char* attribute, const char* condition, const char* value,
+ nsIAbBooleanConditionString** conditionString);
+};
+
+#endif
diff --git a/comm/mailnews/addrbook/src/nsAbWinHelper.cpp b/comm/mailnews/addrbook/src/nsAbWinHelper.cpp
new file mode 100644
index 0000000000..79379c45c7
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbWinHelper.cpp
@@ -0,0 +1,1491 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#define INITGUID
+#define USES_IID_IMAPIProp
+#define USES_IID_IMessage
+#define USES_IID_IMAPIFolder
+#define USES_IID_IMAPIContainer
+#define USES_IID_IABContainer
+#define USES_IID_IMAPITable
+#define USES_IID_IDistList
+
+#include "nsAbWinHelper.h"
+#include "nsMapiAddressBook.h"
+
+#include <mapiguid.h>
+
+#include "mozilla/Logging.h"
+
+#define PRINT_TO_CONSOLE 0
+#if PRINT_TO_CONSOLE
+# define PRINTF(args) printf args
+#else
+static mozilla::LazyLogModule gAbWinHelperLog("AbWinHelper");
+# define PRINTF(args) MOZ_LOG(gAbWinHelperLog, mozilla::LogLevel::Debug, args)
+#endif
+
+// Small utility to ensure release of all MAPI interfaces
+template <class tInterface>
+struct nsMapiInterfaceWrapper {
+ tInterface mInterface;
+
+ nsMapiInterfaceWrapper(void) : mInterface(NULL) {}
+ ~nsMapiInterfaceWrapper(void) {
+ if (mInterface != NULL) {
+ mInterface->Release();
+ }
+ }
+ operator LPUNKNOWN*(void) {
+ return reinterpret_cast<LPUNKNOWN*>(&mInterface);
+ }
+ tInterface operator->(void) const { return mInterface; }
+ operator tInterface*(void) { return &mInterface; }
+ tInterface Get(void) const { return mInterface; }
+};
+
+static void assignEntryID(LPENTRYID& aTarget, LPENTRYID aSource,
+ ULONG aByteCount) {
+ if (aTarget != NULL) {
+ delete[] (reinterpret_cast<LPBYTE>(aTarget));
+ aTarget = NULL;
+ }
+ if (aSource != NULL) {
+ aTarget = reinterpret_cast<LPENTRYID>(new BYTE[aByteCount]);
+ memcpy(aTarget, aSource, aByteCount);
+ }
+}
+
+nsMapiEntry::nsMapiEntry(void) : mByteCount(0), mEntryId(NULL) {
+ MOZ_COUNT_CTOR(nsMapiEntry);
+}
+
+nsMapiEntry::nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId)
+ : mByteCount(0), mEntryId(NULL) {
+ Assign(aByteCount, aEntryId);
+ MOZ_COUNT_CTOR(nsMapiEntry);
+}
+
+void nsMapiEntry::Move(nsMapiEntry& target, nsMapiEntry& source) {
+ target.mByteCount = source.mByteCount;
+ target.mEntryId = source.mEntryId;
+ source.mByteCount = 0;
+ source.mEntryId = NULL;
+}
+
+nsMapiEntry::~nsMapiEntry(void) {
+ Assign(0, NULL);
+ MOZ_COUNT_DTOR(nsMapiEntry);
+}
+
+void nsMapiEntry::Assign(ULONG aByteCount, LPENTRYID aEntryId) {
+ assignEntryID(mEntryId, aEntryId, aByteCount);
+ mByteCount = aByteCount;
+}
+
+void nsMapiEntry::Assign(const nsCString& aString) {
+ Assign(0, NULL);
+ ULONG byteCount = aString.Length() / 2;
+
+ if ((aString.Length() & 0x01) != 0) {
+ // Something wrong here, we should always get an even number of hex digits.
+ byteCount += 1;
+ }
+ unsigned char* currentTarget = new unsigned char[byteCount];
+
+ mByteCount = byteCount;
+ mEntryId = reinterpret_cast<LPENTRYID>(currentTarget);
+ ULONG j = 0;
+ for (uint32_t i = 0; i < aString.Length(); i += 2) {
+ char c1 = aString.CharAt(i);
+ char c2 = i + 1 < aString.Length() ? aString.CharAt(i + 1) : '0';
+ // clang-format off
+ currentTarget[j] =
+ ((c1 <= '9' ? c1 - '0' : c1 - 'A' + 10) << 4) |
+ (c2 <= '9' ? c2 - '0' : c2 - 'A' + 10);
+ // clang-format on
+ j++;
+ }
+}
+
+void nsMapiEntry::ToString(nsCString& aString) const {
+ aString.Truncate();
+ aString.SetCapacity(mByteCount * 2);
+ char twoBytes[3];
+
+ for (ULONG i = 0; i < mByteCount; i++) {
+ sprintf(twoBytes, "%02X", (reinterpret_cast<unsigned char*>(mEntryId))[i]);
+ aString.Append(twoBytes);
+ }
+}
+
+void nsMapiEntry::Dump(void) const {
+ PRINTF(("%lu\n", mByteCount));
+ for (ULONG i = 0; i < mByteCount; ++i) {
+ PRINTF(("%02X", (reinterpret_cast<unsigned char*>(mEntryId))[i]));
+ }
+ PRINTF(("\n"));
+}
+
+nsMapiEntryArray::nsMapiEntryArray(void) : mEntries(NULL), mNbEntries(0) {
+ MOZ_COUNT_CTOR(nsMapiEntryArray);
+}
+
+nsMapiEntryArray::~nsMapiEntryArray(void) {
+ if (mEntries) {
+ delete[] mEntries;
+ }
+ MOZ_COUNT_DTOR(nsMapiEntryArray);
+}
+
+void nsMapiEntryArray::CleanUp(void) {
+ if (mEntries != NULL) {
+ delete[] mEntries;
+ mEntries = NULL;
+ mNbEntries = 0;
+ }
+}
+
+// Microsoft distinguishes between address book entries and contacts.
+// Address book entries are of class IMailUser and are stored in containers
+// of class IABContainer.
+// Local contacts are stored in the "contacts folder" of class IMAPIFolder and
+// are of class IMessage with "message class" IPM.Contact.
+// For local address books the entry ID of the contact can be derived from the
+// entry ID of the address book entry and vice versa.
+// Most attributes can be retrieved from both classes with some exceptions:
+// The primary e-mail address is only stored on the IMailUser, the contact
+// has three named email properties (which are not used so far).
+// The birthday is only stored on the contact.
+// `OpenMAPIObject()` can open the address book entry as well as the contact,
+// to open the concact it needs to get the message store from via the
+// address book container (or "directory" in Thunderbird terms).
+// Apart from Microsoft documentation, the best source of information
+// is the MAPI programmers mailing list at MAPI-L@PEACH.EASE.LSOFT.COM.
+// All the information that was needed to "refresh" the MAPI implementation
+// in Thunderbird was obtained via these threads:
+// https://peach.ease.lsoft.com/scripts/wa-PEACH.exe?A2=2012&L=MAPI-L&D=0&P=20988415
+// https://peach.ease.lsoft.com/scripts/wa-PEACH.exe?A2=2101&L=MAPI-L&D=0&P=21034512
+
+// Some stuff to access the entry ID of the contact (IMessage, IPM.Contact)
+// from the address book entry ID (IMailUser).
+// The address book entry ID has the following structure, see:
+// https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcdata/c33d5b9c-d044-4727-96e2-2051f8419ab1
+#define ABENTRY_FLAGS_LENGTH 4
+#define CONTAB_PROVIDER_ID \
+ "\xFE\x42\xAA\x0A\x18\xC7\x1A\x10\xE8\x85\x0B\x65\x1C\x24\x00\x00"
+#define CONTAB_PROVIDER_ID_LENGTH 16
+#define ABENTRY_VERSION "\x03\x00\x00\x00"
+#define ABENTRY_VERSION_LENGTH 4
+#define ABENTRY_TYPE "\x04\x00\x00\x00"
+#define ABENTRY_TYPE_LENGTH 4
+
+struct AbEntryId {
+ BYTE flags[ABENTRY_FLAGS_LENGTH];
+ BYTE provider[CONTAB_PROVIDER_ID_LENGTH];
+ BYTE version[ABENTRY_VERSION_LENGTH];
+ BYTE type[ABENTRY_TYPE_LENGTH];
+ ULONG index;
+ ULONG length;
+ BYTE idBytes[];
+};
+
+// Some stuff to access the entry IDs of members in a distribution list
+// (IMessage, IPM.DistList):
+// https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxocntc/02656215-1cb0-4b06-a077-b07e756216be
+// Also handy the reference to the so-called "one off" members:
+// https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcdata/b32d23af-85f6-4e92-8387-53a1950ae7ba
+#define DLENTRY_FLAGS_LENGTH 4
+#define DL_PROVIDER_ID \
+ "\xC0\x91\xAD\xD3\x51\x9D\xCF\x11\xA4\xA9\x00\xAA\x00\x47\xFA\xA4"
+#define DL_PROVIDER_ID_LENGTH 16
+#define DLENTRY_TYPE_LENGTH 1
+struct DlEntryId {
+ BYTE flags[DLENTRY_FLAGS_LENGTH];
+ BYTE provider[DL_PROVIDER_ID_LENGTH];
+ BYTE type[DLENTRY_TYPE_LENGTH];
+ BYTE idBytes[];
+};
+
+#define DLENTRY_OO_FLAGS_LENGTH 4
+#define DL_OO_PROVIDER_ID \
+ "\x81\x2B\x1F\xA4\xBE\xA3\x10\x19\x9D\x6E\x00\xDD\x01\x0F\x54\x02"
+#define DL_OO_PROVIDER_ID_LENGTH 16
+struct DlEntryIdOo {
+ BYTE flags[DLENTRY_OO_FLAGS_LENGTH];
+ BYTE provider[DL_OO_PROVIDER_ID_LENGTH];
+ // Note that the documentation specifies a two-byte version followed by a
+ // two-byte "bit collection", but MFCMapi
+ // (https://github.com/stephenegriffin/mfcmapi) shows, for example:
+ // dwBitmask: 0x80010000 = MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO.
+ // Intel x86 and AMD64 / x86-64 hardware is little-endian, so that
+ // equates to 0x0000 0x01 0x80 in memory:
+ // M (1 bit): (mask 0x0100) (MIME) and U (1 bit): (mask 0x0080) (Unicode).
+ ULONG versionAndBits;
+ BYTE variable[];
+};
+
+using namespace mozilla;
+
+uint32_t nsAbWinHelper::sEntryCounter = 0;
+mozilla::StaticMutex nsAbWinHelper::sMutex;
+// There seems to be a deadlock/auto-destruction issue
+// in MAPI when multiple threads perform init/release
+// operations at the same time. So I've put a mutex
+// around both the initialize process and the destruction
+// one. I just hope the rest of the calls don't need the
+// same protection (MAPI is supposed to be thread-safe).
+
+nsAbWinHelper::nsAbWinHelper(void) : mLastError(S_OK), mAddressBook(NULL) {
+ MOZ_COUNT_CTOR(nsAbWinHelper);
+}
+
+nsAbWinHelper::~nsAbWinHelper(void) { MOZ_COUNT_DTOR(nsAbWinHelper); }
+
+BOOL nsAbWinHelper::GetFolders(nsMapiEntryArray& aFolders) {
+ aFolders.CleanUp();
+ nsMapiInterfaceWrapper<LPABCONT> rootFolder;
+ nsMapiInterfaceWrapper<LPMAPITABLE> folders;
+ ULONG objType = 0;
+ ULONG rowCount = 0;
+ SRestriction restriction;
+ SPropTagArray folderColumns;
+
+ mLastError = mAddressBook->OpenEntry(0, NULL, NULL, 0, &objType, rootFolder);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open root %08lx.\n", mLastError));
+ return FALSE;
+ }
+ mLastError = rootFolder->GetHierarchyTable(0, folders);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get hierarchy %08lx.\n", mLastError));
+ return FALSE;
+ }
+ // We only take into account modifiable containers,
+ // otherwise, we end up with all the directory services...
+ restriction.rt = RES_BITMASK;
+ restriction.res.resBitMask.ulPropTag = PR_CONTAINER_FLAGS;
+ restriction.res.resBitMask.relBMR = BMR_NEZ;
+ restriction.res.resBitMask.ulMask = AB_MODIFIABLE;
+ mLastError = folders->Restrict(&restriction, 0);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot restrict table %08lx.\n", mLastError));
+ }
+ folderColumns.cValues = 1;
+ folderColumns.aulPropTag[0] = PR_ENTRYID;
+ mLastError = folders->SetColumns(&folderColumns, 0);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set columns %08lx.\n", mLastError));
+ return FALSE;
+ }
+ mLastError = folders->GetRowCount(0, &rowCount);
+ if (HR_SUCCEEDED(mLastError)) {
+ aFolders.mEntries = new nsMapiEntry[rowCount];
+ aFolders.mNbEntries = 0;
+ do {
+ LPSRowSet rowSet = NULL;
+
+ rowCount = 0;
+ mLastError = folders->QueryRows(1, 0, &rowSet);
+ if (HR_SUCCEEDED(mLastError)) {
+ rowCount = rowSet->cRows;
+ if (rowCount > 0) {
+ nsMapiEntry& current = aFolders.mEntries[aFolders.mNbEntries++];
+ SPropValue& currentValue = rowSet->aRow->lpProps[0];
+
+ current.Assign(
+ currentValue.Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb));
+ }
+ MyFreeProws(rowSet);
+ } else {
+ PRINTF(("Cannot query rows %08lx.\n", mLastError));
+ }
+ } while (rowCount > 0);
+ }
+ return HR_SUCCEEDED(mLastError);
+}
+
+BOOL nsAbWinHelper::GetCards(const nsMapiEntry& aParent,
+ LPSRestriction aRestriction,
+ nsMapiEntryArray& aCards) {
+ aCards.CleanUp();
+ return GetContents(aParent, aRestriction, &aCards.mEntries, aCards.mNbEntries,
+ 0);
+}
+
+BOOL nsAbWinHelper::GetNodes(const nsMapiEntry& aParent,
+ nsMapiEntryArray& aNodes) {
+ aNodes.CleanUp();
+ return GetContents(aParent, NULL, &aNodes.mEntries, aNodes.mNbEntries,
+ MAPI_DISTLIST);
+}
+
+BOOL nsAbWinHelper::GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards) {
+ aNbCards = 0;
+ return GetContents(aParent, NULL, NULL, aNbCards, 0);
+}
+
+BOOL nsAbWinHelper::GetPropertyString(const nsMapiEntry& aObject,
+ ULONG aPropertyTag, nsCString& aName) {
+ aName.Truncate();
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ nsMapiEntry nullEntry;
+ if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values,
+ valueCount)) {
+ return FALSE;
+ }
+
+ if (valueCount != 1 || values == NULL) {
+ PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyString"));
+ return FALSE;
+ }
+
+ BOOL success = TRUE;
+ if (PROP_TYPE(values->ulPropTag) == PT_STRING8) {
+ aName = values->Value.lpszA;
+ } else if (PROP_TYPE(values->ulPropTag) == PT_UNICODE) {
+ aName = NS_LossyConvertUTF16toASCII(values->Value.lpszW);
+ } else {
+ PRINTF(("Unexpected return value for property %08lx (x0A is PT_ERROR).\n",
+ values->ulPropTag));
+ success = FALSE;
+ }
+ FreeBuffer(values);
+ return success;
+}
+
+BOOL nsAbWinHelper::GetPropertyUString(const nsMapiEntry& aObject,
+ ULONG aPropertyTag, nsString& aName) {
+ aName.Truncate();
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ nsMapiEntry nullEntry;
+ if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values,
+ valueCount)) {
+ return FALSE;
+ }
+ if (valueCount != 1 || values == NULL) {
+ PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyUString"));
+ return FALSE;
+ }
+
+ BOOL success = TRUE;
+ if (PROP_TYPE(values->ulPropTag) == PT_UNICODE) {
+ aName = values->Value.lpszW;
+ } else if (PROP_TYPE(values->ulPropTag) == PT_STRING8) {
+ aName.AssignASCII(values->Value.lpszA);
+ } else {
+ PRINTF(("Unexpected return value for property %08lx (x0A is PT_ERROR).\n",
+ values->ulPropTag));
+ success = FALSE;
+ }
+ return success;
+}
+
+BOOL nsAbWinHelper::GetPropertiesUString(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[],
+ ULONG aNbProperties, nsString aNames[],
+ bool aSuccess[]) {
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ if (!GetMAPIProperties(aDir, aObject, aPropertyTags, aNbProperties, values,
+ valueCount, true))
+ return FALSE;
+
+ if (valueCount != aNbProperties || values == NULL) {
+ PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertiesUString"));
+ return FALSE;
+ }
+ for (ULONG i = 0; i < valueCount; ++i) {
+ aNames[i].Truncate();
+ aSuccess[i] = false;
+ if (PROP_ID(values[i].ulPropTag) == PROP_ID(aPropertyTags[i])) {
+ if (PROP_TYPE(values[i].ulPropTag) == PT_STRING8) {
+ aNames[i].AssignASCII(values[i].Value.lpszA);
+ aSuccess[i] = true;
+ } else if (PROP_TYPE(values[i].ulPropTag) == PT_UNICODE) {
+ aNames[i] = values[i].Value.lpszW;
+ aSuccess[i] = true;
+ } else {
+ PRINTF(
+ ("Unexpected return value for property %08lx (x0A is PT_ERROR).\n",
+ values[i].ulPropTag));
+ }
+ }
+ }
+ FreeBuffer(values);
+ return TRUE;
+}
+
+BOOL nsAbWinHelper::GetPropertyDate(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ bool fromContact, ULONG aPropertyTag,
+ WORD& aYear, WORD& aMonth, WORD& aDay) {
+ aYear = 0;
+ aMonth = 0;
+ aDay = 0;
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ if (!GetMAPIProperties(aDir, aObject, &aPropertyTag, 1, values, valueCount,
+ fromContact)) {
+ return FALSE;
+ }
+ if (valueCount != 1 || values == NULL) {
+ PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyDate"));
+ return FALSE;
+ }
+
+ BOOL success = TRUE;
+ if (PROP_TYPE(values->ulPropTag) == PT_SYSTIME) {
+ SYSTEMTIME readableTime;
+ if (FileTimeToSystemTime(&values->Value.ft, &readableTime)) {
+ aYear = readableTime.wYear;
+ aMonth = readableTime.wMonth;
+ aDay = readableTime.wDay;
+ }
+ } else {
+ PRINTF(("Cannot retrieve PT_SYSTIME property %08lx (x0A is PT_ERROR).\n",
+ values->ulPropTag));
+ success = FALSE;
+ }
+ FreeBuffer(values);
+ return success;
+}
+
+BOOL nsAbWinHelper::GetPropertyLong(const nsMapiEntry& aObject,
+ ULONG aPropertyTag, ULONG& aValue) {
+ aValue = 0;
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ nsMapiEntry nullEntry;
+ if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values,
+ valueCount)) {
+ return FALSE;
+ }
+ if (valueCount != 1 || values == NULL) {
+ PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyLong"));
+ return FALSE;
+ }
+
+ BOOL success = TRUE;
+ if (PROP_TYPE(values->ulPropTag) == PT_LONG) {
+ aValue = values->Value.ul;
+ } else {
+ PRINTF(("Cannot retrieve PT_LONG property %08lx (x0A is PT_ERROR).\n",
+ values->ulPropTag));
+ success = FALSE;
+ }
+ FreeBuffer(values);
+ return success;
+}
+
+BOOL nsAbWinHelper::GetPropertyBin(const nsMapiEntry& aObject,
+ ULONG aPropertyTag, nsMapiEntry& aValue) {
+ aValue.Assign(0, NULL);
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ nsMapiEntry nullEntry;
+ if (!GetMAPIProperties(nullEntry, aObject, &aPropertyTag, 1, values,
+ valueCount)) {
+ return FALSE;
+ }
+ if (valueCount != 1 || values == NULL) {
+ PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyBin"));
+ return FALSE;
+ }
+
+ BOOL success = TRUE;
+ if (PROP_TYPE(values->ulPropTag) == PT_BINARY) {
+ aValue.Assign(values->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(values->Value.bin.lpb));
+ } else {
+ PRINTF(("Cannot retrieve PT_BINARY property %08lx (x0A is PT_ERROR).\n",
+ values->ulPropTag));
+ success = FALSE;
+ }
+
+ FreeBuffer(values);
+ return success;
+}
+
+BOOL nsAbWinHelper::GetPropertiesMVBin(
+ const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[], ULONG aNbProperties, nsMapiEntry* aEntryIDs[],
+ ULONG aNbElements[], bool aAllocateMore) {
+ LPSPropValue values = NULL;
+ ULONG valueCount = 0;
+
+ // Initialise output arrays.
+ for (ULONG i = 0; i < aNbProperties; i++) {
+ aEntryIDs[i] = NULL;
+ aNbElements[i] = 0;
+ }
+
+ if (!GetMAPIProperties(aDir, aObject, aPropertyTags, aNbProperties, values,
+ valueCount, true)) {
+ return FALSE;
+ }
+ if (valueCount != aNbProperties || values == NULL) {
+ PRINTF(("Unexpected return value in nsAbWinHelper::GetPropertyMVBin"));
+ return FALSE;
+ }
+
+ BOOL success = TRUE;
+ for (ULONG i = 0; i < valueCount; i++) {
+ if (PROP_TYPE(values[i].ulPropTag) == PT_MV_BINARY) {
+ ULONG count = values[i].Value.MVbin.cValues;
+ PRINTF(("Found %lu members in DL.\n", count));
+ aEntryIDs[i] = new nsMapiEntry[aAllocateMore ? count + 1 : count];
+ aNbElements[i] = count;
+ SBinary* currentValue = values[i].Value.MVbin.lpbin;
+ for (ULONG j = 0; j < count; j++) {
+ nsMapiEntry& current = aEntryIDs[i][j];
+ current.Assign(currentValue->cb,
+ reinterpret_cast<LPENTRYID>(currentValue->lpb));
+ currentValue++;
+ }
+ } else {
+ PRINTF(
+ ("Cannot retrieve PT_MV_BINARY property %08lx (x0A is PT_ERROR).\n",
+ values[i].ulPropTag));
+ success = FALSE;
+ }
+ }
+
+ FreeBuffer(values);
+ if (!success) {
+ for (ULONG i = 0; i < aNbProperties; i++) {
+ if (aNbElements[i] > 0) delete[] aEntryIDs[i];
+ aEntryIDs[i] = NULL;
+ aNbElements[i] = 0;
+ }
+ }
+ return success;
+}
+
+BOOL nsAbWinHelper::SetPropertiesMVBin(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[],
+ ULONG aNbProperties,
+ nsMapiEntry* aEntryIDs[],
+ ULONG aNbElements[]) {
+ LPSPropValue values = new SPropValue[aNbProperties];
+ if (!values) return FALSE;
+
+ for (ULONG i = 0; i < aNbProperties; i++) {
+ values[i].ulPropTag = aPropertyTags[i];
+ values[i].Value.MVbin.cValues = aNbElements[i];
+ values[i].Value.MVbin.lpbin = new SBinary[aNbElements[i]];
+
+ SBinary* currentValue = values[i].Value.MVbin.lpbin;
+ for (ULONG j = 0; j < aNbElements[i]; j++) {
+ currentValue->cb = aEntryIDs[i][j].mByteCount;
+ currentValue->lpb = reinterpret_cast<LPBYTE>(aEntryIDs[i][j].mEntryId);
+ currentValue++;
+ }
+ }
+ BOOL retCode = SetMAPIProperties(aDir, aObject, aNbProperties, values, true);
+ for (ULONG i = 0; i < aNbProperties; i++) {
+ delete[] values[i].Value.MVbin.lpbin;
+ }
+ delete[] values;
+ return retCode;
+}
+
+// This function, supposedly indicating whether a particular entry was
+// in a particular container, doesn't seem to work very well (has
+// a tendency to return TRUE even if we're talking to different containers...).
+BOOL nsAbWinHelper::TestOpenEntry(const nsMapiEntry& aContainer,
+ const nsMapiEntry& aEntry) {
+ nsMapiInterfaceWrapper<LPMAPICONTAINER> container;
+ nsMapiInterfaceWrapper<LPMAPIPROP> subObject;
+ ULONG objType = 0;
+
+ mLastError =
+ mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId,
+ &IID_IMAPIContainer, 0, &objType, container);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08lx.\n", mLastError));
+ return FALSE;
+ }
+ mLastError = container->OpenEntry(aEntry.mByteCount, aEntry.mEntryId, NULL, 0,
+ &objType, subObject);
+ return HR_SUCCEEDED(mLastError);
+}
+
+BOOL nsAbWinHelper::DeleteEntry(const nsMapiEntry& aContainer,
+ const nsMapiEntry& aEntry) {
+ nsMapiInterfaceWrapper<LPABCONT> container;
+ ULONG objType = 0;
+ SBinary entry;
+ SBinaryArray entryArray;
+
+ mLastError = mAddressBook->OpenEntry(aContainer.mByteCount,
+ aContainer.mEntryId, &IID_IABContainer,
+ MAPI_MODIFY, &objType, container);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open container %08lx.\n", mLastError));
+ return FALSE;
+ }
+ entry.cb = aEntry.mByteCount;
+ entry.lpb = reinterpret_cast<LPBYTE>(aEntry.mEntryId);
+ entryArray.cValues = 1;
+ entryArray.lpbin = &entry;
+ mLastError = container->DeleteEntries(&entryArray, 0);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot delete entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL nsAbWinHelper::GetDlMembersTag(IMAPIProp* aMsg, ULONG& aDlMembersTag,
+ ULONG& aDlMembersTagOneOff) {
+ const GUID guid = {0x00062004,
+ 0x0000,
+ 0x0000,
+ {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
+ MAPINAMEID nameID;
+ nameID.lpguid = (GUID*)&guid;
+ nameID.ulKind = MNID_ID;
+ LPSPropTagArray lppPropTags;
+ LPMAPINAMEID lpNameID[1] = {&nameID};
+
+ // Strangely requesting two tags at the same time doesn't appear to work,
+ // so request them separately.
+ // One should be able to set up `lpNameID` with two entries and get two
+ // tags returned in `lppPropTags`, but sadly the second one is always 0.
+ nameID.Kind.lID = 0x8055; // PidLidDistributionListMembers
+ mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get DL prop tag %08lx.\n", mLastError));
+ return FALSE;
+ }
+ aDlMembersTag = lppPropTags[0].aulPropTag[0] | PT_MV_BINARY;
+ mAddressFreeBuffer(lppPropTags);
+
+ nameID.Kind.lID = 0x8054; // PidLidDistributionListOneOffMembers
+ mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open DL prop tag (one off) %08lx.\n", mLastError));
+ return FALSE;
+ }
+ aDlMembersTagOneOff = lppPropTags[0].aulPropTag[0] | PT_MV_BINARY;
+ mAddressFreeBuffer(lppPropTags);
+
+ return TRUE;
+}
+
+BOOL nsAbWinHelper::GetDlNameTag(IMAPIProp* aMsg, ULONG& aDlNameTag) {
+ const GUID guid = {0x00062004,
+ 0x0000,
+ 0x0000,
+ {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
+ MAPINAMEID nameID;
+ nameID.lpguid = (GUID*)&guid;
+ nameID.ulKind = MNID_ID;
+ LPSPropTagArray lppPropTags;
+ LPMAPINAMEID lpNameID[1] = {&nameID};
+
+ nameID.Kind.lID = 0x8053; // PidLidDistributionListName
+ mLastError = aMsg->GetIDsFromNames(1, lpNameID, 0, &lppPropTags);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get DL prop tag %08lx.\n", mLastError));
+ return FALSE;
+ }
+ aDlNameTag = lppPropTags[0].aulPropTag[0] | PT_UNICODE;
+ mAddressFreeBuffer(lppPropTags);
+
+ return TRUE;
+}
+
+BOOL nsAbWinHelper::DeleteEntryfromDL(const nsMapiEntry& aTopDir,
+ const nsMapiEntry& aDistList,
+ const nsMapiEntry& aEntry) {
+ // First we need to open the distribution list to get the property tag.
+ ULONG dlMembersTag = 0;
+ ULONG dlMembersTagOnOff = 0;
+ {
+ // We do this in a block is `msg` going out of scope will release the
+ // object.
+ nsMapiInterfaceWrapper<LPMAPIPROP> msg;
+ mLastError = OpenMAPIObject(aTopDir, aDistList, true, 0, msg);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open DL entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+ if (!GetDlMembersTag(msg.Get(), dlMembersTag, dlMembersTagOnOff))
+ return FALSE;
+ }
+
+ // This will self-destruct when it goes out of scope.
+ nsMapiEntryArray dlMembers;
+ nsMapiEntryArray dlMembersOneOff;
+
+ // Turn IMailUser into IMessage/IPM.Contact.
+ // Check for magic provider GUID.
+ struct AbEntryId* abEntryId = (struct AbEntryId*)aEntry.mEntryId;
+ if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID,
+ CONTAB_PROVIDER_ID_LENGTH) != 0) {
+ PRINTF(("Cannot get to IMessage/IPM.Contact.\n"));
+ return FALSE;
+ }
+ ULONG contactIdLength = abEntryId->length;
+ LPENTRYID contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes));
+
+ ULONG tags[2] = {dlMembersTag, dlMembersTagOnOff};
+ nsMapiEntry* values[2];
+ ULONG counts[2];
+ if (!GetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) {
+ PRINTF(("Cannot get DL members.\n"));
+ return FALSE;
+ }
+ dlMembers.mEntries = values[0];
+ dlMembersOneOff.mEntries = values[1];
+ dlMembers.mNbEntries = counts[0];
+ dlMembersOneOff.mNbEntries = counts[1];
+
+ if (dlMembers.mNbEntries == 0) return FALSE;
+ if (dlMembers.mNbEntries != dlMembersOneOff.mNbEntries) {
+ PRINTF(("DL members and DL one off members have different length.\n"));
+ return FALSE;
+ }
+
+ ULONG result;
+ for (ULONG i = 0; i < dlMembers.mNbEntries; i++) {
+ struct DlEntryId* dlEntryId =
+ (struct DlEntryId*)dlMembers.mEntries[i].mEntryId;
+ if (memcmp(dlEntryId->provider, DL_PROVIDER_ID, DL_PROVIDER_ID_LENGTH) != 0)
+ continue;
+ mLastError = mAddressSession->CompareEntryIDs(
+ contactIdLength, contactId,
+ dlMembers.mEntries[i].mByteCount - sizeof(struct DlEntryId),
+ reinterpret_cast<LPENTRYID>(dlEntryId->idBytes), 0, &result);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("CompareEntryIDs failed with %08lx (DeleteEntryfromDL()).\n",
+ mLastError));
+ }
+ if (result) {
+ PRINTF(("Found card to be deleted at position %lu.\n", i));
+
+ // Kill/free entry and shuffle remaining cards down.
+ dlMembers.mEntries[i].Assign(0, NULL);
+ dlMembersOneOff.mEntries[i].Assign(0, NULL);
+ for (ULONG j = i + 1; j < dlMembers.mNbEntries; j++) {
+ nsMapiEntry::Move(dlMembers.mEntries[j - 1], dlMembers.mEntries[j]);
+ nsMapiEntry::Move(dlMembersOneOff.mEntries[j - 1],
+ dlMembersOneOff.mEntries[j]);
+ }
+ dlMembers.mNbEntries--;
+ dlMembersOneOff.mNbEntries--;
+
+ counts[0] = dlMembers.mNbEntries;
+ counts[1] = dlMembersOneOff.mNbEntries;
+ if (counts[0] >= 1) {
+ if (!SetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) {
+ PRINTF(("Cannot set DL members.\n"));
+ return FALSE;
+ }
+ } else {
+ static const SizedSPropTagArray(2, properties) = {
+ 2, {dlMembersTag, dlMembersTagOnOff}};
+ if (!DeleteMAPIProperties(aTopDir, aDistList,
+ (LPSPropTagArray)&properties, true)) {
+ PRINTF(("Cannot delete DL members.\n"));
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+BOOL nsAbWinHelper::AddEntryToDL(const nsMapiEntry& aTopDir,
+ const nsMapiEntry& aDistList,
+ const nsMapiEntry& aEntry,
+ const wchar_t* aDisplay,
+ const wchar_t* aEmail) {
+ // First we need to open the distribution list to get the property tag.
+ ULONG dlMembersTag = 0;
+ ULONG dlMembersTagOnOff = 0;
+ {
+ // We do this in a block is `msg` going out of scope will release the
+ // object.
+ nsMapiInterfaceWrapper<LPMAPIPROP> msg;
+ mLastError = OpenMAPIObject(aTopDir, aDistList, true, 0, msg);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open DL entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+ if (!GetDlMembersTag(msg.Get(), dlMembersTag, dlMembersTagOnOff))
+ return FALSE;
+ }
+
+ // This will self-destruct when it goes out of scope.
+ nsMapiEntryArray dlMembers;
+ nsMapiEntryArray dlMembersOneOff;
+
+ // Turn IMailUser into IMessage/IPM.Contact.
+ // Check for magic provider GUID.
+ struct AbEntryId* abEntryId = (struct AbEntryId*)aEntry.mEntryId;
+ if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID,
+ CONTAB_PROVIDER_ID_LENGTH) != 0) {
+ PRINTF(("Cannot get to IMessage/IPM.Contact.\n"));
+ return FALSE;
+ }
+ ULONG contactIdLength = abEntryId->length;
+ LPENTRYID contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes));
+
+ ULONG tags[2] = {dlMembersTag, dlMembersTagOnOff};
+ nsMapiEntry* values[2];
+ ULONG counts[2];
+ // We ask for and array one entry larger.
+ if (!GetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts, true)) {
+ // If the properties aren't there, the list has no entries so far.
+ values[0] = new nsMapiEntry[1];
+ values[1] = new nsMapiEntry[1];
+ counts[0] = counts[1] = 0;
+ }
+ dlMembers.mEntries = values[0];
+ dlMembersOneOff.mEntries = values[1];
+ dlMembers.mNbEntries = counts[0];
+ dlMembersOneOff.mNbEntries = counts[1];
+
+ if (dlMembers.mNbEntries != dlMembersOneOff.mNbEntries) {
+ PRINTF(("DL members and DL one off members have different length.\n"));
+ return FALSE;
+ }
+
+ // Append a new entry at the end. The array is already large enough.
+
+ // Construct a distribution list entry based on a contact.
+ size_t dlEntryIdLength = sizeof(struct DlEntryId) + contactIdLength;
+ struct DlEntryId* dlEntryId = (DlEntryId*)moz_xmalloc(dlEntryIdLength);
+ memset(dlEntryId->flags, 0, DLENTRY_FLAGS_LENGTH);
+ memcpy(dlEntryId->provider, DL_PROVIDER_ID, DL_PROVIDER_ID_LENGTH);
+ // See documentation referenced above: 0xC3 = 0x80 | 0x40 | 0x03.
+ memset(dlEntryId->type, 0xC3, DLENTRY_TYPE_LENGTH);
+ memcpy(dlEntryId->idBytes, contactId, contactIdLength);
+ dlMembers.mEntries[dlMembers.mNbEntries].Assign(
+ dlEntryIdLength, reinterpret_cast<LPENTRYID>(dlEntryId));
+
+ // Construct a one-off entry.
+ size_t dlEntryIdOoLength = sizeof(struct DlEntryIdOo) +
+ 2 * (wcslen(aDisplay) + 4 + wcslen(aEmail) + 3);
+ struct DlEntryIdOo* dlEntryIdOo =
+ (DlEntryIdOo*)moz_xmalloc(dlEntryIdOoLength);
+ memset(dlEntryIdOo->flags, 0, DLENTRY_OO_FLAGS_LENGTH);
+ memcpy(dlEntryIdOo->provider, DL_OO_PROVIDER_ID, DL_OO_PROVIDER_ID_LENGTH);
+ dlEntryIdOo->versionAndBits = MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO;
+
+ // Populate the variable part. A bit of stone-age programming ;-)
+ size_t length = 2 * (wcslen(aDisplay) + 1);
+ memcpy(dlEntryIdOo->variable, aDisplay, length);
+ size_t offset = length;
+
+ length = 2 * (4 + 1);
+ memcpy(dlEntryIdOo->variable + offset, L"SMTP", length);
+ offset += length;
+
+ length = 2 * (wcslen(aEmail) + 1);
+ memcpy(dlEntryIdOo->variable + offset, aEmail, length);
+
+ dlMembersOneOff.mEntries[dlMembersOneOff.mNbEntries].Assign(
+ dlEntryIdOoLength, reinterpret_cast<LPENTRYID>(dlEntryIdOo));
+
+ free(dlEntryId);
+ free(dlEntryIdOo);
+
+ dlMembers.mNbEntries++;
+ dlMembersOneOff.mNbEntries++;
+
+ counts[0] = dlMembers.mNbEntries;
+ counts[1] = dlMembersOneOff.mNbEntries;
+ if (!SetPropertiesMVBin(aTopDir, aDistList, tags, 2, values, counts)) {
+ PRINTF(("Cannot set DL members.\n"));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL nsAbWinHelper::SetPropertyUString(const nsMapiEntry& aObject,
+ ULONG aPropertyTag,
+ const char16_t* aValue) {
+ SPropValue value;
+ nsAutoCString alternativeValue;
+
+ value.ulPropTag = aPropertyTag;
+ if (PROP_TYPE(aPropertyTag) == PT_UNICODE) {
+ value.Value.lpszW =
+ reinterpret_cast<wchar_t*>(const_cast<char16_t*>(aValue));
+ } else if (PROP_TYPE(aPropertyTag) == PT_STRING8) {
+ alternativeValue = NS_LossyConvertUTF16toASCII(aValue);
+ value.Value.lpszA = const_cast<char*>(alternativeValue.get());
+ } else {
+ PRINTF(("Property %08lx is not a string.\n", aPropertyTag));
+ return FALSE;
+ }
+ nsMapiEntry nullEntry;
+ return SetMAPIProperties(nullEntry, aObject, 1, &value, false);
+}
+
+BOOL nsAbWinHelper::SetPropertiesUString(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[],
+ ULONG aNbProperties,
+ nsString aValues[]) {
+ LPSPropValue values = new SPropValue[aNbProperties];
+ if (!values) return FALSE;
+
+ ULONG currentValue = 0;
+ nsAutoCString alternativeValue;
+ BOOL retCode = TRUE;
+
+ for (ULONG i = 0; i < aNbProperties; ++i) {
+ values[currentValue].ulPropTag = aPropertyTags[i];
+ if (PROP_TYPE(aPropertyTags[i]) == PT_UNICODE) {
+ const wchar_t* value = aValues[i].get();
+ values[currentValue++].Value.lpszW = const_cast<wchar_t*>(value);
+ } else if (PROP_TYPE(aPropertyTags[i]) == PT_STRING8) {
+ LossyCopyUTF16toASCII(aValues[i], alternativeValue);
+ char* av = strdup(alternativeValue.get());
+ if (!av) {
+ retCode = FALSE;
+ break;
+ }
+ values[currentValue++].Value.lpszA = av;
+ }
+ }
+ if (retCode)
+ retCode = SetMAPIProperties(aDir, aObject, currentValue, values, true);
+ for (ULONG i = 0; i < currentValue; ++i) {
+ if (PROP_TYPE(aPropertyTags[i]) == PT_STRING8) {
+ free(values[i].Value.lpszA);
+ }
+ }
+ delete[] values;
+ return retCode;
+}
+
+BOOL nsAbWinHelper::SetPropertyDate(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ bool fromContact, ULONG aPropertyTag,
+ WORD aYear, WORD aMonth, WORD aDay) {
+ SPropValue value;
+
+ value.ulPropTag = aPropertyTag;
+ if (PROP_TYPE(aPropertyTag) == PT_SYSTIME) {
+ SYSTEMTIME readableTime;
+
+ readableTime.wYear = aYear;
+ readableTime.wMonth = aMonth;
+ readableTime.wDay = aDay;
+ readableTime.wDayOfWeek = 0;
+ readableTime.wHour = 0;
+ readableTime.wMinute = 0;
+ readableTime.wSecond = 0;
+ readableTime.wMilliseconds = 0;
+ if (SystemTimeToFileTime(&readableTime, &value.Value.ft)) {
+ return SetMAPIProperties(aDir, aObject, 1, &value, fromContact);
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL nsAbWinHelper::CreateEntryInternal(const nsMapiEntry& aParent,
+ nsMapiEntry& aNewEntry,
+ const char* aContactClass,
+ const wchar_t* aName) {
+ // We create an IPM.Contact or IPM.DistList message in the contacts folder.
+ // To find that folder, we look for our `aParent` in the hierarchy table
+ // and use the matching `PR_CONTAB_FOLDER_ENTRYID` for the folder.
+ nsMapiInterfaceWrapper<LPABCONT> rootFolder;
+ nsMapiInterfaceWrapper<LPMAPITABLE> folders;
+ ULONG objType = 0;
+ mLastError = mAddressBook->OpenEntry(0, NULL, NULL, 0, &objType, rootFolder);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open root %08lx (creating new entry).\n", mLastError));
+ return FALSE;
+ }
+ mLastError = rootFolder->GetHierarchyTable(CONVENIENT_DEPTH, folders);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get hierarchy %08lx (creating new entry).\n", mLastError));
+ return FALSE;
+ }
+
+ // Request `PR_ENTRYID` and `PR_CONTAB_FOLDER_ENTRYID`.
+#define PR_CONTAB_FOLDER_ENTRYID PROP_TAG(PT_BINARY, 0x6610)
+ static const SizedSPropTagArray(2, properties) = {
+ 2, {PR_ENTRYID, PR_CONTAB_FOLDER_ENTRYID}};
+ mLastError = folders->SetColumns((LPSPropTagArray)&properties, 0);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set columns %08lx (creating new entry).\n", mLastError));
+ return FALSE;
+ }
+
+ ULONG rowCount = 0;
+ bool found = false;
+ nsMapiEntry conTab;
+ mLastError = folders->GetRowCount(0, &rowCount);
+ if (HR_SUCCEEDED(mLastError)) {
+ do {
+ LPSRowSet rowSet = NULL;
+
+ rowCount = 0;
+ mLastError = folders->QueryRows(1, 0, &rowSet);
+ if (HR_SUCCEEDED(mLastError)) {
+ rowCount = rowSet->cRows;
+ if (rowCount > 0) {
+ ULONG result;
+ // Get entry ID from row and compare.
+ SPropValue& colValue = rowSet->aRow->lpProps[0];
+
+ mLastError = mAddressSession->CompareEntryIDs(
+ aParent.mByteCount, aParent.mEntryId, colValue.Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(colValue.Value.bin.lpb), 0, &result);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("CompareEntryIDs failed with %08lx (creating new entry).\n",
+ mLastError));
+ }
+ if (result) {
+ SPropValue& conTabValue = rowSet->aRow->lpProps[1];
+ conTab.Assign(
+ conTabValue.Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(conTabValue.Value.bin.lpb));
+ found = true;
+ break;
+ }
+ }
+ MyFreeProws(rowSet);
+ } else {
+ PRINTF(("Cannot query rows %08lx (creating new entry).\n", mLastError));
+ }
+ } while (rowCount > 0);
+ }
+ if (HR_FAILED(mLastError)) return HR_SUCCEEDED(mLastError);
+
+ if (!found) {
+ PRINTF(("Cannot find folder for contact in hierarchy table.\n"));
+ return FALSE;
+ }
+
+ // Open store and contact folder.
+ PRINTF(("Found contact folder associated with AB container.\n"));
+ nsMapiEntry storeEntry;
+ // Get the entry ID of the related store. This won't work for the
+ // Global Address List (GAL) since it doesn't provide contacts from a
+ // local store.
+ if (!GetPropertyBin(aParent, PR_STORE_ENTRYID, storeEntry)) {
+ PRINTF(("Cannot get PR_STORE_ENTRYID, likely not a local AB.\n"));
+ return FALSE;
+ }
+ nsMapiInterfaceWrapper<LPMDB> store;
+ mLastError = mAddressSession->OpenMsgStore(
+ 0, storeEntry.mByteCount, storeEntry.mEntryId, NULL, 0, store);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open MAPI message store %08lx.\n", mLastError));
+ return FALSE;
+ }
+ nsMapiInterfaceWrapper<LPMAPIFOLDER> contactFolder;
+ mLastError =
+ store->OpenEntry(conTab.mByteCount, conTab.mEntryId, &IID_IMAPIFolder,
+ MAPI_MODIFY, &objType, contactFolder);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open contact folder %08lx.\n", mLastError));
+ return FALSE;
+ }
+
+ // Crazy as it seems, contacts and distribution lists are stored as message.
+ nsMapiInterfaceWrapper<LPMESSAGE> newEntry;
+ mLastError = contactFolder->CreateMessage(&IID_IMessage, 0, newEntry);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot create new entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+
+ SPropValue propValue;
+ LPSPropProblemArray problems = NULL;
+ propValue.ulPropTag = PR_MESSAGE_CLASS_A;
+ propValue.Value.lpszA = const_cast<char*>(aContactClass);
+ mLastError = newEntry->SetProps(1, &propValue, &problems);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set message class %08lx.\n", mLastError));
+ return FALSE;
+ }
+
+ if (strcmp(aContactClass, "IPM.DistList") == 0) {
+ // Set distribution list name.
+ problems = NULL;
+ GetDlNameTag(newEntry.Get(), propValue.ulPropTag);
+ propValue.Value.lpszW = const_cast<wchar_t*>(aName);
+ mLastError = newEntry->SetProps(1, &propValue, &problems);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set DL name %08lx.\n", mLastError));
+ return FALSE;
+ }
+ }
+
+ mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit new entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+
+ // Get the entry ID of the contact (IMessage).
+ SPropTagArray property;
+ LPSPropValue value = NULL;
+ ULONG valueCount = 0;
+ property.cValues = 1;
+ property.aulPropTag[0] = PR_ENTRYID;
+ mLastError = newEntry->GetProps(&property, 0, &valueCount, &value);
+ if (HR_FAILED(mLastError) || valueCount != 1) {
+ PRINTF(("Cannot get entry id %08lx.\n", mLastError));
+ return FALSE;
+ }
+
+ // Construct the entry ID of the related address book entry (IMailUser).
+ AbEntryId* abEntryId =
+ (AbEntryId*)moz_xmalloc(sizeof(AbEntryId) + value->Value.bin.cb);
+ if (!abEntryId) return FALSE;
+ memset(abEntryId, 0, 4); // Null out the flags.
+ memcpy(abEntryId->provider, CONTAB_PROVIDER_ID, CONTAB_PROVIDER_ID_LENGTH);
+ memcpy(abEntryId->version, ABENTRY_VERSION, ABENTRY_VERSION_LENGTH);
+ memcpy(abEntryId->type, ABENTRY_TYPE, ABENTRY_TYPE_LENGTH);
+ abEntryId->index = 0;
+ abEntryId->length = value->Value.bin.cb;
+ memcpy(abEntryId->idBytes, value->Value.bin.lpb, abEntryId->length);
+
+ aNewEntry.Assign(sizeof(AbEntryId) + value->Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(abEntryId));
+ FreeBuffer(value);
+
+ // We need to set a display name otherwise MAPI is really unhappy internally.
+ SPropValue displayName;
+ displayName.ulPropTag = PR_DISPLAY_NAME_W;
+ displayName.Value.lpszW = const_cast<wchar_t*>(aName);
+ nsMapiInterfaceWrapper<LPMAPIPROP> object;
+ mLastError =
+ mAddressBook->OpenEntry(aNewEntry.mByteCount, aNewEntry.mEntryId,
+ &IID_IMAPIProp, MAPI_MODIFY, &objType, object);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open newly created AB entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+ mLastError = object->SetProps(1, &displayName, &problems);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set display name %08lx.\n", mLastError));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL nsAbWinHelper::CreateEntry(const nsMapiEntry& aParent,
+ nsMapiEntry& aNewEntry) {
+ nsAutoString tempName(L"" kDummyDisplayName);
+ tempName.AppendInt(sEntryCounter++);
+ return CreateEntryInternal(aParent, aNewEntry, "IPM.Contact", tempName.get());
+}
+
+BOOL nsAbWinHelper::CreateDistList(const nsMapiEntry& aParent,
+ nsMapiEntry& aNewEntry,
+ const wchar_t* aName) {
+ return CreateEntryInternal(aParent, aNewEntry, "IPM.DistList", aName);
+}
+
+enum {
+ ContentsColumnEntryId = 0,
+ ContentsColumnObjectType,
+ ContentsColumnsSize
+};
+
+static const SizedSPropTagArray(ContentsColumnsSize, ContentsColumns) = {
+ ContentsColumnsSize, {PR_ENTRYID, PR_OBJECT_TYPE}};
+
+BOOL nsAbWinHelper::GetContents(const nsMapiEntry& aParent,
+ LPSRestriction aRestriction,
+ nsMapiEntry** aList, ULONG& aNbElements,
+ ULONG aMapiType) {
+ if (aList != NULL) {
+ *aList = NULL;
+ }
+ aNbElements = 0;
+ nsMapiInterfaceWrapper<LPMAPICONTAINER> parent;
+ nsMapiInterfaceWrapper<LPMAPITABLE> contents;
+ ULONG objType = 0;
+ ULONG rowCount = 0;
+
+ mLastError =
+ mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId,
+ &IID_IMAPIContainer, 0, &objType, parent);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open parent %08lx.\n", mLastError));
+ return FALSE;
+ }
+ // Historic comment: May be relevant in the future.
+ // WAB removed in bug 1687132.
+ // Here, flags for WAB and MAPI could be different, so this works
+ // only as long as we don't want to use any flag in GetContentsTable
+ mLastError = parent->GetContentsTable(0, contents);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get contents %08lx.\n", mLastError));
+ return FALSE;
+ }
+ if (aRestriction != NULL) {
+ mLastError = contents->Restrict(aRestriction, 0);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set restriction %08lx.\n", mLastError));
+ return FALSE;
+ }
+ }
+ mLastError = contents->SetColumns((LPSPropTagArray)&ContentsColumns, 0);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot set columns %08lx.\n", mLastError));
+ return FALSE;
+ }
+ mLastError = contents->GetRowCount(0, &rowCount);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get result count %08lx.\n", mLastError));
+ return FALSE;
+ }
+ if (aList != NULL) {
+ *aList = new nsMapiEntry[rowCount];
+ }
+ aNbElements = 0;
+ do {
+ LPSRowSet rowSet = NULL;
+
+ rowCount = 0;
+ mLastError = contents->QueryRows(1, 0, &rowSet);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot query rows %08lx.\n", mLastError));
+ return FALSE;
+ }
+ rowCount = rowSet->cRows;
+ if (rowCount > 0 &&
+ (aMapiType == 0 ||
+ rowSet->aRow->lpProps[ContentsColumnObjectType].Value.ul ==
+ aMapiType)) {
+ if (aList != NULL) {
+ nsMapiEntry& current = (*aList)[aNbElements];
+ SPropValue& currentValue = rowSet->aRow->lpProps[ContentsColumnEntryId];
+
+ // Sometimes Outlooks spits the dummy here :-(
+ // That is meant to be a byte count and NOT an error code of 0x8004010F.
+ // We gloss over it.
+ if (currentValue.Value.bin.cb == (ULONG)MAPI_E_NOT_FOUND ||
+ currentValue.Value.bin.lpb == NULL) {
+ PRINTF(("Error fetching rows.\n"));
+ return TRUE;
+ }
+ current.Assign(currentValue.Value.bin.cb,
+ reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb));
+ }
+ ++aNbElements;
+ }
+ MyFreeProws(rowSet);
+ } while (rowCount > 0);
+ return TRUE;
+}
+
+HRESULT nsAbWinHelper::OpenMAPIObject(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ bool aFromContact, ULONG aFlags,
+ LPUNKNOWN* aResult) {
+ nsMapiEntry storeEntry;
+ ULONG contactIdLength = 0;
+ LPENTRYID contactId = NULL;
+ if (aFromContact) {
+ // Get the entry ID of the related store. This won't work for the
+ // Global Address List (GAL) since it doesn't provide contacts from a
+ // local store.
+ if (!GetPropertyBin(aDir, PR_STORE_ENTRYID, storeEntry)) {
+ PRINTF(("Cannot get PR_STORE_ENTRYID, likely not a local AB.\n"));
+ aFromContact = false;
+ }
+ // Check for magic provider GUID.
+ struct AbEntryId* abEntryId = (struct AbEntryId*)aObject.mEntryId;
+ if (memcmp(abEntryId->provider, CONTAB_PROVIDER_ID,
+ CONTAB_PROVIDER_ID_LENGTH) != 0) {
+ aFromContact = false;
+ } else {
+ contactIdLength = abEntryId->length;
+ contactId = reinterpret_cast<LPENTRYID>(&(abEntryId->idBytes));
+ }
+ }
+
+ ULONG objType = 0;
+ if (aFromContact) {
+ // Open the store.
+ HRESULT retCode;
+ nsMapiInterfaceWrapper<LPMDB> store;
+ retCode = mAddressSession->OpenMsgStore(
+ 0, storeEntry.mByteCount, storeEntry.mEntryId, NULL, 0, store);
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot open MAPI message store %08lx.\n", retCode));
+ return retCode;
+ }
+ // Open the contact object.
+ retCode = store->OpenEntry(contactIdLength, contactId, &IID_IMessage, 0,
+ &objType, aResult);
+ return retCode;
+ } else {
+ // Open the address book object.
+ return mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId,
+ &IID_IMAPIProp, 0, &objType, aResult);
+ }
+}
+
+BOOL nsAbWinHelper::GetMAPIProperties(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[],
+ ULONG aNbProperties, LPSPropValue& aValue,
+ ULONG& aValueCount, bool aFromContact) {
+ nsMapiInterfaceWrapper<LPMAPIPROP> object;
+ LPSPropTagArray properties = NULL;
+
+ mLastError = OpenMAPIObject(aDir, aObject, aFromContact, 0, object);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+ AllocateBuffer(CbNewSPropTagArray(aNbProperties),
+ reinterpret_cast<void**>(&properties));
+ properties->cValues = aNbProperties;
+ for (ULONG i = 0; i < aNbProperties; ++i) {
+ properties->aulPropTag[i] = aPropertyTags[i];
+ }
+ mLastError = object->GetProps(properties, 0, &aValueCount, &aValue);
+ FreeBuffer(properties);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot get props %08lx.\n", mLastError));
+ }
+ return HR_SUCCEEDED(mLastError);
+}
+
+BOOL nsAbWinHelper::SetMAPIProperties(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ ULONG aNbProperties,
+ const LPSPropValue& aValues,
+ bool aFromContact) {
+ nsMapiInterfaceWrapper<LPMAPIPROP> object;
+ LPSPropProblemArray problems = NULL;
+
+ mLastError = OpenMAPIObject(aDir, aObject, aFromContact, MAPI_MODIFY, object);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+ mLastError = object->SetProps(aNbProperties, aValues, &problems);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot update the object %08lx.\n", mLastError));
+ return FALSE;
+ }
+ if (problems != NULL) {
+ for (ULONG i = 0; i < problems->cProblem; ++i) {
+ PRINTF(("Problem %lu: index %lu code %08lx.\n", i,
+ problems->aProblem[i].ulIndex, problems->aProblem[i].scode));
+ }
+ mAddressFreeBuffer(problems);
+ }
+ mLastError = object->SaveChanges(0);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit changes %08lx.\n", mLastError));
+ }
+ return HR_SUCCEEDED(mLastError);
+}
+
+BOOL nsAbWinHelper::DeleteMAPIProperties(const nsMapiEntry& aDir,
+ const nsMapiEntry& aObject,
+ const LPSPropTagArray aProps,
+ bool aFromContact) {
+ nsMapiInterfaceWrapper<LPMAPIPROP> object;
+ LPSPropProblemArray problems = NULL;
+
+ mLastError = OpenMAPIObject(aDir, aObject, aFromContact, MAPI_MODIFY, object);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot open entry %08lx.\n", mLastError));
+ return FALSE;
+ }
+ mLastError = object->DeleteProps(aProps, &problems);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot update the object (DeleteProps) %08lx.\n", mLastError));
+ return FALSE;
+ }
+ if (problems != NULL) {
+ for (ULONG i = 0; i < problems->cProblem; ++i) {
+ PRINTF(("Problem %lu: index %lu code %08lx.\n", i,
+ problems->aProblem[i].ulIndex, problems->aProblem[i].scode));
+ }
+ mAddressFreeBuffer(problems);
+ }
+ mLastError = object->SaveChanges(0);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("Cannot commit changes %08lx.\n", mLastError));
+ }
+ return HR_SUCCEEDED(mLastError);
+}
+
+void nsAbWinHelper::MyFreeProws(LPSRowSet aRowset) {
+ if (aRowset == NULL) {
+ return;
+ }
+ ULONG i = 0;
+
+ for (i = 0; i < aRowset->cRows; ++i) {
+ FreeBuffer(aRowset->aRow[i].lpProps);
+ }
+ FreeBuffer(aRowset);
+}
+
+nsAbWinHelperGuard::nsAbWinHelperGuard() : mHelper(NULL) {
+ mHelper = new nsMapiAddressBook;
+}
+
+nsAbWinHelperGuard::~nsAbWinHelperGuard(void) { delete mHelper; }
+
+void makeEntryIdFromURI(const char* aScheme, const char* aUri,
+ nsCString& aEntry) {
+ aEntry.Truncate();
+ uint32_t schemeLength = strlen(aScheme);
+
+ if (strncmp(aUri, aScheme, schemeLength) == 0) {
+ // Assign string from position `schemeLength`.
+ aEntry = aUri + schemeLength;
+
+ // Now strip the parent directory before the /.
+ int ind = aEntry.FindChar('/');
+ if (ind != kNotFound) {
+ aEntry = Substring(aEntry, ind + 1);
+ }
+ }
+}
+
+bool nsAbWinHelper::CompareEntryIDs(nsCString& aEntryID1,
+ nsCString& aEntryID2) {
+ ULONG result;
+ nsMapiEntry e1;
+ nsMapiEntry e2;
+ e1.Assign(aEntryID1);
+ e2.Assign(aEntryID2);
+ mLastError = mAddressSession->CompareEntryIDs(
+ e1.mByteCount, e1.mEntryId, e2.mByteCount, e2.mEntryId, 0, &result);
+ if (HR_FAILED(mLastError)) {
+ PRINTF(("CompareEntryIDs failed with %08lx (CompareEntryIDs()).\n",
+ mLastError));
+ return false;
+ }
+ return result ? true : false;
+}
diff --git a/comm/mailnews/addrbook/src/nsAbWinHelper.h b/comm/mailnews/addrbook/src/nsAbWinHelper.h
new file mode 100644
index 0000000000..5411ae94fd
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsAbWinHelper.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+#ifndef nsAbWinHelper_h___
+#define nsAbWinHelper_h___
+
+#include <windows.h>
+#include "../../mapi/include/mapix.h"
+
+#include "nsString.h"
+#include "mozilla/StaticMutex.h"
+
+#define kOutlookDirectoryScheme "moz-aboutlookdirectory:///"
+#define kOutlookCardScheme "moz-aboutlookcard:///"
+#define kDummyDisplayName "__MailUser__"
+
+struct nsMapiEntry {
+ // Can't be assigned since it would double up the reference in `mEntryId`.
+ nsMapiEntry& operator=(nsMapiEntry&) = delete;
+ ULONG mByteCount;
+ LPENTRYID mEntryId;
+
+ nsMapiEntry(void);
+ ~nsMapiEntry(void);
+ nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId);
+
+ static void Move(nsMapiEntry& target, nsMapiEntry& source);
+ void Assign(ULONG aByteCount, LPENTRYID aEntryId);
+ void Assign(const nsCString& aString);
+ void ToString(nsCString& aString) const;
+ void Dump(void) const;
+};
+
+struct nsMapiEntryArray {
+ nsMapiEntry* mEntries;
+ ULONG mNbEntries;
+
+ nsMapiEntryArray(void);
+ ~nsMapiEntryArray(void);
+
+ void CleanUp(void);
+};
+
+class nsAbWinHelper {
+ public:
+ nsAbWinHelper(void);
+ virtual ~nsAbWinHelper(void);
+
+ // Get the top address books
+ BOOL GetFolders(nsMapiEntryArray& aFolders);
+ // Get a list of entries for cards/mailing lists in a folder/mailing list
+ BOOL GetCards(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntryArray& aCards);
+ // Get a list of mailing lists in a folder
+ BOOL GetNodes(const nsMapiEntry& aParent, nsMapiEntryArray& aNodes);
+ // Get the number of cards/mailing lists in a folder/mailing list
+ BOOL GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards);
+ // Access last MAPI error
+ HRESULT LastError(void) const { return mLastError; }
+ // Get the value of a MAPI property of type string
+ BOOL GetPropertyString(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ nsCString& aValue);
+ // Same as previous, but string is returned as unicode.
+ BOOL GetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ nsString& aValue);
+ // Get multiple string MAPI properties in one call.
+ // Retrieves the properties from the associated contact object (IMessage)
+ // not the address book entry (IMailUser).
+ BOOL GetPropertiesUString(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[], ULONG aNbProperties,
+ nsString aValues[], bool aSuccess[]);
+ // Get the value of a MAPI property of type SYSTIME
+ BOOL GetPropertyDate(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ bool fromContact, ULONG aPropertyTag, WORD& aYear,
+ WORD& aMonth, WORD& aDay);
+ // Get the value of a MAPI property of type LONG
+ BOOL GetPropertyLong(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ ULONG& aValue);
+ // Get the value of a MAPI property of type BIN
+ BOOL GetPropertyBin(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ nsMapiEntry& aValue);
+ // Get the values of a multiple MAPI properties of type MV BIN
+ BOOL GetPropertiesMVBin(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[], ULONG aNbProperties,
+ nsMapiEntry* aEntryIDs[], ULONG aNbElements[],
+ bool aAllocateMore = false);
+ // Set the value of a MAPI property of type MV BIN
+ BOOL SetPropertiesMVBin(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[], ULONG aNbProperties,
+ nsMapiEntry* aEntryIDs[], ULONG aNbElements[]);
+ // Tests if a container contains an entry
+ BOOL TestOpenEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry);
+ // Delete an entry in the address book
+ BOOL DeleteEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry);
+ // Delete an entry from an Outlook distribution list.
+ BOOL DeleteEntryfromDL(const nsMapiEntry& aTopDir,
+ const nsMapiEntry& aDistList,
+ const nsMapiEntry& aEntry);
+ // Add an entry to an Outlook distribution list.
+ BOOL AddEntryToDL(const nsMapiEntry& aTopDir, const nsMapiEntry& aDistList,
+ const nsMapiEntry& aEntry, const wchar_t* aDisplay,
+ const wchar_t* aEmail);
+ // Set the value of a MAPI property of type string in unicode
+ BOOL SetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag,
+ const char16_t* aValue);
+ // Same as previous, but with a bunch of properties in one call.
+ // Sets the properties on the associated contact object (IMessage)
+ // not the address book entry (IMailUser).
+ BOOL SetPropertiesUString(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[], ULONG aNbProperties,
+ nsString aValues[]);
+ // Set the value of a MAPI property of type SYSTIME
+ BOOL SetPropertyDate(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ bool fromContact, ULONG aPropertyTag, WORD aYear,
+ WORD aMonth, WORD aDay);
+ // Create entry in the address book
+ BOOL CreateEntry(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry);
+ // Create a distribution list in the address book
+ BOOL CreateDistList(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry,
+ const wchar_t* aName);
+ // Create entry worker
+ BOOL CreateEntryInternal(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry,
+ const char* aContactClass, const wchar_t* aName);
+ // Is the helper correctly initialised?
+ BOOL IsOK(void) const { return mAddressBook != NULL; }
+ // Helper to get distribution list members tag.
+ BOOL GetDlMembersTag(IMAPIProp* aMsg, ULONG& aDlMembersTag,
+ ULONG& aDlMembersTagOneOff);
+ // Helper to get distribution list name tag.
+ BOOL GetDlNameTag(IMAPIProp* aMsg, ULONG& aDlNameTag);
+ // Helper to compare entry IDs.
+ bool CompareEntryIDs(nsCString& aEntryID1, nsCString& aEntryID2);
+
+ protected:
+ HRESULT mLastError;
+ LPADRBOOK mAddressBook;
+ LPMAPISESSION mAddressSession;
+ LPMAPIFREEBUFFER mAddressFreeBuffer;
+ static uint32_t sEntryCounter;
+ static mozilla::StaticMutex sMutex;
+
+ // Retrieve the contents of a container, with an optional restriction
+ BOOL GetContents(const nsMapiEntry& aParent, LPSRestriction aRestriction,
+ nsMapiEntry** aList, ULONG& aNbElements, ULONG aMapiType);
+ // Retrieve the values of a set of properties on a MAPI object
+ BOOL GetMAPIProperties(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ const ULONG aPropertyTags[], ULONG aNbProperties,
+ LPSPropValue& aValues, ULONG& aValueCount,
+ bool aFromContact = false);
+ // Set the values of a set of properties on a MAPI object
+ BOOL SetMAPIProperties(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ ULONG aNbProperties, const LPSPropValue& aValues,
+ bool aFromContact);
+ // Delete a set of properties on a MAPI object
+ BOOL DeleteMAPIProperties(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ const LPSPropTagArray aProps, bool aFromContact);
+ HRESULT OpenMAPIObject(const nsMapiEntry& aDir, const nsMapiEntry& aObject,
+ bool aFromContact, ULONG aFlags, LPUNKNOWN* aResult);
+ // Clean-up a rowset returned by QueryRows
+ void MyFreeProws(LPSRowSet aSet);
+ // Allocation of a buffer for transmission to interfaces
+ virtual void AllocateBuffer(ULONG aByteCount, LPVOID* aBuffer) = 0;
+ // Destruction of a buffer provided by the interfaces
+ virtual void FreeBuffer(LPVOID aBuffer) = 0;
+
+ private:
+};
+
+class nsAbWinHelperGuard {
+ public:
+ explicit nsAbWinHelperGuard();
+ ~nsAbWinHelperGuard(void);
+
+ nsAbWinHelper* operator->(void) { return mHelper; }
+
+ private:
+ nsAbWinHelper* mHelper;
+};
+
+void makeEntryIdFromURI(const char* aScheme, const char* aUri,
+ nsCString& aEntry);
+#endif // nsAbWinHelper_h___
diff --git a/comm/mailnews/addrbook/src/nsLDAPURL.cpp b/comm/mailnews/addrbook/src/nsLDAPURL.cpp
new file mode 100644
index 0000000000..90e4e370e4
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsLDAPURL.cpp
@@ -0,0 +1,591 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "nsLDAPURL.h"
+#include "netCore.h"
+#include "plstr.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIStandardURL.h"
+#include "nsMsgUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/Encoding.h"
+
+// The two schemes we support, LDAP and LDAPS
+//
+constexpr auto LDAP_SCHEME = "ldap"_ns;
+constexpr auto LDAP_SSL_SCHEME = "ldaps"_ns;
+
+NS_IMPL_ISUPPORTS(nsLDAPURL, nsILDAPURL, nsIURI)
+
+nsLDAPURL::nsLDAPURL() : mScope(SCOPE_BASE), mOptions(0) {}
+
+nsLDAPURL::~nsLDAPURL() {}
+
+nsresult nsLDAPURL::Init(uint32_t aUrlType, int32_t aDefaultPort,
+ const nsACString& aSpec, const char* aOriginCharset,
+ nsIURI* aBaseURI) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_STANDARD, aDefaultPort,
+ PromiseFlatCString(aSpec), aOriginCharset, aBaseURI, nullptr)
+ .Finalize(mBaseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now get the spec from the mBaseURL in case it was a relative one
+ nsCString spec;
+ rv = mBaseURL->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SetSpecInternal(spec);
+}
+
+void nsLDAPURL::GetPathInternal(nsCString& aPath) {
+ aPath.Assign('/');
+
+ if (!mDN.IsEmpty()) aPath.Append(mDN);
+
+ if (!mAttributes.IsEmpty()) aPath.Append('?');
+
+ // If mAttributes isn't empty, cut off the internally stored commas at start
+ // and end, and append to the path.
+ if (!mAttributes.IsEmpty())
+ aPath.Append(Substring(mAttributes, 1, mAttributes.Length() - 2));
+
+ if (mScope || !mFilter.IsEmpty()) {
+ aPath.Append((mAttributes.IsEmpty() ? "??" : "?"));
+ if (mScope) {
+ if (mScope == SCOPE_ONELEVEL)
+ aPath.Append("one");
+ else if (mScope == SCOPE_SUBTREE)
+ aPath.Append("sub");
+ }
+ if (!mFilter.IsEmpty()) {
+ aPath.Append('?');
+ aPath.Append(mFilter);
+ }
+ }
+}
+
+nsresult nsLDAPURL::SetPathInternal(const nsCString& aPath) {
+ nsCOMPtr<nsILDAPURLParser> parser =
+ do_CreateInstance("@mozilla.org/network/ldap-url-parser;1");
+ nsCOMPtr<nsILDAPURLParserResult> parserResult;
+ nsresult rv = parser->Parse(aPath, getter_AddRefs(parserResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ parserResult->GetDn(mDN);
+ parserResult->GetScope(&mScope);
+ parserResult->GetFilter(mFilter);
+ parserResult->GetOptions(&mOptions);
+
+ nsCString attributes;
+ parserResult->GetAttributes(attributes);
+ mAttributes.Truncate();
+ if (!attributes.IsEmpty()) {
+ // Always start and end with a comma if not empty.
+ mAttributes.Append(',');
+ mAttributes.Append(attributes);
+ mAttributes.Append(',');
+ }
+
+ return NS_OK;
+}
+
+// A string representation of the URI. Setting the spec
+// causes the new spec to be parsed, initializing the URI. Setting
+// the spec (or any of the accessors) causes also any currently
+// open streams on the URI's channel to be closed.
+
+NS_IMETHODIMP
+nsLDAPURL::GetSpec(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetSpec(_retval);
+}
+
+nsresult nsLDAPURL::SetSpecInternal(const nsACString& aSpec) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ // Cache the original spec in case we don't like what we've been passed and
+ // need to reset ourselves.
+ nsCString originalSpec;
+ nsresult rv = mBaseURL->GetSpec(originalSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_MutateURI(mBaseURL).SetSpec(aSpec).Finalize(mBaseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetPathInternal(PromiseFlatCString(aSpec));
+ if (NS_FAILED(rv)) {
+ nsresult rv2 =
+ NS_MutateURI(mBaseURL).SetSpec(originalSpec).Finalize(mBaseURL);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsLDAPURL::GetPrePath(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetPrePath(_retval);
+}
+
+NS_IMETHODIMP nsLDAPURL::GetScheme(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetScheme(_retval);
+}
+
+nsresult nsLDAPURL::SetScheme(const nsACString& aScheme) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ if (aScheme.Equals(LDAP_SCHEME, nsCaseInsensitiveCStringComparator))
+ mOptions &= ~OPT_SECURE;
+ else if (aScheme.Equals(LDAP_SSL_SCHEME, nsCaseInsensitiveCStringComparator))
+ mOptions |= OPT_SECURE;
+ else
+ return NS_ERROR_MALFORMED_URI;
+
+ return NS_MutateURI(mBaseURL).SetScheme(aScheme).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetUserPass(nsACString& _retval) {
+ _retval.Truncate();
+ return NS_OK;
+}
+
+nsresult nsLDAPURL::SetUserPass(const nsACString& aUserPass) { return NS_OK; }
+
+NS_IMETHODIMP
+nsLDAPURL::GetUsername(nsACString& _retval) {
+ _retval.Truncate();
+ return NS_OK;
+}
+
+nsresult nsLDAPURL::SetUsername(const nsACString& aUsername) { return NS_OK; }
+
+NS_IMETHODIMP
+nsLDAPURL::GetPassword(nsACString& _retval) {
+ _retval.Truncate();
+ return NS_OK;
+}
+
+nsresult nsLDAPURL::SetPassword(const nsACString& aPassword) { return NS_OK; }
+
+NS_IMETHODIMP
+nsLDAPURL::GetHostPort(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetHostPort(_retval);
+}
+
+nsresult nsLDAPURL::SetHostPort(const nsACString& aHostPort) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return NS_MutateURI(mBaseURL).SetHostPort(aHostPort).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetHost(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetHost(_retval);
+}
+
+nsresult nsLDAPURL::SetHost(const nsACString& aHost) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return NS_MutateURI(mBaseURL).SetHost(aHost).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetPort(int32_t* _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetPort(_retval);
+}
+
+nsresult nsLDAPURL::SetPort(int32_t aPort) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return NS_MutateURI(mBaseURL).SetPort(aPort).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP nsLDAPURL::GetPathQueryRef(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetPathQueryRef(_retval);
+}
+
+nsresult nsLDAPURL::SetPathQueryRef(const nsACString& aPath) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = SetPathInternal(PromiseFlatCString(aPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_MutateURI(mBaseURL).SetPathQueryRef(aPath).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP nsLDAPURL::GetAsciiSpec(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ // XXX handle extra items?
+ return mBaseURL->GetAsciiSpec(_retval);
+}
+
+NS_IMETHODIMP nsLDAPURL::GetAsciiHost(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetAsciiHost(_retval);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetAsciiHostPort(nsACString& _retval) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->GetAsciiHostPort(_retval);
+}
+
+// boolean equals (in nsIURI other)
+// (based on nsSimpleURI::Equals)
+NS_IMETHODIMP nsLDAPURL::Equals(nsIURI* other, bool* _retval) {
+ *_retval = false;
+ if (other) {
+ nsresult rv;
+ nsCOMPtr<nsILDAPURL> otherURL(do_QueryInterface(other, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString thisSpec, otherSpec;
+ uint32_t otherOptions;
+
+ rv = GetSpec(thisSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = otherURL->GetSpec(otherSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = otherURL->GetOptions(&otherOptions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (thisSpec == otherSpec && mOptions == otherOptions) *_retval = true;
+ }
+ }
+ return NS_OK;
+}
+
+// boolean schemeIs(in const char * scheme);
+//
+NS_IMETHODIMP nsLDAPURL::SchemeIs(const char* aScheme, bool* aEquals) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ return mBaseURL->SchemeIs(aScheme, aEquals);
+}
+
+// nsIURI clone ();
+//
+nsresult nsLDAPURL::Clone(nsIURI** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ RefPtr<nsLDAPURL> clone = new nsLDAPURL();
+
+ clone->mDN = mDN;
+ clone->mScope = mScope;
+ clone->mFilter = mFilter;
+ clone->mOptions = mOptions;
+ clone->mAttributes = mAttributes;
+
+ nsresult rv = NS_MutateURI(mBaseURL).Finalize(clone->mBaseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ clone.forget(aResult);
+ return NS_OK;
+}
+
+// string resolve (in string relativePath);
+//
+NS_IMETHODIMP nsLDAPURL::Resolve(const nsACString& relativePath,
+ nsACString& _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// The following attributes come from nsILDAPURL
+
+// attribute AUTF8String dn;
+//
+NS_IMETHODIMP nsLDAPURL::GetDn(nsACString& _retval) {
+ _retval.Assign(mDN);
+ return NS_OK;
+}
+NS_IMETHODIMP nsLDAPURL::SetDn(const nsACString& aDn) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ mDN.Assign(aDn);
+
+ // Now get the current path
+ nsCString newPath;
+ GetPathInternal(newPath);
+
+ // and update the base url
+ return NS_MutateURI(mBaseURL).SetPathQueryRef(newPath).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP nsLDAPURL::GetAttributes(nsACString& aAttributes) {
+ if (mAttributes.IsEmpty()) {
+ aAttributes.Truncate();
+ return NS_OK;
+ }
+
+ NS_ASSERTION(
+ mAttributes[0] == ',' && mAttributes[mAttributes.Length() - 1] == ',',
+ "mAttributes does not begin and end with a comma");
+
+ // We store the string internally with comma before and after, so strip
+ // them off here.
+ aAttributes = Substring(mAttributes, 1, mAttributes.Length() - 2);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLDAPURL::SetAttributes(const nsACString& aAttributes) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ if (aAttributes.IsEmpty())
+ mAttributes.Truncate();
+ else {
+ // We need to make sure we start off the string with a comma.
+ if (aAttributes[0] != ',') mAttributes = ',';
+
+ mAttributes.Append(aAttributes);
+
+ // Also end with a comma if appropriate.
+ if (mAttributes[mAttributes.Length() - 1] != ',') mAttributes.Append(',');
+ }
+
+ // Now get the current path
+ nsCString newPath;
+ GetPathInternal(newPath);
+
+ // and update the base url
+ return NS_MutateURI(mBaseURL).SetPathQueryRef(newPath).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP nsLDAPURL::AddAttribute(const nsACString& aAttribute) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ if (mAttributes.IsEmpty()) {
+ mAttributes = ',';
+ mAttributes.Append(aAttribute);
+ mAttributes.Append(',');
+ } else {
+ // Wrap the attribute in commas, so that we can do an exact match.
+ nsAutoCString findAttribute(",");
+ findAttribute.Append(aAttribute);
+ findAttribute.Append(',');
+
+ // Check to see if the attribute is already stored. If it is, then also
+ // check to see if it is the last attribute in the string, or if the next
+ // character is a comma, this means we won't match substrings.
+ if (FindInReadable(findAttribute, mAttributes,
+ nsCaseInsensitiveCStringComparator)) {
+ return NS_OK;
+ }
+
+ mAttributes.Append(Substring(findAttribute, 1));
+ }
+
+ // Now get the current path
+ nsCString newPath;
+ GetPathInternal(newPath);
+
+ // and update the base url
+ return NS_MutateURI(mBaseURL).SetPathQueryRef(newPath).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP nsLDAPURL::RemoveAttribute(const nsACString& aAttribute) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ if (mAttributes.IsEmpty()) return NS_OK;
+
+ // We use comma as delimiter (even first attr has a leading comma).
+ nsAutoCString findAttribute(",");
+ findAttribute.Append(aAttribute);
+ findAttribute.Append(',');
+
+ if (!FindInReadable(findAttribute, mAttributes,
+ nsCaseInsensitiveCStringComparator)) {
+ return NS_OK;
+ }
+ if (mAttributes.Equals(findAttribute, nsCaseInsensitiveCStringComparator)) {
+ mAttributes.Truncate();
+ } else {
+ mAttributes.ReplaceSubstring(findAttribute, ","_ns);
+ }
+
+ // Now get the current path
+ nsCString newPath;
+ GetPathInternal(newPath);
+
+ // and update the base url
+ return NS_MutateURI(mBaseURL).SetPathQueryRef(newPath).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP nsLDAPURL::HasAttribute(const nsACString& aAttribute,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // We use comma as delimiter (even first attr has a leading comma).
+ nsAutoCString findAttribute(",");
+ findAttribute.Append(aAttribute);
+ findAttribute.Append(',');
+
+ *_retval = FindInReadable(findAttribute, mAttributes,
+ nsCaseInsensitiveCStringComparator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLDAPURL::GetScope(int32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mScope;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLDAPURL::SetScope(int32_t aScope) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ // Only allow scopes supported by the C-SDK
+ if ((aScope != SCOPE_BASE) && (aScope != SCOPE_ONELEVEL) &&
+ (aScope != SCOPE_SUBTREE))
+ return NS_ERROR_MALFORMED_URI;
+
+ mScope = aScope;
+
+ // Now get the current path
+ nsCString newPath;
+ GetPathInternal(newPath);
+
+ // and update the base url
+ return NS_MutateURI(mBaseURL).SetPathQueryRef(newPath).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP nsLDAPURL::GetFilter(nsACString& _retval) {
+ _retval.Assign(mFilter);
+ return NS_OK;
+}
+NS_IMETHODIMP nsLDAPURL::SetFilter(const nsACString& aFilter) {
+ if (!mBaseURL) return NS_ERROR_NOT_INITIALIZED;
+
+ mFilter.Assign(aFilter);
+
+ if (mFilter.IsEmpty()) mFilter.AssignLiteral("(objectclass=*)");
+
+ // Now get the current path
+ nsCString newPath;
+ GetPathInternal(newPath);
+
+ // and update the base url
+ return NS_MutateURI(mBaseURL).SetPathQueryRef(newPath).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP nsLDAPURL::GetOptions(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mOptions;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLDAPURL::SetOptions(uint32_t aOptions) {
+ // Secure is the only option supported at the moment
+ if ((mOptions & OPT_SECURE) == (aOptions & OPT_SECURE)) return NS_OK;
+
+ mOptions = aOptions;
+
+ if ((aOptions & OPT_SECURE) == OPT_SECURE) return SetScheme(LDAP_SSL_SCHEME);
+
+ return SetScheme(LDAP_SCHEME);
+}
+
+nsresult nsLDAPURL::SetRef(const nsACString& aRef) {
+ return NS_MutateURI(mBaseURL).SetRef(aRef).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetRef(nsACString& result) { return mBaseURL->GetRef(result); }
+
+NS_IMETHODIMP nsLDAPURL::EqualsExceptRef(nsIURI* other, bool* result) {
+ return mBaseURL->EqualsExceptRef(other, result);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetSpecIgnoringRef(nsACString& result) {
+ return mBaseURL->GetSpecIgnoringRef(result);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetDisplaySpec(nsACString& aUnicodeSpec) {
+ return mBaseURL->GetDisplaySpec(aUnicodeSpec);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
+ return mBaseURL->GetDisplayHostPort(aUnicodeHostPort);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetDisplayHost(nsACString& aUnicodeHost) {
+ return mBaseURL->GetDisplayHost(aUnicodeHost);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetDisplayPrePath(nsACString& aPrePath) {
+ return mBaseURL->GetDisplayPrePath(aPrePath);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetHasRef(bool* result) { return mBaseURL->GetHasRef(result); }
+
+NS_IMETHODIMP
+nsLDAPURL::GetFilePath(nsACString& aFilePath) {
+ return mBaseURL->GetFilePath(aFilePath);
+}
+
+nsresult nsLDAPURL::SetFilePath(const nsACString& aFilePath) {
+ return NS_MutateURI(mBaseURL).SetFilePath(aFilePath).Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP
+nsLDAPURL::GetQuery(nsACString& aQuery) { return mBaseURL->GetQuery(aQuery); }
+
+nsresult nsLDAPURL::SetQuery(const nsACString& aQuery) {
+ return NS_MutateURI(mBaseURL).SetQuery(aQuery).Finalize(mBaseURL);
+}
+
+nsresult nsLDAPURL::SetQueryWithEncoding(const nsACString& aQuery,
+ const mozilla::Encoding* aEncoding) {
+ return NS_MutateURI(mBaseURL)
+ .SetQueryWithEncoding(aQuery, aEncoding)
+ .Finalize(mBaseURL);
+}
+
+NS_IMETHODIMP_(void)
+nsLDAPURL::Serialize(mozilla::ipc::URIParams& aParams) {
+ mBaseURL->Serialize(aParams);
+}
+
+NS_IMPL_ISUPPORTS(nsLDAPURL::Mutator, nsIURISetters, nsIURIMutator)
+
+NS_IMETHODIMP
+nsLDAPURL::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsLDAPURL::Mutator> mutator = new nsLDAPURL::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
diff --git a/comm/mailnews/addrbook/src/nsLDAPURL.h b/comm/mailnews/addrbook/src/nsLDAPURL.h
new file mode 100644
index 0000000000..60c3cbb6de
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsLDAPURL.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "nsString.h"
+#include "nsILDAPURL.h"
+#include "nsCOMPtr.h"
+#include "nsIURIMutator.h"
+
+/**
+ * nsLDAPURL
+ *
+ * nsLDAPURL uses an nsStandardURL stored in mBaseURL as its main url formatter.
+ *
+ * This is done to ensure that the pre-path sections of the URI are correctly
+ * formatted and to re-use the functions for nsIURI as appropriate.
+ *
+ * Handling of the path sections of the URI are done within nsLDAPURL/parts of
+ * the LDAP c-sdk. nsLDAPURL holds the individual sections of the path of the
+ * URI locally (to allow convenient get/set), but always updates the mBaseURL
+ * when one changes to ensure that mBaseURL.spec and the local data are kept
+ * consistent.
+ */
+
+class nsLDAPURL : public nsILDAPURL {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSILDAPURL
+
+ nsLDAPURL();
+
+ protected:
+ virtual nsresult Clone(nsIURI** _retval);
+ virtual nsresult SetSpecInternal(const nsACString& aSpec);
+ virtual nsresult SetScheme(const nsACString& aScheme);
+ virtual nsresult SetUserPass(const nsACString& aUserPass);
+ virtual nsresult SetUsername(const nsACString& aUsername);
+ virtual nsresult SetPassword(const nsACString& aPassword);
+ virtual nsresult SetHostPort(const nsACString& aHostPort);
+ virtual nsresult SetHost(const nsACString& aHost);
+ virtual nsresult SetPort(int32_t aPort);
+ virtual nsresult SetPathQueryRef(const nsACString& aPath);
+ virtual nsresult SetRef(const nsACString& aRef);
+ virtual nsresult SetFilePath(const nsACString& aFilePath);
+ virtual nsresult SetQuery(const nsACString& aQuery);
+ virtual nsresult SetQueryWithEncoding(const nsACString& aQuery,
+ const mozilla::Encoding* aEncoding);
+
+ public:
+ class Mutator : public nsIURIMutator, public BaseURIMutator<nsLDAPURL> {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+
+ NS_IMETHOD Deserialize(const mozilla::ipc::URIParams& aParams) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD Finalize(nsIURI** aURI) override {
+ mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) NS_ADDREF(*aMutator = this);
+ return InitFromSpec(aSpec);
+ }
+
+ explicit Mutator() {}
+
+ private:
+ virtual ~Mutator() {}
+
+ friend class nsLDAPURL;
+ };
+ friend BaseURIMutator<nsLDAPURL>;
+
+ protected:
+ virtual ~nsLDAPURL();
+
+ void GetPathInternal(nsCString& aPath);
+ nsresult SetPathInternal(const nsCString& aPath);
+
+ nsCString mDN; // Base Distinguished Name (Base DN)
+ int32_t mScope; // Search scope (base, one or sub)
+ nsCString mFilter; // LDAP search filter
+ uint32_t mOptions; // Options
+ nsCString
+ mAttributes; // Either empty ("") or comma-separated list with
+ // leading _and_ trailing commas (i.e ",attr1,attr2,").
+ nsCOMPtr<nsIURI> mBaseURL;
+};
diff --git a/comm/mailnews/addrbook/src/nsMapiAddressBook.cpp b/comm/mailnews/addrbook/src/nsMapiAddressBook.cpp
new file mode 100644
index 0000000000..bd6e080443
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsMapiAddressBook.cpp
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsMapiAddressBook.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/DebugOnly.h"
+
+#define PRINT_TO_CONSOLE 0
+#if PRINT_TO_CONSOLE
+# define PRINTF(args) printf args
+#else
+static mozilla::LazyLogModule gMapiAddressBookLog("MAPIAddressBook");
+# define PRINTF(args) \
+ MOZ_LOG(gMapiAddressBookLog, mozilla::LogLevel::Debug, args)
+#endif
+
+using namespace mozilla;
+
+HMODULE nsMapiAddressBook::mLibrary = NULL;
+int32_t nsMapiAddressBook::mLibUsage = 0;
+LPMAPIINITIALIZE nsMapiAddressBook::mMAPIInitialize = NULL;
+LPMAPIUNINITIALIZE nsMapiAddressBook::mMAPIUninitialize = NULL;
+LPMAPIALLOCATEBUFFER nsMapiAddressBook::mMAPIAllocateBuffer = NULL;
+LPMAPIFREEBUFFER nsMapiAddressBook::mMAPIFreeBuffer = NULL;
+LPMAPILOGONEX nsMapiAddressBook::mMAPILogonEx = NULL;
+
+BOOL nsMapiAddressBook::mInitialized = FALSE;
+BOOL nsMapiAddressBook::mLogonDone = FALSE;
+LPMAPISESSION nsMapiAddressBook::mRootSession = NULL;
+LPADRBOOK nsMapiAddressBook::mRootBook = NULL;
+
+BOOL nsMapiAddressBook::LoadMapiLibrary(void) {
+ if (mLibrary) {
+ ++mLibUsage;
+ return TRUE;
+ }
+ HMODULE libraryHandle = LoadLibraryW(L"MAPI32.DLL");
+
+ if (!libraryHandle) {
+ return FALSE;
+ }
+ FARPROC entryPoint = GetProcAddress(libraryHandle, "MAPIGetNetscapeVersion");
+
+ if (entryPoint) {
+ FreeLibrary(libraryHandle);
+ libraryHandle = LoadLibraryW(L"MAPI32BAK.DLL");
+ if (!libraryHandle) {
+ return FALSE;
+ }
+ }
+ mLibrary = libraryHandle;
+ ++mLibUsage;
+ mMAPIInitialize = reinterpret_cast<LPMAPIINITIALIZE>(
+ GetProcAddress(mLibrary, "MAPIInitialize"));
+ if (!mMAPIInitialize) {
+ return FALSE;
+ }
+ mMAPIUninitialize = reinterpret_cast<LPMAPIUNINITIALIZE>(
+ GetProcAddress(mLibrary, "MAPIUninitialize"));
+ if (!mMAPIUninitialize) {
+ return FALSE;
+ }
+ mMAPIAllocateBuffer = reinterpret_cast<LPMAPIALLOCATEBUFFER>(
+ GetProcAddress(mLibrary, "MAPIAllocateBuffer"));
+ if (!mMAPIAllocateBuffer) {
+ return FALSE;
+ }
+ mMAPIFreeBuffer = reinterpret_cast<LPMAPIFREEBUFFER>(
+ GetProcAddress(mLibrary, "MAPIFreeBuffer"));
+ if (!mMAPIFreeBuffer) {
+ return FALSE;
+ }
+ mMAPILogonEx =
+ reinterpret_cast<LPMAPILOGONEX>(GetProcAddress(mLibrary, "MAPILogonEx"));
+ if (!mMAPILogonEx) {
+ return FALSE;
+ }
+ MAPIINIT_0 mapiInit = {MAPI_INIT_VERSION, MAPI_MULTITHREAD_NOTIFICATIONS};
+ HRESULT retCode = mMAPIInitialize(&mapiInit);
+
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot initialize MAPI %08lx.\n", retCode));
+ return FALSE;
+ }
+ mInitialized = TRUE;
+ retCode = mMAPILogonEx(
+ 0, NULL, NULL,
+ MAPI_NO_MAIL | MAPI_USE_DEFAULT | MAPI_EXTENDED | MAPI_NEW_SESSION,
+ &mRootSession);
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot logon to MAPI %08lx.\n", retCode));
+ return FALSE;
+ }
+ mLogonDone = TRUE;
+ retCode = mRootSession->OpenAddressBook(0, NULL, 0, &mRootBook);
+ if (HR_FAILED(retCode)) {
+ PRINTF(("Cannot open MAPI address book %08lx.\n", retCode));
+ }
+ return HR_SUCCEEDED(retCode);
+}
+
+void nsMapiAddressBook::FreeMapiLibrary(void) {
+ if (mLibrary) {
+ if (--mLibUsage == 0) {
+ {
+ if (mRootBook) {
+ mRootBook->Release();
+ }
+ if (mRootSession) {
+ if (mLogonDone) {
+ mRootSession->Logoff(NULL, 0, 0);
+ mLogonDone = FALSE;
+ }
+ mRootSession->Release();
+ }
+ if (mInitialized) {
+ mMAPIUninitialize();
+ mInitialized = FALSE;
+ }
+ }
+ FreeLibrary(mLibrary);
+ mLibrary = NULL;
+ }
+ }
+}
+
+nsMapiAddressBook::nsMapiAddressBook(void) : nsAbWinHelper() {
+ mozilla::DebugOnly<BOOL> result = Initialize();
+
+ NS_ASSERTION(result == TRUE, "Couldn't initialize Mapi Helper");
+ MOZ_COUNT_CTOR(nsMapiAddressBook);
+}
+
+nsMapiAddressBook::~nsMapiAddressBook(void) {
+ StaticMutexAutoLock guard(sMutex);
+
+ FreeMapiLibrary();
+ MOZ_COUNT_DTOR(nsMapiAddressBook);
+}
+
+BOOL nsMapiAddressBook::Initialize(void) {
+ if (mAddressBook) {
+ return TRUE;
+ }
+ StaticMutexAutoLock guard(sMutex);
+
+ if (!LoadMapiLibrary()) {
+ PRINTF(("Cannot load library.\n"));
+ return FALSE;
+ }
+ mAddressBook = mRootBook;
+ mAddressSession = mRootSession;
+ mAddressFreeBuffer = mMAPIFreeBuffer;
+ return TRUE;
+}
+
+void nsMapiAddressBook::AllocateBuffer(ULONG aByteCount, LPVOID* aBuffer) {
+ mMAPIAllocateBuffer(aByteCount, aBuffer);
+}
+
+void nsMapiAddressBook::FreeBuffer(LPVOID aBuffer) { mMAPIFreeBuffer(aBuffer); }
diff --git a/comm/mailnews/addrbook/src/nsMapiAddressBook.h b/comm/mailnews/addrbook/src/nsMapiAddressBook.h
new file mode 100644
index 0000000000..dd0463dba0
--- /dev/null
+++ b/comm/mailnews/addrbook/src/nsMapiAddressBook.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+#ifndef nsMapiAddressBook_h___
+#define nsMapiAddressBook_h___
+
+#include "mozilla/Attributes.h"
+#include "nsAbWinHelper.h"
+
+class nsMapiAddressBook : public nsAbWinHelper {
+ public:
+ nsMapiAddressBook(void);
+ virtual ~nsMapiAddressBook(void);
+
+ protected:
+ // Class members to handle the library/entry points
+ static HMODULE mLibrary;
+ static int32_t mLibUsage;
+ static LPMAPIINITIALIZE mMAPIInitialize;
+ static LPMAPIUNINITIALIZE mMAPIUninitialize;
+ static LPMAPIALLOCATEBUFFER mMAPIAllocateBuffer;
+ static LPMAPIFREEBUFFER mMAPIFreeBuffer;
+ static LPMAPILOGONEX mMAPILogonEx;
+ // Shared session and address book used by all instances.
+ // For reasons best left unknown, MAPI doesn't seem to like
+ // having different threads playing with supposedly different
+ // sessions and address books. They ll end up fighting over
+ // the same resources, with hangups and GPF resulting. Not nice.
+ // So it seems that if everybody (as long as some client is
+ // still alive) is using the same sessions and address books,
+ // MAPI feels better. And who are we to get in the way of MAPI
+ // happiness? Thus the following class members:
+ static BOOL mInitialized;
+ static BOOL mLogonDone;
+ static LPMAPISESSION mRootSession;
+ static LPADRBOOK mRootBook;
+
+ // Load the MAPI environment
+ BOOL Initialize(void);
+ // Allocation of a buffer for transmission to interfaces
+ virtual void AllocateBuffer(ULONG aByteCount, LPVOID* aBuffer) override;
+ // Destruction of a buffer provided by the interfaces
+ virtual void FreeBuffer(LPVOID aBuffer) override;
+ // Library management
+ static BOOL LoadMapiLibrary(void);
+ static void FreeMapiLibrary(void);
+
+ private:
+};
+
+#endif // nsMapiAddressBook_h___