diff options
Diffstat (limited to '')
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(" <"); + + 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("></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___ |