diff options
author | Matt A. Tobin <email@mattatobin.com> | 2019-11-03 00:17:46 -0400 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2019-11-03 00:17:46 -0400 |
commit | 302bf1b523012e11b60425d6eee1221ebc2724eb (patch) | |
tree | b191a895f8716efcbe42f454f37597a545a6f421 /mailnews/base/util | |
parent | 21b3f6247403c06f85e1f45d219f87549862198f (diff) | |
download | UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.gz UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.lz UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.xz UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.zip |
Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1
Diffstat (limited to 'mailnews/base/util')
55 files changed, 23773 insertions, 0 deletions
diff --git a/mailnews/base/util/ABQueryUtils.jsm b/mailnews/base/util/ABQueryUtils.jsm new file mode 100644 index 000000000..d4b694033 --- /dev/null +++ b/mailnews/base/util/ABQueryUtils.jsm @@ -0,0 +1,130 @@ +/* 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/. */ + +/** + * This file contains helper methods for dealing with addressbook search URIs. + */ + +this.EXPORTED_SYMBOLS = ["getSearchTokens", "getModelQuery", + "modelQueryHasUserValue", "generateQueryURI", + "encodeABTermValue"]; +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * Parse the multiword search string to extract individual search terms + * (separated on the basis of spaces) or quoted exact phrases to search + * against multiple fields of the addressbook cards. + * + * @param aSearchString The full search string entered by the user. + * + * @return an array of separated search terms from the full search string. + */ +function getSearchTokens(aSearchString) { + let searchString = aSearchString.trim(); + if (searchString == "") + return []; + + let quotedTerms = []; + + // Split up multiple search words to create a *foo* and *bar* search against + // search fields, using the OR-search template from modelQuery for each word. + // If the search query has quoted terms as "foo bar", extract them as is. + let startIndex; + while ((startIndex = searchString.indexOf('"')) != -1) { + let endIndex = searchString.indexOf('"', startIndex + 1); + if (endIndex == -1) + endIndex = searchString.length; + + quotedTerms.push(searchString.substring(startIndex + 1, endIndex)); + let query = searchString.substring(0, startIndex); + if (endIndex < searchString.length) + query += searchString.substr(endIndex + 1); + + searchString = query.trim(); + } + + let searchWords = []; + if (searchString.length != 0) { + searchWords = quotedTerms.concat(searchString.split(/\s+/)); + } else { + searchWords = quotedTerms; + } + + return searchWords; +} + +/** + * For AB quicksearch or recipient autocomplete, get the normal or phonetic model + * query URL part from prefs, allowing users to customize these searches. + * @param aBasePrefName the full pref name of default, non-phonetic model query, + * e.g. mail.addr_book.quicksearchquery.format + * If phonetic search is used, corresponding pref must exist: + * e.g. mail.addr_book.quicksearchquery.format.phonetic + * @return depending on mail.addr_book.show_phonetic_fields pref, + * the value of aBasePrefName or aBasePrefName + ".phonetic" + */ +function getModelQuery(aBasePrefName) { + let modelQuery = ""; + if (Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields", + Components.interfaces.nsIPrefLocalizedString).data == "true") { + modelQuery = Services.prefs.getCharPref(aBasePrefName + ".phonetic"); + } else { + modelQuery = Services.prefs.getCharPref(aBasePrefName); + } + // remove leading "?" to migrate existing customized values for mail.addr_book.quicksearchquery.format + // todo: could this be done in a once-off migration at install time to avoid repetitive calls? + if (modelQuery.startsWith("?")) + modelQuery = modelQuery.slice(1); + return modelQuery; +} + +/** + * Check if the currently used pref with the model query was customized by user. + * @param aBasePrefName the full pref name of default, non-phonetic model query, + * e.g. mail.addr_book.quicksearchquery.format + * If phonetic search is used, corresponding pref must exist: + * e.g. mail.addr_book.quicksearchquery.format.phonetic + * @return true or false + */ +function modelQueryHasUserValue(aBasePrefName) { + if (Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields", + Components.interfaces.nsIPrefLocalizedString).data == "true") + return Services.prefs.prefHasUserValue(aBasePrefName + ".phonetic"); + return Services.prefs.prefHasUserValue(aBasePrefName); +} + +/* + * Given a database model query and a list of search tokens, + * return query URI. + * + * @param aModelQuery database model query + * @param aSearchWords an array of search tokens. + * + * @return query URI. + */ +function generateQueryURI(aModelQuery, aSearchWords) { + // If there are no search tokens, we simply return an empty string. + if (!aSearchWords || aSearchWords.length == 0) + return ""; + + let queryURI = ""; + aSearchWords.forEach(searchWord => + queryURI += aModelQuery.replace(/@V/g, encodeABTermValue(searchWord))); + + // queryURI has all the (or(...)) searches, link them up with (and(...)). + queryURI = "?(and" + queryURI + ")"; + + return queryURI; +} + + +/** + * Encode the string passed as value into an addressbook search term. + * The '(' and ')' characters are special for the addressbook + * search query language, but are not escaped in encodeURIComponent() + * so must be done manually on top of it. + */ +function encodeABTermValue(aString) { + return encodeURIComponent(aString).replace(/\(/g, "%28").replace(/\)/g, "%29"); +} diff --git a/mailnews/base/util/IOUtils.js b/mailnews/base/util/IOUtils.js new file mode 100644 index 000000000..ff4eddcdf --- /dev/null +++ b/mailnews/base/util/IOUtils.js @@ -0,0 +1,136 @@ +/* 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/. */ + +this.EXPORTED_SYMBOLS = ["IOUtils"]; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; +var kStringBlockSize = 4096; +var kStreamBlockSize = 8192; + +var IOUtils = +{ + /** + * Read a file containing ASCII text into a string. + * + * @param aFile An nsIFile representing the file to read or a string containing + * the file name of a file under user's profile. + * @returns A string containing the contents of the file, presumed to be ASCII + * text. If the file didn't exist, returns null. + */ + loadFileToString: function(aFile) { + let file; + if (!(aFile instanceof Ci.nsIFile)) { + file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append(aFile); + } else { + file = aFile; + } + + if (!file.exists()) + return null; + + let fstream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + // PR_RDONLY + fstream.init(file, 0x01, 0, 0); + + let sstream = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + sstream.init(fstream); + + let data = ""; + while (sstream.available()) { + data += sstream.read(kStringBlockSize); + } + + sstream.close(); + fstream.close(); + + return data; + }, + + /** + * Save a string containing ASCII text into a file. The file will be overwritten + * and contain only the given text. + * + * @param aFile An nsIFile representing the file to write or a string containing + * the file name of a file under user's profile. + * @param aData The string to write. + * @param aPerms The octal file permissions for the created file. If unset + * the default of 0o600 is used. + */ + saveStringToFile: function(aFile, aData, aPerms = 0o600) { + let file; + if (!(aFile instanceof Ci.nsIFile)) { + file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append(aFile); + } else { + file = aFile; + } + + let foStream = Cc["@mozilla.org/network/safe-file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + + // PR_WRONLY + PR_CREATE_FILE + PR_TRUNCATE + foStream.init(file, 0x02 | 0x08 | 0x20, aPerms, 0); + // safe-file-output-stream appears to throw an error if it doesn't write everything at once + // so we won't worry about looping to deal with partial writes. + // In case we try to use this function for big files where buffering + // is needed we could use the implementation in saveStreamToFile(). + foStream.write(aData, aData.length); + foStream.QueryInterface(Ci.nsISafeOutputStream).finish(); + foStream.close(); + }, + + /** + * Saves the given input stream to a file. + * + * @param aIStream The input stream to save. + * @param aFile The file to which the stream is saved. + * @param aPerms The octal file permissions for the created file. If unset + * the default of 0o600 is used. + */ + saveStreamToFile: function(aIStream, aFile, aPerms = 0o600) { + if (!(aIStream instanceof Ci.nsIInputStream)) + throw new Error("Invalid stream passed to saveStreamToFile"); + if (!(aFile instanceof Ci.nsIFile)) + throw new Error("Invalid file passed to saveStreamToFile"); + + let fstream = Cc["@mozilla.org/network/safe-file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + let buffer = Cc["@mozilla.org/network/buffered-output-stream;1"] + .createInstance(Ci.nsIBufferedOutputStream); + + // Write the input stream to the file. + // PR_WRITE + PR_CREATE + PR_TRUNCATE + fstream.init(aFile, 0x04 | 0x08 | 0x20, aPerms, 0); + buffer.init(fstream, kStreamBlockSize); + + buffer.writeFrom(aIStream, aIStream.available()); + + // Close the output streams. + if (buffer instanceof Components.interfaces.nsISafeOutputStream) + buffer.finish(); + else + buffer.close(); + if (fstream instanceof Components.interfaces.nsISafeOutputStream) + fstream.finish(); + else + fstream.close(); + + // Close the input stream. + aIStream.close(); + return aFile; + }, + + /** + * Returns size of system memory. + */ + getPhysicalMemorySize: function() { + return Services.sysinfo.getPropertyAsInt64("memsize"); + }, +}; diff --git a/mailnews/base/util/JXON.js b/mailnews/base/util/JXON.js new file mode 100644 index 000000000..509c9b5d8 --- /dev/null +++ b/mailnews/base/util/JXON.js @@ -0,0 +1,180 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This is a modification of the JXON parsers found on the page +// <https://developer.mozilla.org/en-US/docs/JXON> + +var EXPORTED_SYMBOLS = ["JXON"]; + +var JXON = new (function() { + const sValueProp = "value"; /* you can customize these values */ + const sAttributesProp = "attr"; + const sAttrPref = "@"; + const sElementListPrefix = "$"; + const sConflictSuffix = "_"; // used when there's a name conflict with special JXON properties + const aCache = []; + const rIsNull = /^\s*$/; + const rIsBool = /^(?:true|false)$/i; + + function parseText(sValue) { + //if (rIsNull.test(sValue)) + // return null; + if (rIsBool.test(sValue)) + return sValue.toLowerCase() === "true"; + if (isFinite(sValue)) + return parseFloat(sValue); + if (isFinite(Date.parse(sValue))) + return new Date(sValue); + return sValue; + }; + + function EmptyTree() { + } + EmptyTree.prototype = { + toString : function () { + return "null"; + }, + valueOf : function () { + return null; + }, + }; + + function objectify(vValue) { + if (vValue === null) + return new EmptyTree(); + else if (vValue instanceof Object) + return vValue; + else + return new vValue.constructor(vValue); // What does this? copy? + }; + + function createObjTree(oParentNode, nVerb, bFreeze, bNesteAttr) { + const nLevelStart = aCache.length; + const bChildren = oParentNode.hasChildNodes(); + const bAttributes = oParentNode.attributes && + oParentNode.attributes.length; + const bHighVerb = Boolean(nVerb & 2); + + var sProp = 0; + var vContent = 0; + var nLength = 0; + var sCollectedTxt = ""; + var vResult = bHighVerb ? {} : /* put here the default value for empty nodes: */ true; + + if (bChildren) { + for (var oNode, nItem = 0; nItem < oParentNode.childNodes.length; nItem++) { + oNode = oParentNode.childNodes.item(nItem); + if (oNode.nodeType === 4) // CDATASection + sCollectedTxt += oNode.nodeValue; + else if (oNode.nodeType === 3) // Text + sCollectedTxt += oNode.nodeValue; + else if (oNode.nodeType === 1) // Element + aCache.push(oNode); + } + } + + const nLevelEnd = aCache.length; + const vBuiltVal = parseText(sCollectedTxt); + + if (!bHighVerb && (bChildren || bAttributes)) + vResult = nVerb === 0 ? objectify(vBuiltVal) : {}; + + for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) { + sProp = aCache[nElId].nodeName; + if (sProp == sValueProp || sProp == sAttributesProp) + sProp = sProp + sConflictSuffix; + vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr); + if (!vResult.hasOwnProperty(sProp)) { + vResult[sProp] = vContent; + vResult[sElementListPrefix + sProp] = []; + } + vResult[sElementListPrefix + sProp].push(vContent); + nLength++; + } + + if (bAttributes) { + const nAttrLen = oParentNode.attributes.length; + const sAPrefix = bNesteAttr ? "" : sAttrPref; + const oAttrParent = bNesteAttr ? {} : vResult; + + for (var oAttrib, nAttrib = 0; nAttrib < nAttrLen; nLength++, nAttrib++) { + oAttrib = oParentNode.attributes.item(nAttrib); + oAttrParent[sAPrefix + oAttrib.name] = parseText(oAttrib.value); + } + + if (bNesteAttr) { + if (bFreeze) + Object.freeze(oAttrParent); + vResult[sAttributesProp] = oAttrParent; + nLength -= nAttrLen - 1; + } + } + + if (nVerb === 3 || (nVerb === 2 || nVerb === 1 && nLength > 0) && sCollectedTxt) + vResult[sValueProp] = vBuiltVal; + else if (!bHighVerb && nLength === 0 && sCollectedTxt) + vResult = vBuiltVal; + + if (bFreeze && (bHighVerb || nLength > 0)) + Object.freeze(vResult); + + aCache.length = nLevelStart; + + return vResult; + }; + + function loadObjTree(oXMLDoc, oParentEl, oParentObj) { + var vValue, oChild; + + if (oParentObj instanceof String || oParentObj instanceof Number || + oParentObj instanceof Boolean) + oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toString())); /* verbosity level is 0 */ + else if (oParentObj.constructor === Date) + oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toGMTString())); + + for (var sName in oParentObj) { + vValue = oParentObj[sName]; + if (isFinite(sName) || vValue instanceof Function) + continue; /* verbosity level is 0 */ + if (sName === sValueProp) { + if (vValue !== null && vValue !== true) { + oParentEl.appendChild(oXMLDoc.createTextNode( + vValue.constructor === Date ? vValue.toGMTString() : String(vValue))); + } + } else if (sName === sAttributesProp) { /* verbosity level is 3 */ + for (var sAttrib in vValue) + oParentEl.setAttribute(sAttrib, vValue[sAttrib]); + } else if (sName.charAt(0) === sAttrPref) { + oParentEl.setAttribute(sName.slice(1), vValue); + } else if (vValue.constructor === Array) { + for (var nItem = 0; nItem < vValue.length; nItem++) { + oChild = oXMLDoc.createElement(sName); + loadObjTree(oXMLDoc, oChild, vValue[nItem]); + oParentEl.appendChild(oChild); + } + } else { + oChild = oXMLDoc.createElement(sName); + if (vValue instanceof Object) + loadObjTree(oXMLDoc, oChild, vValue); + else if (vValue !== null && vValue !== true) + oChild.appendChild(oXMLDoc.createTextNode(vValue.toString())); + oParentEl.appendChild(oChild); + } + } + }; + + this.build = function(oXMLParent, nVerbosity /* optional */, bFreeze /* optional */, bNesteAttributes /* optional */) { + const _nVerb = arguments.length > 1 && + typeof nVerbosity === "number" ? nVerbosity & 3 : + /* put here the default verbosity level: */ 1; + return createObjTree(oXMLParent, _nVerb, bFreeze || false, + arguments.length > 3 ? bNesteAttributes : _nVerb === 3); + }; + + this.unbuild = function(oObjTree) { + const oNewDoc = document.implementation.createDocument("", "", null); + loadObjTree(oNewDoc, oNewDoc, oObjTree); + return oNewDoc; + }; +})(); diff --git a/mailnews/base/util/OAuth2.jsm b/mailnews/base/util/OAuth2.jsm new file mode 100644 index 000000000..8e6f9e713 --- /dev/null +++ b/mailnews/base/util/OAuth2.jsm @@ -0,0 +1,234 @@ +/* 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/. */ + +/** + * Provides OAuth 2.0 authentication + */ +var EXPORTED_SYMBOLS = ["OAuth2"]; + +var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; + +Cu.import("resource://gre/modules/Http.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource:///modules/gloda/log4moz.js"); + +function parseURLData(aData) { + let result = {}; + aData.split(/[?#]/, 2)[1].split("&").forEach(function (aParam) { + let [key, value] = aParam.split("="); + result[key] = value; + }); + return result; +} + +// Only allow one connecting window per endpoint. +var gConnecting = {}; + +function OAuth2(aBaseURI, aScope, aAppKey, aAppSecret) { + this.authURI = aBaseURI + "oauth2/auth"; + this.tokenURI = aBaseURI + "oauth2/token"; + this.consumerKey = aAppKey; + this.consumerSecret = aAppSecret; + this.scope = aScope; + this.extraAuthParams = []; + + this.log = Log4Moz.getConfiguredLogger("TBOAuth"); +} + +OAuth2.CODE_AUTHORIZATION = "authorization_code"; +OAuth2.CODE_REFRESH = "refresh_token"; + +OAuth2.prototype = { + + responseType: "code", + consumerKey: null, + consumerSecret: null, + completionURI: "http://localhost", + requestWindowURI: "chrome://messenger/content/browserRequest.xul", + requestWindowFeatures: "chrome,private,centerscreen,width=980,height=600", + requestWindowTitle: "", + scope: null, + + accessToken: null, + refreshToken: null, + tokenExpires: 0, + + connect: function connect(aSuccess, aFailure, aWithUI, aRefresh) { + + this.connectSuccessCallback = aSuccess; + this.connectFailureCallback = aFailure; + + if (!aRefresh && this.accessToken) { + aSuccess(); + } else if (this.refreshToken) { + this.requestAccessToken(this.refreshToken, OAuth2.CODE_REFRESH); + } else { + if (!aWithUI) { + aFailure('{ "error": "auth_noui" }'); + return; + } + if (gConnecting[this.authURI]) { + aFailure("Window already open"); + return; + } + this.requestAuthorization(); + } + }, + + requestAuthorization: function requestAuthorization() { + let params = [ + ["response_type", this.responseType], + ["client_id", this.consumerKey], + ["redirect_uri", this.completionURI], + ]; + // The scope can be optional. + if (this.scope) { + params.push(["scope", this.scope]); + } + + // Add extra parameters + params.push(...this.extraAuthParams); + + // Now map the parameters to a string + params = params.map(([k,v]) => k + "=" + encodeURIComponent(v)).join("&"); + + this._browserRequest = { + account: this, + url: this.authURI + "?" + params, + _active: true, + iconURI: "", + cancelled: function() { + if (!this._active) { + return; + } + + this.account.finishAuthorizationRequest(); + this.account.onAuthorizationFailed(Components.results.NS_ERROR_ABORT, '{ "error": "cancelled"}'); + }, + + loaded: function (aWindow, aWebProgress) { + if (!this._active) { + return; + } + + this._listener = { + window: aWindow, + webProgress: aWebProgress, + _parent: this.account, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]), + + _cleanUp: function() { + this.webProgress.removeProgressListener(this); + this.window.close(); + delete this.window; + }, + + _checkForRedirect: function(aURL) { + if (aURL.indexOf(this._parent.completionURI) != 0) + return; + + this._parent.finishAuthorizationRequest(); + this._parent.onAuthorizationReceived(aURL); + }, + + onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { + const wpl = Ci.nsIWebProgressListener; + if (aStateFlags & (wpl.STATE_START | wpl.STATE_IS_NETWORK)) + this._checkForRedirect(aRequest.name); + }, + onLocationChange: function(aWebProgress, aRequest, aLocation) { + this._checkForRedirect(aLocation.spec); + }, + onProgressChange: function() {}, + onStatusChange: function() {}, + onSecurityChange: function() {}, + }; + aWebProgress.addProgressListener(this._listener, + Ci.nsIWebProgress.NOTIFY_ALL); + aWindow.document.title = this.account.requestWindowTitle; + } + }; + + this.wrappedJSObject = this._browserRequest; + gConnecting[this.authURI] = true; + Services.ww.openWindow(null, this.requestWindowURI, null, this.requestWindowFeatures, this); + }, + finishAuthorizationRequest: function() { + gConnecting[this.authURI] = false; + if (!("_browserRequest" in this)) { + return; + } + + this._browserRequest._active = false; + if ("_listener" in this._browserRequest) { + this._browserRequest._listener._cleanUp(); + } + delete this._browserRequest; + }, + + onAuthorizationReceived: function(aData) { + this.log.info("authorization received" + aData); + let results = parseURLData(aData); + if (this.responseType == "code" && results.code) { + this.requestAccessToken(results.code, OAuth2.CODE_AUTHORIZATION); + } else if (this.responseType == "token") { + this.onAccessTokenReceived(JSON.stringify(results)); + } + else + this.onAuthorizationFailed(null, aData); + }, + + onAuthorizationFailed: function(aError, aData) { + this.connectFailureCallback(aData); + }, + + requestAccessToken: function requestAccessToken(aCode, aType) { + let params = [ + ["client_id", this.consumerKey], + ["client_secret", this.consumerSecret], + ["grant_type", aType], + ]; + + if (aType == OAuth2.CODE_AUTHORIZATION) { + params.push(["code", aCode]); + params.push(["redirect_uri", this.completionURI]); + } else if (aType == OAuth2.CODE_REFRESH) { + params.push(["refresh_token", aCode]); + } + + let options = { + postData: params, + onLoad: this.onAccessTokenReceived.bind(this), + onError: this.onAccessTokenFailed.bind(this) + } + httpRequest(this.tokenURI, options); + }, + + onAccessTokenFailed: function onAccessTokenFailed(aError, aData) { + if (aError != "offline") { + this.refreshToken = null; + } + this.connectFailureCallback(aData); + }, + + onAccessTokenReceived: function onRequestTokenReceived(aData) { + let result = JSON.parse(aData); + + this.accessToken = result.access_token; + if ("refresh_token" in result) { + this.refreshToken = result.refresh_token; + } + if ("expires_in" in result) { + this.tokenExpires = (new Date()).getTime() + (result.expires_in * 1000); + } else { + this.tokenExpires = Number.MAX_VALUE; + } + this.tokenType = result.token_type; + + this.connectSuccessCallback(); + } +}; diff --git a/mailnews/base/util/OAuth2Providers.jsm b/mailnews/base/util/OAuth2Providers.jsm new file mode 100644 index 000000000..37a750f46 --- /dev/null +++ b/mailnews/base/util/OAuth2Providers.jsm @@ -0,0 +1,77 @@ +/* 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/. */ + +/** + * Details of supported OAuth2 Providers. + */ +var EXPORTED_SYMBOLS = ["OAuth2Providers"]; + +var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; + +// map of hostnames to [issuer, scope] +var kHostnames = new Map([ + ["imap.googlemail.com", ["accounts.google.com", "https://mail.google.com/"]], + ["smtp.googlemail.com", ["accounts.google.com", "https://mail.google.com/"]], + ["imap.gmail.com", ["accounts.google.com", "https://mail.google.com/"]], + ["smtp.gmail.com", ["accounts.google.com", "https://mail.google.com/"]], + + ["imap.mail.ru", ["o2.mail.ru", "mail.imap"]], + ["smtp.mail.ru", ["o2.mail.ru", "mail.imap"]], +]); + +// map of issuers to appKey, appSecret, authURI, tokenURI + +// For the moment, these details are hard-coded, since Google does not +// provide dynamic client registration. Don't copy these values for your +// own application--register it yourself. This code (and possibly even the +// registration itself) will disappear when this is switched to dynamic +// client registration. +var kIssuers = new Map ([ + ["accounts.google.com", [ + '406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com', + 'kSmqreRr0qwBWJgbf5Y-PjSU', + 'https://accounts.google.com/o/oauth2/auth', + 'https://www.googleapis.com/oauth2/v3/token' + ]], + ["o2.mail.ru", [ + 'thunderbird', + 'I0dCAXrcaNFujaaY', + 'https://o2.mail.ru/login', + 'https://o2.mail.ru/token' + ]], +]); + +/** + * OAuth2Providers: Methods to lookup OAuth2 parameters for supported + * email providers. + */ +var OAuth2Providers = { + + /** + * Map a hostname to the relevant issuer and scope. + * + * @param aHostname String representing the url for an imap or smtp + * server (example "imap.googlemail.com"). + * + * @returns Array with [issuer, scope] for the hostname if found, + * else undefined. issuer is a string representing the + * organization, scope is an oauth parameter describing\ + * the required access level. + */ + getHostnameDetails: function (aHostname) { return kHostnames.get(aHostname);}, + + /** + * Map an issuer to OAuth2 account details. + * + * @param aIssuer The organization issuing oauth2 parameters, example + * "accounts.google.com". + * + * @return Array containing [appKey, appSecret, authURI, tokenURI] + * where appKey and appDetails are strings representing the + * account registered for Thunderbird with the organization, + * authURI and tokenURI are url strings representing + * endpoints to access OAuth2 authentication. + */ + getIssuerDetails: function (aIssuer) { return kIssuers.get(aIssuer);} +} diff --git a/mailnews/base/util/ServiceList.h b/mailnews/base/util/ServiceList.h new file mode 100644 index 000000000..cc98c13b5 --- /dev/null +++ b/mailnews/base/util/ServiceList.h @@ -0,0 +1,40 @@ +/* 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/. */ + +// IWYU pragma: private, include "mozilla/mailnews/Services.h" + +MOZ_SERVICE(AbManager, nsIAbManager, + "@mozilla.org/abmanager;1") +MOZ_SERVICE(AccountManager, nsIMsgAccountManager, + "@mozilla.org/messenger/account-manager;1") +MOZ_SERVICE(ComposeService, nsIMsgComposeService, + "@mozilla.org/messengercompose;1") +MOZ_SERVICE(CopyService, nsIMsgCopyService, + "@mozilla.org/messenger/messagecopyservice;1") +MOZ_SERVICE(DBService, nsIMsgDBService, + "@mozilla.org/msgDatabase/msgDBService;1") +MOZ_SERVICE(FilterService, nsIMsgFilterService, + "@mozilla.org/messenger/services/filters;1") +MOZ_SERVICE(HeaderParser, nsIMsgHeaderParser, + "@mozilla.org/messenger/headerparser;1") +MOZ_SERVICE(ImapService, nsIImapService, + "@mozilla.org/messenger/imapservice;1") +MOZ_SERVICE(ImportService, nsIImportService, + "@mozilla.org/import/import-service;1") +MOZ_SERVICE(MailNotifyService, mozINewMailNotificationService, + "@mozilla.org/newMailNotificationService;1") +MOZ_SERVICE(MailSession, nsIMsgMailSession, + "@mozilla.org/messenger/services/session;1") +MOZ_SERVICE(MimeConverter, nsIMimeConverter, + "@mozilla.org/messenger/mimeconverter;1") +MOZ_SERVICE(MFNService, nsIMsgFolderNotificationService, + "@mozilla.org/messenger/msgnotificationservice;1") +MOZ_SERVICE(NntpService, nsINntpService, + "@mozilla.org/messenger/nntpservice;1") +MOZ_SERVICE(Pop3Service, nsIPop3Service, + "@mozilla.org/messenger/popservice;1") +MOZ_SERVICE(SmtpService, nsISmtpService, + "@mozilla.org/messengercompose/smtp;1") +MOZ_SERVICE(TagService, nsIMsgTagService, + "@mozilla.org/messenger/tagservice;1") diff --git a/mailnews/base/util/Services.cpp b/mailnews/base/util/Services.cpp new file mode 100644 index 000000000..a10d44aa3 --- /dev/null +++ b/mailnews/base/util/Services.cpp @@ -0,0 +1,106 @@ +/* -*- 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 "mozilla/mailnews/Services.h" + +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsServiceManagerUtils.h" + +// All of the includes for the services we initiate here +#include "mozINewMailNotificationService.h" +#include "nsIAbManager.h" +#include "nsIImapService.h" +#include "nsIImportService.h" +#include "nsIMimeConverter.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgComposeService.h" +#include "nsIMsgCopyService.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsIMsgHeaderParser.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgTagService.h" +#include "nsINntpService.h" +#include "nsIPop3Service.h" +#include "nsISmtpService.h" + +namespace mozilla { +namespace services { + +namespace { +class ShutdownObserver final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static void EnsureInitialized(); +private: + ~ShutdownObserver() {} + + void ShutdownServices(); + static ShutdownObserver *sShutdownObserver; + static bool sShuttingDown; +}; + +bool ShutdownObserver::sShuttingDown = false; +ShutdownObserver *ShutdownObserver::sShutdownObserver = nullptr; +} + +#define MOZ_SERVICE(NAME, TYPE, CONTRACT_ID) \ + static TYPE *g##NAME = nullptr; \ + already_AddRefed<TYPE> Get##NAME() \ + { \ + ShutdownObserver::EnsureInitialized(); \ + if (!g##NAME) \ + { \ + nsCOMPtr<TYPE> os = do_GetService(CONTRACT_ID); \ + os.forget(&g##NAME); \ + MOZ_ASSERT(g##NAME, "This service is unexpectedly missing."); \ + } \ + nsCOMPtr<TYPE> ret = g##NAME; \ + return ret.forget(); \ + } +#include "mozilla/mailnews/ServiceList.h" +#undef MOZ_SERVICE + +NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver) + +NS_IMETHODIMP ShutdownObserver::Observe(nsISupports *aSubject, + const char *aTopic, const char16_t *aData) +{ + if (!strcmp(aTopic, "xpcom-shutdown-threads")) + ShutdownServices(); + return NS_OK; +} + +void ShutdownObserver::EnsureInitialized() +{ + MOZ_ASSERT(!sShuttingDown, "It is illegal to use this code after shutdown!"); + if (!sShutdownObserver) + { + sShutdownObserver = new ShutdownObserver; + sShutdownObserver->AddRef(); + nsCOMPtr<nsIObserverService> obs(mozilla::services::GetObserverService()); + MOZ_ASSERT(obs, "This should never be null"); + obs->AddObserver(sShutdownObserver, "xpcom-shutdown-threads", false); + } +} + +void ShutdownObserver::ShutdownServices() +{ + sShuttingDown = true; + MOZ_ASSERT(sShutdownObserver, "Shutting down twice?"); + sShutdownObserver->Release(); + sShutdownObserver = nullptr; +#define MOZ_SERVICE(NAME, TYPE, CONTRACT_ID) NS_IF_RELEASE(g##NAME); +#include "mozilla/mailnews/ServiceList.h" +#undef MOZ_SERVICE +} + +} // namespace services +} // namespace mozilla diff --git a/mailnews/base/util/Services.h b/mailnews/base/util/Services.h new file mode 100644 index 000000000..217cecf32 --- /dev/null +++ b/mailnews/base/util/Services.h @@ -0,0 +1,26 @@ +/* -*- 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 mozilla_mailnews_Services_h +#define mozilla_mailnews_Services_h + +#include "mozilla/Services.h" + +#define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) class TYPE; +#include "mozilla/mailnews/ServiceList.h" +#undef MOZ_SERVICE + +namespace mozilla { +namespace services { + +#define MOZ_SERVICE(NAME, TYPE, SERVICE_CID) \ + already_AddRefed<TYPE> Get##NAME(); +#include "ServiceList.h" +#undef MOZ_SERVICE + +} // namespace services +} // namespace mozilla + +#endif diff --git a/mailnews/base/util/StringBundle.js b/mailnews/base/util/StringBundle.js new file mode 100644 index 000000000..a1e10ec62 --- /dev/null +++ b/mailnews/base/util/StringBundle.js @@ -0,0 +1,189 @@ +/* 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/. */ + +this.EXPORTED_SYMBOLS = ["StringBundle"]; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * A string bundle. + * + * This object presents two APIs: a deprecated one that is equivalent to the API + * for the stringbundle XBL binding, to make it easy to switch from that binding + * to this module, and a new one that is simpler and easier to use. + * + * The benefit of this module over the XBL binding is that it can also be used + * in JavaScript modules and components, not only in chrome JS. + * + * To use this module, import it, create a new instance of StringBundle, + * and then use the instance's |get| and |getAll| methods to retrieve strings + * (you can get both plain and formatted strings with |get|): + * + * let strings = + * new StringBundle("chrome://example/locale/strings.properties"); + * let foo = strings.get("foo"); + * let barFormatted = strings.get("bar", [arg1, arg2]); + * for (let string of strings.getAll()) + * dump (string.key + " = " + string.value + "\n"); + * + * @param url {String} + * the URL of the string bundle + */ +function StringBundle(url) { + this.url = url; +} + +StringBundle.prototype = { + /** + * the locale associated with the application + * @type nsILocale + * @private + */ + get _appLocale() { + try { + return Services.locale.getApplicationLocale(); + } + catch(ex) { + return null; + } + }, + + /** + * the wrapped nsIStringBundle + * @type nsIStringBundle + * @private + */ + get _stringBundle() { + let stringBundle = Services.strings.createBundle(this.url, this._appLocale); + this.__defineGetter__("_stringBundle", () => stringBundle); + return this._stringBundle; + }, + + + // the new API + + /** + * the URL of the string bundle + * @type String + */ + _url: null, + get url() { + return this._url; + }, + set url(newVal) { + this._url = newVal; + delete this._stringBundle; + }, + + /** + * Get a string from the bundle. + * + * @param key {String} + * the identifier of the string to get + * @param args {array} [optional] + * an array of arguments that replace occurrences of %S in the string + * + * @returns {String} the value of the string + */ + get: function(key, args) { + if (args) + return this.stringBundle.formatStringFromName(key, args, args.length); + else + return this.stringBundle.GetStringFromName(key); + }, + + /** + * Get all the strings in the bundle. + * + * @returns {Array} + * an array of objects with key and value properties + */ + getAll: function() { + let strings = []; + + // FIXME: for performance, return an enumerable array that wraps the string + // bundle's nsISimpleEnumerator (does JavaScript already support this?). + + let enumerator = this.stringBundle.getSimpleEnumeration(); + + while (enumerator.hasMoreElements()) { + // We could simply return the nsIPropertyElement objects, but I think + // it's better to return standard JS objects that behave as consumers + // expect JS objects to behave (f.e. you can modify them dynamically). + let string = enumerator.getNext() + .QueryInterface(Components.interfaces.nsIPropertyElement); + strings.push({ key: string.key, value: string.value }); + } + + return strings; + }, + + + // the deprecated XBL binding-compatible API + + /** + * the URL of the string bundle + * @deprecated because its name doesn't make sense outside of an XBL binding + * @type String + */ + get src() { + return this.url; + }, + set src(newVal) { + this.url = newVal; + }, + + /** + * the locale associated with the application + * @deprecated because it has never been used outside the XBL binding itself, + * and consumers should obtain it directly from the locale service anyway. + * @type nsILocale + */ + get appLocale() { + return this._appLocale; + }, + + /** + * the wrapped nsIStringBundle + * @deprecated because this module should provide all necessary functionality + * @type nsIStringBundle + * + * If you do ever need to use this, let the authors of this module know why + * so they can surface functionality for your use case in the module itself + * and you don't have to access this underlying XPCOM component. + */ + get stringBundle() { + return this._stringBundle; + }, + + /** + * Get a string from the bundle. + * @deprecated use |get| instead + * + * @param key {String} + * the identifier of the string to get + * + * @returns {String} + * the value of the string + */ + getString: function(key) { + return this.get(key); + }, + + /** + * Get a formatted string from the bundle. + * @deprecated use |get| instead + * + * @param key {string} + * the identifier of the string to get + * @param args {array} + * an array of arguments that replace occurrences of %S in the string + * + * @returns {String} + * the formatted value of the string + */ + getFormattedString: function(key, args) { + return this.get(key, args); + } +} diff --git a/mailnews/base/util/errUtils.js b/mailnews/base/util/errUtils.js new file mode 100644 index 000000000..21b205b82 --- /dev/null +++ b/mailnews/base/util/errUtils.js @@ -0,0 +1,309 @@ +/* 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/. */ + +/** + * This file contains helper methods for debugging -- things like logging + * exception objects, dumping DOM nodes, Events, and generic object dumps. + */ + +this.EXPORTED_SYMBOLS = ["logObject", "logException", "logElement", "logEvent", + "errorWithDebug"]; + +/** + * Report on an object to stdout. + * @param aObj the object to be dumped + * @param aName the name of the object, for informational purposes + */ +function logObject(aObj, aName) { + dump("Dumping Object: " + aName + "\n"); + stringifier.dumpObj(aObj, aName); +} + +/** + * Log an exception to stdout. This function should not be called in + * expected circumstances. + * @param aException the exception to log + * @param [aRethrow] set to true to rethrow the exception after logging + * @param [aMsg] optional message to log + */ +function logException(aException, aRethrow, aMsg) { + stringifier.dumpException(aException, aMsg); + + if (aMsg) + Components.utils.reportError(aMsg); + Components.utils.reportError(aException); + + if (aRethrow) + throw aException; +} + +/** + * Log an DOM element to stdout. + * @param aElement the DOM element to dump + */ +function logElement(aElement) { + stringifier.dumpDOM(aElement); +} + +/** + * Log an DOM event to stdout. + * @param aEvent the DOM event object to dump + */ +function logEvent(aEvent) { + stringifier.dumpEvent(aEvent); +} + +/** + * Dump the current stack and return an Error suitable for throwing. We return + * the new Error so that your code can use a "throw" statement which makes it + * obvious to syntactic analysis that there is an exit occuring at that point. + * + * Example: + * throw errorWithDebug("I did not expect this!"); + * + * @param aString The message payload for the exception. + */ +function errorWithDebug(aString) { + dump("PROBLEM: " + aString + "\n"); + dump("CURRENT STACK (and throwing):\n"); + // skip this frame. + dump(stringifier.getStack(1)); + return new Error(aString); +} + +function Stringifier() {}; + +Stringifier.prototype = { + dumpObj: function (o, name) { + this._reset(); + this._append(this.objectTreeAsString(o, true, true, 0)); + dump(this._asString()); + }, + + dumpDOM: function(node, level, recursive) { + this._reset(); + let s = this.DOMNodeAsString(node, level, recursive); + dump(s); + }, + + dumpEvent: function(event) { + dump(this.eventAsString(event)); + }, + + dumpException: function(exc, message) { + dump(exc + "\n"); + this._reset(); + if (message) + this._append("Exception (" + message + ")\n"); + + this._append("-- Exception object --\n"); + this._append(this.objectTreeAsString(exc)); + if (exc.stack) { + this._append("-- Stack Trace --\n"); + this._append(exc.stack); // skip dumpException and logException + } + dump(this._asString()); + }, + + _reset: function() { + this._buffer = []; + }, + + _append: function(string) { + this._buffer.push(string); + }, + + _asString: function() { + let str = this._buffer.join(''); + this._reset(); + return str; + }, + + getStack: function(skipCount) { + if (!((typeof Components == "object") && + (typeof Components.classes == "object"))) + return "No stack trace available."; + if (typeof(skipCount) === undefined) + skipCount = 0; + + let frame = Components.stack.caller; + let str = "<top>"; + + while (frame) { + if (skipCount > 0) { + // Skip this frame. + skipCount -= 1; + } + else { + // Include the data from this frame. + let name = frame.name ? frame.name : "[anonymous]"; + str += "\n" + name + "@" + frame.filename + ':' + frame.lineNumber; + } + frame = frame.caller; + } + return str + "\n"; + }, + + objectTreeAsString: function(o, recurse, compress, level) { + let s = ""; + if (recurse === undefined) + recurse = 0; + if (level === undefined) + level = 0; + if (compress === undefined) + compress = true; + let pfx = ""; + + for (var junk = 0; junk < level; junk++) + pfx += (compress) ? "| " : "| "; + + let tee = (compress) ? "+ " : "+- "; + + if (typeof(o) != "object") { + s += pfx + tee + " (" + typeof(o) + ") " + o + "\n"; + } + else { + for (let i in o) { + try { + let t = typeof o[i]; + switch (t) { + case "function": + let sfunc = String(o[i]).split("\n"); + if (sfunc[2] == " [native code]") + sfunc = "[native code]"; + else + sfunc = sfunc.length + " lines"; + s += pfx + tee + i + " (function) " + sfunc + "\n"; + break; + case "object": + s += pfx + tee + i + " (object) " + o[i] + "\n"; + if (!compress) + s += pfx + "|\n"; + if ((i != "parent") && (recurse)) + s += this.objectTreeAsString(o[i], recurse - 1, + compress, level + 1); + break; + case "string": + if (o[i].length > 200) + s += pfx + tee + i + " (" + t + ") " + o[i].length + " chars\n"; + else + s += pfx + tee + i + " (" + t + ") '" + o[i] + "'\n"; + break; + default: + s += pfx + tee + i + " (" + t + ") " + o[i] + "\n"; + } + } catch (ex) { + s += pfx + tee + " (exception) " + ex + "\n"; + } + if (!compress) + s += pfx + "|\n"; + } + } + s += pfx + "*\n"; + return s; + }, + + _repeatStr: function (str, aCount) { + let res = ""; + while (--aCount >= 0) + res += str; + return res; + }, + + DOMNodeAsString: function(node, level, recursive) { + if (level === undefined) + level = 0 + if (recursive === undefined) + recursive = true; + this._append(this._repeatStr(" ", 2*level) + "<" + node.nodeName + "\n"); + + if (node.nodeType == 3) { + this._append(this._repeatStr(" ", (2*level) + 4) + node.nodeValue + "'\n"); + } + else { + if (node.attributes) { + for (let i = 0; i < node.attributes.length; i++) { + this._append(this._repeatStr( + " ", (2*level) + 4) + node.attributes[i].nodeName + + "='" + node.attributes[i].nodeValue + "'\n"); + } + } + if (node.childNodes.length == 0) { + this._append(this._repeatStr(" ", (2*level)) + "/>\n"); + } + else if (recursive) { + this._append(this._repeatStr(" ", (2*level)) + ">\n"); + for (let i = 0; i < node.childNodes.length; i++) { + this._append(this.DOMNodeAsString(node.childNodes[i], level + 1)); + } + this._append(this._repeatStr(" ", 2*level) + "</" + node.nodeName + ">\n"); + } + } + return this._asString(); + }, + + eventAsString: function (event) { + this._reset(); + this._append("-EVENT --------------------------\n"); + this._append("type: " + event.type + "\n"); + this._append("eventPhase: " + event.eventPhase + "\n"); + if ("charCode" in event) { + this._append("charCode: " + event.charCode + "\n"); + if ("name" in event) + this._append("str(charCode): '" + String.fromCharCode(event.charCode) + "'\n"); + } + if (("target" in event) && event.target) { + this._append("target: " + event.target + "\n"); + if ("nodeName" in event.target) + this._append("target.nodeName: " + event.target.nodeName + "\n"); + if ("getAttribute" in event.target) + this._append("target.id: " + event.target.getAttribute("id") + "\n"); + } + if (("currentTarget" in event) && event.currentTarget) { + this._append("currentTarget: " + event.currentTarget + "\n"); + if ("nodeName" in event.currentTarget) + this._append("currentTarget.nodeName: "+ event.currentTarget.nodeName + "\n"); + if ("getAttribute" in event.currentTarget) + this._append("currentTarget.id: "+ event.currentTarget.getAttribute("id") + "\n"); + } + if (("originalTarget" in event) && event.originalTarget) { + this._append("originalTarget: " + event.originalTarget + "\n"); + if ("nodeName" in event.originalTarget) + this._append("originalTarget.nodeName: "+ event.originalTarget.nodeName + "\n"); + if ("getAttribute" in event.originalTarget) + this._append("originalTarget.id: "+ event.originalTarget.getAttribute("id") + "\n"); + } + let names = [ + "bubbles", + "cancelable", + "detail", + "button", + "keyCode", + "isChar", + "shiftKey", + "altKey", + "ctrlKey", + "metaKey", + "clientX", + "clientY", + "screenX", + "screenY", + "layerX", + "layerY", + "isTrusted", + "timeStamp", + "currentTargetXPath", + "targetXPath", + "originalTargetXPath" + ]; + for (let i in names) { + if (names[i] in event) + this._append(names[i] + ": " + event[names[i]] + "\n"); + } + this._append("-------------------------------------\n"); + return this._asString(); + } +}; + +var stringifier = new Stringifier(); diff --git a/mailnews/base/util/folderUtils.jsm b/mailnews/base/util/folderUtils.jsm new file mode 100644 index 000000000..62fb7700b --- /dev/null +++ b/mailnews/base/util/folderUtils.jsm @@ -0,0 +1,234 @@ +/* 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/. */ + +/** + * This file contains helper methods for dealing with nsIMsgFolders. + */ + +this.EXPORTED_SYMBOLS = ["getFolderProperties", "getSpecialFolderString", + "getFolderFromUri", "allAccountsSorted", + "getMostRecentFolders", "folderNameCompare"]; + +Components.utils.import("resource:///modules/mailServices.js"); +Components.utils.import("resource:///modules/iteratorUtils.jsm"); + +/** + * Returns a string representation of a folder's "special" type. + * + * @param aFolder the nsIMsgFolder whose special type should be returned + */ +function getSpecialFolderString(aFolder) { + const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags; + let flags = aFolder.flags; + if (flags & nsMsgFolderFlags.Inbox) + return "Inbox"; + if (flags & nsMsgFolderFlags.Trash) + return "Trash"; + if (flags & nsMsgFolderFlags.Queue) + return "Outbox"; + if (flags & nsMsgFolderFlags.SentMail) + return "Sent"; + if (flags & nsMsgFolderFlags.Drafts) + return "Drafts"; + if (flags & nsMsgFolderFlags.Templates) + return "Templates"; + if (flags & nsMsgFolderFlags.Junk) + return "Junk"; + if (flags & nsMsgFolderFlags.Archive) + return "Archive"; + if (flags & nsMsgFolderFlags.Virtual) + return "Virtual"; + return "none"; +} + +/** + * This function is meant to be used with trees. It returns the property list + * for all of the common properties that css styling is based off of. + * + * @param nsIMsgFolder aFolder the folder whose properties should be returned + * as a string + * @param bool aOpen true if the folder is open/expanded + * + * @return A string of the property names, delimited by space. + */ +function getFolderProperties(aFolder, aOpen) { + const nsIMsgFolder = Components.interfaces.nsIMsgFolder; + let properties = []; + + properties.push("folderNameCol"); + + properties.push("serverType-" + aFolder.server.type); + + // set the SpecialFolder attribute + properties.push("specialFolder-" + getSpecialFolderString(aFolder)); + + // Now set the biffState + switch (aFolder.biffState) { + case nsIMsgFolder.nsMsgBiffState_NewMail: + properties.push("biffState-NewMail"); + break; + case nsIMsgFolder.nsMsgBiffState_NoMail: + properties.push("biffState-NoMail"); + break; + default: + properties.push("biffState-UnknownMail"); + } + + properties.push("isSecure-" + aFolder.server.isSecure); + + // A folder has new messages, or a closed folder or any subfolder has new messages. + if (aFolder.hasNewMessages || + (!aOpen && aFolder.hasSubFolders && aFolder.hasFolderOrSubfolderNewMessages)) + properties.push("newMessages-true"); + + if (aFolder.isServer) { + properties.push("isServer-true"); + } + else + { + // We only set this if we're not a server + let shallowUnread = aFolder.getNumUnread(false); + if (shallowUnread > 0) { + properties.push("hasUnreadMessages-true"); + } + else + { + // Make sure that shallowUnread isn't negative + shallowUnread = 0; + } + let deepUnread = aFolder.getNumUnread(true); + if (deepUnread - shallowUnread > 0) + properties.push("subfoldersHaveUnreadMessages-true"); + } + + properties.push("noSelect-" + aFolder.noSelect); + properties.push("imapShared-" + aFolder.imapShared); + + return properties.join(" "); +} + +/** + * Returns a folder for a particular uri + * + * @param aUri the rdf uri of the folder to return + */ +function getFolderFromUri(aUri) { + const Cc = Components.classes; + const Ci = Components.interfaces; + return Cc["@mozilla.org/mail/folder-lookup;1"]. + getService(Ci.nsIFolderLookupService).getFolderById(aUri); +} + +/** + * Returns the sort order value based on the server type to be used for sorting. + * The servers (accounts) go in the following order: + * (0) default account, (1) other mail accounts, (2) Local Folders, + * (3) IM accounts, (4) RSS, (5) News, (9) others (no server) + * This ordering is encoded in the .sortOrder property of each server type. + * + * @param aServer the server object to be tested + */ +function getServerSortOrder(aServer) { + // If there is no server sort this object to the end. + if (!aServer) + return 999999999; + + // Otherwise get the server sort order from the Account manager. + return MailServices.accounts.getSortOrder(aServer); +} + +/** + * Compares the passed in accounts according to their precedence. + */ +function compareAccounts(aAccount1, aAccount2) { + return getServerSortOrder(aAccount1.incomingServer) + - getServerSortOrder(aAccount2.incomingServer); +} + +/** + * Returns a list of accounts sorted by server type. + * + * @param aExcludeIMAccounts Remove IM accounts from the list? + */ +function allAccountsSorted(aExcludeIMAccounts) { + // Get the account list, and add the proper items. + let accountList = toArray(fixIterator(MailServices.accounts.accounts, + Components.interfaces.nsIMsgAccount)); + + // This is a HACK to work around bug 41133. If we have one of the + // dummy "news" accounts there, that account won't have an + // incomingServer attached to it, and everything will blow up. + accountList = accountList.filter(function hasServer(a) { + return a.incomingServer; + }); + + // Remove IM servers. + if (aExcludeIMAccounts) { + accountList = accountList.filter(function(a) { + return a.incomingServer.type != "im"; + }); + } + + return accountList.sort(compareAccounts); +} + +/** + * Returns the most recently used/modified folders from the passed in list. + * + * @param aFolderList The array of nsIMsgFolders to search for recent folders. + * @param aMaxHits How many folders to return. + * @param aTimeProperty Which folder time property to use. + * Use "MRMTime" for most recently modified time. + * Use "MRUTime" for most recently used time. + */ +function getMostRecentFolders(aFolderList, aMaxHits, aTimeProperty) { + let recentFolders = []; + + /** + * This sub-function will add a folder to the recentFolders array if it + * is among the aMaxHits most recent. If we exceed aMaxHits folders, + * it will pop the oldest folder, ensuring that we end up with the + * right number. + * + * @param aFolder The folder to check for recency. + */ + let oldestTime = 0; + function addIfRecent(aFolder) { + let time = 0; + try { + time = Number(aFolder.getStringProperty(aTimeProperty)) || 0; + } catch(e) {} + if (time <= oldestTime) + return; + + if (recentFolders.length == aMaxHits) { + recentFolders.sort(function sort_folders_by_time(a, b) { + return a.time < b.time; }); + recentFolders.pop(); + oldestTime = recentFolders[recentFolders.length - 1].time; + } + recentFolders.push({ folder: aFolder, time: time }); + } + + for (let folder of aFolderList) { + addIfRecent(folder); + } + + return recentFolders.map(function (f) { return f.folder; }); +} + +/** + * A locale dependent comparison function to produce a case-insensitive sort order + * used to sort folder names. + * Returns positive number if aString1 > aString2, negative number if aString1 > aString2, + * otherwise 0. + * + * @param aString1 first string to compare + * @param aString2 second string to compare + */ +function folderNameCompare(aString1, aString2) { + // TODO: improve this as described in bug 992651. + return aString1.toLocaleLowerCase() + .localeCompare(aString2.toLocaleLowerCase()); +} diff --git a/mailnews/base/util/hostnameUtils.jsm b/mailnews/base/util/hostnameUtils.jsm new file mode 100644 index 000000000..e2d0ee4ff --- /dev/null +++ b/mailnews/base/util/hostnameUtils.jsm @@ -0,0 +1,341 @@ +/* -*- 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/. */ + +/** + * Generic shared utility code for checking of IP and hostname validity. + */ + +this.EXPORTED_SYMBOLS = [ "isLegalHostNameOrIP", + "isLegalHostName", + "isLegalIPv4Address", + "isLegalIPv6Address", + "isLegalIPAddress", + "isLegalLocalIPAddress", + "cleanUpHostName", + "kMinPort", + "kMaxPort" ]; + +var kMinPort = 1; +var kMaxPort = 65535; + +/** + * Check if aHostName is an IP address or a valid hostname. + * + * @param aHostName The string to check for validity. + * @param aAllowExtendedIPFormats Allow hex/octal formats in addition to decimal. + * @return Unobscured host name if aHostName is valid. + * Returns null if it's not. + */ +function isLegalHostNameOrIP(aHostName, aAllowExtendedIPFormats) +{ + /* + RFC 1123: + Whenever a user inputs the identity of an Internet host, it SHOULD + be possible to enter either (1) a host domain name or (2) an IP + address in dotted-decimal ("#.#.#.#") form. The host SHOULD check + the string syntactically for a dotted-decimal number before + looking it up in the Domain Name System. + */ + + return isLegalIPAddress(aHostName, aAllowExtendedIPFormats) || + isLegalHostName(aHostName); +} + +/** + * Check if aHostName is a valid hostname. + * + * @return The host name if it is valid. + * Returns null if it's not. + */ +function isLegalHostName(aHostName) +{ + /* + RFC 952: + A "name" (Net, Host, Gateway, or Domain name) is a text string up + to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus + sign (-), and period (.). Note that periods are only allowed when + they serve to delimit components of "domain style names". (See + RFC-921, "Domain Name System Implementation Schedule", for + background). No blank or space characters are permitted as part of a + name. No distinction is made between upper and lower case. The first + character must be an alpha character. The last character must not be + a minus sign or period. + + RFC 1123: + The syntax of a legal Internet host name was specified in RFC-952 + [DNS:4]. One aspect of host name syntax is hereby changed: the + restriction on the first character is relaxed to allow either a + letter or a digit. Host software MUST support this more liberal + syntax. + + Host software MUST handle host names of up to 63 characters and + SHOULD handle host names of up to 255 characters. + + RFC 1034: + Relative names are either taken relative to a well known origin, or to a + list of domains used as a search list. Relative names appear mostly at + the user interface, where their interpretation varies from + implementation to implementation, and in master files, where they are + relative to a single origin domain name. The most common interpretation + uses the root "." as either the single origin or as one of the members + of the search list, so a multi-label relative name is often one where + the trailing dot has been omitted to save typing. + + Since a complete domain name ends with the root label, this leads to + a printed form which ends in a dot. + */ + + const hostPattern = /^(([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.?$/i; + return ((aHostName.length <= 255) && hostPattern.test(aHostName)) ? aHostName : null; +} + +/** + * Check if aHostName is a valid IPv4 address. + * + * @param aHostName The string to check for validity. + * @param aAllowExtendedIPFormats If false, only IPv4 addresses in the common + decimal format (4 components, each up to 255) + * will be accepted, no hex/octal formats. + * @return Unobscured canonicalized address if aHostName is an IPv4 address. + * Returns null if it's not. + */ +function isLegalIPv4Address(aHostName, aAllowExtendedIPFormats) +{ + // Scammers frequently obscure the IP address by encoding each component as + // decimal, octal, hex or in some cases a mix match of each. There can even + // be less than 4 components where the last number covers the missing components. + // See the test at mailnews/base/test/unit/test_hostnameUtils.js for possible + // combinations. + + if (!aHostName) + return null; + + // Break the IP address down into individual components. + let ipComponents = aHostName.split("."); + let componentCount = ipComponents.length; + if (componentCount > 4 || (componentCount < 4 && !aAllowExtendedIPFormats)) + return null; + + /** + * Checks validity of an IP address component. + * + * @param aValue The component string. + * @param aWidth How many components does this string cover. + * @return The value of the component in decimal if it is valid. + * Returns null if it's not. + */ + const kPowersOf256 = [ 1, 256, 65536, 16777216, 4294967296 ]; + function isLegalIPv4Component(aValue, aWidth) { + let component; + // Is the component decimal? + if (/^(0|([1-9][0-9]{0,9}))$/.test(aValue)) { + component = parseInt(aValue, 10); + } else if (aAllowExtendedIPFormats) { + // Is the component octal? + if (/^(0[0-7]{1,12})$/.test(aValue)) + component = parseInt(aValue, 8); + // Is the component hex? + else if (/^(0x[0-9a-f]{1,8})$/i.test(aValue)) + component = parseInt(aValue, 16); + else + return null; + } else { + return null; + } + + // Make sure the component in not larger than the expected maximum. + if (component >= kPowersOf256[aWidth]) + return null; + + return component; + } + + for (let i = 0; i < componentCount; i++) { + // If we are on the last supplied component but we do not have 4, + // the last one covers the remaining ones. + let componentWidth = (i == componentCount - 1 ? 4 - i : 1); + let componentValue = isLegalIPv4Component(ipComponents[i], componentWidth); + if (componentValue == null) + return null; + + // If we have a component spanning multiple ones, split it. + for (let j = 0; j < componentWidth; j++) { + ipComponents[i + j] = (componentValue >> ((componentWidth - 1 - j) * 8)) & 255; + } + } + + // First component of zero is not valid. + if (ipComponents[0] == 0) + return null; + + return ipComponents.join("."); +} + +/** + * Check if aHostName is a valid IPv6 address. + * + * @param aHostName The string to check for validity. + * @return Unobscured canonicalized address if aHostName is an IPv6 address. + * Returns null if it's not. + */ +function isLegalIPv6Address(aHostName) +{ + if (!aHostName) + return null; + + // Break the IP address down into individual components. + let ipComponents = aHostName.toLowerCase().split(":"); + + // Make sure there are at least 3 components. + if (ipComponents.length < 3) + return null; + + let ipLength = ipComponents.length - 1; + + // Take care if the last part is written in decimal using dots as separators. + let lastPart = isLegalIPv4Address(ipComponents[ipLength], false); + if (lastPart) + { + let lastPartComponents = lastPart.split("."); + // Convert it into standard IPv6 components. + ipComponents[ipLength] = + ((lastPartComponents[0] << 8) | lastPartComponents[1]).toString(16); + ipComponents[ipLength + 1] = + ((lastPartComponents[2] << 8) | lastPartComponents[3]).toString(16); + } + + // Make sure that there is only one empty component. + let emptyIndex; + for (let i = 1; i < ipComponents.length - 1; i++) + { + if (ipComponents[i] == "") + { + // If we already found an empty component return null. + if (emptyIndex) + return null; + + emptyIndex = i; + } + } + + // If we found an empty component, extend it. + if (emptyIndex) + { + ipComponents[emptyIndex] = 0; + + // Add components so we have a total of 8. + for (let count = ipComponents.length; count < 8; count++) + ipComponents.splice(emptyIndex, 0, 0); + } + + // Make sure there are 8 components. + if (ipComponents.length != 8) + return null; + + // Format all components to 4 character hex value. + for (let i = 0; i < ipComponents.length; i++) + { + if (ipComponents[i] == "") + ipComponents[i] = 0; + + // Make sure the component is a number and it isn't larger than 0xffff. + if (/^[0-9a-f]{1,4}$/.test(ipComponents[i])) { + ipComponents[i] = parseInt(ipComponents[i], 16); + if (isNaN(ipComponents[i]) || ipComponents[i] > 0xffff) + return null; + } + else { + return null; + } + + // Pad the component with 0:s. + ipComponents[i] = ("0000" + ipComponents[i].toString(16)).substr(-4); + } + + // TODO: support Zone indices in Link-local addresses? Currently they are rejected. + // http://en.wikipedia.org/wiki/IPv6_address#Link-local_addresses_and_zone_indices + + let hostName = ipComponents.join(":"); + // Treat 0000:0000:0000:0000:0000:0000:0000:0000 as an invalid IPv6 address. + return (hostName != "0000:0000:0000:0000:0000:0000:0000:0000") ? + hostName : null; +} + +/** + * Check if aHostName is a valid IP address (IPv4 or IPv6). + * + * @param aHostName The string to check for validity. + * @param aAllowExtendedIPFormats Allow hex/octal formats in addition to decimal. + * @return Unobscured canonicalized IPv4 or IPv6 address if it is valid, + * otherwise null. + */ +function isLegalIPAddress(aHostName, aAllowExtendedIPFormats) +{ + return isLegalIPv4Address(aHostName, aAllowExtendedIPFormats) || + isLegalIPv6Address(aHostName); +} + +/** + * Check if aIPAddress is a local or private IP address. + * + * @param aIPAddress A valid IP address literal in canonical (unobscured) form. + * @return True if it is a local/private IPv4 or IPv6 address, + * otherwise false. + * + * Note: if the passed in address is not in canonical (unobscured form), + * the result may be wrong. + */ +function isLegalLocalIPAddress(aIPAddress) +{ + // IPv4 address? + let ipComponents = aIPAddress.split("."); + if (ipComponents.length == 4) + { + // Check if it's a local or private IPv4 address. + return ipComponents[0] == 10 || + ipComponents[0] == 127 || // loopback address + (ipComponents[0] == 192 && ipComponents[1] == 168) || + (ipComponents[0] == 169 && ipComponents[1] == 254) || + (ipComponents[0] == 172 && ipComponents[1] >= 16 && ipComponents[1] < 32); + } + + // IPv6 address? + ipComponents = aIPAddress.split(":"); + if (ipComponents.length == 8) + { + // ::1/128 - localhost + if (ipComponents[0] == "0000" && ipComponents[1] == "0000" && + ipComponents[2] == "0000" && ipComponents[3] == "0000" && + ipComponents[4] == "0000" && ipComponents[5] == "0000" && + ipComponents[6] == "0000" && ipComponents[7] == "0001") + return true; + + // fe80::/10 - link local addresses + if (ipComponents[0] == "fe80") + return true; + + // fc00::/7 - unique local addresses + if (ipComponents[0].startsWith("fc") || // usage has not been defined yet + ipComponents[0].startsWith("fd")) + return true; + + return false; + } + + return false; +} + +/** + * Clean up the hostname or IP. Usually used to sanitize a value input by the user. + * It is usually applied before we know if the hostname is even valid. + * + * @param aHostName The hostname or IP string to clean up. + */ +function cleanUpHostName(aHostName) +{ + // TODO: Bug 235312: if UTF8 string was input, convert to punycode using convertUTF8toACE() + // but bug 563172 needs resolving first. + return aHostName.trim(); +} diff --git a/mailnews/base/util/iteratorUtils.jsm b/mailnews/base/util/iteratorUtils.jsm new file mode 100644 index 000000000..266c4d7b6 --- /dev/null +++ b/mailnews/base/util/iteratorUtils.jsm @@ -0,0 +1,166 @@ +/* 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/. */ + +/** + * This file contains helper methods for dealing with XPCOM iterators (arrays + * and enumerators) in JS-friendly ways. + */ + +this.EXPORTED_SYMBOLS = ["fixIterator", "toXPCOMArray", "toArray"]; + +Components.utils.import("resource://gre/modules/Deprecated.jsm"); + +var Ci = Components.interfaces; + +var JS_HAS_SYMBOLS = typeof Symbol === "function"; +var ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator"; + +/** + * This function will take a number of objects and convert them to an array. + * + * Currently, we support the following objects: + * Anything you can for (let x of aObj) on + * (e.g. toArray(fixIterator(enum))[4], + * also a NodeList from element.childNodes) + * + * @param aObj The object to convert + */ +function toArray(aObj) { + if (ITERATOR_SYMBOL in aObj) { + return Array.from(aObj); + } + + // We got something unexpected, notify the caller loudly. + throw new Error("An unsupported object sent to toArray: " + + (("toString" in aObj) ? aObj.toString() : aObj)); +} + +/** + * Given a JS array, JS iterator, or one of a variety of XPCOM collections or + * iterators, return a JS iterator suitable for use in a for...of expression. + * + * Currently, we support the following types of XPCOM iterators: + * nsIArray + * nsISupportsArray + * nsISimpleEnumerator + * + * This intentionally does not support nsIEnumerator as it is obsolete and + * no longer used in the base code. + * + * Note that old-style JS iterators are explicitly not supported in this + * method, as they are going away. For a limited time, the resulting iterator + * can be used in a for...in loop, but this is a legacy compatibility shim that + * will not work forever. See bug 1098412. + * + * @param aEnum the enumerator to convert + * @param aIface (optional) an interface to QI each object to prior to + * returning + * + * @note This returns an object that can be used in 'for...of' loops. + * Do not use 'for each...in'. 'for...in' may be used, but only as a + * legacy feature. + * This does *not* return an Array object. To create such an array, use + * let array = toArray(fixIterator(xpcomEnumerator)); + */ +function fixIterator(aEnum, aIface) { + // Minor internal details: to support both for (let x of fixIterator()) and + // for (let x in fixIterator()), we need to add in a __iterator__ kludge + // property. __iterator__ is to go away in bug 1098412; we could theoretically + // make it work beyond that by using Proxies, but that's far to go for + // something we want to get rid of anyways. + // Note that the new-style iterator uses Symbol.iterator to work, and anything + // that has Symbol.iterator works with for-of. + function makeDualIterator(newStyle) { + newStyle.__iterator__ = function() { + for (let item of newStyle) + yield item; + }; + return newStyle; + } + + // If the input is an array or something that sports Symbol.iterator, then + // the original input is sufficient to directly return. However, if we want + // to support the aIface parameter, we need to do a lazy version of Array.map. + if (Array.isArray(aEnum) || ITERATOR_SYMBOL in aEnum) { + if (!aIface) { + return makeDualIterator(aEnum); + } else { + return makeDualIterator((function*() { + for (let o of aEnum) + yield o.QueryInterface(aIface); + })()); + } + } + + let face = aIface || Ci.nsISupports; + // Figure out which kind of array object we have. + // First try nsIArray (covers nsIMutableArray too). + if (aEnum instanceof Ci.nsIArray) { + return makeDualIterator((function*() { + let count = aEnum.length; + for (let i = 0; i < count; i++) + yield aEnum.queryElementAt(i, face); + })()); + } + + // Try an nsISupportsArray. + // This object is deprecated, but we need to keep supporting it + // while anything in the base code (including mozilla-central) produces it. + if (aEnum instanceof Ci.nsISupportsArray) { + return makeDualIterator((function*() { + let count = aEnum.Count(); + for (let i = 0; i < count; i++) + yield aEnum.QueryElementAt(i, face); + })()); + } + + // How about nsISimpleEnumerator? This one is nice and simple. + if (aEnum instanceof Ci.nsISimpleEnumerator) { + return makeDualIterator((function*() { + while (aEnum.hasMoreElements()) + yield aEnum.getNext().QueryInterface(face); + })()); + } + + // We got something unexpected, notify the caller loudly. + throw new Error("An unsupported object sent to fixIterator: " + + (("toString" in aEnum) ? aEnum.toString() : aEnum)); +} + +/** + * This function takes an Array object and returns an XPCOM array + * of the desired type. It will *not* work if you extend Array.prototype. + * + * @param aArray the array (anything fixIterator supports) to convert to an XPCOM array + * @param aInterface the type of XPCOM array to convert + * + * @note The returned array is *not* dynamically updated. Changes made to the + * JS array after a call to this function will not be reflected in the + * XPCOM array. + */ +function toXPCOMArray(aArray, aInterface) { + if (aInterface.equals(Ci.nsISupportsArray)) { + Deprecated.warning("nsISupportsArray object is deprecated, avoid creating new ones.", + "https://developer.mozilla.org/en-US/docs/XPCOM_array_guide"); + let supportsArray = Components.classes["@mozilla.org/supports-array;1"] + .createInstance(Ci.nsISupportsArray); + for (let item of fixIterator(aArray)) { + supportsArray.AppendElement(item); + } + return supportsArray; + } + + if (aInterface.equals(Ci.nsIMutableArray)) { + let mutableArray = Components.classes["@mozilla.org/array;1"] + .createInstance(Ci.nsIMutableArray); + for (let item of fixIterator(aArray)) { + mutableArray.appendElement(item, false); + } + return mutableArray; + } + + // We got something unexpected, notify the caller loudly. + throw new Error("An unsupported interface requested from toXPCOMArray: " + + aInterface); +} diff --git a/mailnews/base/util/jsTreeSelection.js b/mailnews/base/util/jsTreeSelection.js new file mode 100644 index 000000000..0ba5ea42c --- /dev/null +++ b/mailnews/base/util/jsTreeSelection.js @@ -0,0 +1,654 @@ +/* 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/. */ + +this.EXPORTED_SYMBOLS = ['JSTreeSelection']; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +/** + * Partial nsITreeSelection implementation so that we can have nsMsgDBViews that + * exist only for message display but do not need to be backed by a full + * tree view widget. This could also hopefully be used for more xpcshell unit + * testing of the FolderDisplayWidget. It might also be useful for creating + * transient selections when right-click selection happens. + * + * Our current limitations: + * - We do not support any single selection modes. This is mainly because we + * need to look at the box object for that and we don't want to do it. + * - Timed selection. Our expected consumers don't use it. + * + * Our current laziness: + * - We aren't very precise about invalidation when it would be potentially + * complicated. The theory is that if there is a tree box object, it's + * probably native and the XPConnect overhead is probably a lot more than + * any potential savings, at least for now when the tree display is + * generally C++ XPCOM backed rather than JS XPCOM backed. Also, we + * aren't intended to actually be used with a real tree display; you should + * be using the C++ object in that case! + * + * If documentation is omitted for something, it is because we have little to + * add to the documentation of nsITreeSelection and really hope that our + * documentation tool will copy-down that documentation. + * + * This implementation attempts to mimic the behavior of nsTreeSelection. In + * a few cases, this leads to potentially confusing actions. I attempt to note + * when we are doing this and why we do it. + * + * Unit test is in mailnews/base/util/test_jsTreeSelection.js + */ +function JSTreeSelection(aTreeBoxObject) { + this._treeBoxObject = aTreeBoxObject; + + this._currentIndex = null; + this._shiftSelectPivot = null; + this._ranges = []; + this._count = 0; + + this._selectEventsSuppressed = false; +} +JSTreeSelection.prototype = { + /** + * The current nsITreeBoxObject, appropriately QueryInterfaced. May be null. + */ + _treeBoxObject: null, + + /** + * Where the focus rectangle (that little dotted thing) shows up. Just + * because something is focused does not mean it is actually selected. + */ + _currentIndex: null, + /** + * The view index where the shift is anchored when it is not (conceptually) + * the same as _currentIndex. This only happens when you perform a ranged + * selection. In that case, the start index of the ranged selection becomes + * the shift pivot (and the _currentIndex becomes the end of the ranged + * selection.) + * It gets cleared whenever the selection changes and it's not the result of + * a call to rangedSelect. + */ + _shiftSelectPivot: null, + /** + * A list of [lowIndexInclusive, highIndexInclusive] non-overlapping, + * non-adjacent 'tuples' sort in ascending order. + */ + _ranges: [], + /** + * The number of currently selected rows. + */ + _count: 0, + + // In the case of the stand-alone message window, there's no tree, but + // there's a view. + _view: null, + + get tree() { + return this._treeBoxObject; + }, + set tree(aTreeBoxObject) { + this._treeBoxObject = aTreeBoxObject; + }, + + set view(aView) { + this._view = aView; + }, + /** + * Although the nsITreeSelection documentation doesn't say, what this method + * is supposed to do is check if the seltype attribute on the XUL tree is any + * of the following: "single" (only a single row may be selected at a time, + * "cell" (a single cell may be selected), or "text" (the row gets selected + * but only the primary column shows up as selected.) + * + * @return false because we don't support single-selection. + */ + get single() { + return false; + }, + + _updateCount: function JSTreeSelection__updateCount() { + this._count = 0; + for (let [low, high] of this._ranges) { + this._count += high - low + 1; + } + }, + + get count() { + return this._count; + }, + + isSelected: function JSTreeSelection_isSelected(aViewIndex) { + for (let [low, high] of this._ranges) { + if (aViewIndex >= low && aViewIndex <= high) + return true; + } + return false; + }, + + /** + * Select the given row. It does nothing if that row was already selected. + */ + select: function JSTreeSelection_select(aViewIndex) { + // current index will provide our effective shift pivot + this._shiftSelectPivot = null; + this.currentIndex = aViewIndex; + + if (this._count == 1 && this._ranges[0][0] == aViewIndex) + return; + + this._count = 1; + this._ranges = [[aViewIndex, aViewIndex]]; + + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + + this._fireSelectionChanged(); + }, + + timedSelect: function JSTreeSelection_timedSelect(aIndex, aDelay) { + throw new Error("We do not implement timed selection."); + }, + + toggleSelect: function JSTreeSelection_toggleSelect(aIndex) { + this.currentIndex = aIndex; + // If nothing's selected, select aIndex + if (this._count == 0) { + this._count = 1; + this._ranges = [[aIndex, aIndex]]; + } + else for (let [iTupe, [low, high]] of this._ranges.entries()) { + // below the range? add it to the existing range or create a new one + if (aIndex < low) { + this._count++; + // is it just below an existing range? (range fusion only happens in the + // high case, not here.) + if (aIndex == low - 1) { + this._ranges[iTupe][0] = aIndex; + break; + } + // then it gets its own range + this._ranges.splice(iTupe, 0, [aIndex, aIndex]); + break; + } + // in the range? will need to either nuke, shrink, or split the range to + // remove it + if (aIndex >= low && aIndex <= high) { + this._count--; + // nuke + if (aIndex == low && aIndex == high) + this._ranges.splice(iTupe, 1); + // lower shrink + else if (aIndex == low) + this._ranges[iTupe][0] = aIndex + 1; + // upper shrink + else if (aIndex == high) + this._ranges[iTupe][1] = aIndex - 1; + // split + else + this._ranges.splice(iTupe, 1, [low, aIndex - 1], [aIndex + 1, high]); + break; + } + // just above the range? fuse into the range, and possibly the next + // range up. + if (aIndex == high + 1) { + this._count++; + // see if there is another range and there was just a gap of one between + // the two ranges. + if ((iTupe + 1 < this._ranges.length) && + (this._ranges[iTupe+1][0] == aIndex + 1)) { + // yes, merge the ranges + this._ranges.splice(iTupe, 2, [low, this._ranges[iTupe+1][1]]); + break; + } + // nope, no merge required, just update the range + this._ranges[iTupe][1] = aIndex; + break; + } + // otherwise we need to keep going + } + + if (this._treeBoxObject) + this._treeBoxObject.invalidateRow(aIndex); + this._fireSelectionChanged(); + }, + + /** + * @param aRangeStart If omitted, it implies a shift-selection is happening, + * in which case we use _shiftSelectPivot as the start if we have it, + * _currentIndex if we don't, and if we somehow didn't have a + * _currentIndex, we use the range end. + * @param aRangeEnd Just the inclusive end of the range. + * @param aAugment Does this set a new selection or should it be merged with + * the existing selection? + */ + rangedSelect: function JSTreeSelection_rangedSelect(aRangeStart, aRangeEnd, + aAugment) { + if (aRangeStart == -1) { + if (this._shiftSelectPivot != null) + aRangeStart = this._shiftSelectPivot; + else if (this._currentIndex != null) + aRangeStart = this._currentIndex; + else + aRangeStart = aRangeEnd; + } + + this._shiftSelectPivot = aRangeStart; + this.currentIndex = aRangeEnd; + + // enforce our ordering constraint for our ranges + if (aRangeStart > aRangeEnd) + [aRangeStart, aRangeEnd] = [aRangeEnd, aRangeStart]; + + // if we're not augmenting, then this is really easy. + if (!aAugment) { + this._count = aRangeEnd - aRangeStart + 1; + this._ranges = [[aRangeStart, aRangeEnd]]; + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + this._fireSelectionChanged(); + return; + } + + // Iterate over our existing set of ranges, finding the 'range' of ranges + // that our new range overlaps or simply obviates. + // Overlap variables track blocks we need to keep some part of, Nuke + // variables are for blocks that get spliced out. For our purposes, all + // overlap blocks are also nuke blocks. + let lowOverlap, lowNuke, highNuke, highOverlap; + // in case there is no overlap, also figure an insertionPoint + let insertionPoint = this._ranges.length; // default to the end + for (let [iTupe, [low, high]] of this._ranges.entries()) { + // If it's completely include the range, it should be nuked + if (aRangeStart <= low && aRangeEnd >= high) { + if (lowNuke == null) // only the first one we see is the low one + lowNuke = iTupe; + highNuke = iTupe; + } + // If our new range start is inside a range or is adjacent, it's overlap + if (aRangeStart >= low - 1 && aRangeStart <= high + 1 && + lowOverlap == null) + lowOverlap = lowNuke = highNuke = iTupe; + // If our new range ends inside a range or is adjacent, it's overlap + if (aRangeEnd >= low - 1 && aRangeEnd <= high + 1) { + highOverlap = highNuke = iTupe; + if (lowNuke == null) + lowNuke = iTupe; + } + + // we're done when no more overlap is possible + if (aRangeEnd < low) { + insertionPoint = iTupe; + break; + } + } + + if (lowOverlap != null) + aRangeStart = Math.min(aRangeStart, this._ranges[lowOverlap][0]); + if (highOverlap != null) + aRangeEnd = Math.max(aRangeEnd, this._ranges[highOverlap][1]); + if (lowNuke != null) + this._ranges.splice(lowNuke, highNuke - lowNuke + 1, + [aRangeStart, aRangeEnd]); + else + this._ranges.splice(insertionPoint, 0, [aRangeStart, aRangeEnd]); + + this._updateCount(); + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + this._fireSelectionChanged(); + }, + + /** + * This is basically RangedSelect but without insertion of a new range and we + * don't need to worry about adjacency. + * Oddly, nsTreeSelection doesn't fire a selection changed event here... + */ + clearRange: function JSTreeSelection_clearRange(aRangeStart, aRangeEnd) { + // Iterate over our existing set of ranges, finding the 'range' of ranges + // that our clear range overlaps or simply obviates. + // Overlap variables track blocks we need to keep some part of, Nuke + // variables are for blocks that get spliced out. For our purposes, all + // overlap blocks are also nuke blocks. + let lowOverlap, lowNuke, highNuke, highOverlap; + for (let [iTupe, [low, high]] of this._ranges.entries()) { + // If we completely include the range, it should be nuked + if (aRangeStart <= low && aRangeEnd >= high) { + if (lowNuke == null) // only the first one we see is the low one + lowNuke = iTupe; + highNuke = iTupe; + } + // If our new range start is inside a range, it's nuke and maybe overlap + if (aRangeStart >= low && aRangeStart <= high && lowNuke == null) { + lowNuke = highNuke = iTupe; + // it's only overlap if we don't match at the low end + if (aRangeStart > low) + lowOverlap = iTupe; + } + // If our new range ends inside a range, it's nuke and maybe overlap + if (aRangeEnd >= low && aRangeEnd <= high) { + highNuke = iTupe; + // it's only overlap if we don't match at the high end + if (aRangeEnd < high) + highOverlap = iTupe; + if (lowNuke == null) + lowNuke = iTupe; + } + + // we're done when no more overlap is possible + if (aRangeEnd < low) + break; + } + // nothing to do since there's nothing to nuke + if (lowNuke == null) + return; + let args = [lowNuke, highNuke - lowNuke + 1]; + if (lowOverlap != null) + args.push([this._ranges[lowOverlap][0], aRangeStart - 1]); + if (highOverlap != null) + args.push([aRangeEnd + 1, this._ranges[highOverlap][1]]); + this._ranges.splice.apply(this._ranges, args); + + this._updateCount(); + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + // note! nsTreeSelection doesn't fire a selection changed event, so neither + // do we, but it seems like we should + }, + + /** + * nsTreeSelection always fires a select notification when the range is + * cleared, even if there is no effective chance in selection. + */ + clearSelection: function JSTreeSelection_clearSelection() { + this._shiftSelectPivot = null; + this._count = 0; + this._ranges = []; + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + this._fireSelectionChanged(); + }, + + /** + * Not even nsTreeSelection implements this. + */ + invertSelection: function JSTreeSelection_invertSelection() { + throw new Error("Who really was going to use this?"); + }, + + /** + * Select all with no rows is a no-op, otherwise we select all and notify. + */ + selectAll: function JSTreeSelection_selectAll() { + if (!this._view) + return; + + let view = this._view; + let rowCount = view.rowCount; + + // no-ops-ville + if (!rowCount) + return; + + this._count = rowCount; + this._ranges = [[0, rowCount - 1]]; + + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + this._fireSelectionChanged(); + }, + + getRangeCount: function JSTreeSelection_getRangeCount() { + return this._ranges.length; + }, + getRangeAt: function JSTreeSelection_getRangeAt(aRangeIndex, aMinObj, + aMaxObj) { + if (aRangeIndex < 0 || aRangeIndex > this._ranges.length) + throw new Exception("Try a real range index next time."); + [aMinObj.value, aMaxObj.value] = this._ranges[aRangeIndex]; + }, + + invalidateSelection: function JSTreeSelection_invalidateSelection() { + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + }, + + /** + * Helper method to adjust points in the face of row additions/removal. + * @param aPoint The point, null if there isn't one, or an index otherwise. + * @param aDeltaAt The row at which the change is happening. + * @param aDelta The number of rows added if positive, or the (negative) + * number of rows removed. + */ + _adjustPoint: function JSTreeSelection__adjustPoint(aPoint, aDeltaAt, + aDelta) { + // if there is no point, no change + if (aPoint == null) + return aPoint; + // if the point is before the change, no change + if (aPoint < aDeltaAt) + return aPoint; + // if it's a deletion and it includes the point, clear it + if (aDelta < 0 && aPoint >= aDeltaAt && (aPoint + aDelta < aDeltaAt)) + return null; + // (else) the point is at/after the change, compensate + return aPoint + aDelta; + }, + /** + * Find the index of the range, if any, that contains the given index, and + * the index at which to insert a range if one does not exist. + * + * @return A tuple containing: 1) the index if there is one, null otherwise, + * 2) the index at which to insert a range that would contain the point. + */ + _findRangeContainingRow: + function JSTreeSelection__findRangeContainingRow(aIndex) { + for (let [iTupe, [low, high]] of this._ranges.entries()) { + if (aIndex >= low && aIndex <= high) + return [iTupe, iTupe]; + if (aIndex < low) + return [null, iTupe]; + } + return [null, this._ranges.length]; + }, + + + /** + * When present, a list of calls made to adjustSelection. See + * |logAdjustSelectionForReplay| and |replayAdjustSelectionLog|. + */ + _adjustSelectionLog: null, + /** + * Start logging calls to adjustSelection made against this instance. You + * would do this because you are replacing an existing selection object + * with this instance for the purposes of creating a transient selection. + * Of course, you want the original selection object to be up-to-date when + * you go to put it back, so then you can call replayAdjustSelectionLog + * with that selection object and everything will be peachy. + */ + logAdjustSelectionForReplay: + function JSTreeSelection_logAdjustSelectionForReplay() { + this._adjustSelectionLog = []; + }, + /** + * Stop logging calls to adjustSelection and replay the existing log against + * aSelection. + * + * @param aSelection {nsITreeSelection}. + */ + replayAdjustSelectionLog: + function JSTreeSelection_replayAdjustSelectionLog(aSelection) { + if (this._adjustSelectionLog.length) { + // Temporarily disable selection events because adjustSelection is going + // to generate an event each time otherwise, and better 1 event than + // many. + aSelection.selectEventsSuppressed = true; + for (let [index, count] of this._adjustSelectionLog) { + aSelection.adjustSelection(index, count); + } + aSelection.selectEventsSuppressed = false; + } + this._adjustSelectionLog = null; + }, + + adjustSelection: function JSTreeSelection_adjustSelection(aIndex, aCount) { + // nothing to do if there is no actual change + if (!aCount) + return; + + if (this._adjustSelectionLog) + this._adjustSelectionLog.push([aIndex, aCount]); + + // adjust our points + this._shiftSelectPivot = this._adjustPoint(this._shiftSelectPivot, + aIndex, aCount); + this._currentIndex = this._adjustPoint(this._currentIndex, aIndex, aCount); + + // If we are adding rows, we want to split any range at aIndex and then + // translate all of the ranges above that point up. + if (aCount > 0) { + let [iContain, iInsert] = this._findRangeContainingRow(aIndex); + if (iContain != null) { + let [low, high] = this._ranges[iContain]; + // if it is the low value, we just want to shift the range entirely, so + // do nothing (and keep iInsert pointing at it for translation) + // if it is not the low value, then there must be at least two values so + // we should split it and only translate the new/upper block + if (aIndex != low) { + this._ranges.splice(iContain, 1, [low, aIndex - 1], [aIndex, high]); + iInsert++; + } + } + // now translate everything from iInsert on up + for (let iTrans = iInsert; iTrans < this._ranges.length; iTrans++) { + let [low, high] = this._ranges[iTrans]; + this._ranges[iTrans] = [low + aCount, high + aCount]; + } + // invalidate and fire selection change notice + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + this._fireSelectionChanged(); + return; + } + + // If we are removing rows, we are basically clearing the range that is + // getting deleted and translating everyone above the remaining point + // downwards. The one trick is we may have to merge the lowest translated + // block. + let saveSuppress = this.selectEventsSuppressed; + this.selectEventsSuppressed = true; + this.clearRange(aIndex, aIndex - aCount - 1); + // translate + let iTrans = this._findRangeContainingRow(aIndex)[1]; + for (; iTrans < this._ranges.length; iTrans++) { + let [low, high] = this._ranges[iTrans]; + // for the first range, low may be below the index, in which case it + // should not get translated + this._ranges[iTrans] = [(low >= aIndex) ? low + aCount : low, + high + aCount]; + } + // we may have to merge the lowest translated block because it may now be + // adjacent to the previous block + if (iTrans > 0 && iTrans < this._ranges.length && + this._ranges[iTrans-1][1] == this_ranges[iTrans][0]) { + this._ranges[iTrans-1][1] = this._ranges[iTrans][1]; + this._ranges.splice(iTrans, 1); + } + + if (this._treeBoxObject) + this._treeBoxObject.invalidate(); + this.selectEventsSuppressed = saveSuppress; + }, + + get selectEventsSuppressed() { + return this._selectEventsSuppressed; + }, + /** + * Control whether selection events are suppressed. For consistency with + * nsTreeSelection, we always generate a selection event when a value of + * false is assigned, even if the value was already false. + */ + set selectEventsSuppressed(aSuppress) { + this._selectEventsSuppressed = aSuppress; + if (!aSuppress) + this._fireSelectionChanged(); + }, + + /** + * Note that we bypass any XUL "onselect" handler that may exist and go + * straight to the view. If you have a tree, you shouldn't be using us, + * so this seems aboot right. + */ + _fireSelectionChanged: function JSTreeSelection__fireSelectionChanged() { + // don't fire if we are suppressed; we will fire when un-suppressed + if (this.selectEventsSuppressed) + return; + let view; + if (this._treeBoxObject && this._treeBoxObject.view) + view = this._treeBoxObject.view; + else + view = this._view; + + // We might not have a view if we're in the middle of setting up things + if (view) { + view = view.QueryInterface(Ci.nsITreeView); + view.selectionChanged(); + } + }, + + get currentIndex() { + if (this._currentIndex == null) + return -1; + return this._currentIndex; + }, + /** + * Sets the current index. Other than updating the variable, this just + * invalidates the tree row if we have a tree. + * The real selection object would send a DOM event we don't care about. + */ + set currentIndex(aIndex) { + if (aIndex == this.currentIndex) + return; + + this._currentIndex = (aIndex != -1) ? aIndex : null; + if (this._treeBoxObject) + this._treeBoxObject.invalidateRow(aIndex); + }, + + currentColumn: null, + + get shiftSelectPivot() { + return this._shiftSelectPivot != null ? this._shiftSelectPivot : -1; + }, + + QueryInterface: XPCOMUtils.generateQI( + [Ci.nsITreeSelection]), + + /* + * Functions after this aren't part of the nsITreeSelection interface. + */ + + /** + * Duplicate this selection on another nsITreeSelection. This is useful + * when you would like to discard this selection for a real tree selection. + * We assume that both selections are for the same tree. + * + * @note We don't transfer the correct shiftSelectPivot over. + * @note This will fire a selectionChanged event on the tree view. + * + * @param aSelection an nsITreeSelection to duplicate this selection onto + */ + duplicateSelection: function JSTreeSelection_duplicateSelection(aSelection) { + aSelection.selectEventsSuppressed = true; + aSelection.clearSelection(); + for (let [iTupe, [low, high]] of this._ranges.entries()) + aSelection.rangedSelect(low, high, iTupe > 0); + + aSelection.currentIndex = this.currentIndex; + // This will fire a selectionChanged event + aSelection.selectEventsSuppressed = false; + }, +}; diff --git a/mailnews/base/util/mailServices.js b/mailnews/base/util/mailServices.js new file mode 100644 index 000000000..f6bf9ca01 --- /dev/null +++ b/mailnews/base/util/mailServices.js @@ -0,0 +1,73 @@ +/* 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 = ["MailServices"]; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var MailServices = {}; + +XPCOMUtils.defineLazyServiceGetter(MailServices, "mailSession", + "@mozilla.org/messenger/services/session;1", + "nsIMsgMailSession"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "accounts", + "@mozilla.org/messenger/account-manager;1", + "nsIMsgAccountManager"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "pop3", + "@mozilla.org/messenger/popservice;1", + "nsIPop3Service"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "imap", + "@mozilla.org/messenger/imapservice;1", + "nsIImapService"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "nntp", + "@mozilla.org/messenger/nntpservice;1", + "nsINntpService"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "smtp", + "@mozilla.org/messengercompose/smtp;1", + "nsISmtpService"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "compose", + "@mozilla.org/messengercompose;1", + "nsIMsgComposeService"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "ab", + "@mozilla.org/abmanager;1", + "nsIAbManager"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "copy", + "@mozilla.org/messenger/messagecopyservice;1", + "nsIMsgCopyService"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "mfn", + "@mozilla.org/messenger/msgnotificationservice;1", + "nsIMsgFolderNotificationService"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "headerParser", + "@mozilla.org/messenger/headerparser;1", + "nsIMsgHeaderParser"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "mimeConverter", + "@mozilla.org/messenger/mimeconverter;1", + "nsIMimeConverter"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "tags", + "@mozilla.org/messenger/tagservice;1", + "nsIMsgTagService"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "filters", + "@mozilla.org/messenger/services/filters;1", + "nsIMsgFilterService"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "junk", + "@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter", + "nsIJunkMailPlugin"); + +XPCOMUtils.defineLazyServiceGetter(MailServices, "newMailNotification", + "@mozilla.org/newMailNotificationService;1", + "mozINewMailNotificationService"); diff --git a/mailnews/base/util/mailnewsMigrator.js b/mailnews/base/util/mailnewsMigrator.js new file mode 100644 index 000000000..2e3be6906 --- /dev/null +++ b/mailnews/base/util/mailnewsMigrator.js @@ -0,0 +1,203 @@ +/* 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/. */ + +/** + * Migrate profile (prefs and other files) from older versions of Mailnews to + * current. + * This should be run at startup. It migrates as needed: each migration + * function should be written to be a no-op when the value is already migrated + * or was never used in the old version. + */ + +this.EXPORTED_SYMBOLS = [ "migrateMailnews" ]; + +Components.utils.import("resource:///modules/errUtils.js"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource:///modules/mailServices.js"); +var Ci = Components.interfaces; +var kServerPrefVersion = 1; +var kSmtpPrefVersion = 1; +var kABRemoteContentPrefVersion = 1; +var kDefaultCharsetsPrefVersion = 1; + +function migrateMailnews() +{ + try { + MigrateServerAuthPref(); + } catch (e) { logException(e); } + + try { + MigrateABRemoteContentSettings(); + } catch (e) { logException(e); } + + try { + MigrateDefaultCharsets(); + } catch (e) { logException(e); } +} + +/** + * Migrates from pref useSecAuth to pref authMethod + */ +function MigrateServerAuthPref() +{ + try { + // comma-separated list of all accounts. + var accounts = Services.prefs.getCharPref("mail.accountmanager.accounts") + .split(","); + for (let i = 0; i < accounts.length; i++) + { + let accountKey = accounts[i]; // e.g. "account1" + if (!accountKey) + continue; + let serverKey = Services.prefs.getCharPref("mail.account." + accountKey + + ".server"); + let server = "mail.server." + serverKey + "."; + if (Services.prefs.prefHasUserValue(server + "authMethod")) + continue; + if (!Services.prefs.prefHasUserValue(server + "useSecAuth") && + !Services.prefs.prefHasUserValue(server + "auth_login")) + continue; + if (Services.prefs.prefHasUserValue(server + "migrated")) + continue; + // auth_login = false => old-style auth + // else: useSecAuth = true => "secure auth" + // else: cleartext pw + let auth_login = true; + let useSecAuth = false; // old default, default pref now removed + try { + auth_login = Services.prefs.getBoolPref(server + "auth_login"); + } catch (e) {} + try { + useSecAuth = Services.prefs.getBoolPref(server + "useSecAuth"); + } catch (e) {} + + Services.prefs.setIntPref(server + "authMethod", + auth_login ? (useSecAuth ? + Ci.nsMsgAuthMethod.secure : + Ci.nsMsgAuthMethod.passwordCleartext) : + Ci.nsMsgAuthMethod.old); + Services.prefs.setIntPref(server + "migrated", kServerPrefVersion); + } + + // same again for SMTP servers + var smtpservers = Services.prefs.getCharPref("mail.smtpservers").split(","); + for (let i = 0; i < smtpservers.length; i++) + { + if (!smtpservers[i]) + continue; + let server = "mail.smtpserver." + smtpservers[i] + "."; + if (Services.prefs.prefHasUserValue(server + "authMethod")) + continue; + if (!Services.prefs.prefHasUserValue(server + "useSecAuth") && + !Services.prefs.prefHasUserValue(server + "auth_method")) + continue; + if (Services.prefs.prefHasUserValue(server + "migrated")) + continue; + // auth_method = 0 => no auth + // else: useSecAuth = true => "secure auth" + // else: cleartext pw + let auth_method = 1; + let useSecAuth = false; + try { + auth_method = Services.prefs.getIntPref(server + "auth_method"); + } catch (e) {} + try { + useSecAuth = Services.prefs.getBoolPref(server + "useSecAuth"); + } catch (e) {} + + Services.prefs.setIntPref(server + "authMethod", + auth_method ? (useSecAuth ? + Ci.nsMsgAuthMethod.secure : + Ci.nsMsgAuthMethod.passwordCleartext) : + Ci.nsMsgAuthMethod.none); + Services.prefs.setIntPref(server + "migrated", kSmtpPrefVersion); + } + } catch(e) { logException(e); } +} + +/** + * The address book used to contain information about wheather to allow remote + * content for a given contact. Now we use the permission manager for that. + * Do a one-time migration for it. + */ +function MigrateABRemoteContentSettings() +{ + if (Services.prefs.prefHasUserValue("mail.ab_remote_content.migrated")) + return; + + // Search through all of our local address books looking for a match. + let enumerator = MailServices.ab.directories; + while (enumerator.hasMoreElements()) + { + let migrateAddress = function(aEmail) { + let uri = Services.io.newURI( + "chrome://messenger/content/email=" + aEmail, null, null); + Services.perms.add(uri, "image", Services.perms.ALLOW_ACTION); + } + + let addrbook = enumerator.getNext() + .QueryInterface(Components.interfaces.nsIAbDirectory); + try { + // If it's a read-only book, don't try to find a card as we we could never + // have set the AllowRemoteContent property. + if (addrbook.readOnly) + continue; + + let childCards = addrbook.childCards; + while (childCards.hasMoreElements()) + { + let card = childCards.getNext() + .QueryInterface(Components.interfaces.nsIAbCard); + + if (card.getProperty("AllowRemoteContent", false) == false) + continue; // not allowed for this contact + + if (card.primaryEmail) + migrateAddress(card.primaryEmail); + + if (card.getProperty("SecondEmail", "")) + migrateAddress(card.getProperty("SecondEmail", "")); + } + } catch (e) { logException(e); } + } + + Services.prefs.setIntPref("mail.ab_remote_content.migrated", + kABRemoteContentPrefVersion); +} + +/** + * If the default sending or viewing charset is one that is no longer available, + * change it back to the default. + */ +function MigrateDefaultCharsets() +{ + if (Services.prefs.prefHasUserValue("mail.default_charsets.migrated")) + return; + + let charsetConvertManager = Components.classes['@mozilla.org/charset-converter-manager;1'] + .getService(Components.interfaces.nsICharsetConverterManager); + + let sendCharsetStr = Services.prefs.getComplexValue( + "mailnews.send_default_charset", + Components.interfaces.nsIPrefLocalizedString).data; + + try { + charsetConvertManager.getCharsetTitle(sendCharsetStr); + } catch (e) { + Services.prefs.clearUserPref("mailnews.send_default_charset"); + } + + let viewCharsetStr = Services.prefs.getComplexValue( + "mailnews.view_default_charset", + Components.interfaces.nsIPrefLocalizedString).data; + + try { + charsetConvertManager.getCharsetTitle(viewCharsetStr); + } catch (e) { + Services.prefs.clearUserPref("mailnews.view_default_charset"); + } + + Services.prefs.setIntPref("mail.default_charsets.migrated", + kDefaultCharsetsPrefVersion); +} diff --git a/mailnews/base/util/moz.build b/mailnews/base/util/moz.build new file mode 100644 index 000000000..a016cbcb9 --- /dev/null +++ b/mailnews/base/util/moz.build @@ -0,0 +1,78 @@ +# 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 += [ + 'nsImapMoveCoalescer.h', + 'nsMsgCompressIStream.h', + 'nsMsgCompressOStream.h', + 'nsMsgDBFolder.h', + 'nsMsgDBFolderAtomList.h', + 'nsMsgI18N.h', + 'nsMsgIdentity.h', + 'nsMsgIncomingServer.h', + 'nsMsgKeyArray.h', + 'nsMsgKeySet.h', + 'nsMsgLineBuffer.h', + 'nsMsgMailNewsUrl.h', + 'nsMsgProtocol.h', + 'nsMsgReadStateTxn.h', + 'nsMsgTxn.h', + 'nsMsgUtils.h', +] + +EXPORTS.mozilla.mailnews += [ + 'ServiceList.h', + 'Services.h', +] + +SOURCES += [ + 'nsImapMoveCoalescer.cpp', + 'nsMsgCompressIStream.cpp', + 'nsMsgCompressOStream.cpp', + 'nsMsgDBFolder.cpp', + 'nsMsgFileStream.cpp', + 'nsMsgI18N.cpp', + 'nsMsgIdentity.cpp', + 'nsMsgIncomingServer.cpp', + 'nsMsgKeyArray.cpp', + 'nsMsgKeySet.cpp', + 'nsMsgLineBuffer.cpp', + 'nsMsgMailNewsUrl.cpp', + 'nsMsgProtocol.cpp', + 'nsMsgReadStateTxn.cpp', + 'nsMsgTxn.cpp', + 'nsMsgUtils.cpp', + 'nsStopwatch.cpp', + 'Services.cpp', +] + +EXTRA_JS_MODULES += [ + 'ABQueryUtils.jsm', + 'errUtils.js', + 'folderUtils.jsm', + 'hostnameUtils.jsm', + 'IOUtils.js', + 'iteratorUtils.jsm', + 'jsTreeSelection.js', + 'JXON.js', + 'mailnewsMigrator.js', + 'mailServices.js', + 'msgDBCacheManager.js', + 'OAuth2.jsm', + 'OAuth2Providers.jsm', + 'StringBundle.js', + 'templateUtils.js', + 'traceHelper.js', +] + +LOCAL_INCLUDES += [ + '/mozilla/netwerk/base' +] + +FINAL_LIBRARY = 'mail' + +Library('msgbsutl_s') + +DEFINES['_IMPL_NS_MSG_BASE'] = True diff --git a/mailnews/base/util/msgDBCacheManager.js b/mailnews/base/util/msgDBCacheManager.js new file mode 100644 index 000000000..267962375 --- /dev/null +++ b/mailnews/base/util/msgDBCacheManager.js @@ -0,0 +1,175 @@ +/* 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/. */ + +/** + * Message DB Cache manager + */ + +/* :::::::: Constants and Helpers ::::::::::::::: */ + +this.EXPORTED_SYMBOLS = ["msgDBCacheManager"]; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +Cu.import("resource:///modules/mailServices.js"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource:///modules/gloda/log4moz.js"); +var log = Log4Moz.getConfiguredLogger("mailnews.database.dbcache"); + +/** + */ +var DBCACHE_INTERVAL_DEFAULT_MS = 60000; // 1 minute + +/* :::::::: The Module ::::::::::::::: */ + +var msgDBCacheManager = +{ + _initialized: false, + + _msgDBCacheTimer: null, + + _msgDBCacheTimerIntervalMS: DBCACHE_INTERVAL_DEFAULT_MS, + + _dbService: null, + + /** + * This is called on startup + */ + init: function dbcachemgr_init() + { + if (this._initialized) + return; + + this._dbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"] + .getService(Ci.nsIMsgDBService); + + // we listen for "quit-application-granted" instead of + // "quit-application-requested" because other observers of the + // latter can cancel the shutdown. + Services.obs.addObserver(this, "quit-application-granted", false); + + this.startPeriodicCheck(); + + this._initialized = true; + }, + +/* ........ Timer Callback ................*/ + + _dbCacheCheckTimerCallback: function dbCache_CheckTimerCallback() + { + msgDBCacheManager.checkCachedDBs(); + }, + +/* ........ Observer Notification Handler ................*/ + + observe: function dbCache_observe(aSubject, aTopic, aData) { + switch (aTopic) { + // This is observed before any windows start unloading if something other + // than the last 3pane window closing requested the application be + // shutdown. For example, when the user quits via the file menu. + case "quit-application-granted": + Services.obs.removeObserver(this, "quit-application-granted"); + this.stopPeriodicCheck(); + break; + } + }, + +/* ........ Public API ................*/ + + /** + * Stops db cache check + */ + stopPeriodicCheck: function dbcache_stopPeriodicCheck() + { + if (this._dbCacheCheckTimer) { + this._dbCacheCheckTimer.cancel(); + + delete this._dbCacheCheckTimer; + this._dbCacheCheckTimer = null; + } + }, + + /** + * Starts periodic db cache check + */ + startPeriodicCheck: function dbcache_startPeriodicCheck() + { + if (!this._dbCacheCheckTimer) { + this._dbCacheCheckTimer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + + this._dbCacheCheckTimer.initWithCallback( + this._dbCacheCheckTimerCallback, + this._msgDBCacheTimerIntervalMS, + Ci.nsITimer.TYPE_REPEATING_SLACK); + } + }, + + /** + * Checks if any DBs need to be closed due to inactivity or too many of them open. + */ + checkCachedDBs: function() + { + let idleLimit = Services.prefs.getIntPref("mail.db.idle_limit"); + let maxOpenDBs = Services.prefs.getIntPref("mail.db.max_open"); + + // db.lastUseTime below is in microseconds while Date.now and idleLimit pref + // is in milliseconds. + let closeThreshold = (Date.now() - idleLimit) * 1000; + let cachedDBs = this._dbService.openDBs; + log.info("Periodic check of cached folder databases (DBs), count=" + cachedDBs.length); + // Count databases that are already closed or get closed now due to inactivity. + let numClosing = 0; + // Count databases whose folder is open in a window. + let numOpenInWindow = 0; + let dbs = []; + for (let i = 0; i < cachedDBs.length; i++) { + let db = cachedDBs.queryElementAt(i, Ci.nsIMsgDatabase); + if (!db.folder.databaseOpen) { + // The DB isn't really open anymore. + log.debug("Skipping, DB not open for folder: " + db.folder.name); + numClosing++; + continue; + } + + if (MailServices.mailSession.IsFolderOpenInWindow(db.folder)) { + // The folder is open in a window so this DB must not be closed. + log.debug("Skipping, DB open in window for folder: " + db.folder.name); + numOpenInWindow++; + continue; + } + + if (db.lastUseTime < closeThreshold) + { + // DB open too log without activity. + log.debug("Closing expired DB for folder: " + db.folder.name); + db.folder.msgDatabase = null; + numClosing++; + continue; + } + + // Database eligible for closing. + dbs.push(db); + } + log.info("DBs open in a window: " + numOpenInWindow + ", DBs open: " + dbs.length + ", DBs already closing: " + numClosing); + let dbsToClose = Math.max(dbs.length - Math.max(maxOpenDBs - numOpenInWindow, 0), 0); + if (dbsToClose > 0) { + // Close some DBs so that we do not have more than maxOpenDBs. + // However, we skipped DBs for folders that are open in a window + // so if there are so many windows open, it may be possible for + // more than maxOpenDBs folders to stay open after this loop. + log.info("Need to close " + dbsToClose + " more DBs"); + // Order databases by lowest lastUseTime (oldest) at the end. + dbs.sort((a, b) => b.lastUseTime - a.lastUseTime); + while (dbsToClose > 0) { + let db = dbs.pop(); + log.debug("Closing DB for folder: " + db.folder.name); + db.folder.msgDatabase = null; + dbsToClose--; + } + } + }, +}; diff --git a/mailnews/base/util/nsImapMoveCoalescer.cpp b/mailnews/base/util/nsImapMoveCoalescer.cpp new file mode 100644 index 000000000..56958cdc9 --- /dev/null +++ b/mailnews/base/util/nsImapMoveCoalescer.cpp @@ -0,0 +1,233 @@ +/* -*- 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" +#include "nsMsgImapCID.h" +#include "nsImapMoveCoalescer.h" +#include "nsIImapService.h" +#include "nsIMsgCopyService.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later... +#include "nsMsgFolderFlags.h" +#include "nsIMsgHdr.h" +#include "nsIMsgImapMailFolder.h" +#include "nsThreadUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/ArrayUtils.h" + +NS_IMPL_ISUPPORTS(nsImapMoveCoalescer, nsIUrlListener) + +nsImapMoveCoalescer::nsImapMoveCoalescer(nsIMsgFolder *sourceFolder, nsIMsgWindow *msgWindow) +{ + m_sourceFolder = sourceFolder; + m_msgWindow = msgWindow; + m_hasPendingMoves = false; +} + +nsImapMoveCoalescer::~nsImapMoveCoalescer() +{ +} + +nsresult nsImapMoveCoalescer::AddMove(nsIMsgFolder *folder, nsMsgKey key) +{ + m_hasPendingMoves = true; + int32_t folderIndex = m_destFolders.IndexOf(folder); + nsTArray<nsMsgKey> *keysToAdd = nullptr; + + if (folderIndex >= 0) + keysToAdd = &(m_sourceKeyArrays[folderIndex]); + else + { + m_destFolders.AppendObject(folder); + keysToAdd = m_sourceKeyArrays.AppendElement(); + if (!keysToAdd) + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!keysToAdd->Contains(key)) + keysToAdd->AppendElement(key); + + return NS_OK; +} + +nsresult nsImapMoveCoalescer::PlaybackMoves(bool doNewMailNotification /* = false */) +{ + int32_t numFolders = m_destFolders.Count(); + // Nothing to do, so don't change the member variables. + if (numFolders == 0) + return NS_OK; + + nsresult rv = NS_OK; + m_hasPendingMoves = false; + m_doNewMailNotification = doNewMailNotification; + m_outstandingMoves = 0; + + for (int32_t i = 0; i < numFolders; ++i) + { + // XXX TODO + // JUNK MAIL RELATED + // is this the right place to make sure dest folder exists + // (and has proper flags?), before we start copying? + nsCOMPtr <nsIMsgFolder> destFolder(m_destFolders[i]); + nsTArray<nsMsgKey>& keysToAdd = m_sourceKeyArrays[i]; + int32_t numNewMessages = 0; + int32_t numKeysToAdd = keysToAdd.Length(); + if (numKeysToAdd == 0) + continue; + + nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID)); + for (uint32_t keyIndex = 0; keyIndex < keysToAdd.Length(); keyIndex++) + { + nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr; + rv = m_sourceFolder->GetMessageHeader(keysToAdd.ElementAt(keyIndex), getter_AddRefs(mailHdr)); + if (NS_SUCCEEDED(rv) && mailHdr) + { + messages->AppendElement(mailHdr, false); + bool isRead = false; + mailHdr->GetIsRead(&isRead); + if (!isRead) + numNewMessages++; + } + } + uint32_t destFlags; + destFolder->GetFlags(&destFlags); + if (! (destFlags & nsMsgFolderFlags::Junk)) // don't set has new on junk folder + { + destFolder->SetNumNewMessages(numNewMessages); + if (numNewMessages > 0) + destFolder->SetHasNewMessages(true); + } + // adjust the new message count on the source folder + int32_t oldNewMessageCount = 0; + m_sourceFolder->GetNumNewMessages(false, &oldNewMessageCount); + if (oldNewMessageCount >= numKeysToAdd) + oldNewMessageCount -= numKeysToAdd; + else + oldNewMessageCount = 0; + + m_sourceFolder->SetNumNewMessages(oldNewMessageCount); + + nsCOMPtr <nsISupports> sourceSupports = do_QueryInterface(m_sourceFolder, &rv); + nsCOMPtr <nsIUrlListener> urlListener(do_QueryInterface(sourceSupports)); + + keysToAdd.Clear(); + nsCOMPtr<nsIMsgCopyService> copySvc = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID); + if (copySvc) + { + nsCOMPtr <nsIMsgCopyServiceListener> listener; + if (m_doNewMailNotification) + { + nsMoveCoalescerCopyListener *copyListener = new nsMoveCoalescerCopyListener(this, destFolder); + if (copyListener) + listener = do_QueryInterface(copyListener); + } + rv = copySvc->CopyMessages(m_sourceFolder, messages, destFolder, true, + listener, m_msgWindow, false /*allowUndo*/); + if (NS_SUCCEEDED(rv)) + m_outstandingMoves++; + } + } + return rv; +} + +NS_IMETHODIMP +nsImapMoveCoalescer::OnStartRunningUrl(nsIURI *aUrl) +{ + NS_PRECONDITION(aUrl, "just a sanity check"); + return NS_OK; +} + +NS_IMETHODIMP +nsImapMoveCoalescer::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode) +{ + m_outstandingMoves--; + if (m_doNewMailNotification && !m_outstandingMoves) + { + nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_sourceFolder); + if (imapFolder) + imapFolder->NotifyIfNewMail(); + } + return NS_OK; +} + +nsTArray<nsMsgKey> *nsImapMoveCoalescer::GetKeyBucket(uint32_t keyArrayIndex) +{ + NS_ASSERTION(keyArrayIndex < MOZ_ARRAY_LENGTH(m_keyBuckets), "invalid index"); + + return keyArrayIndex < mozilla::ArrayLength(m_keyBuckets) ? + &(m_keyBuckets[keyArrayIndex]) : nullptr; +} + +NS_IMPL_ISUPPORTS(nsMoveCoalescerCopyListener, nsIMsgCopyServiceListener) + +nsMoveCoalescerCopyListener::nsMoveCoalescerCopyListener(nsImapMoveCoalescer * coalescer, + nsIMsgFolder *destFolder) +{ + m_destFolder = destFolder; + m_coalescer = coalescer; +} + +nsMoveCoalescerCopyListener::~nsMoveCoalescerCopyListener() +{ +} + +NS_IMETHODIMP nsMoveCoalescerCopyListener::OnStartCopy() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */ +NS_IMETHODIMP nsMoveCoalescerCopyListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void SetMessageKey (in uint32_t aKey); */ +NS_IMETHODIMP nsMoveCoalescerCopyListener::SetMessageKey(uint32_t aKey) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void GetMessageId (in nsACString aMessageId); */ +NS_IMETHODIMP nsMoveCoalescerCopyListener::GetMessageId(nsACString& messageId) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void OnStopCopy (in nsresult aStatus); */ +NS_IMETHODIMP nsMoveCoalescerCopyListener::OnStopCopy(nsresult aStatus) +{ + nsresult rv = NS_OK; + if (NS_SUCCEEDED(aStatus)) + { + // if the dest folder is imap, update it. + nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_destFolder); + if (imapFolder) + { + uint32_t folderFlags; + m_destFolder->GetFlags(&folderFlags); + if (!(folderFlags & (nsMsgFolderFlags::Junk | nsMsgFolderFlags::Trash))) + { + nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIURI> url; + nsCOMPtr <nsIUrlListener> listener = do_QueryInterface(m_coalescer); + rv = imapService->SelectFolder(m_destFolder, listener, nullptr, getter_AddRefs(url)); + } + } + else // give junk filters a chance to run on new msgs in destination local folder + { + bool filtersRun; + m_destFolder->CallFilterPlugins(nullptr, &filtersRun); + } + } + return rv; +} + + + diff --git a/mailnews/base/util/nsImapMoveCoalescer.h b/mailnews/base/util/nsImapMoveCoalescer.h new file mode 100644 index 000000000..2085408fb --- /dev/null +++ b/mailnews/base/util/nsImapMoveCoalescer.h @@ -0,0 +1,73 @@ +/* -*- 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 _nsImapMoveCoalescer_H +#define _nsImapMoveCoalescer_H + +#include "msgCore.h" +#include "nsCOMArray.h" +#include "nsIMsgWindow.h" +#include "nsCOMPtr.h" +#include "MailNewsTypes.h" +#include "nsTArray.h" +#include "nsIUrlListener.h" +#include "nsIMsgCopyServiceListener.h" + +// imap move coalescer class - in order to keep nsImapMailFolder from growing like Topsy +// Logically, we want to keep track of an nsTArray<nsMsgKey> per nsIMsgFolder, and then +// be able to retrieve them one by one and play back the moves. +// This utility class will be used by both the filter code and the offline playback code, +// to avoid multiple moves to the same folder. + +class NS_MSG_BASE nsImapMoveCoalescer : public nsIUrlListener +{ +public: + friend class nsMoveCoalescerCopyListener; + + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + + nsImapMoveCoalescer(nsIMsgFolder *sourceFolder, nsIMsgWindow *msgWindow); + + nsresult AddMove(nsIMsgFolder *folder, nsMsgKey key); + nsresult PlaybackMoves(bool doNewMailNotification = false); + // this lets the caller store keys in an arbitrary number of buckets. If the bucket + // for the passed in index doesn't exist, it will get created. + nsTArray<nsMsgKey> *GetKeyBucket(uint32_t keyArrayIndex); + nsIMsgWindow *GetMsgWindow() {return m_msgWindow;} + bool HasPendingMoves() {return m_hasPendingMoves;} +protected: + virtual ~nsImapMoveCoalescer(); + // m_sourceKeyArrays and m_destFolders are parallel arrays. + nsTArray<nsTArray<nsMsgKey> > m_sourceKeyArrays; + nsCOMArray<nsIMsgFolder> m_destFolders; + nsCOMPtr <nsIMsgWindow> m_msgWindow; + nsCOMPtr <nsIMsgFolder> m_sourceFolder; + bool m_doNewMailNotification; + bool m_hasPendingMoves; + nsTArray<nsMsgKey> m_keyBuckets[2]; + int32_t m_outstandingMoves; +}; + +class nsMoveCoalescerCopyListener final : public nsIMsgCopyServiceListener +{ +public: + nsMoveCoalescerCopyListener(nsImapMoveCoalescer * coalescer, nsIMsgFolder *destFolder); + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOPYSERVICELISTENER + + nsCOMPtr <nsIMsgFolder> m_destFolder; + + nsImapMoveCoalescer *m_coalescer; + // when we get OnStopCopy, update the folder. When we've finished all the copies, + // send the biff notification. + +private: + ~nsMoveCoalescerCopyListener(); +}; + + +#endif // _nsImapMoveCoalescer_H + diff --git a/mailnews/base/util/nsMsgCompressIStream.cpp b/mailnews/base/util/nsMsgCompressIStream.cpp new file mode 100644 index 000000000..5b47422f2 --- /dev/null +++ b/mailnews/base/util/nsMsgCompressIStream.cpp @@ -0,0 +1,228 @@ +/* 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 "nsMsgCompressIStream.h" +#include "prio.h" +#include "prmem.h" +#include "nsAlgorithm.h" +#include <algorithm> + +#define BUFFER_SIZE 16384 + +nsMsgCompressIStream::nsMsgCompressIStream() : + m_dataptr(nullptr), + m_dataleft(0), + m_inflateAgain(false) +{ +} + +nsMsgCompressIStream::~nsMsgCompressIStream() +{ + Close(); +} + +NS_IMPL_ISUPPORTS(nsMsgCompressIStream, nsIInputStream, + nsIAsyncInputStream) + +nsresult nsMsgCompressIStream::InitInputStream(nsIInputStream *rawStream) +{ + // protect against repeat calls + if (m_iStream) + return NS_ERROR_UNEXPECTED; + + // allocate some memory for buffering + m_zbuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE); + if (!m_zbuf) + return NS_ERROR_OUT_OF_MEMORY; + + // allocate some memory for buffering + m_databuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE); + if (!m_databuf) + return NS_ERROR_OUT_OF_MEMORY; + + // set up zlib object + m_zstream.zalloc = Z_NULL; + m_zstream.zfree = Z_NULL; + m_zstream.opaque = Z_NULL; + + // http://zlib.net/manual.html is rather silent on the topic, but + // perl's Compress::Raw::Zlib manual says: + // -WindowBits + // To compress an RFC 1951 data stream, set WindowBits to -MAX_WBITS. + if (inflateInit2(&m_zstream, -MAX_WBITS) != Z_OK) + return NS_ERROR_FAILURE; + + m_iStream = rawStream; + + return NS_OK; +} + +nsresult nsMsgCompressIStream::DoInflation() +{ + // if there's something in the input buffer of the zstream, process it. + m_zstream.next_out = (Bytef *) m_databuf.get(); + m_zstream.avail_out = BUFFER_SIZE; + int zr = inflate(&m_zstream, Z_SYNC_FLUSH); + + // inflate() should normally be called until it returns + // Z_STREAM_END or an error, and Z_BUF_ERROR just means + // unable to progress any further (possible if we filled + // an output buffer exactly) + if (zr == Z_BUF_ERROR || zr == Z_STREAM_END) + zr = Z_OK; + + // otherwise it's an error + if (zr != Z_OK) + return NS_ERROR_FAILURE; + + // http://www.zlib.net/manual.html says: + // If inflate returns Z_OK and with zero avail_out, it must be called + // again after making room in the output buffer because there might be + // more output pending. + m_inflateAgain = m_zstream.avail_out ? false : true; + + // set the pointer to the start of the buffer, and the count to how + // based on how many bytes are left unconsumed. + m_dataptr = m_databuf.get(); + m_dataleft = BUFFER_SIZE - m_zstream.avail_out; + + return NS_OK; +} + +/* void close (); */ +NS_IMETHODIMP nsMsgCompressIStream::Close() +{ + return CloseWithStatus(NS_OK); +} + +NS_IMETHODIMP nsMsgCompressIStream::CloseWithStatus(nsresult reason) +{ + nsresult rv = NS_OK; + + if (m_iStream) + { + // pass the status through to our wrapped stream + nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_iStream); + if (asyncInputStream) + rv = asyncInputStream->CloseWithStatus(reason); + + // tidy up + m_iStream = nullptr; + inflateEnd(&m_zstream); + } + + // clean up all the buffers + m_zbuf = nullptr; + m_databuf = nullptr; + m_dataptr = nullptr; + m_dataleft = 0; + + return rv; +} + +/* unsigned long long available (); */ +NS_IMETHODIMP nsMsgCompressIStream::Available(uint64_t *aResult) +{ + if (!m_iStream) + return NS_BASE_STREAM_CLOSED; + + // check if there's anything still in flight + if (!m_dataleft && m_inflateAgain) + { + nsresult rv = DoInflation(); + NS_ENSURE_SUCCESS(rv, rv); + } + + // we'll be returning this many to the next read, guaranteed + if (m_dataleft) + { + *aResult = m_dataleft; + return NS_OK; + } + + // this value isn't accurate, but will give a good true/false + // indication for idle purposes, and next read will fill + // m_dataleft, so we'll have an accurate count for the next call. + return m_iStream->Available(aResult); +} + +/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */ +NS_IMETHODIMP nsMsgCompressIStream::Read(char * aBuf, uint32_t aCount, uint32_t *aResult) +{ + if (!m_iStream) + { + *aResult = 0; + return NS_OK; + } + + // There are two stages of buffering: + // * m_zbuf contains the compressed data from the remote server + // * m_databuf contains the uncompressed raw bytes for consumption + // by the local client. + // + // Each buffer will only be filled when the following buffers + // have been entirely consumed. + // + // m_dataptr and m_dataleft are respectively a pointer to the + // unconsumed portion of m_databuf and the number of bytes + // of uncompressed data remaining in m_databuf. + // + // both buffers have a maximum size of BUFFER_SIZE, so it is + // possible that multiple inflate passes will be required to + // consume all of m_zbuf. + while (!m_dataleft) + { + // get some more data if we don't already have any + if (!m_inflateAgain) + { + uint32_t bytesRead; + nsresult rv = m_iStream->Read(m_zbuf.get(), (uint32_t)BUFFER_SIZE, &bytesRead); + NS_ENSURE_SUCCESS(rv, rv); + if (!bytesRead) + return NS_BASE_STREAM_CLOSED; + m_zstream.next_in = (Bytef *) m_zbuf.get(); + m_zstream.avail_in = bytesRead; + } + + nsresult rv = DoInflation(); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aResult = std::min(m_dataleft, aCount); + + if (*aResult) + { + memcpy(aBuf, m_dataptr, *aResult); + m_dataptr += *aResult; + m_dataleft -= *aResult; + } + + return NS_OK; +} + +/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */ +NS_IMETHODIMP nsMsgCompressIStream::ReadSegments(nsWriteSegmentFun aWriter, void * aClosure, uint32_t aCount, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgCompressIStream::AsyncWait(nsIInputStreamCallback *callback, uint32_t flags, uint32_t amount, nsIEventTarget *target) +{ + if (!m_iStream) + return NS_BASE_STREAM_CLOSED; + + nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_iStream); + if (asyncInputStream) + return asyncInputStream->AsyncWait(callback, flags, amount, target); + + return NS_OK; +} + +/* boolean isNonBlocking (); */ +NS_IMETHODIMP nsMsgCompressIStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + diff --git a/mailnews/base/util/nsMsgCompressIStream.h b/mailnews/base/util/nsMsgCompressIStream.h new file mode 100644 index 000000000..6c91e084d --- /dev/null +++ b/mailnews/base/util/nsMsgCompressIStream.h @@ -0,0 +1,35 @@ +/* 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" +#include "nsIAsyncInputStream.h" +#include "nsIInputStream.h" +#include "nsCOMPtr.h" +#include "mozilla/UniquePtr.h" +#include "zlib.h" + +class NS_MSG_BASE nsMsgCompressIStream final : public nsIAsyncInputStream +{ +public: + nsMsgCompressIStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsresult InitInputStream(nsIInputStream *rawStream); + +protected: + ~nsMsgCompressIStream(); + nsresult DoInflation(); + nsCOMPtr<nsIInputStream> m_iStream; + mozilla::UniquePtr<char[]> m_zbuf; + mozilla::UniquePtr<char[]> m_databuf; + char *m_dataptr; + uint32_t m_dataleft; + bool m_inflateAgain; + z_stream m_zstream; +}; + diff --git a/mailnews/base/util/nsMsgCompressOStream.cpp b/mailnews/base/util/nsMsgCompressOStream.cpp new file mode 100644 index 000000000..0a0a69549 --- /dev/null +++ b/mailnews/base/util/nsMsgCompressOStream.cpp @@ -0,0 +1,145 @@ +/* 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 "nsMsgCompressOStream.h" +#include "prio.h" +#include "prmem.h" + +#define BUFFER_SIZE 16384 + +nsMsgCompressOStream::nsMsgCompressOStream() : + m_zbuf(nullptr) +{ +} + +nsMsgCompressOStream::~nsMsgCompressOStream() +{ + Close(); +} + +NS_IMPL_ISUPPORTS(nsMsgCompressOStream, nsIOutputStream) + +nsresult nsMsgCompressOStream::InitOutputStream(nsIOutputStream *rawStream) +{ + // protect against repeat calls + if (m_oStream) + return NS_ERROR_UNEXPECTED; + + // allocate some memory for a buffer + m_zbuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE); + if (!m_zbuf) + return NS_ERROR_OUT_OF_MEMORY; + + // set up the zlib object + m_zstream.zalloc = Z_NULL; + m_zstream.zfree = Z_NULL; + m_zstream.opaque = Z_NULL; + + // http://zlib.net/manual.html is rather silent on the topic, but + // perl's Compress::Raw::Zlib manual says: + // -WindowBits [...] + // To compress an RFC 1951 data stream, set WindowBits to -MAX_WBITS. + if (deflateInit2(&m_zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) + return NS_ERROR_FAILURE; + + m_oStream = rawStream; + + return NS_OK; +} + +/* void close (); */ +NS_IMETHODIMP nsMsgCompressOStream::Close() +{ + if (m_oStream) + { + m_oStream = nullptr; + deflateEnd(&m_zstream); + } + m_zbuf = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompressOStream::Write(const char *buf, uint32_t count, uint32_t *result) +{ + if (!m_oStream) + return NS_BASE_STREAM_CLOSED; + + m_zstream.next_in = (Bytef *) buf; + m_zstream.avail_in = count; + + // keep looping until the buffer doesn't get filled + do + { + m_zstream.next_out = (Bytef *) m_zbuf.get(); + m_zstream.avail_out = BUFFER_SIZE; + // Using "Z_SYNC_FLUSH" may cause excess flushes if the calling + // code does a lot of small writes. An option with the IMAP + // protocol is to check the buffer for "\n" at the end, but + // in the interests of keeping this generic, don't optimise + // yet. An alternative is to require ->Flush always, but that + // is likely to break callers. + int zr = deflate(&m_zstream, Z_SYNC_FLUSH); + if (zr == Z_STREAM_END || zr == Z_BUF_ERROR) + zr = Z_OK; // not an error for our purposes + if (zr != Z_OK) + return NS_ERROR_FAILURE; + + uint32_t out_size = BUFFER_SIZE - m_zstream.avail_out; + const char *out_buf = m_zbuf.get(); + + // push everything in the buffer before repeating + while (out_size) + { + uint32_t out_result; + nsresult rv = m_oStream->Write(out_buf, out_size, &out_result); + NS_ENSURE_SUCCESS(rv, rv); + if (!out_result) + return NS_BASE_STREAM_CLOSED; + out_size -= out_result; + out_buf += out_result; + } + + // http://www.zlib.net/manual.html says: + // If deflate returns with avail_out == 0, this function must be + // called again with the same value of the flush parameter and + // more output space (updated avail_out), until the flush is + // complete (deflate returns with non-zero avail_out). + } while (!m_zstream.avail_out); + + *result = count; + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompressOStream::Flush(void) +{ + if (!m_oStream) + return NS_BASE_STREAM_CLOSED; + + return m_oStream->Flush(); +} + +NS_IMETHODIMP +nsMsgCompressOStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgCompressOStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* boolean isNonBlocking (); */ +NS_IMETHODIMP nsMsgCompressOStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + diff --git a/mailnews/base/util/nsMsgCompressOStream.h b/mailnews/base/util/nsMsgCompressOStream.h new file mode 100644 index 000000000..c706a9617 --- /dev/null +++ b/mailnews/base/util/nsMsgCompressOStream.h @@ -0,0 +1,28 @@ +/* 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" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" +#include "mozilla/UniquePtr.h" +#include "zlib.h" + +class NS_MSG_BASE nsMsgCompressOStream final : public nsIOutputStream +{ +public: + nsMsgCompressOStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIOUTPUTSTREAM + + nsresult InitOutputStream(nsIOutputStream *rawStream); + +protected: + ~nsMsgCompressOStream(); + nsCOMPtr<nsIOutputStream> m_oStream; + mozilla::UniquePtr<char[]> m_zbuf; + z_stream m_zstream; +}; + diff --git a/mailnews/base/util/nsMsgDBFolder.cpp b/mailnews/base/util/nsMsgDBFolder.cpp new file mode 100644 index 000000000..3a1a571e2 --- /dev/null +++ b/mailnews/base/util/nsMsgDBFolder.cpp @@ -0,0 +1,6040 @@ +/* -*- 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" +#include "nsUnicharUtils.h" +#include "nsMsgDBFolder.h" +#include "nsMsgFolderFlags.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsRDFCID.h" +#include "nsNetUtil.h" +#include "nsIMsgFolderCache.h" +#include "nsIMsgFolderCacheElement.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMsgDatabase.h" +#include "nsIMsgAccountManager.h" +#include "nsISeekableStream.h" +#include "nsNativeCharsetUtils.h" +#include "nsIChannel.h" +#include "nsITransport.h" +#include "nsIMsgFolderCompactor.h" +#include "nsIDocShell.h" +#include "nsIMsgWindow.h" +#include "nsIPrompt.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILocale.h" +#include "nsILocaleService.h" +#include "nsCollationCID.h" +#include "nsAbBaseCID.h" +#include "nsIAbCard.h" +#include "nsIAbDirectory.h" +#include "nsISpamSettings.h" +#include "nsIMsgFilterPlugin.h" +#include "nsIMsgMailSession.h" +#include "nsIRDFService.h" +#include "nsTextFormatter.h" +#include "nsMsgDBCID.h" +#include "nsReadLine.h" +#include "nsLayoutCID.h" +#include "nsIParserUtils.h" +#include "nsIDocumentEncoder.h" +#include "nsMsgI18N.h" +#include "nsIMIMEHeaderParam.h" +#include "plbase64.h" +#include "nsArrayEnumerator.h" +#include <time.h> +#include "nsIMsgFolderNotificationService.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsIMimeHeaders.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIMsgTraitService.h" +#include "nsIMessenger.h" +#include "nsThreadUtils.h" +#include "nsITransactionManager.h" +#include "nsMsgReadStateTxn.h" +#include "nsAutoPtr.h" +#include "prmem.h" +#include "nsIPK11TokenDB.h" +#include "nsIPK11Token.h" +#include "nsMsgLocalFolderHdrs.h" +#include <algorithm> +#define oneHour 3600000000U +#include "nsMsgUtils.h" +#include "nsIMsgFilterService.h" +#include "nsDirectoryServiceUtils.h" +#include "mozilla/Services.h" +#include "nsMimeTypes.h" +#include "nsIMsgFilter.h" + +static PRTime gtimeOfLastPurgeCheck; //variable to know when to check for purge_threshhold + +#define PREF_MAIL_PROMPT_PURGE_THRESHOLD "mail.prompt_purge_threshhold" +#define PREF_MAIL_PURGE_THRESHOLD "mail.purge_threshhold" +#define PREF_MAIL_PURGE_THRESHOLD_MB "mail.purge_threshhold_mb" +#define PREF_MAIL_PURGE_MIGRATED "mail.purge_threshold_migrated" +#define PREF_MAIL_PURGE_ASK "mail.purge.ask" +#define PREF_MAIL_WARN_FILTER_CHANGED "mail.warn_filter_changed" + +const char *kUseServerRetentionProp = "useServerRetention"; + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +nsICollation * nsMsgDBFolder::gCollationKeyGenerator = nullptr; + +char16_t *nsMsgDBFolder::kLocalizedInboxName; +char16_t *nsMsgDBFolder::kLocalizedTrashName; +char16_t *nsMsgDBFolder::kLocalizedSentName; +char16_t *nsMsgDBFolder::kLocalizedDraftsName; +char16_t *nsMsgDBFolder::kLocalizedTemplatesName; +char16_t *nsMsgDBFolder::kLocalizedUnsentName; +char16_t *nsMsgDBFolder::kLocalizedJunkName; +char16_t *nsMsgDBFolder::kLocalizedArchivesName; + +char16_t *nsMsgDBFolder::kLocalizedBrandShortName; + +nsrefcnt nsMsgDBFolder::mInstanceCount=0; + +NS_IMPL_ISUPPORTS_INHERITED(nsMsgDBFolder, nsRDFResource, + nsISupportsWeakReference, nsIMsgFolder, + nsIDBChangeListener, nsIUrlListener, + nsIJunkMailClassificationListener, + nsIMsgTraitClassificationListener) + +#define MSGDBFOLDER_ATOM(name_, value_) nsIAtom* nsMsgDBFolder::name_ = nullptr; +#include "nsMsgDBFolderAtomList.h" +#undef MSGDBFOLDER_ATOM + +nsMsgDBFolder::nsMsgDBFolder(void) +: mAddListener(true), + mNewMessages(false), + mGettingNewMessages(false), + mLastMessageLoaded(nsMsgKey_None), + mFlags(0), + mNumUnreadMessages(-1), + mNumTotalMessages(-1), + mNotifyCountChanges(true), + mExpungedBytes(0), + mInitializedFromCache(false), + mSemaphoreHolder(nullptr), + mNumPendingUnreadMessages(0), + mNumPendingTotalMessages(0), + mFolderSize(kSizeUnknown), + mNumNewBiffMessages(0), + mHaveParsedURI(false), + mIsServerIsValid(false), + mIsServer(false), + mInVFEditSearchScope (false) +{ + if (mInstanceCount++ <=0) { +#define MSGDBFOLDER_ATOM(name_, value_) name_ = MsgNewAtom(value_).take(); +#include "nsMsgDBFolderAtomList.h" +#undef MSGDBFOLDER_ATOM + initializeStrings(); + createCollationKeyGenerator(); + gtimeOfLastPurgeCheck = 0; + } + + mProcessingFlag[0].bit = nsMsgProcessingFlags::ClassifyJunk; + mProcessingFlag[1].bit = nsMsgProcessingFlags::ClassifyTraits; + mProcessingFlag[2].bit = nsMsgProcessingFlags::TraitsDone; + mProcessingFlag[3].bit = nsMsgProcessingFlags::FiltersDone; + mProcessingFlag[4].bit = nsMsgProcessingFlags::FilterToMove; + mProcessingFlag[5].bit = nsMsgProcessingFlags::NotReportedClassified; + for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++) + mProcessingFlag[i].keys = nsMsgKeySetU::Create(); +} + +nsMsgDBFolder::~nsMsgDBFolder(void) +{ + for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++) + delete mProcessingFlag[i].keys; + + if (--mInstanceCount == 0) { + NS_IF_RELEASE(gCollationKeyGenerator); + NS_Free(kLocalizedInboxName); + NS_Free(kLocalizedTrashName); + NS_Free(kLocalizedSentName); + NS_Free(kLocalizedDraftsName); + NS_Free(kLocalizedTemplatesName); + NS_Free(kLocalizedUnsentName); + NS_Free(kLocalizedJunkName); + NS_Free(kLocalizedArchivesName); + NS_Free(kLocalizedBrandShortName); + +#define MSGDBFOLDER_ATOM(name_, value_) NS_RELEASE(name_); +#include "nsMsgDBFolderAtomList.h" +#undef MSGDBFOLDER_ATOM + } + //shutdown but don't shutdown children. + Shutdown(false); +} + +NS_IMETHODIMP nsMsgDBFolder::Shutdown(bool shutdownChildren) +{ + if(mDatabase) + { + mDatabase->RemoveListener(this); + mDatabase->ForceClosed(); + mDatabase = nullptr; + if (mBackupDatabase) + { + mBackupDatabase->ForceClosed(); + mBackupDatabase = nullptr; + } + } + + if(shutdownChildren) + { + int32_t count = mSubFolders.Count(); + + for (int32_t i = 0; i < count; i++) + mSubFolders[i]->Shutdown(true); + + // Reset incoming server pointer and pathname. + mServer = nullptr; + mPath = nullptr; + mHaveParsedURI = false; + mName.Truncate(); + mSubFolders.Clear(); + } + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDBFolder::ForceDBClosed() +{ + int32_t count = mSubFolders.Count(); + for (int32_t i = 0; i < count; i++) + mSubFolders[i]->ForceDBClosed(); + + if (mDatabase) + { + mDatabase->ForceClosed(); + mDatabase = nullptr; + } + else + { + nsCOMPtr<nsIMsgDBService> mailDBFactory(do_GetService(NS_MSGDB_SERVICE_CONTRACTID)); + if (mailDBFactory) + mailDBFactory->ForceFolderDBClosed(this); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::CloseAndBackupFolderDB(const nsACString& newName) +{ + ForceDBClosed(); + + // We only support backup for mail at the moment + if ( !(mFlags & nsMsgFolderFlags::Mail)) + return NS_OK; + + nsCOMPtr<nsIFile> folderPath; + nsresult rv = GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> dbFile; + rv = GetSummaryFileLocation(folderPath, getter_AddRefs(dbFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> backupDir; + rv = CreateBackupDirectory(getter_AddRefs(backupDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> backupDBFile; + rv = GetBackupSummaryFile(getter_AddRefs(backupDBFile), newName); + NS_ENSURE_SUCCESS(rv, rv); + + if (mBackupDatabase) + { + mBackupDatabase->ForceClosed(); + mBackupDatabase = nullptr; + } + + backupDBFile->Remove(false); + bool backupExists; + backupDBFile->Exists(&backupExists); + NS_ASSERTION(!backupExists, "Couldn't delete database backup"); + if (backupExists) + return NS_ERROR_FAILURE; + + if (!newName.IsEmpty()) + { + nsAutoCString backupName; + rv = backupDBFile->GetNativeLeafName(backupName); + NS_ENSURE_SUCCESS(rv, rv); + return dbFile->CopyToNative(backupDir, backupName); + } + else + return dbFile->CopyToNative(backupDir, EmptyCString()); +} + +NS_IMETHODIMP nsMsgDBFolder::OpenBackupMsgDatabase() +{ + if (mBackupDatabase) + return NS_OK; + nsCOMPtr<nsIFile> folderPath; + nsresult rv = GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString folderName; + rv = folderPath->GetLeafName(folderName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> backupDir; + rv = CreateBackupDirectory(getter_AddRefs(backupDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // We use a dummy message folder file so we can use + // GetSummaryFileLocation to get the db file name + nsCOMPtr<nsIFile> backupDBDummyFolder; + rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = backupDBDummyFolder->Append(folderName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgDBService->OpenMailDBFromFile( + backupDBDummyFolder, this, false, true, + getter_AddRefs(mBackupDatabase)); + // we add a listener so that we can close the db during OnAnnouncerGoingAway. There should + // not be any other calls to the listener with the backup database + if (NS_SUCCEEDED(rv) && mBackupDatabase) + mBackupDatabase->AddListener(this); + + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + // this is normal in reparsing + rv = NS_OK; + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::RemoveBackupMsgDatabase() +{ + nsCOMPtr<nsIFile> folderPath; + nsresult rv = GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString folderName; + rv = folderPath->GetLeafName(folderName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> backupDir; + rv = CreateBackupDirectory(getter_AddRefs(backupDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // We use a dummy message folder file so we can use + // GetSummaryFileLocation to get the db file name + nsCOMPtr<nsIFile> backupDBDummyFolder; + rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = backupDBDummyFolder->Append(folderName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> backupDBFile; + rv = GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile)); + NS_ENSURE_SUCCESS(rv, rv); + + if (mBackupDatabase) + { + mBackupDatabase->ForceClosed(); + mBackupDatabase = nullptr; + } + + return backupDBFile->Remove(false); +} + +NS_IMETHODIMP nsMsgDBFolder::StartFolderLoading(void) +{ + if(mDatabase) + mDatabase->RemoveListener(this); + mAddListener = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::EndFolderLoading(void) +{ + if(mDatabase) + mDatabase->AddListener(this); + mAddListener = true; + UpdateSummaryTotals(true); + + //GGGG check for new mail here and call SetNewMessages...?? -- ONE OF THE 2 PLACES + if(mDatabase) + m_newMsgs.Clear(); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetExpungedBytes(int64_t *count) +{ + NS_ENSURE_ARG_POINTER(count); + + if (mDatabase) + { + nsresult rv; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + rv = mDatabase->GetDBFolderInfo(getter_AddRefs(folderInfo)); + if (NS_FAILED(rv)) return rv; + rv = folderInfo->GetExpungedBytes(count); + if (NS_SUCCEEDED(rv)) + mExpungedBytes = *count; // sync up with the database + return rv; + } + else + { + ReadDBFolderInfo(false); + *count = mExpungedBytes; + } + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDBFolder::GetCharset(nsACString& aCharset) +{ + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if(NS_SUCCEEDED(rv)) + rv = folderInfo->GetEffectiveCharacterSet(aCharset); + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::SetCharset(const nsACString& aCharset) +{ + nsresult rv; + + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgDatabase> db; + rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if(NS_SUCCEEDED(rv)) + { + rv = folderInfo->SetCharacterSet(aCharset); + db->Commit(nsMsgDBCommitType::kLargeCommit); + mCharset = aCharset; + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::GetCharsetOverride(bool *aCharsetOverride) +{ + NS_ENSURE_ARG_POINTER(aCharsetOverride); + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if(NS_SUCCEEDED(rv)) + rv = folderInfo->GetCharacterSetOverride(aCharsetOverride); + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::SetCharsetOverride(bool aCharsetOverride) +{ + nsresult rv; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgDatabase> db; + rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if(NS_SUCCEEDED(rv)) + { + rv = folderInfo->SetCharacterSetOverride(aCharsetOverride); + db->Commit(nsMsgDBCommitType::kLargeCommit); + mCharsetOverride = aCharsetOverride; // synchronize member variable + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::GetHasNewMessages(bool *hasNewMessages) +{ + NS_ENSURE_ARG_POINTER(hasNewMessages); + *hasNewMessages = mNewMessages; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetHasNewMessages(bool curNewMessages) +{ + if (curNewMessages != mNewMessages) + { + // Only change mru time if we're going from doesn't have new to has new. + // technically, we should probably update mru time for every new message + // but we would pay a performance penalty for that. If the user + // opens the folder, the mrutime will get updated anyway. + if (curNewMessages) + SetMRUTime(); + bool oldNewMessages = mNewMessages; + mNewMessages = curNewMessages; + NotifyBoolPropertyChanged(kNewMessagesAtom, oldNewMessages, curNewMessages); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetHasFolderOrSubfolderNewMessages(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + bool hasNewMessages = mNewMessages; + + if (!hasNewMessages) + { + int32_t count = mSubFolders.Count(); + for (int32_t i = 0; i < count; i++) + { + bool hasNew = false; + mSubFolders[i]->GetHasFolderOrSubfolderNewMessages(&hasNew); + if (hasNew) + { + hasNewMessages = true; + break; + } + } + } + + *aResult = hasNewMessages; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetGettingNewMessages(bool *gettingNewMessages) +{ + NS_ENSURE_ARG_POINTER(gettingNewMessages); + *gettingNewMessages = mGettingNewMessages; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetGettingNewMessages(bool gettingNewMessages) +{ + mGettingNewMessages = gettingNewMessages; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetFirstNewMessage(nsIMsgDBHdr **firstNewMessage) +{ + //If there's not a db then there can't be new messages. Return failure since you + //should use HasNewMessages first. + if(!mDatabase) + return NS_ERROR_FAILURE; + + nsresult rv; + nsMsgKey key; + rv = mDatabase->GetFirstNew(&key); + if(NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgDBHdr> hdr; + rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr)); + if(NS_FAILED(rv)) + return rv; + + return mDatabase->GetMsgHdrForKey(key, firstNewMessage); +} + +NS_IMETHODIMP nsMsgDBFolder::ClearNewMessages() +{ + nsresult rv = NS_OK; + bool dbWasCached = mDatabase != nullptr; + if (!dbWasCached) + GetDatabase(); + + if (mDatabase) + { + uint32_t numNewKeys; + nsMsgKey *newMessageKeys; + rv = mDatabase->GetNewList(&numNewKeys, &newMessageKeys); + if (NS_SUCCEEDED(rv) && newMessageKeys) + { + m_saveNewMsgs.Clear(); + m_saveNewMsgs.AppendElements(newMessageKeys, numNewKeys); + NS_Free(newMessageKeys); + } + mDatabase->ClearNewList(true); + } + if (!dbWasCached) + SetMsgDatabase(nullptr); + + m_newMsgs.Clear(); + mNumNewBiffMessages = 0; + return rv; +} + +void nsMsgDBFolder::UpdateNewMessages() +{ + if (! (mFlags & nsMsgFolderFlags::Virtual)) + { + bool hasNewMessages = false; + for (uint32_t keyIndex = 0; keyIndex < m_newMsgs.Length(); keyIndex++) + { + bool containsKey = false; + mDatabase->ContainsKey(m_newMsgs[keyIndex], &containsKey); + if (!containsKey) + continue; + bool isRead = false; + nsresult rv2 = mDatabase->IsRead(m_newMsgs[keyIndex], &isRead); + if (NS_SUCCEEDED(rv2) && !isRead) + { + hasNewMessages = true; + mDatabase->AddToNewList(m_newMsgs[keyIndex]); + } + } + SetHasNewMessages(hasNewMessages); + } +} + +// helper function that gets the cache element that corresponds to the passed in file spec. +// This could be static, or could live in another class - it's not specific to the current +// nsMsgDBFolder. If it lived at a higher level, we could cache the account manager and folder cache. +nsresult nsMsgDBFolder::GetFolderCacheElemFromFile(nsIFile *file, nsIMsgFolderCacheElement **cacheElement) +{ + nsresult result; + NS_ENSURE_ARG_POINTER(file); + NS_ENSURE_ARG_POINTER(cacheElement); + nsCOMPtr <nsIMsgFolderCache> folderCache; +#ifdef DEBUG_bienvenu1 + bool exists; + NS_ASSERTION(NS_SUCCEEDED(fileSpec->Exists(&exists)) && exists, "whoops, file doesn't exist, mac will break"); +#endif + nsCOMPtr<nsIMsgAccountManager> accountMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result); + if(NS_SUCCEEDED(result)) + { + result = accountMgr->GetFolderCache(getter_AddRefs(folderCache)); + if (NS_SUCCEEDED(result) && folderCache) + { + nsCString persistentPath; + result = file->GetPersistentDescriptor(persistentPath); + NS_ENSURE_SUCCESS(result, result); + result = folderCache->GetCacheElement(persistentPath, false, cacheElement); + } + } + return result; +} + +nsresult nsMsgDBFolder::ReadDBFolderInfo(bool force) +{ + // Since it turns out to be pretty expensive to open and close + // the DBs all the time, if we have to open it once, get everything + // we might need while we're here + nsresult result = NS_OK; + + // don't need to reload from cache if we've already read from cache, + // and, we might get stale info, so don't do it. + if (!mInitializedFromCache) + { + nsCOMPtr <nsIFile> dbPath; + result = GetFolderCacheKey(getter_AddRefs(dbPath), true /* createDBIfMissing */); + if (dbPath) + { + nsCOMPtr <nsIMsgFolderCacheElement> cacheElement; + result = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement)); + if (NS_SUCCEEDED(result) && cacheElement) + result = ReadFromFolderCacheElem(cacheElement); + } + } + + if (force || !mInitializedFromCache) + { + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgDatabase> db; + result = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if(NS_SUCCEEDED(result)) + { + if (folderInfo) + { + if (!mInitializedFromCache) + { + folderInfo->GetFlags((int32_t *)&mFlags); +#ifdef DEBUG_bienvenu1 + nsString name; + GetName(name); + NS_ASSERTION(Compare(name, kLocalizedTrashName) || (mFlags & nsMsgFolderFlags::Trash), "lost trash flag"); +#endif + mInitializedFromCache = true; + } + + folderInfo->GetNumMessages(&mNumTotalMessages); + folderInfo->GetNumUnreadMessages(&mNumUnreadMessages); + folderInfo->GetExpungedBytes(&mExpungedBytes); + + nsCString utf8Name; + folderInfo->GetFolderName(utf8Name); + if (!utf8Name.IsEmpty()) + CopyUTF8toUTF16(utf8Name, mName); + + //These should be put in IMAP folder only. + //folderInfo->GetImapTotalPendingMessages(&mNumPendingTotalMessages); + //folderInfo->GetImapUnreadPendingMessages(&mNumPendingUnreadMessages); + + folderInfo->GetCharacterSet(mCharset); + folderInfo->GetCharacterSetOverride(&mCharsetOverride); + + if (db) { + bool hasnew; + nsresult rv; + rv = db->HasNew(&hasnew); + if (NS_FAILED(rv)) return rv; + } + } + } + else { + // we tried to open DB but failed - don't keep trying. + // If a DB is created, we will call this method with force == TRUE, + // and read from the db that way. + mInitializedFromCache = true; + } + if (db) + db->Close(false); + } + return result; +} + +nsresult nsMsgDBFolder::SendFlagNotifications(nsIMsgDBHdr *item, uint32_t oldFlags, uint32_t newFlags) +{ + nsresult rv = NS_OK; + uint32_t changedFlags = oldFlags ^ newFlags; + if((changedFlags & nsMsgMessageFlags::Read) && (changedFlags & nsMsgMessageFlags::New)) + { + //..so..if the msg is read in the folder and the folder has new msgs clear the account level and status bar biffs. + rv = NotifyPropertyFlagChanged(item, kStatusAtom, oldFlags, newFlags); + rv = SetBiffState(nsMsgBiffState_NoMail); + } + else if(changedFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded + | nsMsgMessageFlags::IMAPDeleted | nsMsgMessageFlags::New | nsMsgMessageFlags::Offline)) + rv = NotifyPropertyFlagChanged(item, kStatusAtom, oldFlags, newFlags); + else if((changedFlags & nsMsgMessageFlags::Marked)) + rv = NotifyPropertyFlagChanged(item, kFlaggedAtom, oldFlags, newFlags); + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *) +{ + NS_ASSERTION(false, "imap and news need to override this"); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow) +{ + NS_ASSERTION(false, "imap and news need to override this"); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetMsgStore(nsIMsgPluggableStore **aStore) +{ + NS_ENSURE_ARG_POINTER(aStore); + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + return server->GetMsgStore(aStore); +} + +bool nsMsgDBFolder::VerifyOfflineMessage(nsIMsgDBHdr *msgHdr, nsIInputStream *fileStream) +{ + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(fileStream); + if (seekableStream) + { + uint64_t offset; + msgHdr->GetMessageOffset(&offset); + nsresult rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, offset); + char startOfMsg[100]; + uint32_t bytesRead = 0; + uint32_t bytesToRead = sizeof(startOfMsg) - 1; + if (NS_SUCCEEDED(rv)) + rv = fileStream->Read(startOfMsg, bytesToRead, &bytesRead); + startOfMsg[bytesRead] = '\0'; + // check if message starts with From, or is a draft and starts with FCC + if (NS_FAILED(rv) || bytesRead != bytesToRead || + (strncmp(startOfMsg, "From ", 5) && (! (mFlags & nsMsgFolderFlags::Drafts) || strncmp(startOfMsg, "FCC", 3)))) + return false; + } + return true; +} + +NS_IMETHODIMP nsMsgDBFolder::GetOfflineFileStream(nsMsgKey msgKey, int64_t *offset, uint32_t *size, nsIInputStream **aFileStream) +{ + NS_ENSURE_ARG(aFileStream); + + *offset = *size = 0; + + nsresult rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, NS_OK); + nsCOMPtr<nsIMsgDBHdr> hdr; + rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr)); + NS_ENSURE_TRUE(hdr, NS_OK); + NS_ENSURE_SUCCESS(rv, rv); + if (hdr) + hdr->GetOfflineMessageSize(size); + + bool reusable; + rv = GetMsgInputStream(hdr, &reusable, aFileStream); + // check if offline store really has the correct offset into the offline + // store by reading the first few bytes. If it doesn't, clear the offline + // flag on the msg and return false, which will fall back to reading the message + // from the server. + // We'll also advance the offset past the envelope header and + // X-Mozilla-Status lines. + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(*aFileStream); + if (seekableStream) + { + seekableStream->Tell(offset); + char startOfMsg[200]; + uint32_t bytesRead = 0; + uint32_t bytesToRead = sizeof(startOfMsg) - 1; + if (NS_SUCCEEDED(rv)) + rv = (*aFileStream)->Read(startOfMsg, bytesToRead, &bytesRead); + startOfMsg[bytesRead] = '\0'; + // check if message starts with From, or is a draft and starts with FCC + if (NS_FAILED(rv) || bytesRead != bytesToRead || + (strncmp(startOfMsg, "From ", 5) && (! (mFlags & nsMsgFolderFlags::Drafts) || strncmp(startOfMsg, "FCC", 3)))) + rv = NS_ERROR_FAILURE; + else + { + uint32_t msgOffset = 0; + // skip "From "/FCC line + bool foundNextLine = MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1); + if (foundNextLine && !strncmp(startOfMsg + msgOffset, + X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN)) + { + // skip X-Mozilla-Status line + if (MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1)) + { + if (!strncmp(startOfMsg + msgOffset, + X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN)) + MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1); + } + } + int32_t findPos = MsgFindCharInSet(nsDependentCString(startOfMsg), + ":\n\r", msgOffset); + // Check that the first line is a header line, i.e., with a ':' in it. + // Or, the line starts with "From " - I've seen IMAP servers return + // a bogus "From " line without a ':'. + if (findPos != -1 && (startOfMsg[findPos] == ':' || + !(strncmp(startOfMsg, "From ", 5)))) + { + *offset += msgOffset; + *size -= msgOffset; + } + else + { + rv = NS_ERROR_FAILURE; + } + } + if (NS_SUCCEEDED(rv)) + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, *offset); + else if (mDatabase) + mDatabase->MarkOffline(msgKey, false, nullptr); + } + + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetOfflineStoreOutputStream(nsIMsgDBHdr *aHdr, + nsIOutputStream **aOutputStream) +{ + NS_ENSURE_ARG_POINTER(aOutputStream); + NS_ENSURE_ARG_POINTER(aHdr); + + nsCOMPtr<nsIMsgPluggableStore> offlineStore; + nsresult rv = GetMsgStore(getter_AddRefs(offlineStore)); + NS_ENSURE_SUCCESS(rv, rv); + bool reusable; + rv = offlineStore->GetNewMsgOutputStream(this, &aHdr, &reusable, + aOutputStream); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetMsgInputStream(nsIMsgDBHdr *aMsgHdr, bool *aReusable, + nsIInputStream **aInputStream) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + NS_ENSURE_ARG_POINTER(aReusable); + NS_ENSURE_ARG_POINTER(aInputStream); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString storeToken; + rv = aMsgHdr->GetStringProperty("storeToken", getter_Copies(storeToken)); + NS_ENSURE_SUCCESS(rv, rv); + int64_t offset; + rv = msgStore->GetMsgInputStream(this, storeToken, &offset, aMsgHdr, aReusable, + aInputStream); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(*aInputStream)); + if (seekableStream) + rv = seekableStream->Seek(PR_SEEK_SET, offset); + NS_WARNING_ASSERTION(seekableStream || !offset, + "non-zero offset w/ non-seekable stream"); + return rv; +} + +// path coming in is the root path without the leaf name, +// on the way out, it's the whole path. +nsresult nsMsgDBFolder::CreateFileForDB(const nsAString& userLeafName, nsIFile *path, nsIFile **dbFile) +{ + NS_ENSURE_ARG_POINTER(dbFile); + + nsAutoString proposedDBName(userLeafName); + NS_MsgHashIfNecessary(proposedDBName); + + // (note, the caller of this will be using the dbFile to call db->Open() + // will turn the path into summary file path, and append the ".msf" extension) + // + // we want db->Open() to create a new summary file + // so we have to jump through some hoops to make sure the .msf it will + // create is unique. now that we've got the "safe" proposedDBName, + // we append ".msf" to see if the file exists. if so, we make the name + // unique and then string off the ".msf" so that we pass the right thing + // into Open(). this isn't ideal, since this is not atomic + // but it will make do. + nsresult rv; + nsCOMPtr <nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + dbPath->InitWithFile(path); + proposedDBName.AppendLiteral(SUMMARY_SUFFIX); + dbPath->Append(proposedDBName); + bool exists; + dbPath->Exists(&exists); + if (exists) + { + rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + NS_ENSURE_SUCCESS(rv, rv); + dbPath->GetLeafName(proposedDBName); + } + // now, take the ".msf" off + proposedDBName.SetLength(proposedDBName.Length() - NS_LITERAL_CSTRING(SUMMARY_SUFFIX).Length()); + dbPath->SetLeafName(proposedDBName); + + dbPath.swap(*dbFile); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase) +{ + NS_ENSURE_ARG_POINTER(aMsgDatabase); + GetDatabase(); + if (!mDatabase) + return NS_ERROR_FAILURE; + NS_ADDREF(*aMsgDatabase = mDatabase); + mDatabase->SetLastUseTime(PR_Now()); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::SetMsgDatabase(nsIMsgDatabase *aMsgDatabase) +{ + if (mDatabase) + { + // commit here - db might go away when all these refs are released. + mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); + mDatabase->RemoveListener(this); + mDatabase->ClearCachedHdrs(); + if (!aMsgDatabase) + { + uint32_t numNewKeys; + nsMsgKey *newMessageKeys; + nsresult rv = mDatabase->GetNewList(&numNewKeys, &newMessageKeys); + if (NS_SUCCEEDED(rv) && newMessageKeys) + { + m_newMsgs.Clear(); + m_newMsgs.AppendElements(newMessageKeys, numNewKeys); + } + NS_Free(newMessageKeys); + } + } + mDatabase = aMsgDatabase; + + if (aMsgDatabase) + aMsgDatabase->AddListener(this); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetDatabaseOpen(bool *aOpen) +{ + NS_ENSURE_ARG_POINTER(aOpen); + + *aOpen = (mDatabase != nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetBackupMsgDatabase(nsIMsgDatabase** aMsgDatabase) +{ + NS_ENSURE_ARG_POINTER(aMsgDatabase); + nsresult rv = OpenBackupMsgDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + if (!mBackupDatabase) + return NS_ERROR_FAILURE; + + NS_ADDREF(*aMsgDatabase = mBackupDatabase); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **database) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgDBFolder::OnReadChanged(nsIDBChangeListener * aInstigator) +{ + /* do nothing. if you care about this, override it. see nsNewsFolder.cpp */ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::OnJunkScoreChanged(nsIDBChangeListener * aInstigator) +{ + NotifyFolderEvent(mJunkStatusChangedAtom); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus, + nsIDBChangeListener *aInstigator) +{ + /* do nothing. if you care about this, override it.*/ + return NS_OK; +} + +// 1. When the status of a message changes. +NS_IMETHODIMP nsMsgDBFolder::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags, + nsIDBChangeListener * aInstigator) +{ + if(aHdrChanged) + { + SendFlagNotifications(aHdrChanged, aOldFlags, aNewFlags); + UpdateSummaryTotals(true); + } + + // The old state was new message state + // We check and see if this state has changed + if(aOldFlags & nsMsgMessageFlags::New) + { + // state changing from new to something else + if (!(aNewFlags & nsMsgMessageFlags::New)) + CheckWithNewMessagesStatus(false); + } + + return NS_OK; +} + +nsresult nsMsgDBFolder::CheckWithNewMessagesStatus(bool messageAdded) +{ + bool hasNewMessages; + if (messageAdded) + SetHasNewMessages(true); + else // message modified or deleted + { + if(mDatabase) + { + nsresult rv = mDatabase->HasNew(&hasNewMessages); + NS_ENSURE_SUCCESS(rv, rv); + SetHasNewMessages(hasNewMessages); + } + } + + return NS_OK; +} + +// 3. When a message gets deleted, we need to see if it was new +// When we lose a new message we need to check if there are still new messages +NS_IMETHODIMP nsMsgDBFolder::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags, + nsIDBChangeListener * aInstigator) +{ + // check to see if a new message is being deleted + // as in this case, if there is only one new message and it's being deleted + // the folder newness has to be cleared. + CheckWithNewMessagesStatus(false); + // Remove all processing flags. This is generally a good thing although + // undo-ing a message back into position will not re-gain the flags. + nsMsgKey msgKey; + aHdrChanged->GetMessageKey(&msgKey); + AndProcessingFlags(msgKey, 0); + return OnHdrAddedOrDeleted(aHdrChanged, false); +} + +// 2. When a new messages gets added, we need to see if it's new. +NS_IMETHODIMP nsMsgDBFolder::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey , int32_t aFlags, + nsIDBChangeListener * aInstigator) +{ + if(aFlags & nsMsgMessageFlags::New) + CheckWithNewMessagesStatus(true); + return OnHdrAddedOrDeleted(aHdrChanged, true); +} + +nsresult nsMsgDBFolder::OnHdrAddedOrDeleted(nsIMsgDBHdr *aHdrChanged, bool added) +{ + if(added) + NotifyItemAdded(aHdrChanged); + else + NotifyItemRemoved(aHdrChanged); + UpdateSummaryTotals(true); + return NS_OK; + +} + +NS_IMETHODIMP nsMsgDBFolder::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, + nsIDBChangeListener * aInstigator) +{ + nsCOMPtr<nsIMsgDBHdr> hdrChanged; + mDatabase->GetMsgHdrForKey(aKeyChanged, getter_AddRefs(hdrChanged)); + //In reality we probably want to just change the parent because otherwise we will lose things like + //selection. + if (hdrChanged) + { + //First delete the child from the old threadParent + OnHdrAddedOrDeleted(hdrChanged, false); + //Then add it to the new threadParent + OnHdrAddedOrDeleted(hdrChanged, true); + } + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDBFolder::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) +{ + if (mBackupDatabase && instigator == mBackupDatabase) + { + mBackupDatabase->RemoveListener(this); + mBackupDatabase = nullptr; + } + else if (mDatabase) + { + mDatabase->RemoveListener(this); + mDatabase = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::OnEvent(nsIMsgDatabase *aDB, const char *aEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetManyHeadersToDownload(bool *retval) +{ + NS_ENSURE_ARG_POINTER(retval); + int32_t numTotalMessages; + + // is there any reason to return false? + if (!mDatabase) + *retval = true; + else if (NS_SUCCEEDED(GetTotalMessages(false, &numTotalMessages)) && numTotalMessages <= 0) + *retval = true; + else + *retval = false; + return NS_OK; +} + +nsresult nsMsgDBFolder::MsgFitsDownloadCriteria(nsMsgKey msgKey, bool *result) +{ + if(!mDatabase) + return NS_ERROR_FAILURE; + + nsresult rv; + nsCOMPtr<nsIMsgDBHdr> hdr; + rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr)); + if(NS_FAILED(rv)) + return rv; + + if (hdr) + { + uint32_t msgFlags = 0; + hdr->GetFlags(&msgFlags); + // check if we already have this message body offline + if (! (msgFlags & nsMsgMessageFlags::Offline)) + { + *result = true; + // check against the server download size limit . + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + rv = GetServer(getter_AddRefs(incomingServer)); + if (NS_SUCCEEDED(rv) && incomingServer) + { + bool limitDownloadSize = false; + rv = incomingServer->GetLimitOfflineMessageSize(&limitDownloadSize); + NS_ENSURE_SUCCESS(rv, rv); + if (limitDownloadSize) + { + int32_t maxDownloadMsgSize = 0; + uint32_t msgSize; + hdr->GetMessageSize(&msgSize); + rv = incomingServer->GetMaxMessageSize(&maxDownloadMsgSize); + NS_ENSURE_SUCCESS(rv, rv); + maxDownloadMsgSize *= 1024; + if (msgSize > (uint32_t) maxDownloadMsgSize) + *result = false; + } + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetSupportsOffline(bool *aSupportsOffline) +{ + NS_ENSURE_ARG_POINTER(aSupportsOffline); + if (mFlags & nsMsgFolderFlags::Virtual) + { + *aSupportsOffline = false; + return NS_OK; + } + + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv,rv); + if (!server) + return NS_ERROR_FAILURE; + + int32_t offlineSupportLevel; + rv = server->GetOfflineSupportLevel(&offlineSupportLevel); + NS_ENSURE_SUCCESS(rv,rv); + + *aSupportsOffline = (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::ShouldStoreMsgOffline(nsMsgKey msgKey, bool *result) +{ + NS_ENSURE_ARG(result); + uint32_t flags = 0; + *result = false; + GetFlags(&flags); + return flags & nsMsgFolderFlags::Offline ? MsgFitsDownloadCriteria(msgKey, result) : NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::HasMsgOffline(nsMsgKey msgKey, bool *result) +{ + NS_ENSURE_ARG(result); + *result = false; + GetDatabase(); + if(!mDatabase) + return NS_ERROR_FAILURE; + + nsresult rv; + nsCOMPtr<nsIMsgDBHdr> hdr; + rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr)); + if(NS_FAILED(rv)) + return rv; + + if (hdr) + { + uint32_t msgFlags = 0; + hdr->GetFlags(&msgFlags); + // check if we already have this message body offline + if ((msgFlags & nsMsgMessageFlags::Offline)) + *result = true; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder **aMsgFolder) { + NS_ENSURE_ARG_POINTER(aMsgFolder); + nsCOMPtr<nsIMsgFolder> subMsgFolder; + GetDatabase(); + if (!mDatabase) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgDBHdr> hdr; + nsresult rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr)); + if (NS_FAILED(rv)) + return rv; + + if (hdr) + { + uint32_t msgFlags = 0; + hdr->GetFlags(&msgFlags); + // Check if we already have this message body offline + if ((msgFlags & nsMsgMessageFlags::Offline)) + { + NS_IF_ADDREF(*aMsgFolder = this); + return NS_OK; + } + } + // it's okay to not get a folder. Folder is remain unchanged in that case. + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetFlags(uint32_t *_retval) +{ + ReadDBFolderInfo(false); + *_retval = mFlags; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element) +{ + nsresult rv = NS_OK; + nsCString charset; + + element->GetInt32Property("flags", (int32_t *) &mFlags); + element->GetInt32Property("totalMsgs", &mNumTotalMessages); + element->GetInt32Property("totalUnreadMsgs", &mNumUnreadMessages); + element->GetInt32Property("pendingUnreadMsgs", &mNumPendingUnreadMessages); + element->GetInt32Property("pendingMsgs", &mNumPendingTotalMessages); + element->GetInt64Property("expungedBytes", &mExpungedBytes); + element->GetInt64Property("folderSize", &mFolderSize); + element->GetStringProperty("charset", mCharset); + +#ifdef DEBUG_bienvenu1 + nsCString uri; + GetURI(uri); + printf("read total %ld for %s\n", mNumTotalMessages, uri.get()); +#endif + mInitializedFromCache = true; + return rv; +} + +nsresult nsMsgDBFolder::GetFolderCacheKey(nsIFile **aFile, bool createDBIfMissing /* = false */) +{ + nsresult rv; + nsCOMPtr <nsIFile> path; + rv = GetFilePath(getter_AddRefs(path)); + + // now we put a new file in aFile, because we're going to change it. + nsCOMPtr <nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (dbPath) + { + dbPath->InitWithFile(path); + // if not a server, we need to convert to a db Path with .msf on the end + bool isServer = false; + GetIsServer(&isServer); + + // if it's a server, we don't need the .msf appended to the name + if (!isServer) + { + nsCOMPtr <nsIFile> summaryName; + rv = GetSummaryFileLocation(dbPath, getter_AddRefs(summaryName)); + dbPath->InitWithFile(summaryName); + + // create the .msf file + // see bug #244217 for details + bool exists; + if (createDBIfMissing && NS_SUCCEEDED(dbPath->Exists(&exists)) && !exists) { + rv = dbPath->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + NS_IF_ADDREF(*aFile = dbPath); + return rv; +} + +nsresult nsMsgDBFolder::FlushToFolderCache() +{ + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && accountManager) + { + nsCOMPtr<nsIMsgFolderCache> folderCache; + rv = accountManager->GetFolderCache(getter_AddRefs(folderCache)); + if (NS_SUCCEEDED(rv) && folderCache) + rv = WriteToFolderCache(folderCache, false); + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCache(nsIMsgFolderCache *folderCache, bool deep) +{ + nsresult rv = NS_OK; + + if (folderCache) + { + nsCOMPtr <nsIMsgFolderCacheElement> cacheElement; + nsCOMPtr <nsIFile> dbPath; + rv = GetFolderCacheKey(getter_AddRefs(dbPath)); +#ifdef DEBUG_bienvenu1 + bool exists; + NS_ASSERTION(NS_SUCCEEDED(dbPath->Exists(&exists)) && exists, "file spec we're adding to cache should exist"); +#endif + if (NS_SUCCEEDED(rv) && dbPath) + { + nsCString persistentPath; + rv = dbPath->GetPersistentDescriptor(persistentPath); + NS_ENSURE_SUCCESS(rv, rv); + rv = folderCache->GetCacheElement(persistentPath, true, getter_AddRefs(cacheElement)); + if (NS_SUCCEEDED(rv) && cacheElement) + rv = WriteToFolderCacheElem(cacheElement); + } + } + + if (!deep) + return rv; + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = GetSubFolders(getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) + return rv; + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item)); + if (!msgFolder) + continue; + + if (folderCache) + { + rv = msgFolder->WriteToFolderCache(folderCache, true); + if (NS_FAILED(rv)) + break; + } + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element) +{ + nsresult rv = NS_OK; + + element->SetInt32Property("flags", (int32_t) mFlags); + element->SetInt32Property("totalMsgs", mNumTotalMessages); + element->SetInt32Property("totalUnreadMsgs", mNumUnreadMessages); + element->SetInt32Property("pendingUnreadMsgs", mNumPendingUnreadMessages); + element->SetInt32Property("pendingMsgs", mNumPendingTotalMessages); + element->SetInt64Property("expungedBytes", mExpungedBytes); + element->SetInt64Property("folderSize", mFolderSize); + element->SetStringProperty("charset", mCharset); + +#ifdef DEBUG_bienvenu1 + nsCString uri; + GetURI(uri); + printf("writing total %ld for %s\n", mNumTotalMessages, uri.get()); +#endif + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag) +{ + NS_ENSURE_ARG_POINTER(aMessage); + + nsresult rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsMsgKey msgKey; + aMessage->GetMessageKey(&msgKey); + + if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied) + mDatabase->MarkReplied(msgKey, true, nullptr); + else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded) + mDatabase->MarkForwarded(msgKey, true, nullptr); + return NS_OK; +} + +nsresult nsMsgDBFolder::AddMarkAllReadUndoAction(nsIMsgWindow *msgWindow, + nsMsgKey *thoseMarked, + uint32_t numMarked) +{ + RefPtr<nsMsgReadStateTxn> readStateTxn = new nsMsgReadStateTxn(); + if (!readStateTxn) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = readStateTxn->Init(this, numMarked, thoseMarked); + NS_ENSURE_SUCCESS(rv, rv); + + rv = readStateTxn->SetTransactionType(nsIMessenger::eMarkAllMsg); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsITransactionManager> txnMgr; + rv = msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = txnMgr->DoTransaction(readStateTxn); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow) +{ + nsresult rv = GetDatabase(); + m_newMsgs.Clear(); + + if (NS_SUCCEEDED(rv)) + { + EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/); + nsMsgKey *thoseMarked; + uint32_t numMarked; + rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked); + EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/); + NS_ENSURE_SUCCESS(rv, rv); + + // Setup a undo-state + if (aMsgWindow && numMarked) + rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked); + free(thoseMarked); + } + + SetHasNewMessages(false); + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::MarkThreadRead(nsIMsgThread *thread) +{ + nsresult rv = GetDatabase(); + if(NS_SUCCEEDED(rv)) + { + nsMsgKey *keys; + uint32_t numKeys; + rv = mDatabase->MarkThreadRead(thread, nullptr, &numKeys, &keys); + free(keys); + } + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::OnStartRunningUrl(nsIURI *aUrl) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode) +{ + NS_ENSURE_ARG_POINTER(aUrl); + nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl); + if (mailUrl) + { + bool updatingFolder = false; + if (NS_SUCCEEDED(mailUrl->GetUpdatingFolder(&updatingFolder)) && updatingFolder) + NotifyFolderEvent(mFolderLoadedAtom); + + // be sure to remove ourselves as a url listener + mailUrl->UnRegisterListener(this); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetRetentionSettings(nsIMsgRetentionSettings **settings) +{ + NS_ENSURE_ARG_POINTER(settings); + *settings = nullptr; + nsresult rv = NS_OK; + bool useServerDefaults = false; + if (!m_retentionSettings) + { + nsCString useServerRetention; + GetStringProperty(kUseServerRetentionProp, useServerRetention); + if (useServerRetention.EqualsLiteral("1")) + { + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + rv = GetServer(getter_AddRefs(incomingServer)); + if (NS_SUCCEEDED(rv) && incomingServer) + { + rv = incomingServer->GetRetentionSettings(settings); + useServerDefaults = true; + } + } + else + { + GetDatabase(); + if (mDatabase) + { + // get the settings from the db - if the settings from the db say the folder + // is not overriding the incoming server settings, get the settings from the + // server. + rv = mDatabase->GetMsgRetentionSettings(settings); + if (NS_SUCCEEDED(rv) && *settings) + { + (*settings)->GetUseServerDefaults(&useServerDefaults); + if (useServerDefaults) + { + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + rv = GetServer(getter_AddRefs(incomingServer)); + NS_IF_RELEASE(*settings); + if (NS_SUCCEEDED(rv) && incomingServer) + incomingServer->GetRetentionSettings(settings); + } + if (useServerRetention.EqualsLiteral("1") != useServerDefaults) + { + if (useServerDefaults) + useServerRetention.AssignLiteral("1"); + else + useServerRetention.AssignLiteral("0"); + SetStringProperty(kUseServerRetentionProp, useServerRetention); + } + } + } + else + return NS_ERROR_FAILURE; + } + // Only cache the retention settings if we've overridden the server + // settings (otherwise, we won't notice changes to the server settings). + if (!useServerDefaults) + m_retentionSettings = *settings; + } + else + NS_IF_ADDREF(*settings = m_retentionSettings); + + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::SetRetentionSettings(nsIMsgRetentionSettings *settings) +{ + bool useServerDefaults; + nsCString useServerRetention; + + settings->GetUseServerDefaults(&useServerDefaults); + if (useServerDefaults) + { + useServerRetention.AssignLiteral("1"); + m_retentionSettings = nullptr; + } + else + { + useServerRetention.AssignLiteral("0"); + m_retentionSettings = settings; + } + SetStringProperty(kUseServerRetentionProp, useServerRetention); + GetDatabase(); + if (mDatabase) + mDatabase->SetMsgRetentionSettings(settings); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetDownloadSettings(nsIMsgDownloadSettings **settings) +{ + NS_ENSURE_ARG_POINTER(settings); + nsresult rv = NS_OK; + if (!m_downloadSettings) + { + GetDatabase(); + if (mDatabase) + { + // get the settings from the db - if the settings from the db say the folder + // is not overriding the incoming server settings, get the settings from the + // server. + rv = mDatabase->GetMsgDownloadSettings(getter_AddRefs(m_downloadSettings)); + if (NS_SUCCEEDED(rv) && m_downloadSettings) + { + bool useServerDefaults; + m_downloadSettings->GetUseServerDefaults(&useServerDefaults); + if (useServerDefaults) + { + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + rv = GetServer(getter_AddRefs(incomingServer)); + if (NS_SUCCEEDED(rv) && incomingServer) + incomingServer->GetDownloadSettings(getter_AddRefs(m_downloadSettings)); + } + } + } + } + NS_IF_ADDREF(*settings = m_downloadSettings); + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::SetDownloadSettings(nsIMsgDownloadSettings *settings) +{ + m_downloadSettings = settings; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::IsCommandEnabled(const nsACString& command, bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = true; + return NS_OK; +} + +nsresult nsMsgDBFolder::WriteStartOfNewLocalMessage() +{ + nsAutoCString result; + uint32_t writeCount; + time_t now = time ((time_t*) 0); + char *ct = ctime(&now); + ct[24] = 0; + result = "From - "; + result += ct; + result += MSG_LINEBREAK; + m_bytesAddedToLocalMsg = result.Length(); + + nsCOMPtr <nsISeekableStream> seekable; + + if (m_offlineHeader) + seekable = do_QueryInterface(m_tempMessageStream); + + m_tempMessageStream->Write(result.get(), result.Length(), + &writeCount); + + NS_NAMED_LITERAL_CSTRING(MozillaStatus, "X-Mozilla-Status: 0001" MSG_LINEBREAK); + m_tempMessageStream->Write(MozillaStatus.get(), MozillaStatus.Length(), + &writeCount); + m_bytesAddedToLocalMsg += writeCount; + NS_NAMED_LITERAL_CSTRING(MozillaStatus2, "X-Mozilla-Status2: 00000000" MSG_LINEBREAK); + m_bytesAddedToLocalMsg += MozillaStatus2.Length(); + return m_tempMessageStream->Write(MozillaStatus2.get(), + MozillaStatus2.Length(), &writeCount); +} + +nsresult nsMsgDBFolder::StartNewOfflineMessage() +{ + bool isLocked; + GetLocked(&isLocked); + bool hasSemaphore = false; + if (isLocked) + { + // it's OK if we, the folder, have the semaphore. + TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore); + if (!hasSemaphore) + { + NS_WARNING("folder locked trying to download offline"); + return NS_MSG_FOLDER_BUSY; + } + } + nsresult rv = GetOfflineStoreOutputStream(m_offlineHeader, + getter_AddRefs(m_tempMessageStream)); + if (NS_SUCCEEDED(rv) && !hasSemaphore) + AcquireSemaphore(static_cast<nsIMsgFolder*>(this)); + if (NS_SUCCEEDED(rv)) + WriteStartOfNewLocalMessage(); + m_numOfflineMsgLines = 0; + return rv; +} + +nsresult nsMsgDBFolder::EndNewOfflineMessage() +{ + nsCOMPtr <nsISeekableStream> seekable; + int64_t curStorePos; + uint64_t messageOffset; + uint32_t messageSize; + + nsMsgKey messageKey; + + nsresult rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + + m_offlineHeader->GetMessageKey(&messageKey); + if (m_tempMessageStream) + seekable = do_QueryInterface(m_tempMessageStream); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + GetMsgStore(getter_AddRefs(msgStore)); + + if (seekable) + { + mDatabase->MarkOffline(messageKey, true, nullptr); + m_tempMessageStream->Flush(); + int64_t tellPos; + seekable->Tell(&tellPos); + curStorePos = tellPos; + + // N.B. This only works if we've set the offline flag for the message, + // so be careful about moving the call to MarkOffline above. + m_offlineHeader->GetMessageOffset(&messageOffset); + curStorePos -= messageOffset; + m_offlineHeader->SetOfflineMessageSize(curStorePos); + m_offlineHeader->GetMessageSize(&messageSize); + messageSize += m_bytesAddedToLocalMsg; + // unix/mac has a one byte line ending, but the imap server returns + // crlf terminated lines. + if (MSG_LINEBREAK_LEN == 1) + messageSize -= m_numOfflineMsgLines; + + // We clear the offline flag on the message if the size + // looks wrong. Check if we're off by more than one byte per line. + if (messageSize > (uint32_t) curStorePos && + (messageSize - (uint32_t) curStorePos) > (uint32_t) m_numOfflineMsgLines) + { + mDatabase->MarkOffline(messageKey, false, nullptr); + // we should truncate the offline store at messageOffset + ReleaseSemaphore(static_cast<nsIMsgFolder*>(this)); + if (msgStore) + // this closes the stream + msgStore->DiscardNewMessage(m_tempMessageStream, m_offlineHeader); + else + m_tempMessageStream->Close(); + m_tempMessageStream = nullptr; +#ifdef _DEBUG + nsAutoCString message("Offline message too small: messageSize="); + message.AppendInt(messageSize); + message.Append(" curStorePos="); + message.AppendInt(curStorePos); + message.Append(" numOfflineMsgLines="); + message.AppendInt(m_numOfflineMsgLines); + message.Append(" bytesAdded="); + message.AppendInt(m_bytesAddedToLocalMsg); + NS_ERROR(message.get()); +#endif + m_offlineHeader = nullptr; + return NS_ERROR_FAILURE; + } + else + m_offlineHeader->SetLineCount(m_numOfflineMsgLines); + } + if (msgStore) + msgStore->FinishNewMessage(m_tempMessageStream, m_offlineHeader); + + m_offlineHeader = nullptr; + if (m_tempMessageStream) + { + m_tempMessageStream->Close(); + m_tempMessageStream = nullptr; + } + return NS_OK; +} + +nsresult nsMsgDBFolder::CompactOfflineStore(nsIMsgWindow *inWindow, nsIUrlListener *aListener) +{ + nsresult rv; + nsCOMPtr <nsIMsgFolderCompactor> folderCompactor = do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderCompactor->Compact(this, true, aListener, inWindow); +} + +class AutoCompactEvent : public mozilla::Runnable +{ +public: + AutoCompactEvent(nsIMsgWindow *aMsgWindow, nsMsgDBFolder *aFolder) + : mMsgWindow(aMsgWindow), mFolder(aFolder) + {} + + NS_IMETHOD Run() + { + if (mFolder) + mFolder->HandleAutoCompactEvent(mMsgWindow); + return NS_OK; + } + +private: + nsCOMPtr<nsIMsgWindow> mMsgWindow; + RefPtr<nsMsgDBFolder> mFolder; +}; + +nsresult nsMsgDBFolder::HandleAutoCompactEvent(nsIMsgWindow *aWindow) +{ + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIArray> allServers; + rv = accountMgr->GetAllServers(getter_AddRefs(allServers)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numServers = 0, serverIndex = 0; + rv = allServers->GetLength(&numServers); + int32_t offlineSupportLevel; + if (numServers > 0) + { + nsCOMPtr<nsIMutableArray> folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMutableArray> offlineFolderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + int64_t totalExpungedBytes = 0; + int64_t offlineExpungedBytes = 0; + int64_t localExpungedBytes = 0; + do + { + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(allServers, serverIndex, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + if (!msgStore) + continue; + bool supportsCompaction; + msgStore->GetSupportsCompaction(&supportsCompaction); + if (!supportsCompaction) + continue; + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + if(NS_SUCCEEDED(rv) && rootFolder) + { + rv = server->GetOfflineSupportLevel(&offlineSupportLevel); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIArray> allDescendents; + rootFolder->GetDescendants(getter_AddRefs(allDescendents)); + uint32_t cnt = 0; + rv = allDescendents->GetLength(&cnt); + NS_ENSURE_SUCCESS(rv, rv); + int64_t expungedBytes = 0; + if (offlineSupportLevel > 0) + { + uint32_t flags; + for (uint32_t i = 0; i < cnt; i++) + { + nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i); + expungedBytes = 0; + folder->GetFlags(&flags); + if (flags & nsMsgFolderFlags::Offline) + folder->GetExpungedBytes(&expungedBytes); + if (expungedBytes > 0 ) + { + offlineFolderArray->AppendElement(folder, false); + offlineExpungedBytes += expungedBytes; + } + } + } + else //pop or local + { + for (uint32_t i = 0; i < cnt; i++) + { + nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i); + expungedBytes = 0; + folder->GetExpungedBytes(&expungedBytes); + if (expungedBytes > 0 ) + { + folderArray->AppendElement(folder, false); + localExpungedBytes += expungedBytes; + } + } + } + } + } + while (++serverIndex < numServers); + totalExpungedBytes = localExpungedBytes + offlineExpungedBytes; + int32_t purgeThreshold; + rv = GetPurgeThreshold(&purgeThreshold); + NS_ENSURE_SUCCESS(rv, rv); + if (totalExpungedBytes > (purgeThreshold * 1024)) + { + bool okToCompact = false; + nsCOMPtr<nsIPrefService> pref = do_GetService(NS_PREFSERVICE_CONTRACTID); + nsCOMPtr<nsIPrefBranch> branch; + pref->GetBranch("", getter_AddRefs(branch)); + + bool askBeforePurge; + branch->GetBoolPref(PREF_MAIL_PURGE_ASK, &askBeforePurge); + if (askBeforePurge && aWindow) + { + nsCOMPtr <nsIStringBundle> bundle; + rv = GetBaseStringBundle(getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + nsString dialogTitle; + nsString confirmString; + nsString checkboxText; + nsString buttonCompactNowText; + nsAutoString compactSize; + FormatFileSize(totalExpungedBytes, true, compactSize); + const char16_t* params[] = { compactSize.get() }; + rv = bundle->GetStringFromName(u"autoCompactAllFoldersTitle", getter_Copies(dialogTitle)); + NS_ENSURE_SUCCESS(rv, rv); + rv = bundle->FormatStringFromName(u"autoCompactAllFoldersText", + params, 1, getter_Copies(confirmString)); + NS_ENSURE_SUCCESS(rv, rv); + rv = bundle->GetStringFromName(u"autoCompactAlwaysAskCheckbox", + getter_Copies(checkboxText)); + NS_ENSURE_SUCCESS(rv, rv); + rv = bundle->GetStringFromName(u"compactNowButton", + getter_Copies(buttonCompactNowText)); + NS_ENSURE_SUCCESS(rv, rv); + bool alwaysAsk = true; // "Always ask..." - checked by default. + int32_t buttonPressed = 0; + + nsCOMPtr<nsIPrompt> dialog; + rv = aWindow->GetPromptDialog(getter_AddRefs(dialog)); + NS_ENSURE_SUCCESS(rv, rv); + + const uint32_t buttonFlags = + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1); + rv = dialog->ConfirmEx(dialogTitle.get(), confirmString.get(), buttonFlags, + buttonCompactNowText.get(), nullptr, nullptr, + checkboxText.get(), &alwaysAsk, &buttonPressed); + NS_ENSURE_SUCCESS(rv, rv); + if (!buttonPressed) + { + okToCompact = true; + if (!alwaysAsk) // [ ] Always ask me before compacting folders automatically + branch->SetBoolPref(PREF_MAIL_PURGE_ASK, false); + } + } + else + okToCompact = aWindow || !askBeforePurge; + + if (okToCompact) + { + nsCOMPtr <nsIAtom> aboutToCompactAtom = MsgGetAtom("AboutToCompact"); + NotifyFolderEvent(aboutToCompactAtom); + + if (localExpungedBytes > 0) + { + nsCOMPtr<nsIMsgFolderCompactor> folderCompactor = + do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (offlineExpungedBytes > 0) + folderCompactor->CompactFolders(folderArray, offlineFolderArray, nullptr, aWindow); + else + folderCompactor->CompactFolders(folderArray, nullptr, nullptr, aWindow); + } + else if (offlineExpungedBytes > 0) + CompactAllOfflineStores(nullptr, aWindow, offlineFolderArray); + } + } + } + } + return rv; +} + +nsresult +nsMsgDBFolder::AutoCompact(nsIMsgWindow *aWindow) +{ + // we don't check for null aWindow, because this routine can get called + // in unit tests where we have no window. Just assume not OK if no window. + bool prompt; + nsresult rv = GetPromptPurgeThreshold(&prompt); + NS_ENSURE_SUCCESS(rv, rv); + PRTime timeNow = PR_Now(); //time in microseconds + PRTime timeAfterOneHourOfLastPurgeCheck = gtimeOfLastPurgeCheck + oneHour; + if (timeAfterOneHourOfLastPurgeCheck < timeNow && prompt) + { + gtimeOfLastPurgeCheck = timeNow; + nsCOMPtr<nsIRunnable> event = new AutoCompactEvent(aWindow, this); + // Post this as an event because it can put up an alert, which + // might cause issues depending on the stack when we are called. + if (event) + NS_DispatchToCurrentThread(event); + } + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::CompactAllOfflineStores(nsIUrlListener *aUrlListener, + nsIMsgWindow *aWindow, + nsIArray *aOfflineFolderArray) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolderCompactor> folderCompactor + = do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderCompactor->CompactFolders(nullptr, aOfflineFolderArray, aUrlListener, aWindow); +} + +nsresult +nsMsgDBFolder::GetPromptPurgeThreshold(bool *aPrompt) +{ + NS_ENSURE_ARG(aPrompt); + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && prefBranch) + { + rv = prefBranch->GetBoolPref(PREF_MAIL_PROMPT_PURGE_THRESHOLD, aPrompt); + if (NS_FAILED(rv)) + { + *aPrompt = false; + rv = NS_OK; + } + } + return rv; +} + +nsresult +nsMsgDBFolder::GetPurgeThreshold(int32_t *aThreshold) +{ + NS_ENSURE_ARG(aThreshold); + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && prefBranch) + { + int32_t thresholdMB = 20; + bool thresholdMigrated = false; + prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, &thresholdMB); + prefBranch->GetBoolPref(PREF_MAIL_PURGE_MIGRATED, &thresholdMigrated); + if (!thresholdMigrated) + { + *aThreshold = 20480; + (void) prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD, aThreshold); + if (*aThreshold/1024 != thresholdMB) + { + thresholdMB = std::max(1, *aThreshold/1024); + prefBranch->SetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, thresholdMB); + } + prefBranch->SetBoolPref(PREF_MAIL_PURGE_MIGRATED, true); + } + *aThreshold = thresholdMB * 1024; + } + return rv; +} + +NS_IMETHODIMP //called on the folder that is renamed or about to be deleted +nsMsgDBFolder::MatchOrChangeFilterDestination(nsIMsgFolder *newFolder, bool caseInsensitive, bool *found) +{ + NS_ENSURE_ARG_POINTER(found); + nsCString oldUri; + nsresult rv = GetURI(oldUri); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString newUri; + if (newFolder) //for matching uri's this will be null + { + rv = newFolder->GetURI(newUri); + NS_ENSURE_SUCCESS(rv,rv); + } + + nsCOMPtr<nsIMsgFilterList> filterList; + nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIArray> allServers; + rv = accountMgr->GetAllServers(getter_AddRefs(allServers)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t numServers; + rv = allServers->GetLength(&numServers); + for (uint32_t serverIndex = 0; serverIndex < numServers; serverIndex++) + { + nsCOMPtr <nsIMsgIncomingServer> server = do_QueryElementAt(allServers, serverIndex); + if (server) + { + bool canHaveFilters; + rv = server->GetCanHaveFilters(&canHaveFilters); + if (NS_SUCCEEDED(rv) && canHaveFilters) + { + // update the filterlist to match the new folder name + rv = server->GetFilterList(nullptr, getter_AddRefs(filterList)); + if (NS_SUCCEEDED(rv) && filterList) + { + rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found); + if (NS_SUCCEEDED(rv) && *found && newFolder && !newUri.IsEmpty()) + rv = filterList->SaveToDefaultFile(); + } + // update the editable filterlist to match the new folder name + rv = server->GetEditableFilterList(nullptr, getter_AddRefs(filterList)); + if (NS_SUCCEEDED(rv) && filterList) + { + rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found); + if (NS_SUCCEEDED(rv) && *found && newFolder && !newUri.IsEmpty()) + rv = filterList->SaveToDefaultFile(); + } + } + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetDBTransferInfo(nsIDBFolderInfo **aTransferInfo) +{ + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr <nsIMsgDatabase> db; + GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db)); + if (dbFolderInfo) + dbFolderInfo->GetTransferInfo(aTransferInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::SetDBTransferInfo(nsIDBFolderInfo *aTransferInfo) +{ + NS_ENSURE_ARG(aTransferInfo); + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr <nsIMsgDatabase> db; + GetMsgDatabase(getter_AddRefs(db)); + if (db) + { + db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if(dbFolderInfo) + { + dbFolderInfo->InitFromTransferInfo(aTransferInfo); + dbFolderInfo->SetBooleanProperty("forceReparse", false); + } + db->SetSummaryValid(true); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetStringProperty(const char *propertyName, nsACString& propertyValue) +{ + NS_ENSURE_ARG_POINTER(propertyName); + nsCOMPtr <nsIFile> dbPath; + nsresult rv = GetFolderCacheKey(getter_AddRefs(dbPath)); + if (dbPath) + { + nsCOMPtr <nsIMsgFolderCacheElement> cacheElement; + rv = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement)); + if (cacheElement) //try to get from cache + rv = cacheElement->GetStringProperty(propertyName, propertyValue); + if (NS_FAILED(rv)) //if failed, then try to get from db + { + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgDatabase> db; + bool exists; + rv = dbPath->Exists(&exists); + if (NS_FAILED(rv) || !exists) + return NS_MSG_ERROR_FOLDER_MISSING; + rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if (NS_SUCCEEDED(rv)) + rv = folderInfo->GetCharProperty(propertyName, propertyValue); + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::SetStringProperty(const char *propertyName, const nsACString& propertyValue) +{ + NS_ENSURE_ARG_POINTER(propertyName); + nsCOMPtr <nsIFile> dbPath; + GetFolderCacheKey(getter_AddRefs(dbPath)); + if (dbPath) + { + nsCOMPtr <nsIMsgFolderCacheElement> cacheElement; + GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement)); + if (cacheElement) //try to set in the cache + cacheElement->SetStringProperty(propertyName, propertyValue); + } + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if(NS_SUCCEEDED(rv)) + { + folderInfo->SetCharProperty(propertyName, propertyValue); + db->Commit(nsMsgDBCommitType::kLargeCommit); //commiting the db also commits the cache + } + return NS_OK; +} + +// Get/Set ForcePropertyEmpty is only used with inherited properties +NS_IMETHODIMP +nsMsgDBFolder::GetForcePropertyEmpty(const char *aPropertyName, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + nsAutoCString nameEmpty(aPropertyName); + nameEmpty.Append(NS_LITERAL_CSTRING(".empty")); + nsCString value; + GetStringProperty(nameEmpty.get(), value); + *_retval = value.EqualsLiteral("true"); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::SetForcePropertyEmpty(const char *aPropertyName, bool aValue) +{ + nsAutoCString nameEmpty(aPropertyName); + nameEmpty.Append(NS_LITERAL_CSTRING(".empty")); + return SetStringProperty(nameEmpty.get(), + aValue ? NS_LITERAL_CSTRING("true") : NS_LITERAL_CSTRING("")); +} + +NS_IMETHODIMP +nsMsgDBFolder::GetInheritedStringProperty(const char *aPropertyName, nsACString& aPropertyValue) +{ + NS_ENSURE_ARG_POINTER(aPropertyName); + nsCString value; + nsCOMPtr<nsIMsgIncomingServer> server; + + bool forceEmpty = false; + + if (!mIsServer) + { + GetForcePropertyEmpty(aPropertyName, &forceEmpty); + } + else + { + // root folders must get their values from the server + GetServer(getter_AddRefs(server)); + if (server) + server->GetForcePropertyEmpty(aPropertyName, &forceEmpty); + } + + if (forceEmpty) + { + aPropertyValue.Truncate(); + return NS_OK; + } + + // servers will automatically inherit from the preference mail.server.default.(propertyName) + if (server) + return server->GetCharValue(aPropertyName, aPropertyValue); + + GetStringProperty(aPropertyName, value); + if (value.IsEmpty()) + { + // inherit from the parent + nsCOMPtr<nsIMsgFolder> parent; + GetParent(getter_AddRefs(parent)); + if (parent) + return parent->GetInheritedStringProperty(aPropertyName, aPropertyValue); + } + + aPropertyValue.Assign(value); + return NS_OK; +} + +nsresult +nsMsgDBFolder::SpamFilterClassifyMessage(const char *aURI, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin) +{ + nsresult rv; + nsCOMPtr<nsIMsgTraitService> traitService(do_GetService("@mozilla.org/msg-trait-service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count; + uint32_t *proIndices; + uint32_t *antiIndices; + rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aJunkMailPlugin->ClassifyTraitsInMessage(aURI, count, proIndices, antiIndices, this, aMsgWindow, this); + NS_Free(proIndices); + NS_Free(antiIndices); + return rv; +} + +nsresult +nsMsgDBFolder::SpamFilterClassifyMessages(const char **aURIArray, uint32_t aURICount, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin) +{ + + nsresult rv; + nsCOMPtr<nsIMsgTraitService> traitService(do_GetService("@mozilla.org/msg-trait-service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count; + uint32_t *proIndices; + uint32_t *antiIndices; + rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aJunkMailPlugin->ClassifyTraitsInMessages(aURICount, aURIArray, count, + proIndices, antiIndices, this, aMsgWindow, this); + NS_Free(proIndices); + NS_Free(antiIndices); + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::OnMessageClassified(const char *aMsgURI, + nsMsgJunkStatus aClassification, + uint32_t aJunkPercent) +{ + if (!aMsgURI) // This signifies end of batch. + { + nsresult rv = NS_OK; + // Apply filters if needed. + uint32_t length; + if (mPostBayesMessagesToFilter && + NS_SUCCEEDED(mPostBayesMessagesToFilter->GetLength(&length)) && + length) + { + // Apply post-bayes filtering. + nsCOMPtr<nsIMsgFilterService> + filterService(do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + // We use a null nsIMsgWindow because we don't want some sort of ui + // appearing in the middle of automatic filtering (plus I really don't + // want to propagate that value.) + rv = filterService->ApplyFilters(nsMsgFilterType::PostPlugin, + mPostBayesMessagesToFilter, + this, nullptr, nullptr); + mPostBayesMessagesToFilter->Clear(); + } + + // Bail if we didn't actually classify any messages. + if (mClassifiedMsgKeys.IsEmpty()) + return rv; + + // Notify that we classified some messages. + nsCOMPtr<nsIMsgFolderNotificationService> + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIMutableArray> classifiedMsgHdrs = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t numKeys = mClassifiedMsgKeys.Length(); + for (uint32_t i = 0 ; i < numKeys ; ++i) + { + nsMsgKey msgKey = mClassifiedMsgKeys[i]; + bool hasKey; + // It is very possible for a message header to no longer be around because + // a filter moved it. + rv = mDatabase->ContainsKey(msgKey, &hasKey); + if (!NS_SUCCEEDED(rv) || !hasKey) + continue; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr)); + if (!NS_SUCCEEDED(rv)) + continue; + classifiedMsgHdrs->AppendElement(msgHdr, false); + } + + // only generate the notification if there are some classified messages + if (NS_SUCCEEDED(classifiedMsgHdrs->GetLength(&length)) && length) + notifier->NotifyMsgsClassified(classifiedMsgHdrs, + mBayesJunkClassifying, + mBayesTraitClassifying); + mClassifiedMsgKeys.Clear(); + + return rv; + } + + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISpamSettings> spamSettings; + rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgKey msgKey; + rv = msgHdr->GetMessageKey(&msgKey); + NS_ENSURE_SUCCESS(rv, rv); + + // check if this message needs junk classification + uint32_t processingFlags; + GetProcessingFlags(msgKey, &processingFlags); + + if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) + { + mClassifiedMsgKeys.AppendElement(msgKey); + AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyJunk); + + nsAutoCString msgJunkScore; + msgJunkScore.AppendInt(aClassification == nsIJunkMailPlugin::JUNK ? + nsIJunkMailPlugin::IS_SPAM_SCORE: + nsIJunkMailPlugin::IS_HAM_SCORE); + mDatabase->SetStringProperty(msgKey, "junkscore", msgJunkScore.get()); + mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "plugin"); + + nsAutoCString strPercent; + strPercent.AppendInt(aJunkPercent); + mDatabase->SetStringProperty(msgKey, "junkpercent", strPercent.get()); + + if (aClassification == nsIJunkMailPlugin::JUNK) + { + // IMAP has its own way of marking read. + if (!(mFlags & nsMsgFolderFlags::ImapBox)) + { + bool markAsReadOnSpam; + (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam); + if (markAsReadOnSpam) + { + rv = mDatabase->MarkRead(msgKey, true, this); + if (!NS_SUCCEEDED(rv)) + NS_WARNING("failed marking spam message as read"); + } + } + // mail folders will log junk hits with move info. Perhaps we should + // add a log here for non-mail folders as well, that don't override + // onMessageClassified + //rv = spamSettings->LogJunkHit(msgHdr, false); + //NS_ENSURE_SUCCESS(rv,rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::OnMessageTraitsClassified(const char *aMsgURI, + uint32_t aTraitCount, + uint32_t *aTraits, + uint32_t *aPercents) +{ + if (!aMsgURI) // This signifies end of batch + return NS_OK; // We are not handling batching + + nsresult rv; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgKey msgKey; + rv = msgHdr->GetMessageKey(&msgKey); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t processingFlags; + GetProcessingFlags(msgKey, &processingFlags); + if (!(processingFlags & nsMsgProcessingFlags::ClassifyTraits)) + return NS_OK; + + AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyTraits); + + nsCOMPtr<nsIMsgTraitService> traitService; + traitService = do_GetService("@mozilla.org/msg-trait-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < aTraitCount; i++) + { + if (aTraits[i] == nsIJunkMailPlugin::JUNK_TRAIT) + continue; // junk is processed by the junk listener + nsAutoCString traitId; + rv = traitService->GetId(aTraits[i], traitId); + traitId.Insert(NS_LITERAL_CSTRING("bayespercent/"), 0); + nsAutoCString strPercent; + strPercent.AppendInt(aPercents[i]); + mDatabase->SetStringPropertyByHdr(msgHdr, traitId.get(), strPercent.get()); + } + return NS_OK; +} + +/** + * Call the filter plugins (XXX currently just one) + */ +NS_IMETHODIMP +nsMsgDBFolder::CallFilterPlugins(nsIMsgWindow *aMsgWindow, bool *aFiltersRun) +{ + NS_ENSURE_ARG_POINTER(aFiltersRun); + *aFiltersRun = false; + nsCOMPtr<nsIMsgIncomingServer> server; + nsCOMPtr<nsISpamSettings> spamSettings; + int32_t spamLevel = 0; + + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString serverType; + server->GetType(serverType); + + rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); + nsCOMPtr <nsIMsgFilterPlugin> filterPlugin; + server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin)); + if (!filterPlugin) // it's not an error not to have the filter plugin. + return NS_OK; + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIJunkMailPlugin> junkMailPlugin = do_QueryInterface(filterPlugin); + if (!junkMailPlugin) // we currently only support the junk mail plugin + return NS_OK; + + // if it's a news folder, then we really don't support junk in the ui + // yet the legacy spamLevel seems to think we should analyze it. + // Maybe we should upgrade that, but for now let's not analyze. We'll + // let an extension set an inherited property if they really want us to + // analyze this. We need that anyway to allow extension-based overrides. + // When we finalize adding junk in news to core, we'll deal with the + // spamLevel issue + + // if this is the junk folder, or the trash folder + // don't analyze for spam, because we don't care + // + // if it's the sent, unsent, templates, or drafts, + // don't analyze for spam, because the user + // created that message + // + // if it's a public imap folder, or another users + // imap folder, don't analyze for spam, because + // it's not ours to analyze + // + + bool filterForJunk = true; + if (serverType.EqualsLiteral("rss") || + (mFlags & (nsMsgFolderFlags::SpecialUse | + nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::Newsgroup | + nsMsgFolderFlags::ImapOtherUser) && + !(mFlags & nsMsgFolderFlags::Inbox))) + filterForJunk = false; + + spamSettings->GetLevel(&spamLevel); + if (!spamLevel) + filterForJunk = false; + + /* + * We'll use inherited folder properties for the junk trait to override the + * standard server-based activation of junk processing. This provides a + * hook for extensions to customize the application of junk filtering. + * Set inherited property "dobayes.mailnews@mozilla.org#junk" to "true" + * to force junk processing, and "false" to skip junk processing. + */ + + nsAutoCString junkEnableOverride; + GetInheritedStringProperty("dobayes.mailnews@mozilla.org#junk", junkEnableOverride); + if (junkEnableOverride.EqualsLiteral("true")) + filterForJunk = true; + else if (junkEnableOverride.EqualsLiteral("false")) + filterForJunk = false; + + bool userHasClassified = false; + // if the user has not classified any messages yet, then we shouldn't bother + // running the junk mail controls. This creates a better first use experience. + // See Bug #250084. + junkMailPlugin->GetUserHasClassified(&userHasClassified); + if (!userHasClassified) + filterForJunk = false; + + nsCOMPtr<nsIMsgDatabase> database(mDatabase); + rv = GetMsgDatabase(getter_AddRefs(database)); + NS_ENSURE_SUCCESS(rv, rv); + + // check if trait processing needed + + nsCOMPtr<nsIMsgTraitService> traitService( + do_GetService("@mozilla.org/msg-trait-service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count = 0, *proIndices, *antiIndices; + rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices); + bool filterForOther = false; + if (NS_SUCCEEDED(rv)) // We just skip this on failure, since it is rarely used + { + for (uint32_t i = 0; i < count; ++i) + { + // The trait service determines which traits are globally enabled or + // disabled. If a trait is enabled, it can still be made inactive + // on a particular folder using an inherited property. To do that, + // set "dobayes." + trait proID as an inherited folder property with + // the string value "false" + // + // If any non-junk traits are active on the folder, then the bayes + // processing will calculate probabilities for all enabled traits. + + if (proIndices[i] != nsIJunkMailPlugin::JUNK_TRAIT) + { + filterForOther = true; + nsAutoCString traitId; + nsAutoCString property("dobayes."); + traitService->GetId(proIndices[i], traitId); + property.Append(traitId); + nsAutoCString isEnabledOnFolder; + GetInheritedStringProperty(property.get(), isEnabledOnFolder); + if (isEnabledOnFolder.EqualsLiteral("false")) + filterForOther = false; + // We might have to allow a "true" override in the future, but + // for now there is no way for that to affect the processing + break; + } + } + NS_Free(proIndices); + NS_Free(antiIndices); + } + + // Do we need to apply message filters? + bool filterPostPlugin = false; // Do we have a post-analysis filter? + nsCOMPtr<nsIMsgFilterList> filterList; + GetFilterList(aMsgWindow, getter_AddRefs(filterList)); + if (filterList) + { + uint32_t filterCount = 0; + filterList->GetFilterCount(&filterCount); + for (uint32_t index = 0; index < filterCount && !filterPostPlugin; ++index) + { + nsCOMPtr<nsIMsgFilter> filter; + filterList->GetFilterAt(index, getter_AddRefs(filter)); + if (!filter) + continue; + nsMsgFilterTypeType filterType; + filter->GetFilterType(&filterType); + if (!(filterType & nsMsgFilterType::PostPlugin)) + continue; + bool enabled = false; + filter->GetEnabled(&enabled); + if (!enabled) + continue; + filterPostPlugin = true; + } + } + + // If there is nothing to do, leave now but let NotifyHdrsNotBeingClassified + // generate the msgsClassified notification for all newly added messages as + // tracked by the NotReportedClassified processing flag. + if (!filterForOther && !filterForJunk && !filterPostPlugin) + { + NotifyHdrsNotBeingClassified(); + return NS_OK; + } + + // get the list of new messages + // + uint32_t numNewKeys; + nsMsgKey *newKeys; + rv = database->GetNewList(&numNewKeys, &newKeys); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<nsMsgKey> newMessageKeys; + // Start from m_saveNewMsgs (and clear its current state). m_saveNewMsgs is + // where we stash the list of new messages when we are told to clear the list + // of new messages by the UI (which purges the list from the nsMsgDatabase). + newMessageKeys.SwapElements(m_saveNewMsgs); + if (numNewKeys) + newMessageKeys.AppendElements(newKeys, numNewKeys); + + NS_Free(newKeys); + + // build up list of keys to classify + nsTArray<nsMsgKey> classifyMsgKeys; + nsCString uri; + + uint32_t numNewMessages = newMessageKeys.Length(); + for (uint32_t i = 0 ; i < numNewMessages ; ++i) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsMsgKey msgKey = newMessageKeys[i]; + rv = database->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr)); + if (!NS_SUCCEEDED(rv)) + continue; + // per-message junk tests. + bool filterMessageForJunk = false; + while (filterForJunk) // we'll break from this at the end + { + nsCString junkScore; + msgHdr->GetStringProperty("junkscore", getter_Copies(junkScore)); + if (!junkScore.IsEmpty()) // ignore already scored messages. + break; + + bool whiteListMessage = false; + spamSettings->CheckWhiteList(msgHdr, &whiteListMessage); + if (whiteListMessage) + { + // mark this msg as non-junk, because we whitelisted it. + + nsAutoCString msgJunkScore; + msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE); + database->SetStringProperty(msgKey, "junkscore", msgJunkScore.get()); + database->SetStringProperty(msgKey, "junkscoreorigin", "whitelist"); + break; // skip this msg since it's in the white list + } + filterMessageForJunk = true; + + OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyJunk); + // Since we are junk processing, we want to defer the msgsClassified + // notification until the junk classification has occurred. The event + // is sufficiently reliable that we know this will be handled in + // OnMessageClassified at the end of the batch. We clear the + // NotReportedClassified flag since we know the message is in good hands. + AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::NotReportedClassified); + break; + } + + uint32_t processingFlags; + GetProcessingFlags(msgKey, &processingFlags); + + bool filterMessageForOther = false; + // trait processing + if (!(processingFlags & nsMsgProcessingFlags::TraitsDone)) + { + // don't do trait processing on this message again + OrProcessingFlags(msgKey, nsMsgProcessingFlags::TraitsDone); + if (filterForOther) + { + filterMessageForOther = true; + OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyTraits); + } + } + + if (filterMessageForJunk || filterMessageForOther) + classifyMsgKeys.AppendElement(newMessageKeys[i]); + + // Set messages to filter post-bayes. + // Have we already filtered this message? + if (!(processingFlags & nsMsgProcessingFlags::FiltersDone)) + { + if (filterPostPlugin) + { + // Don't do filters on this message again. + // (Only set this if we are actually filtering since this is + // tantamount to a memory leak.) + OrProcessingFlags(msgKey, nsMsgProcessingFlags::FiltersDone); + // Lazily create the array. + if (!mPostBayesMessagesToFilter) + mPostBayesMessagesToFilter = do_CreateInstance(NS_ARRAY_CONTRACTID); + mPostBayesMessagesToFilter->AppendElement(msgHdr, false); + } + } + } + + NotifyHdrsNotBeingClassified(); + // If there weren't any new messages, just return. + if (newMessageKeys.IsEmpty()) + return NS_OK; + + // If we do not need to do any work, leave. + // (We needed to get the list of new messages so we could get their headers so + // we can send notifications about them here.) + + if (!classifyMsgKeys.IsEmpty()) + { + // Remember what classifications are the source of this decision for when + // we perform the notification in OnMessageClassified at the conclusion of + // classification. + mBayesJunkClassifying = filterForJunk; + mBayesTraitClassifying = filterForOther; + + uint32_t numMessagesToClassify = classifyMsgKeys.Length(); + char ** messageURIs = (char **) PR_MALLOC(sizeof(const char *) * numMessagesToClassify); + if (!messageURIs) + return NS_ERROR_OUT_OF_MEMORY; + + for (uint32_t msgIndex = 0; msgIndex < numMessagesToClassify ; ++msgIndex ) + { + nsCString tmpStr; + rv = GenerateMessageURI(classifyMsgKeys[msgIndex], tmpStr); + messageURIs[msgIndex] = ToNewCString(tmpStr); + if (NS_FAILED(rv)) + NS_WARNING("nsMsgDBFolder::CallFilterPlugins(): could not" + " generate URI for message"); + } + // filterMsgs + *aFiltersRun = true; + rv = SpamFilterClassifyMessages((const char **) messageURIs, numMessagesToClassify, aMsgWindow, junkMailPlugin); + for ( uint32_t freeIndex=0 ; freeIndex < numMessagesToClassify ; ++freeIndex ) + PR_Free(messageURIs[freeIndex]); + PR_Free(messageURIs); + } + else if (filterPostPlugin) + { + // Nothing to classify, so need to end batch ourselves. We do this so that + // post analysis filters will run consistently on a folder, even if + // disabled junk processing, which could be dynamic through whitelisting, + // makes the bayes analysis unnecessary. + OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0); + } + + return rv; +} + +/** + * Adds the messages in the NotReportedClassified mProcessing set to the + * (possibly empty) array of msgHdrsNotBeingClassified, and send the + * nsIMsgFolderNotificationService notification. + */ +nsresult nsMsgDBFolder::NotifyHdrsNotBeingClassified() +{ + nsCOMPtr<nsIMutableArray> msgHdrsNotBeingClassified; + + if (mProcessingFlag[5].keys) + { + nsTArray<nsMsgKey> keys; + mProcessingFlag[5].keys->ToMsgKeyArray(keys); + if (keys.Length()) + { + msgHdrsNotBeingClassified = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (!msgHdrsNotBeingClassified) + return NS_ERROR_OUT_OF_MEMORY; + nsresult rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + MsgGetHeadersFromKeys(mDatabase, keys, msgHdrsNotBeingClassified); + + // Since we know we've handled all the NotReportedClassified messages, + // we clear the set by deleting and recreating it. + delete mProcessingFlag[5].keys; + mProcessingFlag[5].keys = nsMsgKeySetU::Create(); + nsCOMPtr<nsIMsgFolderNotificationService> + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgsClassified(msgHdrsNotBeingClassified, + // no classification is being performed + false, false); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetLastMessageLoaded(nsMsgKey *aMsgKey) +{ + NS_ENSURE_ARG_POINTER(aMsgKey); + *aMsgKey = mLastMessageLoaded; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::SetLastMessageLoaded(nsMsgKey aMsgKey) +{ + mLastMessageLoaded = aMsgKey; + return NS_OK; +} + +// Returns true if: a) there is no need to prompt or b) the user is already +// logged in or c) the user logged in successfully. +bool nsMsgDBFolder::PromptForMasterPasswordIfNecessary() +{ + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, false); + + bool userNeedsToAuthenticate = false; + // if we're PasswordProtectLocalCache, then we need to find out if the server + // is authenticated. + (void) accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate); + if (!userNeedsToAuthenticate) + return true; + + // Do we have a master password? + nsCOMPtr<nsIPK11TokenDB> tokenDB = + do_GetService(NS_PK11TOKENDB_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsIPK11Token> token; + rv = tokenDB->GetInternalKeyToken(getter_AddRefs(token)); + NS_ENSURE_SUCCESS(rv, false); + + bool result; + rv = token->CheckPassword(EmptyCString(), &result); + NS_ENSURE_SUCCESS(rv, false); + + if (result) + { + // We don't have a master password, so this function isn't supported, + // therefore just tell account manager we've authenticated and return true. + accountManager->SetUserNeedsToAuthenticate(false); + return true; + } + + // We have a master password, so try and login to the slot. + rv = token->Login(false); + if (NS_FAILED(rv)) + // Login failed, so we didn't get a password (e.g. prompt cancelled). + return false; + + // Double-check that we are now logged in + rv = token->IsLoggedIn(&result); + NS_ENSURE_SUCCESS(rv, false); + + accountManager->SetUserNeedsToAuthenticate(!result); + return result; +} + +// this gets called after the last junk mail classification has run. +nsresult nsMsgDBFolder::PerformBiffNotifications(void) +{ + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + int32_t numBiffMsgs = 0; + nsCOMPtr<nsIMsgFolder> root; + rv = GetRootFolder(getter_AddRefs(root)); + root->GetNumNewMessages(true, &numBiffMsgs); + if (numBiffMsgs > 0) + { + server->SetPerformingBiff(true); + SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail); + server->SetPerformingBiff(false); + } + return NS_OK; +} + +nsresult +nsMsgDBFolder::initializeStrings() +{ + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + bundle->GetStringFromName(u"inboxFolderName", + &kLocalizedInboxName); + bundle->GetStringFromName(u"trashFolderName", + &kLocalizedTrashName); + bundle->GetStringFromName(u"sentFolderName", + &kLocalizedSentName); + bundle->GetStringFromName(u"draftsFolderName", + &kLocalizedDraftsName); + bundle->GetStringFromName(u"templatesFolderName", + &kLocalizedTemplatesName); + bundle->GetStringFromName(u"junkFolderName", + &kLocalizedJunkName); + bundle->GetStringFromName(u"outboxFolderName", + &kLocalizedUnsentName); + bundle->GetStringFromName(u"archivesFolderName", + &kLocalizedArchivesName); + + nsCOMPtr<nsIStringBundle> brandBundle; + rv = bundleService->CreateBundle("chrome://branding/locale/brand.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + bundle->GetStringFromName(u"brandShortName", + &kLocalizedBrandShortName); + return NS_OK; +} + +nsresult +nsMsgDBFolder::createCollationKeyGenerator() +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsILocaleService> localeSvc = do_GetService(NS_LOCALESERVICE_CONTRACTID,&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILocale> locale; + rv = localeSvc->GetApplicationLocale(getter_AddRefs(locale)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsICollationFactory> factory = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = factory->CreateCollation(locale, &gCollationKeyGenerator); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::Init(const char* aURI) +{ + // for now, just initialize everything during Init() + nsresult rv; + rv = nsRDFResource::Init(aURI); + NS_ENSURE_SUCCESS(rv, rv); + return CreateBaseMessageURI(nsDependentCString(aURI)); +} + +nsresult nsMsgDBFolder::CreateBaseMessageURI(const nsACString& aURI) +{ + // Each folder needs to implement this. + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetURI(nsACString& name) +{ + return nsRDFResource::GetValueUTF8(name); +} + +//////////////////////////////////////////////////////////////////////////////// +#if 0 +typedef bool +(*nsArrayFilter)(nsISupports* element, void* data); +#endif +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsMsgDBFolder::GetSubFolders(nsISimpleEnumerator **aResult) +{ + return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsMsgDBFolder::FindSubFolder(const nsACString& aEscapedSubFolderName, nsIMsgFolder **aFolder) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv)); + + if (NS_FAILED(rv)) + return rv; + + // XXX use necko here + nsAutoCString uri; + uri.Append(mURI); + uri.Append('/'); + uri.Append(aEscapedSubFolderName); + + nsCOMPtr<nsIRDFResource> res; + rv = rdf->GetResource(uri, getter_AddRefs(res)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv)); + if (NS_FAILED(rv)) + return rv; + + folder.swap(*aFolder); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetHasSubFolders(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mSubFolders.Count() > 0; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetNumSubFolders(uint32_t *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mSubFolders.Count(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::AddFolderListener(nsIFolderListener * listener) +{ + return mListeners.AppendElement(listener) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgDBFolder::RemoveFolderListener(nsIFolderListener * listener) +{ + mListeners.RemoveElement(listener); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetParent(nsIMsgFolder *aParent) +{ + mParent = do_GetWeakReference(aParent); + if (aParent) + { + nsresult rv; + nsCOMPtr<nsIMsgFolder> parentMsgFolder = do_QueryInterface(aParent, &rv); + if (NS_SUCCEEDED(rv)) + { + // servers do not have parents, so we must not be a server + mIsServer = false; + mIsServerIsValid = true; + + // also set the server itself while we're here. + nsCOMPtr<nsIMsgIncomingServer> server; + rv = parentMsgFolder->GetServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + mServer = do_GetWeakReference(server); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetParent(nsIMsgFolder **aParent) +{ + NS_ENSURE_ARG_POINTER(aParent); + nsCOMPtr<nsIMsgFolder> parent = do_QueryReferent(mParent); + parent.swap(*aParent); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetMessages(nsISimpleEnumerator **result) +{ + // XXX should this return an empty enumeration? + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMsgDBFolder::UpdateFolder(nsIMsgWindow *) +{ + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsMsgDBFolder::GetFolderURL(nsACString& url) +{ + url.Assign(EmptyCString()); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetServer(nsIMsgIncomingServer ** aServer) +{ + NS_ENSURE_ARG_POINTER(aServer); + nsresult rv; + // short circut the server if we have it. + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv); + if (NS_FAILED(rv)) + { + // try again after parsing the URI + rv = parseURI(true); + server = do_QueryReferent(mServer); + } + server.swap(*aServer); + return *aServer ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +nsMsgDBFolder::parseURI(bool needServer) +{ + nsresult rv; + nsCOMPtr<nsIURL> url; + + url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = url->SetSpec(mURI); + NS_ENSURE_SUCCESS(rv, rv); + // empty path tells us it's a server. + if (!mIsServerIsValid) + { + nsAutoCString path; + rv = url->GetPath(path); + if (NS_SUCCEEDED(rv)) + mIsServer = path.EqualsLiteral("/"); + mIsServerIsValid = true; + } + + // grab the name off the leaf of the server + if (mName.IsEmpty()) + { + // mName: + // the name is the trailing directory in the path + nsAutoCString fileName; + nsAutoCString escapedFileName; + url->GetFileName(escapedFileName); + if (!escapedFileName.IsEmpty()) + { + // XXX conversion to unicode here? is fileName in UTF8? + // yes, let's say it is in utf8 + MsgUnescapeString(escapedFileName, 0, fileName); + NS_ASSERTION(MsgIsUTF8(fileName), "fileName is not in UTF-8"); + CopyUTF8toUTF16(fileName, mName); + } + } + + // grab the server by parsing the URI and looking it up + // in the account manager... + // But avoid this extra work by first asking the parent, if any + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv); + if (NS_FAILED(rv)) + { + // first try asking the parent instead of the URI + nsCOMPtr<nsIMsgFolder> parentMsgFolder; + GetParent(getter_AddRefs(parentMsgFolder)); + + if (parentMsgFolder) + rv = parentMsgFolder->GetServer(getter_AddRefs(server)); + + // no parent. do the extra work of asking + if (!server && needServer) + { + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString serverType; + GetIncomingServerType(serverType); + if (serverType.IsEmpty()) + { + NS_WARNING("can't determine folder's server type"); + return NS_ERROR_FAILURE; + } + + url->SetScheme(serverType); + rv = accountManager->FindServerByURI(url, false, + getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + } + mServer = do_GetWeakReference(server); + } /* !mServer */ + + // now try to find the local path for this folder + if (server) + { + nsAutoCString newPath; + nsAutoCString escapedUrlPath; + nsAutoCString urlPath; + url->GetFilePath(escapedUrlPath); + if (!escapedUrlPath.IsEmpty()) + { + MsgUnescapeString(escapedUrlPath, 0, urlPath); + + // transform the filepath from the URI, such as + // "/folder1/folder2/foldern" + // to + // "folder1.sbd/folder2.sbd/foldern" + // (remove leading / and add .sbd to first n-1 folders) + // to be appended onto the server's path + bool isNewsFolder = false; + nsAutoCString scheme; + if (NS_SUCCEEDED(url->GetScheme(scheme))) + { + isNewsFolder = scheme.EqualsLiteral("news") || + scheme.EqualsLiteral("snews") || + scheme.EqualsLiteral("nntp"); + } + NS_MsgCreatePathStringFromFolderURI(urlPath.get(), newPath, scheme, + isNewsFolder); + } + + // now append munged path onto server path + nsCOMPtr<nsIFile> serverPath; + rv = server->GetLocalPath(getter_AddRefs(serverPath)); + if (NS_FAILED(rv)) return rv; + + if (!mPath && serverPath) + { + if (!newPath.IsEmpty()) + { + // I hope this is temporary - Ultimately, + // NS_MsgCreatePathStringFromFolderURI will need to be fixed. +#if defined(XP_WIN) + MsgReplaceChar(newPath, '/', '\\'); +#endif + rv = serverPath->AppendRelativeNativePath(newPath); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to append to the serverPath"); + if (NS_FAILED(rv)) + { + mPath = nullptr; + return rv; + } + } + mPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mPath->InitWithFile(serverPath); + } + // URI is completely parsed when we've attempted to get the server + mHaveParsedURI=true; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetIsServer(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + // make sure we've parsed the URI + if (!mIsServerIsValid) + { + nsresult rv = parseURI(); + if (NS_FAILED(rv) || !mIsServerIsValid) + return NS_ERROR_FAILURE; + } + + *aResult = mIsServer; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetNoSelect(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetImapShared(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + return GetFlag(nsMsgFolderFlags::PersonalShared, aResult); +} + +NS_IMETHODIMP +nsMsgDBFolder::GetCanSubscribe(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + // by default, you can't subscribe. + // if otherwise, override it. + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetCanFileMessages(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + //varada - checking folder flag to see if it is the "Unsent Messages" + //and if so return FALSE + if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual)) + { + *aResult = false; + return NS_OK; + } + + bool isServer = false; + nsresult rv = GetIsServer(&isServer); + if (NS_FAILED(rv)) return rv; + + // by default, you can't file messages into servers, only to folders + // if otherwise, override it. + *aResult = !isServer; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetCanDeleteMessages(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetCanCreateSubfolders(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + //Checking folder flag to see if it is the "Unsent Messages" + //or a virtual folder, and if so return FALSE + if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual)) + { + *aResult = false; + return NS_OK; + } + + // by default, you can create subfolders on server and folders + // if otherwise, override it. + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetCanRename(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + bool isServer = false; + nsresult rv = GetIsServer(&isServer); + if (NS_FAILED(rv)) return rv; + // by default, you can't rename servers, only folders + // if otherwise, override it. + // + // check if the folder is a special folder + // (Trash, Drafts, Unsent Messages, Inbox, Sent, Templates, Junk, Archives) + // if it is, don't allow the user to rename it + // (which includes dnd moving it with in the same server) + // + // this errors on the side of caution. we'll return false a lot + // more often if we use flags, + // instead of checking if the folder really is being used as a + // special folder by looking at the "copies and folders" prefs on the + // identities. + *aResult = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetCanCompact(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + bool isServer = false; + nsresult rv = GetIsServer(&isServer); + NS_ENSURE_SUCCESS(rv,rv); + // servers cannot be compacted --> 4.x + // virtual search folders cannot be compacted + *aResult = !isServer && !(mFlags & nsMsgFolderFlags::Virtual); + // Check if the store supports compaction + if (*aResult) + { + nsCOMPtr<nsIMsgPluggableStore> msgStore; + GetMsgStore(getter_AddRefs(msgStore)); + if (msgStore) + msgStore->GetSupportsCompaction(aResult); + } + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDBFolder::GetPrettyName(nsAString& name) +{ + return GetName(name); +} + +NS_IMETHODIMP nsMsgDBFolder::SetPrettyName(const nsAString& name) +{ + nsresult rv; + + //Set pretty name only if special flag is set and if it the default folder name + if (mFlags & nsMsgFolderFlags::Inbox && name.LowerCaseEqualsLiteral("inbox")) + rv = SetName(nsDependentString(kLocalizedInboxName)); + else if (mFlags & nsMsgFolderFlags::SentMail && name.LowerCaseEqualsLiteral("sent")) + rv = SetName(nsDependentString(kLocalizedSentName)); + else if (mFlags & nsMsgFolderFlags::Drafts && name.LowerCaseEqualsLiteral("drafts")) + rv = SetName(nsDependentString(kLocalizedDraftsName)); + else if (mFlags & nsMsgFolderFlags::Templates && name.LowerCaseEqualsLiteral("templates")) + rv = SetName(nsDependentString(kLocalizedTemplatesName)); + else if (mFlags & nsMsgFolderFlags::Trash && name.LowerCaseEqualsLiteral("trash")) + rv = SetName(nsDependentString(kLocalizedTrashName)); + else if (mFlags & nsMsgFolderFlags::Queue && name.LowerCaseEqualsLiteral("unsent messages")) + rv = SetName(nsDependentString(kLocalizedUnsentName)); + else if (mFlags & nsMsgFolderFlags::Junk && name.LowerCaseEqualsLiteral("junk")) + rv = SetName(nsDependentString(kLocalizedJunkName)); + else if (mFlags & nsMsgFolderFlags::Archive && name.LowerCaseEqualsLiteral("archives")) + rv = SetName(nsDependentString(kLocalizedArchivesName)); + else + rv = SetName(name); + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::GetName(nsAString& name) +{ + nsresult rv; + if (!mHaveParsedURI && mName.IsEmpty()) + { + rv = parseURI(); + if (NS_FAILED(rv)) return rv; + } + + // if it's a server, just forward the call + if (mIsServer) + { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + return server->GetPrettyName(name); + } + + name = mName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetName(const nsAString& name) +{ + // override the URI-generated name + if (!mName.Equals(name)) + { + mName = name; + // old/new value doesn't matter here + NotifyUnicharPropertyChanged(kNameAtom, name, name); + } + return NS_OK; +} + +//For default, just return name +NS_IMETHODIMP nsMsgDBFolder::GetAbbreviatedName(nsAString& aAbbreviatedName) +{ + return GetName(aAbbreviatedName); +} + +NS_IMETHODIMP +nsMsgDBFolder::GetChildNamed(const nsAString& aName, nsIMsgFolder **aChild) +{ + NS_ENSURE_ARG_POINTER(aChild); + nsCOMPtr<nsISimpleEnumerator> dummy; + GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders + *aChild = nullptr; + int32_t count = mSubFolders.Count(); + + for (int32_t i = 0; i < count; i++) + { + nsString folderName; + nsresult rv = mSubFolders[i]->GetName(folderName); + // case-insensitive compare is probably LCD across OS filesystems + if (NS_SUCCEEDED(rv) && + folderName.Equals(aName, nsCaseInsensitiveStringComparator())) + { + NS_ADDREF(*aChild = mSubFolders[i]); + return NS_OK; + } + } + // don't return NS_OK if we didn't find the folder + // see http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c15 + // and http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c17 + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgDBFolder::GetChildWithURI(const nsACString& uri, bool deep, bool caseInsensitive, nsIMsgFolder ** child) +{ + NS_ENSURE_ARG_POINTER(child); + // will return nullptr if we can't find it + *child = nullptr; + nsCOMPtr<nsISimpleEnumerator> enumerator; + nsresult rv = GetSubFolders(getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) + return rv; + + bool hasMore; + while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIRDFResource> folderResource(do_QueryInterface(item)); + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item)); + if (folderResource && folder) + { + const char *folderURI; + rv = folderResource->GetValueConst(&folderURI); + if (NS_FAILED(rv)) return rv; + bool equal = folderURI && (caseInsensitive ? uri.Equals(folderURI, nsCaseInsensitiveCStringComparator()) + : uri.Equals(folderURI)); + if (equal) + { + *child = folder; + NS_ADDREF(*child); + return NS_OK; + } + if (deep) + { + rv = folder->GetChildWithURI(uri, deep, caseInsensitive, child); + if (NS_FAILED(rv)) + return rv; + + if (*child) + return NS_OK; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetPrettiestName(nsAString& name) +{ + if (NS_SUCCEEDED(GetPrettyName(name))) + return NS_OK; + return GetName(name); +} + + +NS_IMETHODIMP nsMsgDBFolder::GetShowDeletedMessages(bool *showDeletedMessages) +{ + NS_ENSURE_ARG_POINTER(showDeletedMessages); + *showDeletedMessages = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::Delete() +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::DeleteSubFolders(nsIArray *folders, + nsIMsgWindow *msgWindow) +{ + uint32_t count; + nsresult rv = folders->GetLength(&count); + for(uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(folders, i, &rv)); + if (folder) + PropagateDelete(folder, true, msgWindow); + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::CreateStorageIfMissing(nsIUrlListener* /* urlListener */) +{ + NS_ASSERTION(false, "needs to be overridden"); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::PropagateDelete(nsIMsgFolder *folder, bool deleteStorage, nsIMsgWindow *msgWindow) +{ + // first, find the folder we're looking to delete + nsresult rv = NS_OK; + + int32_t count = mSubFolders.Count(); + for (int32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> child(mSubFolders[i]); + if (folder == child.get()) + { + // Remove self as parent + child->SetParent(nullptr); + // maybe delete disk storage for it, and its subfolders + rv = child->RecursiveDelete(deleteStorage, msgWindow); + if (NS_SUCCEEDED(rv)) + { + // Remove from list of subfolders. + mSubFolders.RemoveObjectAt(i); + NotifyItemRemoved(child); + break; + } + else // setting parent back if we failed + child->SetParent(this); + } + else + rv = child->PropagateDelete(folder, deleteStorage, msgWindow); + } + + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::RecursiveDelete(bool deleteStorage, nsIMsgWindow *msgWindow) +{ + // If deleteStorage is true, recursively deletes disk storage for this folder + // and all its subfolders. + // Regardless of deleteStorage, always unlinks them from the children lists and + // frees memory for the subfolders but NOT for _this_ + + nsresult status = NS_OK; + nsCOMPtr <nsIFile> dbPath; + + // first remove the deleted folder from the folder cache; + nsresult result = GetFolderCacheKey(getter_AddRefs(dbPath)); + + nsCOMPtr<nsIMsgAccountManager> accountMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result); + if(NS_SUCCEEDED(result)) + { + nsCOMPtr <nsIMsgFolderCache> folderCache; + result = accountMgr->GetFolderCache(getter_AddRefs(folderCache)); + if (NS_SUCCEEDED(result) && folderCache) + { + nsCString persistentPath; + result = dbPath->GetPersistentDescriptor(persistentPath); + if (NS_SUCCEEDED(result)) + folderCache->RemoveElement(persistentPath); + } + } + + int32_t count = mSubFolders.Count(); + while (count > 0) + { + nsIMsgFolder *child = mSubFolders[0]; + + child->SetParent(nullptr); + status = child->RecursiveDelete(deleteStorage, msgWindow); // recur + if (NS_SUCCEEDED(status)) + // unlink it from this child's list + mSubFolders.RemoveObjectAt(0); + else + { + // setting parent back if we failed for some reason + child->SetParent(this); + break; + } + + count--; + } + + // now delete the disk storage for _this_ + if (deleteStorage && NS_SUCCEEDED(status)) + { + // All delete commands use deleteStorage = true, and local moves use false. + // IMAP moves use true, leaving this here in the hope that bug 439108 + // works out. + nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyFolderDeleted(this); + status = Delete(); + } + return status; +} + +NS_IMETHODIMP nsMsgDBFolder::CreateSubfolder(const nsAString& folderName, nsIMsgWindow *msgWindow) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::AddSubfolder(const nsAString& name, + nsIMsgFolder** child) +{ + NS_ENSURE_ARG_POINTER(child); + + int32_t flags = 0; + nsresult rv; + nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoCString uri(mURI); + uri.Append('/'); + + // URI should use UTF-8 + // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax) + nsAutoCString escapedName; + rv = NS_MsgEscapeEncodeURLPath(name, escapedName); + NS_ENSURE_SUCCESS(rv, rv); + + // fix for #192780 + // if this is the root folder + // make sure the the special folders + // have the right uri. + // on disk, host\INBOX should be a folder with the uri mailbox://user@host/Inbox" + // as mailbox://user@host/Inbox != mailbox://user@host/INBOX + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder && (rootFolder.get() == (nsIMsgFolder *)this)) + { + if (MsgLowerCaseEqualsLiteral(escapedName, "inbox")) + uri += "Inbox"; + else if (MsgLowerCaseEqualsLiteral(escapedName, "unsent%20messages")) + uri += "Unsent%20Messages"; + else if (MsgLowerCaseEqualsLiteral(escapedName, "drafts")) + uri += "Drafts"; + else if (MsgLowerCaseEqualsLiteral(escapedName, "trash")) + uri += "Trash"; + else if (MsgLowerCaseEqualsLiteral(escapedName, "sent")) + uri += "Sent"; + else if (MsgLowerCaseEqualsLiteral(escapedName, "templates")) + uri +="Templates"; + else if (MsgLowerCaseEqualsLiteral(escapedName, "archives")) + uri += "Archives"; + else + uri += escapedName.get(); + } + else + uri += escapedName.get(); + + nsCOMPtr <nsIMsgFolder> msgFolder; + rv = GetChildWithURI(uri, false/*deep*/, true /*case Insensitive*/, getter_AddRefs(msgFolder)); + if (NS_SUCCEEDED(rv) && msgFolder) + return NS_MSG_FOLDER_EXISTS; + + nsCOMPtr<nsIRDFResource> res; + rv = rdf->GetResource(uri, getter_AddRefs(res)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv)); + if (NS_FAILED(rv)) + return rv; + + folder->GetFlags((uint32_t *)&flags); + flags |= nsMsgFolderFlags::Mail; + folder->SetParent(this); + + bool isServer; + rv = GetIsServer(&isServer); + + //Only set these if these are top level children. + if(NS_SUCCEEDED(rv) && isServer) + { + if(name.LowerCaseEqualsLiteral("inbox")) + { + flags |= nsMsgFolderFlags::Inbox; + SetBiffState(nsIMsgFolder::nsMsgBiffState_Unknown); + } + else if (name.LowerCaseEqualsLiteral("trash")) + flags |= nsMsgFolderFlags::Trash; + else if (name.LowerCaseEqualsLiteral("unsent messages") || + name.LowerCaseEqualsLiteral("outbox")) + flags |= nsMsgFolderFlags::Queue; + } + + folder->SetFlags(flags); + + if (folder) + mSubFolders.AppendObject(folder); + + folder.swap(*child); + // at this point we must be ok and we don't want to return failure in case + // GetIsServer failed. + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::CompactAll(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow, bool aCompactOfflineAlso) +{ + NS_ASSERTION(false, "should be overridden by child class"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::EmptyTrash(nsIMsgWindow *msgWindow, nsIUrlListener *aListener) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsMsgDBFolder::CheckIfFolderExists(const nsAString& newFolderName, nsIMsgFolder *parentFolder, nsIMsgWindow *msgWindow) +{ + NS_ENSURE_ARG_POINTER(parentFolder); + nsCOMPtr<nsISimpleEnumerator> subFolders; + nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(subFolders->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> item; + rv = subFolders->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item)); + if (!msgFolder) + break; + + nsString folderName; + + msgFolder->GetName(folderName); + if (folderName.Equals(newFolderName, nsCaseInsensitiveStringComparator())) + { + ThrowAlertMsg("folderExists", msgWindow); + return NS_MSG_FOLDER_EXISTS; + } + } + return NS_OK; +} + +bool +nsMsgDBFolder::ConfirmAutoFolderRename(nsIMsgWindow *msgWindow, + const nsString& aOldName, + const nsString& aNewName) +{ + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + nsString folderName; + GetName(folderName); + const char16_t *formatStrings[] = + { + aOldName.get(), + folderName.get(), + aNewName.get() + }; + + nsString confirmString; + rv = bundle->FormatStringFromName(u"confirmDuplicateFolderRename", + formatStrings, 3, getter_Copies(confirmString)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + bool confirmed = false; + rv = ThrowConfirmationPrompt(msgWindow, confirmString, &confirmed); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + return confirmed; +} + +nsresult +nsMsgDBFolder::AddDirectorySeparator(nsIFile *path) +{ + nsAutoString leafName; + path->GetLeafName(leafName); + leafName.AppendLiteral(FOLDER_SUFFIX); + return path->SetLeafName(leafName); +} + +/* Finds the directory associated with this folder. That is if the path is + c:\Inbox, it will return c:\Inbox.sbd if it succeeds. If that path doesn't + currently exist then it will create it. Path is strictly an out parameter. + */ +nsresult nsMsgDBFolder::CreateDirectoryForFolder(nsIFile **resultFile) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIFile> path; + rv = GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) return rv; + + bool pathIsDirectory = false; + path->IsDirectory(&pathIsDirectory); + + bool isServer; + GetIsServer(&isServer); + + // Make sure this is REALLY the parent for subdirectories + if (pathIsDirectory && !isServer) + { + nsAutoString leafName; + path->GetLeafName(leafName); + nsAutoString ext; + int32_t idx = leafName.RFindChar('.'); + if (idx != -1) + ext = Substring(leafName, idx); + if (!ext.EqualsLiteral(FOLDER_SUFFIX)) + pathIsDirectory = false; + } + + if(!pathIsDirectory) + { + //If the current path isn't a directory, add directory separator + //and test it out. + rv = AddDirectorySeparator(path); + if(NS_FAILED(rv)) + return rv; + + //If that doesn't exist, then we have to create this directory + pathIsDirectory = false; + path->IsDirectory(&pathIsDirectory); + if(!pathIsDirectory) + { + bool pathExists; + path->Exists(&pathExists); + //If for some reason there's a file with the directory separator + //then we are going to fail. + rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY : path->Create(nsIFile::DIRECTORY_TYPE, 0700); + } + } + if (NS_SUCCEEDED(rv)) + path.swap(*resultFile); + return rv; +} + +/* Finds the backup directory associated with this folder, stored on the temp + drive. If that path doesn't currently exist then it will create it. Path is + strictly an out parameter. + */ +nsresult nsMsgDBFolder::CreateBackupDirectory(nsIFile **resultFile) +{ + nsCOMPtr<nsIFile> path; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = path->Append(NS_LITERAL_STRING("MozillaMailnews")); + bool pathIsDirectory; + path->IsDirectory(&pathIsDirectory); + + // If that doesn't exist, then we have to create this directory + if (!pathIsDirectory) + { + bool pathExists; + path->Exists(&pathExists); + // If for some reason there's a file with the directory separator + // then we are going to fail. + rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY : + path->Create(nsIFile::DIRECTORY_TYPE, 0700); + } + if (NS_SUCCEEDED(rv)) + path.swap(*resultFile); + return rv; +} + +nsresult nsMsgDBFolder::GetBackupSummaryFile(nsIFile **aBackupFile, const nsACString& newName) +{ + nsCOMPtr<nsIFile> backupDir; + nsresult rv = CreateBackupDirectory(getter_AddRefs(backupDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // We use a dummy message folder file so we can use + // GetSummaryFileLocation to get the db file name + nsCOMPtr<nsIFile> backupDBDummyFolder; + rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!newName.IsEmpty()) + { + rv = backupDBDummyFolder->AppendNative(newName); + } + else // if newName is null, use the folder name + { + nsCOMPtr<nsIFile> folderPath; + rv = GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString folderName; + rv = folderPath->GetNativeLeafName(folderName); + NS_ENSURE_SUCCESS(rv, rv); + rv = backupDBDummyFolder->AppendNative(folderName); + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> backupDBFile; + rv = GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile)); + NS_ENSURE_SUCCESS(rv, rv); + + backupDBFile.swap(*aBackupFile); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::Rename(const nsAString& aNewName, nsIMsgWindow *msgWindow) +{ + nsCOMPtr<nsIFile> oldPathFile; + nsCOMPtr<nsIAtom> folderRenameAtom; + nsresult rv = GetFilePath(getter_AddRefs(oldPathFile)); + if (NS_FAILED(rv)) + return rv; + nsCOMPtr<nsIMsgFolder> parentFolder; + rv = GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) + return NS_ERROR_FAILURE; + nsCOMPtr<nsISupports> parentSupport = do_QueryInterface(parentFolder); + nsCOMPtr<nsIFile> oldSummaryFile; + rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> dirFile; + int32_t count = mSubFolders.Count(); + + if (count > 0) + rv = CreateDirectoryForFolder(getter_AddRefs(dirFile)); + + nsAutoString newDiskName(aNewName); + NS_MsgHashIfNecessary(newDiskName); + + if (mName.Equals(aNewName, nsCaseInsensitiveStringComparator())) + { + rv = ThrowAlertMsg("folderExists", msgWindow); + return NS_MSG_FOLDER_EXISTS; + } + else + { + nsCOMPtr <nsIFile> parentPathFile; + parentFolder->GetFilePath(getter_AddRefs(parentPathFile)); + NS_ENSURE_SUCCESS(rv,rv); + bool isDirectory = false; + parentPathFile->IsDirectory(&isDirectory); + if (!isDirectory) + AddDirectorySeparator(parentPathFile); + + rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow); + if (NS_FAILED(rv)) + return rv; + } + + ForceDBClosed(); + + // Save of dir name before appending .msf + nsAutoString newNameDirStr(newDiskName); + + if (! (mFlags & nsMsgFolderFlags::Virtual)) + rv = oldPathFile->MoveTo(nullptr, newDiskName); + if (NS_SUCCEEDED(rv)) + { + newDiskName.AppendLiteral(SUMMARY_SUFFIX); + oldSummaryFile->MoveTo(nullptr, newDiskName); + } + else + { + ThrowAlertMsg("folderRenameFailed", msgWindow); + return rv; + } + + if (NS_SUCCEEDED(rv) && count > 0) + { + // rename "*.sbd" directory + newNameDirStr.AppendLiteral(".sbd"); + dirFile->MoveTo(nullptr, newNameDirStr); + } + + nsCOMPtr<nsIMsgFolder> newFolder; + if (parentSupport) + { + rv = parentFolder->AddSubfolder(aNewName, getter_AddRefs(newFolder)); + if (newFolder) + { + newFolder->SetPrettyName(EmptyString()); + newFolder->SetPrettyName(aNewName); + newFolder->SetFlags(mFlags); + bool changed = false; + MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &changed); + if (changed) + AlertFilterChanged(msgWindow); + + if (count > 0) + newFolder->RenameSubFolders(msgWindow, this); + + if (parentFolder) + { + SetParent(nullptr); + parentFolder->PropagateDelete(this, false, msgWindow); + parentFolder->NotifyItemAdded(newFolder); + } + folderRenameAtom = MsgGetAtom("RenameCompleted"); + newFolder->NotifyFolderEvent(folderRenameAtom); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::RenameSubFolders(nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::ContainsChildNamed(const nsAString& name, bool* containsChild) +{ + NS_ENSURE_ARG_POINTER(containsChild); + nsCOMPtr<nsIMsgFolder> child; + GetChildNamed(name, getter_AddRefs(child)); + *containsChild = child != nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::IsAncestorOf(nsIMsgFolder *child, bool *isAncestor) +{ + NS_ENSURE_ARG_POINTER(isAncestor); + nsresult rv = NS_OK; + + int32_t count = mSubFolders.Count(); + + for (int32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]); + if (folder.get() == child) + *isAncestor = true; + else + folder->IsAncestorOf(child, isAncestor); + + if (*isAncestor) + return NS_OK; + } + *isAncestor = false; + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::GenerateUniqueSubfolderName(const nsAString& prefix, + nsIMsgFolder *otherFolder, + nsAString& name) +{ + /* only try 256 times */ + for (int count = 0; count < 256; count++) + { + nsAutoString uniqueName; + uniqueName.Assign(prefix); + uniqueName.AppendInt(count); + bool containsChild; + bool otherContainsChild = false; + ContainsChildNamed(uniqueName, &containsChild); + if (otherFolder) + otherFolder->ContainsChildNamed(uniqueName, &otherContainsChild); + + if (!containsChild && !otherContainsChild) + { + name = uniqueName; + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::UpdateSummaryTotals(bool force) +{ + if (!mNotifyCountChanges) + return NS_OK; + + int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages; + int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages; + //We need to read this info from the database + nsresult rv = ReadDBFolderInfo(force); + + if (NS_SUCCEEDED(rv)) + { + int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages; + int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages; + + //Need to notify listeners that total count changed. + if(oldTotalMessages != newTotalMessages) + NotifyIntPropertyChanged(kTotalMessagesAtom, oldTotalMessages, newTotalMessages); + + if(oldUnreadMessages != newUnreadMessages) + NotifyIntPropertyChanged(kTotalUnreadMessagesAtom, oldUnreadMessages, newUnreadMessages); + + FlushToFolderCache(); + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::SummaryChanged() +{ + UpdateSummaryTotals(false); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetNumUnread(bool deep, int32_t *numUnread) +{ + NS_ENSURE_ARG_POINTER(numUnread); + + bool isServer = false; + nsresult rv = GetIsServer(&isServer); + NS_ENSURE_SUCCESS(rv, rv); + int32_t total = isServer ? 0 : mNumUnreadMessages + mNumPendingUnreadMessages; + + if (deep) + { + if (total < 0) // deep search never returns negative counts + total = 0; + int32_t count = mSubFolders.Count(); + for (int32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]); + int32_t num; + uint32_t folderFlags; + folder->GetFlags(&folderFlags); + if (!(folderFlags & nsMsgFolderFlags::Virtual)) + { + folder->GetNumUnread(deep, &num); + total += num; + } + } + } + *numUnread = total; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetTotalMessages(bool deep, int32_t *totalMessages) +{ + NS_ENSURE_ARG_POINTER(totalMessages); + + bool isServer = false; + nsresult rv = GetIsServer(&isServer); + NS_ENSURE_SUCCESS(rv, rv); + int32_t total = isServer ? 0 : mNumTotalMessages + mNumPendingTotalMessages; + + if (deep) + { + if (total < 0) // deep search never returns negative counts + total = 0; + int32_t count = mSubFolders.Count(); + for (int32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]); + int32_t num; + uint32_t folderFlags; + folder->GetFlags(&folderFlags); + if (!(folderFlags & nsMsgFolderFlags::Virtual)) + { + folder->GetTotalMessages(deep, &num); + total += num; + } + } + } + *totalMessages = total; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetNumPendingUnread(int32_t *aPendingUnread) +{ + *aPendingUnread = mNumPendingUnreadMessages; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetNumPendingTotalMessages(int32_t *aPendingTotal) +{ + *aPendingTotal = mNumPendingTotalMessages; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingUnread(int32_t delta) +{ + if (delta) + { + int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages; + mNumPendingUnreadMessages += delta; + int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages; + NS_ASSERTION(newUnreadMessages >= 0, "shouldn't have negative unread message count"); + if (newUnreadMessages >= 0) + { + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && folderInfo) + folderInfo->SetImapUnreadPendingMessages(mNumPendingUnreadMessages); + NotifyIntPropertyChanged(kTotalUnreadMessagesAtom, oldUnreadMessages, newUnreadMessages); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingTotalMessages(int32_t delta) +{ + if (delta) + { + int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages; + mNumPendingTotalMessages += delta; + int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages; + + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && folderInfo) + folderInfo->SetImapTotalPendingMessages(mNumPendingTotalMessages); + NotifyIntPropertyChanged(kTotalMessagesAtom, oldTotalMessages, newTotalMessages); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetFlag(uint32_t flag) +{ + // If calling this function causes us to open the db (i.e., it was not + // open before), we're going to close the db before returning. + bool dbWasOpen = mDatabase != nullptr; + + ReadDBFolderInfo(false); + // OnFlagChange can be expensive, so don't call it if we don't need to + bool flagSet; + nsresult rv; + + if (NS_FAILED(rv = GetFlag(flag, &flagSet))) + return rv; + + if (!flagSet) + { + mFlags |= flag; + OnFlagChange(flag); + } + if (!dbWasOpen && mDatabase) + SetMsgDatabase(nullptr); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::ClearFlag(uint32_t flag) +{ + // OnFlagChange can be expensive, so don't call it if we don't need to + bool flagSet; + nsresult rv; + + if (NS_FAILED(rv = GetFlag(flag, &flagSet))) + return rv; + + if (flagSet) + { + mFlags &= ~flag; + OnFlagChange (flag); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetFlag(uint32_t flag, bool *_retval) +{ + *_retval = ((mFlags & flag) != 0); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::ToggleFlag(uint32_t flag) +{ + mFlags ^= flag; + OnFlagChange (flag); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::OnFlagChange(uint32_t flag) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && folderInfo) + { +#ifdef DEBUG_bienvenu1 + nsString name; + rv = GetName(name); + NS_ASSERTION(Compare(name, kLocalizedTrashName) || (mFlags & nsMsgFolderFlags::Trash), "lost trash flag"); +#endif + folderInfo->SetFlags((int32_t) mFlags); + if (db) + db->Commit(nsMsgDBCommitType::kLargeCommit); + + if (mFlags & flag) + NotifyIntPropertyChanged(mFolderFlagAtom, mFlags & ~flag, mFlags); + else + NotifyIntPropertyChanged(mFolderFlagAtom, mFlags | flag, mFlags); + + if (flag & nsMsgFolderFlags::Offline) + { + bool newValue = mFlags & nsMsgFolderFlags::Offline; + rv = NotifyBoolPropertyChanged(kSynchronizeAtom, !newValue, !!newValue); + } + else if (flag & nsMsgFolderFlags::Elided) + { + bool newValue = mFlags & nsMsgFolderFlags::Elided; + rv = NotifyBoolPropertyChanged(kOpenAtom, !!newValue, !newValue); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::SetFlags(uint32_t aFlags) +{ + if (mFlags != aFlags) + { + uint32_t changedFlags = aFlags ^ mFlags; + mFlags = aFlags; + OnFlagChange(changedFlags); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetFolderWithFlags(uint32_t aFlags, nsIMsgFolder** aResult) +{ + if ((mFlags & aFlags) == aFlags) + { + NS_ADDREF(*aResult = this); + return NS_OK; + } + + nsCOMPtr<nsISimpleEnumerator> dummy; + GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders + + int32_t count = mSubFolders.Count(); + *aResult = nullptr; + for (int32_t i = 0; !*aResult && i < count; ++i) + mSubFolders[i]->GetFolderWithFlags(aFlags, aResult); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetFoldersWithFlags(uint32_t aFlags, nsIArray** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + ListFoldersWithFlags(aFlags, array); + NS_ADDREF(*aResult = array); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::ListFoldersWithFlags(uint32_t aFlags, nsIMutableArray* aFolders) +{ + NS_ENSURE_ARG_POINTER(aFolders); + if ((mFlags & aFlags) == aFlags) + aFolders->AppendElement(static_cast<nsRDFResource*>(this), false); + + nsCOMPtr<nsISimpleEnumerator> dummy; + GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders + + int32_t count = mSubFolders.Count(); + for (int32_t i = 0; i < count; ++i) + mSubFolders[i]->ListFoldersWithFlags(aFlags, aFolders); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::IsSpecialFolder(uint32_t aFlags, + bool aCheckAncestors, + bool *aIsSpecial) +{ + NS_ENSURE_ARG_POINTER(aIsSpecial); + + if ((mFlags & aFlags) == 0) + { + nsCOMPtr<nsIMsgFolder> parentMsgFolder; + GetParent(getter_AddRefs(parentMsgFolder)); + + if (parentMsgFolder && aCheckAncestors) + parentMsgFolder->IsSpecialFolder(aFlags, aCheckAncestors, aIsSpecial); + else + *aIsSpecial = false; + } + else + { + // The user can set their INBOX to be their SENT folder. + // in that case, we want this folder to act like an INBOX, + // and not a SENT folder + *aIsSpecial = !((aFlags & nsMsgFolderFlags::SentMail) && + (mFlags & nsMsgFolderFlags::Inbox)); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetDeletable(bool *deletable) +{ + NS_ENSURE_ARG_POINTER(deletable); + *deletable = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetDisplayRecipients(bool *displayRecipients) +{ + *displayRecipients = false; + if (mFlags & nsMsgFolderFlags::SentMail && !(mFlags & nsMsgFolderFlags::Inbox)) + *displayRecipients = true; + else if (mFlags & nsMsgFolderFlags::Queue) + *displayRecipients = true; + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDBFolder::AcquireSemaphore(nsISupports *semHolder) +{ + nsresult rv = NS_OK; + if (mSemaphoreHolder == NULL) + mSemaphoreHolder = semHolder; //Don't AddRef due to ownership issues. + else + rv = NS_MSG_FOLDER_BUSY; + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::ReleaseSemaphore(nsISupports *semHolder) +{ + if (!mSemaphoreHolder || mSemaphoreHolder == semHolder) + mSemaphoreHolder = NULL; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::TestSemaphore(nsISupports *semHolder, bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = (mSemaphoreHolder == semHolder); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetLocked(bool *isLocked) +{ + *isLocked = mSemaphoreHolder != NULL; + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDBFolder::GetRelativePathName(nsACString& pathName) +{ + pathName.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetSizeOnDisk(int64_t *size) +{ + NS_ENSURE_ARG_POINTER(size); + *size = kSizeUnknown; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetSizeOnDisk(int64_t aSizeOnDisk) +{ + NotifyIntPropertyChanged(kFolderSizeAtom, mFolderSize, aSizeOnDisk); + mFolderSize = aSizeOnDisk; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetUsername(nsACString& userName) +{ + nsresult rv; + nsCOMPtr <nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->GetUsername(userName); +} + +NS_IMETHODIMP nsMsgDBFolder::GetHostname(nsACString& hostName) +{ + nsresult rv; + nsCOMPtr<nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->GetHostName(hostName); +} + +NS_IMETHODIMP nsMsgDBFolder::GetNewMessages(nsIMsgWindow *, nsIUrlListener * /* aListener */) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::GetBiffState(uint32_t *aBiffState) +{ + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->GetBiffState(aBiffState); +} + +NS_IMETHODIMP nsMsgDBFolder::SetBiffState(uint32_t aBiffState) +{ + uint32_t oldBiffState = nsMsgBiffState_Unknown; + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + rv = server->GetBiffState(&oldBiffState); + + if (oldBiffState != aBiffState) + { + // Get the server and notify it and not inbox. + if (!mIsServer) + { + nsCOMPtr<nsIMsgFolder> folder; + rv = GetRootFolder(getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv) && folder) + return folder->SetBiffState(aBiffState); + } + if (server) + server->SetBiffState(aBiffState); + + NotifyIntPropertyChanged(kBiffStateAtom, oldBiffState, aBiffState); + } + else if (aBiffState == oldBiffState && aBiffState == nsMsgBiffState_NewMail) + { + // The folder has been updated, so update the MRUTime + SetMRUTime(); + // biff is already set, but notify that there is additional new mail for the folder + NotifyIntPropertyChanged(kNewMailReceivedAtom, 0, mNumNewBiffMessages); + } + else if (aBiffState == nsMsgBiffState_NoMail) + { + // even if the old biff state equals the new biff state, it is still possible that we've never + // cleared the number of new messages for this particular folder. This happens when the new mail state + // got cleared by viewing a new message in folder that is different from this one. Biff state is stored per server + // the num. of new messages is per folder. + SetNumNewMessages(0); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetNumNewMessages(bool deep, int32_t *aNumNewMessages) +{ + NS_ENSURE_ARG_POINTER(aNumNewMessages); + + int32_t numNewMessages = (!deep || ! (mFlags & nsMsgFolderFlags::Virtual)) + ? mNumNewBiffMessages : 0; + if (deep) + { + int32_t count = mSubFolders.Count(); + for (int32_t i = 0; i < count; i++) + { + int32_t num; + mSubFolders[i]->GetNumNewMessages(deep, &num); + if (num > 0) // it's legal for counts to be negative if we don't know + numNewMessages += num; + } + } + *aNumNewMessages = numNewMessages; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetNumNewMessages(int32_t aNumNewMessages) +{ + if (aNumNewMessages != mNumNewBiffMessages) + { + int32_t oldNumMessages = mNumNewBiffMessages; + mNumNewBiffMessages = aNumNewMessages; + + nsAutoCString oldNumMessagesStr; + oldNumMessagesStr.AppendInt(oldNumMessages); + nsAutoCString newNumMessagesStr; + newNumMessagesStr.AppendInt(aNumNewMessages); + NotifyPropertyChanged(kNumNewBiffMessagesAtom, oldNumMessagesStr, newNumMessagesStr); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetRootFolder(nsIMsgFolder * *aRootFolder) +{ + NS_ENSURE_ARG_POINTER(aRootFolder); + nsresult rv; + nsCOMPtr<nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->GetRootMsgFolder(aRootFolder); +} + +NS_IMETHODIMP +nsMsgDBFolder::SetFilePath(nsIFile *aFile) +{ + mPath = aFile; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetFilePath(nsIFile * *aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + nsresult rv; + // make a new nsIFile object in case the caller + // alters the underlying file object. + nsCOMPtr <nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (!mPath) + parseURI(true); + rv = file->InitWithFile(mPath); + file.swap(*aFile); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetSummaryFile(nsIFile **aSummaryFile) +{ + NS_ENSURE_ARG_POINTER(aSummaryFile); + + nsresult rv; + nsCOMPtr <nsIFile> newSummaryLocation = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> pathFile; + rv = GetFilePath(getter_AddRefs(pathFile)); + NS_ENSURE_SUCCESS(rv, rv); + + newSummaryLocation->InitWithFile(pathFile); + + nsString fileName; + rv = newSummaryLocation->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + fileName.Append(NS_LITERAL_STRING(SUMMARY_SUFFIX)); + rv = newSummaryLocation->SetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + newSummaryLocation.forget(aSummaryFile); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::MarkMessagesRead(nsIArray *messages, bool markRead) +{ + uint32_t count; + nsresult rv; + + rv = messages->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + for(uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(messages, i, &rv); + if (message) + rv = message->MarkRead(markRead); + if (NS_FAILED(rv)) + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::MarkMessagesFlagged(nsIArray *messages, bool markFlagged) +{ + uint32_t count; + nsresult rv; + + rv = messages->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + for(uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(messages, i, &rv); + if (message) + rv = message->MarkFlagged(markFlagged); + if (NS_FAILED(rv)) + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::SetLabelForMessages(nsIArray *aMessages, nsMsgLabelValue aLabel) +{ + NS_ENSURE_ARG(aMessages); + GetDatabase(); + if (mDatabase) + { + uint32_t count; + nsresult rv = aMessages->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + for(uint32_t i = 0; i < count; i++) + { + nsMsgKey msgKey; + nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + (void) message->GetMessageKey(&msgKey); + rv = mDatabase->SetLabel(msgKey, aLabel); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::SetJunkScoreForMessages(nsIArray *aMessages, const nsACString& junkScore) +{ + NS_ENSURE_ARG(aMessages); + nsresult rv = NS_OK; + GetDatabase(); + if (mDatabase) + { + uint32_t count; + nsresult rv = aMessages->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + for(uint32_t i = 0; i < count; i++) + { + nsMsgKey msgKey; + nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + (void) message->GetMessageKey(&msgKey); + mDatabase->SetStringProperty(msgKey, "junkscore", nsCString(junkScore).get()); + mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter"); + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::ApplyRetentionSettings() +{ + return ApplyRetentionSettings(true); +} + +nsresult nsMsgDBFolder::ApplyRetentionSettings(bool deleteViaFolder) +{ + if (mFlags & nsMsgFolderFlags::Virtual) // ignore virtual folders. + return NS_OK; + bool weOpenedDB = !mDatabase; + nsCOMPtr<nsIMsgRetentionSettings> retentionSettings; + nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings)); + if (NS_SUCCEEDED(rv)) + { + nsMsgRetainByPreference retainByPreference = + nsIMsgRetentionSettings::nsMsgRetainAll; + + retentionSettings->GetRetainByPreference(&retainByPreference); + if (retainByPreference != nsIMsgRetentionSettings::nsMsgRetainAll) + { + rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + if (mDatabase) + rv = mDatabase->ApplyRetentionSettings(retentionSettings, deleteViaFolder); + } + } + // we don't want applying retention settings to keep the db open, because + // if we try to purge a bunch of folders, that will leave the dbs all open. + // So if we opened the db, close it. + if (weOpenedDB) + CloseDBIfFolderNotOpen(); + return rv; +} + +NS_IMETHODIMP +nsMsgDBFolder::DeleteMessages(nsIArray *messages, + nsIMsgWindow *msgWindow, + bool deleteStorage, + bool isMove, + nsIMsgCopyServiceListener *listener, + bool allowUndo) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgDBFolder::CopyMessages(nsIMsgFolder* srcFolder, + nsIArray *messages, + bool isMove, + nsIMsgWindow *window, + nsIMsgCopyServiceListener* listener, + bool isFolder, + bool allowUndo) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgDBFolder::CopyFolder(nsIMsgFolder* srcFolder, + bool isMoveFolder, + nsIMsgWindow *window, + nsIMsgCopyServiceListener* listener) +{ + NS_ASSERTION(false, "should be overridden by child class"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgDBFolder::CopyFileMessage(nsIFile* aFile, + nsIMsgDBHdr* messageToReplace, + bool isDraftOrTemplate, + uint32_t aNewMsgFlags, + const nsACString &aNewMsgKeywords, + nsIMsgWindow *window, + nsIMsgCopyServiceListener* listener) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::CopyDataToOutputStreamForAppend(nsIInputStream *aInStream, + int32_t aLength, nsIOutputStream *aOutputStream) +{ + if (!aInStream) + return NS_OK; + + uint32_t uiWritten; + return aOutputStream->WriteFrom(aInStream, aLength, &uiWritten); +} + +NS_IMETHODIMP nsMsgDBFolder::CopyDataDone() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::NotifyPropertyChanged(nsIAtom *aProperty, + const nsACString& aOldValue, + const nsACString& aNewValue) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener, + OnItemPropertyChanged, + (this, aProperty, + nsCString(aOldValue).get(), + nsCString(aNewValue).get())); + + // Notify listeners who listen to every folder + nsresult rv; + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderListenerManager->OnItemPropertyChanged(this, aProperty, + nsCString(aOldValue).get(), + nsCString(aNewValue).get()); +} + +NS_IMETHODIMP +nsMsgDBFolder::NotifyUnicharPropertyChanged(nsIAtom *aProperty, + const nsAString& aOldValue, + const nsAString& aNewValue) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener, + OnItemUnicharPropertyChanged, + (this, aProperty, + nsString(aOldValue).get(), + nsString(aNewValue).get())); + + // Notify listeners who listen to every folder + nsresult rv; + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderListenerManager->OnItemUnicharPropertyChanged(this, + aProperty, + nsString(aOldValue).get(), + nsString(aNewValue).get()); +} + +NS_IMETHODIMP +nsMsgDBFolder::NotifyIntPropertyChanged(nsIAtom *aProperty, int64_t aOldValue, + int64_t aNewValue) +{ + // Don't send off count notifications if they are turned off. + if (!mNotifyCountChanges && + ((aProperty == kTotalMessagesAtom) || + (aProperty == kTotalUnreadMessagesAtom))) + return NS_OK; + + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener, + OnItemIntPropertyChanged, + (this, aProperty, aOldValue, aNewValue)); + + // Notify listeners who listen to every folder + nsresult rv; + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderListenerManager->OnItemIntPropertyChanged(this, aProperty, + aOldValue, aNewValue); +} + +NS_IMETHODIMP +nsMsgDBFolder::NotifyBoolPropertyChanged(nsIAtom* aProperty, + bool aOldValue, bool aNewValue) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener, + OnItemBoolPropertyChanged, + (this, aProperty, aOldValue, aNewValue)); + + // Notify listeners who listen to every folder + nsresult rv; + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderListenerManager->OnItemBoolPropertyChanged(this, aProperty, + aOldValue, aNewValue); +} + +NS_IMETHODIMP +nsMsgDBFolder::NotifyPropertyFlagChanged(nsIMsgDBHdr *aItem, nsIAtom *aProperty, + uint32_t aOldValue, uint32_t aNewValue) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener, + OnItemPropertyFlagChanged, + (aItem, aProperty, aOldValue, aNewValue)); + + // Notify listeners who listen to every folder + nsresult rv; + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderListenerManager->OnItemPropertyFlagChanged(aItem, aProperty, + aOldValue, aNewValue); +} + +NS_IMETHODIMP nsMsgDBFolder::NotifyItemAdded(nsISupports *aItem) +{ + static bool notify = true; + + if (!notify) + return NS_OK; + + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener, + OnItemAdded, + (this, aItem)); + + // Notify listeners who listen to every folder + nsresult rv; + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderListenerManager->OnItemAdded(this, aItem); +} + +nsresult nsMsgDBFolder::NotifyItemRemoved(nsISupports *aItem) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener, + OnItemRemoved, + (this, aItem)); + + // Notify listeners who listen to every folder + nsresult rv; + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderListenerManager->OnItemRemoved(this, aItem); +} + +nsresult nsMsgDBFolder::NotifyFolderEvent(nsIAtom* aEvent) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener, + OnItemEvent, + (this, aEvent)); + + //Notify listeners who listen to every folder + nsresult rv; + nsCOMPtr<nsIFolderListener> folderListenerManager = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderListenerManager->OnItemEvent(this, aEvent); +} + +NS_IMETHODIMP +nsMsgDBFolder::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult) +{ + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->GetFilterList(aMsgWindow, aResult); +} + +NS_IMETHODIMP +nsMsgDBFolder::SetFilterList(nsIMsgFilterList *aFilterList) +{ + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->SetFilterList(aFilterList); +} + +NS_IMETHODIMP +nsMsgDBFolder::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->GetEditableFilterList(aMsgWindow, aResult); +} + +NS_IMETHODIMP +nsMsgDBFolder::SetEditableFilterList(nsIMsgFilterList *aFilterList) +{ + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->SetEditableFilterList(aFilterList); +} + +/* void enableNotifications (in long notificationType, in boolean enable); */ +NS_IMETHODIMP nsMsgDBFolder::EnableNotifications(int32_t notificationType, bool enable, bool dbBatching) +{ + if (notificationType == nsIMsgFolder::allMessageCountNotifications) + { + mNotifyCountChanges = enable; + // start and stop db batching here. This is under the theory + // that any time we want to enable and disable notifications, + // we're probably doing something that should be batched. + nsCOMPtr <nsIMsgDatabase> database; + + if (dbBatching) //only if we do dbBatching we need to get db + GetMsgDatabase(getter_AddRefs(database)); + + if (enable) + { + if (database) + database->EndBatch(); + UpdateSummaryTotals(true); + } + else if (database) + return database->StartBatch(); + return NS_OK; + } + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::GetMessageHeader(nsMsgKey msgKey, nsIMsgDBHdr **aMsgHdr) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + nsCOMPtr <nsIMsgDatabase> database; + nsresult rv = GetMsgDatabase(getter_AddRefs(database)); + NS_ENSURE_SUCCESS(rv, rv); + return (database) ? database->GetMsgHdrForKey(msgKey, aMsgHdr) : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgDBFolder::GetDescendants(nsIArray** aDescendants) +{ + NS_ENSURE_ARG_POINTER(aDescendants); + + nsresult rv; + nsCOMPtr<nsIMutableArray> allFolders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ListDescendants(allFolders); + allFolders.forget(aDescendants); + return NS_OK; +} + +// this gets the deep sub-folders too, e.g., the children of the children +NS_IMETHODIMP nsMsgDBFolder::ListDescendants(nsIMutableArray *aDescendants) +{ + NS_ENSURE_ARG_POINTER(aDescendants); + + nsCOMPtr<nsISimpleEnumerator> dummy; + GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders + uint32_t count = mSubFolders.Count(); + for (uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> child(mSubFolders[i]); + aDescendants->AppendElement(child, false); + child->ListDescendants(aDescendants); // recurse + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetBaseMessageURI(nsACString& baseMessageURI) +{ + if (mBaseMessageURI.IsEmpty()) + return NS_ERROR_FAILURE; + baseMessageURI = mBaseMessageURI; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetUriForMsg(nsIMsgDBHdr *msgHdr, nsACString& aURI) +{ + NS_ENSURE_ARG(msgHdr); + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + nsAutoCString uri; + uri.Assign(mBaseMessageURI); + + // append a "#" followed by the message key. + uri.Append('#'); + uri.AppendInt(msgKey); + aURI = uri; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GenerateMessageURI(nsMsgKey msgKey, nsACString& aURI) +{ + nsCString uri; + nsresult rv = GetBaseMessageURI( uri); + NS_ENSURE_SUCCESS(rv,rv); + + // append a "#" followed by the message key. + uri.Append('#'); + uri.AppendInt(msgKey); + aURI = uri; + return NS_OK; +} + +nsresult +nsMsgDBFolder::GetBaseStringBundle(nsIStringBundle **aBundle) +{ + NS_ENSURE_ARG_POINTER(aBundle); + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + bundle.swap(*aBundle); + return NS_OK; +} + +nsresult //Do not use this routine if you have to call it very often because it creates a new bundle each time +nsMsgDBFolder::GetStringFromBundle(const char *msgName, nsString& aResult) +{ + nsresult rv; + nsCOMPtr <nsIStringBundle> bundle; + rv = GetBaseStringBundle(getter_AddRefs(bundle)); + if (NS_SUCCEEDED(rv) && bundle) + rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(msgName).get(), getter_Copies(aResult)); + return rv; +} + +nsresult +nsMsgDBFolder::ThrowConfirmationPrompt(nsIMsgWindow *msgWindow, const nsAString& confirmString, bool *confirmed) +{ + if (msgWindow) + { + nsCOMPtr <nsIDocShell> docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) + { + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell)); + if (dialog && !confirmString.IsEmpty()) + dialog->Confirm(nullptr, nsString(confirmString).get(), confirmed); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBFolder::GetStringWithFolderNameFromBundle(const char * msgName, nsAString& aResult) +{ + nsCOMPtr <nsIStringBundle> bundle; + nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle)); + if (NS_SUCCEEDED(rv) && bundle) + { + nsString folderName; + GetName(folderName); + const char16_t *formatStrings[] = + { + folderName.get(), + kLocalizedBrandShortName + }; + + nsString resultStr; + rv = bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(msgName).get(), + formatStrings, 2, getter_Copies(resultStr)); + if (NS_SUCCEEDED(rv)) + aResult.Assign(resultStr); + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::ConfirmFolderDeletionForFilter(nsIMsgWindow *msgWindow, bool *confirmed) +{ + nsString confirmString; + nsresult rv = GetStringWithFolderNameFromBundle("confirmFolderDeletionForFilter", confirmString); + NS_ENSURE_SUCCESS(rv, rv); + return ThrowConfirmationPrompt(msgWindow, confirmString, confirmed); +} + +NS_IMETHODIMP nsMsgDBFolder::ThrowAlertMsg(const char * msgName, nsIMsgWindow *msgWindow) +{ + nsString alertString; + nsresult rv = GetStringWithFolderNameFromBundle(msgName, alertString); + if (NS_SUCCEEDED(rv) && !alertString.IsEmpty() && msgWindow) + { + nsCOMPtr<nsIPrompt> dialog; + msgWindow->GetPromptDialog(getter_AddRefs(dialog)); + if (dialog) + dialog->Alert(nullptr, alertString.get()); + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::AlertFilterChanged(nsIMsgWindow *msgWindow) +{ + NS_ENSURE_ARG(msgWindow); + nsresult rv = NS_OK; + bool checkBox=false; + GetWarnFilterChanged(&checkBox); + if (!checkBox) + { + nsCOMPtr <nsIDocShell> docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + nsString alertString; + rv = GetStringFromBundle("alertFilterChanged", alertString); + nsString alertCheckbox; + rv = GetStringFromBundle("alertFilterCheckbox", alertCheckbox); + if (!alertString.IsEmpty() && !alertCheckbox.IsEmpty() && docShell) + { + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell)); + if (dialog) + { + dialog->AlertCheck(nullptr, alertString.get(), alertCheckbox.get(), &checkBox); + SetWarnFilterChanged(checkBox); + } + } + } + return rv; +} + +nsresult +nsMsgDBFolder::GetWarnFilterChanged(bool *aVal) +{ + NS_ENSURE_ARG(aVal); + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = prefBranch->GetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal); + if (NS_FAILED(rv)) + *aVal = false; + return NS_OK; +} + +nsresult +nsMsgDBFolder::SetWarnFilterChanged(bool aVal) +{ + nsresult rv=NS_OK; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return prefBranch->SetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal); +} + +NS_IMETHODIMP nsMsgDBFolder::NotifyCompactCompleted() +{ + NS_ASSERTION(false, "should be overridden by child class"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsMsgDBFolder::CloseDBIfFolderNotOpen() +{ + nsresult rv; + nsCOMPtr<nsIMsgMailSession> session = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + bool folderOpen; + session->IsFolderOpenInWindow(this, &folderOpen); + if (!folderOpen && ! (mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox))) + SetMsgDatabase(nullptr); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetSortOrder(int32_t order) +{ + NS_ASSERTION(false, "not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::GetSortOrder(int32_t *order) +{ + NS_ENSURE_ARG_POINTER(order); + + uint32_t flags; + nsresult rv = GetFlags(&flags); + NS_ENSURE_SUCCESS(rv,rv); + + if (flags & nsMsgFolderFlags::Inbox) + *order = 0; + else if (flags & nsMsgFolderFlags::Drafts) + *order = 1; + else if (flags & nsMsgFolderFlags::Templates) + *order = 2; + else if (flags & nsMsgFolderFlags::SentMail) + *order = 3; + else if (flags & nsMsgFolderFlags::Archive) + *order = 4; + else if (flags & nsMsgFolderFlags::Junk) + *order = 5; + else if (flags & nsMsgFolderFlags::Trash) + *order = 6; + else if (flags & nsMsgFolderFlags::Virtual) + *order = 7; + else if (flags & nsMsgFolderFlags::Queue) + *order = 8; + else + *order = 9; + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetSortKey(uint32_t *aLength, uint8_t **aKey) +{ + NS_ENSURE_ARG(aKey); + int32_t order; + nsresult rv = GetSortOrder(&order); + NS_ENSURE_SUCCESS(rv,rv); + nsAutoString orderString; + orderString.AppendInt(order); + nsString folderName; + rv = GetName(folderName); + NS_ENSURE_SUCCESS(rv,rv); + orderString.Append(folderName); + return CreateCollationKey(orderString, aKey, aLength); +} + +nsresult +nsMsgDBFolder::CreateCollationKey(const nsString &aSource, uint8_t **aKey, uint32_t *aLength) +{ + NS_ENSURE_TRUE(gCollationKeyGenerator, NS_ERROR_NULL_POINTER); + return gCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, aSource, + aKey, aLength); +} + +NS_IMETHODIMP nsMsgDBFolder::CompareSortKeys(nsIMsgFolder *aFolder, int32_t *sortOrder) +{ + uint8_t *sortKey1=nullptr; + uint8_t *sortKey2=nullptr; + uint32_t sortKey1Length; + uint32_t sortKey2Length; + nsresult rv = GetSortKey(&sortKey1Length, &sortKey1); + NS_ENSURE_SUCCESS(rv,rv); + aFolder->GetSortKey(&sortKey2Length, &sortKey2); + NS_ENSURE_SUCCESS(rv,rv); + + rv = gCollationKeyGenerator->CompareRawSortKey(sortKey1, sortKey1Length, sortKey2, sortKey2Length, sortOrder); + PR_Free(sortKey1); + PR_Free(sortKey2); + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::GetInVFEditSearchScope (bool *aInVFEditSearchScope) +{ + *aInVFEditSearchScope = mInVFEditSearchScope; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::SetInVFEditSearchScope (bool aInVFEditSearchScope, bool aSetOnSubFolders) +{ + bool oldInVFEditSearchScope = mInVFEditSearchScope; + mInVFEditSearchScope = aInVFEditSearchScope; + NotifyBoolPropertyChanged(kInVFEditSearchScopeAtom, oldInVFEditSearchScope, mInVFEditSearchScope); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys, + bool aLocalOnly, nsIUrlListener *aUrlListener, + bool *aAsyncResults) +{ + NS_ENSURE_ARG_POINTER(aKeysToFetch); + NS_ENSURE_ARG_POINTER(aAsyncResults); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBFolder::GetMsgTextFromStream(nsIInputStream *stream, const nsACString &aCharset, + uint32_t bytesToRead, uint32_t aMaxOutputLen, + bool aCompressQuotes, bool aStripHTMLTags, + nsACString &aContentType, nsACString &aMsgText) +{ + /* + 1. non mime message - the message body starts after the blank line following the headers. + 2. mime message, multipart/alternative - we could simply scan for the boundary line, + advance past its headers, and treat the next few lines as the text. + 3. mime message, text/plain - body follows headers + 4. multipart/mixed - scan past boundary, treat next part as body. + */ + + nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>); + NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY); + + nsAutoCString msgText; + nsAutoString contentType; + nsAutoString encoding; + nsAutoCString curLine; + nsAutoCString charset(aCharset); + + // might want to use a state var instead of bools. + bool msgBodyIsHtml = false; + bool more = true; + bool reachedEndBody = false; + bool isBase64 = false; + bool inMsgBody = false; + bool justPassedEndBoundary = false; + + uint32_t bytesRead = 0; + + nsresult rv; + + // Both are used to extract data from the headers + nsCOMPtr<nsIMimeHeaders> mimeHeaders(do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParam(do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Stack of boundaries, used to figure out where we are + nsTArray<nsCString> boundaryStack; + + while (!inMsgBody && bytesRead <= bytesToRead) + { + nsAutoCString msgHeaders; + // We want to NS_ReadLine until we get to a blank line (the end of the headers) + while (more) + { + rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more); + NS_ENSURE_SUCCESS(rv, rv); + if (curLine.IsEmpty()) + break; + msgHeaders.Append(curLine); + msgHeaders.Append(NS_LITERAL_CSTRING("\r\n")); + bytesRead += curLine.Length(); + if (bytesRead > bytesToRead) + break; + } + + // There's no point in processing if we can't get the body + if (bytesRead > bytesToRead) + break; + + // Process the headers, looking for things we need + rv = mimeHeaders->Initialize(msgHeaders); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString contentTypeHdr; + mimeHeaders->ExtractHeader("Content-Type", false, contentTypeHdr); + + // Get the content type + // If we don't have a content type, then we assign text/plain + // this is in violation of the RFC for multipart/digest, though + // Also, if we've just passed an end boundary, we're going to ignore this. + if (!justPassedEndBoundary && contentTypeHdr.IsEmpty()) + contentType.Assign(NS_LITERAL_STRING("text/plain")); + else + mimeHdrParam->GetParameter(contentTypeHdr, nullptr, EmptyCString(), false, nullptr, contentType); + + justPassedEndBoundary = false; + + // If we are multipart, then we need to get the boundary + if (StringBeginsWith(contentType, NS_LITERAL_STRING("multipart/"), nsCaseInsensitiveStringComparator())) + { + nsAutoString boundaryParam; + mimeHdrParam->GetParameter(contentTypeHdr, "boundary", EmptyCString(), false, nullptr, boundaryParam); + if (!boundaryParam.IsEmpty()) + { + nsAutoCString boundary(NS_LITERAL_CSTRING("--")); + boundary.Append(NS_ConvertUTF16toUTF8(boundaryParam)); + boundaryStack.AppendElement(boundary); + } + } + + // If we are message/rfc822, then there's another header block coming up + else if (contentType.LowerCaseEqualsLiteral("message/rfc822")) + continue; + + // If we are a text part, then we want it + else if (StringBeginsWith(contentType, NS_LITERAL_STRING("text/"), nsCaseInsensitiveStringComparator())) + { + inMsgBody = true; + + if (contentType.LowerCaseEqualsLiteral("text/html")) + msgBodyIsHtml = true; + + // Also get the charset if required + if (charset.IsEmpty()) + { + nsAutoString charsetW; + mimeHdrParam->GetParameter(contentTypeHdr, "charset", EmptyCString(), false, nullptr, charsetW); + charset.Assign(NS_ConvertUTF16toUTF8(charsetW)); + } + + // Finally, get the encoding + nsAutoCString encodingHdr; + mimeHeaders->ExtractHeader("Content-Transfer-Encoding", false, encodingHdr); + if (!encodingHdr.IsEmpty()) + mimeHdrParam->GetParameter(encodingHdr, nullptr, EmptyCString(), false, nullptr, encoding); + + if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + isBase64 = true; + } + + // We need to consume the rest, until the next headers + uint32_t count = boundaryStack.Length(); + nsAutoCString boundary; + nsAutoCString endBoundary; + if (count) + { + boundary.Assign(boundaryStack.ElementAt(count - 1)); + endBoundary.Assign(boundary); + endBoundary.Append(NS_LITERAL_CSTRING("--")); + } + while (more) + { + rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more); + NS_ENSURE_SUCCESS(rv, rv); + + if (count) + { + // If we've reached a MIME final delimiter, pop and break + if (StringBeginsWith(curLine, endBoundary)) + { + if (inMsgBody) + reachedEndBody = true; + boundaryStack.RemoveElementAt(count - 1); + justPassedEndBoundary = true; + break; + } + // If we've reached the end of this MIME part, we can break + if (StringBeginsWith(curLine, boundary)) + { + if (inMsgBody) + reachedEndBody = true; + break; + } + } + + // Only append the text if we're actually in the message body + if (inMsgBody) + { + msgText.Append(curLine); + if (!isBase64) + msgText.Append(NS_LITERAL_CSTRING("\r\n")); + } + + bytesRead += curLine.Length(); + if (bytesRead > bytesToRead) + break; + } + } + lineBuffer = nullptr; + + // if the snippet is encoded, decode it + if (!encoding.IsEmpty()) + decodeMsgSnippet(NS_ConvertUTF16toUTF8(encoding), !reachedEndBody, msgText); + + // In order to turn our snippet into unicode, we need to convert it from the charset we + // detected earlier. + nsString unicodeMsgBodyStr; + ConvertToUnicode(charset.get(), msgText, unicodeMsgBodyStr); + + // now we've got a msg body. If it's html, convert it to plain text. + if (msgBodyIsHtml && aStripHTMLTags) + ConvertMsgSnippetToPlainText(unicodeMsgBodyStr, unicodeMsgBodyStr); + + // We want to remove any whitespace from the beginning and end of the string + unicodeMsgBodyStr.Trim(" \t\r\n", true, true); + + // step 3, optionally remove quoted text from the snippet + nsString compressedQuotesMsgStr; + if (aCompressQuotes) + compressQuotesInMsgSnippet(unicodeMsgBodyStr, compressedQuotesMsgStr); + + // now convert back to utf-8 which is more convenient for storage + CopyUTF16toUTF8(aCompressQuotes ? compressedQuotesMsgStr : unicodeMsgBodyStr, aMsgText); + + // finally, truncate the string based on aMaxOutputLen + if (aMsgText.Length() > aMaxOutputLen) { + if (NS_IsAscii(aMsgText.BeginReading())) + aMsgText.SetLength(aMaxOutputLen); + else + nsMsgI18NShrinkUTF8Str(nsCString(aMsgText), + aMaxOutputLen, aMsgText); + } + + // Also assign the content type being returned + aContentType.Assign(NS_ConvertUTF16toUTF8(contentType)); + return rv; +} + +/** + * decodeMsgSnippet - helper function which applies the appropriate transfer decoding + * to the message snippet based on aEncodingType. Currently handles + * base64 and quoted-printable. If aEncodingType refers to an encoding we don't + * handle, the message data is passed back unmodified. + * @param aEncodingType the encoding type (base64, quoted-printable) + * @param aIsComplete the snippet is actually the entire message so the decoder + * doesn't have to worry about partial data + * @param aMsgSnippet in/out argument. The encoded msg snippet and then the decoded snippet + */ +void nsMsgDBFolder::decodeMsgSnippet(const nsACString& aEncodingType, bool aIsComplete, nsCString& aMsgSnippet) +{ + if (MsgLowerCaseEqualsLiteral(aEncodingType, ENCODING_BASE64)) + { + int32_t base64Len = aMsgSnippet.Length(); + if (aIsComplete) + base64Len -= base64Len % 4; + char *decodedBody = PL_Base64Decode(aMsgSnippet.get(), base64Len, nullptr); + if (decodedBody) + aMsgSnippet.Adopt(decodedBody); + } + else if (MsgLowerCaseEqualsLiteral(aEncodingType, ENCODING_QUOTED_PRINTABLE)) + { + // giant hack - decode in place, and truncate string. + MsgStripQuotedPrintable((unsigned char *) aMsgSnippet.get()); + aMsgSnippet.SetLength(strlen(aMsgSnippet.get())); + } +} + +/** + * stripQuotesFromMsgSnippet - Reduces quoted reply text including the citation (Scott wrote:) from + * the message snippet to " ... ". Assumes the snippet has been decoded and converted to + * plain text. + * @param aMsgSnippet in/out argument. The string to strip quotes from. + */ +void nsMsgDBFolder::compressQuotesInMsgSnippet(const nsString& aMsgSnippet, nsAString& aCompressedQuotes) +{ + int32_t msgBodyStrLen = aMsgSnippet.Length(); + bool lastLineWasAQuote = false; + int32_t offset = 0; + int32_t lineFeedPos = 0; + while (offset < msgBodyStrLen) + { + lineFeedPos = aMsgSnippet.FindChar('\n', offset); + if (lineFeedPos != -1) + { + const nsAString& currentLine = Substring(aMsgSnippet, offset, lineFeedPos - offset); + // this catches quoted text ("> "), nested quotes of any level (">> ", ">>> ", ...) + // it also catches empty line quoted text (">"). It might be over agressive and require + // tweaking later. + // Try to strip the citation. If the current line ends with a ':' and the next line + // looks like a quoted reply (starts with a ">") skip the current line + if (StringBeginsWith(currentLine, NS_LITERAL_STRING(">")) || + (lineFeedPos + 1 < msgBodyStrLen && lineFeedPos + && aMsgSnippet[lineFeedPos - 1] == char16_t(':') + && aMsgSnippet[lineFeedPos + 1] == char16_t('>'))) + { + lastLineWasAQuote = true; + } + else if (!currentLine.IsEmpty()) + { + if (lastLineWasAQuote) + { + aCompressedQuotes += NS_LITERAL_STRING(" ... "); + lastLineWasAQuote = false; + } + + aCompressedQuotes += currentLine; + aCompressedQuotes += char16_t(' '); // don't forget to substitute a space for the line feed + } + + offset = lineFeedPos + 1; + } + else + { + aCompressedQuotes.Append(Substring(aMsgSnippet, offset, msgBodyStrLen - offset)); + break; + } + } +} + +NS_IMETHODIMP nsMsgDBFolder::ConvertMsgSnippetToPlainText( + const nsAString& aMessageText, nsAString& aOutText) +{ + uint32_t flags = nsIDocumentEncoder::OutputLFLineBreak + | nsIDocumentEncoder::OutputNoScriptContent + | nsIDocumentEncoder::OutputNoFramesContent + | nsIDocumentEncoder::OutputBodyOnly; + nsCOMPtr<nsIParserUtils> utils = + do_GetService(NS_PARSERUTILS_CONTRACTID); + return utils->ConvertToPlainText(aMessageText, flags, 80, aOutText); +} + +nsresult nsMsgDBFolder::GetMsgPreviewTextFromStream(nsIMsgDBHdr *msgHdr, nsIInputStream *stream) +{ + nsCString msgBody; + nsAutoCString charset; + msgHdr->GetCharset(getter_Copies(charset)); + nsAutoCString contentType; + nsresult rv = GetMsgTextFromStream(stream, charset, 4096, 255, true, true, contentType, msgBody); + // replaces all tabs and line returns with a space, + // then trims off leading and trailing white space + MsgCompressWhitespace(msgBody); + msgHdr->SetStringProperty("preview", msgBody.get()); + return rv; +} + +void nsMsgDBFolder::UpdateTimestamps(bool allowUndo) +{ + if (!(mFlags & (nsMsgFolderFlags::Trash|nsMsgFolderFlags::Junk))) + { + SetMRUTime(); + if (allowUndo) // This is a proxy for a user-initiated act. + { + bool isArchive; + IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isArchive); + if (!isArchive) + { + SetMRMTime(); + nsCOMPtr<nsIAtom> MRMTimeChangedAtom = MsgGetAtom("MRMTimeChanged"); + NotifyFolderEvent(MRMTimeChangedAtom); + } + } + } +} + +void nsMsgDBFolder::SetMRUTime() +{ + uint32_t seconds; + PRTime2Seconds(PR_Now(), &seconds); + nsAutoCString nowStr; + nowStr.AppendInt(seconds); + SetStringProperty(MRU_TIME_PROPERTY, nowStr); +} + +void nsMsgDBFolder::SetMRMTime() +{ + uint32_t seconds; + PRTime2Seconds(PR_Now(), &seconds); + nsAutoCString nowStr; + nowStr.AppendInt(seconds); + SetStringProperty(MRM_TIME_PROPERTY, nowStr); +} + +NS_IMETHODIMP nsMsgDBFolder::AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords) +{ + NS_ENSURE_ARG(aMessages); + nsresult rv = NS_OK; + GetDatabase(); + if (mDatabase) + { + uint32_t count; + nsresult rv = aMessages->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + nsCString keywords; + + for(uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + message->GetStringProperty("keywords", getter_Copies(keywords)); + nsTArray<nsCString> keywordArray; + ParseString(aKeywords, ' ', keywordArray); + uint32_t addCount = 0; + for (uint32_t j = 0; j < keywordArray.Length(); j++) + { + int32_t start, length; + if (!MsgFindKeyword(keywordArray[j], keywords, &start, &length)) + { + if (!keywords.IsEmpty()) + keywords.Append(' '); + keywords.Append(keywordArray[j]); + addCount++; + } + } + // avoid using the message key to set the string property, because + // in the case of filters running on incoming pop3 mail with quarantining + // turned on, the message key is wrong. + mDatabase->SetStringPropertyByHdr(message, "keywords", keywords.get()); + + if (addCount) + NotifyPropertyFlagChanged(message, kKeywords, 0, addCount); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords) +{ + NS_ENSURE_ARG(aMessages); + nsresult rv = NS_OK; + GetDatabase(); + if (mDatabase) + { + uint32_t count; + nsresult rv = aMessages->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + nsTArray<nsCString> keywordArray; + ParseString(aKeywords, ' ', keywordArray); + nsCString keywords; + // If the tag is also a label, we should remove the label too... + + for(uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = message->GetStringProperty("keywords", getter_Copies(keywords)); + uint32_t removeCount = 0; + for (uint32_t j = 0; j < keywordArray.Length(); j++) + { + bool keywordIsLabel = (StringBeginsWith(keywordArray[j], NS_LITERAL_CSTRING("$label")) + && keywordArray[j].CharAt(6) >= '1' && keywordArray[j].CharAt(6) <= '5'); + if (keywordIsLabel) + { + nsMsgLabelValue labelValue; + message->GetLabel(&labelValue); + // if we're removing the keyword that corresponds to a pre 2.0 label, + // we need to clear the old label attribute on the message. + if (labelValue == (nsMsgLabelValue) (keywordArray[j].CharAt(6) - '0')) + message->SetLabel((nsMsgLabelValue) 0); + } + int32_t startOffset, length; + if (MsgFindKeyword(keywordArray[j], keywords, &startOffset, &length)) + { + // delete any leading space delimiters + while (startOffset && keywords.CharAt(startOffset - 1) == ' ') + { + startOffset--; + length++; + } + // but if the keyword is at the start then delete the following space + if (!startOffset && length < static_cast<int32_t>(keywords.Length()) && + keywords.CharAt(length) == ' ') + length++; + keywords.Cut(startOffset, length); + removeCount++; + } + } + + if (removeCount) + { + mDatabase->SetStringPropertyByHdr(message, "keywords", keywords.get()); + NotifyPropertyFlagChanged(message, kKeywords, removeCount, 0); + } + } + } + return rv; +} + +NS_IMETHODIMP nsMsgDBFolder::GetCustomIdentity(nsIMsgIdentity **aIdentity) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + *aIdentity = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::GetProcessingFlags(nsMsgKey aKey, uint32_t *aFlags) +{ + NS_ENSURE_ARG_POINTER(aFlags); + *aFlags = 0; + for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++) + if (mProcessingFlag[i].keys && mProcessingFlag[i].keys->IsMember(aKey)) + *aFlags |= mProcessingFlag[i].bit; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::OrProcessingFlags(nsMsgKey aKey, uint32_t mask) +{ + for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++) + if (mProcessingFlag[i].bit & mask && mProcessingFlag[i].keys) + mProcessingFlag[i].keys->Add(aKey); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBFolder::AndProcessingFlags(nsMsgKey aKey, uint32_t mask) +{ + for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++) + if (!(mProcessingFlag[i].bit & mask) && mProcessingFlag[i].keys) + mProcessingFlag[i].keys->Remove(aKey); + return NS_OK; +} + +// Each implementation must provide an override of this, connecting the folder +// type to the corresponding incoming server type. +NS_IMETHODIMP nsMsgDBFolder::GetIncomingServerType(nsACString& aIncomingServerType) +{ + NS_ASSERTION(false, "subclasses need to override this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +void nsMsgDBFolder::ClearProcessingFlags() +{ + for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++) + { + // There is no clear method so we need to delete and re-create. + delete mProcessingFlag[i].keys; + mProcessingFlag[i].keys = nsMsgKeySetU::Create(); + } +} + +nsresult nsMsgDBFolder::MessagesInKeyOrder(nsTArray<nsMsgKey> &aKeyArray, + nsIMsgFolder *srcFolder, nsIMutableArray* messages) +{ + // XXX: the output messages - should really be and nsCOMArray<nsIMsgDBHdr> + + nsresult rv = NS_OK; + uint32_t numMessages = aKeyArray.Length(); + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgDatabase> db; + rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && db) + { + for (uint32_t i = 0; i < numMessages; i++) + { + rv = db->GetMsgHdrForKey(aKeyArray[i], getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv,rv); + if (msgHdr) + messages->AppendElement(msgHdr, false); + } + } + return rv; +} + +/* static */ nsMsgKeySetU* nsMsgKeySetU::Create() +{ + nsMsgKeySetU* set = new nsMsgKeySetU; + if (set) + { + set->loKeySet = nsMsgKeySet::Create(); + set->hiKeySet = nsMsgKeySet::Create(); + if (!(set->loKeySet && set->hiKeySet)) + { + delete set; + set = nullptr; + } + } + return set; +} + +nsMsgKeySetU::nsMsgKeySetU() +{ } + +nsMsgKeySetU::~nsMsgKeySetU() +{ + delete loKeySet; + delete hiKeySet; +} + +const uint32_t kLowerBits = 0x7fffffff; + +int nsMsgKeySetU::Add(nsMsgKey aKey) +{ + int32_t intKey = static_cast<int32_t>(aKey); + if (intKey >= 0) + return loKeySet->Add(intKey); + return hiKeySet->Add(intKey & kLowerBits); +} + +int nsMsgKeySetU::Remove(nsMsgKey aKey) +{ + int32_t intKey = static_cast<int32_t>(aKey); + if (intKey >= 0) + return loKeySet->Remove(intKey); + return hiKeySet->Remove(intKey & kLowerBits); +} + +bool nsMsgKeySetU::IsMember(nsMsgKey aKey) +{ + int32_t intKey = static_cast<int32_t>(aKey); + if (intKey >= 0) + return loKeySet->IsMember(intKey); + return hiKeySet->IsMember(intKey & kLowerBits); +} + +nsresult nsMsgKeySetU::ToMsgKeyArray(nsTArray<nsMsgKey> &aArray) +{ + nsresult rv = loKeySet->ToMsgKeyArray(aArray); + NS_ENSURE_SUCCESS(rv, rv); + return hiKeySet->ToMsgKeyArray(aArray); +} + diff --git a/mailnews/base/util/nsMsgDBFolder.h b/mailnews/base/util/nsMsgDBFolder.h new file mode 100644 index 000000000..6074b45f9 --- /dev/null +++ b/mailnews/base/util/nsMsgDBFolder.h @@ -0,0 +1,297 @@ +/* -*- 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 nsMsgDBFolder_h__ +#define nsMsgDBFolder_h__ + +#include "mozilla/Attributes.h" +#include "msgCore.h" +#include "nsIMsgFolder.h" +#include "nsRDFResource.h" +#include "nsIDBFolderInfo.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgIncomingServer.h" +#include "nsCOMPtr.h" +#include "nsStaticAtom.h" +#include "nsIDBChangeListener.h" +#include "nsIMsgPluggableStore.h" +#include "nsIURL.h" +#include "nsIFile.h" +#include "nsWeakReference.h" +#include "nsIMsgFilterList.h" +#include "nsIUrlListener.h" +#include "nsIMsgHdr.h" +#include "nsIOutputStream.h" +#include "nsITransport.h" +#include "nsIStringBundle.h" +#include "nsTObserverArray.h" +#include "nsCOMArray.h" +#include "nsMsgKeySet.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgFilterPlugin.h" +class nsIMsgFolderCacheElement; +class nsICollation; +class nsMsgKeySetU; + + /* + * nsMsgDBFolder + * class derived from nsMsgFolder for those folders that use an nsIMsgDatabase + */ + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT + +class NS_MSG_BASE nsMsgDBFolder: public nsRDFResource, + public nsSupportsWeakReference, + public nsIMsgFolder, + public nsIDBChangeListener, + public nsIUrlListener, + public nsIJunkMailClassificationListener, + public nsIMsgTraitClassificationListener +{ +public: + nsMsgDBFolder(void); + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIMSGFOLDER + NS_DECL_NSIDBCHANGELISTENER + NS_DECL_NSIURLLISTENER + NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER + NS_DECL_NSIMSGTRAITCLASSIFICATIONLISTENER + + NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement *element); + NS_IMETHOD ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element); + + // nsRDFResource overrides + NS_IMETHOD Init(const char* aURI) override; + + nsresult CreateDirectoryForFolder(nsIFile **result); + nsresult CreateBackupDirectory(nsIFile **result); + nsresult GetBackupSummaryFile(nsIFile **result, const nsACString& newName); + nsresult GetMsgPreviewTextFromStream(nsIMsgDBHdr *msgHdr, nsIInputStream *stream); + nsresult HandleAutoCompactEvent(nsIMsgWindow *aMsgWindow); +protected: + virtual ~nsMsgDBFolder(); + + virtual nsresult CreateBaseMessageURI(const nsACString& aURI); + + void compressQuotesInMsgSnippet(const nsString& aMessageText, nsAString& aCompressedQuotesStr); + void decodeMsgSnippet(const nsACString& aEncodingType, bool aIsComplete, nsCString& aMsgSnippet); + + // helper routine to parse the URI and update member variables + nsresult parseURI(bool needServer=false); + nsresult GetBaseStringBundle(nsIStringBundle **aBundle); + nsresult GetStringFromBundle(const char* msgName, nsString& aResult); + nsresult ThrowConfirmationPrompt(nsIMsgWindow *msgWindow, const nsAString& confirmString, bool *confirmed); + nsresult GetWarnFilterChanged(bool *aVal); + nsresult SetWarnFilterChanged(bool aVal); + nsresult CreateCollationKey(const nsString &aSource, uint8_t **aKey, uint32_t *aLength); + +protected: + // all children will override this to create the right class of object. + virtual nsresult CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder) = 0; + virtual nsresult ReadDBFolderInfo(bool force); + virtual nsresult FlushToFolderCache(); + virtual nsresult GetDatabase() = 0; + virtual nsresult SendFlagNotifications(nsIMsgDBHdr *item, uint32_t oldFlags, uint32_t newFlags); + nsresult CheckWithNewMessagesStatus(bool messageAdded); + void UpdateNewMessages(); + nsresult OnHdrAddedOrDeleted(nsIMsgDBHdr *hdrChanged, bool added); + nsresult CreateFileForDB(const nsAString& userLeafName, nsIFile *baseDir, + nsIFile **dbFile); + + nsresult GetFolderCacheKey(nsIFile **aFile, bool createDBIfMissing = false); + nsresult GetFolderCacheElemFromFile(nsIFile *file, nsIMsgFolderCacheElement **cacheElement); + nsresult AddDirectorySeparator(nsIFile *path); + nsresult CheckIfFolderExists(const nsAString& newFolderName, nsIMsgFolder *parentFolder, nsIMsgWindow *msgWindow); + bool ConfirmAutoFolderRename(nsIMsgWindow *aMsgWindow, + const nsString& aOldName, + const nsString& aNewName); + + // Returns true if: a) there is no need to prompt or b) the user is already + // logged in or c) the user logged in successfully. + static bool PromptForMasterPasswordIfNecessary(); + + // offline support methods. + nsresult StartNewOfflineMessage(); + nsresult WriteStartOfNewLocalMessage(); + nsresult EndNewOfflineMessage(); + nsresult CompactOfflineStore(nsIMsgWindow *inWindow, nsIUrlListener *aUrlListener); + nsresult AutoCompact(nsIMsgWindow *aWindow); + // this is a helper routine that ignores whether nsMsgMessageFlags::Offline is set for the folder + nsresult MsgFitsDownloadCriteria(nsMsgKey msgKey, bool *result); + nsresult GetPromptPurgeThreshold(bool *aPrompt); + nsresult GetPurgeThreshold(int32_t *aThreshold); + nsresult ApplyRetentionSettings(bool deleteViaFolder); + bool VerifyOfflineMessage(nsIMsgDBHdr *msgHdr, nsIInputStream *fileStream); + nsresult AddMarkAllReadUndoAction(nsIMsgWindow *msgWindow, + nsMsgKey *thoseMarked, uint32_t numMarked); + + nsresult PerformBiffNotifications(void); // if there are new, non spam messages, do biff + nsresult CloseDBIfFolderNotOpen(); + + virtual nsresult SpamFilterClassifyMessage(const char *aURI, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin); + virtual nsresult SpamFilterClassifyMessages(const char **aURIArray, uint32_t aURICount, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin); + // Helper function for Move code to call to update the MRU and MRM time. + void UpdateTimestamps(bool allowUndo); + void SetMRUTime(); + void SetMRMTime(); + /** + * Clear all processing flags, presumably because message keys are no longer + * valid. + */ + void ClearProcessingFlags(); + + nsresult NotifyHdrsNotBeingClassified(); + + /** + * Produce an array of messages ordered like the input keys. + */ + nsresult MessagesInKeyOrder(nsTArray<nsMsgKey> &aKeyArray, + nsIMsgFolder *srcFolder, + nsIMutableArray* messages); + +protected: + nsCOMPtr<nsIMsgDatabase> mDatabase; + nsCOMPtr<nsIMsgDatabase> mBackupDatabase; + nsCString mCharset; + bool mCharsetOverride; + bool mAddListener; + bool mNewMessages; + bool mGettingNewMessages; + nsMsgKey mLastMessageLoaded; + + nsCOMPtr <nsIMsgDBHdr> m_offlineHeader; + int32_t m_numOfflineMsgLines; + int32_t m_bytesAddedToLocalMsg; + // this is currently used when we do a save as of an imap or news message.. + nsCOMPtr<nsIOutputStream> m_tempMessageStream; + + nsCOMPtr <nsIMsgRetentionSettings> m_retentionSettings; + nsCOMPtr <nsIMsgDownloadSettings> m_downloadSettings; + static NS_MSG_BASE_STATIC_MEMBER_(nsrefcnt) mInstanceCount; + +protected: + uint32_t mFlags; + nsWeakPtr mParent; //This won't be refcounted for ownership reasons. + int32_t mNumUnreadMessages; /* count of unread messages (-1 means unknown; -2 means unknown but we already tried to find out.) */ + int32_t mNumTotalMessages; /* count of existing messages. */ + bool mNotifyCountChanges; + int64_t mExpungedBytes; + nsCOMArray<nsIMsgFolder> mSubFolders; + // This can't be refcounted due to ownsership issues + nsTObserverArray<nsIFolderListener*> mListeners; + + bool mInitializedFromCache; + nsISupports *mSemaphoreHolder; // set when the folder is being written to + //Due to ownership issues, this won't be AddRef'd. + + nsWeakPtr mServer; + + // These values are used for tricking the front end into thinking that we have more + // messages than are really in the DB. This is usually after and IMAP message copy where + // we don't want to do an expensive select until the user actually opens that folder + int32_t mNumPendingUnreadMessages; + int32_t mNumPendingTotalMessages; + int64_t mFolderSize; + + int32_t mNumNewBiffMessages; + + // these are previous set of new msgs, which we might + // want to run junk controls on. This is in addition to "new" hdrs + // in the db, which might get cleared because the user clicked away + // from the folder. + nsTArray<nsMsgKey> m_saveNewMsgs; + + // These are the set of new messages for a folder who has had + // its db closed, without the user reading the folder. This + // happens with pop3 mail filtered to a different local folder. + nsTArray<nsMsgKey> m_newMsgs; + + // + // stuff from the uri + // + bool mHaveParsedURI; // is the URI completely parsed? + bool mIsServerIsValid; + bool mIsServer; + nsString mName; + nsCOMPtr<nsIFile> mPath; + nsCString mBaseMessageURI; //The uri with the message scheme + + bool mInVFEditSearchScope ; // non persistant state used by the virtual folder UI + + // static stuff for cross-instance objects like atoms + static NS_MSG_BASE_STATIC_MEMBER_(nsrefcnt) gInstanceCount; + + static nsresult initializeStrings(); + static nsresult createCollationKeyGenerator(); + + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedInboxName; + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedTrashName; + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedSentName; + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedDraftsName; + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedTemplatesName; + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedUnsentName; + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedJunkName; + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedArchivesName; + + static NS_MSG_BASE_STATIC_MEMBER_(char16_t*) kLocalizedBrandShortName; + +#define MSGDBFOLDER_ATOM(name_, value) static NS_MSG_BASE_STATIC_MEMBER_(nsIAtom*) name_; +#include "nsMsgDBFolderAtomList.h" +#undef MSGDBFOLDER_ATOM + + static NS_MSG_BASE_STATIC_MEMBER_(nsICollation*) gCollationKeyGenerator; + + // store of keys that have a processing flag set + struct + { + uint32_t bit; + nsMsgKeySetU* keys; + } mProcessingFlag[nsMsgProcessingFlags::NumberOfFlags]; + + // list of nsIMsgDBHdrs for messages to process post-bayes + nsCOMPtr<nsIMutableArray> mPostBayesMessagesToFilter; + + /** + * The list of message keys that have been classified for msgsClassified + * batch notification purposes. We add to this list in OnMessageClassified + * when we are told about a classified message (a URI is provided), and we + * notify for the list and clear it when we are told all the messages in + * the batch were classified (a URI is not provided). + */ + nsTArray<nsMsgKey> mClassifiedMsgKeys; + // Is the current bayes filtering doing junk classification? + bool mBayesJunkClassifying; + // Is the current bayes filtering doing trait classification? + bool mBayesTraitClassifying; +}; + +// This class is a kludge to allow nsMsgKeySet to be used with uint32_t keys +class nsMsgKeySetU +{ +public: + // Creates an empty set. + static nsMsgKeySetU* Create(); + ~nsMsgKeySetU(); + // IsMember() returns whether the given key is a member of this set. + bool IsMember(nsMsgKey key); + // Add() adds the given key to the set. (Returns 1 if a change was + // made, 0 if it was already there, and negative on error.) + int Add(nsMsgKey key); + // Remove() removes the given article from the set. + int Remove(nsMsgKey key); + // Add the keys in the set to aArray. + nsresult ToMsgKeyArray(nsTArray<nsMsgKey> &aArray); + +protected: + nsMsgKeySetU(); + nsMsgKeySet* loKeySet; + nsMsgKeySet* hiKeySet; +}; + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN + +#endif diff --git a/mailnews/base/util/nsMsgDBFolderAtomList.h b/mailnews/base/util/nsMsgDBFolderAtomList.h new file mode 100644 index 000000000..6ec8c0691 --- /dev/null +++ b/mailnews/base/util/nsMsgDBFolderAtomList.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +MSGDBFOLDER_ATOM(kTotalUnreadMessagesAtom, "TotalUnreadMessages") +MSGDBFOLDER_ATOM(kBiffStateAtom, "BiffState") +MSGDBFOLDER_ATOM(kNewMailReceivedAtom, "NewMailReceived") +MSGDBFOLDER_ATOM(kNewMessagesAtom, "NewMessages") +MSGDBFOLDER_ATOM(kInVFEditSearchScopeAtom, "inVFEditSearchScope") +MSGDBFOLDER_ATOM(kNumNewBiffMessagesAtom, "NumNewBiffMessages") +MSGDBFOLDER_ATOM(kTotalMessagesAtom, "TotalMessages") +MSGDBFOLDER_ATOM(kFolderSizeAtom, "FolderSize") +MSGDBFOLDER_ATOM(kStatusAtom, "Status") +MSGDBFOLDER_ATOM(kFlaggedAtom, "Flagged") +MSGDBFOLDER_ATOM(kNameAtom, "Name") +MSGDBFOLDER_ATOM(kSynchronizeAtom, "Synchronize") +MSGDBFOLDER_ATOM(kOpenAtom, "open") +MSGDBFOLDER_ATOM(kIsDeferred, "isDeferred") +MSGDBFOLDER_ATOM(kKeywords, "Keywords") +MSGDBFOLDER_ATOM(mFolderLoadedAtom, "FolderLoaded") +MSGDBFOLDER_ATOM(mDeleteOrMoveMsgCompletedAtom, "DeleteOrMoveMsgCompleted") +MSGDBFOLDER_ATOM(mDeleteOrMoveMsgFailedAtom, "DeleteOrMoveMsgFailed") +MSGDBFOLDER_ATOM(mJunkStatusChangedAtom, "JunkStatusChanged") +MSGDBFOLDER_ATOM(mFiltersAppliedAtom, "FiltersApplied") +MSGDBFOLDER_ATOM(mFolderFlagAtom, "FolderFlag") diff --git a/mailnews/base/util/nsMsgFileStream.cpp b/mailnews/base/util/nsMsgFileStream.cpp new file mode 100644 index 000000000..87d11ab3d --- /dev/null +++ b/mailnews/base/util/nsMsgFileStream.cpp @@ -0,0 +1,196 @@ +/* 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 "nsIFile.h" +#include "nsMsgFileStream.h" +#include "prerr.h" +#include "prerror.h" + +/* From nsDebugImpl.cpp: */ +static nsresult +ErrorAccordingToNSPR() +{ + PRErrorCode err = PR_GetError(); + switch (err) { + case PR_OUT_OF_MEMORY_ERROR: return NS_ERROR_OUT_OF_MEMORY; + case PR_WOULD_BLOCK_ERROR: return NS_BASE_STREAM_WOULD_BLOCK; + case PR_FILE_NOT_FOUND_ERROR: return NS_ERROR_FILE_NOT_FOUND; + case PR_READ_ONLY_FILESYSTEM_ERROR: return NS_ERROR_FILE_READ_ONLY; + case PR_NOT_DIRECTORY_ERROR: return NS_ERROR_FILE_NOT_DIRECTORY; + case PR_IS_DIRECTORY_ERROR: return NS_ERROR_FILE_IS_DIRECTORY; + case PR_LOOP_ERROR: return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + case PR_FILE_EXISTS_ERROR: return NS_ERROR_FILE_ALREADY_EXISTS; + case PR_FILE_IS_LOCKED_ERROR: return NS_ERROR_FILE_IS_LOCKED; + case PR_FILE_TOO_BIG_ERROR: return NS_ERROR_FILE_TOO_BIG; + case PR_NO_DEVICE_SPACE_ERROR: return NS_ERROR_FILE_NO_DEVICE_SPACE; + case PR_NAME_TOO_LONG_ERROR: return NS_ERROR_FILE_NAME_TOO_LONG; + case PR_DIRECTORY_NOT_EMPTY_ERROR: return NS_ERROR_FILE_DIR_NOT_EMPTY; + case PR_NO_ACCESS_RIGHTS_ERROR: return NS_ERROR_FILE_ACCESS_DENIED; + default: return NS_ERROR_FAILURE; + } +} + +nsMsgFileStream::nsMsgFileStream() +{ + mFileDesc = nullptr; + mSeekedToEnd = false; +} + +nsMsgFileStream::~nsMsgFileStream() +{ + if (mFileDesc) + PR_Close(mFileDesc); +} + +NS_IMPL_ISUPPORTS(nsMsgFileStream, nsIInputStream, nsIOutputStream, nsISeekableStream) + +nsresult nsMsgFileStream::InitWithFile(nsIFile *file) +{ + return file->OpenNSPRFileDesc(PR_RDWR|PR_CREATE_FILE, 0664, &mFileDesc); +} + +NS_IMETHODIMP +nsMsgFileStream::Seek(int32_t whence, int64_t offset) +{ + if (mFileDesc == nullptr) + return NS_BASE_STREAM_CLOSED; + + bool seekingToEnd = whence == PR_SEEK_END && offset == 0; + if (seekingToEnd && mSeekedToEnd) + return NS_OK; + + int64_t cnt = PR_Seek64(mFileDesc, offset, (PRSeekWhence)whence); + if (cnt == int64_t(-1)) { + return ErrorAccordingToNSPR(); + } + + mSeekedToEnd = seekingToEnd; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFileStream::Tell(int64_t *result) +{ + if (mFileDesc == nullptr) + return NS_BASE_STREAM_CLOSED; + + int64_t cnt = PR_Seek64(mFileDesc, 0, PR_SEEK_CUR); + if (cnt == int64_t(-1)) { + return ErrorAccordingToNSPR(); + } + *result = cnt; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFileStream::SetEOF() +{ + if (mFileDesc == nullptr) + return NS_BASE_STREAM_CLOSED; + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void close (); */ +NS_IMETHODIMP nsMsgFileStream::Close() +{ + nsresult rv = NS_OK; + if (mFileDesc && (PR_Close(mFileDesc) == PR_FAILURE)) + rv = NS_BASE_STREAM_OSERROR; + mFileDesc = nullptr; + return rv; +} + +/* unsigned long long available (); */ +NS_IMETHODIMP nsMsgFileStream::Available(uint64_t *aResult) +{ + if (!mFileDesc) + return NS_BASE_STREAM_CLOSED; + + int64_t avail = PR_Available64(mFileDesc); + if (avail == -1) + return ErrorAccordingToNSPR(); + + *aResult = avail; + return NS_OK; +} + +/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */ +NS_IMETHODIMP nsMsgFileStream::Read(char * aBuf, uint32_t aCount, uint32_t *aResult) +{ + if (!mFileDesc) + { + *aResult = 0; + return NS_OK; + } + + int32_t bytesRead = PR_Read(mFileDesc, aBuf, aCount); + if (bytesRead == -1) + return ErrorAccordingToNSPR(); + + *aResult = bytesRead; + return NS_OK; +} + +/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in voidPtr aClosure, in unsigned long aCount); */ +NS_IMETHODIMP nsMsgFileStream::ReadSegments(nsWriteSegmentFun aWriter, void * aClosure, uint32_t aCount, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* boolean isNonBlocking (); */ +NS_IMETHODIMP nsMsgFileStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFileStream::Write(const char *buf, uint32_t count, uint32_t *result) +{ + if (mFileDesc == nullptr) + return NS_BASE_STREAM_CLOSED; + + int32_t cnt = PR_Write(mFileDesc, buf, count); + if (cnt == -1) { + return ErrorAccordingToNSPR(); + } + *result = cnt; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFileStream::Flush(void) +{ + if (mFileDesc == nullptr) + return NS_BASE_STREAM_CLOSED; + + int32_t cnt = PR_Sync(mFileDesc); + if (cnt == -1) + return ErrorAccordingToNSPR(); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFileStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) +{ + NS_NOTREACHED("WriteFrom (see source comment)"); + return NS_ERROR_NOT_IMPLEMENTED; + // File streams intentionally do not support this method. + // If you need something like this, then you should wrap + // the file stream using nsIBufferedOutputStream +} + +NS_IMETHODIMP +nsMsgFileStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) +{ + NS_NOTREACHED("WriteSegments (see source comment)"); + return NS_ERROR_NOT_IMPLEMENTED; + // File streams intentionally do not support this method. + // If you need something like this, then you should wrap + // the file stream using nsIBufferedOutputStream +} + + + diff --git a/mailnews/base/util/nsMsgFileStream.h b/mailnews/base/util/nsMsgFileStream.h new file mode 100644 index 000000000..7c2b0cefb --- /dev/null +++ b/mailnews/base/util/nsMsgFileStream.h @@ -0,0 +1,33 @@ +/* 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 "mozilla/Attributes.h" +#include "msgCore.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISeekableStream.h" +#include "prio.h" + +class nsMsgFileStream final : public nsIInputStream, + public nsIOutputStream, + public nsISeekableStream +{ +public: + nsMsgFileStream(); + + NS_DECL_ISUPPORTS + + NS_IMETHOD Available(uint64_t *_retval) override; + NS_IMETHOD Read(char * aBuf, uint32_t aCount, uint32_t *_retval) override; + NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void * aClosure, uint32_t aCount, uint32_t *_retval) override; + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + + nsresult InitWithFile(nsIFile *localFile); +protected: + ~nsMsgFileStream(); + + PRFileDesc *mFileDesc; + bool mSeekedToEnd; +}; diff --git a/mailnews/base/util/nsMsgI18N.cpp b/mailnews/base/util/nsMsgI18N.cpp new file mode 100644 index 000000000..b79a4c196 --- /dev/null +++ b/mailnews/base/util/nsMsgI18N.cpp @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +// as does this +#include "nsICharsetConverterManager.h" +#include "nsIPlatformCharset.h" +#include "nsIServiceManager.h" + +#include "nsISupports.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIMimeConverter.h" +#include "nsMsgUtils.h" +#include "nsMsgI18N.h" +#include "nsMsgMimeCID.h" +#include "nsILineInputStream.h" +#include "nsMimeTypes.h" +#include "nsISaveAsCharset.h" +#include "nsStringGlue.h" +#include "prmem.h" +#include "plstr.h" +#include "nsUTF8Utils.h" +#include "nsNetUtil.h" +#include "nsCRTGlue.h" +#include "nsComponentManagerUtils.h" +#include "nsUnicharUtils.h" +#include "nsIFileStreams.h" +// +// International functions necessary for composition +// + +nsresult nsMsgI18NConvertFromUnicode(const char* aCharset, + const nsString& inString, + nsACString& outString, + bool aIsCharsetCanonical, + bool aReportUencNoMapping) +{ + if (inString.IsEmpty()) { + outString.Truncate(); + return NS_OK; + } + // Note: This will hide a possible error if the Unicode contains more than one + // charset, e.g. Latin1 + Japanese. + else if (!aReportUencNoMapping && (!*aCharset || + !PL_strcasecmp(aCharset, "us-ascii") || + !PL_strcasecmp(aCharset, "ISO-8859-1"))) { + LossyCopyUTF16toASCII(inString, outString); + return NS_OK; + } + else if (!PL_strcasecmp(aCharset, "UTF-8")) { + CopyUTF16toUTF8(inString, outString); + return NS_OK; + } + + nsresult rv; + nsCOMPtr <nsICharsetConverterManager> ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIUnicodeEncoder> encoder; + + // get an unicode converter + if (aIsCharsetCanonical) // optimize for modified UTF-7 used by IMAP + rv = ccm->GetUnicodeEncoderRaw(aCharset, getter_AddRefs(encoder)); + else + rv = ccm->GetUnicodeEncoder(aCharset, getter_AddRefs(encoder)); + NS_ENSURE_SUCCESS(rv, rv); + // Must set behavior to kOnError_Signal if we want to receive the + // NS_ERROR_UENC_NOMAPPING signal, should it occur. + int32_t behavior = aReportUencNoMapping ? nsIUnicodeEncoder::kOnError_Signal: + nsIUnicodeEncoder::kOnError_Replace; + rv = encoder->SetOutputErrorBehavior(behavior, nullptr, '?'); + NS_ENSURE_SUCCESS(rv, rv); + + const char16_t *originalSrcPtr = inString.get(); + const char16_t *currentSrcPtr = originalSrcPtr; + int32_t originalUnicharLength = inString.Length(); + int32_t srcLength; + int32_t dstLength; + char localbuf[512+10]; // We have seen cases were the buffer was overrun + // by two (!!) bytes (Bug 1255863). + // So give it ten bytes more for now to avoid a crash. + int32_t consumedLen = 0; + + bool mappingFailure = false; + outString.Truncate(); + // convert + while (consumedLen < originalUnicharLength) { + srcLength = originalUnicharLength - consumedLen; + dstLength = 512; + rv = encoder->Convert(currentSrcPtr, &srcLength, localbuf, &dstLength); +#ifdef DEBUG + if (dstLength > 512) { + char warning[100]; + sprintf(warning, "encoder->Convert() returned %d bytes. Limit = 512", dstLength); + NS_WARNING(warning); + } +#endif + if (rv == NS_ERROR_UENC_NOMAPPING) { + mappingFailure = true; + } + if (NS_FAILED(rv) || dstLength == 0) + break; + outString.Append(localbuf, dstLength); + + currentSrcPtr += srcLength; + consumedLen = currentSrcPtr - originalSrcPtr; // src length used so far + } + dstLength = 512; // Reset available buffer size. + rv = encoder->Finish(localbuf, &dstLength); + if (NS_SUCCEEDED(rv)) { + if (dstLength) + outString.Append(localbuf, dstLength); + return !mappingFailure ? rv: NS_ERROR_UENC_NOMAPPING; + } + return rv; +} + +nsresult nsMsgI18NConvertToUnicode(const char* aCharset, + const nsCString& inString, + nsAString& outString, + bool aIsCharsetCanonical) +{ + if (inString.IsEmpty()) { + outString.Truncate(); + return NS_OK; + } + else if (!*aCharset || !PL_strcasecmp(aCharset, "us-ascii") || + !PL_strcasecmp(aCharset, "ISO-8859-1")) { + // Despite its name, it also works for Latin-1. + CopyASCIItoUTF16(inString, outString); + return NS_OK; + } + else if (!PL_strcasecmp(aCharset, "UTF-8")) { + if (MsgIsUTF8(inString)) { + nsAutoString tmp; + CopyUTF8toUTF16(inString, tmp); + if (!tmp.IsEmpty() && tmp.First() == char16_t(0xFEFF)) + tmp.Cut(0, 1); + outString.Assign(tmp); + return NS_OK; + } + NS_WARNING("Invalid UTF-8 string"); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv; + nsCOMPtr <nsICharsetConverterManager> ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIUnicodeDecoder> decoder; + + // get an unicode converter + if (aIsCharsetCanonical) // optimize for modified UTF-7 used by IMAP + rv = ccm->GetUnicodeDecoderRaw(aCharset, getter_AddRefs(decoder)); + else + rv = ccm->GetUnicodeDecoderInternal(aCharset, getter_AddRefs(decoder)); + NS_ENSURE_SUCCESS(rv, rv); + + const char *originalSrcPtr = inString.get(); + const char *currentSrcPtr = originalSrcPtr; + int32_t originalLength = inString.Length(); + int32_t srcLength; + int32_t dstLength; + char16_t localbuf[512]; + int32_t consumedLen = 0; + + outString.Truncate(); + + // convert + while (consumedLen < originalLength) { + srcLength = originalLength - consumedLen; + dstLength = 512; + rv = decoder->Convert(currentSrcPtr, &srcLength, localbuf, &dstLength); + if (NS_FAILED(rv) || dstLength == 0) + break; + outString.Append(localbuf, dstLength); + + currentSrcPtr += srcLength; + consumedLen = currentSrcPtr - originalSrcPtr; // src length used so far + } + return rv; +} + +// Charset used by the file system. +const char * nsMsgI18NFileSystemCharset() +{ + /* Get a charset used for the file. */ + static nsAutoCString fileSystemCharset; + + if (fileSystemCharset.IsEmpty()) + { + nsresult rv; + nsCOMPtr <nsIPlatformCharset> platformCharset = do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = platformCharset->GetCharset(kPlatformCharsetSel_FileName, + fileSystemCharset); + } + + if (NS_FAILED(rv)) + fileSystemCharset.Assign("ISO-8859-1"); + } + return fileSystemCharset.get(); +} + +// Charset used by the text file. +void nsMsgI18NTextFileCharset(nsACString& aCharset) +{ + nsresult rv; + nsCOMPtr <nsIPlatformCharset> platformCharset = + do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = platformCharset->GetCharset(kPlatformCharsetSel_PlainTextInFile, + aCharset); + } + + if (NS_FAILED(rv)) + aCharset.Assign("ISO-8859-1"); +} + +// MIME encoder, output string should be freed by PR_FREE +// XXX : fix callers later to avoid allocation and copy +char * nsMsgI18NEncodeMimePartIIStr(const char *header, bool structured, const char *charset, int32_t fieldnamelen, bool usemime) +{ + // No MIME, convert to the outgoing mail charset. + if (false == usemime) { + nsAutoCString convertedStr; + if (NS_SUCCEEDED(ConvertFromUnicode(charset, NS_ConvertUTF8toUTF16(header), + convertedStr))) + return PL_strdup(convertedStr.get()); + else + return PL_strdup(header); + } + + nsAutoCString encodedString; + nsresult res; + nsCOMPtr<nsIMimeConverter> converter = do_GetService(NS_MIME_CONVERTER_CONTRACTID, &res); + if (NS_SUCCEEDED(res) && nullptr != converter) + res = converter->EncodeMimePartIIStr_UTF8(nsDependentCString(header), + structured, "UTF-8", fieldnamelen, + nsIMimeConverter::MIME_ENCODED_WORD_SIZE, encodedString); + + return NS_SUCCEEDED(res) ? PL_strdup(encodedString.get()) : nullptr; +} + +// Return True if a charset is stateful (e.g. JIS). +bool nsMsgI18Nstateful_charset(const char *charset) +{ + //TODO: use charset manager's service + return (PL_strcasecmp(charset, "ISO-2022-JP") == 0); +} + +bool nsMsgI18Nmultibyte_charset(const char *charset) +{ + nsresult res; + nsCOMPtr <nsICharsetConverterManager> ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res); + bool result = false; + + if (NS_SUCCEEDED(res)) { + nsAutoString charsetData; + res = ccm->GetCharsetData(charset, u".isMultibyte", charsetData); + if (NS_SUCCEEDED(res)) { + result = charsetData.LowerCaseEqualsLiteral("true"); + } + } + + return result; +} + +bool nsMsgI18Ncheck_data_in_charset_range(const char *charset, const char16_t* inString, char **fallbackCharset) +{ + if (!charset || !*charset || !inString || !*inString) + return true; + + nsresult res; + bool result = true; + + nsCOMPtr <nsICharsetConverterManager> ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res); + + if (NS_SUCCEEDED(res)) { + nsCOMPtr <nsIUnicodeEncoder> encoder; + + // get an unicode converter + res = ccm->GetUnicodeEncoderRaw(charset, getter_AddRefs(encoder)); + if(NS_SUCCEEDED(res)) { + const char16_t *originalPtr = inString; + int32_t originalLen = NS_strlen(inString); + const char16_t *currentSrcPtr = originalPtr; + char localBuff[512]; + int32_t consumedLen = 0; + int32_t srcLen; + int32_t dstLength; + + // convert from unicode + while (consumedLen < originalLen) { + srcLen = originalLen - consumedLen; + dstLength = 512; + res = encoder->Convert(currentSrcPtr, &srcLen, localBuff, &dstLength); + if (NS_ERROR_UENC_NOMAPPING == res) { + result = false; + break; + } + else if (NS_FAILED(res) || (0 == dstLength)) + break; + + currentSrcPtr += srcLen; + consumedLen = currentSrcPtr - originalPtr; // src length used so far + } + } + } + + // if the conversion was not successful then try fallback to other charsets + if (!result && fallbackCharset) { + nsCString convertedString; + res = nsMsgI18NConvertFromUnicode(*fallbackCharset, + nsDependentString(inString), convertedString, false, true); + result = (NS_SUCCEEDED(res) && NS_ERROR_UENC_NOMAPPING != res); + } + + return result; +} + +// Simple parser to parse META charset. +// It only supports the case when the description is within one line. +const char * +nsMsgI18NParseMetaCharset(nsIFile* file) +{ + static char charset[nsIMimeConverter::MAX_CHARSET_NAME_LENGTH+1]; + + *charset = '\0'; + + bool isDirectory = false; + file->IsDirectory(&isDirectory); + if (isDirectory) { + NS_ERROR("file is a directory"); + return charset; + } + + nsresult rv; + nsCOMPtr <nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, charset); + + rv = fileStream->Init(file, PR_RDONLY, 0664, false); + nsCOMPtr <nsILineInputStream> lineStream = do_QueryInterface(fileStream, &rv); + + nsCString curLine; + bool more = true; + while (NS_SUCCEEDED(rv) && more) { + rv = lineStream->ReadLine(curLine, &more); + if (curLine.IsEmpty()) + continue; + + ToUpperCase(curLine); + + if (curLine.Find("/HEAD") != -1) + break; + + if (curLine.Find("META") != -1 && + curLine.Find("HTTP-EQUIV") != -1 && + curLine.Find("CONTENT-TYPE") != -1 && + curLine.Find("CHARSET") != -1) { + char *cp = (char *) PL_strchr(PL_strstr(curLine.get(), "CHARSET"), '='); + char *token = nullptr; + if (cp) + { + char *newStr = cp + 1; + token = NS_strtok(" \"\'", &newStr); + } + if (token) { + PL_strncpy(charset, token, sizeof(charset)); + charset[sizeof(charset)-1] = '\0'; + + // this function cannot parse a file if it is really + // encoded by one of the following charsets + // so we can say that the charset label must be incorrect for + // the .html if we actually see those charsets parsed + // and we should ignore them + if (!PL_strncasecmp("UTF-16", charset, sizeof("UTF-16")-1) || + !PL_strncasecmp("UTF-32", charset, sizeof("UTF-32")-1)) + charset[0] = '\0'; + + break; + } + } + } + + return charset; +} + +nsresult nsMsgI18NShrinkUTF8Str(const nsCString &inString, + uint32_t aMaxLength, + nsACString &outString) +{ + if (inString.IsEmpty()) { + outString.Truncate(); + return NS_OK; + } + if (inString.Length() < aMaxLength) { + outString.Assign(inString); + return NS_OK; + } + NS_ASSERTION(MsgIsUTF8(inString), "Invalid UTF-8 string is inputted"); + const char* start = inString.get(); + const char* end = start + inString.Length(); + const char* last = start + aMaxLength; + const char* cur = start; + const char* prev = nullptr; + bool err = false; + while (cur < last) { + prev = cur; + if (!UTF8CharEnumerator::NextChar(&cur, end, &err) || err) + break; + } + if (!prev || err) { + outString.Truncate(); + return NS_OK; + } + uint32_t len = prev - start; + outString.Assign(Substring(inString, 0, len)); + return NS_OK; +} + +void nsMsgI18NConvertRawBytesToUTF16(const nsCString& inString, + const char* charset, + nsAString& outString) +{ + if (MsgIsUTF8(inString)) + { + CopyUTF8toUTF16(inString, outString); + return; + } + + nsresult rv = ConvertToUnicode(charset, inString, outString); + if (NS_SUCCEEDED(rv)) + return; + + const char* cur = inString.BeginReading(); + const char* end = inString.EndReading(); + outString.Truncate(); + while (cur < end) { + char c = *cur++; + if (c & char(0x80)) + outString.Append(UCS2_REPLACEMENT_CHAR); + else + outString.Append(c); + } +} + +void nsMsgI18NConvertRawBytesToUTF8(const nsCString& inString, + const char* charset, + nsACString& outString) +{ + if (MsgIsUTF8(inString)) + { + outString.Assign(inString); + return; + } + + nsAutoString utf16Text; + nsresult rv = ConvertToUnicode(charset, inString, utf16Text); + if (NS_SUCCEEDED(rv)) + { + CopyUTF16toUTF8(utf16Text, outString); + return; + } + + // EF BF BD (UTF-8 encoding of U+FFFD) + NS_NAMED_LITERAL_CSTRING(utf8ReplacementChar, "\357\277\275"); + const char* cur = inString.BeginReading(); + const char* end = inString.EndReading(); + outString.Truncate(); + while (cur < end) { + char c = *cur++; + if (c & char(0x80)) + outString.Append(utf8ReplacementChar); + else + outString.Append(c); + } +} diff --git a/mailnews/base/util/nsMsgI18N.h b/mailnews/base/util/nsMsgI18N.h new file mode 100644 index 000000000..e58ae620a --- /dev/null +++ b/mailnews/base/util/nsMsgI18N.h @@ -0,0 +1,198 @@ +/* -*- 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 _nsMsgI18N_H_ +#define _nsMsgI18N_H_ + +#include "nscore.h" +#include "msgCore.h" +#include "nsStringGlue.h" +class nsIFile; + +/** + * Encode an input string into RFC 2047 form. + * + * @param header [IN] A header to encode. + * @param structured [IN] Specify the header is structured or non-structured field (See RFC-822). + * @param charset [IN] Charset name to convert. + * @param fieldnamelen [IN] Header field name length. (e.g. "From: " -> 6) + * @param usemime [IN] If false then apply charset conversion only no MIME encoding. + * @return Encoded buffer (in C string) or NULL in case of error. + */ +NS_MSG_BASE char *nsMsgI18NEncodeMimePartIIStr(const char *header, bool structured, const char *charset, int32_t fieldnamelen, bool usemime); + +/** + * Check if given charset is stateful (e.g. ISO-2022-JP). + * + * @param charset [IN] Charset name. + * @return True if stateful + */ +NS_MSG_BASE bool nsMsgI18Nstateful_charset(const char *charset); + +/** + * Check if given charset is multibye (e.g. Shift_JIS, Big5). + * + * @param charset [IN] Charset name. + * @return True if multibyte + */ +NS_MSG_BASE bool nsMsgI18Nmultibyte_charset(const char *charset); + +/** + * Check the input (unicode) string is in a range of the given charset after the conversion. + * Note, do not use this for large string (e.g. message body) since this actually applies the conversion to the buffer. + * + * @param charset [IN] Charset to be converted. + * @param inString [IN] Input unicode string to be examined. + * @param fallbackCharset [OUT] + * null if fallback charset is not needed. + * Otherwise, a fallback charset name may be set if that was used for the conversion. + * Caller is responsible for freeing the memory. + * @return True if the string can be converted within the charset range. + * False if one or more characters cannot be converted to the target charset. + */ +NS_MSG_BASE bool nsMsgI18Ncheck_data_in_charset_range(const char *charset, const char16_t* inString, + char **fallbackCharset=nullptr); + +/** + * Return charset name of file system (OS dependent). + * + * @return File system charset name. + */ +NS_MSG_BASE const char * nsMsgI18NFileSystemCharset(void); + +/** + * Return charset name of text file (OS dependent). + * + * @param aCharset [OUT] Text file charset name. + */ +NS_MSG_BASE void nsMsgI18NTextFileCharset(nsACString& aCharset); + +/** + * Convert from unicode to target charset. + * + * @param charset [IN] Charset name. + * @param inString [IN] Unicode string to convert. + * @param outString [OUT] Converted output string. + * @param aIsCharsetCanonical [IN] Whether the charset is canonical or not. + * @param aReportUencNoMapping [IN] Set encoder to report (instead of using + * replacement char on errors). Set to true + * to receive NS_ERROR_UENC_NOMAPPING when + * that happens. Note that + * NS_ERROR_UENC_NOMAPPING is a success code! + * @return nsresult. + */ +NS_MSG_BASE nsresult nsMsgI18NConvertFromUnicode(const char* aCharset, + const nsString& inString, + nsACString& outString, + bool aIsCharsetCanonical = + false, + bool reportUencNoMapping = + false); +/** + * Convert from charset to unicode. + * + * @param charset [IN] Charset name. + * @param inString [IN] Input string to convert. + * @param outString [OUT] Output unicode string. + * @return nsresult. + */ +NS_MSG_BASE nsresult nsMsgI18NConvertToUnicode(const char* aCharset, + const nsCString& inString, + nsAString& outString, + bool aIsCharsetCanonical = + false); +/** + * Parse for META charset. + * + * @param file [IN] A nsIFile. + * @return A charset name or empty string if not found. + */ +NS_MSG_BASE const char *nsMsgI18NParseMetaCharset(nsIFile* file); + +/** + * Shrink the aStr to aMaxLength bytes. Note that this doesn't check whether + * the aUTF8Str is valid UTF-8 string. + * + * @param inString [IN] Input UTF-8 string (it must be valid UTF-8 string) + * @param aMaxLength [IN] Shrink to this length (it means bytes) + * @param outString [OUT] Shrunken UTF-8 string + * @return nsresult + */ +NS_MSG_BASE nsresult nsMsgI18NShrinkUTF8Str(const nsCString &inString, + uint32_t aMaxLength, + nsACString &outString); + +/* + * Convert raw bytes in header to UTF-16 + * + * @param inString [IN] Input raw octets + * @param outString [OUT] Output UTF-16 string + */ +NS_MSG_BASE void nsMsgI18NConvertRawBytesToUTF16(const nsCString& inString, + const char* charset, + nsAString& outString); + +/* + * Convert raw bytes in header to UTF-8 + * + * @param inString [IN] Input raw octets + * @param outString [OUT] Output UTF-8 string + */ +NS_MSG_BASE void nsMsgI18NConvertRawBytesToUTF8(const nsCString& inString, + const char* charset, + nsACString& outString); + +// inline forwarders to avoid littering with 'x-imap4-.....' +inline nsresult CopyUTF16toMUTF7(const nsString &aSrc, nsACString& aDest) +{ + return nsMsgI18NConvertFromUnicode("x-imap4-modified-utf7", aSrc, + aDest, true); +} + +inline nsresult CopyMUTF7toUTF16(const nsCString& aSrc, nsAString& aDest) +{ + return nsMsgI18NConvertToUnicode("x-imap4-modified-utf7", aSrc, + aDest, true); +} + +inline nsresult ConvertToUnicode(const char* charset, + const nsCString &aSrc, nsAString& aDest) +{ + return nsMsgI18NConvertToUnicode(charset, aSrc, aDest); +} + +inline nsresult ConvertToUnicode(const char* charset, + const char* aSrc, nsAString& aDest) +{ + return nsMsgI18NConvertToUnicode(charset, nsDependentCString(aSrc), aDest); +} + +inline nsresult ConvertFromUnicode(const char* charset, + const nsString &aSrc, nsACString& aDest) +{ + return nsMsgI18NConvertFromUnicode(charset, aSrc, aDest); +} + +inline void ConvertRawBytesToUTF16(const nsCString& inString, + const char* charset, nsAString& outString) +{ + return nsMsgI18NConvertRawBytesToUTF16(inString, charset, outString); +} + +inline void ConvertRawBytesToUTF16(const char* inString, + const char* charset, nsAString& outString) +{ + return nsMsgI18NConvertRawBytesToUTF16(nsDependentCString(inString), + charset, + outString); +} + +inline void ConvertRawBytesToUTF8(const nsCString& inString, + const char* charset, nsACString& outString) +{ + return nsMsgI18NConvertRawBytesToUTF8(inString, charset, outString); +} + +#endif /* _nsMsgI18N_H_ */ diff --git a/mailnews/base/util/nsMsgIdentity.cpp b/mailnews/base/util/nsMsgIdentity.cpp new file mode 100644 index 000000000..51fabdee1 --- /dev/null +++ b/mailnews/base/util/nsMsgIdentity.cpp @@ -0,0 +1,669 @@ +/* -*- 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/. */ + +#include "msgCore.h" // for pre-compiled headers +#include "nsMsgIdentity.h" +#include "nsIPrefService.h" +#include "nsStringGlue.h" +#include "nsMsgCompCID.h" +#include "nsIRDFService.h" +#include "nsIRDFResource.h" +#include "nsRDFCID.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgFolder.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgAccountManager.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "nsMsgBaseCID.h" +#include "prprf.h" +#include "nsISupportsPrimitives.h" +#include "nsMsgUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsArrayUtils.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +#define REL_FILE_PREF_SUFFIX "-rel" + +NS_IMPL_ISUPPORTS(nsMsgIdentity, + nsIMsgIdentity) + +/* + * accessors for pulling values directly out of preferences + * instead of member variables, etc + */ + +NS_IMETHODIMP +nsMsgIdentity::GetKey(nsACString& aKey) +{ + aKey = mKey; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIdentity::SetKey(const nsACString& identityKey) +{ + mKey = identityKey; + nsresult rv; + nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString branchName; + branchName.AssignLiteral("mail.identity."); + branchName += mKey; + branchName.Append('.'); + rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mPrefBranch)); + if (NS_FAILED(rv)) + return rv; + + rv = prefs->GetBranch("mail.identity.default.", getter_AddRefs(mDefPrefBranch)); + return rv; +} + +nsresult +nsMsgIdentity::GetIdentityName(nsAString& idName) +{ + idName.AssignLiteral(""); + // Try to use "fullname <email>" as the name. + nsresult rv = GetFullAddress(idName); + NS_ENSURE_SUCCESS(rv, rv); + + // If a non-empty label exists, append it. + nsString label; + rv = GetLabel(label); + if (NS_SUCCEEDED(rv) && !label.IsEmpty()) + { // TODO: this should be localizable + idName.AppendLiteral(" ("); + idName.Append(label); + idName.AppendLiteral(")"); + } + + if (!idName.IsEmpty()) + return NS_OK; + + // If we still found nothing to use, use our key. + return ToString(idName); +} + +nsresult +nsMsgIdentity::GetFullAddress(nsAString& fullAddress) +{ + nsAutoString fullName; + nsresult rv = GetFullName(fullName); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString email; + rv = GetEmail(email); + NS_ENSURE_SUCCESS(rv, rv); + + if (fullName.IsEmpty() && email.IsEmpty()) + fullAddress.Truncate(); + else + mozilla::mailnews::MakeMimeAddress(fullName, NS_ConvertASCIItoUTF16(email), fullAddress); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIdentity::ToString(nsAString& aResult) +{ + aResult.AssignLiteral("[nsIMsgIdentity: "); + aResult.Append(NS_ConvertASCIItoUTF16(mKey)); + aResult.AppendLiteral("]"); + return NS_OK; +} + +/* Identity attribute accessors */ + +NS_IMETHODIMP +nsMsgIdentity::GetSignature(nsIFile **sig) +{ + bool gotRelPref; + nsresult rv = NS_GetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", nullptr, gotRelPref, sig, mPrefBranch); + if (NS_SUCCEEDED(rv) && !gotRelPref) + { + rv = NS_SetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", *sig, mPrefBranch); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to write signature file pref."); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIdentity::SetSignature(nsIFile *sig) +{ + nsresult rv = NS_OK; + if (sig) + rv = NS_SetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", sig, mPrefBranch); + return rv; +} + +NS_IMETHODIMP +nsMsgIdentity::ClearAllValues() +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + return mPrefBranch->DeleteBranch(""); +} + +NS_IMPL_IDPREF_STR(EscapedVCard, "escapedVCard") +NS_IMPL_IDPREF_STR(SmtpServerKey, "smtpServer") +NS_IMPL_IDPREF_WSTR(FullName, "fullName") +NS_IMPL_IDPREF_STR(Email, "useremail") +NS_IMPL_IDPREF_WSTR(Label, "label") +NS_IMPL_IDPREF_STR(ReplyTo, "reply_to") +NS_IMPL_IDPREF_WSTR(Organization, "organization") +NS_IMPL_IDPREF_BOOL(ComposeHtml, "compose_html") +NS_IMPL_IDPREF_BOOL(AttachVCard, "attach_vcard") +NS_IMPL_IDPREF_BOOL(AttachSignature, "attach_signature") +NS_IMPL_IDPREF_WSTR(HtmlSigText, "htmlSigText") +NS_IMPL_IDPREF_BOOL(HtmlSigFormat, "htmlSigFormat") + +NS_IMPL_IDPREF_BOOL(AutoQuote, "auto_quote") +NS_IMPL_IDPREF_INT(ReplyOnTop, "reply_on_top") +NS_IMPL_IDPREF_BOOL(SigBottom, "sig_bottom") +NS_IMPL_IDPREF_BOOL(SigOnForward, "sig_on_fwd") +NS_IMPL_IDPREF_BOOL(SigOnReply, "sig_on_reply") + +NS_IMPL_IDPREF_INT(SignatureDate,"sig_date") + +NS_IMPL_IDPREF_BOOL(DoFcc, "fcc") + +NS_IMPL_FOLDERPREF_STR(FccFolder, "fcc_folder", "Sent", nsMsgFolderFlags::SentMail) +NS_IMPL_IDPREF_STR(FccFolderPickerMode, "fcc_folder_picker_mode") +NS_IMPL_IDPREF_BOOL(FccReplyFollowsParent, "fcc_reply_follows_parent") +NS_IMPL_IDPREF_STR(DraftsFolderPickerMode, "drafts_folder_picker_mode") +NS_IMPL_IDPREF_STR(ArchivesFolderPickerMode, "archives_folder_picker_mode") +NS_IMPL_IDPREF_STR(TmplFolderPickerMode, "tmpl_folder_picker_mode") + +NS_IMPL_IDPREF_BOOL(BccSelf, "bcc_self") +NS_IMPL_IDPREF_BOOL(BccOthers, "bcc_other") +NS_IMPL_IDPREF_STR (BccList, "bcc_other_list") + +NS_IMPL_IDPREF_BOOL(SuppressSigSep, "suppress_signature_separator") + +NS_IMPL_IDPREF_BOOL(DoCc, "doCc") +NS_IMPL_IDPREF_STR (DoCcList, "doCcList") + +NS_IMETHODIMP +nsMsgIdentity::GetDoBcc(bool *aValue) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = mPrefBranch->GetBoolPref("doBcc", aValue); + if (NS_SUCCEEDED(rv)) + return rv; + + bool bccSelf = false; + GetBccSelf(&bccSelf); + + bool bccOthers = false; + GetBccOthers(&bccOthers); + + nsCString others; + GetBccList(others); + + *aValue = bccSelf || (bccOthers && !others.IsEmpty()); + + return SetDoBcc(*aValue); +} + +NS_IMETHODIMP +nsMsgIdentity::SetDoBcc(bool aValue) +{ + return SetBoolAttribute("doBcc", aValue); +} + +NS_IMETHODIMP +nsMsgIdentity::GetDoBccList(nsACString& aValue) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsCString val; + nsresult rv = mPrefBranch->GetCharPref("doBccList", getter_Copies(val)); + aValue = val; + if (NS_SUCCEEDED(rv)) + return rv; + + bool bccSelf = false; + rv = GetBccSelf(&bccSelf); + NS_ENSURE_SUCCESS(rv,rv); + + if (bccSelf) + GetEmail(aValue); + + bool bccOthers = false; + rv = GetBccOthers(&bccOthers); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString others; + rv = GetBccList(others); + NS_ENSURE_SUCCESS(rv,rv); + + if (bccOthers && !others.IsEmpty()) { + if (bccSelf) + aValue.AppendLiteral(","); + aValue.Append(others); + } + + return SetDoBccList(aValue); +} + +NS_IMETHODIMP +nsMsgIdentity::SetDoBccList(const nsACString& aValue) +{ + return SetCharAttribute("doBccList", aValue); +} + +NS_IMPL_FOLDERPREF_STR(DraftFolder, "draft_folder", "Drafts", nsMsgFolderFlags::Drafts) +NS_IMPL_FOLDERPREF_STR(ArchiveFolder, "archive_folder", "Archives", nsMsgFolderFlags::Archive) +NS_IMPL_FOLDERPREF_STR(StationeryFolder, "stationery_folder", "Templates", nsMsgFolderFlags::Templates) + +NS_IMPL_IDPREF_BOOL(ArchiveEnabled, "archive_enabled") +NS_IMPL_IDPREF_INT(ArchiveGranularity, "archive_granularity") +NS_IMPL_IDPREF_BOOL(ArchiveKeepFolderStructure, "archive_keep_folder_structure") + +NS_IMPL_IDPREF_BOOL(ShowSaveMsgDlg, "showSaveMsgDlg") +NS_IMPL_IDPREF_STR (DirectoryServer, "directoryServer") +NS_IMPL_IDPREF_BOOL(OverrideGlobalPref, "overrideGlobal_Pref") +NS_IMPL_IDPREF_BOOL(AutocompleteToMyDomain, "autocompleteToMyDomain") + +NS_IMPL_IDPREF_BOOL(Valid, "valid") + +nsresult +nsMsgIdentity::getFolderPref(const char *prefname, nsCString& retval, + const char *folderName, uint32_t folderflag) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = mPrefBranch->GetCharPref(prefname, getter_Copies(retval)); + if (NS_SUCCEEDED(rv) && !retval.IsEmpty()) { + // get the corresponding RDF resource + // RDF will create the folder resource if it doesn't already exist + nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv)); + if (NS_FAILED(rv)) return rv; + nsCOMPtr<nsIRDFResource> resource; + rdf->GetResource(retval, getter_AddRefs(resource)); + + nsCOMPtr <nsIMsgFolder> folderResource = do_QueryInterface(resource); + if (folderResource) + { + // don't check validity of folder - caller will handle creating it + nsCOMPtr<nsIMsgIncomingServer> server; + //make sure that folder hierarchy is built so that legitimate parent-child relationship is established + folderResource->GetServer(getter_AddRefs(server)); + if (server) + { + nsCOMPtr<nsIMsgFolder> rootFolder; + nsCOMPtr<nsIMsgFolder> deferredToRootFolder; + server->GetRootFolder(getter_AddRefs(rootFolder)); + server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder)); + // check if we're using a deferred account - if not, use the uri; + // otherwise, fall through to code that will fix this pref. + if (rootFolder == deferredToRootFolder) + { + nsCOMPtr <nsIMsgFolder> msgFolder; + rv = server->GetMsgFolderFromURI(folderResource, retval, getter_AddRefs(msgFolder)); + return NS_SUCCEEDED(rv) ? msgFolder->GetURI(retval) : rv; + } + } + } + } + + // if the server doesn't exist, fall back to the default pref. + rv = mDefPrefBranch->GetCharPref(prefname, getter_Copies(retval)); + if (NS_SUCCEEDED(rv) && !retval.IsEmpty()) + return setFolderPref(prefname, retval, folderflag); + + // here I think we need to create a uri for the folder on the + // default server for this identity. + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIArray> servers; + rv = accountManager->GetServersForIdentity(this, getter_AddRefs(servers)); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(servers, 0, &rv)); + if (NS_SUCCEEDED(rv)) + { + bool defaultToServer; + server->GetDefaultCopiesAndFoldersPrefsToServer(&defaultToServer); + // if we should default to special folders on the server, + // use the local folders server + if (!defaultToServer) + { + rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCOMPtr<nsIMsgFolder> rootFolder; + // this will get the deferred to server's root folder, if "server" + // is deferred, e.g., using the pop3 global inbox. + rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + if (rootFolder) + { + rv = rootFolder->GetURI(retval); + NS_ENSURE_SUCCESS(rv, rv); + retval.Append('/'); + retval.Append(folderName); + return setFolderPref(prefname, retval, folderflag); + } + } + // if there are no servers for this identity, return generic failure. + return NS_ERROR_FAILURE; +} + +nsresult +nsMsgIdentity::setFolderPref(const char *prefname, const nsACString& value, uint32_t folderflag) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsCString oldpref; + nsresult rv; + nsCOMPtr<nsIRDFResource> res; + nsCOMPtr<nsIMsgFolder> folder; + nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv)); + + if (folderflag == nsMsgFolderFlags::SentMail) + { + // Clear the temporary return receipt filter so that the new filter + // rule can be recreated (by ConfigureTemporaryFilters()). + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIArray> servers; + rv = accountManager->GetServersForIdentity(this, getter_AddRefs(servers)); + NS_ENSURE_SUCCESS(rv,rv); + uint32_t cnt = 0; + servers->GetLength(&cnt); + if (cnt > 0) + { + nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(servers, 0, &rv)); + if (NS_SUCCEEDED(rv)) + server->ClearTemporaryReturnReceiptsFilter(); // okay to fail; no need to check for return code + } + } + + // get the old folder, and clear the special folder flag on it + rv = mPrefBranch->GetCharPref(prefname, getter_Copies(oldpref)); + if (NS_SUCCEEDED(rv) && !oldpref.IsEmpty()) + { + rv = rdf->GetResource(oldpref, getter_AddRefs(res)); + if (NS_SUCCEEDED(rv) && res) + { + folder = do_QueryInterface(res, &rv); + if (NS_SUCCEEDED(rv)) + rv = folder->ClearFlag(folderflag); + } + } + + // set the new folder, and set the special folder flags on it + rv = SetCharAttribute(prefname, value); + if (NS_SUCCEEDED(rv) && !value.IsEmpty()) + { + rv = rdf->GetResource(value, getter_AddRefs(res)); + if (NS_SUCCEEDED(rv) && res) + { + folder = do_QueryInterface(res, &rv); + if (NS_SUCCEEDED(rv)) + rv = folder->SetFlag(folderflag); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgIdentity::SetUnicharAttribute(const char *aName, const nsAString& val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + if (!val.IsEmpty()) { + nsresult rv; + nsCOMPtr<nsISupportsString> supportsString( + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + rv = supportsString->SetData(val); + if (NS_SUCCEEDED(rv)) + rv = mPrefBranch->SetComplexValue(aName, + NS_GET_IID(nsISupportsString), + supportsString); + return rv; + } + + mPrefBranch->ClearUserPref(aName); + return NS_OK; +} + +NS_IMETHODIMP nsMsgIdentity::GetUnicharAttribute(const char *aName, nsAString& val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<nsISupportsString> supportsString; + if (NS_FAILED(mPrefBranch->GetComplexValue(aName, + NS_GET_IID(nsISupportsString), + getter_AddRefs(supportsString)))) + mDefPrefBranch->GetComplexValue(aName, + NS_GET_IID(nsISupportsString), + getter_AddRefs(supportsString)); + + if (supportsString) + supportsString->GetData(val); + else + val.Truncate(); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgIdentity::SetCharAttribute(const char *aName, const nsACString& val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + if (!val.IsEmpty()) + return mPrefBranch->SetCharPref(aName, nsCString(val).get()); + + mPrefBranch->ClearUserPref(aName); + return NS_OK; +} + +NS_IMETHODIMP nsMsgIdentity::GetCharAttribute(const char *aName, nsACString& val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsCString tmpVal; + if (NS_FAILED(mPrefBranch->GetCharPref(aName, getter_Copies(tmpVal)))) + mDefPrefBranch->GetCharPref(aName, getter_Copies(tmpVal)); + val = tmpVal; + return NS_OK; +} + +NS_IMETHODIMP nsMsgIdentity::SetBoolAttribute(const char *aName, bool val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + return mPrefBranch->SetBoolPref(aName, val); +} + +NS_IMETHODIMP nsMsgIdentity::GetBoolAttribute(const char *aName, bool *val) +{ + NS_ENSURE_ARG_POINTER(val); + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + *val = false; + + if (NS_FAILED(mPrefBranch->GetBoolPref(aName, val))) + mDefPrefBranch->GetBoolPref(aName, val); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgIdentity::SetIntAttribute(const char *aName, int32_t val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + return mPrefBranch->SetIntPref(aName, val); +} + +NS_IMETHODIMP nsMsgIdentity::GetIntAttribute(const char *aName, int32_t *val) +{ + NS_ENSURE_ARG_POINTER(val); + + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + *val = 0; + + if (NS_FAILED(mPrefBranch->GetIntPref(aName, val))) + mDefPrefBranch->GetIntPref(aName, val); + + return NS_OK; +} + +#define COPY_IDENTITY_FILE_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \ + { \ + nsresult macro_rv; \ + nsCOMPtr <nsIFile>macro_spec; \ + macro_rv = SRC_ID->MACRO_GETTER(getter_AddRefs(macro_spec)); \ + if (NS_SUCCEEDED(macro_rv)) \ + this->MACRO_SETTER(macro_spec); \ + } + +#define COPY_IDENTITY_INT_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \ + { \ + nsresult macro_rv; \ + int32_t macro_oldInt; \ + macro_rv = SRC_ID->MACRO_GETTER(¯o_oldInt); \ + if (NS_SUCCEEDED(macro_rv)) \ + this->MACRO_SETTER(macro_oldInt); \ + } + +#define COPY_IDENTITY_BOOL_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \ + { \ + nsresult macro_rv; \ + bool macro_oldBool; \ + macro_rv = SRC_ID->MACRO_GETTER(¯o_oldBool); \ + if (NS_SUCCEEDED(macro_rv)) \ + this->MACRO_SETTER(macro_oldBool); \ + } + +#define COPY_IDENTITY_STR_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \ + { \ + nsCString macro_oldStr; \ + nsresult macro_rv; \ + macro_rv = SRC_ID->MACRO_GETTER(macro_oldStr); \ + if (NS_SUCCEEDED(macro_rv)) { \ + this->MACRO_SETTER(macro_oldStr); \ + } \ + } + +#define COPY_IDENTITY_WSTR_VALUE(SRC_ID,MACRO_GETTER,MACRO_SETTER) \ + { \ + nsString macro_oldStr; \ + nsresult macro_rv; \ + macro_rv = SRC_ID->MACRO_GETTER(macro_oldStr); \ + if (NS_SUCCEEDED(macro_rv)) { \ + this->MACRO_SETTER(macro_oldStr); \ + } \ + } + +NS_IMETHODIMP +nsMsgIdentity::Copy(nsIMsgIdentity *identity) +{ + NS_ENSURE_ARG_POINTER(identity); + + COPY_IDENTITY_BOOL_VALUE(identity,GetComposeHtml,SetComposeHtml) + COPY_IDENTITY_STR_VALUE(identity,GetEmail,SetEmail) + COPY_IDENTITY_WSTR_VALUE(identity,GetLabel,SetLabel) + COPY_IDENTITY_STR_VALUE(identity,GetReplyTo,SetReplyTo) + COPY_IDENTITY_WSTR_VALUE(identity,GetFullName,SetFullName) + COPY_IDENTITY_WSTR_VALUE(identity,GetOrganization,SetOrganization) + COPY_IDENTITY_STR_VALUE(identity,GetDraftFolder,SetDraftFolder) + COPY_IDENTITY_STR_VALUE(identity,GetArchiveFolder,SetArchiveFolder) + COPY_IDENTITY_STR_VALUE(identity,GetFccFolder,SetFccFolder) + COPY_IDENTITY_BOOL_VALUE(identity,GetFccReplyFollowsParent, + SetFccReplyFollowsParent) + COPY_IDENTITY_STR_VALUE(identity,GetStationeryFolder,SetStationeryFolder) + COPY_IDENTITY_BOOL_VALUE(identity,GetArchiveEnabled,SetArchiveEnabled) + COPY_IDENTITY_INT_VALUE(identity,GetArchiveGranularity, + SetArchiveGranularity) + COPY_IDENTITY_BOOL_VALUE(identity,GetArchiveKeepFolderStructure, + SetArchiveKeepFolderStructure) + COPY_IDENTITY_BOOL_VALUE(identity,GetAttachSignature,SetAttachSignature) + COPY_IDENTITY_FILE_VALUE(identity,GetSignature,SetSignature) + COPY_IDENTITY_WSTR_VALUE(identity,GetHtmlSigText,SetHtmlSigText) + COPY_IDENTITY_BOOL_VALUE(identity,GetHtmlSigFormat,SetHtmlSigFormat) + COPY_IDENTITY_BOOL_VALUE(identity,GetAutoQuote,SetAutoQuote) + COPY_IDENTITY_INT_VALUE(identity,GetReplyOnTop,SetReplyOnTop) + COPY_IDENTITY_BOOL_VALUE(identity,GetSigBottom,SetSigBottom) + COPY_IDENTITY_BOOL_VALUE(identity,GetSigOnForward,SetSigOnForward) + COPY_IDENTITY_BOOL_VALUE(identity,GetSigOnReply,SetSigOnReply) + COPY_IDENTITY_INT_VALUE(identity,GetSignatureDate,SetSignatureDate) + COPY_IDENTITY_BOOL_VALUE(identity,GetAttachVCard,SetAttachVCard) + COPY_IDENTITY_STR_VALUE(identity,GetEscapedVCard,SetEscapedVCard) + COPY_IDENTITY_STR_VALUE(identity,GetSmtpServerKey,SetSmtpServerKey) + COPY_IDENTITY_BOOL_VALUE(identity,GetSuppressSigSep,SetSuppressSigSep) + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIdentity::GetRequestReturnReceipt(bool *aVal) +{ + NS_ENSURE_ARG_POINTER(aVal); + + bool useCustomPrefs = false; + nsresult rv = GetBoolAttribute("use_custom_prefs", &useCustomPrefs); + NS_ENSURE_SUCCESS(rv, rv); + if (useCustomPrefs) + return GetBoolAttribute("request_return_receipt_on", aVal); + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return prefs->GetBoolPref("mail.receipt.request_return_receipt_on", aVal); +} + +NS_IMETHODIMP +nsMsgIdentity::GetReceiptHeaderType(int32_t *aType) +{ + NS_ENSURE_ARG_POINTER(aType); + + bool useCustomPrefs = false; + nsresult rv = GetBoolAttribute("use_custom_prefs", &useCustomPrefs); + NS_ENSURE_SUCCESS(rv, rv); + if (useCustomPrefs) + return GetIntAttribute("request_receipt_header_type", aType); + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return prefs->GetIntPref("mail.receipt.request_header_type", aType); +} + +NS_IMETHODIMP +nsMsgIdentity::GetRequestDSN(bool *aVal) +{ + NS_ENSURE_ARG_POINTER(aVal); + + bool useCustomPrefs = false; + nsresult rv = GetBoolAttribute("dsn_use_custom_prefs", &useCustomPrefs); + NS_ENSURE_SUCCESS(rv, rv); + if (useCustomPrefs) + return GetBoolAttribute("dsn_always_request_on", aVal); + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return prefs->GetBoolPref("mail.dsn.always_request_on", aVal); +} diff --git a/mailnews/base/util/nsMsgIdentity.h b/mailnews/base/util/nsMsgIdentity.h new file mode 100644 index 000000000..7b17ae3f6 --- /dev/null +++ b/mailnews/base/util/nsMsgIdentity.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; 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 nsMsgIdentity_h___ +#define nsMsgIdentity_h___ + +#include "nsIMsgIdentity.h" +#include "nsIPrefBranch.h" +#include "msgCore.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" + +class NS_MSG_BASE nsMsgIdentity final : public nsIMsgIdentity +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGIDENTITY + +private: + ~nsMsgIdentity() {} + nsCString mKey; + nsCOMPtr<nsIPrefBranch> mPrefBranch; + nsCOMPtr<nsIPrefBranch> mDefPrefBranch; + +protected: + nsresult getFolderPref(const char *pref, nsCString&, const char *, uint32_t); + nsresult setFolderPref(const char *pref, const nsACString&, uint32_t); +}; + + +#define NS_IMPL_IDPREF_STR(_postfix, _prefname) \ +NS_IMETHODIMP \ +nsMsgIdentity::Get##_postfix(nsACString& retval) \ +{ \ + return GetCharAttribute(_prefname, retval); \ +} \ +NS_IMETHODIMP \ +nsMsgIdentity::Set##_postfix(const nsACString& value) \ +{ \ + return SetCharAttribute(_prefname, value); \ +} + +#define NS_IMPL_IDPREF_WSTR(_postfix, _prefname) \ +NS_IMETHODIMP \ +nsMsgIdentity::Get##_postfix(nsAString& retval) \ +{ \ + return GetUnicharAttribute(_prefname, retval); \ +} \ +NS_IMETHODIMP \ +nsMsgIdentity::Set##_postfix(const nsAString& value) \ +{ \ + return SetUnicharAttribute(_prefname, value); \ +} + +#define NS_IMPL_IDPREF_BOOL(_postfix, _prefname) \ +NS_IMETHODIMP \ +nsMsgIdentity::Get##_postfix(bool *retval) \ +{ \ + return GetBoolAttribute(_prefname, retval); \ +} \ +NS_IMETHODIMP \ +nsMsgIdentity::Set##_postfix(bool value) \ +{ \ + return mPrefBranch->SetBoolPref(_prefname, value); \ +} + +#define NS_IMPL_IDPREF_INT(_postfix, _prefname) \ +NS_IMETHODIMP \ +nsMsgIdentity::Get##_postfix(int32_t *retval) \ +{ \ + return GetIntAttribute(_prefname, retval); \ +} \ +NS_IMETHODIMP \ +nsMsgIdentity::Set##_postfix(int32_t value) \ +{ \ + return mPrefBranch->SetIntPref(_prefname, value); \ +} + +#define NS_IMPL_FOLDERPREF_STR(_postfix, _prefname, _foldername, _flag) \ +NS_IMETHODIMP \ +nsMsgIdentity::Get##_postfix(nsACString& retval) \ +{ \ + nsresult rv; \ + nsCString folderPref; \ + rv = getFolderPref(_prefname, folderPref, _foldername, _flag); \ + retval = folderPref; \ + return rv; \ +} \ +NS_IMETHODIMP \ +nsMsgIdentity::Set##_postfix(const nsACString& value) \ +{ \ + return setFolderPref(_prefname, value, _flag); \ +} + +#endif /* nsMsgIdentity_h___ */ diff --git a/mailnews/base/util/nsMsgIncomingServer.cpp b/mailnews/base/util/nsMsgIncomingServer.cpp new file mode 100644 index 000000000..a1e897f12 --- /dev/null +++ b/mailnews/base/util/nsMsgIncomingServer.cpp @@ -0,0 +1,2292 @@ +/* -*- 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 "nsMsgIncomingServer.h" +#include "nscore.h" +#include "plstr.h" +#include "prmem.h" +#include "prprf.h" + +#include "nsIServiceManager.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsISupportsPrimitives.h" + +#include "nsIMsgBiffManager.h" +#include "nsMsgBaseCID.h" +#include "nsMsgDBCID.h" +#include "nsIMsgFolder.h" +#include "nsIMsgFolderCache.h" +#include "nsIMsgPluggableStore.h" +#include "nsIMsgFolderCacheElement.h" +#include "nsIMsgWindow.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgProtocolInfo.h" +#include "nsIPrefService.h" +#include "nsIRelativeFilePref.h" +#include "nsIDocShell.h" +#include "nsIAuthPrompt.h" +#include "nsNetUtil.h" +#include "nsIWindowWatcher.h" +#include "nsIStringBundle.h" +#include "nsIMsgHdr.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILoginInfo.h" +#include "nsILoginManager.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgMdnGenerator.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgUtils.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgSearchTerm.h" +#include "nsAppDirectoryServiceDefs.h" +#include "mozilla/Services.h" +#include "nsIMsgFilter.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" + +#define PORT_NOT_SET -1 + +nsMsgIncomingServer::nsMsgIncomingServer(): + m_rootFolder(nullptr), + m_downloadedHdrs(50), + m_numMsgsDownloaded(0), + m_biffState(nsIMsgFolder::nsMsgBiffState_Unknown), + m_serverBusy(false), + m_canHaveFilters(true), + m_displayStartupPage(true), + mPerformingBiff(false) +{ +} + +nsMsgIncomingServer::~nsMsgIncomingServer() +{ +} + +NS_IMPL_ISUPPORTS(nsMsgIncomingServer, nsIMsgIncomingServer, + nsISupportsWeakReference) + +NS_IMETHODIMP +nsMsgIncomingServer::SetServerBusy(bool aServerBusy) +{ + m_serverBusy = aServerBusy; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetServerBusy(bool * aServerBusy) +{ + NS_ENSURE_ARG_POINTER(aServerBusy); + *aServerBusy = m_serverBusy; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetKey(nsACString& serverKey) +{ + serverKey = m_serverKey; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetKey(const nsACString& serverKey) +{ + m_serverKey.Assign(serverKey); + + // in order to actually make use of the key, we need the prefs + nsresult rv; + nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString branchName; + branchName.AssignLiteral("mail.server."); + branchName.Append(m_serverKey); + branchName.Append('.'); + rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mPrefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + return prefs->GetBranch("mail.server.default.", getter_AddRefs(mDefPrefBranch)); +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetRootFolder(nsIMsgFolder * aRootFolder) +{ + m_rootFolder = aRootFolder; + return NS_OK; +} + +// this will return the root folder of this account, +// even if this server is deferred. +NS_IMETHODIMP +nsMsgIncomingServer::GetRootFolder(nsIMsgFolder * *aRootFolder) +{ + NS_ENSURE_ARG_POINTER(aRootFolder); + if (!m_rootFolder) + { + nsresult rv = CreateRootFolder(); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_IF_ADDREF(*aRootFolder = m_rootFolder); + return NS_OK; +} + +// this will return the root folder of the deferred to account, +// if this server is deferred. +NS_IMETHODIMP +nsMsgIncomingServer::GetRootMsgFolder(nsIMsgFolder **aRootMsgFolder) +{ + return GetRootFolder(aRootMsgFolder); +} + +NS_IMETHODIMP +nsMsgIncomingServer::PerformExpand(nsIMsgWindow *aMsgWindow) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::VerifyLogon(nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow, + nsIURI **aURL) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow) +{ + //This has to be implemented in the derived class, but in case someone doesn't implement it + //just return not implemented. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetNewMessages(nsIMsgFolder *aFolder, nsIMsgWindow *aMsgWindow, + nsIUrlListener *aUrlListener) +{ + NS_ENSURE_ARG_POINTER(aFolder); + return aFolder->GetNewMessages(aMsgWindow, aUrlListener); +} + +NS_IMETHODIMP nsMsgIncomingServer::GetPerformingBiff(bool *aPerformingBiff) +{ + NS_ENSURE_ARG_POINTER(aPerformingBiff); + *aPerformingBiff = mPerformingBiff; + return NS_OK; +} + +NS_IMETHODIMP nsMsgIncomingServer::SetPerformingBiff(bool aPerformingBiff) +{ + mPerformingBiff = aPerformingBiff; + return NS_OK; +} + +NS_IMPL_GETSET(nsMsgIncomingServer, BiffState, uint32_t, m_biffState) + +NS_IMETHODIMP nsMsgIncomingServer::WriteToFolderCache(nsIMsgFolderCache *folderCache) +{ + nsresult rv = NS_OK; + if (m_rootFolder) + { + nsCOMPtr <nsIMsgFolder> msgFolder = do_QueryInterface(m_rootFolder, &rv); + if (NS_SUCCEEDED(rv) && msgFolder) + rv = msgFolder->WriteToFolderCache(folderCache, true /* deep */); + } + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::Shutdown() +{ + nsresult rv = CloseCachedConnections(); + mFilterPlugin = nullptr; + NS_ENSURE_SUCCESS(rv,rv); + + if (mFilterList) + { + // close the filter log stream + rv = mFilterList->SetLogStream(nullptr); + NS_ENSURE_SUCCESS(rv,rv); + mFilterList = nullptr; + } + + if (mSpamSettings) + { + // close the spam log stream + rv = mSpamSettings->SetLogStream(nullptr); + NS_ENSURE_SUCCESS(rv,rv); + mSpamSettings = nullptr; + } + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::CloseCachedConnections() +{ + // derived class should override if they cache connections. + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetDownloadMessagesAtStartup(bool *getMessagesAtStartup) +{ + // derived class should override if they need to do this. + *getMessagesAtStartup = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetCanHaveFilters(bool *canHaveFilters) +{ + NS_ENSURE_ARG_POINTER(canHaveFilters); + *canHaveFilters = m_canHaveFilters; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetCanHaveFilters(bool aCanHaveFilters) +{ + m_canHaveFilters = aCanHaveFilters; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetCanBeDefaultServer(bool *canBeDefaultServer) +{ + // derived class should override if they need to do this. + *canBeDefaultServer = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetCanSearchMessages(bool *canSearchMessages) +{ + // derived class should override if they need to do this. + NS_ENSURE_ARG_POINTER(canSearchMessages); + *canSearchMessages = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetCanCompactFoldersOnServer(bool *canCompactFoldersOnServer) +{ + // derived class should override if they need to do this. + NS_ENSURE_ARG_POINTER(canCompactFoldersOnServer); + *canCompactFoldersOnServer = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetCanUndoDeleteOnServer(bool *canUndoDeleteOnServer) +{ + // derived class should override if they need to do this. + NS_ENSURE_ARG_POINTER(canUndoDeleteOnServer); + *canUndoDeleteOnServer = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetCanEmptyTrashOnExit(bool *canEmptyTrashOnExit) +{ + // derived class should override if they need to do this. + NS_ENSURE_ARG_POINTER(canEmptyTrashOnExit); + *canEmptyTrashOnExit = true; + return NS_OK; +} + +// construct <localStoreType>://[<username>@]<hostname +NS_IMETHODIMP +nsMsgIncomingServer::GetServerURI(nsACString& aResult) +{ + nsresult rv; + rv = GetLocalStoreType(aResult); + NS_ENSURE_SUCCESS(rv, rv); + aResult.AppendLiteral("://"); + + nsCString username; + rv = GetUsername(username); + if (NS_SUCCEEDED(rv) && !username.IsEmpty()) { + nsCString escapedUsername; + MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername); + // not all servers have a username + aResult.Append(escapedUsername); + aResult.Append('@'); + } + + nsCString hostname; + rv = GetHostName(hostname); + if (NS_SUCCEEDED(rv) && !hostname.IsEmpty()) { + nsCString escapedHostname; + MsgEscapeString(hostname, nsINetUtil::ESCAPE_URL_PATH, escapedHostname); + // not all servers have a hostname + aResult.Append(escapedHostname); + } + return NS_OK; +} + +// helper routine to create local folder on disk, if it doesn't exist. +nsresult +nsMsgIncomingServer::CreateLocalFolder(const nsAString& folderName) +{ + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgFolder> child; + rv = rootFolder->GetChildNamed(folderName, getter_AddRefs(child)); + if (child) + return NS_OK; + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->CreateFolder(rootFolder, folderName, getter_AddRefs(child)); +} + +nsresult +nsMsgIncomingServer::CreateRootFolder() +{ + nsresult rv; + // get the URI from the incoming server + nsCString serverUri; + rv = GetServerURI(serverUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // get the corresponding RDF resource + // RDF will create the server resource if it doesn't already exist + nsCOMPtr<nsIRDFResource> serverResource; + rv = rdf->GetResource(serverUri, getter_AddRefs(serverResource)); + NS_ENSURE_SUCCESS(rv, rv); + + // make incoming server know about its root server folder so we + // can find sub-folders given an incoming server. + m_rootFolder = do_QueryInterface(serverResource, &rv); + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetBoolValue(const char *prefname, + bool *val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + NS_ENSURE_ARG_POINTER(val); + *val = false; + + if (NS_FAILED(mPrefBranch->GetBoolPref(prefname, val))) + mDefPrefBranch->GetBoolPref(prefname, val); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetBoolValue(const char *prefname, + bool val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + bool defaultValue; + nsresult rv = mDefPrefBranch->GetBoolPref(prefname, &defaultValue); + + if (NS_SUCCEEDED(rv) && val == defaultValue) + mPrefBranch->ClearUserPref(prefname); + else + rv = mPrefBranch->SetBoolPref(prefname, val); + + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetIntValue(const char *prefname, + int32_t *val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + NS_ENSURE_ARG_POINTER(val); + *val = 0; + + if (NS_FAILED(mPrefBranch->GetIntPref(prefname, val))) + mDefPrefBranch->GetIntPref(prefname, val); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetFileValue(const char* aRelPrefName, + const char* aAbsPrefName, + nsIFile** aLocalFile) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + // Get the relative first + nsCOMPtr<nsIRelativeFilePref> relFilePref; + nsresult rv = mPrefBranch->GetComplexValue(aRelPrefName, + NS_GET_IID(nsIRelativeFilePref), + getter_AddRefs(relFilePref)); + if (relFilePref) { + rv = relFilePref->GetFile(aLocalFile); + NS_ASSERTION(*aLocalFile, "An nsIRelativeFilePref has no file."); + if (NS_SUCCEEDED(rv)) + (*aLocalFile)->Normalize(); + } else { + rv = mPrefBranch->GetComplexValue(aAbsPrefName, + NS_GET_IID(nsIFile), + reinterpret_cast<void**>(aLocalFile)); + if (NS_FAILED(rv)) + return rv; + + rv = NS_NewRelativeFilePref(*aLocalFile, + NS_LITERAL_CSTRING(NS_APP_USER_PROFILE_50_DIR), + getter_AddRefs(relFilePref)); + if (relFilePref) + rv = mPrefBranch->SetComplexValue(aRelPrefName, + NS_GET_IID(nsIRelativeFilePref), + relFilePref); + } + + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetFileValue(const char* aRelPrefName, + const char* aAbsPrefName, + nsIFile* aLocalFile) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + // Write the relative path. + nsCOMPtr<nsIRelativeFilePref> relFilePref; + NS_NewRelativeFilePref(aLocalFile, + NS_LITERAL_CSTRING(NS_APP_USER_PROFILE_50_DIR), + getter_AddRefs(relFilePref)); + if (relFilePref) { + nsresult rv = mPrefBranch->SetComplexValue(aRelPrefName, + NS_GET_IID(nsIRelativeFilePref), + relFilePref); + if (NS_FAILED(rv)) + return rv; + } + return mPrefBranch->SetComplexValue(aAbsPrefName, NS_GET_IID(nsIFile), aLocalFile); +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetIntValue(const char *prefname, + int32_t val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + int32_t defaultVal; + nsresult rv = mDefPrefBranch->GetIntPref(prefname, &defaultVal); + + if (NS_SUCCEEDED(rv) && defaultVal == val) + mPrefBranch->ClearUserPref(prefname); + else + rv = mPrefBranch->SetIntPref(prefname, val); + + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetCharValue(const char *prefname, + nsACString& val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsCString tmpVal; + if (NS_FAILED(mPrefBranch->GetCharPref(prefname, getter_Copies(tmpVal)))) + mDefPrefBranch->GetCharPref(prefname, getter_Copies(tmpVal)); + val = tmpVal; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetUnicharValue(const char *prefname, + nsAString& val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<nsISupportsString> supportsString; + if (NS_FAILED(mPrefBranch->GetComplexValue(prefname, + NS_GET_IID(nsISupportsString), + getter_AddRefs(supportsString)))) + mDefPrefBranch->GetComplexValue(prefname, + NS_GET_IID(nsISupportsString), + getter_AddRefs(supportsString)); + + if (supportsString) + return supportsString->GetData(val); + val.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetCharValue(const char *prefname, + const nsACString& val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + if (val.IsEmpty()) { + mPrefBranch->ClearUserPref(prefname); + return NS_OK; + } + + nsCString defaultVal; + nsresult rv = mDefPrefBranch->GetCharPref(prefname, getter_Copies(defaultVal)); + + if (NS_SUCCEEDED(rv) && defaultVal.Equals(val)) + mPrefBranch->ClearUserPref(prefname); + else + rv = mPrefBranch->SetCharPref(prefname, nsCString(val).get()); + + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetUnicharValue(const char *prefname, + const nsAString& val) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + if (val.IsEmpty()) { + mPrefBranch->ClearUserPref(prefname); + return NS_OK; + } + + nsCOMPtr<nsISupportsString> supportsString; + nsresult rv = mDefPrefBranch->GetComplexValue(prefname, + NS_GET_IID(nsISupportsString), + getter_AddRefs(supportsString)); + nsString defaultVal; + if (NS_SUCCEEDED(rv) && + NS_SUCCEEDED(supportsString->GetData(defaultVal)) && + defaultVal.Equals(val)) + mPrefBranch->ClearUserPref(prefname); + else { + supportsString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (supportsString) { + supportsString->SetData(val); + rv = mPrefBranch->SetComplexValue(prefname, + NS_GET_IID(nsISupportsString), + supportsString); + } + } + + return rv; +} + +// pretty name is the display name to show to the user +NS_IMETHODIMP +nsMsgIncomingServer::GetPrettyName(nsAString& retval) +{ + nsresult rv = GetUnicharValue("name", retval); + NS_ENSURE_SUCCESS(rv, rv); + + // if there's no name, then just return the hostname + return retval.IsEmpty() ? GetConstructedPrettyName(retval) : rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetPrettyName(const nsAString& value) +{ + SetUnicharValue("name", value); + nsCOMPtr<nsIMsgFolder> rootFolder; + GetRootFolder(getter_AddRefs(rootFolder)); + if (rootFolder) + rootFolder->SetPrettyName(value); + return NS_OK; +} + + +// construct the pretty name to show to the user if they haven't +// specified one. This should be overridden for news and mail. +NS_IMETHODIMP +nsMsgIncomingServer::GetConstructedPrettyName(nsAString& retval) +{ + nsCString username; + nsresult rv = GetUsername(username); + NS_ENSURE_SUCCESS(rv, rv); + if (!username.IsEmpty()) { + CopyASCIItoUTF16(username, retval); + retval.AppendLiteral(" on "); + } + + nsCString hostname; + rv = GetHostName(hostname); + NS_ENSURE_SUCCESS(rv, rv); + + retval.Append(NS_ConvertASCIItoUTF16(hostname)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::ToString(nsAString& aResult) +{ + aResult.AssignLiteral("[nsIMsgIncomingServer: "); + aResult.Append(NS_ConvertASCIItoUTF16(m_serverKey)); + aResult.AppendLiteral("]"); + return NS_OK; +} + +NS_IMETHODIMP nsMsgIncomingServer::SetPassword(const nsACString& aPassword) +{ + m_password = aPassword; + return NS_OK; +} + +NS_IMETHODIMP nsMsgIncomingServer::GetPassword(nsACString& aPassword) +{ + aPassword = m_password; + return NS_OK; +} + +NS_IMETHODIMP nsMsgIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff) +{ + NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff); + *aServerRequiresPasswordForBiff = true; + return NS_OK; +} + +// This sets m_password if we find a password in the pw mgr. +nsresult nsMsgIncomingServer::GetPasswordWithoutUI() +{ + nsresult rv; + nsCOMPtr<nsILoginManager> loginMgr(do_GetService(NS_LOGINMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current server URI + nsCString currServerUri; + rv = GetLocalStoreType(currServerUri); + NS_ENSURE_SUCCESS(rv, rv); + + currServerUri.AppendLiteral("://"); + + nsCString temp; + rv = GetHostName(temp); + NS_ENSURE_SUCCESS(rv, rv); + + currServerUri.Append(temp); + + NS_ConvertUTF8toUTF16 currServer(currServerUri); + + uint32_t numLogins = 0; + nsILoginInfo** logins = nullptr; + rv = loginMgr->FindLogins(&numLogins, currServer, EmptyString(), + currServer, &logins); + + // Login manager can produce valid fails, e.g. NS_ERROR_ABORT when a user + // cancels the master password dialog. Therefore handle that here, but don't + // warn about it. + if (NS_FAILED(rv)) + return rv; + + // Don't abort here, if we didn't find any or failed, then we'll just have + // to prompt. + if (numLogins > 0) + { + nsCString serverCUsername; + rv = GetUsername(serverCUsername); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertUTF8toUTF16 serverUsername(serverCUsername); + + nsString username; + for (uint32_t i = 0; i < numLogins; ++i) + { + rv = logins[i]->GetUsername(username); + NS_ENSURE_SUCCESS(rv, rv); + + if (username.Equals(serverUsername)) + { + nsString password; + rv = logins[i]->GetPassword(password); + NS_ENSURE_SUCCESS(rv, rv); + + m_password = NS_LossyConvertUTF16toASCII(password); + break; + } + } + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numLogins, logins); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetPasswordWithUI(const nsAString& aPromptMessage, const + nsAString& aPromptTitle, + nsIMsgWindow* aMsgWindow, + nsACString& aPassword) +{ + nsresult rv = NS_OK; + + if (m_password.IsEmpty()) + { + // let's see if we have the password in the password manager and + // can avoid this prompting thing. This makes it easier to get embedders + // to get up and running w/o a password prompting UI. + rv = GetPasswordWithoutUI(); + // If GetPasswordWithoutUI returns NS_ERROR_ABORT, the most likely case + // is the user canceled getting the master password, so just return + // straight away, as they won't want to get prompted again. + if (rv == NS_ERROR_ABORT) + return NS_MSG_PASSWORD_PROMPT_CANCELLED; + } + if (m_password.IsEmpty()) + { + nsCOMPtr<nsIAuthPrompt> dialog; + // aMsgWindow is required if we need to prompt + if (aMsgWindow) + { + rv = aMsgWindow->GetAuthPrompt(getter_AddRefs(dialog)); + NS_ENSURE_SUCCESS(rv, rv); + } + if (dialog) + { + // prompt the user for the password + nsCString serverUri; + rv = GetLocalStoreType(serverUri); + NS_ENSURE_SUCCESS(rv, rv); + + serverUri.AppendLiteral("://"); + nsCString temp; + rv = GetUsername(temp); + NS_ENSURE_SUCCESS(rv, rv); + + if (!temp.IsEmpty()) + { + nsCString escapedUsername; + MsgEscapeString(temp, nsINetUtil::ESCAPE_XALPHAS, escapedUsername); + serverUri.Append(escapedUsername); + serverUri.Append('@'); + } + + rv = GetHostName(temp); + NS_ENSURE_SUCCESS(rv, rv); + + serverUri.Append(temp); + + // we pass in the previously used password, if any, into PromptPassword + // so that it will appear as ******. This means we can't use an nsString + // and getter_Copies. + char16_t *uniPassword = nullptr; + if (!aPassword.IsEmpty()) + uniPassword = ToNewUnicode(NS_ConvertASCIItoUTF16(aPassword)); + + bool okayValue = true; + rv = dialog->PromptPassword(PromiseFlatString(aPromptTitle).get(), + PromiseFlatString(aPromptMessage).get(), + NS_ConvertASCIItoUTF16(serverUri).get(), + nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, + &uniPassword, &okayValue); + nsAutoString uniPasswordAdopted; + uniPasswordAdopted.Adopt(uniPassword); + NS_ENSURE_SUCCESS(rv, rv); + + if (!okayValue) // if the user pressed cancel, just return an empty string; + { + aPassword.Truncate(); + return NS_MSG_PASSWORD_PROMPT_CANCELLED; + } + + // we got a password back...so remember it + rv = SetPassword(NS_LossyConvertUTF16toASCII(uniPasswordAdopted)); + NS_ENSURE_SUCCESS(rv, rv); + } // if we got a prompt dialog + else + return NS_ERROR_FAILURE; + } // if the password is empty + return GetPassword(aPassword); +} + +NS_IMETHODIMP +nsMsgIncomingServer::ForgetPassword() +{ + nsresult rv; + nsCOMPtr<nsILoginManager> loginMgr = + do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current server URI + nsCString currServerUri; + rv = GetLocalStoreType(currServerUri); + NS_ENSURE_SUCCESS(rv, rv); + + currServerUri.AppendLiteral("://"); + + nsCString temp; + rv = GetHostName(temp); + NS_ENSURE_SUCCESS(rv, rv); + + currServerUri.Append(temp); + + uint32_t count; + nsILoginInfo** logins; + + NS_ConvertUTF8toUTF16 currServer(currServerUri); + + nsCString serverCUsername; + rv = GetUsername(serverCUsername); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertUTF8toUTF16 serverUsername(serverCUsername); + + rv = loginMgr->FindLogins(&count, currServer, EmptyString(), + currServer, &logins); + NS_ENSURE_SUCCESS(rv, rv); + + // There should only be one-login stored for this url, however just in case + // there isn't. + nsString username; + for (uint32_t i = 0; i < count; ++i) + { + if (NS_SUCCEEDED(logins[i]->GetUsername(username)) && + username.Equals(serverUsername)) + { + // If this fails, just continue, we'll still want to remove the password + // from our local cache. + loginMgr->RemoveLogin(logins[i]); + } + } + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins); + + return SetPassword(EmptyCString()); +} + +NS_IMETHODIMP +nsMsgIncomingServer::ForgetSessionPassword() +{ + m_password.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetDefaultLocalPath(nsIFile *aDefaultLocalPath) +{ + nsresult rv; + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + return protocolInfo->SetDefaultLocalPath(aDefaultLocalPath); +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetLocalPath(nsIFile **aLocalPath) +{ + nsresult rv; + + // if the local path has already been set, use it + rv = GetFileValue("directory-rel", "directory", aLocalPath); + if (NS_SUCCEEDED(rv) && *aLocalPath) + return rv; + + // otherwise, create the path using the protocol info. + // note we are using the + // hostname, unless that directory exists. +// this should prevent all collisions. + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> localPath; + rv = protocolInfo->GetDefaultLocalPath(getter_AddRefs(localPath)); + NS_ENSURE_SUCCESS(rv, rv); + rv = localPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (rv == NS_ERROR_FILE_ALREADY_EXISTS) + rv = NS_OK; + NS_ENSURE_SUCCESS(rv, rv); + + nsCString hostname; + rv = GetHostName(hostname); + NS_ENSURE_SUCCESS(rv, rv); + + // set the leaf name to "dummy", and then call MakeUnique with a suggested leaf name + rv = localPath->AppendNative(hostname); + NS_ENSURE_SUCCESS(rv, rv); + rv = localPath->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetLocalPath(localPath); + NS_ENSURE_SUCCESS(rv, rv); + + localPath.swap(*aLocalPath); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetMsgStore(nsIMsgPluggableStore **aMsgStore) +{ + NS_ENSURE_ARG_POINTER(aMsgStore); + if (!m_msgStore) + { + nsCString storeContractID; + nsresult rv; + // We don't want there to be a default pref, I think, since + // we can't change the default. We may want no pref to mean + // berkeley store, and then set the store pref off of some sort + // of default when creating a server. But we need to make sure + // that we do always write a store pref. + GetCharValue("storeContractID", storeContractID); + if (storeContractID.IsEmpty()) + { + storeContractID.Assign("@mozilla.org/msgstore/berkeleystore;1"); + SetCharValue("storeContractID", storeContractID); + } + + // After someone starts using the pluggable store, we can no longer + // change the value. + SetBoolValue("canChangeStoreType", false); + + // Right now, we just have one pluggable store per server. If we want + // to support multiple, this pref could be a list of pluggable store + // contract id's. + m_msgStore = do_CreateInstance(storeContractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + NS_IF_ADDREF(*aMsgStore = m_msgStore); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetLocalPath(nsIFile *aLocalPath) +{ + NS_ENSURE_ARG_POINTER(aLocalPath); + nsresult rv = aLocalPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (rv == NS_ERROR_FILE_ALREADY_EXISTS) + rv = NS_OK; + NS_ENSURE_SUCCESS(rv, rv); + return SetFileValue("directory-rel", "directory", aLocalPath); +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetLocalStoreType(nsACString& aResult) +{ + NS_NOTYETIMPLEMENTED("nsMsgIncomingServer superclass not implementing GetLocalStoreType!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetLocalDatabaseType(nsACString& aResult) +{ + NS_NOTYETIMPLEMENTED("nsMsgIncomingServer superclass not implementing GetLocalDatabaseType!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetAccountManagerChrome(nsAString& aResult) +{ + aResult.AssignLiteral("am-main.xul"); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::Equals(nsIMsgIncomingServer *server, bool *_retval) +{ + nsresult rv; + + NS_ENSURE_ARG_POINTER(server); + NS_ENSURE_ARG_POINTER(_retval); + + nsCString key1; + nsCString key2; + + rv = GetKey(key1); + NS_ENSURE_SUCCESS(rv, rv); + + rv = server->GetKey(key2); + NS_ENSURE_SUCCESS(rv, rv); + + // compare the server keys + *_retval = key1.Equals(key2, nsCaseInsensitiveCStringComparator()); + + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::ClearAllValues() +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + return mPrefBranch->DeleteBranch(""); +} + +NS_IMETHODIMP +nsMsgIncomingServer::RemoveFiles() +{ + // IMPORTANT, see bug #77652 + // TODO: Decide what to do for deferred accounts. + nsCString deferredToAccount; + GetCharValue("deferred_to_account", deferredToAccount); + bool isDeferredTo = true; + GetIsDeferredTo(&isDeferredTo); + if (!deferredToAccount.IsEmpty() || isDeferredTo) + { + NS_ASSERTION(false, "shouldn't remove files for a deferred account"); + return NS_ERROR_FAILURE; + } + nsCOMPtr <nsIFile> localPath; + nsresult rv = GetLocalPath(getter_AddRefs(localPath)); + NS_ENSURE_SUCCESS(rv, rv); + return localPath->Remove(true); +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetFilterList(nsIMsgFilterList *aFilterList) +{ + mFilterList = aFilterList; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (!mFilterList) + { + nsCOMPtr<nsIMsgFolder> msgFolder; + // use GetRootFolder so for deferred pop3 accounts, we'll get the filters + // file from the deferred account, not the deferred to account, + // so that filters will still be per-server. + nsresult rv = GetRootFolder(getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString filterType; + rv = GetCharValue("filter.type", filterType); + NS_ENSURE_SUCCESS(rv, rv); + + if (!filterType.IsEmpty() && !filterType.EqualsLiteral("default")) + { + nsAutoCString contractID("@mozilla.org/filterlist;1?type="); + contractID += filterType; + ToLowerCase(contractID); + mFilterList = do_CreateInstance(contractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mFilterList->SetFolder(msgFolder); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aResult = mFilterList); + return NS_OK; + } + + // The default case, a local folder, is a bit special. It requires + // more initialization. + + nsCOMPtr<nsIFile> thisFolder; + rv = msgFolder->GetFilePath(getter_AddRefs(thisFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + mFilterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = mFilterFile->InitWithFile(thisFolder); + NS_ENSURE_SUCCESS(rv, rv); + + mFilterFile->AppendNative(NS_LITERAL_CSTRING("msgFilterRules.dat")); + + bool fileExists; + mFilterFile->Exists(&fileExists); + if (!fileExists) + { + nsCOMPtr<nsIFile> oldFilterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = oldFilterFile->InitWithFile(thisFolder); + NS_ENSURE_SUCCESS(rv, rv); + oldFilterFile->AppendNative(NS_LITERAL_CSTRING("rules.dat")); + + oldFilterFile->Exists(&fileExists); + if (fileExists) //copy rules.dat --> msgFilterRules.dat + { + rv = oldFilterFile->CopyToNative(thisFolder, NS_LITERAL_CSTRING("msgFilterRules.dat")); + NS_ENSURE_SUCCESS(rv, rv); + } + } + nsCOMPtr<nsIMsgFilterService> filterService = + do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = filterService->OpenFilterList(mFilterFile, msgFolder, aMsgWindow, getter_AddRefs(mFilterList)); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_IF_ADDREF(*aResult = mFilterList); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetEditableFilterList(nsIMsgFilterList *aEditableFilterList) +{ + mEditableFilterList = aEditableFilterList; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (!mEditableFilterList) + { + bool editSeparate; + nsresult rv = GetBoolValue("filter.editable.separate", &editSeparate); + if (NS_FAILED(rv) || !editSeparate) + return GetFilterList(aMsgWindow, aResult); + + nsCString filterType; + rv = GetCharValue("filter.editable.type", filterType); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString contractID("@mozilla.org/filterlist;1?type="); + contractID += filterType; + ToLowerCase(contractID); + mEditableFilterList = do_CreateInstance(contractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> msgFolder; + // use GetRootFolder so for deferred pop3 accounts, we'll get the filters + // file from the deferred account, not the deferred to account, + // so that filters will still be per-server. + rv = GetRootFolder(getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mEditableFilterList->SetFolder(msgFolder); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aResult = mEditableFilterList); + return NS_OK; + } + + NS_IF_ADDREF(*aResult = mEditableFilterList); + return NS_OK; +} + +// If the hostname contains ':' (like hostname:1431) +// then parse and set the port number. +nsresult +nsMsgIncomingServer::InternalSetHostName(const nsACString& aHostname, const char * prefName) +{ + nsCString hostname; + hostname = aHostname; + if (MsgCountChar(hostname, ':') == 1) + { + int32_t colonPos = hostname.FindChar(':'); + nsAutoCString portString(Substring(hostname, colonPos)); + hostname.SetLength(colonPos); + nsresult err; + int32_t port = portString.ToInteger(&err); + if (NS_SUCCEEDED(err)) + SetPort(port); + } + return SetCharValue(prefName, hostname); +} + +NS_IMETHODIMP +nsMsgIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName, + const nsACString& newName, + bool hostnameChanged) +{ + nsresult rv; + + // 1. Reset password so that users are prompted for new password for the new user/host. + ForgetPassword(); + + // 2. Let the derived class close all cached connection to the old host. + CloseCachedConnections(); + + // 3. Notify any listeners for account server changes. + nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = accountManager->NotifyServerChanged(this); + NS_ENSURE_SUCCESS(rv, rv); + + // 4. Lastly, replace all occurrences of old name in the acct name with the new one. + nsString acctName; + rv = GetPrettyName(acctName); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_FALSE(acctName.IsEmpty(), NS_OK); + + // exit if new name contains @ then better do not update the account name + if (!hostnameChanged && (newName.FindChar('@') != kNotFound)) + return NS_OK; + + int32_t atPos = acctName.FindChar('@'); + + // get previous username and hostname + nsCString userName, hostName; + if (hostnameChanged) + { + rv = GetRealUsername(userName); + NS_ENSURE_SUCCESS(rv, rv); + hostName.Assign(oldName); + } + else + { + userName.Assign(oldName); + rv = GetRealHostName(hostName); + NS_ENSURE_SUCCESS(rv, rv); + } + + // switch corresponding part of the account name to the new name... + if (!hostnameChanged && (atPos != kNotFound)) + { + // ...if username changed and the previous username was equal to the part + // of the account name before @ + if (StringHead(acctName, atPos).Equals(NS_ConvertASCIItoUTF16(userName))) + acctName.Replace(0, userName.Length(), NS_ConvertASCIItoUTF16(newName)); + } + if (hostnameChanged) + { + // ...if hostname changed and the previous hostname was equal to the part + // of the account name after @, or to the whole account name + if (atPos == kNotFound) + atPos = 0; + else + atPos += 1; + if (Substring(acctName, atPos).Equals(NS_ConvertASCIItoUTF16(hostName))) { + acctName.Replace(atPos, acctName.Length() - atPos, + NS_ConvertASCIItoUTF16(newName)); + } + } + + return SetPrettyName(acctName); +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetHostName(const nsACString& aHostname) +{ + return (InternalSetHostName(aHostname, "hostname")); +} + +// SetRealHostName() is called only when the server name is changed from the +// UI (Account Settings page). No one should call it in any circumstances. +NS_IMETHODIMP +nsMsgIncomingServer::SetRealHostName(const nsACString& aHostname) +{ + nsCString oldName; + nsresult rv = GetRealHostName(oldName); + NS_ENSURE_SUCCESS(rv, rv); + rv = InternalSetHostName(aHostname, "realhostname"); + + // A few things to take care of if we're changing the hostname. + if (!aHostname.Equals(oldName, nsCaseInsensitiveCStringComparator())) + rv = OnUserOrHostNameChanged(oldName, aHostname, true); + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetHostName(nsACString& aResult) +{ + nsresult rv; + rv = GetCharValue("hostname", aResult); + if (MsgCountChar(aResult, ':') == 1) + { + // gack, we need to reformat the hostname - SetHostName will do that + SetHostName(aResult); + rv = GetCharValue("hostname", aResult); + } + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetRealHostName(nsACString& aResult) +{ + // If 'realhostname' is set (was changed) then use it, otherwise use 'hostname' + nsresult rv; + rv = GetCharValue("realhostname", aResult); + NS_ENSURE_SUCCESS(rv, rv); + + if (aResult.IsEmpty()) + return GetHostName(aResult); + + if (MsgCountChar(aResult, ':') == 1) + { + SetRealHostName(aResult); + rv = GetCharValue("realhostname", aResult); + } + + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetRealUsername(nsACString& aResult) +{ + // If 'realuserName' is set (was changed) then use it, otherwise use 'userName' + nsresult rv; + rv = GetCharValue("realuserName", aResult); + NS_ENSURE_SUCCESS(rv, rv); + return aResult.IsEmpty() ? GetUsername(aResult) : rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetRealUsername(const nsACString& aUsername) +{ + // Need to take care of few things if we're changing the username. + nsCString oldName; + nsresult rv = GetRealUsername(oldName); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetCharValue("realuserName", aUsername); + if (!oldName.Equals(aUsername)) + rv = OnUserOrHostNameChanged(oldName, aUsername, false); + return rv; +} + +#define BIFF_PREF_NAME "check_new_mail" + +NS_IMETHODIMP +nsMsgIncomingServer::GetDoBiff(bool *aDoBiff) +{ + NS_ENSURE_ARG_POINTER(aDoBiff); + + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + + rv = mPrefBranch->GetBoolPref(BIFF_PREF_NAME, aDoBiff); + if (NS_SUCCEEDED(rv)) + return rv; + + // if the pref isn't set, use the default + // value based on the protocol + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = protocolInfo->GetDefaultDoBiff(aDoBiff); + // note, don't call SetDoBiff() + // since we keep changing our minds on + // if biff should be on or off, let's keep the ability + // to change the default in future builds. + // if we call SetDoBiff() here, it will be in the users prefs. + // and we can't do anything after that. + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetDoBiff(bool aDoBiff) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + // Update biffManager immediately, no restart required. Adding/removing + // existing/non-existing server is handled without error checking. + nsresult rv; + nsCOMPtr<nsIMsgBiffManager> biffService = + do_GetService(NS_MSGBIFFMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && biffService) + { + if (aDoBiff) + (void) biffService->AddServerBiff(this); + else + (void) biffService->RemoveServerBiff(this); + } + + return mPrefBranch->SetBoolPref(BIFF_PREF_NAME, aDoBiff); +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetPort(int32_t *aPort) +{ + NS_ENSURE_ARG_POINTER(aPort); + + nsresult rv; + rv = GetIntValue("port", aPort); + // We can't use a port of 0, because the URI parsing code fails. + if (*aPort != PORT_NOT_SET && *aPort) + return rv; + + // if the port isn't set, use the default + // port based on the protocol + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t socketType; + rv = GetSocketType(&socketType); + NS_ENSURE_SUCCESS(rv, rv); + bool useSSLPort = (socketType == nsMsgSocketType::SSL); + return protocolInfo->GetDefaultServerPort(useSSLPort, aPort); +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetPort(int32_t aPort) +{ + nsresult rv; + + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t socketType; + rv = GetSocketType(&socketType); + NS_ENSURE_SUCCESS(rv, rv); + bool useSSLPort = (socketType == nsMsgSocketType::SSL); + + int32_t defaultPort; + protocolInfo->GetDefaultServerPort(useSSLPort, &defaultPort); + return SetIntValue("port", aPort == defaultPort ? PORT_NOT_SET : aPort); +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetProtocolInfo(nsIMsgProtocolInfo **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsCString type; + nsresult rv = GetType(type); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString contractid(NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX); + contractid.Append(type); + + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo = do_GetService(contractid.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + protocolInfo.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP nsMsgIncomingServer::GetRetentionSettings(nsIMsgRetentionSettings **settings) +{ + NS_ENSURE_ARG_POINTER(settings); + nsMsgRetainByPreference retainByPreference; + int32_t daysToKeepHdrs = 0; + int32_t numHeadersToKeep = 0; + int32_t daysToKeepBodies = 0; + bool cleanupBodiesByDays = false; + bool applyToFlaggedMessages = false; + nsresult rv = NS_OK; + // Create an empty retention settings object, + // get the settings from the server prefs, and init the object from the prefs. + nsCOMPtr <nsIMsgRetentionSettings> retentionSettings = + do_CreateInstance(NS_MSG_RETENTIONSETTINGS_CONTRACTID); + if (retentionSettings) + { + rv = GetIntValue("retainBy", (int32_t*) &retainByPreference); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetIntValue("numHdrsToKeep", &numHeadersToKeep); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetIntValue("daysToKeepHdrs", &daysToKeepHdrs); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetIntValue("daysToKeepBodies", &daysToKeepBodies); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetBoolValue("cleanupBodies", &cleanupBodiesByDays); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetBoolValue("applyToFlaggedMessages", &applyToFlaggedMessages); + NS_ENSURE_SUCCESS(rv, rv); + retentionSettings->SetRetainByPreference(retainByPreference); + retentionSettings->SetNumHeadersToKeep((uint32_t) numHeadersToKeep); + retentionSettings->SetDaysToKeepBodies(daysToKeepBodies); + retentionSettings->SetDaysToKeepHdrs(daysToKeepHdrs); + retentionSettings->SetCleanupBodiesByDays(cleanupBodiesByDays); + retentionSettings->SetApplyToFlaggedMessages(applyToFlaggedMessages); + } + else + rv = NS_ERROR_OUT_OF_MEMORY; + NS_IF_ADDREF(*settings = retentionSettings); + return rv; +} + +NS_IMETHODIMP nsMsgIncomingServer::SetRetentionSettings(nsIMsgRetentionSettings *settings) +{ + nsMsgRetainByPreference retainByPreference; + uint32_t daysToKeepHdrs = 0; + uint32_t numHeadersToKeep = 0; + uint32_t daysToKeepBodies = 0; + bool cleanupBodiesByDays = false; + bool applyToFlaggedMessages = false; + settings->GetRetainByPreference(&retainByPreference); + settings->GetNumHeadersToKeep(&numHeadersToKeep); + settings->GetDaysToKeepBodies(&daysToKeepBodies); + settings->GetDaysToKeepHdrs(&daysToKeepHdrs); + settings->GetCleanupBodiesByDays(&cleanupBodiesByDays); + settings->GetApplyToFlaggedMessages(&applyToFlaggedMessages); + nsresult rv = SetIntValue("retainBy", retainByPreference); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetIntValue("numHdrsToKeep", numHeadersToKeep); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetIntValue("daysToKeepHdrs", daysToKeepHdrs); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetIntValue("daysToKeepBodies", daysToKeepBodies); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetBoolValue("cleanupBodies", cleanupBodiesByDays); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetBoolValue("applyToFlaggedMessages", applyToFlaggedMessages); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetDisplayStartupPage(bool *displayStartupPage) +{ + NS_ENSURE_ARG_POINTER(displayStartupPage); + *displayStartupPage = m_displayStartupPage; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetDisplayStartupPage(bool displayStartupPage) +{ + m_displayStartupPage = displayStartupPage; + return NS_OK; +} + + +NS_IMETHODIMP nsMsgIncomingServer::GetDownloadSettings(nsIMsgDownloadSettings **settings) +{ + NS_ENSURE_ARG_POINTER(settings); + bool downloadUnreadOnly = false; + bool downloadByDate = false; + uint32_t ageLimitOfMsgsToDownload = 0; + nsresult rv = NS_OK; + if (!m_downloadSettings) + { + m_downloadSettings = do_CreateInstance(NS_MSG_DOWNLOADSETTINGS_CONTRACTID); + if (m_downloadSettings) + { + rv = GetBoolValue("downloadUnreadOnly", &downloadUnreadOnly); + rv = GetBoolValue("downloadByDate", &downloadByDate); + rv = GetIntValue("ageLimit", (int32_t *) &ageLimitOfMsgsToDownload); + m_downloadSettings->SetDownloadUnreadOnly(downloadUnreadOnly); + m_downloadSettings->SetDownloadByDate(downloadByDate); + m_downloadSettings->SetAgeLimitOfMsgsToDownload(ageLimitOfMsgsToDownload); + } + else + rv = NS_ERROR_OUT_OF_MEMORY; + // Create an empty download settings object, + // get the settings from the server prefs, and init the object from the prefs. + } + NS_IF_ADDREF(*settings = m_downloadSettings); + return rv; +} + +NS_IMETHODIMP nsMsgIncomingServer::SetDownloadSettings(nsIMsgDownloadSettings *settings) +{ + m_downloadSettings = settings; + bool downloadUnreadOnly = false; + bool downloadByDate = false; + uint32_t ageLimitOfMsgsToDownload = 0; + m_downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly); + m_downloadSettings->GetDownloadByDate(&downloadByDate); + m_downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload); + nsresult rv = SetBoolValue("downloadUnreadOnly", downloadUnreadOnly); + NS_ENSURE_SUCCESS(rv, rv); + SetBoolValue("downloadByDate", downloadByDate); + return SetIntValue("ageLimit", ageLimitOfMsgsToDownload); +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetSupportsDiskSpace(bool *aSupportsDiskSpace) +{ + NS_ENSURE_ARG_POINTER(aSupportsDiskSpace); + *aSupportsDiskSpace = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel) +{ + NS_ENSURE_ARG_POINTER(aSupportLevel); + + nsresult rv = GetIntValue("offline_support_level", aSupportLevel); + NS_ENSURE_SUCCESS(rv, rv); + + if (*aSupportLevel == OFFLINE_SUPPORT_LEVEL_UNDEFINED) + *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetOfflineSupportLevel(int32_t aSupportLevel) +{ + SetIntValue("offline_support_level", aSupportLevel); + return NS_OK; +} +#define BASE_MSGS_URL "chrome://messenger/locale/messenger.properties" + +NS_IMETHODIMP nsMsgIncomingServer::DisplayOfflineMsg(nsIMsgWindow *aMsgWindow) +{ + NS_ENSURE_ARG_POINTER(aMsgWindow); + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = bundleService->CreateBundle(BASE_MSGS_URL, getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + if (bundle) + { + nsString errorMsgTitle; + nsString errorMsgBody; + bundle->GetStringFromName(u"nocachedbodybody2", getter_Copies(errorMsgBody)); + bundle->GetStringFromName(u"nocachedbodytitle", getter_Copies(errorMsgTitle)); + aMsgWindow->DisplayHTMLInMessagePane(errorMsgTitle, errorMsgBody, true); + } + + return NS_OK; +} + +// Called only during the migration process. A unique name is generated for the +// migrated account. +NS_IMETHODIMP +nsMsgIncomingServer::GeneratePrettyNameForMigration(nsAString& aPrettyName) +{ +/** + * 4.x had provisions for multiple imap servers to be maintained under + * single identity. So, when migrated each of those server accounts need + * to be represented by unique account name. nsImapIncomingServer will + * override the implementation for this to do the right thing. +*/ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetFilterScope(nsMsgSearchScopeValue *filterScope) +{ + NS_ENSURE_ARG_POINTER(filterScope); + *filterScope = nsMsgSearchScope::offlineMailFilter; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetSearchScope(nsMsgSearchScopeValue *searchScope) +{ + NS_ENSURE_ARG_POINTER(searchScope); + *searchScope = nsMsgSearchScope::offlineMail; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetIsSecure(bool *aIsSecure) +{ + NS_ENSURE_ARG_POINTER(aIsSecure); + int32_t socketType; + nsresult rv = GetSocketType(&socketType); + NS_ENSURE_SUCCESS(rv,rv); + *aIsSecure = (socketType == nsMsgSocketType::alwaysSTARTTLS || + socketType == nsMsgSocketType::SSL); + return NS_OK; +} + +// use the convenience macros to implement the accessors +NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Username, "userName") +NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, AuthMethod, "authMethod") +NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, BiffMinutes, "check_time") +NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Type, "type") +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, DownloadOnBiff, "download_on_biff") +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, Valid, "valid") +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, EmptyTrashOnExit, + "empty_trash_on_exit") +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, CanDelete, "canDelete") +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, LoginAtStartUp, "login_at_startup") +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, + DefaultCopiesAndFoldersPrefsToServer, + "allows_specialfolders_usage") + +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, + CanCreateFoldersOnServer, + "canCreateFolders") + +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, + CanFileMessagesOnServer, + "canFileMessages") + +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, + LimitOfflineMessageSize, + "limit_offline_message_size") + +NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, MaxMessageSize, "max_size") + +NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, IncomingDuplicateAction, "dup_action") + +NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, Hidden, "hidden") + +NS_IMETHODIMP nsMsgIncomingServer::GetSocketType(int32_t *aSocketType) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = mPrefBranch->GetIntPref("socketType", aSocketType); + + // socketType is set to default value. Look at isSecure setting + if (NS_FAILED(rv)) + { + bool isSecure; + rv = mPrefBranch->GetBoolPref("isSecure", &isSecure); + if (NS_SUCCEEDED(rv) && isSecure) + { + *aSocketType = nsMsgSocketType::SSL; + // don't call virtual method in case overrides call GetSocketType + nsMsgIncomingServer::SetSocketType(*aSocketType); + } + else + { + if (!mDefPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + rv = mDefPrefBranch->GetIntPref("socketType", aSocketType); + if (NS_FAILED(rv)) + *aSocketType = nsMsgSocketType::plain; + } + } + return rv; +} + +NS_IMETHODIMP nsMsgIncomingServer::SetSocketType(int32_t aSocketType) +{ + if (!mPrefBranch) + return NS_ERROR_NOT_INITIALIZED; + + int32_t socketType = nsMsgSocketType::plain; + mPrefBranch->GetIntPref("socketType", &socketType); + + nsresult rv = mPrefBranch->SetIntPref("socketType", aSocketType); + NS_ENSURE_SUCCESS(rv, rv); + + bool isSecureOld = (socketType == nsMsgSocketType::alwaysSTARTTLS || + socketType == nsMsgSocketType::SSL); + bool isSecureNew = (aSocketType == nsMsgSocketType::alwaysSTARTTLS || + aSocketType == nsMsgSocketType::SSL); + if ((isSecureOld != isSecureNew) && m_rootFolder) { + nsCOMPtr <nsIAtom> isSecureAtom = MsgGetAtom("isSecure"); + m_rootFolder->NotifyBoolPropertyChanged(isSecureAtom, + isSecureOld, isSecureNew); + } + return NS_OK; +} + +// Check if the password is available and return a boolean indicating whether +// it is being authenticated or not. +NS_IMETHODIMP +nsMsgIncomingServer::GetPasswordPromptRequired(bool *aPasswordIsRequired) +{ + NS_ENSURE_ARG_POINTER(aPasswordIsRequired); + *aPasswordIsRequired = true; + + // If the password is not even required for biff we don't need to check any further + nsresult rv = GetServerRequiresPasswordForBiff(aPasswordIsRequired); + NS_ENSURE_SUCCESS(rv, rv); + if (!*aPasswordIsRequired) + return NS_OK; + + // If the password is empty, check to see if it is stored and to be retrieved + if (m_password.IsEmpty()) + (void)GetPasswordWithoutUI(); + + *aPasswordIsRequired = m_password.IsEmpty(); + return rv; +} + +NS_IMETHODIMP nsMsgIncomingServer::ConfigureTemporaryFilters(nsIMsgFilterList *aFilterList) +{ + nsresult rv = ConfigureTemporaryReturnReceiptsFilter(aFilterList); + if (NS_FAILED(rv)) // shut up warnings... + return rv; + return ConfigureTemporaryServerSpamFilters(aFilterList); +} + +nsresult +nsMsgIncomingServer::ConfigureTemporaryServerSpamFilters(nsIMsgFilterList *filterList) +{ + nsCOMPtr<nsISpamSettings> spamSettings; + nsresult rv = GetSpamSettings(getter_AddRefs(spamSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + bool useServerFilter; + rv = spamSettings->GetUseServerFilter(&useServerFilter); + NS_ENSURE_SUCCESS(rv, rv); + + // if we aren't configured to use server filters, then return early. + if (!useServerFilter) + return NS_OK; + + // For performance reasons, we'll handle clearing of filters if the user turns + // off the server-side filters from the junk mail controls, in the junk mail controls. + nsAutoCString serverFilterName; + spamSettings->GetServerFilterName(serverFilterName); + if (serverFilterName.IsEmpty()) + return NS_OK; + int32_t serverFilterTrustFlags = 0; + (void) spamSettings->GetServerFilterTrustFlags(&serverFilterTrustFlags); + if (!serverFilterTrustFlags) + return NS_OK; + // check if filters have been setup already. + nsAutoString yesFilterName, noFilterName; + CopyASCIItoUTF16(serverFilterName, yesFilterName); + yesFilterName.AppendLiteral("Yes"); + + CopyASCIItoUTF16(serverFilterName, noFilterName); + noFilterName.AppendLiteral("No"); + + nsCOMPtr<nsIMsgFilter> newFilter; + (void) filterList->GetFilterNamed(yesFilterName, + getter_AddRefs(newFilter)); + + if (!newFilter) + (void) filterList->GetFilterNamed(noFilterName, + getter_AddRefs(newFilter)); + if (newFilter) + return NS_OK; + + nsCOMPtr<nsIFile> file; + spamSettings->GetServerFilterFile(getter_AddRefs(file)); + + // it's possible that we can no longer find the sfd file (i.e. the user disabled an extnsion that + // was supplying the .sfd file. + if (!file) + return NS_OK; + + nsCOMPtr<nsIMsgFilterService> filterService = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv); + nsCOMPtr<nsIMsgFilterList> serverFilterList; + + rv = filterService->OpenFilterList(file, NULL, NULL, getter_AddRefs(serverFilterList)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = serverFilterList->GetFilterNamed(yesFilterName, + getter_AddRefs(newFilter)); + if (newFilter && serverFilterTrustFlags & nsISpamSettings::TRUST_POSITIVES) + { + newFilter->SetTemporary(true); + // check if we're supposed to move junk mail to junk folder; if so, + // add filter action to do so. + + /* + * We don't want this filter to activate on messages that have + * been marked by the user as not spam. This occurs when messages that + * were marked as good are moved back into the inbox. But to + * do this with a filter, we have to add a boolean term. That requires + * that we rewrite the existing filter search terms to group them. + */ + + // get the list of search terms from the filter + nsCOMPtr<nsISupportsArray> searchTerms; + rv = newFilter->GetSearchTerms(getter_AddRefs(searchTerms)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t count = 0; + searchTerms->Count(&count); + if (count > 1) // don't need to group a single term + { + // beginGrouping the first term, and endGrouping the last term + nsCOMPtr<nsIMsgSearchTerm> firstTerm(do_QueryElementAt(searchTerms, + 0, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + firstTerm->SetBeginsGrouping(true); + + nsCOMPtr<nsIMsgSearchTerm> lastTerm(do_QueryElementAt(searchTerms, + count - 1, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + lastTerm->SetEndsGrouping(true); + } + + // Create a new term, checking if the user set junk status. The term will + // search for junkscoreorigin != "user" + nsCOMPtr<nsIMsgSearchTerm> searchTerm; + rv = newFilter->CreateTerm(getter_AddRefs(searchTerm)); + NS_ENSURE_SUCCESS(rv, rv); + + searchTerm->SetAttrib(nsMsgSearchAttrib::JunkScoreOrigin); + searchTerm->SetOp(nsMsgSearchOp::Isnt); + searchTerm->SetBooleanAnd(true); + + nsCOMPtr<nsIMsgSearchValue> searchValue; + searchTerm->GetValue(getter_AddRefs(searchValue)); + NS_ENSURE_SUCCESS(rv, rv); + searchValue->SetAttrib(nsMsgSearchAttrib::JunkScoreOrigin); + searchValue->SetStr(NS_LITERAL_STRING("user")); + searchTerm->SetValue(searchValue); + + searchTerms->InsertElementAt(searchTerm, count); + + bool moveOnSpam, markAsReadOnSpam; + spamSettings->GetMoveOnSpam(&moveOnSpam); + if (moveOnSpam) + { + nsCString spamFolderURI; + rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI)); + if (NS_SUCCEEDED(rv) && (!spamFolderURI.IsEmpty())) + { + nsCOMPtr <nsIMsgRuleAction> moveAction; + rv = newFilter->CreateAction(getter_AddRefs(moveAction)); + if (NS_SUCCEEDED(rv)) + { + moveAction->SetType(nsMsgFilterAction::MoveToFolder); + moveAction->SetTargetFolderUri(spamFolderURI); + newFilter->AppendAction(moveAction); + } + } + } + spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam); + if (markAsReadOnSpam) + { + nsCOMPtr <nsIMsgRuleAction> markAsReadAction; + rv = newFilter->CreateAction(getter_AddRefs(markAsReadAction)); + if (NS_SUCCEEDED(rv)) + { + markAsReadAction->SetType(nsMsgFilterAction::MarkRead); + newFilter->AppendAction(markAsReadAction); + } + } + filterList->InsertFilterAt(0, newFilter); + } + + rv = serverFilterList->GetFilterNamed(noFilterName, + getter_AddRefs(newFilter)); + if (newFilter && serverFilterTrustFlags & nsISpamSettings::TRUST_NEGATIVES) + { + newFilter->SetTemporary(true); + filterList->InsertFilterAt(0, newFilter); + } + + return rv; +} + +nsresult +nsMsgIncomingServer::ConfigureTemporaryReturnReceiptsFilter(nsIMsgFilterList *filterList) +{ + nsresult rv; + + nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIdentity> identity; + rv = accountMgr->GetFirstIdentityForServer(this, getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + // this can return success and a null identity... + + bool useCustomPrefs = false; + int32_t incorp = nsIMsgMdnGenerator::eIncorporateInbox; + NS_ENSURE_TRUE(identity, NS_ERROR_NULL_POINTER); + + identity->GetBoolAttribute("use_custom_prefs", &useCustomPrefs); + if (useCustomPrefs) + rv = GetIntValue("incorporate_return_receipt", &incorp); + else + { + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetIntPref("mail.incorporate.return_receipt", &incorp); + } + + bool enable = (incorp == nsIMsgMdnGenerator::eIncorporateSent); + + // this is a temporary, internal mozilla filter + // it will not show up in the UI, it will not be written to disk + NS_NAMED_LITERAL_STRING(internalReturnReceiptFilterName, "mozilla-temporary-internal-MDN-receipt-filter"); + + nsCOMPtr<nsIMsgFilter> newFilter; + rv = filterList->GetFilterNamed(internalReturnReceiptFilterName, + getter_AddRefs(newFilter)); + if (newFilter) + newFilter->SetEnabled(enable); + else if (enable) + { + nsCString actionTargetFolderUri; + rv = identity->GetFccFolder(actionTargetFolderUri); + if (!actionTargetFolderUri.IsEmpty()) + { + filterList->CreateFilter(internalReturnReceiptFilterName, + getter_AddRefs(newFilter)); + if (newFilter) + { + newFilter->SetEnabled(true); + // this internal filter is temporary + // and should not show up in the UI or be written to disk + newFilter->SetTemporary(true); + + nsCOMPtr<nsIMsgSearchTerm> term; + nsCOMPtr<nsIMsgSearchValue> value; + + rv = newFilter->CreateTerm(getter_AddRefs(term)); + if (NS_SUCCEEDED(rv)) + { + rv = term->GetValue(getter_AddRefs(value)); + if (NS_SUCCEEDED(rv)) + { + // we need to use OtherHeader + 1 so nsMsgFilter::GetTerm will + // return our custom header. + value->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1); + value->SetStr(NS_LITERAL_STRING("multipart/report")); + term->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1); + term->SetOp(nsMsgSearchOp::Contains); + term->SetBooleanAnd(true); + term->SetArbitraryHeader(NS_LITERAL_CSTRING("Content-Type")); + term->SetValue(value); + newFilter->AppendTerm(term); + } + } + rv = newFilter->CreateTerm(getter_AddRefs(term)); + if (NS_SUCCEEDED(rv)) + { + rv = term->GetValue(getter_AddRefs(value)); + if (NS_SUCCEEDED(rv)) + { + // XXX todo + // determine if ::OtherHeader is the best way to do this. + // see nsMsgSearchOfflineMail::MatchTerms() + value->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1); + value->SetStr(NS_LITERAL_STRING("disposition-notification")); + term->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1); + term->SetOp(nsMsgSearchOp::Contains); + term->SetBooleanAnd(true); + term->SetArbitraryHeader(NS_LITERAL_CSTRING("Content-Type")); + term->SetValue(value); + newFilter->AppendTerm(term); + } + } + nsCOMPtr<nsIMsgRuleAction> filterAction; + rv = newFilter->CreateAction(getter_AddRefs(filterAction)); + if (NS_SUCCEEDED(rv)) + { + filterAction->SetType(nsMsgFilterAction::MoveToFolder); + filterAction->SetTargetFolderUri(actionTargetFolderUri); + newFilter->AppendAction(filterAction); + filterList->InsertFilterAt(0, newFilter); + } + } + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgIncomingServer::ClearTemporaryReturnReceiptsFilter() +{ + if (mFilterList) + { + nsCOMPtr<nsIMsgFilter> mdnFilter; + nsresult rv = mFilterList->GetFilterNamed(NS_LITERAL_STRING("mozilla-temporary-internal-MDN-receipt-filter"), + getter_AddRefs(mdnFilter)); + if (NS_SUCCEEDED(rv) && mdnFilter) + return mFilterList->RemoveFilter(mdnFilter); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetMsgFolderFromURI(nsIMsgFolder *aFolderResource, const nsACString& aURI, nsIMsgFolder **aFolder) +{ + nsCOMPtr<nsIMsgFolder> rootMsgFolder; + nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + NS_ENSURE_TRUE(rootMsgFolder, NS_ERROR_UNEXPECTED); + + nsCOMPtr <nsIMsgFolder> msgFolder; + rv = rootMsgFolder->GetChildWithURI(aURI, true, true /*caseInsensitive*/, getter_AddRefs(msgFolder)); + if (NS_FAILED(rv) || !msgFolder) + msgFolder = aFolderResource; + NS_IF_ADDREF(*aFolder = msgFolder); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetSpamSettings(nsISpamSettings **aSpamSettings) +{ + NS_ENSURE_ARG_POINTER(aSpamSettings); + + nsAutoCString spamActionTargetAccount; + GetCharValue("spamActionTargetAccount", spamActionTargetAccount); + if (spamActionTargetAccount.IsEmpty()) + { + GetServerURI(spamActionTargetAccount); + SetCharValue("spamActionTargetAccount", spamActionTargetAccount); + } + + if (!mSpamSettings) { + nsresult rv; + mSpamSettings = do_CreateInstance(NS_SPAMSETTINGS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + mSpamSettings->Initialize(this); + NS_ENSURE_SUCCESS(rv,rv); + } + + NS_ADDREF(*aSpamSettings = mSpamSettings); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetSpamFilterPlugin(nsIMsgFilterPlugin **aFilterPlugin) +{ + NS_ENSURE_ARG_POINTER(aFilterPlugin); + if (!mFilterPlugin) + { + nsresult rv; + mFilterPlugin = do_GetService("@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_IF_ADDREF(*aFilterPlugin = mFilterPlugin); + return NS_OK; +} + +// get all the servers that defer to the account for the passed in server. Note that +// destServer may not be "this" +nsresult nsMsgIncomingServer::GetDeferredServers(nsIMsgIncomingServer *destServer, nsCOMArray<nsIPop3IncomingServer>& aServers) +{ + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager + = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIMsgAccount> thisAccount; + accountManager->FindAccountForServer(destServer, getter_AddRefs(thisAccount)); + if (thisAccount) + { + nsCOMPtr<nsIArray> allServers; + nsCString accountKey; + thisAccount->GetKey(accountKey); + accountManager->GetAllServers(getter_AddRefs(allServers)); + if (allServers) + { + uint32_t serverCount; + allServers->GetLength(&serverCount); + for (uint32_t i = 0; i < serverCount; i++) + { + nsCOMPtr<nsIPop3IncomingServer> server(do_QueryElementAt(allServers, i)); + if (server) + { + nsCString deferredToAccount; + server->GetDeferredToAccount(deferredToAccount); + if (deferredToAccount.Equals(accountKey)) + aServers.AppendElement(server); + } + } + } + } + return rv; +} + +NS_IMETHODIMP nsMsgIncomingServer::GetIsDeferredTo(bool *aIsDeferredTo) +{ + NS_ENSURE_ARG_POINTER(aIsDeferredTo); + nsCOMPtr<nsIMsgAccountManager> accountManager + = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID); + if (accountManager) + { + nsCOMPtr <nsIMsgAccount> thisAccount; + accountManager->FindAccountForServer(this, getter_AddRefs(thisAccount)); + if (thisAccount) + { + nsCOMPtr<nsIArray> allServers; + nsCString accountKey; + thisAccount->GetKey(accountKey); + accountManager->GetAllServers(getter_AddRefs(allServers)); + if (allServers) + { + uint32_t serverCount; + allServers->GetLength(&serverCount); + for (uint32_t i = 0; i < serverCount; i++) + { + nsCOMPtr <nsIMsgIncomingServer> server (do_QueryElementAt(allServers, i)); + if (server) + { + nsCString deferredToAccount; + server->GetCharValue("deferred_to_account", deferredToAccount); + if (deferredToAccount.Equals(accountKey)) + { + *aIsDeferredTo = true; + return NS_OK; + } + } + } + } + } + } + *aIsDeferredTo = false; + return NS_OK; +} + +const long kMaxDownloadTableSize = 500; + +// hash the concatenation of the message-id and subject as the hash table key, +// and store the arrival index as the value. To limit the size of the hash table, +// we just throw out ones with a lower ordinal value than the cut-off point. +NS_IMETHODIMP nsMsgIncomingServer::IsNewHdrDuplicate(nsIMsgDBHdr *aNewHdr, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aNewHdr); + *aResult = false; + + // If the message has been partially downloaded, the message should not + // be considered a duplicated message. See bug 714090. + uint32_t flags; + aNewHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) + return NS_OK; + + nsAutoCString strHashKey; + nsCString messageId, subject; + aNewHdr->GetMessageId(getter_Copies(messageId)); + strHashKey.Append(messageId); + aNewHdr->GetSubject(getter_Copies(subject)); + // err on the side of caution and ignore messages w/o subject or messageid. + if (subject.IsEmpty() || messageId.IsEmpty()) + return NS_OK; + strHashKey.Append(subject); + int32_t hashValue = 0; + m_downloadedHdrs.Get(strHashKey, &hashValue); + if (hashValue) + *aResult = true; + else + { + // we store the current size of the hash table as the hash + // value - this allows us to delete older entries. + m_downloadedHdrs.Put(strHashKey, ++m_numMsgsDownloaded); + // Check if hash table is larger than some reasonable size + // and if is it, iterate over hash table deleting messages + // with an arrival index < number of msgs downloaded - half the reasonable size. + if (m_downloadedHdrs.Count() >= kMaxDownloadTableSize) { + for (auto iter = m_downloadedHdrs.Iter(); !iter.Done(); iter.Next()) { + if (iter.Data() < m_numMsgsDownloaded - kMaxDownloadTableSize/2) { + iter.Remove(); + } else if (m_downloadedHdrs.Count() <= kMaxDownloadTableSize/2) { + break; + } + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetForcePropertyEmpty(const char *aPropertyName, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + nsAutoCString nameEmpty(aPropertyName); + nameEmpty.Append(NS_LITERAL_CSTRING(".empty")); + nsCString value; + GetCharValue(nameEmpty.get(), value); + *_retval = value.EqualsLiteral("true"); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgIncomingServer::SetForcePropertyEmpty(const char *aPropertyName, bool aValue) +{ + nsAutoCString nameEmpty(aPropertyName); + nameEmpty.Append(NS_LITERAL_CSTRING(".empty")); + return SetCharValue(nameEmpty.get(), + aValue ? NS_LITERAL_CSTRING("true") : NS_LITERAL_CSTRING("")); +} + +NS_IMETHODIMP +nsMsgIncomingServer::GetSortOrder(int32_t* aSortOrder) +{ + NS_ENSURE_ARG_POINTER(aSortOrder); + *aSortOrder = 100000000; + return NS_OK; +} diff --git a/mailnews/base/util/nsMsgIncomingServer.h b/mailnews/base/util/nsMsgIncomingServer.h new file mode 100644 index 000000000..0c4219cd2 --- /dev/null +++ b/mailnews/base/util/nsMsgIncomingServer.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 4; 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 nsMsgIncomingServer_h__ +#define nsMsgIncomingServer_h__ + +#include "nsIMsgIncomingServer.h" +#include "nsIPrefBranch.h" +#include "nsIMsgFilterList.h" +#include "msgCore.h" +#include "nsIMsgFolder.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIPop3IncomingServer.h" +#include "nsWeakReference.h" +#include "nsIMsgDatabase.h" +#include "nsISpamSettings.h" +#include "nsIMsgFilterPlugin.h" +#include "nsDataHashtable.h" +#include "nsIMsgPluggableStore.h" + +class nsIMsgFolderCache; +class nsIMsgProtocolInfo; + +/* + * base class for nsIMsgIncomingServer - derive your class from here + * if you want to get some free implementation + * + * this particular implementation is not meant to be used directly. + */ + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT + +class NS_MSG_BASE nsMsgIncomingServer : public nsIMsgIncomingServer, + public nsSupportsWeakReference +{ + public: + nsMsgIncomingServer(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGINCOMINGSERVER + +protected: + virtual ~nsMsgIncomingServer(); + nsCString m_serverKey; + + // Sets m_password, if password found. Can return NS_ERROR_ABORT if the + // user cancels the master password dialog. + nsresult GetPasswordWithoutUI(); + + nsresult ConfigureTemporaryReturnReceiptsFilter(nsIMsgFilterList *filterList); + nsresult ConfigureTemporaryServerSpamFilters(nsIMsgFilterList *filterList); + + nsCOMPtr <nsIMsgFolder> m_rootFolder; + nsCOMPtr <nsIMsgDownloadSettings> m_downloadSettings; + + // For local servers, where we put messages. For imap/pop3, where we store + // offline messages. + nsCOMPtr <nsIMsgPluggableStore> m_msgStore; + + /// Helper routine to create local folder on disk if it doesn't exist + /// under the account's rootFolder. + nsresult CreateLocalFolder(const nsAString& folderName); + + static nsresult GetDeferredServers(nsIMsgIncomingServer *destServer, nsCOMArray<nsIPop3IncomingServer>& aServers); + + nsresult CreateRootFolder(); + virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri, + nsIMsgFolder **rootFolder) = 0; + + nsresult InternalSetHostName(const nsACString& aHostname, const char * prefName); + + nsCOMPtr <nsIFile> mFilterFile; + nsCOMPtr <nsIMsgFilterList> mFilterList; + nsCOMPtr <nsIMsgFilterList> mEditableFilterList; + nsCOMPtr<nsIPrefBranch> mPrefBranch; + nsCOMPtr<nsIPrefBranch> mDefPrefBranch; + + // these allow us to handle duplicate incoming messages, e.g. delete them. + nsDataHashtable<nsCStringHashKey,int32_t> m_downloadedHdrs; + int32_t m_numMsgsDownloaded; + +private: + uint32_t m_biffState; + bool m_serverBusy; + nsCOMPtr <nsISpamSettings> mSpamSettings; + nsCOMPtr<nsIMsgFilterPlugin> mFilterPlugin; // XXX should be a list + +protected: + nsCString m_password; + bool m_canHaveFilters; + bool m_displayStartupPage; + bool mPerformingBiff; +}; + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN + +#endif // nsMsgIncomingServer_h__ diff --git a/mailnews/base/util/nsMsgKeyArray.cpp b/mailnews/base/util/nsMsgKeyArray.cpp new file mode 100644 index 000000000..e04dc948b --- /dev/null +++ b/mailnews/base/util/nsMsgKeyArray.cpp @@ -0,0 +1,77 @@ +/* -*- 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 "nsMsgKeyArray.h" +#include "nsMemory.h" + +NS_IMPL_ISUPPORTS(nsMsgKeyArray, nsIMsgKeyArray) + +nsMsgKeyArray::nsMsgKeyArray() +{ +#ifdef DEBUG + m_sorted = false; +#endif +} + +nsMsgKeyArray::~nsMsgKeyArray() +{ +} + +NS_IMETHODIMP nsMsgKeyArray::Sort() +{ +#ifdef DEBUG + m_sorted = true; +#endif + m_keys.Sort(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgKeyArray::GetKeyAt(int32_t aIndex, nsMsgKey *aKey) +{ + NS_ENSURE_ARG_POINTER(aKey); + *aKey = m_keys[aIndex]; + return NS_OK; +} + +NS_IMETHODIMP nsMsgKeyArray::GetLength(uint32_t *aLength) +{ + NS_ENSURE_ARG_POINTER(aLength); + *aLength = m_keys.Length(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgKeyArray::SetCapacity(uint32_t aCapacity) +{ + m_keys.SetCapacity(aCapacity); + return NS_OK; +} + +NS_IMETHODIMP nsMsgKeyArray::AppendElement(nsMsgKey aKey) +{ +#ifdef DEBUG + NS_ASSERTION(!m_sorted || m_keys.Length() == 0 || + aKey > m_keys[m_keys.Length() - 1], + "Inserting a new key at wrong position in a sorted key list!"); +#endif + m_keys.AppendElement(aKey); + return NS_OK; +} + +NS_IMETHODIMP nsMsgKeyArray::InsertElementSorted(nsMsgKey aKey) +{ + // Ths function should be removed after interfaces are not frozen for TB38. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgKeyArray::GetArray(uint32_t *aCount, nsMsgKey **aKeys) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aKeys); + *aCount = m_keys.Length(); + *aKeys = + (nsMsgKey *) nsMemory::Clone(m_keys.begin(), + m_keys.Length() * sizeof(nsMsgKey)); + return (*aKeys) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} diff --git a/mailnews/base/util/nsMsgKeyArray.h b/mailnews/base/util/nsMsgKeyArray.h new file mode 100644 index 000000000..02c952b77 --- /dev/null +++ b/mailnews/base/util/nsMsgKeyArray.h @@ -0,0 +1,33 @@ +/* -*- 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 nsMsgKeyArray_h__ +#define nsMsgKeyArray_h__ + +#include "nsIMsgKeyArray.h" +#include "nsTArray.h" + +/* + * This class is a thin wrapper around an nsTArray<nsMsgKey> + */ +class nsMsgKeyArray : public nsIMsgKeyArray +{ +public: + nsMsgKeyArray(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGKEYARRAY + + nsTArray<nsMsgKey> m_keys; + +private: + virtual ~nsMsgKeyArray(); + +#ifdef DEBUG + bool m_sorted; +#endif +}; + +#endif diff --git a/mailnews/base/util/nsMsgKeySet.cpp b/mailnews/base/util/nsMsgKeySet.cpp new file mode 100644 index 000000000..427fb61af --- /dev/null +++ b/mailnews/base/util/nsMsgKeySet.cpp @@ -0,0 +1,1520 @@ +/* -*- 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/. */ + +#include "msgCore.h" // precompiled header... +#include "prlog.h" + +#include "MailNewsTypes.h" +#include "nsMsgKeySet.h" +#include "prprf.h" +#include "prmem.h" +#include "nsTArray.h" +#include "nsMemory.h" +#include <ctype.h> + +#if defined(DEBUG_seth_) || defined(DEBUG_sspitzer_) +#define DEBUG_MSGKEYSET 1 +#endif + +/* A compressed encoding for sets of article. This is usually for lines from + the newsrc, which have article lists like + + 1-29627,29635,29658,32861-32863 + + so the data has these properties: + + - strictly increasing + - large subsequences of monotonically increasing ranges + - gaps in the set are usually small, but not always + - consecutive ranges tend to be large + + The biggest win is to run-length encode the data, storing ranges as two + numbers (start+length or start,end). We could also store each number as a + delta from the previous number for further compression, but that gets kind + of tricky, since there are no guarentees about the sizes of the gaps, and + we'd have to store variable-length words. + + Current data format: + + DATA := SIZE [ CHUNK ]* + CHUNK := [ RANGE | VALUE ] + RANGE := -LENGTH START + START := VALUE + LENGTH := int32_t + VALUE := a literal positive integer, for now + it could also be an offset from the previous value. + LENGTH could also perhaps be a less-than-32-bit quantity, + at least most of the time. + + Lengths of CHUNKs are stored negative to distinguish the beginning of + a chunk from a literal: negative means two-word sequence, positive + means one-word sequence. + + 0 represents a literal 0, but should not occur, and should never occur + except in the first position. + + A length of -1 won't occur either, except temporarily - a sequence of + two elements is represented as two literals, since they take up the same + space. + + Another optimization we make is to notice that we typically ask the + question ``is N a member of the set'' for increasing values of N. So the + set holds a cache of the last value asked for, and can simply resume the + search from there. */ + +nsMsgKeySet::nsMsgKeySet(/* MSG_NewsHost* host*/) +{ + MOZ_COUNT_CTOR(nsMsgKeySet); + m_cached_value = -1; + m_cached_value_index = 0; + m_length = 0; + m_data_size = 10; + m_data = (int32_t *) PR_Malloc (sizeof (int32_t) * m_data_size); +#ifdef NEWSRC_DOES_HOST_STUFF + m_host = host; +#endif +} + + +nsMsgKeySet::~nsMsgKeySet() +{ + MOZ_COUNT_DTOR(nsMsgKeySet); + PR_FREEIF(m_data); +} + + +bool nsMsgKeySet::Grow() +{ + int32_t new_size; + int32_t *new_data; + new_size = m_data_size * 2; + new_data = (int32_t *) PR_REALLOC (m_data, sizeof (int32_t) * new_size); + if (! new_data) + return false; + m_data_size = new_size; + m_data = new_data; + return true; +} + + +nsMsgKeySet::nsMsgKeySet(const char* numbers /* , MSG_NewsHost* host */) +{ + int32_t *head, *tail, *end; + MOZ_COUNT_CTOR(nsMsgKeySet); + +#ifdef NEWSRC_DOES_HOST_STUFF + m_host = host; +#endif + m_cached_value = -1; + m_cached_value_index = 0; + m_length = 0; + m_data_size = 10; + m_data = (int32_t *) PR_Malloc (sizeof (int32_t) * m_data_size); + if (!m_data) return; + + head = m_data; + tail = head; + end = head + m_data_size; + + if(!numbers) { + return; + } + + while (isspace (*numbers)) numbers++; + while (*numbers) { + int32_t from = 0; + int32_t to; + + if (tail >= end - 4) { + /* out of room! */ + int32_t tailo = tail - head; + if (!Grow()) { + PR_FREEIF(m_data); + return; + } + /* data may have been relocated */ + head = m_data; + tail = head + tailo; + end = head + m_data_size; + } + + while (isspace(*numbers)) numbers++; + if (*numbers && !isdigit(*numbers)) { + break; /* illegal character */ + } + while (isdigit (*numbers)) { + from = (from * 10) + (*numbers++ - '0'); + } + while (isspace(*numbers)) numbers++; + if (*numbers != '-') { + to = from; + } else { + to = 0; + numbers++; + while (*numbers >= '0' && *numbers <= '9') + to = (to * 10) + (*numbers++ - '0'); + while (isspace(*numbers)) numbers++; + } + + if (to < from) to = from; /* illegal */ + + /* This is a hack - if the newsrc file specifies a range 1-x as + being read, we internally pretend that article 0 is read as well. + (But if only 2-x are read, then 0 is not read.) This is needed + because some servers think that article 0 is an article (I think) + but some news readers (including Netscape 1.1) choke if the .newsrc + file has lines beginning with 0... ### */ + if (from == 1) from = 0; + + if (to == from) { + /* Write it as a literal */ + *tail = from; + tail++; + } else /* Write it as a range. */ { + *tail = -(to - from); + tail++; + *tail = from; + tail++; + } + + while (*numbers == ',' || isspace(*numbers)) { + numbers++; + } + } + + m_length = tail - head; /* size of data */ +} + + + +nsMsgKeySet* +nsMsgKeySet::Create(/*MSG_NewsHost* host*/) +{ + nsMsgKeySet* set = new nsMsgKeySet(/* host */); + if (set && set->m_data == NULL) { + delete set; + set = NULL; + } + return set; +} + + +nsMsgKeySet* +nsMsgKeySet::Create(const char* value /* , MSG_NewsHost* host */) +{ +#ifdef DEBUG_MSGKEYSET + printf("create from %s\n",value); +#endif + + nsMsgKeySet* set = new nsMsgKeySet(value /* , host */); + if (set && set->m_data == NULL) { + delete set; + set = NULL; + } + return set; +} + + + +/* Returns the lowest non-member of the set greater than 0. + */ +int32_t +nsMsgKeySet::FirstNonMember () +{ + if (m_length <= 0) { + return 1; + } else if(m_data[0] < 0 && m_data[1] != 1 && m_data[1] != 0) { + /* first range not equal to 0 or 1, always return 1 */ + return 1; + } else if (m_data[0] < 0) { + /* it's a range */ + /* If there is a range [N-M] we can presume that M+1 is not in the + set. */ + return (m_data[1] - m_data[0] + 1); + } else { + /* it's a literal */ + if (m_data[0] == 1) { + /* handle "1,..." */ + if (m_length > 1 && m_data[1] == 2) { + /* This is "1,2,M-N,..." or "1,2,M,..." where M >= 4. Note + that M will never be 3, because in that case we would have + started with a range: "1-3,..." */ + return 3; + } else { + return 2; /* handle "1,M-N,.." or "1,M,..." + where M >= 3; */ + } + } + else if (m_data[0] == 0) { + /* handle "0,..." */ + if (m_length > 1 && m_data[1] == 1) { + /* this is 0,1, (see above) */ + return 2; + } + else { + return 1; + } + + } else { + /* handle "M,..." where M >= 2. */ + return 1; + } + } +} + + +nsresult +nsMsgKeySet::Output(char **outputStr) +{ + NS_ENSURE_ARG(outputStr); + int32_t size; + int32_t *head; + int32_t *tail; + int32_t *end; + int32_t s_size; + char *s_head; + char *s, *s_end; + int32_t last_art = -1; + + *outputStr = nullptr; + + size = m_length; + head = m_data; + tail = head; + end = head + size; + + s_size = (size * 12) + 10; // dmb - try to make this allocation get used at least once. + s_head = (char *) moz_xmalloc(s_size); + if (! s_head) return NS_ERROR_OUT_OF_MEMORY; + + s_head[0] = '\0'; // otherwise, s_head will contain garbage. + s = s_head; + s_end = s + s_size; + + while (tail < end) { + int32_t from; + int32_t to; + + if (s > (s_end - (12 * 2 + 10))) { /* 12 bytes for each number (enough + for "2147483647" aka 2^31-1), + plus 10 bytes of slop. */ + int32_t so = s - s_head; + s_size += 200; + char* tmp = (char *) moz_xmalloc(s_size); + if (tmp) PL_strcpy(tmp, s_head); + free(s_head); + s_head = tmp; + if (!s_head) return NS_ERROR_OUT_OF_MEMORY; + s = s_head + so; + s_end = s_head + s_size; + } + + if (*tail < 0) { + /* it's a range */ + from = tail[1]; + to = from + (-(tail[0])); + tail += 2; + } + else /* it's a literal */ + { + from = *tail; + to = from; + tail++; + } + if (from == 0) { + from = 1; /* See 'hack' comment above ### */ + } + if (from <= last_art) from = last_art + 1; + if (from <= to) { + if (from < to) { + PR_snprintf(s, s_end - s, "%lu-%lu,", from, to); + } else { + PR_snprintf(s, s_end - s, "%lu,", from); + } + s += PL_strlen(s); + last_art = to; + } + } + if (last_art >= 0) { + s--; /* Strip off the last ',' */ + } + + *s = 0; + + *outputStr = s_head; + return NS_OK; +} + +int32_t +nsMsgKeySet::GetLastMember() +{ + if (m_length > 1) + { + int32_t nextToLast = m_data[m_length - 2]; + if (nextToLast < 0) // is range at end? + { + int32_t last = m_data[m_length - 1]; + return (-nextToLast + last - 1); + } + else // no, so last number must be last member + { + return m_data[m_length - 1]; + } + } + else if (m_length == 1) + return m_data[0]; // must be only 1 read. + else + return 0; +} + +void nsMsgKeySet::SetLastMember(int32_t newHighWaterMark) +{ + if (newHighWaterMark < GetLastMember()) + { + while (true) + { + if (m_length > 1) + { + int32_t nextToLast = m_data[m_length - 2]; + int32_t curHighWater; + if (nextToLast < 0) // is range at end? + { + int32_t rangeStart = m_data[m_length - 1]; + int32_t rangeLength = -nextToLast; + curHighWater = (rangeLength + rangeStart - 1); + if (curHighWater > newHighWaterMark) + { + if (rangeStart > newHighWaterMark) + { + m_length -= 2; // throw away whole range + } + else if (rangeStart == newHighWaterMark) + { + // turn range into single element. + m_data[m_length - 2] = newHighWaterMark; + m_length--; + break; + } + else // just shorten range + { + m_data[m_length - 2] = -(newHighWaterMark - rangeStart); + break; + } + } + else { + // prevent the infinite loop + // see bug #13062 + break; + } + } + else if (m_data[m_length - 1] > newHighWaterMark) // no, so last number must be last member + { + m_length--; + } + else + break; + } + else + break; + } + // well, the whole range is probably invalid, because the server probably re-ordered ids, + // but what can you do? +#ifdef NEWSRC_DOES_HOST_STUFF + if (m_host) + m_host->MarkDirty(); +#endif + } +} + +int32_t +nsMsgKeySet::GetFirstMember() +{ + if (m_length > 1) + { + int32_t first = m_data[0]; + if (first < 0) // is range at start? + { + int32_t second = m_data[1]; + return (second); + } + else // no, so first number must be first member + { + return m_data[0]; + } + } + else if (m_length == 1) + return m_data[0]; // must be only 1 read. + else + return 0; +} + +/* Re-compresses a `nsMsgKeySet' object. + + The assumption is made that the `nsMsgKeySet' is syntactically correct + (all ranges have a length of at least 1, and all values are non- + decreasing) but will optimize the compression, for example, merging + consecutive literals or ranges into one range. + + Returns true if successful, false if there wasn't enough memory to + allocate scratch space. + + #### This should be changed to modify the buffer in place. + + Also note that we never call Optimize() unless we actually changed + something, so it's a great place to tell the MSG_NewsHost* that something + changed. + */ +bool +nsMsgKeySet::Optimize() +{ + int32_t input_size; + int32_t output_size; + int32_t *input_tail; + int32_t *output_data; + int32_t *output_tail; + int32_t *input_end; + int32_t *output_end; + + input_size = m_length; + output_size = input_size + 1; + input_tail = m_data; + output_data = (int32_t *) PR_Malloc (sizeof (int32_t) * output_size); + if (!output_data) return false; + + output_tail = output_data; + input_end = input_tail + input_size; + output_end = output_data + output_size; + + /* We're going to modify the set, so invalidate the cache. */ + m_cached_value = -1; + + while (input_tail < input_end) { + int32_t from, to; + bool range_p = (*input_tail < 0); + + if (range_p) { + /* it's a range */ + from = input_tail[1]; + to = from + (-(input_tail[0])); + + /* Copy it over */ + *output_tail++ = *input_tail++; + *output_tail++ = *input_tail++; + } else { + /* it's a literal */ + from = *input_tail; + to = from; + + /* Copy it over */ + *output_tail++ = *input_tail++; + } + NS_ASSERTION(output_tail < output_end, "invalid end of output string"); + if (output_tail >= output_end) { + PR_Free(output_data); + return false; + } + + /* As long as this chunk is followed by consecutive chunks, + keep extending it. */ + while (input_tail < input_end && + ((*input_tail > 0 && /* literal... */ + *input_tail == to + 1) || /* ...and consecutive, or */ + (*input_tail <= 0 && /* range... */ + input_tail[1] == to + 1)) /* ...and consecutive. */ + ) { + if (! range_p) { + /* convert the literal to a range. */ + output_tail++; + output_tail [-2] = 0; + output_tail [-1] = from; + range_p = true; + } + + if (*input_tail > 0) { /* literal */ + output_tail[-2]--; /* increase length by 1 */ + to++; + input_tail++; + } else { + int32_t L2 = (- *input_tail) + 1; + output_tail[-2] -= L2; /* increase length by N */ + to += L2; + input_tail += 2; + } + } + } + + PR_Free (m_data); + m_data = output_data; + m_data_size = output_size; + m_length = output_tail - output_data; + + /* One last pass to turn [N - N+1] into [N, N+1]. */ + output_tail = output_data; + output_end = output_tail + m_length; + while (output_tail < output_end) { + if (*output_tail < 0) { + /* it's a range */ + if (output_tail[0] == -1) { + output_tail[0] = output_tail[1]; + output_tail[1]++; + } + output_tail += 2; + } else { + /* it's a literal */ + output_tail++; + } + } + +#ifdef NEWSRC_DOES_HOST_STUFF + if (m_host) m_host->MarkDirty(); +#endif + return true; +} + + + +bool +nsMsgKeySet::IsMember(int32_t number) +{ + bool value = false; + int32_t size; + int32_t *head; + int32_t *tail; + int32_t *end; + + size = m_length; + head = m_data; + tail = head; + end = head + size; + + /* If there is a value cached, and that value is smaller than the + value we're looking for, skip forward that far. */ + if (m_cached_value > 0 && + m_cached_value < number) { + tail += m_cached_value_index; + } + + while (tail < end) { + if (*tail < 0) { + /* it's a range */ + int32_t from = tail[1]; + int32_t to = from + (-(tail[0])); + if (from > number) { + /* This range begins after the number - we've passed it. */ + value = false; + goto DONE; + } else if (to >= number) { + /* In range. */ + value = true; + goto DONE; + } else { + tail += 2; + } + } + else { + /* it's a literal */ + if (*tail == number) { + /* bang */ + value = true; + goto DONE; + } else if (*tail > number) { + /* This literal is after the number - we've passed it. */ + value = false; + goto DONE; + } else { + tail++; + } + } + } + +DONE: + /* Store the position of this chunk for next time. */ + m_cached_value = number; + m_cached_value_index = tail - head; + + return value; +} + + +int +nsMsgKeySet::Add(int32_t number) +{ + int32_t size; + int32_t *head; + int32_t *tail; + int32_t *end; + +#ifdef DEBUG_MSGKEYSET + printf("add %d\n",number); +#endif + + size = m_length; + head = m_data; + tail = head; + end = head + size; + + NS_ASSERTION (number >= 0, "can't have negative items"); + if (number < 0) + return 0; + + /* We're going to modify the set, so invalidate the cache. */ + m_cached_value = -1; + + while (tail < end) { + if (*tail < 0) { + /* it's a range */ + int32_t from = tail[1]; + int32_t to = from + (-(tail[0])); + + if (from <= number && to >= number) { + /* This number is already present - we don't need to do + anything. */ + return 0; + } + + if (to > number) { + /* We have found the point before which the new number + should be inserted. */ + break; + } + + tail += 2; + } else { + /* it's a literal */ + if (*tail == number) { + /* This number is already present - we don't need to do + anything. */ + return 0; + } + + if (*tail > number) { + /* We have found the point before which the new number + should be inserted. */ + break; + } + + tail++; + } + } + + /* At this point, `tail' points to a position in the set which represents + a value greater than `new'; or it is at `end'. In the interest of + avoiding massive duplication of code, simply insert a literal here and + then run the optimizer. + */ + int mid = (tail - head); + + if (m_data_size <= m_length + 1) { + int endo = end - head; + if (!Grow()) { + // out of memory + return -1; + } + head = m_data; + end = head + endo; + } + + if (tail == end) { + /* at the end */ + /* Add a literal to the end. */ + m_data[m_length++] = number; + } else { + /* need to insert (or edit) in the middle */ + int32_t i; + for (i = size; i > mid; i--) { + m_data[i] = m_data[i-1]; + } + m_data[i] = number; + m_length++; + } + + Optimize(); + return 1; +} + + + +int +nsMsgKeySet::Remove(int32_t number) +{ + int32_t size; + int32_t *head; + int32_t *tail; + int32_t *end; +#ifdef DEBUG_MSGKEYSET + printf("remove %d\n",number); +#endif + + size = m_length; + head = m_data; + tail = head; + end = head + size; + + // **** I am not sure this is a right thing to comment the following + // statements out. The reason for this is due to the implementation of + // offline save draft and template. We use faked UIDs (negative ids) for + // offline draft and template in order to distinguish them from real + // UID. David I need your help here. **** jt + + // PR_ASSERT(number >= 0); + // if (number < 0) { + // return -1; + /// } + + /* We're going to modify the set, so invalidate the cache. */ + m_cached_value = -1; + + while (tail < end) { + int32_t mid = (tail - m_data); + + if (*tail < 0) { + /* it's a range */ + int32_t from = tail[1]; + int32_t to = from + (-(tail[0])); + + if (number < from || number > to) { + /* Not this range */ + tail += 2; + continue; + } + + if (to == from + 1) { + /* If this is a range [N - N+1] and we are removing M + (which must be either N or N+1) replace it with a + literal. This reduces the length by 1. */ + m_data[mid] = (number == from ? to : from); + while (++mid < m_length) { + m_data[mid] = m_data[mid+1]; + } + m_length--; + Optimize(); + return 1; + } else if (to == from + 2) { + /* If this is a range [N - N+2] and we are removing M, + replace it with the literals L,M (that is, either + (N, N+1), (N, N+2), or (N+1, N+2). The overall + length remains the same. */ + m_data[mid] = from; + m_data[mid+1] = to; + if (from == number) { + m_data[mid] = from+1; + } else if (to == number) { + m_data[mid+1] = to-1; + } + Optimize(); + return 1; + } else if (from == number) { + /* This number is at the beginning of a long range (meaning a + range which will still be long enough to remain a range.) + Increase start and reduce length of the range. */ + m_data[mid]++; + m_data[mid+1]++; + Optimize(); + return 1; + } else if (to == number) { + /* This number is at the end of a long range (meaning a range + which will still be long enough to remain a range.) + Just decrease the length of the range. */ + m_data[mid]++; + Optimize(); + return 1; + } else { + /* The number being deleted is in the middle of a range which + must be split. This increases overall length by 2. + */ + int32_t i; + int endo = end - head; + if (m_data_size - m_length <= 2) { + if (!Grow()) + // out of memory + return -1; + } + head = m_data; + end = head + endo; + + for (i = m_length + 2; i > mid + 2; i--) { + m_data[i] = m_data[i-2]; + } + + m_data[mid] = (- (number - from - 1)); + m_data[mid+1] = from; + m_data[mid+2] = (- (to - number - 1)); + m_data[mid+3] = number + 1; + m_length += 2; + + /* Oops, if we've ended up with a range with a 0 length, + which is illegal, convert it to a literal, which reduces + the overall length by 1. */ + if (m_data[mid] == 0) { + /* first range */ + m_data[mid] = m_data[mid+1]; + for (i = mid + 1; i < m_length; i++) { + m_data[i] = m_data[i+1]; + } + m_length--; + } + if (m_data[mid+2] == 0) { + /* second range */ + m_data[mid+2] = m_data[mid+3]; + for (i = mid + 3; i < m_length; i++) { + m_data[i] = m_data[i+1]; + } + m_length--; + } + Optimize(); + return 1; + } + } else { + /* it's a literal */ + if (*tail != number) { + /* Not this literal */ + tail++; + continue; + } + + /* Excise this literal. */ + m_length--; + while (mid < m_length) { + m_data[mid] = m_data[mid+1]; + mid++; + } + Optimize(); + return 1; + } + } + + /* It wasn't here at all. */ + return 0; +} + + +static int32_t* +msg_emit_range(int32_t* tmp, int32_t a, int32_t b) +{ + if (a == b) { + *tmp++ = a; + } else { + NS_ASSERTION(a < b && a >= 0, "range is out of order"); + *tmp++ = -(b - a); + *tmp++ = a; + } + return tmp; +} + + +int +nsMsgKeySet::AddRange(int32_t start, int32_t end) +{ + int32_t tmplength; + int32_t* tmp; + int32_t* in; + int32_t* out; + int32_t* tail; + int32_t a; + int32_t b; + bool didit = false; + + /* We're going to modify the set, so invalidate the cache. */ + m_cached_value = -1; + + NS_ASSERTION(start <= end, "invalid range"); + if (start > end) return -1; + + if (start == end) { + return Add(start); + } + + tmplength = m_length + 2; + tmp = (int32_t*) PR_Malloc(sizeof(int32_t) * tmplength); + + if (!tmp) + // out of memory + return -1; + + in = m_data; + out = tmp; + tail = in + m_length; + +#define EMIT(x, y) out = msg_emit_range(out, x, y) + + while (in < tail) { + // Set [a,b] to be this range. + if (*in < 0) { + b = - *in++; + a = *in++; + b += a; + } else { + a = b = *in++; + } + + if (a <= start && b >= end) { + // We already have the entire range marked. + PR_Free(tmp); + return 0; + } + if (start > b + 1) { + // No overlap yet. + EMIT(a, b); + } else if (end < a - 1) { + // No overlap, and we passed it. + EMIT(start, end); + EMIT(a, b); + didit = true; + break; + } else { + // The ranges overlap. Suck this range into our new range, and + // keep looking for other ranges that might overlap. + start = start < a ? start : a; + end = end > b ? end : b; + } + } + if (!didit) EMIT(start, end); + while (in < tail) { + *out++ = *in++; + } + +#undef EMIT + + PR_Free(m_data); + m_data = tmp; + m_length = out - tmp; + m_data_size = tmplength; +#ifdef NEWSRC_DOES_HOST_STUFF + if (m_host) m_host->MarkDirty(); +#endif + return 1; +} + +int32_t +nsMsgKeySet::CountMissingInRange(int32_t range_start, int32_t range_end) +{ + int32_t count; + int32_t *head; + int32_t *tail; + int32_t *end; + + NS_ASSERTION (range_start >= 0 && range_end >= 0 && range_end >= range_start, "invalid range"); + if (range_start < 0 || range_end < 0 || range_end < range_start) return -1; + + head = m_data; + tail = head; + end = head + m_length; + + count = range_end - range_start + 1; + + while (tail < end) { + if (*tail < 0) { + /* it's a range */ + int32_t from = tail[1]; + int32_t to = from + (-(tail[0])); + if (from < range_start) from = range_start; + if (to > range_end) to = range_end; + + if (to >= from) + count -= (to - from + 1); + + tail += 2; + } else { + /* it's a literal */ + if (*tail >= range_start && *tail <= range_end) count--; + tail++; + } + NS_ASSERTION (count >= 0, "invalid count"); + } + return count; +} + + +int +nsMsgKeySet::FirstMissingRange(int32_t min, int32_t max, + int32_t* first, int32_t* last) +{ + int32_t size; + int32_t *head; + int32_t *tail; + int32_t *end; + int32_t from = 0; + int32_t to = 0; + int32_t a; + int32_t b; + + NS_ASSERTION(first && last, "invalid parameter"); + if (!first || !last) return -1; + + *first = *last = 0; + + NS_ASSERTION(min <= max && min > 0, "invalid min or max param"); + if (min > max || min <= 0) return -1; + + size = m_length; + head = m_data; + tail = head; + end = head + size; + + while (tail < end) { + a = to + 1; + if (*tail < 0) { /* We got a range. */ + from = tail[1]; + to = from + (-(tail[0])); + tail += 2; + } else { + from = to = tail[0]; + tail++; + } + b = from - 1; + /* At this point, [a,b] is the range of unread articles just before + the current range of read articles [from,to]. See if this range + intersects the [min,max] range we were given. */ + if (a > max) return 0; /* It's hopeless; there are none. */ + if (a <= b && b >= min) { + /* Ah-hah! We found an intersection. */ + *first = a > min ? a : min; + *last = b < max ? b : max; + return 0; + } + } + /* We found no holes in the newsrc that overlaps the range, nor did we hit + something read beyond the end of the range. So, the great infinite + range of unread articles at the end of any newsrc line intersects the + range we want, and we just need to return that. */ + a = to + 1; + *first = a > min ? a : min; + *last = max; + return 0; +} + +// I'm guessing we didn't include this because we didn't think we're going +// to need it. I'm not so sure. I'm putting it in for now. +int +nsMsgKeySet::LastMissingRange(int32_t min, int32_t max, + int32_t* first, int32_t* last) +{ + int32_t size; + int32_t *head; + int32_t *tail; + int32_t *end; + int32_t from = 0; + int32_t to = 0; + int32_t a; + int32_t b; + + NS_ASSERTION(first && last, "invalid null param"); + if (!first || !last) return -1; + + *first = *last = 0; + + + NS_ASSERTION(min <= max && min > 0, "invalid min or max"); + if (min > max || min <= 0) return -1; + + size = m_length; + head = m_data; + tail = head; + end = head + size; + + while (tail < end) { + a = to + 1; + if (*tail < 0) { /* We got a range. */ + from = tail[1]; + to = from + (-(tail[0])); + tail += 2; + } else { + from = to = tail[0]; + tail++; + } + b = from - 1; + /* At this point, [a,b] is the range of unread articles just before + the current range of read articles [from,to]. See if this range + intersects the [min,max] range we were given. */ + if (a > max) return 0; /* We're done. If we found something, it's already + sitting in [*first,*last]. */ + if (a <= b && b >= min) { + /* Ah-hah! We found an intersection. */ + *first = a > min ? a : min; + *last = b < max ? b : max; + /* Continue on, looking for a later range. */ + } + } + if (to < max) { + /* The great infinite range of unread articles at the end of any newsrc + line intersects the range we want, and we just need to return that. */ + a = to + 1; + *first = a > min ? a : min; + *last = max; + } + return 0; +} + +/** + * Fill the passed in aArray with the keys in the message key set. + */ +nsresult +nsMsgKeySet::ToMsgKeyArray(nsTArray<nsMsgKey> &aArray) +{ + int32_t size; + int32_t *head; + int32_t *tail; + int32_t *end; + int32_t last_art = -1; + + size = m_length; + head = m_data; + tail = head; + end = head + size; + + while (tail < end) { + int32_t from; + int32_t to; + + if (*tail < 0) { + /* it's a range */ + from = tail[1]; + to = from + (-(tail[0])); + tail += 2; + } + else /* it's a literal */ + { + from = *tail; + to = from; + tail++; + } + // The horrible news-hack used to adjust from to 1 if it was zero right + // here, but there is no longer a consumer of this method with that + // broken use-case. + if (from <= last_art) from = last_art + 1; + if (from <= to) { + if (from < to) { + for (int32_t i = from; i <= to ; ++i ) { + aArray.AppendElement(i); + } + } else { + aArray.AppendElement(from); + } + last_art = to; + } + } + + return NS_OK; +} + + +#ifdef DEBUG /* A lot of test cases for the above */ + +#define countof(x) (sizeof(x) / sizeof(*(x))) + +void +nsMsgKeySet::test_decoder (const char *string) +{ + nsMsgKeySet set(string /* , NULL */); + char* tmp; + set.Output(&tmp); + printf ("\t\"%s\"\t--> \"%s\"\n", string, tmp); + free(tmp); +} + + +#define START(STRING) \ + string = STRING; \ + if (!(set = nsMsgKeySet::Create(string))) abort () + +#define FROB(N,PUSHP) \ + i = N; \ + if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \ + printf ("%3lu: %-58s %c %3lu =\n", (unsigned long)set->m_length, s, \ + (PUSHP ? '+' : '-'), (unsigned long)i); \ + free(s); \ + if (PUSHP \ + ? set->Add(i) < 0 \ + : set->Remove(i) < 0) \ + abort (); \ + if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \ + printf ("%3lu: %-58s optimized =\n", (unsigned long)set->m_length, s); \ + free(s); \ + +#define END() \ + if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \ + printf ("%3lu: %s\n\n", (unsigned long)set->m_length, s); \ + free(s); \ + delete set; \ + + + +void +nsMsgKeySet::test_adder (void) +{ + const char *string; + nsMsgKeySet *set; + char *s; + int32_t i; + + START("0-70,72-99,105,107,110-111,117-200"); + + FROB(205, true); + FROB(206, true); + FROB(207, true); + FROB(208, true); + FROB(208, true); + FROB(109, true); + FROB(72, true); + + FROB(205, false); + FROB(206, false); + FROB(207, false); + FROB(208, false); + FROB(208, false); + FROB(109, false); + FROB(72, false); + + FROB(72, true); + FROB(109, true); + FROB(208, true); + FROB(208, true); + FROB(207, true); + FROB(206, true); + FROB(205, true); + + FROB(205, false); + FROB(206, false); + FROB(207, false); + FROB(208, false); + FROB(208, false); + FROB(109, false); + FROB(72, false); + + FROB(100, true); + FROB(101, true); + FROB(102, true); + FROB(103, true); + FROB(106, true); + FROB(104, true); + FROB(109, true); + FROB(108, true); + END(); + + START("1-6"); FROB(7, false); END(); + START("1-6"); FROB(6, false); END(); + START("1-6"); FROB(5, false); END(); + START("1-6"); FROB(4, false); END(); + START("1-6"); FROB(3, false); END(); + START("1-6"); FROB(2, false); END(); + START("1-6"); FROB(1, false); END(); + START("1-6"); FROB(0, false); END(); + + START("1-3"); FROB(1, false); END(); + START("1-3"); FROB(2, false); END(); + START("1-3"); FROB(3, false); END(); + + START("1,3,5-7,9,10"); FROB(5, false); END(); + START("1,3,5-7,9,10"); FROB(6, false); END(); + START("1,3,5-7,9,10"); FROB(7, false); FROB(7, true); FROB(8, true); + FROB (4, true); FROB (2, false); FROB (2, true); + + FROB (4, false); FROB (5, false); FROB (6, false); FROB (7, false); + FROB (8, false); FROB (9, false); FROB (10, false); FROB (3, false); + FROB (2, false); FROB (1, false); FROB (1, false); FROB (0, false); + END(); +} + +#undef START +#undef FROB +#undef END + + + +#define START(STRING) \ + string = STRING; \ + if (!(set = nsMsgKeySet::Create(string))) abort () + +#define FROB(N,M) \ + i = N; \ + j = M; \ + if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \ + printf ("%3lu: %-58s + %3lu-%3lu =\n", (unsigned long)set->m_length, s, (unsigned long)i, (unsigned long)j); \ + free(s); \ + switch (set->AddRange(i, j)) { \ + case 0: \ + printf("(no-op)\n"); \ + break; \ + case 1: \ + break; \ + default: \ + abort(); \ + } \ + if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \ + printf ("%3lu: %-58s\n", (unsigned long)set->m_length, s); \ + free(s); \ + + +#define END() \ + if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \ + printf ("%3lu: %s\n\n", (unsigned long)set->m_length, s); \ + free(s); \ + delete set; + + +void +nsMsgKeySet::test_ranges(void) +{ + const char *string; + nsMsgKeySet *set; + char *s; + int32_t i; + int32_t j; + + START("20-40,72-99,105,107,110-111,117-200"); + + FROB(205, 208); + FROB(50, 70); + FROB(0, 10); + FROB(112, 113); + FROB(101, 101); + FROB(5, 75); + FROB(103, 109); + FROB(2, 20); + FROB(1, 9999); + + END(); + + +#undef START +#undef FROB +#undef END +} + + + + +#define TEST(N) \ + if (! with_cache) set->m_cached_value = -1; \ + if (!(NS_SUCCEEDED(set->Output(&s)))) abort (); \ + printf (" %3d = %s\n", N, \ + (set->IsMember(N) ? "true" : "false")); \ + free(s); + +void +nsMsgKeySet::test_member(bool with_cache) +{ + nsMsgKeySet *set; + char *s; + + const char *st1 = "1-70,72-99,105,107,110-111,117-200"; + printf ("\n\nTesting %s (with%s cache)\n", st1, with_cache ? "" : "out"); + if (!(set = Create(st1))) { + abort(); + } + + TEST(-1); + TEST(0); + TEST(1); + TEST(20); + + delete set; + const char *st2 = "0-70,72-99,105,107,110-111,117-200"; + printf ("\n\nTesting %s (with%s cache)\n", st2, with_cache ? "" : "out"); + if (!(set = Create(st2))) { + abort(); + } + + TEST(-1); + TEST(0); + TEST(1); + TEST(20); + TEST(69); + TEST(70); + TEST(71); + TEST(72); + TEST(73); + TEST(74); + TEST(104); + TEST(105); + TEST(106); + TEST(107); + TEST(108); + TEST(109); + TEST(110); + TEST(111); + TEST(112); + TEST(116); + TEST(117); + TEST(118); + TEST(119); + TEST(200); + TEST(201); + TEST(65535); + + delete set; +} + +#undef TEST + + +// static void +// test_newsrc (char *file) +// { +// FILE *fp = fopen (file, "r"); +// char buf [1024]; +// if (! fp) abort (); +// while (fgets (buf, sizeof (buf), fp)) +// { +// if (!strncmp (buf, "options ", 8)) +// fwrite (buf, 1, strlen (buf), stdout); +// else +// { +// char *sep = buf; +// while (*sep != 0 && *sep != ':' && *sep != '!') +// sep++; +// if (*sep) sep++; +// while (isspace (*sep)) sep++; +// fwrite (buf, 1, sep - buf, stdout); +// if (*sep) +// { +// char *s; +// msg_NewsRCSet *set = msg_parse_newsrc_set (sep, &allocinfo); +// if (! set) +// abort (); +// if (! msg_OptimizeNewsRCSet (set)) +// abort (); +// if (! ((s = msg_format_newsrc_set (set)))) +// abort (); +// msg_free_newsrc_set (set, &allocinfo); +// fwrite (s, 1, strlen (s), stdout); +// free (s); +// fwrite ("\n", 1, 1, stdout); +// } +// } +// } +// fclose (fp); +// } + +void +nsMsgKeySet::RunTests () +{ + + test_decoder (""); + test_decoder (" "); + test_decoder ("0"); + test_decoder ("1"); + test_decoder ("123"); + test_decoder (" 123 "); + test_decoder (" 123 4"); + test_decoder (" 1,2, 3, 4"); + test_decoder ("0-70,72-99,100,101"); + test_decoder (" 0-70 , 72 - 99 ,100,101 "); + test_decoder ("0 - 268435455"); + /* This one overflows - we can't help it. + test_decoder ("0 - 4294967295"); */ + + test_adder (); + + test_ranges(); + + test_member (false); + test_member (true); + + // test_newsrc ("/u/montulli/.newsrc"); + /* test_newsrc ("/u/jwz/.newsrc");*/ +} + +#endif /* DEBUG */ diff --git a/mailnews/base/util/nsMsgKeySet.h b/mailnews/base/util/nsMsgKeySet.h new file mode 100644 index 000000000..c33306ad8 --- /dev/null +++ b/mailnews/base/util/nsMsgKeySet.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; 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 _nsMsgKeySet_H_ +#define _nsMsgKeySet_H_ + +#include "msgCore.h" +#include "nsTArray.h" + +// nsMsgKeySet represents a set of articles. Typically, it is the set of +// read articles from a .newsrc file, but it can be used for other purposes +// too. + +#if 0 +// If a MSG_NewsHost* is supplied to the creation routine, then that +// MSG_NewsHost will be notified whenever a change is made to set. +class MSG_NewsHost; +#endif + +class NS_MSG_BASE nsMsgKeySet { +public: + // Creates an empty set. + static nsMsgKeySet* Create(/* MSG_NewsHost* host = NULL*/); + + // Creates a set from the list of numbers, as might be found in a + // newsrc file. + static nsMsgKeySet* Create(const char* str/* , MSG_NewsHost* host = NULL*/); + ~nsMsgKeySet(); + + // FirstNonMember() returns the lowest non-member of the set that is + // greater than 0. + int32_t FirstNonMember(); + + // Output() converts to a string representation suitable for writing to a + // .newsrc file. + nsresult Output(char **outputStr); + + // IsMember() returns whether the given article is a member of this set. + bool IsMember(int32_t art); + + // Add() adds the given article to the set. (Returns 1 if a change was + // made, 0 if it was already there, and negative on error.) + int Add(int32_t art); + + // Remove() removes the given article from the set. + int Remove(int32_t art); + + // AddRange() adds the (inclusive) given range of articles to the set. + int AddRange(int32_t first, int32_t last); + + // CountMissingInRange() takes an inclusive range of articles and returns + // the number of articles in that range which are not in the set. + int32_t CountMissingInRange(int32_t start, int32_t end); + + // FirstMissingRange() takes an inclusive range and finds the first range + // of articles that are not in the set. If none, return zeros. + int FirstMissingRange(int32_t min, int32_t max, int32_t* first, int32_t* last); + + + // LastMissingRange() takes an inclusive range and finds the last range + // of articles that are not in the set. If none, return zeros. + int LastMissingRange(int32_t min, int32_t max, int32_t* first, int32_t* last); + + int32_t GetLastMember(); + int32_t GetFirstMember(); + void SetLastMember(int32_t highWaterMark); + // For debugging only... + int32_t getLength() {return m_length;} + +/** + * Fill the passed in aArray with the keys in the message key set. + */ + nsresult ToMsgKeyArray(nsTArray<nsMsgKey> &aArray); + +#ifdef DEBUG + static void RunTests(); +#endif + +protected: + nsMsgKeySet(/* MSG_NewsHost* host */); + nsMsgKeySet(const char* /* , MSG_NewsHost* host */); + bool Grow(); + bool Optimize(); + +#ifdef DEBUG + static void test_decoder(const char*); + static void test_adder(); + static void test_ranges(); + static void test_member(bool with_cache); +#endif + + int32_t *m_data; /* the numbers composing the `chunks' */ + int32_t m_data_size; /* size of that malloc'ed block */ + int32_t m_length; /* active area */ + + int32_t m_cached_value; /* a potential set member, or -1 if unset*/ + int32_t m_cached_value_index; /* the index into `data' at which a search + to determine whether `cached_value' was + a member of the set ended. */ +#ifdef NEWSRC_DOES_HOST_STUFF + MSG_NewsHost* m_host; +#endif +}; + + +#endif /* _nsMsgKeySet_H_ */ diff --git a/mailnews/base/util/nsMsgLineBuffer.cpp b/mailnews/base/util/nsMsgLineBuffer.cpp new file mode 100644 index 000000000..0a88cd840 --- /dev/null +++ b/mailnews/base/util/nsMsgLineBuffer.cpp @@ -0,0 +1,441 @@ +/* -*- Mode: C++; tab-width: 4; 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" +#include "prlog.h" +#include "prmem.h" +#include "nsMsgLineBuffer.h" +#include "nsAlgorithm.h" +#include "nsMsgUtils.h" +#include "nsIInputStream.h" // used by nsMsgLineStreamBuffer +#include <algorithm> + +nsByteArray::nsByteArray() +{ + MOZ_COUNT_CTOR(nsByteArray); + m_buffer = NULL; + m_bufferSize = 0; + m_bufferPos = 0; +} + +nsByteArray::~nsByteArray() +{ + MOZ_COUNT_DTOR(nsByteArray); + PR_FREEIF(m_buffer); +} + +nsresult nsByteArray::GrowBuffer(uint32_t desired_size, uint32_t quantum) +{ + if (m_bufferSize < desired_size) + { + char *new_buf; + uint32_t increment = desired_size - m_bufferSize; + if (increment < quantum) /* always grow by a minimum of N bytes */ + increment = quantum; + + + new_buf = (m_buffer + ? (char *) PR_REALLOC (m_buffer, (m_bufferSize + increment)) + : (char *) PR_MALLOC (m_bufferSize + increment)); + if (! new_buf) + return NS_ERROR_OUT_OF_MEMORY; + m_buffer = new_buf; + m_bufferSize += increment; + } + return NS_OK; +} + +nsresult nsByteArray::AppendString(const char *string) +{ + uint32_t strLength = (string) ? PL_strlen(string) : 0; + return AppendBuffer(string, strLength); + +} + +nsresult nsByteArray::AppendBuffer(const char *buffer, uint32_t length) +{ + nsresult ret = NS_OK; + if (m_bufferPos + length > m_bufferSize) + ret = GrowBuffer(m_bufferPos + length, 1024); + if (NS_SUCCEEDED(ret)) + { + memcpy(m_buffer + m_bufferPos, buffer, length); + m_bufferPos += length; + } + return ret; +} + +nsMsgLineBuffer::nsMsgLineBuffer(nsMsgLineBufferHandler *handler, bool convertNewlinesP) +{ + MOZ_COUNT_CTOR(nsMsgLineBuffer); + m_handler = handler; + m_convertNewlinesP = convertNewlinesP; + m_lookingForCRLF = true; +} + +nsMsgLineBuffer::~nsMsgLineBuffer() +{ + MOZ_COUNT_DTOR(nsMsgLineBuffer); +} + +void +nsMsgLineBuffer::SetLookingForCRLF(bool b) +{ + m_lookingForCRLF = b; +} + +nsresult nsMsgLineBuffer::BufferInput(const char *net_buffer, int32_t net_buffer_size) +{ + nsresult status = NS_OK; + if (m_bufferPos > 0 && m_buffer && m_buffer[m_bufferPos - 1] == '\r' && + net_buffer_size > 0 && net_buffer[0] != '\n') { + /* The last buffer ended with a CR. The new buffer does not start + with a LF. This old buffer should be shipped out and discarded. */ + PR_ASSERT(m_bufferSize > m_bufferPos); + if (m_bufferSize <= m_bufferPos) + return NS_ERROR_UNEXPECTED; + if (NS_FAILED(ConvertAndSendBuffer())) + return NS_ERROR_FAILURE; + m_bufferPos = 0; + } + while (net_buffer_size > 0) + { + const char *net_buffer_end = net_buffer + net_buffer_size; + const char *newline = 0; + const char *s; + + for (s = net_buffer; s < net_buffer_end; s++) + { + if (m_lookingForCRLF) { + /* Move forward in the buffer until the first newline. + Stop when we see CRLF, CR, or LF, or the end of the buffer. + *But*, if we see a lone CR at the *very end* of the buffer, + treat this as if we had reached the end of the buffer without + seeing a line terminator. This is to catch the case of the + buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n". + */ + if (*s == '\r' || *s == '\n') { + newline = s; + if (newline[0] == '\r') { + if (s == net_buffer_end - 1) { + /* CR at end - wait for the next character. */ + newline = 0; + break; + } + else if (newline[1] == '\n') { + /* CRLF seen; swallow both. */ + newline++; + } + } + newline++; + break; + } + } + else { + /* if not looking for a CRLF, stop at CR or LF. (for example, when parsing the newsrc file). this fixes #9896, where we'd lose the last line of anything we'd parse that used CR as the line break. */ + if (*s == '\r' || *s == '\n') { + newline = s; + newline++; + break; + } + } + } + + /* Ensure room in the net_buffer and append some or all of the current + chunk of data to it. */ + { + const char *end = (newline ? newline : net_buffer_end); + uint32_t desired_size = (end - net_buffer) + m_bufferPos + 1; + + if (desired_size >= m_bufferSize) + { + status = GrowBuffer (desired_size, 1024); + if (NS_FAILED(status)) + return status; + } + memcpy (m_buffer + m_bufferPos, net_buffer, (end - net_buffer)); + m_bufferPos += (end - net_buffer); + } + + /* Now m_buffer contains either a complete line, or as complete + a line as we have read so far. + + If we have a line, process it, and then remove it from `m_buffer'. + Then go around the loop again, until we drain the incoming data. + */ + if (!newline) + return NS_OK; + + if (NS_FAILED(ConvertAndSendBuffer())) + return NS_ERROR_FAILURE; + + net_buffer_size -= (newline - net_buffer); + net_buffer = newline; + m_bufferPos = 0; + } + return NS_OK; +} + +nsresult nsMsgLineBuffer::HandleLine(const char *line, uint32_t line_length) +{ + NS_ASSERTION(false, "must override this method if you don't provide a handler"); + return NS_OK; +} + +nsresult nsMsgLineBuffer::ConvertAndSendBuffer() +{ + /* Convert the line terminator to the native form. + */ + + char *buf = m_buffer; + int32_t length = m_bufferPos; + + char* newline; + + PR_ASSERT(buf && length > 0); + if (!buf || length <= 0) + return NS_ERROR_FAILURE; + newline = buf + length; + + PR_ASSERT(newline[-1] == '\r' || newline[-1] == '\n'); + if (newline[-1] != '\r' && newline[-1] != '\n') + return NS_ERROR_FAILURE; + + if (m_convertNewlinesP) + { +#if (MSG_LINEBREAK_LEN == 1) + if ((newline - buf) >= 2 && + newline[-2] == '\r' && + newline[-1] == '\n') + { + /* CRLF -> CR or LF */ + buf [length - 2] = MSG_LINEBREAK[0]; + length--; + } + else if (newline > buf + 1 && + newline[-1] != MSG_LINEBREAK[0]) + { + /* CR -> LF or LF -> CR */ + buf [length - 1] = MSG_LINEBREAK[0]; + } +#else + if (((newline - buf) >= 2 && newline[-2] != '\r') || + ((newline - buf) >= 1 && newline[-1] != '\n')) + { + /* LF -> CRLF or CR -> CRLF */ + length++; + buf[length - 2] = MSG_LINEBREAK[0]; + buf[length - 1] = MSG_LINEBREAK[1]; + } +#endif + } + return (m_handler) ? m_handler->HandleLine(buf, length) : HandleLine(buf, length); +} + +// If there's still some data (non CRLF terminated) flush it out +nsresult nsMsgLineBuffer::FlushLastLine() +{ + char *buf = m_buffer + m_bufferPos; + int32_t length = m_bufferPos - 1; + if (length > 0) + return (m_handler) ? m_handler->HandleLine(buf, length) : HandleLine(buf, length); + else + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// This is a utility class used to efficiently extract lines from an input stream by buffering +// read but unprocessed stream data in a buffer. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +nsMsgLineStreamBuffer::nsMsgLineStreamBuffer(uint32_t aBufferSize, bool aAllocateNewLines, bool aEatCRLFs, char aLineToken) + : m_eatCRLFs(aEatCRLFs), m_allocateNewLines(aAllocateNewLines), m_lineToken(aLineToken) +{ + NS_PRECONDITION(aBufferSize > 0, "invalid buffer size!!!"); + m_dataBuffer = nullptr; + m_startPos = 0; + m_numBytesInBuffer = 0; + + // used to buffer incoming data by ReadNextLineFromInput + if (aBufferSize > 0) + { + m_dataBuffer = (char *) PR_CALLOC(sizeof(char) * aBufferSize); + } + + m_dataBufferSize = aBufferSize; +} + +nsMsgLineStreamBuffer::~nsMsgLineStreamBuffer() +{ + PR_FREEIF(m_dataBuffer); // release our buffer... +} + + +nsresult nsMsgLineStreamBuffer::GrowBuffer(int32_t desiredSize) +{ + char* newBuffer = (char *) PR_REALLOC(m_dataBuffer, desiredSize); + NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY); + m_dataBuffer = newBuffer; + m_dataBufferSize = desiredSize; + return NS_OK; +} + +void nsMsgLineStreamBuffer::ClearBuffer() +{ + m_startPos = 0; + m_numBytesInBuffer = 0; +} + +// aInputStream - the input stream we want to read a line from +// aPauseForMoreData is returned as true if the stream does not yet contain a line and we must wait for more +// data to come into the stream. +// Note to people wishing to modify this function: Be *VERY CAREFUL* this is a critical function used by all of +// our mail protocols including imap, nntp, and pop. If you screw it up, you could break a lot of stuff..... + +char * nsMsgLineStreamBuffer::ReadNextLine(nsIInputStream * aInputStream, uint32_t &aNumBytesInLine, bool &aPauseForMoreData, nsresult *prv, bool addLineTerminator) +{ + // try to extract a line from m_inputBuffer. If we don't have an entire line, + // then read more bytes out from the stream. If the stream is empty then wait + // on the monitor for more data to come in. + + NS_PRECONDITION(m_dataBuffer && m_dataBufferSize > 0, "invalid input arguments for read next line from input"); + + if (prv) + *prv = NS_OK; + // initialize out values + aPauseForMoreData = false; + aNumBytesInLine = 0; + char * endOfLine = nullptr; + char * startOfLine = m_dataBuffer+m_startPos; + + if (m_numBytesInBuffer > 0) // any data in our internal buffer? + endOfLine = PL_strchr(startOfLine, m_lineToken); // see if we already have a line ending... + + // it's possible that we got here before the first time we receive data from the server + // so aInputStream will be nullptr... + if (!endOfLine && aInputStream) // get some more data from the server + { + nsresult rv; + uint64_t numBytesInStream = 0; + uint32_t numBytesCopied = 0; + bool nonBlockingStream; + aInputStream->IsNonBlocking(&nonBlockingStream); + rv = aInputStream->Available(&numBytesInStream); + if (NS_FAILED(rv)) + { + if (prv) + *prv = rv; + aNumBytesInLine = -1; + return nullptr; + } + if (!nonBlockingStream && numBytesInStream == 0) // if no data available, + numBytesInStream = m_dataBufferSize / 2; // ask for half the data buffer size. + + // if the number of bytes we want to read from the stream, is greater than the number + // of bytes left in our buffer, then we need to shift the start pos and its contents + // down to the beginning of m_dataBuffer... + uint32_t numFreeBytesInBuffer = m_dataBufferSize - m_startPos - m_numBytesInBuffer; + if (numBytesInStream >= numFreeBytesInBuffer) + { + if (m_startPos) + { + memmove(m_dataBuffer, startOfLine, m_numBytesInBuffer); + // make sure the end of the buffer is terminated + m_dataBuffer[m_numBytesInBuffer] = '\0'; + m_startPos = 0; + startOfLine = m_dataBuffer; + numFreeBytesInBuffer = m_dataBufferSize - m_numBytesInBuffer; + //printf("moving data in read line around because buffer filling up\n"); + } + // If we didn't make enough space (or any), grow the buffer + if (numBytesInStream >= numFreeBytesInBuffer) + { + int64_t growBy = (numBytesInStream - numFreeBytesInBuffer) * 2 + 1; + // GrowBuffer cannot handles over 4GB size + if (m_dataBufferSize + growBy > PR_UINT32_MAX) + return nullptr; + // try growing buffer by twice as much as we need. + nsresult rv = GrowBuffer(m_dataBufferSize + growBy); + // if we can't grow the buffer, we have to bail. + if (NS_FAILED(rv)) + return nullptr; + startOfLine = m_dataBuffer; + numFreeBytesInBuffer += growBy; + } + NS_ASSERTION(m_startPos == 0, "m_startPos should be 0 .....\n"); + } + + uint32_t numBytesToCopy = std::min(uint64_t(numFreeBytesInBuffer - 1) /* leave one for a null terminator */, numBytesInStream); + if (numBytesToCopy > 0) + { + // read the data into the end of our data buffer + char *startOfNewData = startOfLine + m_numBytesInBuffer; + rv = aInputStream->Read(startOfNewData, numBytesToCopy, &numBytesCopied); + if (prv) + *prv = rv; + uint32_t i; + for (i = 0; i < numBytesCopied; i++) // replace nulls with spaces + { + if (!startOfNewData[i]) + startOfNewData[i] = ' '; + } + m_numBytesInBuffer += numBytesCopied; + m_dataBuffer[m_startPos + m_numBytesInBuffer] = '\0'; + + // okay, now that we've tried to read in more data from the stream, + // look for another end of line character in the new data + endOfLine = PL_strchr(startOfNewData, m_lineToken); + } + } + + // okay, now check again for endOfLine. + if (endOfLine) + { + if (!m_eatCRLFs) + endOfLine += 1; // count for LF or CR + + aNumBytesInLine = endOfLine - startOfLine; + + if (m_eatCRLFs && aNumBytesInLine > 0 && startOfLine[aNumBytesInLine-1] == '\r') // Remove the CR in a CRLF sequence + aNumBytesInLine--; + + // PR_CALLOC zeros out the allocated line + char* newLine = (char*) PR_CALLOC(aNumBytesInLine + (addLineTerminator ? MSG_LINEBREAK_LEN : 0) + 1); + if (!newLine) + { + aNumBytesInLine = 0; + aPauseForMoreData = true; + return nullptr; + } + + memcpy(newLine, startOfLine, aNumBytesInLine); // copy the string into the new line buffer + if (addLineTerminator) + { + memcpy(newLine + aNumBytesInLine, MSG_LINEBREAK, MSG_LINEBREAK_LEN); + aNumBytesInLine += MSG_LINEBREAK_LEN; + } + + if (m_eatCRLFs) + endOfLine += 1; // advance past LF or CR if we haven't already done so... + + // now we need to update the data buffer to go past the line we just read out. + m_numBytesInBuffer -= (endOfLine - startOfLine); + if (m_numBytesInBuffer) + m_startPos = endOfLine - m_dataBuffer; + else + m_startPos = 0; + + return newLine; + } + + aPauseForMoreData = true; + return nullptr; // if we somehow got here. we don't have another line in the buffer yet...need to wait for more data... +} + +bool nsMsgLineStreamBuffer::NextLineAvailable() +{ + return (m_numBytesInBuffer > 0 && PL_strchr(m_dataBuffer+m_startPos, m_lineToken)); +} + diff --git a/mailnews/base/util/nsMsgLineBuffer.h b/mailnews/base/util/nsMsgLineBuffer.h new file mode 100644 index 000000000..0383b2d43 --- /dev/null +++ b/mailnews/base/util/nsMsgLineBuffer.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 4; 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 _nsMsgLineBuffer_H +#define _nsMsgLineBuffer_H + +#include "msgCore.h" // precompiled header... + +// I can't believe I have to have this stupid class, but I can't find +// anything suitable (nsStrImpl might be, when it's done). nsIByteBuffer +// would do, if I had a stream for input, which I don't. + +class NS_MSG_BASE nsByteArray +{ +public: + nsByteArray(); + virtual ~nsByteArray(); + uint32_t GetSize() {return m_bufferSize;} + uint32_t GetBufferPos() {return m_bufferPos;} + nsresult GrowBuffer(uint32_t desired_size, uint32_t quantum = 1024); + nsresult AppendString(const char *string); + nsresult AppendBuffer(const char *buffer, uint32_t length); + void ResetWritePos() {m_bufferPos = 0;} + char *GetBuffer() {return m_buffer;} +protected: + char *m_buffer; + uint32_t m_bufferSize; + uint32_t m_bufferPos; // write Pos in m_buffer - where the next byte should go. +}; + + +class NS_MSG_BASE nsMsgLineBufferHandler : public nsByteArray +{ +public: + virtual nsresult HandleLine(const char *line, uint32_t line_length) = 0; +}; + +class NS_MSG_BASE nsMsgLineBuffer : public nsMsgLineBufferHandler +{ +public: + nsMsgLineBuffer(nsMsgLineBufferHandler *handler, bool convertNewlinesP); + + virtual ~nsMsgLineBuffer(); + nsresult BufferInput(const char *net_buffer, int32_t net_buffer_size); + // Not sure why anyone cares, by NNTPHost seems to want to know the buf pos. + uint32_t GetBufferPos() {return m_bufferPos;} + + virtual nsresult HandleLine(const char *line, uint32_t line_length); + // flush last line, though it won't be CRLF terminated. + virtual nsresult FlushLastLine(); +protected: + nsMsgLineBuffer(bool convertNewlinesP); + + nsresult ConvertAndSendBuffer(); + void SetLookingForCRLF(bool b); + + nsMsgLineBufferHandler *m_handler; + bool m_convertNewlinesP; + bool m_lookingForCRLF; +}; + +// I'm adding this utility class here for lack of a better place. This utility class is similar to nsMsgLineBuffer +// except it works from an input stream. It is geared towards efficiently parsing new lines out of a stream by storing +// read but unprocessed bytes in a buffer. I envision the primary use of this to be our mail protocols such as imap, news and +// pop which need to process line by line data being returned in the form of a proxied stream from the server. + +class nsIInputStream; + +class NS_MSG_BASE nsMsgLineStreamBuffer +{ +public: + // aBufferSize -- size of the buffer you want us to use for buffering stream data + // aEndOfLinetoken -- The delimiter string to be used for determining the end of line. This + // allows us to parse platform specific end of line endings by making it + // a parameter. + // aAllocateNewLines -- true if you want calls to ReadNextLine to allocate new memory for the line. + // if false, the char * returned is just a ptr into the buffer. Subsequent calls to + // ReadNextLine will alter the data so your ptr only has a life time of a per call. + // aEatCRLFs -- true if you don't want to see the CRLFs on the lines returned by ReadNextLine. + // false if you do want to see them. + // aLineToken -- Specify the line token to look for, by default is LF ('\n') which cover as well CRLF. If + // lines are terminated with a CR only, you need to set aLineToken to CR ('\r') + nsMsgLineStreamBuffer(uint32_t aBufferSize, bool aAllocateNewLines, + bool aEatCRLFs = true, char aLineToken = '\n'); // specify the size of the buffer you want the class to use.... + virtual ~nsMsgLineStreamBuffer(); + + // Caller must free the line returned using PR_Free + // aEndOfLinetoken -- delimiter used to denote the end of a line. + // aNumBytesInLine -- The number of bytes in the line returned + // aPauseForMoreData -- There is not enough data in the stream to make a line at this time... + char * ReadNextLine(nsIInputStream * aInputStream, uint32_t &anumBytesInLine, bool &aPauseForMoreData, nsresult *rv = nullptr, bool addLineTerminator = false); + nsresult GrowBuffer(int32_t desiredSize); + void ClearBuffer(); + bool NextLineAvailable(); +protected: + bool m_eatCRLFs; + bool m_allocateNewLines; + char * m_dataBuffer; + uint32_t m_dataBufferSize; + uint32_t m_startPos; + uint32_t m_numBytesInBuffer; + char m_lineToken; +}; + + +#endif diff --git a/mailnews/base/util/nsMsgMailNewsUrl.cpp b/mailnews/base/util/nsMsgMailNewsUrl.cpp new file mode 100644 index 000000000..e9dc52b33 --- /dev/null +++ b/mailnews/base/util/nsMsgMailNewsUrl.cpp @@ -0,0 +1,1060 @@ +/* -*- 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" +#include "nsMsgMailNewsUrl.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgAccountManager.h" +#include "nsStringGlue.h" +#include "nsILoadGroup.h" +#include "nsIDocShell.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsNetUtil.h" +#include "nsIFile.h" +#include "prmem.h" +#include <time.h> +#include "nsMsgUtils.h" +#include "mozilla/Services.h" +#include <algorithm> +#include "nsProxyRelease.h" +#include "mozilla/BasePrincipal.h" + +nsMsgMailNewsUrl::nsMsgMailNewsUrl() +{ + // nsIURI specific state + m_errorMessage = nullptr; + m_runningUrl = false; + m_updatingFolder = false; + m_msgIsInLocalCache = false; + m_suppressErrorMsgs = false; + m_isPrincipalURL = false; + mMaxProgress = -1; + m_baseURL = do_CreateInstance(NS_STANDARDURL_CONTRACTID); +} + +#define NOTIFY_URL_LISTENERS(propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<nsCOMPtr<nsIUrlListener> >::ForwardIterator iter(mUrlListeners); \ + while (iter.HasMore()) { \ + nsCOMPtr<nsIUrlListener> listener = iter.GetNext(); \ + listener->propertyfunc_ params_; \ + } \ + PR_END_MACRO + +nsMsgMailNewsUrl::~nsMsgMailNewsUrl() +{ + PR_FREEIF(m_errorMessage); + + // In IMAP this URL is created and destroyed on the imap thread, + // so we must ensure that releases of XPCOM objects (which might be + // implemented by non-threadsafe JS components) are released on the + // main thread. + NS_ReleaseOnMainThread(m_baseURL.forget()); + NS_ReleaseOnMainThread(mMimeHeaders.forget()); + NS_ReleaseOnMainThread(m_searchSession.forget()); + NS_ReleaseOnMainThread(mMsgHeaderSink.forget()); + + nsTObserverArray<nsCOMPtr<nsIUrlListener>>::ForwardIterator iter(mUrlListeners); + while (iter.HasMore()) { + nsCOMPtr<nsIUrlListener> listener = iter.GetNext(); + if (listener) + NS_ReleaseOnMainThread(listener.forget()); + } +} + +NS_IMPL_ADDREF(nsMsgMailNewsUrl) +NS_IMPL_RELEASE(nsMsgMailNewsUrl) + +// We want part URLs to QI to nsIURIWithPrincipal so we can give +// them a "normalised" origin. URLs that already have a "normalised" +// origin should not QI to nsIURIWithPrincipal. +NS_INTERFACE_MAP_BEGIN(nsMsgMailNewsUrl) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgMailNewsUrl) + NS_INTERFACE_MAP_ENTRY(nsIMsgMailNewsUrl) + NS_INTERFACE_MAP_ENTRY(nsIURL) + NS_INTERFACE_MAP_ENTRY(nsIURIWithQuery) + NS_INTERFACE_MAP_ENTRY(nsIURI) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIURIWithPrincipal, !m_isPrincipalURL) +NS_INTERFACE_MAP_END + +// Support for nsIURIWithPrincipal. +NS_IMETHODIMP nsMsgMailNewsUrl::GetPrincipal(nsIPrincipal **aPrincipal) +{ + MOZ_ASSERT(!m_isPrincipalURL, + "nsMsgMailNewsUrl::GetPrincipal() can only be called for non-principal URLs"); + + if (!m_principal) { + nsCOMPtr <nsIMsgMessageUrl> msgUrl; + QueryInterface(NS_GET_IID(nsIMsgMessageUrl), getter_AddRefs(msgUrl)); + + nsAutoCString spec; + if (!msgUrl || NS_FAILED(msgUrl->GetPrincipalSpec(spec))) { + MOZ_ASSERT(false, "Can't get principal spec"); + // just use the normal spec. + GetSpec(spec); + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), spec); + NS_ENSURE_SUCCESS(rv, rv); + mozilla::PrincipalOriginAttributes attrs; + m_principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs); + } + + NS_IF_ADDREF(*aPrincipal = m_principal); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetPrincipalUri(nsIURI **aPrincipalURI) +{ + NS_ENSURE_ARG_POINTER(aPrincipalURI); + if (!m_principal) { + nsCOMPtr<nsIPrincipal> p; + GetPrincipal(getter_AddRefs(p)); + } + if (!m_principal) + return NS_ERROR_NULL_POINTER; + return m_principal->GetURI(aPrincipalURI); +} + +//////////////////////////////////////////////////////////////////////////////////// +// Begin nsIMsgMailNewsUrl specific support +//////////////////////////////////////////////////////////////////////////////////// + +nsresult nsMsgMailNewsUrl::GetUrlState(bool * aRunningUrl) +{ + if (aRunningUrl) + *aRunningUrl = m_runningUrl; + + return NS_OK; +} + +nsresult nsMsgMailNewsUrl::SetUrlState(bool aRunningUrl, nsresult aExitCode) +{ + // if we already knew this running state, return, unless the url was aborted + if (m_runningUrl == aRunningUrl && aExitCode != NS_MSG_ERROR_URL_ABORTED) + return NS_OK; + m_runningUrl = aRunningUrl; + nsCOMPtr <nsIMsgStatusFeedback> statusFeedback; + + // put this back - we need it for urls that don't run through the doc loader + if (NS_SUCCEEDED(GetStatusFeedback(getter_AddRefs(statusFeedback))) && statusFeedback) + { + if (m_runningUrl) + statusFeedback->StartMeteors(); + else + { + statusFeedback->ShowProgress(0); + statusFeedback->StopMeteors(); + } + } + + if (m_runningUrl) + { + NOTIFY_URL_LISTENERS(OnStartRunningUrl, (this)); + } + else + { + NOTIFY_URL_LISTENERS(OnStopRunningUrl, (this, aExitCode)); + mUrlListeners.Clear(); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::RegisterListener(nsIUrlListener *aUrlListener) +{ + NS_ENSURE_ARG_POINTER(aUrlListener); + mUrlListeners.AppendElement(aUrlListener); + return NS_OK; +} + +nsresult nsMsgMailNewsUrl::UnRegisterListener(nsIUrlListener *aUrlListener) +{ + NS_ENSURE_ARG_POINTER(aUrlListener); + + // Due to the way mailnews is structured, some listeners attempt to remove + // themselves twice. This may in fact be an error in the coding, however + // if they didn't do it as they do currently, then they could fail to remove + // their listeners. + mUrlListeners.RemoveElement(aUrlListener); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetServer(nsIMsgIncomingServer ** aIncomingServer) +{ + // mscott --> we could cache a copy of the server here....but if we did, we run + // the risk of leaking the server if any single url gets leaked....of course that + // shouldn't happen...but it could. so i'm going to look it up every time and + // we can look at caching it later. + + nsresult rv; + nsAutoCString urlstr; + nsAutoCString scheme; + + nsCOMPtr<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = m_baseURL->GetSpec(urlstr); + NS_ENSURE_SUCCESS(rv, rv); + rv = url->SetSpec(urlstr); + if (NS_FAILED(rv)) return rv; + rv = GetScheme(scheme); + if (NS_SUCCEEDED(rv)) + { + if (scheme.EqualsLiteral("pop")) + scheme.Assign("pop3"); + // we use "nntp" in the server list so translate it here. + if (scheme.EqualsLiteral("news")) + scheme.Assign("nntp"); + url->SetScheme(scheme); + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = accountManager->FindServerByURI(url, false, + aIncomingServer); + if (!*aIncomingServer && scheme.EqualsLiteral("imap")) + { + // look for any imap server with this host name so clicking on + // other users folder urls will work. We could override this method + // for imap urls, or we could make caching of servers work and + // just set the server in the imap code for this case. + url->SetUserPass(EmptyCString()); + rv = accountManager->FindServerByURI(url, false, + aIncomingServer); + } + } + + return rv; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgWindow(nsIMsgWindow **aMsgWindow) +{ + NS_ENSURE_ARG_POINTER(aMsgWindow); + *aMsgWindow = nullptr; + + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + msgWindow.swap(*aMsgWindow); + return *aMsgWindow ? NS_OK : NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgWindow(nsIMsgWindow *aMsgWindow) +{ +#ifdef DEBUG_David_Bienvenu + NS_ASSERTION(aMsgWindow || !m_msgWindowWeak, "someone crunching non-null msg window"); +#endif + m_msgWindowWeak = do_GetWeakReference(aMsgWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetStatusFeedback(nsIMsgStatusFeedback **aMsgFeedback) +{ + // note: it is okay to return a null status feedback and not return an error + // it's possible the url really doesn't have status feedback + *aMsgFeedback = nullptr; + if (!m_statusFeedbackWeak) + { + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) + msgWindow->GetStatusFeedback(aMsgFeedback); + } + else + { + nsCOMPtr<nsIMsgStatusFeedback> statusFeedback(do_QueryReferent(m_statusFeedbackWeak)); + statusFeedback.swap(*aMsgFeedback); + } + return *aMsgFeedback ? NS_OK : NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetStatusFeedback(nsIMsgStatusFeedback *aMsgFeedback) +{ + if (aMsgFeedback) + m_statusFeedbackWeak = do_GetWeakReference(aMsgFeedback); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetMaxProgress(int64_t *aMaxProgress) +{ + *aMaxProgress = mMaxProgress; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetMaxProgress(int64_t aMaxProgress) +{ + mMaxProgress = aMaxProgress; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetLoadGroup(nsILoadGroup **aLoadGroup) +{ + *aLoadGroup = nullptr; + // note: it is okay to return a null load group and not return an error + // it's possible the url really doesn't have load group + nsCOMPtr<nsILoadGroup> loadGroup (do_QueryReferent(m_loadGroupWeak)); + if (!loadGroup) + { + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) + { + // XXXbz This is really weird... why are we getting some + // random loadgroup we're not really a part of? + nsCOMPtr<nsIDocShell> docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + loadGroup = do_GetInterface(docShell); + m_loadGroupWeak = do_GetWeakReference(loadGroup); + } + } + loadGroup.swap(*aLoadGroup); + return *aLoadGroup ? NS_OK : NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetUpdatingFolder(bool *aResult) +{ + NS_ENSURE_ARG(aResult); + *aResult = m_updatingFolder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetUpdatingFolder(bool updatingFolder) +{ + m_updatingFolder = updatingFolder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgIsInLocalCache(bool *aMsgIsInLocalCache) +{ + NS_ENSURE_ARG(aMsgIsInLocalCache); + *aMsgIsInLocalCache = m_msgIsInLocalCache; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgIsInLocalCache(bool aMsgIsInLocalCache) +{ + m_msgIsInLocalCache = aMsgIsInLocalCache; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetSuppressErrorMsgs(bool *aSuppressErrorMsgs) +{ + NS_ENSURE_ARG(aSuppressErrorMsgs); + *aSuppressErrorMsgs = m_suppressErrorMsgs; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetSuppressErrorMsgs(bool aSuppressErrorMsgs) +{ + m_suppressErrorMsgs = aSuppressErrorMsgs; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::IsUrlType(uint32_t type, bool *isType) +{ + //base class doesn't know about any specific types + NS_ENSURE_ARG(isType); + *isType = false; + return NS_OK; + +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetSearchSession(nsIMsgSearchSession *aSearchSession) +{ + if (aSearchSession) + m_searchSession = do_QueryInterface(aSearchSession); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetSearchSession(nsIMsgSearchSession **aSearchSession) +{ + NS_ENSURE_ARG(aSearchSession); + *aSearchSession = m_searchSession; + NS_IF_ADDREF(*aSearchSession); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////////// +// End nsIMsgMailNewsUrl specific support +//////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////// +// Begin nsIURI support +//////////////////////////////////////////////////////////////////////////////////// + + +NS_IMETHODIMP nsMsgMailNewsUrl::GetSpec(nsACString &aSpec) +{ + return m_baseURL->GetSpec(aSpec); +} + +#define FILENAME_PART_LEN 10 + +NS_IMETHODIMP nsMsgMailNewsUrl::SetSpec(const nsACString &aSpec) +{ + nsAutoCString spec(aSpec); + // Parse out "filename" attribute if present. + char *start, *end; + start = PL_strcasestr(spec.BeginWriting(),"?filename="); + if (!start) + start = PL_strcasestr(spec.BeginWriting(),"&filename="); + if (start) + { // Make sure we only get our own value. + end = PL_strcasestr((char*)(start+FILENAME_PART_LEN),"&"); + if (end) + { + *end = 0; + mAttachmentFileName = start+FILENAME_PART_LEN; + *end = '&'; + } + else + mAttachmentFileName = start+FILENAME_PART_LEN; + } + + // Now, set the rest. + nsresult rv = m_baseURL->SetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + + // Check whether the URL is in normalised form. + nsCOMPtr <nsIMsgMessageUrl> msgUrl; + QueryInterface(NS_GET_IID(nsIMsgMessageUrl), getter_AddRefs(msgUrl)); + + nsAutoCString principalSpec; + if (!msgUrl || NS_FAILED(msgUrl->GetPrincipalSpec(principalSpec))) { + // If we can't get the principal spec, never QI this to nsIURIWithPrincipal. + m_isPrincipalURL = true; + } else { + m_isPrincipalURL = spec.Equals(principalSpec); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetPrePath(nsACString &aPrePath) +{ + return m_baseURL->GetPrePath(aPrePath); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetScheme(nsACString &aScheme) +{ + return m_baseURL->GetScheme(aScheme); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetScheme(const nsACString &aScheme) +{ + return m_baseURL->SetScheme(aScheme); +} + + +NS_IMETHODIMP nsMsgMailNewsUrl::GetUserPass(nsACString &aUserPass) +{ + return m_baseURL->GetUserPass(aUserPass); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetUserPass(const nsACString &aUserPass) +{ + return m_baseURL->SetUserPass(aUserPass); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetUsername(nsACString &aUsername) +{ + /* note: this will return an escaped string */ + return m_baseURL->GetUsername(aUsername); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetUsername(const nsACString &aUsername) +{ + return m_baseURL->SetUsername(aUsername); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetPassword(nsACString &aPassword) +{ + return m_baseURL->GetPassword(aPassword); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetPassword(const nsACString &aPassword) +{ + return m_baseURL->SetPassword(aPassword); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetHostPort(nsACString &aHostPort) +{ + return m_baseURL->GetHostPort(aHostPort); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetHostPort(const nsACString &aHostPort) +{ + return m_baseURL->SetHostPort(aHostPort); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetHostAndPort(const nsACString &aHostPort) +{ + return m_baseURL->SetHostAndPort(aHostPort); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetHost(nsACString &aHost) +{ + return m_baseURL->GetHost(aHost); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetHost(const nsACString &aHost) +{ + return m_baseURL->SetHost(aHost); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetPort(int32_t *aPort) +{ + return m_baseURL->GetPort(aPort); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetPort(int32_t aPort) +{ + return m_baseURL->SetPort(aPort); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetPath(nsACString &aPath) +{ + return m_baseURL->GetPath(aPath); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetPath(const nsACString &aPath) +{ + return m_baseURL->SetPath(aPath); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiHost(nsACString &aHostA) +{ + return m_baseURL->GetAsciiHost(aHostA); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiHostPort(nsACString &aHostPortA) +{ + return m_baseURL->GetAsciiHostPort(aHostPortA); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiSpec(nsACString &aSpecA) +{ + return m_baseURL->GetAsciiSpec(aSpecA); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetOriginCharset(nsACString &aOriginCharset) +{ + return m_baseURL->GetOriginCharset(aOriginCharset); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetBaseURI(nsIURI **aBaseURI) +{ + NS_ENSURE_ARG_POINTER(aBaseURI); + return m_baseURL->QueryInterface(NS_GET_IID(nsIURI), (void**) aBaseURI); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::Equals(nsIURI *other, bool *_retval) +{ + // The passed-in URI might be a mail news url. Pass our inner URL to its + // Equals method. The other mail news url will then pass its inner URL to + // to the Equals method of our inner URL. Other URIs will return false. + if (other) + return other->Equals(m_baseURL, _retval); + + return m_baseURL->Equals(other, _retval); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::EqualsExceptRef(nsIURI *other, bool *result) +{ + // The passed-in URI might be a mail news url. Pass our inner URL to its + // Equals method. The other mail news url will then pass its inner URL to + // to the Equals method of our inner URL. Other URIs will return false. + if (other) + return other->EqualsExceptRef(m_baseURL, result); + + return m_baseURL->EqualsExceptRef(other, result); +} + +NS_IMETHODIMP +nsMsgMailNewsUrl::GetSpecIgnoringRef(nsACString &result) +{ + return m_baseURL->GetSpecIgnoringRef(result); +} + +NS_IMETHODIMP +nsMsgMailNewsUrl::GetHasRef(bool *result) +{ + return m_baseURL->GetHasRef(result); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SchemeIs(const char *aScheme, bool *_retval) +{ + return m_baseURL->SchemeIs(aScheme, _retval); +} + +NS_IMETHODIMP +nsMsgMailNewsUrl::CloneInternal(uint32_t aRefHandlingMode, + const nsACString& newRef, nsIURI** _retval) +{ + nsresult rv; + nsAutoCString urlSpec; + nsCOMPtr<nsIIOService> ioService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + rv = GetSpec(urlSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = ioService->NewURI(urlSpec, nullptr, nullptr, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + // add the msg window to the cloned url + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgMailNewsUrl = do_QueryInterface(*_retval, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgMailNewsUrl->SetMsgWindow(msgWindow); + } + + if (aRefHandlingMode == nsIMsgMailNewsUrl::REPLACE_REF) { + rv = (*_retval)->SetRef(newRef); + NS_ENSURE_SUCCESS(rv, rv); + } else if (aRefHandlingMode == nsIMsgMailNewsUrl::IGNORE_REF) { + rv = (*_retval)->SetRef(EmptyCString()); + NS_ENSURE_SUCCESS(rv, rv); + } + + return rv; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::Clone(nsIURI **_retval) +{ + return CloneInternal(nsIMsgMailNewsUrl::HONOR_REF, EmptyCString(), _retval); +} + +NS_IMETHODIMP +nsMsgMailNewsUrl::CloneIgnoringRef(nsIURI** _retval) +{ + return CloneInternal(nsIMsgMailNewsUrl::IGNORE_REF, EmptyCString(), _retval); +} + +NS_IMETHODIMP +nsMsgMailNewsUrl::CloneWithNewRef(const nsACString& newRef, nsIURI** _retval) +{ + return CloneInternal(nsIMsgMailNewsUrl::REPLACE_REF, newRef, _retval); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::Resolve(const nsACString &relativePath, nsACString &result) +{ + // only resolve anchor urls....i.e. urls which start with '#' against the mailnews url... + // everything else shouldn't be resolved against mailnews urls. + nsresult rv = NS_OK; + + if (!relativePath.IsEmpty() && relativePath.First() == '#') // an anchor + return m_baseURL->Resolve(relativePath, result); + else + { + // if relativePath is a complete url with it's own scheme then allow it... + nsCOMPtr<nsIIOService> ioService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + nsAutoCString scheme; + + rv = ioService->ExtractScheme(relativePath, scheme); + // if we have a fully qualified scheme then pass the relative path back as the result + if (NS_SUCCEEDED(rv) && !scheme.IsEmpty()) + { + result = relativePath; + rv = NS_OK; + } + else + { + result.Truncate(); + rv = NS_ERROR_FAILURE; + } + } + + return rv; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetDirectory(nsACString &aDirectory) +{ + return m_baseURL->GetDirectory(aDirectory); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetDirectory(const nsACString &aDirectory) +{ + + return m_baseURL->SetDirectory(aDirectory); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetFileName(nsACString &aFileName) +{ + if (!mAttachmentFileName.IsEmpty()) + { + aFileName = mAttachmentFileName; + return NS_OK; + } + return m_baseURL->GetFileName(aFileName); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetFileBaseName(nsACString &aFileBaseName) +{ + return m_baseURL->GetFileBaseName(aFileBaseName); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetFileBaseName(const nsACString &aFileBaseName) +{ + return m_baseURL->SetFileBaseName(aFileBaseName); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetFileExtension(nsACString &aFileExtension) +{ + if (!mAttachmentFileName.IsEmpty()) + { + int32_t pos = mAttachmentFileName.RFindChar(char16_t('.')); + if (pos > 0) + aFileExtension = Substring(mAttachmentFileName, pos + 1 /* skip the '.' */); + return NS_OK; + } + return m_baseURL->GetFileExtension(aFileExtension); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetFileExtension(const nsACString &aFileExtension) +{ + return m_baseURL->SetFileExtension(aFileExtension); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetFileName(const nsACString &aFileName) +{ + mAttachmentFileName = aFileName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetQuery(nsACString &aQuery) +{ + return m_baseURL->GetQuery(aQuery); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetQuery(const nsACString &aQuery) +{ + return m_baseURL->SetQuery(aQuery); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetRef(nsACString &aRef) +{ + return m_baseURL->GetRef(aRef); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetRef(const nsACString &aRef) +{ + return m_baseURL->SetRef(aRef); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetFilePath(nsACString &o_DirFile) +{ + return m_baseURL->GetFilePath(o_DirFile); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetFilePath(const nsACString &i_DirFile) +{ + return m_baseURL->SetFilePath(i_DirFile); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetCommonBaseSpec(nsIURI *uri2, nsACString &result) +{ + return m_baseURL->GetCommonBaseSpec(uri2, result); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetRelativeSpec(nsIURI *uri2, nsACString &result) +{ + return m_baseURL->GetRelativeSpec(uri2, result); +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetMemCacheEntry(nsICacheEntry *memCacheEntry) +{ + m_memCacheEntry = memCacheEntry; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetMemCacheEntry(nsICacheEntry **memCacheEntry) +{ + NS_ENSURE_ARG(memCacheEntry); + nsresult rv = NS_OK; + + if (m_memCacheEntry) + { + *memCacheEntry = m_memCacheEntry; + NS_ADDREF(*memCacheEntry); + } + else + { + *memCacheEntry = nullptr; + return NS_ERROR_NULL_POINTER; + } + + return rv; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetMimeHeaders(nsIMimeHeaders * *mimeHeaders) +{ + NS_ENSURE_ARG_POINTER(mimeHeaders); + NS_IF_ADDREF(*mimeHeaders = mMimeHeaders); + return (mMimeHeaders) ? NS_OK : NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetMimeHeaders(nsIMimeHeaders *mimeHeaders) +{ + mMimeHeaders = mimeHeaders; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::LoadURI(nsIDocShell* docShell, + nsIDocShellLoadInfo* loadInfo, + uint32_t aLoadFlags) +{ + NS_ENSURE_ARG_POINTER(docShell); + return docShell->LoadURI(this, loadInfo, aLoadFlags, false); +} + +#define SAVE_BUF_SIZE FILE_IO_BUFFER_SIZE +class nsMsgSaveAsListener : public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsMsgSaveAsListener(nsIFile *aFile, bool addDummyEnvelope); + nsresult SetupMsgWriteStream(nsIFile *aFile, bool addDummyEnvelope); +protected: + virtual ~nsMsgSaveAsListener(); + nsCOMPtr<nsIOutputStream> m_outputStream; + nsCOMPtr<nsIFile> m_outputFile; + bool m_addDummyEnvelope; + bool m_writtenData; + uint32_t m_leftOver; + char m_dataBuffer[SAVE_BUF_SIZE+1]; // temporary buffer for this save operation + +}; + +NS_IMPL_ISUPPORTS(nsMsgSaveAsListener, + nsIStreamListener, + nsIRequestObserver) + +nsMsgSaveAsListener::nsMsgSaveAsListener(nsIFile *aFile, bool addDummyEnvelope) +{ + m_outputFile = aFile; + m_writtenData = false; + m_addDummyEnvelope = addDummyEnvelope; + m_leftOver = 0; +} + +nsMsgSaveAsListener::~nsMsgSaveAsListener() +{ +} + +NS_IMETHODIMP nsMsgSaveAsListener::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSaveAsListener::OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus) +{ + if (m_outputStream) + { + m_outputStream->Flush(); + m_outputStream->Close(); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSaveAsListener::OnDataAvailable(nsIRequest* request, + nsISupports* aSupport, + nsIInputStream* inStream, + uint64_t srcOffset, + uint32_t count) +{ + nsresult rv; + uint64_t available; + rv = inStream->Available(&available); + if (!m_writtenData) + { + m_writtenData = true; + rv = SetupMsgWriteStream(m_outputFile, m_addDummyEnvelope); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool useCanonicalEnding = false; + nsCOMPtr <nsIMsgMessageUrl> msgUrl = do_QueryInterface(aSupport); + if (msgUrl) + msgUrl->GetCanonicalLineEnding(&useCanonicalEnding); + + const char *lineEnding = (useCanonicalEnding) ? CRLF : MSG_LINEBREAK; + uint32_t lineEndingLength = (useCanonicalEnding) ? 2 : MSG_LINEBREAK_LEN; + + uint32_t readCount, maxReadCount = SAVE_BUF_SIZE - m_leftOver; + uint32_t writeCount; + char *start, *end, lastCharInPrevBuf = '\0'; + uint32_t linebreak_len = 0; + + while (count > 0) + { + if (count < maxReadCount) + maxReadCount = count; + rv = inStream->Read(m_dataBuffer + m_leftOver, + maxReadCount, + &readCount); + if (NS_FAILED(rv)) return rv; + + m_leftOver += readCount; + m_dataBuffer[m_leftOver] = '\0'; + + start = m_dataBuffer; + // make sure we don't insert another LF, accidentally, by ignoring + // second half of CRLF spanning blocks. + if (lastCharInPrevBuf == '\r' && *start == '\n') + start++; + + end = PL_strchr(start, '\r'); + if (!end) + end = PL_strchr(start, '\n'); + else if (*(end+1) == '\n' && linebreak_len == 0) + linebreak_len = 2; + + if (linebreak_len == 0) // not initialize yet + linebreak_len = 1; + + count -= readCount; + maxReadCount = SAVE_BUF_SIZE - m_leftOver; + + if (!end && count > maxReadCount) + // must be a very very long line; sorry cannot handle it + return NS_ERROR_FAILURE; + + while (start && end) + { + if (m_outputStream && + PL_strncasecmp(start, "X-Mozilla-Status:", 17) && + PL_strncasecmp(start, "X-Mozilla-Status2:", 18) && + PL_strncmp(start, "From - ", 7)) + { + rv = m_outputStream->Write(start, end-start, &writeCount); + nsresult tmp = m_outputStream->Write(lineEnding, lineEndingLength, &writeCount); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + start = end+linebreak_len; + if (start >= m_dataBuffer + m_leftOver) + { + maxReadCount = SAVE_BUF_SIZE; + m_leftOver = 0; + break; + } + end = PL_strchr(start, '\r'); + if (!end) + end = PL_strchr(start, '\n'); + if (start && !end) + { + m_leftOver -= (start - m_dataBuffer); + memcpy(m_dataBuffer, start, + m_leftOver+1); // including null + maxReadCount = SAVE_BUF_SIZE - m_leftOver; + } + } + if (NS_FAILED(rv)) return rv; + if (end) + lastCharInPrevBuf = *end; + } + return rv; + + // rv = m_outputStream->WriteFrom(inStream, std::min(available, count), &bytesWritten); +} + +nsresult nsMsgSaveAsListener::SetupMsgWriteStream(nsIFile *aFile, bool addDummyEnvelope) +{ + // If the file already exists, delete it, but do this before + // getting the outputstream. + // Due to bug 328027, the nsSaveMsgListener created in + // nsMessenger::SaveAs now opens the stream on the nsIFile + // object, thus creating an empty file. Actual save operations for + // IMAP and NNTP use this nsMsgSaveAsListener here, though, so we + // have to close the stream before deleting the file, else data + // would still be written happily into a now non-existing file. + // (Windows doesn't care, btw, just unixoids do...) + aFile->Remove(false); + + nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream), + aFile, -1, 0666); + NS_ENSURE_SUCCESS(rv, rv); + + if (m_outputStream && addDummyEnvelope) + { + nsAutoCString result; + uint32_t writeCount; + + time_t now = time((time_t*) 0); + char *ct = ctime(&now); + // Remove the ending new-line character. + ct[24] = '\0'; + result = "From - "; + result += ct; + result += MSG_LINEBREAK; + m_outputStream->Write(result.get(), result.Length(), &writeCount); + + result = "X-Mozilla-Status: 0001"; + result += MSG_LINEBREAK; + result += "X-Mozilla-Status2: 00000000"; + result += MSG_LINEBREAK; + m_outputStream->Write(result.get(), result.Length(), &writeCount); + } + + return rv; +} + + +NS_IMETHODIMP nsMsgMailNewsUrl::GetSaveAsListener(bool addDummyEnvelope, + nsIFile *aFile, nsIStreamListener **aSaveListener) +{ + NS_ENSURE_ARG_POINTER(aSaveListener); + nsMsgSaveAsListener *saveAsListener = new nsMsgSaveAsListener(aFile, addDummyEnvelope); + return saveAsListener->QueryInterface(NS_GET_IID(nsIStreamListener), (void **) aSaveListener); +} + + +NS_IMETHODIMP nsMsgMailNewsUrl::SetFolder(nsIMsgFolder * /* aFolder */) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetFolder(nsIMsgFolder ** /* aFolder */) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgHeaderSink(nsIMsgHeaderSink * *aMsgHdrSink) +{ + NS_ENSURE_ARG_POINTER(aMsgHdrSink); + NS_IF_ADDREF(*aMsgHdrSink = mMsgHeaderSink); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgHeaderSink(nsIMsgHeaderSink * aMsgHdrSink) +{ + mMsgHeaderSink = aMsgHdrSink; + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailNewsUrl::GetIsMessageUri(bool *aIsMessageUri) +{ + NS_ENSURE_ARG(aIsMessageUri); + nsAutoCString scheme; + m_baseURL->GetScheme(scheme); + *aIsMessageUri = StringEndsWith(scheme, NS_LITERAL_CSTRING("-message")); + return NS_OK; +} diff --git a/mailnews/base/util/nsMsgMailNewsUrl.h b/mailnews/base/util/nsMsgMailNewsUrl.h new file mode 100644 index 000000000..5833dfef1 --- /dev/null +++ b/mailnews/base/util/nsMsgMailNewsUrl.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; 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 nsMsgMailNewsUrl_h___ +#define nsMsgMailNewsUrl_h___ + +#include "nscore.h" +#include "nsISupports.h" +#include "nsIUrlListener.h" +#include "nsTObserverArray.h" +#include "nsIMsgWindow.h" +#include "nsIMsgStatusFeedback.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIMimeHeaders.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIURL.h" +#include "nsIURIWithPrincipal.h" +#include "nsILoadGroup.h" +#include "nsIMsgSearchSession.h" +#include "nsICacheEntry.h" +#include "nsICacheSession.h" +#include "nsIMimeMiscStatus.h" +#include "nsWeakReference.h" +#include "nsStringGlue.h" + +/////////////////////////////////////////////////////////////////////////////////// +// Okay, I found that all of the mail and news url interfaces needed to support +// several common interfaces (in addition to those provided through nsIURI). +// So I decided to group them all in this implementation so we don't have to +// duplicate the code. +// +////////////////////////////////////////////////////////////////////////////////// + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT + +class NS_MSG_BASE nsMsgMailNewsUrl : public nsIMsgMailNewsUrl, + public nsIURIWithPrincipal +{ +public: + nsMsgMailNewsUrl(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGMAILNEWSURL + NS_DECL_NSIURI + NS_DECL_NSIURIWITHQUERY + NS_DECL_NSIURL + NS_DECL_NSIURIWITHPRINCIPAL + +protected: + virtual ~nsMsgMailNewsUrl(); + + nsCOMPtr<nsIURL> m_baseURL; + nsCOMPtr<nsIPrincipal> m_principal; + nsWeakPtr m_statusFeedbackWeak; + nsWeakPtr m_msgWindowWeak; + nsWeakPtr m_loadGroupWeak; + nsCOMPtr<nsIMimeHeaders> mMimeHeaders; + nsCOMPtr<nsIMsgSearchSession> m_searchSession; + nsCOMPtr<nsICacheEntry> m_memCacheEntry; + nsCOMPtr<nsIMsgHeaderSink> mMsgHeaderSink; + char *m_errorMessage; + int64_t mMaxProgress; + bool m_runningUrl; + bool m_updatingFolder; + bool m_msgIsInLocalCache; + bool m_suppressErrorMsgs; + bool m_isPrincipalURL; + + // the following field is really a bit of a hack to make + // open attachments work. The external applications code sometimes tries to figure out the right + // handler to use by looking at the file extension of the url we are trying to load. Unfortunately, + // the attachment file name really isn't part of the url string....so we'll store it here...and if + // the url we are running is an attachment url, we'll set it here. Then when the helper apps code + // asks us for it, we'll return the right value. + nsCString mAttachmentFileName; + + nsTObserverArray<nsCOMPtr<nsIUrlListener> > mUrlListeners; +}; + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN + +#endif /* nsMsgMailNewsUrl_h___ */ diff --git a/mailnews/base/util/nsMsgProtocol.cpp b/mailnews/base/util/nsMsgProtocol.cpp new file mode 100644 index 000000000..f2190cb45 --- /dev/null +++ b/mailnews/base/util/nsMsgProtocol.cpp @@ -0,0 +1,1552 @@ +/* -*- 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" +#include "nsStringGlue.h" +#include "nsMsgProtocol.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgMailSession.h" +#include "nsMsgBaseCID.h" +#include "nsIStreamTransportService.h" +#include "nsISocketTransportService.h" +#include "nsISocketTransport.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIIOService.h" +#include "nsNetUtil.h" +#include "nsIFileURL.h" +#include "nsIMsgWindow.h" +#include "nsIMsgStatusFeedback.h" +#include "nsIWebProgressListener.h" +#include "nsIPipe.h" +#include "nsIPrompt.h" +#include "prprf.h" +#include "plbase64.h" +#include "nsIStringBundle.h" +#include "nsIProxyInfo.h" +#include "nsThreadUtils.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsMsgUtils.h" +#include "nsILineInputStream.h" +#include "nsIAsyncInputStream.h" +#include "nsIMsgIncomingServer.h" +#include "nsIInputStreamPump.h" +#include "nsMimeTypes.h" +#include "nsAlgorithm.h" +#include "mozilla/Services.h" +#include <algorithm> +#include "nsContentSecurityManager.h" + +#undef PostMessage // avoid to collision with WinUser.h + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsMsgProtocol, nsIChannel, nsIStreamListener, + nsIRequestObserver, nsIRequest, nsITransportEventSink) + +static char16_t *FormatStringWithHostNameByName(const char16_t* stringName, nsIMsgMailNewsUrl *msgUri); + + +nsMsgProtocol::nsMsgProtocol(nsIURI * aURL) +{ + m_flags = 0; + m_readCount = 0; + mLoadFlags = 0; + m_socketIsOpen = false; + mContentLength = -1; + + GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "tempMessage.eml", + getter_AddRefs(m_tempMsgFile)); + + mSuppressListenerNotifications = false; + InitFromURI(aURL); +} + +nsresult nsMsgProtocol::InitFromURI(nsIURI *aUrl) +{ + m_url = aUrl; + + nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl); + if (mailUrl) + { + mailUrl->GetLoadGroup(getter_AddRefs(m_loadGroup)); + nsCOMPtr<nsIMsgStatusFeedback> statusFeedback; + mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback)); + mProgressEventSink = do_QueryInterface(statusFeedback); + } + + // Reset channel data in case the object is reused and initialised again. + mCharset.Truncate(); + + return NS_OK; +} + +nsMsgProtocol::~nsMsgProtocol() +{} + + +static bool gGotTimeoutPref; +static int32_t gSocketTimeout = 60; + +nsresult +nsMsgProtocol::GetQoSBits(uint8_t *aQoSBits) +{ + NS_ENSURE_ARG_POINTER(aQoSBits); + const char* protocol = GetType(); + + if (!protocol) + return NS_ERROR_NOT_IMPLEMENTED; + + nsAutoCString prefName("mail."); + prefName.Append(protocol); + prefName.Append(".qos"); + + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + int32_t val; + rv = prefBranch->GetIntPref(prefName.get(), &val); + NS_ENSURE_SUCCESS(rv, rv); + *aQoSBits = (uint8_t) clamped(val, 0, 0xff); + return NS_OK; +} + +nsresult +nsMsgProtocol::OpenNetworkSocketWithInfo(const char * aHostName, + int32_t aGetPort, + const char *connectionType, + nsIProxyInfo *aProxyInfo, + nsIInterfaceRequestor* callbacks) +{ + NS_ENSURE_ARG(aHostName); + + nsresult rv = NS_OK; + nsCOMPtr<nsISocketTransportService> socketService (do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(socketService, NS_ERROR_FAILURE); + + // with socket connections we want to read as much data as arrives + m_readCount = -1; + + nsCOMPtr<nsISocketTransport> strans; + rv = socketService->CreateTransport(&connectionType, connectionType != nullptr, + nsDependentCString(aHostName), + aGetPort, aProxyInfo, + getter_AddRefs(strans)); + if (NS_FAILED(rv)) return rv; + + strans->SetSecurityCallbacks(callbacks); + + // creates cyclic reference! + nsCOMPtr<nsIThread> currentThread(do_GetCurrentThread()); + strans->SetEventSink(this, currentThread); + + m_socketIsOpen = false; + m_transport = strans; + + if (!gGotTimeoutPref) + { + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (prefBranch) + { + prefBranch->GetIntPref("mailnews.tcptimeout", &gSocketTimeout); + gGotTimeoutPref = true; + } + } + strans->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, gSocketTimeout + 60); + strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout); + + uint8_t qos; + rv = GetQoSBits(&qos); + if (NS_SUCCEEDED(rv)) + strans->SetQoSBits(qos); + + return SetupTransportState(); +} + +nsresult nsMsgProtocol::GetFileFromURL(nsIURI * aURL, nsIFile **aResult) +{ + NS_ENSURE_ARG_POINTER(aURL); + NS_ENSURE_ARG_POINTER(aResult); + // extract the file path from the uri... + nsAutoCString urlSpec; + aURL->GetPath(urlSpec); + urlSpec.Insert(NS_LITERAL_CSTRING("file://"), 0); + nsresult rv; + +// dougt - there should be an easier way! + nsCOMPtr<nsIURI> uri; + if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), urlSpec.get()))) + return rv; + + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri); + if (!fileURL) return NS_ERROR_FAILURE; + + return fileURL->GetFile(aResult); + // dougt +} + +nsresult nsMsgProtocol::OpenFileSocket(nsIURI * aURL, uint32_t aStartPosition, int32_t aReadCount) +{ + // mscott - file needs to be encoded directly into aURL. I should be able to get + // rid of this method completely. + + nsresult rv = NS_OK; + m_readCount = aReadCount; + nsCOMPtr <nsIFile> file; + + rv = GetFileFromURL(aURL, getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); + if (NS_FAILED(rv)) return rv; + + // create input stream transport + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = sts->CreateInputTransport(stream, int64_t(aStartPosition), + int64_t(aReadCount), true, + getter_AddRefs(m_transport)); + + m_socketIsOpen = false; + return rv; +} + +nsresult nsMsgProtocol::GetTopmostMsgWindow(nsIMsgWindow **aWindow) +{ + nsresult rv; + nsCOMPtr<nsIMsgMailSession> mailSession(do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return mailSession->GetTopmostMsgWindow(aWindow); +} + +nsresult nsMsgProtocol::SetupTransportState() +{ + if (!m_socketIsOpen && m_transport) + { + nsresult rv; + + // open buffered, blocking output stream + rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(m_outputStream)); + if (NS_FAILED(rv)) return rv; + // we want to open the stream + } // if m_transport + + return NS_OK; +} + +nsresult nsMsgProtocol::CloseSocket() +{ + nsresult rv = NS_OK; + // release all of our socket state + m_socketIsOpen = false; + m_inputStream = nullptr; + m_outputStream = nullptr; + if (m_transport) { + nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport); + if (strans) { + strans->SetEventSink(nullptr, nullptr); // break cyclic reference! + } + } + // we need to call Cancel so that we remove the socket transport from the mActiveTransportList. see bug #30648 + if (m_request) { + rv = m_request->Cancel(NS_BINDING_ABORTED); + } + m_request = nullptr; + if (m_transport) { + m_transport->Close(NS_BINDING_ABORTED); + m_transport = nullptr; + } + + return rv; +} + +/* +* Writes the data contained in dataBuffer into the current output stream. It also informs +* the transport layer that this data is now available for transmission. +* Returns a positive number for success, 0 for failure (not all the bytes were written to the +* stream, etc). We need to make another pass through this file to install an error system (mscott) +* +* No logging is done in the base implementation, so aSuppressLogging is ignored. +*/ + +nsresult nsMsgProtocol::SendData(const char * dataBuffer, bool aSuppressLogging) +{ + uint32_t writeCount = 0; + + if (dataBuffer && m_outputStream) + return m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &writeCount); + // TODO make sure all the bytes in PL_strlen(dataBuffer) were written + else + return NS_ERROR_INVALID_ARG; +} + +// Whenever data arrives from the connection, core netlib notifices the protocol by calling +// OnDataAvailable. We then read and process the incoming data from the input stream. +NS_IMETHODIMP nsMsgProtocol::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) +{ + // right now, this really just means turn around and churn through the state machine + nsCOMPtr<nsIURI> uri = do_QueryInterface(ctxt); + return ProcessProtocolState(uri, inStr, sourceOffset, count); +} + +NS_IMETHODIMP nsMsgProtocol::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + nsresult rv = NS_OK; + nsCOMPtr <nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(ctxt, &rv); + if (NS_SUCCEEDED(rv) && aMsgUrl) + { + rv = aMsgUrl->SetUrlState(true, NS_OK); + if (m_loadGroup) + m_loadGroup->AddRequest(static_cast<nsIRequest *>(this), nullptr /* context isupports */); + } + + // if we are set up as a channel, we should notify our channel listener that we are starting... + // so pass in ourself as the channel and not the underlying socket or file channel the protocol + // happens to be using + if (!mSuppressListenerNotifications && m_channelListener) + { + if (!m_channelContext) + m_channelContext = do_QueryInterface(ctxt); + rv = m_channelListener->OnStartRequest(this, m_channelContext); + } + + nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport); + + if (strans) + strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout); + + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +void nsMsgProtocol::ShowAlertMessage(nsIMsgMailNewsUrl *aMsgUrl, nsresult aStatus) +{ + const char16_t* errorString = nullptr; + switch (aStatus) + { + case NS_ERROR_UNKNOWN_HOST: + case NS_ERROR_UNKNOWN_PROXY_HOST: + errorString = u"unknownHostError"; + break; + case NS_ERROR_CONNECTION_REFUSED: + case NS_ERROR_PROXY_CONNECTION_REFUSED: + errorString = u"connectionRefusedError"; + break; + case NS_ERROR_NET_TIMEOUT: + errorString = u"netTimeoutError"; + break; + case NS_ERROR_NET_RESET: + errorString = u"unknownHostError"; // ESR HACK: Can't use new correct string "netResetError" + break; + case NS_ERROR_NET_INTERRUPT: + errorString = u"unknownHostError"; // ESR HACK: Can't use new correct string u"netInterruptError" + break; + default: + // Leave the string as nullptr. + break; + } + + NS_ASSERTION(errorString, "unknown error, but don't alert user."); + if (errorString) + { + nsString errorMsg; + errorMsg.Adopt(FormatStringWithHostNameByName(errorString, aMsgUrl)); + if (errorMsg.IsEmpty()) + { + errorMsg.Assign(NS_LITERAL_STRING("[StringID ")); + errorMsg.Append(errorString); + errorMsg.AppendLiteral("?]"); + } + + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID); + if (mailSession) + mailSession->AlertUser(errorMsg, aMsgUrl); + } +} + +// stop binding is a "notification" informing us that the stream associated with aURL is going away. +NS_IMETHODIMP nsMsgProtocol::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult aStatus) +{ + nsresult rv = NS_OK; + + // if we are set up as a channel, we should notify our channel listener that we are starting... + // so pass in ourself as the channel and not the underlying socket or file channel the protocol + // happens to be using + if (!mSuppressListenerNotifications && m_channelListener) + rv = m_channelListener->OnStopRequest(this, m_channelContext, aStatus); + + nsCOMPtr <nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(ctxt, &rv); + if (NS_SUCCEEDED(rv) && msgUrl) + { + rv = msgUrl->SetUrlState(false, aStatus); // Always returns NS_OK. + if (m_loadGroup) + m_loadGroup->RemoveRequest(static_cast<nsIRequest *>(this), nullptr, aStatus); + + // !m_channelContext because if we're set up as a channel, then the remove + // request above will handle alerting the user, so we don't need to. + // + // !NS_BINDING_ABORTED because we don't want to see an alert if the user + // cancelled the operation. also, we'll get here because we call Cancel() + // to force removal of the nsSocketTransport. see CloseSocket() + // bugs #30775 and #30648 relate to this + if (!m_channelContext && NS_FAILED(aStatus) && + (aStatus != NS_BINDING_ABORTED)) + ShowAlertMessage(msgUrl, aStatus); + } // if we have a mailnews url. + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + mProgressEventSink = nullptr; + // Call CloseSocket(), in case we got here because the server dropped the + // connection while reading, and we never get a chance to get back into + // the protocol state machine via OnDataAvailable. + if (m_socketIsOpen) + CloseSocket(); + + return rv; +} + +nsresult nsMsgProtocol::GetPromptDialogFromUrl(nsIMsgMailNewsUrl * aMsgUrl, nsIPrompt ** aPromptDialog) +{ + // get the nsIPrompt interface from the message window associated wit this url. + nsCOMPtr<nsIMsgWindow> msgWindow; + aMsgUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + NS_ENSURE_TRUE(msgWindow, NS_ERROR_FAILURE); + + msgWindow->GetPromptDialog(aPromptDialog); + + NS_ENSURE_TRUE(*aPromptDialog, NS_ERROR_FAILURE); + + return NS_OK; +} + +nsresult nsMsgProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer) +{ + // okay now kick us off to the next state... + // our first state is a process state so drive the state machine... + nsresult rv = NS_OK; + nsCOMPtr <nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(aURL, &rv); + + if (NS_SUCCEEDED(rv) && aMsgUrl) + { + bool msgIsInLocalCache; + aMsgUrl->GetMsgIsInLocalCache(&msgIsInLocalCache); + + rv = aMsgUrl->SetUrlState(true, NS_OK); // set the url as a url currently being run... + + // if the url is given a stream consumer then we should use it to forward calls to... + if (!m_channelListener && aConsumer) // if we don't have a registered listener already + { + m_channelListener = do_QueryInterface(aConsumer); + if (!m_channelContext) + m_channelContext = do_QueryInterface(aURL); + } + + if (!m_socketIsOpen) + { + nsCOMPtr<nsISupports> urlSupports = do_QueryInterface(aURL); + if (m_transport) + { + // don't open the input stream more than once + if (!m_inputStream) + { + // open buffered, asynchronous input stream + rv = m_transport->OpenInputStream(0, 0, 0, getter_AddRefs(m_inputStream)); + if (NS_FAILED(rv)) return rv; + } + + nsCOMPtr<nsIInputStreamPump> pump; + rv = NS_NewInputStreamPump(getter_AddRefs(pump), + m_inputStream, -1, m_readCount); + if (NS_FAILED(rv)) return rv; + + m_request = pump; // keep a reference to the pump so we can cancel it + + // put us in a state where we are always notified of incoming data + rv = pump->AsyncRead(this, urlSupports); + NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed"); + m_socketIsOpen = true; // mark the channel as open + } + } // if we got an event queue service + else if (!msgIsInLocalCache) // the connection is already open so we should begin processing our new url... + rv = ProcessProtocolState(aURL, nullptr, 0, 0); + } + + return rv; +} + +/////////////////////////////////////////////////////////////////////// +// The rest of this file is mostly nsIChannel mumbo jumbo stuff +/////////////////////////////////////////////////////////////////////// + +nsresult nsMsgProtocol::SetUrl(nsIURI * aURL) +{ + m_url = aURL; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::SetLoadGroup(nsILoadGroup * aLoadGroup) +{ + m_loadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::GetOriginalURI(nsIURI* *aURI) +{ + *aURI = m_originalUrl ? m_originalUrl : m_url; + NS_IF_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::SetOriginalURI(nsIURI* aURI) +{ + m_originalUrl = aURI; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::GetURI(nsIURI* *aURI) +{ + *aURI = m_url; + NS_IF_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::Open(nsIInputStream **_retval) +{ + return NS_ImplementChannelOpen(this, _retval); +} + +NS_IMETHODIMP nsMsgProtocol::Open2(nsIInputStream **_retval) +{ + nsCOMPtr<nsIStreamListener> listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(_retval); +} + +NS_IMETHODIMP nsMsgProtocol::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) +{ + int32_t port; + nsresult rv = m_url->GetPort(&port); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString scheme; + rv = m_url->GetScheme(scheme); + if (NS_FAILED(rv)) + return rv; + + + rv = NS_CheckPortSafety(port, scheme.get()); + if (NS_FAILED(rv)) + return rv; + + // set the stream listener and then load the url + m_channelContext = ctxt; + m_channelListener = listener; + return LoadUrl(m_url, nullptr); +} + +NS_IMETHODIMP nsMsgProtocol::AsyncOpen2(nsIStreamListener *aListener) +{ + nsCOMPtr<nsIStreamListener> listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +NS_IMETHODIMP nsMsgProtocol::GetLoadFlags(nsLoadFlags *aLoadFlags) +{ + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::SetLoadFlags(nsLoadFlags aLoadFlags) +{ + mLoadFlags = aLoadFlags; + return NS_OK; // don't fail when trying to set this +} + +NS_IMETHODIMP nsMsgProtocol::GetContentType(nsACString &aContentType) +{ + // as url dispatching matures, we'll be intelligent and actually start + // opening the url before specifying the content type. This will allow + // us to optimize the case where the message url actual refers to + // a part in the message that has a content type that is not message/rfc822 + + if (mContentType.IsEmpty()) + aContentType.AssignLiteral("message/rfc822"); + else + aContentType = mContentType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::SetContentType(const nsACString &aContentType) +{ + nsAutoCString charset; + nsresult rv = NS_ParseResponseContentType(aContentType, mContentType, charset); + if (NS_FAILED(rv) || mContentType.IsEmpty()) + mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); + return rv; +} + +NS_IMETHODIMP nsMsgProtocol::GetContentCharset(nsACString &aContentCharset) +{ + aContentCharset.Assign(mCharset); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::SetContentCharset(const nsACString &aContentCharset) +{ + mCharset.Assign(aContentCharset); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgProtocol::GetContentDisposition(uint32_t *aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsMsgProtocol::SetContentDisposition(uint32_t aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsMsgProtocol::GetContentDispositionFilename(nsAString &aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsMsgProtocol::SetContentDispositionFilename(const nsAString &aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsMsgProtocol::GetContentDispositionHeader(nsACString &aContentDispositionHeader) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsMsgProtocol::GetContentLength(int64_t *aContentLength) +{ + *aContentLength = mContentLength; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::SetContentLength(int64_t aContentLength) +{ + mContentLength = aContentLength; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::GetSecurityInfo(nsISupports * *aSecurityInfo) +{ + *aSecurityInfo = nullptr; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgProtocol::GetName(nsACString &result) +{ + if (m_url) + return m_url->GetSpec(result); + result.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::GetOwner(nsISupports * *aPrincipal) +{ + *aPrincipal = mOwner; + NS_IF_ADDREF(*aPrincipal); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::SetOwner(nsISupports * aPrincipal) +{ + mOwner = aPrincipal; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::GetLoadGroup(nsILoadGroup * *aLoadGroup) +{ + *aLoadGroup = m_loadGroup; + NS_IF_ADDREF(*aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::GetLoadInfo(nsILoadInfo **aLoadInfo) +{ + *aLoadInfo = m_loadInfo; + NS_IF_ADDREF(*aLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::SetLoadInfo(nsILoadInfo *aLoadInfo) +{ + m_loadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgProtocol::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks) +{ + *aNotificationCallbacks = mCallbacks.get(); + NS_IF_ADDREF(*aNotificationCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgProtocol::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks) +{ + mCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgProtocol::OnTransportStatus(nsITransport *transport, nsresult status, + int64_t progress, int64_t progressMax) +{ + if ((mLoadFlags & LOAD_BACKGROUND) || !m_url) + return NS_OK; + + // these transport events should not generate any status messages + if (status == NS_NET_STATUS_RECEIVING_FROM || + status == NS_NET_STATUS_SENDING_TO) + return NS_OK; + + if (!mProgressEventSink) + { + NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink); + if (!mProgressEventSink) + return NS_OK; + } + + nsAutoCString host; + m_url->GetHost(host); + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl) + { + nsCOMPtr<nsIMsgIncomingServer> server; + mailnewsUrl->GetServer(getter_AddRefs(server)); + if (server) + server->GetRealHostName(host); + } + mProgressEventSink->OnStatus(this, nullptr, status, + NS_ConvertUTF8toUTF16(host).get()); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// From nsIRequest +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsMsgProtocol::IsPending(bool *result) +{ + *result = m_channelListener != nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgProtocol::GetStatus(nsresult *status) +{ + if (m_request) + return m_request->GetStatus(status); + + *status = NS_OK; + return *status; +} + +NS_IMETHODIMP nsMsgProtocol::Cancel(nsresult status) +{ + NS_ASSERTION(m_request,"no channel"); + if (!m_request) + return NS_ERROR_FAILURE; + + return m_request->Cancel(status); +} + +NS_IMETHODIMP nsMsgProtocol::Suspend() +{ + if (m_request) + return m_request->Suspend(); + + NS_WARNING("no request to suspend"); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsMsgProtocol::Resume() +{ + if (m_request) + return m_request->Resume(); + + NS_WARNING("no request to resume"); + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult nsMsgProtocol::PostMessage(nsIURI* url, nsIFile *postFile) +{ + if (!url || !postFile) return NS_ERROR_NULL_POINTER; + +#define POST_DATA_BUFFER_SIZE 2048 + + // mscott -- this function should be re-written to use the file url code + // so it can be asynch + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), postFile); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(inputStream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more = true; + nsCString line; + nsCString outputBuffer; + + do + { + lineInputStream->ReadLine(line, &more); + + /* escape starting periods + */ + if (line.CharAt(0) == '.') + line.Insert('.', 0); + line.Append(NS_LITERAL_CSTRING(CRLF)); + outputBuffer.Append(line); + // test hack by mscott. If our buffer is almost full, then + // send it off & reset ourselves + // to make more room. + if (outputBuffer.Length() > POST_DATA_BUFFER_SIZE || !more) + { + rv = SendData(outputBuffer.get()); + NS_ENSURE_SUCCESS(rv, rv); + // does this keep the buffer around? That would be best. + // Maybe SetLength(0) instead? + outputBuffer.Truncate(); + } + } while (more); + + return NS_OK; +} + +nsresult nsMsgProtocol::DoGSSAPIStep1(const char *service, const char *username, nsCString &response) +{ + nsresult rv; +#ifdef DEBUG_BenB + printf("GSSAPI step 1 for service %s, username %s\n", service, username); +#endif + + // if this fails, then it means that we cannot do GSSAPI SASL. + m_authModule = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sasl-gssapi", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + m_authModule->Init(service, nsIAuthModule::REQ_DEFAULT, nullptr, NS_ConvertUTF8toUTF16(username).get(), nullptr); + + void *outBuf; + uint32_t outBufLen; + rv = m_authModule->GetNextToken((void *)nullptr, 0, &outBuf, &outBufLen); + if (NS_SUCCEEDED(rv) && outBuf) + { + char *base64Str = PL_Base64Encode((char *)outBuf, outBufLen, nullptr); + if (base64Str) + response.Adopt(base64Str); + else + rv = NS_ERROR_OUT_OF_MEMORY; + free(outBuf); + } + +#ifdef DEBUG_BenB + printf("GSSAPI step 1 succeeded\n"); +#endif + return rv; +} + +nsresult nsMsgProtocol::DoGSSAPIStep2(nsCString &commandResponse, nsCString &response) +{ +#ifdef DEBUG_BenB + printf("GSSAPI step 2\n"); +#endif + nsresult rv; + void *inBuf, *outBuf; + uint32_t inBufLen, outBufLen; + uint32_t len = commandResponse.Length(); + + // Cyrus SASL may send us zero length tokens (grrrr) + if (len > 0) { + // decode into the input secbuffer + inBufLen = (len * 3)/4; // sufficient size (see plbase64.h) + inBuf = moz_xmalloc(inBufLen); + if (!inBuf) + return NS_ERROR_OUT_OF_MEMORY; + + // strip off any padding (see bug 230351) + const char *challenge = commandResponse.get(); + while (challenge[len - 1] == '=') + len--; + + // We need to know the exact length of the decoded string to give to + // the GSSAPI libraries. But NSPR's base64 routine doesn't seem capable + // of telling us that. So, we figure it out for ourselves. + + // For every 4 characters, add 3 to the destination + // If there are 3 remaining, add 2 + // If there are 2 remaining, add 1 + // 1 remaining is an error + inBufLen = (len / 4)*3 + ((len % 4 == 3)?2:0) + ((len % 4 == 2)?1:0); + + rv = (PL_Base64Decode(challenge, len, (char *)inBuf)) + ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen) + : NS_ERROR_FAILURE; + + free(inBuf); + } + else + { + rv = m_authModule->GetNextToken(NULL, 0, &outBuf, &outBufLen); + } + if (NS_SUCCEEDED(rv)) + { + // And in return, we may need to send Cyrus zero length tokens back + if (outBuf) + { + char *base64Str = PL_Base64Encode((char *)outBuf, outBufLen, nullptr); + if (base64Str) + response.Adopt(base64Str); + else + rv = NS_ERROR_OUT_OF_MEMORY; + } + else + response.Adopt((char *)nsMemory::Clone("",1)); + } + +#ifdef DEBUG_BenB + printf(NS_SUCCEEDED(rv) ? "GSSAPI step 2 succeeded\n" : "GSSAPI step 2 failed\n"); +#endif + return rv; +} + +nsresult nsMsgProtocol::DoNtlmStep1(const char *username, const char *password, nsCString &response) +{ + nsresult rv; + + m_authModule = do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "ntlm", &rv); + // if this fails, then it means that we cannot do NTLM auth. + if (NS_FAILED(rv) || !m_authModule) + return rv; + + m_authModule->Init(nullptr, 0, nullptr, NS_ConvertUTF8toUTF16(username).get(), + NS_ConvertUTF8toUTF16(password).get()); + + void *outBuf; + uint32_t outBufLen; + rv = m_authModule->GetNextToken((void *)nullptr, 0, &outBuf, &outBufLen); + if (NS_SUCCEEDED(rv) && outBuf) + { + char *base64Str = PL_Base64Encode((char *)outBuf, outBufLen, nullptr); + if (base64Str) + response.Adopt(base64Str); + else + rv = NS_ERROR_OUT_OF_MEMORY; + free(outBuf); + } + + return rv; +} + +nsresult nsMsgProtocol::DoNtlmStep2(nsCString &commandResponse, nsCString &response) +{ + nsresult rv; + void *inBuf, *outBuf; + uint32_t inBufLen, outBufLen; + uint32_t len = commandResponse.Length(); + + // decode into the input secbuffer + inBufLen = (len * 3)/4; // sufficient size (see plbase64.h) + inBuf = moz_xmalloc(inBufLen); + if (!inBuf) + return NS_ERROR_OUT_OF_MEMORY; + + // strip off any padding (see bug 230351) + const char *challenge = commandResponse.get(); + while (challenge[len - 1] == '=') + len--; + + rv = (PL_Base64Decode(challenge, len, (char *)inBuf)) + ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen) + : NS_ERROR_FAILURE; + + free(inBuf); + if (NS_SUCCEEDED(rv) && outBuf) + { + char *base64Str = PL_Base64Encode((char *)outBuf, outBufLen, nullptr); + if (base64Str) + response.Adopt(base64Str); + else + rv = NS_ERROR_OUT_OF_MEMORY; + } + + if (NS_FAILED(rv)) + response = "*"; + + return rv; +} + +///////////////////////////////////////////////////////////////////// +// nsMsgAsyncWriteProtocol subclass and related helper classes +///////////////////////////////////////////////////////////////////// + +class nsMsgProtocolStreamProvider : public nsIOutputStreamCallback +{ +public: + // XXX this probably doesn't need to be threadsafe + NS_DECL_THREADSAFE_ISUPPORTS + + nsMsgProtocolStreamProvider() { } + + void Init(nsMsgAsyncWriteProtocol *aProtInstance, nsIInputStream *aInputStream) + { + mMsgProtocol = do_GetWeakReference(static_cast<nsIStreamListener*> (aProtInstance)); + mInStream = aInputStream; + } + + // + // nsIOutputStreamCallback implementation ... + // + NS_IMETHODIMP OnOutputStreamReady(nsIAsyncOutputStream *aOutStream) override + { + NS_ASSERTION(mInStream, "not initialized"); + + nsresult rv; + uint64_t avail; + + // Write whatever is available in the pipe. If the pipe is empty, then + // return NS_BASE_STREAM_WOULD_BLOCK; we will resume the write when there + // is more data. + + rv = mInStream->Available(&avail); + if (NS_FAILED(rv)) return rv; + + nsMsgAsyncWriteProtocol *protInst = nullptr; + nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mMsgProtocol); + if (!callback) + return NS_ERROR_FAILURE; + protInst = static_cast<nsMsgAsyncWriteProtocol *>(callback.get()); + + if (avail == 0 && !protInst->mAsyncBuffer.Length()) + { + // ok, stop writing... + protInst->mSuspendedWrite = true; + return NS_OK; + } + protInst->mSuspendedWrite = false; + + uint32_t bytesWritten; + + if (avail) + { + rv = aOutStream->WriteFrom(mInStream, std::min(avail, uint64_t(FILE_IO_BUFFER_SIZE)), &bytesWritten); + // if were full at the time, the input stream may be backed up and we need to read any remains from the last ODA call + // before we'll get more ODA calls + if (protInst->mSuspendedRead) + protInst->UnblockPostReader(); + } + else + { + rv = aOutStream->Write(protInst->mAsyncBuffer.get(), + protInst->mAsyncBuffer.Length(), + &bytesWritten); + protInst->mAsyncBuffer.Cut(0, bytesWritten); + } + + protInst->UpdateProgress(bytesWritten); + + // try to write again... + if (NS_SUCCEEDED(rv)) + rv = aOutStream->AsyncWait(this, 0, 0, protInst->mProviderThread); + + NS_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_BINDING_ABORTED, "unexpected error writing stream"); + return NS_OK; + } + + +protected: + virtual ~nsMsgProtocolStreamProvider() {} + + nsCOMPtr<nsIWeakReference> mMsgProtocol; + nsCOMPtr<nsIInputStream> mInStream; +}; + +NS_IMPL_ISUPPORTS(nsMsgProtocolStreamProvider, + nsIOutputStreamCallback) + +class nsMsgFilePostHelper : public nsIStreamListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsMsgFilePostHelper() { mSuspendedPostFileRead = false;} + nsresult Init(nsIOutputStream * aOutStream, nsMsgAsyncWriteProtocol * aProtInstance, nsIFile *aFileToPost); + nsCOMPtr<nsIRequest> mPostFileRequest; + bool mSuspendedPostFileRead; + void CloseSocket() { mProtInstance = nullptr; } +protected: + virtual ~nsMsgFilePostHelper() {} + nsCOMPtr<nsIOutputStream> mOutStream; + nsCOMPtr<nsIWeakReference> mProtInstance; +}; + +NS_IMPL_ISUPPORTS(nsMsgFilePostHelper, nsIStreamListener, nsIRequestObserver) + +nsresult nsMsgFilePostHelper::Init(nsIOutputStream * aOutStream, nsMsgAsyncWriteProtocol * aProtInstance, nsIFile *aFileToPost) +{ + nsresult rv = NS_OK; + mOutStream = aOutStream; + mProtInstance = do_GetWeakReference(static_cast<nsIStreamListener*> (aProtInstance)); + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFileToPost); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIInputStreamPump> pump; + rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream); + if (NS_FAILED(rv)) return rv; + + rv = pump->AsyncRead(this, nullptr); + if (NS_FAILED(rv)) return rv; + + mPostFileRequest = pump; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilePostHelper::OnStartRequest(nsIRequest * aChannel, nsISupports *ctxt) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilePostHelper::OnStopRequest(nsIRequest * aChannel, nsISupports *ctxt, nsresult aStatus) +{ + nsMsgAsyncWriteProtocol *protInst = nullptr; + nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance); + if (!callback) + return NS_OK; + protInst = static_cast<nsMsgAsyncWriteProtocol *>(callback.get()); + + if (!mSuspendedPostFileRead) + protInst->PostDataFinished(); + + mSuspendedPostFileRead = false; + protInst->mFilePostHelper = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilePostHelper::OnDataAvailable(nsIRequest * /* aChannel */, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) +{ + nsMsgAsyncWriteProtocol *protInst = nullptr; + nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance); + if (!callback) + return NS_OK; + + protInst = static_cast<nsMsgAsyncWriteProtocol *>(callback.get()); + + if (mSuspendedPostFileRead) + { + protInst->UpdateSuspendedReadBytes(count, protInst->mInsertPeriodRequired); + return NS_OK; + } + + protInst->ProcessIncomingPostData(inStr, count); + + if (protInst->mSuspendedWrite) + { + // if we got here then we had suspended the write 'cause we didn't have anymore + // data to write (i.e. the pipe went empty). So resume the channel to kick + // things off again. + protInst->mSuspendedWrite = false; + protInst->mAsyncOutStream->AsyncWait(protInst->mProvider, 0, 0, + protInst->mProviderThread); + } + + return NS_OK; +} + +NS_IMPL_ADDREF_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol) +NS_IMPL_RELEASE_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol) + +NS_INTERFACE_MAP_BEGIN(nsMsgAsyncWriteProtocol) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol) + +nsMsgAsyncWriteProtocol::nsMsgAsyncWriteProtocol(nsIURI * aURL) : nsMsgProtocol(aURL) +{ + mSuspendedWrite = false; + mSuspendedReadBytes = 0; + mSuspendedRead = false; + mInsertPeriodRequired = false; + mGenerateProgressNotifications = false; + mSuspendedReadBytesPostPeriod = 0; + mFilePostHelper = nullptr; +} + +nsMsgAsyncWriteProtocol::~nsMsgAsyncWriteProtocol() +{} + +NS_IMETHODIMP nsMsgAsyncWriteProtocol::Cancel(nsresult status) +{ + mGenerateProgressNotifications = false; + + if (m_request) + m_request->Cancel(status); + + if (mAsyncOutStream) + mAsyncOutStream->CloseWithStatus(status); + + return NS_OK; +} + +nsresult nsMsgAsyncWriteProtocol::PostMessage(nsIURI* url, nsIFile *file) +{ + nsCOMPtr<nsIStreamListener> listener = new nsMsgFilePostHelper(); + + if (!listener) return NS_ERROR_OUT_OF_MEMORY; + + // be sure to initialize some state before posting + mSuspendedReadBytes = 0; + mNumBytesPosted = 0; + file->GetFileSize(&mFilePostSize); + mSuspendedRead = false; + mInsertPeriodRequired = false; + mSuspendedReadBytesPostPeriod = 0; + mGenerateProgressNotifications = true; + + mFilePostHelper = static_cast<nsMsgFilePostHelper*>(static_cast<nsIStreamListener*>(listener)); + + static_cast<nsMsgFilePostHelper*>(static_cast<nsIStreamListener*>(listener))->Init(m_outputStream, this, file); + + return NS_OK; +} + +nsresult nsMsgAsyncWriteProtocol::SuspendPostFileRead() +{ + if (mFilePostHelper && !mFilePostHelper->mSuspendedPostFileRead) + { + // uhoh we need to pause reading in the file until we get unblocked... + mFilePostHelper->mPostFileRequest->Suspend(); + mFilePostHelper->mSuspendedPostFileRead = true; + } + + return NS_OK; +} + +nsresult nsMsgAsyncWriteProtocol::ResumePostFileRead() +{ + if (mFilePostHelper) + { + if (mFilePostHelper->mSuspendedPostFileRead) + { + mFilePostHelper->mPostFileRequest->Resume(); + mFilePostHelper->mSuspendedPostFileRead = false; + } + } + else // we must be done with the download so send the '.' + { + PostDataFinished(); + } + + return NS_OK; +} + +nsresult nsMsgAsyncWriteProtocol::UpdateSuspendedReadBytes(uint32_t aNewBytes, bool aAddToPostPeriodByteCount) +{ + // depending on our current state, we'll either add aNewBytes to mSuspendedReadBytes + // or mSuspendedReadBytesAfterPeriod. + + mSuspendedRead = true; + if (aAddToPostPeriodByteCount) + mSuspendedReadBytesPostPeriod += aNewBytes; + else + mSuspendedReadBytes += aNewBytes; + + return NS_OK; +} + +nsresult nsMsgAsyncWriteProtocol::PostDataFinished() +{ + nsresult rv = SendData("." CRLF); + if (NS_FAILED(rv)) + return rv; + mGenerateProgressNotifications = false; + mPostDataStream = nullptr; + return NS_OK; +} + +nsresult nsMsgAsyncWriteProtocol::ProcessIncomingPostData(nsIInputStream *inStr, uint32_t count) +{ + if (!m_socketIsOpen) return NS_OK; // kick out if the socket was canceled + + // We need to quote any '.' that occur at the beginning of a line. + // but I don't want to waste time reading out the data into a buffer and searching + // let's try to leverage nsIBufferedInputStream and see if we can "peek" into the + // current contents for this particular case. + + nsCOMPtr<nsISearchableInputStream> bufferInputStr = do_QueryInterface(inStr); + NS_ASSERTION(bufferInputStr, "i made a wrong assumption about the type of stream we are getting"); + NS_ASSERTION(mSuspendedReadBytes == 0, "oops, I missed something"); + + if (!mPostDataStream) mPostDataStream = inStr; + + if (bufferInputStr) + { + uint32_t amountWritten; + + while (count > 0) + { + bool found = false; + uint32_t offset = 0; + bufferInputStr->Search("\012.", true, &found, &offset); // LF. + + if (!found || offset > count) + { + // push this data into the output stream + m_outputStream->WriteFrom(inStr, count, &amountWritten); + // store any remains which need read out at a later date + if (count > amountWritten) // stream will block + { + UpdateSuspendedReadBytes(count - amountWritten, false); + SuspendPostFileRead(); + } + break; + } + else + { + // count points to the LF in a LF followed by a '.' + // go ahead and write up to offset.. + m_outputStream->WriteFrom(inStr, offset + 1, &amountWritten); + count -= amountWritten; + if (offset+1 > amountWritten) + { + UpdateSuspendedReadBytes(offset+1 - amountWritten, false); + mInsertPeriodRequired = true; + UpdateSuspendedReadBytes(count, mInsertPeriodRequired); + SuspendPostFileRead(); + break; + } + + // write out the extra '.' + m_outputStream->Write(".", 1, &amountWritten); + if (amountWritten != 1) + { + mInsertPeriodRequired = true; + // once we do write out the '.', if we are now blocked we need to remember the remaining count that comes + // after the '.' so we can perform processing on that once we become unblocked. + UpdateSuspendedReadBytes(count, mInsertPeriodRequired); + SuspendPostFileRead(); + break; + } + } + } // while count > 0 + } + + return NS_OK; +} +nsresult nsMsgAsyncWriteProtocol::UnblockPostReader() +{ + uint32_t amountWritten = 0; + + if (!m_socketIsOpen) return NS_OK; // kick out if the socket was canceled + + if (mSuspendedRead) + { + // (1) attempt to write out any remaining read bytes we need in order to unblock the reader + if (mSuspendedReadBytes > 0 && mPostDataStream) + { + uint64_t avail = 0; + mPostDataStream->Available(&avail); + + m_outputStream->WriteFrom(mPostDataStream, std::min(avail, uint64_t(mSuspendedReadBytes)), &amountWritten); + // hmm sometimes my mSuspendedReadBytes is getting out of whack...so for now, reset it + // if necessary. + if (mSuspendedReadBytes > avail) + mSuspendedReadBytes = avail; + + if (mSuspendedReadBytes > amountWritten) + mSuspendedReadBytes -= amountWritten; + else + mSuspendedReadBytes = 0; + } + + // (2) if we are now unblocked, and we need to insert a '.' then do so now... + if (mInsertPeriodRequired && mSuspendedReadBytes == 0) + { + amountWritten = 0; + m_outputStream->Write(".", 1, &amountWritten); + if (amountWritten == 1) // if we succeeded then clear pending '.' flag + mInsertPeriodRequired = false; + } + + // (3) if we inserted a '.' and we still have bytes after the '.' which need processed before the stream is unblocked + // then fake an ODA call to handle this now... + if (!mInsertPeriodRequired && mSuspendedReadBytesPostPeriod > 0) + { + // these bytes actually need processed for extra '.''s..... + uint32_t postbytes = mSuspendedReadBytesPostPeriod; + mSuspendedReadBytesPostPeriod = 0; + ProcessIncomingPostData(mPostDataStream, postbytes); + } + + // (4) determine if we are out of the suspended read state... + if (mSuspendedReadBytes == 0 && !mInsertPeriodRequired && mSuspendedReadBytesPostPeriod == 0) + { + mSuspendedRead = false; + ResumePostFileRead(); + } + + } // if we are in the suspended read state + + return NS_OK; +} + +nsresult nsMsgAsyncWriteProtocol::SetupTransportState() +{ + nsresult rv = NS_OK; + + if (!m_outputStream && m_transport) + { + // first create a pipe which we'll use to write the data we want to send + // into. + nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1"); + rv = pipe->Init(true, true, 1024, 8); + NS_ENSURE_SUCCESS(rv, rv); + + nsIAsyncInputStream *inputStream = nullptr; + // This always succeeds because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(&inputStream)); + mInStream = dont_AddRef(static_cast<nsIInputStream *>(inputStream)); + + nsIAsyncOutputStream *outputStream = nullptr; + // This always succeeds because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(&outputStream)); + m_outputStream = dont_AddRef(static_cast<nsIOutputStream *>(outputStream)); + + mProviderThread = do_GetCurrentThread(); + + nsMsgProtocolStreamProvider *provider = new nsMsgProtocolStreamProvider(); + + if (!provider) return NS_ERROR_OUT_OF_MEMORY; + + provider->Init(this, mInStream); + mProvider = provider; // ADDREF + + nsCOMPtr<nsIOutputStream> stream; + rv = m_transport->OpenOutputStream(0, 0, 0, getter_AddRefs(stream)); + if (NS_FAILED(rv)) return rv; + + mAsyncOutStream = do_QueryInterface(stream, &rv); + if (NS_FAILED(rv)) return rv; + + // wait for the output stream to become writable + rv = mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread); + } // if m_transport + + return rv; +} + +nsresult nsMsgAsyncWriteProtocol::CloseSocket() +{ + nsresult rv = NS_OK; + if (mAsyncOutStream) + mAsyncOutStream->CloseWithStatus(NS_BINDING_ABORTED); + + nsMsgProtocol::CloseSocket(); + + if (mFilePostHelper) + { + mFilePostHelper->CloseSocket(); + mFilePostHelper = nullptr; + } + + mAsyncOutStream = nullptr; + mProvider = nullptr; + mProviderThread = nullptr; + mAsyncBuffer.Truncate(); + return rv; +} + +void nsMsgAsyncWriteProtocol::UpdateProgress(uint32_t aNewBytes) +{ + if (!mGenerateProgressNotifications) return; + + mNumBytesPosted += aNewBytes; + if (mFilePostSize > 0) + { + nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url); + if (!mailUrl) return; + + nsCOMPtr<nsIMsgStatusFeedback> statusFeedback; + mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback)); + if (!statusFeedback) return; + + nsCOMPtr<nsIWebProgressListener> webProgressListener (do_QueryInterface(statusFeedback)); + if (!webProgressListener) return; + + // XXX not sure if m_request is correct here + webProgressListener->OnProgressChange(nullptr, m_request, mNumBytesPosted, mFilePostSize, mNumBytesPosted, mFilePostSize); + } + + return; +} + +nsresult nsMsgAsyncWriteProtocol::SendData(const char * dataBuffer, bool aSuppressLogging) +{ + this->mAsyncBuffer.Append(dataBuffer); + if (!mAsyncOutStream) + return NS_ERROR_FAILURE; + return mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread); +} + +char16_t *FormatStringWithHostNameByName(const char16_t* stringName, nsIMsgMailNewsUrl *msgUri) +{ + if (!msgUri) + return nullptr; + + nsresult rv; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sBundleService, nullptr); + + nsCOMPtr<nsIStringBundle> sBundle; + rv = sBundleService->CreateBundle(MSGS_URL, getter_AddRefs(sBundle)); + NS_ENSURE_SUCCESS(rv, nullptr); + + char16_t *ptrv = nullptr; + nsCOMPtr<nsIMsgIncomingServer> server; + rv = msgUri->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCString hostName; + rv = server->GetRealHostName(hostName); + NS_ENSURE_SUCCESS(rv, nullptr); + + NS_ConvertASCIItoUTF16 hostStr(hostName); + const char16_t *params[] = { hostStr.get() }; + rv = sBundle->FormatStringFromName(stringName, params, 1, &ptrv); + NS_ENSURE_SUCCESS(rv, nullptr); + + return ptrv; +} + +// vim: ts=2 sw=2 diff --git a/mailnews/base/util/nsMsgProtocol.h b/mailnews/base/util/nsMsgProtocol.h new file mode 100644 index 000000000..98aa67285 --- /dev/null +++ b/mailnews/base/util/nsMsgProtocol.h @@ -0,0 +1,239 @@ +/* -*- 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 nsMsgProtocol_h__ +#define nsMsgProtocol_h__ + +#include "mozilla/Attributes.h" +#include "nsIStreamListener.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIChannel.h" +#include "nsIURL.h" +#include "nsIThread.h" +#include "nsILoadGroup.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIProgressEventSink.h" +#include "nsITransport.h" +#include "nsIAsyncOutputStream.h" +#include "nsIAuthModule.h" +#include "nsStringGlue.h" +#include "nsWeakReference.h" + +class nsIMsgWindow; +class nsIPrompt; +class nsIMsgMailNewsUrl; +class nsMsgFilePostHelper; +class nsIProxyInfo; + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT + +// This is a helper class used to encapsulate code shared between all of the +// mailnews protocol objects (imap, news, pop, smtp, etc.) In particular, +// it unifies the core networking code for the protocols. My hope is that +// this will make unification with Necko easier as we'll only have to change +// this class and not all of the mailnews protocols. +class NS_MSG_BASE nsMsgProtocol : public nsIStreamListener + , public nsIChannel + , public nsITransportEventSink +{ +public: + nsMsgProtocol(nsIURI * aURL); + + NS_DECL_THREADSAFE_ISUPPORTS + // nsIChannel support + NS_DECL_NSICHANNEL + NS_DECL_NSIREQUEST + + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSITRANSPORTEVENTSINK + + // LoadUrl -- A protocol typically overrides this function, sets up any local state for the url and + // then calls the base class which opens the socket if it needs opened. If the socket is + // already opened then we just call ProcessProtocolState to start the churning process. + // aConsumer is the consumer for the url. It can be null if this argument is not appropriate + virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer = nullptr); + + virtual nsresult SetUrl(nsIURI * aURL); // sometimes we want to set the url before we load it + void ShowAlertMessage(nsIMsgMailNewsUrl *aMsgUrl, nsresult aStatus); + + // Flag manipulators + virtual bool TestFlag (uint32_t flag) {return flag & m_flags;} + virtual void SetFlag (uint32_t flag) { m_flags |= flag; } + virtual void ClearFlag (uint32_t flag) { m_flags &= ~flag; } + +protected: + virtual ~nsMsgProtocol(); + + // methods for opening and closing a socket with core netlib.... + // mscott -okay this is lame. I should break this up into a file protocol and a socket based + // protocool class instead of cheating and putting both methods here... + + // open a connection with a specific host and port + // aHostName must be UTF-8 encoded. + virtual nsresult OpenNetworkSocketWithInfo(const char * aHostName, + int32_t aGetPort, + const char *connectionType, + nsIProxyInfo *aProxyInfo, + nsIInterfaceRequestor* callbacks); + // helper routine + nsresult GetFileFromURL(nsIURI * aURL, nsIFile **aResult); + virtual nsresult OpenFileSocket(nsIURI * aURL, uint32_t aStartPosition, int32_t aReadCount); // used to open a file socket connection + + nsresult GetTopmostMsgWindow(nsIMsgWindow **aWindow); + + virtual const char* GetType() {return nullptr;} + nsresult GetQoSBits(uint8_t *aQoSBits); + + // a Protocol typically overrides this method. They free any of their own connection state and then + // they call up into the base class to free the generic connection objects + virtual nsresult CloseSocket(); + + virtual nsresult SetupTransportState(); // private method used by OpenNetworkSocket and OpenFileSocket + + // ProcessProtocolState - This is the function that gets churned by calls to OnDataAvailable. + // As data arrives on the socket, OnDataAvailable calls ProcessProtocolState. + + virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream, + uint64_t sourceOffset, uint32_t length) = 0; + + // SendData -- Writes the data contained in dataBuffer into the current output stream. + // It also informs the transport layer that this data is now available for transmission. + // Returns a positive number for success, 0 for failure (not all the bytes were written to the + // stream, etc). + // aSuppressLogging is a hint that sensitive data is being sent and should not be logged + virtual nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false); + + virtual nsresult PostMessage(nsIURI* url, nsIFile* aPostFile); + + virtual nsresult InitFromURI(nsIURI *aUrl); + + nsresult DoNtlmStep1(const char *username, const char *password, nsCString &response); + nsresult DoNtlmStep2(nsCString &commandResponse, nsCString &response); + + nsresult DoGSSAPIStep1(const char *service, const char *username, nsCString &response); + nsresult DoGSSAPIStep2(nsCString &commandResponse, nsCString &response); + // Ouput stream for writing commands to the socket + nsCOMPtr<nsIOutputStream> m_outputStream; // this will be obtained from the transport interface + nsCOMPtr<nsIInputStream> m_inputStream; + + // Ouput stream for writing commands to the socket + nsCOMPtr<nsITransport> m_transport; + nsCOMPtr<nsIRequest> m_request; + + bool m_socketIsOpen; // mscott: we should look into keeping this state in the nsSocketTransport... + // I'm using it to make sure I open the socket the first time a URL is loaded into the connection + uint32_t m_flags; // used to store flag information + //uint32_t m_startPosition; + int32_t m_readCount; + + nsCOMPtr<nsIFile> m_tempMsgFile; // we currently have a hack where displaying a msg involves writing it to a temp file first + + // auth module for access to NTLM functions + nsCOMPtr<nsIAuthModule> m_authModule; + + // the following is a catch all for nsIChannel related data + nsCOMPtr<nsIURI> m_originalUrl; // the original url + nsCOMPtr<nsIURI> m_url; // the running url + nsCOMPtr<nsIStreamListener> m_channelListener; + nsCOMPtr<nsISupports> m_channelContext; + nsCOMPtr<nsILoadGroup> m_loadGroup; + nsLoadFlags mLoadFlags; + nsCOMPtr<nsIProgressEventSink> mProgressEventSink; + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsISupports> mOwner; + nsCString mContentType; + nsCString mCharset; + int64_t mContentLength; + nsCOMPtr<nsILoadInfo> m_loadInfo; + + nsCString m_lastPasswordSent; // used to prefill the password prompt + + // private helper routine used by subclasses to quickly get a reference to the correct prompt dialog + // for a mailnews url. + nsresult GetPromptDialogFromUrl(nsIMsgMailNewsUrl * aMsgUrl, nsIPrompt ** aPromptDialog); + + // if a url isn't going to result in any content then we want to suppress calls to + // OnStartRequest, OnDataAvailable and OnStopRequest + bool mSuppressListenerNotifications; +}; + + +// This is is a subclass of nsMsgProtocol extends the parent class with AsyncWrite support. Protocols like smtp +// and news want to leverage aysnc write. We don't want everyone who inherits from nsMsgProtocol to have to +// pick up the extra overhead. +class NS_MSG_BASE nsMsgAsyncWriteProtocol : public nsMsgProtocol + , public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Cancel(nsresult status) override; + + nsMsgAsyncWriteProtocol(nsIURI * aURL); + + // temporary over ride... + virtual nsresult PostMessage(nsIURI* url, nsIFile *postFile) override; + + // over ride the following methods from the base class + virtual nsresult SetupTransportState() override; + virtual nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false) override; + nsCString mAsyncBuffer; + + // if we suspended the asynch write while waiting for more data to write then this will be TRUE + bool mSuspendedWrite; + nsCOMPtr<nsIRequest> m_WriteRequest; + nsCOMPtr<nsIAsyncOutputStream> mAsyncOutStream; + nsCOMPtr<nsIOutputStreamCallback> mProvider; + nsCOMPtr<nsIThread> mProviderThread; + + // because we are reading the post data in asychronously, it's possible that we aren't sending it + // out fast enough and the reading gets blocked. The following set of state variables are used to + // track this. + bool mSuspendedRead; + bool mInsertPeriodRequired; // do we need to insert a '.' as part of the unblocking process + + nsresult ProcessIncomingPostData(nsIInputStream *inStr, uint32_t count); + nsresult UnblockPostReader(); + nsresult UpdateSuspendedReadBytes(uint32_t aNewBytes, bool aAddToPostPeriodByteCount); + nsresult PostDataFinished(); // this is so we'll send out a closing '.' and release any state related to the post + + + // these two routines are used to pause and resume our loading of the file containing the contents + // we are trying to post. We call these routines when we aren't sending the bits out fast enough + // to keep up with the file read. + nsresult SuspendPostFileRead(); + nsresult ResumePostFileRead(); + nsresult UpdateSuspendedReadBytes(uint32_t aNewBytes); + void UpdateProgress(uint32_t aNewBytes); + nsMsgFilePostHelper * mFilePostHelper; // needs to be a weak reference +protected: + virtual ~nsMsgAsyncWriteProtocol(); + + // the streams for the pipe used to queue up data for the async write calls to the server. + // we actually re-use the same mOutStream variable in our parent class for the output + // stream to the socket channel. So no need for a new variable here. + nsCOMPtr<nsIInputStream> mInStream; + nsCOMPtr<nsIInputStream> mPostDataStream; + uint32_t mSuspendedReadBytes; // remaining # of bytes we need to read before + // the input stream becomes unblocked + uint32_t mSuspendedReadBytesPostPeriod; // # of bytes which need processed after we insert a '.' before + // the input stream becomes unblocked. + int64_t mFilePostSize; // used for determining progress on posting files. + uint32_t mNumBytesPosted; // used for deterimining progress on posting files + bool mGenerateProgressNotifications; // set during a post operation after we've started sending the post data... + + virtual nsresult CloseSocket() override; +}; + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN + +#endif /* nsMsgProtocol_h__ */ diff --git a/mailnews/base/util/nsMsgReadStateTxn.cpp b/mailnews/base/util/nsMsgReadStateTxn.cpp new file mode 100644 index 000000000..68524e960 --- /dev/null +++ b/mailnews/base/util/nsMsgReadStateTxn.cpp @@ -0,0 +1,66 @@ +/* -*- 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 "nsMsgReadStateTxn.h" + +#include "nsIMutableArray.h" +#include "nsIMsgHdr.h" +#include "nsComponentManagerUtils.h" + + +nsMsgReadStateTxn::nsMsgReadStateTxn() +{ +} + +nsMsgReadStateTxn::~nsMsgReadStateTxn() +{ +} + +nsresult +nsMsgReadStateTxn::Init(nsIMsgFolder *aParentFolder, + uint32_t aNumKeys, + nsMsgKey *aMsgKeyArray) +{ + NS_ENSURE_ARG_POINTER(aParentFolder); + + mParentFolder = aParentFolder; + mMarkedMessages.AppendElements(aMsgKeyArray, aNumKeys); + + return nsMsgTxn::Init(); +} + +NS_IMETHODIMP +nsMsgReadStateTxn::UndoTransaction() +{ + return MarkMessages(false); +} + +NS_IMETHODIMP +nsMsgReadStateTxn::RedoTransaction() +{ + return MarkMessages(true); +} + +NS_IMETHODIMP +nsMsgReadStateTxn::MarkMessages(bool aAsRead) +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> messageArray = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t length = mMarkedMessages.Length(); + for (uint32_t i = 0; i < length; i++) { + nsCOMPtr<nsIMsgDBHdr> curMsgHdr; + rv = mParentFolder->GetMessageHeader(mMarkedMessages[i], + getter_AddRefs(curMsgHdr)); + if (NS_SUCCEEDED(rv) && curMsgHdr) { + messageArray->AppendElement(curMsgHdr, false); + } + } + + return mParentFolder->MarkMessagesRead(messageArray, aAsRead); +} + diff --git a/mailnews/base/util/nsMsgReadStateTxn.h b/mailnews/base/util/nsMsgReadStateTxn.h new file mode 100644 index 000000000..45a15e27c --- /dev/null +++ b/mailnews/base/util/nsMsgReadStateTxn.h @@ -0,0 +1,48 @@ +/* -*- 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 nsMsgBaseUndoTxn_h_ +#define nsMsgBaseUndoTxn_h_ + +#include "mozilla/Attributes.h" +#include "nsMsgTxn.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "MailNewsTypes.h" +#include "nsIMsgFolder.h" + + +#define NS_MSGREADSTATETXN_IID \ +{ /* 121FCE4A-3EA1-455C-8161-839E1557D0CF */ \ + 0x121FCE4A, 0x3EA1, 0x455C, \ + { 0x81, 0x61, 0x83, 0x9E, 0x15, 0x57, 0xD0, 0xCF } \ +} + + +//------------------------------------------------------------------------------ +// A mark-all transaction handler. Helper for redo/undo of message read states. +//------------------------------------------------------------------------------ +class NS_MSG_BASE nsMsgReadStateTxn : public nsMsgTxn +{ +public: + nsMsgReadStateTxn(); + virtual ~nsMsgReadStateTxn(); + + nsresult Init(nsIMsgFolder *aParentFolder, + uint32_t aNumKeys, + nsMsgKey *aMsgKeyArray); + NS_IMETHOD UndoTransaction() override; + NS_IMETHOD RedoTransaction() override; + +protected: + NS_IMETHOD MarkMessages(bool aAsRead); + +private: + nsCOMPtr<nsIMsgFolder> mParentFolder; + nsTArray<nsMsgKey> mMarkedMessages; +}; + +#endif // nsMsgBaseUndoTxn_h_ + diff --git a/mailnews/base/util/nsMsgTxn.cpp b/mailnews/base/util/nsMsgTxn.cpp new file mode 100644 index 000000000..6d72360c2 --- /dev/null +++ b/mailnews/base/util/nsMsgTxn.cpp @@ -0,0 +1,294 @@ +/* -*- 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/. */ + +#include "nsMsgTxn.h" +#include "nsIMsgHdr.h" +#include "nsIMsgDatabase.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "nsComponentManagerUtils.h" +#include "nsVariant.h" +#include "nsIProperty.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgFolder.h" + +NS_IMPL_ADDREF(nsMsgTxn) +NS_IMPL_RELEASE(nsMsgTxn) +NS_INTERFACE_MAP_BEGIN(nsMsgTxn) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsITransaction) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END + +nsMsgTxn::nsMsgTxn() +{ + m_txnType = 0; +} + +nsMsgTxn::~nsMsgTxn() +{ +} + +nsresult nsMsgTxn::Init() +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgTxn::HasKey(const nsAString& name, bool *aResult) +{ + *aResult = mPropertyHash.Get(name, nullptr); + return NS_OK; +} + +NS_IMETHODIMP nsMsgTxn::Get(const nsAString& name, nsIVariant* *_retval) +{ + mPropertyHash.Get(name, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsMsgTxn::GetProperty(const nsAString& name, nsIVariant* * _retval) +{ + return mPropertyHash.Get(name, _retval) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgTxn::SetProperty(const nsAString& name, nsIVariant *value) +{ + NS_ENSURE_ARG_POINTER(value); + mPropertyHash.Put(name, value); + return NS_OK; +} + +NS_IMETHODIMP nsMsgTxn::DeleteProperty(const nsAString& name) +{ + if (!mPropertyHash.Get(name, nullptr)) + return NS_ERROR_FAILURE; + + mPropertyHash.Remove(name); + return mPropertyHash.Get(name, nullptr) ? NS_ERROR_FAILURE : NS_OK; +} + +// +// nsMailSimpleProperty class and impl; used for GetEnumerator +// This is same as nsSimpleProperty but for external API use. +// + +class nsMailSimpleProperty final : public nsIProperty +{ +public: + nsMailSimpleProperty(const nsAString& aName, nsIVariant* aValue) + : mName(aName), mValue(aValue) + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTY +protected: + ~nsMailSimpleProperty() {} + + nsString mName; + nsCOMPtr<nsIVariant> mValue; +}; + +NS_IMPL_ISUPPORTS(nsMailSimpleProperty, nsIProperty) + +NS_IMETHODIMP nsMailSimpleProperty::GetName(nsAString& aName) +{ + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP nsMailSimpleProperty::GetValue(nsIVariant* *aValue) +{ + NS_IF_ADDREF(*aValue = mValue); + return NS_OK; +} + +// end nsMailSimpleProperty + +NS_IMETHODIMP nsMsgTxn::GetEnumerator(nsISimpleEnumerator* *_retval) +{ + nsCOMArray<nsIProperty> propertyArray; + for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) { + nsMailSimpleProperty *sprop = new nsMailSimpleProperty(iter.Key(), + iter.Data()); + propertyArray.AppendObject(sprop); + } + return NS_NewArrayEnumerator(_retval, propertyArray); +} + +#define IMPL_GETSETPROPERTY_AS(Name, Type) \ +NS_IMETHODIMP \ +nsMsgTxn::GetPropertyAs ## Name (const nsAString & prop, Type *_retval) \ +{ \ + nsIVariant* v = mPropertyHash.GetWeak(prop); \ + if (!v) \ + return NS_ERROR_NOT_AVAILABLE; \ + return v->GetAs ## Name(_retval); \ +} \ +\ +NS_IMETHODIMP \ +nsMsgTxn::SetPropertyAs ## Name (const nsAString & prop, Type value) \ +{ \ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); \ + var->SetAs ## Name(value); \ + return SetProperty(prop, var); \ +} + +IMPL_GETSETPROPERTY_AS(Int32, int32_t) +IMPL_GETSETPROPERTY_AS(Uint32, uint32_t) +IMPL_GETSETPROPERTY_AS(Int64, int64_t) +IMPL_GETSETPROPERTY_AS(Uint64, uint64_t) +IMPL_GETSETPROPERTY_AS(Double, double) +IMPL_GETSETPROPERTY_AS(Bool, bool) + +NS_IMETHODIMP nsMsgTxn::GetPropertyAsAString(const nsAString & prop, + nsAString & _retval) +{ + nsIVariant* v = mPropertyHash.GetWeak(prop); + if (!v) + return NS_ERROR_NOT_AVAILABLE; + return v->GetAsAString(_retval); +} + +NS_IMETHODIMP nsMsgTxn::GetPropertyAsACString(const nsAString & prop, + nsACString & _retval) +{ + nsIVariant* v = mPropertyHash.GetWeak(prop); + if (!v) + return NS_ERROR_NOT_AVAILABLE; + return v->GetAsACString(_retval); +} + +NS_IMETHODIMP nsMsgTxn::GetPropertyAsAUTF8String(const nsAString & prop, + nsACString & _retval) +{ + nsIVariant* v = mPropertyHash.GetWeak(prop); + if (!v) + return NS_ERROR_NOT_AVAILABLE; + return v->GetAsAUTF8String(_retval); +} + +NS_IMETHODIMP nsMsgTxn::GetPropertyAsInterface(const nsAString & prop, + const nsIID & aIID, + void** _retval) +{ + nsIVariant* v = mPropertyHash.GetWeak(prop); + if (!v) + return NS_ERROR_NOT_AVAILABLE; + nsCOMPtr<nsISupports> val; + nsresult rv = v->GetAsISupports(getter_AddRefs(val)); + if (NS_FAILED(rv)) + return rv; + if (!val) { + // We have a value, but it's null + *_retval = nullptr; + return NS_OK; + } + return val->QueryInterface(aIID, _retval); +} + +NS_IMETHODIMP nsMsgTxn::SetPropertyAsAString(const nsAString & prop, + const nsAString & value) +{ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsAString(value); + return SetProperty(prop, var); +} + +NS_IMETHODIMP nsMsgTxn::SetPropertyAsACString(const nsAString & prop, + const nsACString & value) +{ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsACString(value); + return SetProperty(prop, var); +} + +NS_IMETHODIMP nsMsgTxn::SetPropertyAsAUTF8String(const nsAString & prop, + const nsACString & value) +{ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsAUTF8String(value); + return SetProperty(prop, var); +} + +NS_IMETHODIMP nsMsgTxn::SetPropertyAsInterface(const nsAString & prop, + nsISupports* value) +{ + nsCOMPtr<nsIWritableVariant> var = new nsVariant(); + var->SetAsISupports(value); + return SetProperty(prop, var); +} + +/////////////////////// Transaction Stuff ////////////////// +NS_IMETHODIMP nsMsgTxn::DoTransaction(void) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgTxn::GetIsTransient(bool *aIsTransient) +{ + if (nullptr!=aIsTransient) + *aIsTransient = false; + else + return NS_ERROR_NULL_POINTER; + return NS_OK; +} + +NS_IMETHODIMP nsMsgTxn::Merge(nsITransaction *aTransaction, bool *aDidMerge) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +nsresult nsMsgTxn::GetMsgWindow(nsIMsgWindow **msgWindow) +{ + if (!msgWindow || !m_msgWindow) + return NS_ERROR_NULL_POINTER; + *msgWindow = m_msgWindow; + NS_ADDREF (*msgWindow); + return NS_OK; +} + +nsresult nsMsgTxn::SetMsgWindow(nsIMsgWindow *msgWindow) +{ + m_msgWindow = msgWindow; + return NS_OK; +} + + +nsresult +nsMsgTxn::SetTransactionType(uint32_t txnType) +{ + return SetPropertyAsUint32(NS_LITERAL_STRING("type"), txnType); +} + +/*none of the callers pass null aFolder, + we always initialize aResult (before we pass in) for the case where the key is not in the db*/ +nsresult +nsMsgTxn::CheckForToggleDelete(nsIMsgFolder *aFolder, const nsMsgKey &aMsgKey, bool *aResult) +{ + NS_ENSURE_ARG(aResult); + nsCOMPtr<nsIMsgDBHdr> message; + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db)); + if (db) + { + bool containsKey; + rv = db->ContainsKey(aMsgKey, &containsKey); + if (NS_FAILED(rv) || !containsKey) // the message has been deleted from db, so we cannot do toggle here + return NS_OK; + rv = db->GetMsgHdrForKey(aMsgKey, getter_AddRefs(message)); + uint32_t flags; + if (NS_SUCCEEDED(rv) && message) + { + message->GetFlags(&flags); + *aResult = (flags & nsMsgMessageFlags::IMAPDeleted) != 0; + } + } + return rv; +} diff --git a/mailnews/base/util/nsMsgTxn.h b/mailnews/base/util/nsMsgTxn.h new file mode 100644 index 000000000..5c34de4d4 --- /dev/null +++ b/mailnews/base/util/nsMsgTxn.h @@ -0,0 +1,73 @@ +/* -*- 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 nsMsgTxn_h__ +#define nsMsgTxn_h__ + +#include "mozilla/Attributes.h" +#include "nsITransaction.h" +#include "msgCore.h" +#include "nsCOMPtr.h" +#include "nsIMsgWindow.h" +#include "nsInterfaceHashtable.h" +#include "MailNewsTypes2.h" +#include "nsIVariant.h" +#include "nsIWritablePropertyBag.h" +#include "nsIWritablePropertyBag2.h" + +#define NS_MESSAGETRANSACTION_IID \ +{ /* da621b30-1efc-11d3-abe4-00805f8ac968 */ \ + 0xda621b30, 0x1efc, 0x11d3, \ + { 0xab, 0xe4, 0x00, 0x80, 0x5f, 0x8a, 0xc9, 0x68 } } +/** + * base class for all message undo/redo transactions. + */ + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT + +class NS_MSG_BASE nsMsgTxn : public nsITransaction, + public nsIWritablePropertyBag, + public nsIWritablePropertyBag2 +{ +public: + nsMsgTxn(); + + nsresult Init(); + + NS_IMETHOD DoTransaction(void) override; + + NS_IMETHOD UndoTransaction(void) override = 0; + + NS_IMETHOD RedoTransaction(void) override = 0; + + NS_IMETHOD GetIsTransient(bool *aIsTransient) override; + + NS_IMETHOD Merge(nsITransaction *aTransaction, bool *aDidMerge) override; + + nsresult GetMsgWindow(nsIMsgWindow **msgWindow); + nsresult SetMsgWindow(nsIMsgWindow *msgWindow); + nsresult SetTransactionType(uint32_t txnType); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROPERTYBAG + NS_DECL_NSIPROPERTYBAG2 + NS_DECL_NSIWRITABLEPROPERTYBAG + NS_DECL_NSIWRITABLEPROPERTYBAG2 + +protected: + virtual ~nsMsgTxn(); + + // a hash table of string -> nsIVariant + nsInterfaceHashtable<nsStringHashKey, nsIVariant> mPropertyHash; + nsCOMPtr<nsIMsgWindow> m_msgWindow; + uint32_t m_txnType; + nsresult CheckForToggleDelete(nsIMsgFolder *aFolder, const nsMsgKey &aMsgKey, bool *aResult); +}; + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_HIDDEN + +#endif diff --git a/mailnews/base/util/nsMsgUtils.cpp b/mailnews/base/util/nsMsgUtils.cpp new file mode 100644 index 000000000..0ed04cd47 --- /dev/null +++ b/mailnews/base/util/nsMsgUtils.cpp @@ -0,0 +1,2520 @@ +/* -*- 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" +#include "nsIMsgHdr.h" +#include "nsMsgUtils.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgMessageFlags.h" +#include "nsStringGlue.h" +#include "nsIServiceManager.h" +#include "nsCOMPtr.h" +#include "nsIFolderLookupService.h" +#include "nsIImapUrl.h" +#include "nsIMailboxUrl.h" +#include "nsINntpUrl.h" +#include "nsMsgNewsCID.h" +#include "nsMsgLocalCID.h" +#include "nsMsgBaseCID.h" +#include "nsMsgImapCID.h" +#include "nsMsgI18N.h" +#include "nsNativeCharsetUtils.h" +#include "nsCharTraits.h" +#include "prprf.h" +#include "prmem.h" +#include "nsNetCID.h" +#include "nsIIOService.h" +#include "nsIRDFService.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsISupportsPrimitives.h" +#include "nsIPrefLocalizedString.h" +#include "nsIRelativeFilePref.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsISpamSettings.h" +#include "nsICryptoHash.h" +#include "nsNativeCharsetUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIRssIncomingServer.h" +#include "nsIMsgFolder.h" +#include "nsIMsgProtocolInfo.h" +#include "nsIMsgMessageService.h" +#include "nsIMsgAccountManager.h" +#include "nsIOutputStream.h" +#include "nsMsgFileStream.h" +#include "nsIFileURL.h" +#include "nsNetUtil.h" +#include "nsProtocolProxyService.h" +#include "nsIMsgDatabase.h" +#include "nsIMutableArray.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsArrayUtils.h" +#include "nsIStringBundle.h" +#include "nsIMsgWindow.h" +#include "nsIWindowWatcher.h" +#include "nsIPrompt.h" +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" +#include "nsIMsgSearchTerm.h" +#include "nsTextFormatter.h" +#include "nsIAtomService.h" +#include "nsIStreamListener.h" +#include "nsReadLine.h" +#include "nsICharsetDetectionObserver.h" +#include "nsICharsetDetector.h" +#include "nsILineInputStream.h" +#include "nsIPlatformCharset.h" +#include "nsIParserUtils.h" +#include "nsICharsetConverterManager.h" +#include "nsIDocumentEncoder.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Services.h" +#include "locale.h" +#include "nsStringStream.h" +#include "nsIInputStreamPump.h" +#include "nsIChannel.h" + +/* for logging to Error Console */ +#include "nsIScriptError.h" +#include "nsIConsoleService.h" + +// Log an error string to the error console +// (adapted from nsContentUtils::LogSimpleConsoleError). +// Flag can indicate error, warning or info. +NS_MSG_BASE void MsgLogToConsole4(const nsAString &aErrorText, + const nsAString &aFilename, + uint32_t aLinenumber, + uint32_t aFlag) +{ + nsCOMPtr<nsIScriptError> scriptError = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + if (NS_WARN_IF(!scriptError)) + return; + nsCOMPtr<nsIConsoleService> console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (NS_WARN_IF(!console)) + return; + if (NS_FAILED(scriptError->Init(aErrorText, + aFilename, + EmptyString(), + aLinenumber, + 0, + aFlag, + "mailnews"))) + return; + console->LogMessage(scriptError); + return; +} + +using namespace mozilla; +using namespace mozilla::net; + +static NS_DEFINE_CID(kImapUrlCID, NS_IMAPURL_CID); +static NS_DEFINE_CID(kCMailboxUrl, NS_MAILBOXURL_CID); +static NS_DEFINE_CID(kCNntpUrlCID, NS_NNTPURL_CID); + +#define ILLEGAL_FOLDER_CHARS ";#" +#define ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER "." +#define ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER ".~ " + +nsresult GetMessageServiceContractIDForURI(const char *uri, nsCString &contractID) +{ + nsresult rv = NS_OK; + //Find protocol + nsAutoCString uriStr(uri); + int32_t pos = uriStr.FindChar(':'); + if (pos == -1) + return NS_ERROR_FAILURE; + + nsAutoCString protocol(StringHead(uriStr, pos)); + + if (protocol.Equals("file") && uriStr.Find("application/x-message-display") != -1) + protocol.Assign("mailbox"); + //Build message service contractid + contractID = "@mozilla.org/messenger/messageservice;1?type="; + contractID += protocol.get(); + + return rv; +} + +nsresult GetMessageServiceFromURI(const nsACString& uri, nsIMsgMessageService **aMessageService) +{ + nsresult rv; + + nsAutoCString contractID; + rv = GetMessageServiceContractIDForURI(PromiseFlatCString(uri).get(), contractID); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIMsgMessageService> msgService = do_GetService(contractID.get(), &rv); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*aMessageService = msgService); + return rv; +} + +nsresult GetMsgDBHdrFromURI(const char *uri, nsIMsgDBHdr **msgHdr) +{ + nsCOMPtr <nsIMsgMessageService> msgMessageService; + nsresult rv = GetMessageServiceFromURI(nsDependentCString(uri), getter_AddRefs(msgMessageService)); + NS_ENSURE_SUCCESS(rv,rv); + if (!msgMessageService) return NS_ERROR_FAILURE; + + return msgMessageService->MessageURIToMsgHdr(uri, msgHdr); +} + +nsresult CreateStartupUrl(const char *uri, nsIURI** aUrl) +{ + nsresult rv = NS_ERROR_NULL_POINTER; + if (!uri || !*uri || !aUrl) return rv; + *aUrl = nullptr; + + // XXX fix this, so that base doesn't depend on imap, local or news. + // we can't do NS_NewURI(uri, aUrl), because these are imap-message://, mailbox-message://, news-message:// uris. + // I think we should do something like GetMessageServiceFromURI() to get the service, and then have the service create the + // appropriate nsI*Url, and then QI to nsIURI, and return it. + // see bug #110689 + if (PL_strncasecmp(uri, "imap", 4) == 0) + { + nsCOMPtr<nsIImapUrl> imapUrl = do_CreateInstance(kImapUrlCID, &rv); + + if (NS_SUCCEEDED(rv) && imapUrl) + rv = imapUrl->QueryInterface(NS_GET_IID(nsIURI), + (void**) aUrl); + } + else if (PL_strncasecmp(uri, "mailbox", 7) == 0) + { + nsCOMPtr<nsIMailboxUrl> mailboxUrl = do_CreateInstance(kCMailboxUrl, &rv); + if (NS_SUCCEEDED(rv) && mailboxUrl) + rv = mailboxUrl->QueryInterface(NS_GET_IID(nsIURI), + (void**) aUrl); + } + else if (PL_strncasecmp(uri, "news", 4) == 0) + { + nsCOMPtr<nsINntpUrl> nntpUrl = do_CreateInstance(kCNntpUrlCID, &rv); + if (NS_SUCCEEDED(rv) && nntpUrl) + rv = nntpUrl->QueryInterface(NS_GET_IID(nsIURI), + (void**) aUrl); + } + if (*aUrl) // SetSpec can fail, for mailbox urls, but we still have a url. + (void) (*aUrl)->SetSpec(nsDependentCString(uri)); + return rv; +} + + +// Where should this live? It's a utility used to convert a string priority, +// e.g., "High, Low, Normal" to an enum. +// Perhaps we should have an interface that groups together all these +// utilities... +nsresult NS_MsgGetPriorityFromString( + const char * const priority, + nsMsgPriorityValue & outPriority) +{ + if (!priority) + return NS_ERROR_NULL_POINTER; + + // Note: Checking the values separately and _before_ the names, + // hoping for a much faster match; + // Only _drawback_, as "priority" handling is not truly specified: + // some softwares may have the number meanings reversed (1=Lowest) !? + if (PL_strchr(priority, '1')) + outPriority = nsMsgPriority::highest; + else if (PL_strchr(priority, '2')) + outPriority = nsMsgPriority::high; + else if (PL_strchr(priority, '3')) + outPriority = nsMsgPriority::normal; + else if (PL_strchr(priority, '4')) + outPriority = nsMsgPriority::low; + else if (PL_strchr(priority, '5')) + outPriority = nsMsgPriority::lowest; + else if (PL_strcasestr(priority, "Highest")) + outPriority = nsMsgPriority::highest; + // Important: "High" must be tested after "Highest" ! + else if (PL_strcasestr(priority, "High") || + PL_strcasestr(priority, "Urgent")) + outPriority = nsMsgPriority::high; + else if (PL_strcasestr(priority, "Normal")) + outPriority = nsMsgPriority::normal; + else if (PL_strcasestr(priority, "Lowest")) + outPriority = nsMsgPriority::lowest; + // Important: "Low" must be tested after "Lowest" ! + else if (PL_strcasestr(priority, "Low") || + PL_strcasestr(priority, "Non-urgent")) + outPriority = nsMsgPriority::low; + else + // "Default" case gets default value. + outPriority = nsMsgPriority::Default; + + return NS_OK; +} + +nsresult NS_MsgGetPriorityValueString( + const nsMsgPriorityValue p, + nsACString & outValueString) +{ + switch (p) + { + case nsMsgPriority::highest: + outValueString.AssignLiteral("1"); + break; + case nsMsgPriority::high: + outValueString.AssignLiteral("2"); + break; + case nsMsgPriority::normal: + outValueString.AssignLiteral("3"); + break; + case nsMsgPriority::low: + outValueString.AssignLiteral("4"); + break; + case nsMsgPriority::lowest: + outValueString.AssignLiteral("5"); + break; + case nsMsgPriority::none: + case nsMsgPriority::notSet: + // Note: '0' is a "fake" value; we expect to never be in this case. + outValueString.AssignLiteral("0"); + break; + default: + NS_ASSERTION(false, "invalid priority value"); + } + + return NS_OK; +} + +nsresult NS_MsgGetUntranslatedPriorityName( + const nsMsgPriorityValue p, + nsACString & outName) +{ + switch (p) + { + case nsMsgPriority::highest: + outName.AssignLiteral("Highest"); + break; + case nsMsgPriority::high: + outName.AssignLiteral("High"); + break; + case nsMsgPriority::normal: + outName.AssignLiteral("Normal"); + break; + case nsMsgPriority::low: + outName.AssignLiteral("Low"); + break; + case nsMsgPriority::lowest: + outName.AssignLiteral("Lowest"); + break; + case nsMsgPriority::none: + case nsMsgPriority::notSet: + // Note: 'None' is a "fake" value; we expect to never be in this case. + outName.AssignLiteral("None"); + break; + default: + NS_ASSERTION(false, "invalid priority value"); + } + + return NS_OK; +} + + +/* this used to be XP_StringHash2 from xp_hash.c */ +/* phong's linear congruential hash */ +static uint32_t StringHash(const char *ubuf, int32_t len = -1) +{ + unsigned char * buf = (unsigned char*) ubuf; + uint32_t h=1; + unsigned char *end = buf + (len == -1 ? strlen(ubuf) : len); + while(buf < end) { + h = 0x63c63cd9*h + 0x9c39c33d + (int32_t)*buf; + buf++; + } + return h; +} + +inline uint32_t StringHash(const nsAutoString& str) +{ + const char16_t *strbuf = str.get(); + return StringHash(reinterpret_cast<const char*>(strbuf), + str.Length() * 2); +} + +#ifndef MOZILLA_INTERNAL_API +static int GetFindInSetFilter(const char* aChars) +{ + uint8_t filter = 0; + while (*aChars) + filter |= *aChars++; + return ~filter; +} +#endif + +/* Utility functions used in a few places in mailnews */ +int32_t +MsgFindCharInSet(const nsCString &aString, + const char* aChars, uint32_t aOffset) +{ +#ifdef MOZILLA_INTERNAL_API + return aString.FindCharInSet(aChars, aOffset); +#else + const char *str; + uint32_t len = aString.BeginReading(&str); + int filter = GetFindInSetFilter(aChars); + for (uint32_t index = aOffset; index < len; index++) { + if (!(str[index] & filter) && strchr(aChars, str[index])) + return index; + } + return -1; +#endif +} + +int32_t +MsgFindCharInSet(const nsString &aString, + const char* aChars, uint32_t aOffset) +{ +#ifdef MOZILLA_INTERNAL_API + return aString.FindCharInSet(aChars, aOffset); +#else + const char16_t *str; + uint32_t len = aString.BeginReading(&str); + int filter = GetFindInSetFilter(aChars); + for (uint32_t index = aOffset; index < len; index++) { + if (!(str[index] & filter) && strchr(aChars, str[index])) + return index; + } + return -1; +#endif +} + +static bool ConvertibleToNative(const nsAutoString& str) +{ + nsAutoCString native; + nsAutoString roundTripped; +#ifdef MOZILLA_INTERNAL_API + NS_CopyUnicodeToNative(str, native); + NS_CopyNativeToUnicode(native, roundTripped); +#else + nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), str, native); + nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), native, roundTripped); +#endif + return str.Equals(roundTripped); +} + +#if defined(XP_UNIX) + const static uint32_t MAX_LEN = 55; +#elif defined(XP_WIN32) + const static uint32_t MAX_LEN = 55; +#else + #error need_to_define_your_max_filename_length +#endif + +nsresult NS_MsgHashIfNecessary(nsAutoCString &name) +{ + if (name.IsEmpty()) + return NS_OK; // Nothing to do. + nsAutoCString str(name); + + // Given a filename, make it safe for filesystem + // certain filenames require hashing because they + // are too long or contain illegal characters + int32_t illegalCharacterIndex = MsgFindCharInSet(str, + FILE_PATH_SEPARATOR + FILE_ILLEGAL_CHARACTERS + ILLEGAL_FOLDER_CHARS, 0); + + // Need to check the first ('.') and last ('.', '~' and ' ') char + if (illegalCharacterIndex == -1) + { + int32_t lastIndex = str.Length() - 1; + if (NS_LITERAL_CSTRING(ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER).FindChar(str[0]) != -1) + illegalCharacterIndex = 0; + else if (NS_LITERAL_CSTRING(ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER).FindChar(str[lastIndex]) != -1) + illegalCharacterIndex = lastIndex; + else + illegalCharacterIndex = -1; + } + + char hashedname[MAX_LEN + 1]; + if (illegalCharacterIndex == -1) + { + // no illegal chars, it's just too long + // keep the initial part of the string, but hash to make it fit + if (str.Length() > MAX_LEN) + { + PL_strncpy(hashedname, str.get(), MAX_LEN + 1); + PR_snprintf(hashedname + MAX_LEN - 8, 9, "%08lx", + (unsigned long) StringHash(str.get())); + name = hashedname; + } + } + else + { + // found illegal chars, hash the whole thing + // if we do substitution, then hash, two strings + // could hash to the same value. + // for example, on mac: "foo__bar", "foo:_bar", "foo::bar" + // would map to "foo_bar". this way, all three will map to + // different values + PR_snprintf(hashedname, 9, "%08lx", + (unsigned long) StringHash(str.get())); + name = hashedname; + } + + return NS_OK; +} + +// XXX : The number of UTF-16 2byte code units are half the number of +// bytes in legacy encodings for CJK strings and non-Latin1 in UTF-8. +// The ratio can be 1/3 for CJK strings in UTF-8. However, we can +// get away with using the same MAX_LEN for nsCString and nsString +// because MAX_LEN is defined rather conservatively in the first place. +nsresult NS_MsgHashIfNecessary(nsAutoString &name) +{ + if (name.IsEmpty()) + return NS_OK; // Nothing to do. + int32_t illegalCharacterIndex = MsgFindCharInSet(name, + FILE_PATH_SEPARATOR + FILE_ILLEGAL_CHARACTERS + ILLEGAL_FOLDER_CHARS, 0); + + // Need to check the first ('.') and last ('.', '~' and ' ') char + if (illegalCharacterIndex == -1) + { + int32_t lastIndex = name.Length() - 1; + if (NS_LITERAL_STRING(ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER).FindChar(name[0]) != -1) + illegalCharacterIndex = 0; + else if (NS_LITERAL_STRING(ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER).FindChar(name[lastIndex]) != -1) + illegalCharacterIndex = lastIndex; + else + illegalCharacterIndex = -1; + } + + char hashedname[9]; + int32_t keptLength = -1; + if (illegalCharacterIndex != -1) + keptLength = illegalCharacterIndex; + else if (!ConvertibleToNative(name)) + keptLength = 0; + else if (name.Length() > MAX_LEN) { + keptLength = MAX_LEN-8; + // To avoid keeping only the high surrogate of a surrogate pair + if (NS_IS_HIGH_SURROGATE(name.CharAt(keptLength-1))) + --keptLength; + } + + if (keptLength >= 0) { + PR_snprintf(hashedname, 9, "%08lx", (unsigned long) StringHash(name)); + name.SetLength(keptLength); + name.Append(NS_ConvertASCIItoUTF16(hashedname)); + } + + return NS_OK; +} + +nsresult FormatFileSize(int64_t size, bool useKB, nsAString &formattedSize) +{ + NS_NAMED_LITERAL_STRING(byteAbbr, "byteAbbreviation2"); + NS_NAMED_LITERAL_STRING(kbAbbr, "kiloByteAbbreviation2"); + NS_NAMED_LITERAL_STRING(mbAbbr, "megaByteAbbreviation2"); + NS_NAMED_LITERAL_STRING(gbAbbr, "gigaByteAbbreviation2"); + + const char16_t *sizeAbbrNames[] = { + byteAbbr.get(), kbAbbr.get(), mbAbbr.get(), gbAbbr.get() + }; + + nsresult rv; + + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + double unitSize = size < 0 ? 0.0 : size; + uint32_t unitIndex = 0; + + if (useKB) { + // Start by formatting in kilobytes + unitSize /= 1024; + if (unitSize < 0.1 && unitSize != 0) + unitSize = 0.1; + unitIndex++; + } + + // Convert to next unit if it needs 4 digits (after rounding), but only if + // we know the name of the next unit + while ((unitSize >= 999.5) && (unitIndex < ArrayLength(sizeAbbrNames) - 1)) + { + unitSize /= 1024; + unitIndex++; + } + + // Grab the string for the appropriate unit + nsString sizeAbbr; + rv = bundle->GetStringFromName(sizeAbbrNames[unitIndex], + getter_Copies(sizeAbbr)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get rid of insignificant bits by truncating to 1 or 0 decimal points + // 0.1 -> 0.1; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235 + nsTextFormatter::ssprintf( + formattedSize, sizeAbbr.get(), + (unitIndex != 0) && (unitSize < 99.95 && unitSize != 0) ? 1 : 0, unitSize); + + int32_t separatorPos = formattedSize.FindChar('.'); + if (separatorPos != kNotFound) { + // The ssprintf returned a decimal number using a dot (.) as the decimal + // separator. Now we try to localize the separator. + // Try to get the decimal separator from the system's locale. + char *decimalPoint; +#ifdef HAVE_LOCALECONV + struct lconv *locale = localeconv(); + decimalPoint = locale->decimal_point; +#else + decimalPoint = getenv("LOCALE_DECIMAL_POINT"); +#endif + NS_ConvertUTF8toUTF16 decimalSeparator(decimalPoint); + if (decimalSeparator.IsEmpty()) + decimalSeparator.AssignLiteral("."); + + formattedSize.Replace(separatorPos, 1, decimalSeparator); + } + + return NS_OK; +} + +nsresult NS_MsgCreatePathStringFromFolderURI(const char *aFolderURI, + nsCString& aPathCString, + const nsCString &aScheme, + bool aIsNewsFolder) +{ + // A file name has to be in native charset. Here we convert + // to UTF-16 and check for 'unsafe' characters before converting + // to native charset. + NS_ENSURE_TRUE(MsgIsUTF8(nsDependentCString(aFolderURI)), NS_ERROR_UNEXPECTED); + NS_ConvertUTF8toUTF16 oldPath(aFolderURI); + + nsAutoString pathPiece, path; + + int32_t startSlashPos = oldPath.FindChar('/'); + int32_t endSlashPos = (startSlashPos >= 0) + ? oldPath.FindChar('/', startSlashPos + 1) - 1 : oldPath.Length() - 1; + if (endSlashPos < 0) + endSlashPos = oldPath.Length(); +#if defined(XP_UNIX) || defined(XP_MACOSX) + bool isLocalUri = aScheme.EqualsLiteral("none") || + aScheme.EqualsLiteral("pop3") || + aScheme.EqualsLiteral("rss"); +#endif + // trick to make sure we only add the path to the first n-1 folders + bool haveFirst=false; + while (startSlashPos != -1) { + pathPiece.Assign(Substring(oldPath, startSlashPos + 1, endSlashPos - startSlashPos)); + // skip leading '/' (and other // style things) + if (!pathPiece.IsEmpty()) + { + + // add .sbd onto the previous path + if (haveFirst) + { + path.AppendLiteral(".sbd/"); + } + + if (aIsNewsFolder) + { + nsAutoCString tmp; + CopyUTF16toMUTF7(pathPiece, tmp); + CopyASCIItoUTF16(tmp, pathPiece); + } +#if defined(XP_UNIX) || defined(XP_MACOSX) + // Don't hash path pieces because local mail folder uri's have already + // been hashed. We're only doing this on the mac to limit potential + // regressions. + if (!isLocalUri) +#endif + NS_MsgHashIfNecessary(pathPiece); + path += pathPiece; + haveFirst=true; + } + // look for the next slash + startSlashPos = endSlashPos + 1; + + endSlashPos = (startSlashPos >= 0) + ? oldPath.FindChar('/', startSlashPos + 1) - 1: oldPath.Length() - 1; + if (endSlashPos < 0) + endSlashPos = oldPath.Length(); + + if (startSlashPos >= endSlashPos) + break; + } +#ifdef MOZILLA_INTERNAL_API + return NS_CopyUnicodeToNative(path, aPathCString); +#else + return nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), path, aPathCString); +#endif +} + +bool NS_MsgStripRE(const nsCString& subject, nsCString& modifiedSubject) +{ + bool result = false; + + // Get localizedRe pref. + nsresult rv; + nsString utf16LocalizedRe; + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, + "mailnews.localizedRe", + EmptyString(), + utf16LocalizedRe); + NS_ConvertUTF16toUTF8 localizedRe(utf16LocalizedRe); + + // Hardcoded "Re" so that no one can configure Mozilla standards incompatible. + nsAutoCString checkString("Re,RE,re,rE"); + if (!localizedRe.IsEmpty()) { + checkString.Append(','); + checkString.Append(localizedRe); + } + + // Decode the string. + nsCString decodedString; + nsCOMPtr<nsIMimeConverter> mimeConverter; + // We cannot strip "Re:" for RFC2047-encoded subject without modifying the original. + if (subject.Find("=?") != kNotFound) + { + mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + rv = mimeConverter->DecodeMimeHeaderToUTF8(subject, + nullptr, false, true, decodedString); + } + + const char *s, *s_end; + if (decodedString.IsEmpty()) { + s = subject.BeginReading(); + s_end = s + subject.Length(); + } else { + s = decodedString.BeginReading(); + s_end = s + decodedString.Length(); + } + +AGAIN: + while (s < s_end && IS_SPACE(*s)) + s++; + + const char *tokPtr = checkString.get(); + while (*tokPtr) + { + // Tokenize the comma separated list. + size_t tokenLength = 0; + while (*tokPtr && *tokPtr != ',') { + tokenLength++; + tokPtr++; + } + // Check if the beginning of s is the actual token. + if (tokenLength && !strncmp(s, tokPtr - tokenLength, tokenLength)) + { + if (s[tokenLength] == ':') + { + s = s + tokenLength + 1; /* Skip over "Re:" */ + result = true; /* Yes, we stripped it. */ + goto AGAIN; /* Skip whitespace and try again. */ + } + else if (s[tokenLength] == '[' || s[tokenLength] == '(') + { + const char *s2 = s + tokenLength + 1; /* Skip over "Re[" */ + + // Skip forward over digits after the "[". + while (s2 < (s_end - 2) && isdigit((unsigned char)*s2)) + s2++; + + // Now ensure that the following thing is "]:". + // Only if it is do we alter `s`. + if ((s2[0] == ']' || s2[0] == ')') && s2[1] == ':') + { + s = s2 + 2; /* Skip over "]:" */ + result = true; /* Yes, we stripped it. */ + goto AGAIN; /* Skip whitespace and try again. */ + } + } + } + if (*tokPtr) + tokPtr++; + } + + // If we didn't strip anything, we can return here. + if (!result) + return false; + + if (decodedString.IsEmpty()) { + // We didn't decode anything, so just return a new string. + modifiedSubject.Assign(s); + return true; + } + + // We decoded the string, so we need to encode it again. We always encode in UTF-8. + mimeConverter->EncodeMimePartIIStr_UTF8(nsDependentCString(s), + false, "UTF-8", sizeof("Subject:"), + nsIMimeConverter::MIME_ENCODED_WORD_SIZE, modifiedSubject); + return true; +} + +/* Very similar to strdup except it free's too + */ +char * NS_MsgSACopy (char **destination, const char *source) +{ + if(*destination) + { + PR_Free(*destination); + *destination = 0; + } + if (! source) + *destination = nullptr; + else + { + *destination = (char *) PR_Malloc (PL_strlen(source) + 1); + if (*destination == nullptr) + return(nullptr); + + PL_strcpy (*destination, source); + } + return *destination; +} + +/* Again like strdup but it concatenates and free's and uses Realloc. +*/ +char * NS_MsgSACat (char **destination, const char *source) +{ + if (source && *source) + { + int destLength = *destination ? PL_strlen(*destination) : 0; + char* newDestination = (char*) PR_Realloc(*destination, destLength + PL_strlen(source) + 1); + if (newDestination == nullptr) + return nullptr; + + *destination = newDestination; + PL_strcpy(*destination + destLength, source); + } + return *destination; +} + +nsresult NS_MsgEscapeEncodeURLPath(const nsAString& aStr, nsCString& aResult) +{ + return MsgEscapeString(NS_ConvertUTF16toUTF8(aStr), nsINetUtil::ESCAPE_URL_PATH, aResult); +} + +nsresult NS_MsgDecodeUnescapeURLPath(const nsACString& aPath, + nsAString& aResult) +{ + nsAutoCString unescapedName; + MsgUnescapeString(aPath, nsINetUtil::ESCAPE_URL_FILE_BASENAME | + nsINetUtil::ESCAPE_URL_FORCED, unescapedName); + CopyUTF8toUTF16(unescapedName, aResult); + return NS_OK; +} + +bool WeAreOffline() +{ + bool offline = false; + + nsCOMPtr <nsIIOService> ioService = + mozilla::services::GetIOService(); + if (ioService) + ioService->GetOffline(&offline); + + return offline; +} + +nsresult GetExistingFolder(const nsCString& aFolderURI, nsIMsgFolder **aFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + + *aFolder = nullptr; + + nsresult rv; + nsCOMPtr<nsIFolderLookupService> fls(do_GetService(NSIFLS_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = fls->GetFolderForURL(aFolderURI, aFolder); + NS_ENSURE_SUCCESS(rv, rv); + + return *aFolder ? NS_OK : NS_ERROR_FAILURE; +} + +bool IsAFromSpaceLine(char *start, const char *end) +{ + bool rv = false; + while ((start < end) && (*start == '>')) + start++; + // If the leading '>'s are followed by an 'F' then we have a possible case here. + if ( (*start == 'F') && (end-start > 4) && !strncmp(start, "From ", 5) ) + rv = true; + return rv; +} + +// +// This function finds all lines starting with "From " or "From " preceeding +// with one or more '>' (ie, ">From", ">>From", etc) in the input buffer +// (between 'start' and 'end') and prefix them with a ">" . +// +nsresult EscapeFromSpaceLine(nsIOutputStream *outputStream, char *start, const char *end) +{ + nsresult rv; + char *pChar; + uint32_t written; + + pChar = start; + while (start < end) + { + while ((pChar < end) && (*pChar != '\r') && ((pChar + 1) < end) && + (*(pChar + 1) != '\n')) + pChar++; + if ((pChar + 1) == end) + pChar++; + + if (pChar < end) + { + // Found a line so check if it's a qualified "From " line. + if (IsAFromSpaceLine(start, pChar)) + rv = outputStream->Write(">", 1, &written); + int32_t lineTerminatorCount = (*(pChar + 1) == '\n') ? 2 : 1; + rv = outputStream->Write(start, pChar - start + lineTerminatorCount, &written); + NS_ENSURE_SUCCESS(rv,rv); + pChar += lineTerminatorCount; + start = pChar; + } + else if (start < end) + { + // Check and flush out the remaining data and we're done. + if (IsAFromSpaceLine(start, end)) + rv = outputStream->Write(">", 1, &written); + rv = outputStream->Write(start, end-start, &written); + NS_ENSURE_SUCCESS(rv,rv); + break; + } + } + return NS_OK; +} + +nsresult IsRFC822HeaderFieldName(const char *aHdr, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aHdr); + NS_ENSURE_ARG_POINTER(aResult); + uint32_t length = strlen(aHdr); + for(uint32_t i=0; i<length; i++) + { + char c = aHdr[i]; + if ( c < '!' || c == ':' || c > '~') + { + *aResult = false; + return NS_OK; + } + } + *aResult = true; + return NS_OK; +} + +// Warning, currently this routine only works for the Junk Folder +nsresult +GetOrCreateFolder(const nsACString &aURI, nsIUrlListener *aListener) +{ + nsresult rv; + nsCOMPtr <nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // get the corresponding RDF resource + // RDF will create the folder resource if it doesn't already exist + nsCOMPtr<nsIRDFResource> resource; + rv = rdf->GetResource(aURI, getter_AddRefs(resource)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIMsgFolder> folderResource; + folderResource = do_QueryInterface(resource, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // don't check validity of folder - caller will handle creating it + nsCOMPtr<nsIMsgIncomingServer> server; + // make sure that folder hierarchy is built so that legitimate parent-child relationship is established + rv = folderResource->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + if (!server) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr <nsIMsgFolder> msgFolder; + rv = server->GetMsgFolderFromURI(folderResource, aURI, getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIMsgFolder> parent; + rv = msgFolder->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv) || !parent) + { + nsCOMPtr <nsIFile> folderPath; + // for local folders, path is to the berkeley mailbox. + // for imap folders, path needs to have .msf appended to the name + msgFolder->GetFilePath(getter_AddRefs(folderPath)); + + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = server->GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isAsyncFolder; + rv = protocolInfo->GetFoldersCreatedAsync(&isAsyncFolder); + NS_ENSURE_SUCCESS(rv, rv); + + // if we can't get the path from the folder, then try to create the storage. + // for imap, it doesn't matter if the .msf file exists - it still might not + // exist on the server, so we should try to create it + bool exists = false; + if (!isAsyncFolder && folderPath) + folderPath->Exists(&exists); + if (!exists) + { + // Hack to work around a localization bug with the Junk Folder. + // Please see Bug #270261 for more information... + nsString localizedJunkName; + msgFolder->GetName(localizedJunkName); + + // force the junk folder name to be Junk so it gets created on disk correctly... + msgFolder->SetName(NS_LITERAL_STRING("Junk")); + msgFolder->SetFlag(nsMsgFolderFlags::Junk); + rv = msgFolder->CreateStorageIfMissing(aListener); + NS_ENSURE_SUCCESS(rv,rv); + + // now restore the localized folder name... + msgFolder->SetName(localizedJunkName); + + // XXX TODO + // JUNK MAIL RELATED + // ugh, I hate this hack + // we have to do this (for now) + // because imap and local are different (one creates folder asynch, the other synch) + // one will notify the listener, one will not. + // I blame nsMsgCopy. + // we should look into making it so no matter what the folder type + // we always call the listener + // this code should move into local folder's version of CreateStorageIfMissing() + if (!isAsyncFolder && aListener) { + rv = aListener->OnStartRunningUrl(nullptr); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aListener->OnStopRunningUrl(nullptr, NS_OK); + NS_ENSURE_SUCCESS(rv,rv); + } + } + } + else { + // if the folder exists, we should set the junk flag on it + // which is what the listener will do + if (aListener) { + rv = aListener->OnStartRunningUrl(nullptr); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aListener->OnStopRunningUrl(nullptr, NS_OK); + NS_ENSURE_SUCCESS(rv,rv); + } + } + + return NS_OK; +} + +nsresult IsRSSArticle(nsIURI * aMsgURI, bool *aIsRSSArticle) +{ + nsresult rv; + *aIsRSSArticle = false; + + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aMsgURI, &rv); + if (NS_FAILED(rv)) return rv; + + nsCString resourceURI; + msgUrl->GetUri(getter_Copies(resourceURI)); + + // get the msg service for this URI + nsCOMPtr<nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(resourceURI, getter_AddRefs(msgService)); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if the message is a feed message, regardless of folder. + uint32_t flags; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = msgService->MessageURIToMsgHdr(resourceURI.get(), getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::FeedMsg) + { + *aIsRSSArticle = true; + return rv; + } + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aMsgURI, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // get the folder and the server from the msghdr + nsCOMPtr<nsIRssIncomingServer> rssServer; + nsCOMPtr<nsIMsgFolder> folder; + rv = msgHdr->GetFolder(getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv) && folder) + { + nsCOMPtr<nsIMsgIncomingServer> server; + folder->GetServer(getter_AddRefs(server)); + rssServer = do_QueryInterface(server); + + if (rssServer) + *aIsRSSArticle = true; + } + + return rv; +} + + +// digest needs to be a pointer to a DIGEST_LENGTH (16) byte buffer +nsresult MSGCramMD5(const char *text, int32_t text_len, const char *key, int32_t key_len, unsigned char *digest) +{ + nsresult rv; + + nsAutoCString hash; + nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + + // this code adapted from http://www.cis.ohio-state.edu/cgi-bin/rfc/rfc2104.html + + char innerPad[65]; /* inner padding - key XORd with innerPad */ + char outerPad[65]; /* outer padding - key XORd with outerPad */ + int i; + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) + { + + rv = hasher->Init(nsICryptoHash::MD5); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Update((const uint8_t*) key, key_len); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Finish(false, hash); + NS_ENSURE_SUCCESS(rv, rv); + + key = hash.get(); + key_len = DIGEST_LENGTH; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR outerPad, MD5(K XOR innerPad, text)) + * + * where K is an n byte key + * innerPad is the byte 0x36 repeated 64 times + * outerPad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + memset(innerPad, 0, sizeof innerPad); + memset(outerPad, 0, sizeof outerPad); + memcpy(innerPad, key, key_len); + memcpy(outerPad, key, key_len); + + /* XOR key with innerPad and outerPad values */ + for (i=0; i<64; i++) + { + innerPad[i] ^= 0x36; + outerPad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + nsAutoCString result; + rv = hasher->Init(nsICryptoHash::MD5); /* init context for 1st pass */ + rv = hasher->Update((const uint8_t*)innerPad, 64); /* start with inner pad */ + rv = hasher->Update((const uint8_t*)text, text_len); /* then text of datagram */ + rv = hasher->Finish(false, result); /* finish up 1st pass */ + + /* + * perform outer MD5 + */ + hasher->Init(nsICryptoHash::MD5); /* init context for 2nd pass */ + rv = hasher->Update((const uint8_t*)outerPad, 64); /* start with outer pad */ + rv = hasher->Update((const uint8_t*)result.get(), 16);/* then results of 1st hash */ + rv = hasher->Finish(false, result); /* finish up 2nd pass */ + + if (result.Length() != DIGEST_LENGTH) + return NS_ERROR_UNEXPECTED; + + memcpy(digest, result.get(), DIGEST_LENGTH); + + return rv; + +} + + +// digest needs to be a pointer to a DIGEST_LENGTH (16) byte buffer +nsresult MSGApopMD5(const char *text, int32_t text_len, const char *password, int32_t password_len, unsigned char *digest) +{ + nsresult rv; + nsAutoCString result; + + nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Init(nsICryptoHash::MD5); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Update((const uint8_t*) text, text_len); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Update((const uint8_t*) password, password_len); + NS_ENSURE_SUCCESS(rv, rv); + + rv = hasher->Finish(false, result); + NS_ENSURE_SUCCESS(rv, rv); + + if (result.Length() != DIGEST_LENGTH) + return NS_ERROR_UNEXPECTED; + + memcpy(digest, result.get(), DIGEST_LENGTH); + return rv; +} + +NS_MSG_BASE nsresult NS_GetPersistentFile(const char *relPrefName, + const char *absPrefName, + const char *dirServiceProp, + bool& gotRelPref, + nsIFile **aFile, + nsIPrefBranch *prefBranch) +{ + NS_ENSURE_ARG_POINTER(aFile); + *aFile = nullptr; + NS_ENSURE_ARG(relPrefName); + NS_ENSURE_ARG(absPrefName); + gotRelPref = false; + + nsCOMPtr<nsIPrefBranch> mainBranch; + if (!prefBranch) { + nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (!prefService) return NS_ERROR_FAILURE; + prefService->GetBranch(nullptr, getter_AddRefs(mainBranch)); + if (!mainBranch) return NS_ERROR_FAILURE; + prefBranch = mainBranch; + } + + nsCOMPtr<nsIFile> localFile; + + // Get the relative first + nsCOMPtr<nsIRelativeFilePref> relFilePref; + prefBranch->GetComplexValue(relPrefName, + NS_GET_IID(nsIRelativeFilePref), getter_AddRefs(relFilePref)); + if (relFilePref) { + relFilePref->GetFile(getter_AddRefs(localFile)); + NS_ASSERTION(localFile, "An nsIRelativeFilePref has no file."); + if (localFile) + gotRelPref = true; + } + + // If not, get the old absolute + if (!localFile) { + prefBranch->GetComplexValue(absPrefName, + NS_GET_IID(nsIFile), getter_AddRefs(localFile)); + + // If not, and given a dirServiceProp, use directory service. + if (!localFile && dirServiceProp) { + nsCOMPtr<nsIProperties> dirService(do_GetService("@mozilla.org/file/directory_service;1")); + if (!dirService) return NS_ERROR_FAILURE; + dirService->Get(dirServiceProp, NS_GET_IID(nsIFile), getter_AddRefs(localFile)); + if (!localFile) return NS_ERROR_FAILURE; + } + } + + if (localFile) { + localFile->Normalize(); + *aFile = localFile; + NS_ADDREF(*aFile); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_MSG_BASE nsresult NS_SetPersistentFile(const char *relPrefName, + const char *absPrefName, + nsIFile *aFile, + nsIPrefBranch *prefBranch) +{ + NS_ENSURE_ARG(relPrefName); + NS_ENSURE_ARG(absPrefName); + NS_ENSURE_ARG(aFile); + + nsCOMPtr<nsIPrefBranch> mainBranch; + if (!prefBranch) { + nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (!prefService) return NS_ERROR_FAILURE; + prefService->GetBranch(nullptr, getter_AddRefs(mainBranch)); + if (!mainBranch) return NS_ERROR_FAILURE; + prefBranch = mainBranch; + } + + // Write the absolute for backwards compatibilty's sake. + // Or, if aPath is on a different drive than the profile dir. + nsresult rv = prefBranch->SetComplexValue(absPrefName, NS_GET_IID(nsIFile), aFile); + + // Write the relative path. + nsCOMPtr<nsIRelativeFilePref> relFilePref; + NS_NewRelativeFilePref(aFile, nsDependentCString(NS_APP_USER_PROFILE_50_DIR), getter_AddRefs(relFilePref)); + if (relFilePref) { + nsresult rv2 = prefBranch->SetComplexValue(relPrefName, NS_GET_IID(nsIRelativeFilePref), relFilePref); + if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) + prefBranch->ClearUserPref(relPrefName); + } + + return rv; +} + +NS_MSG_BASE nsresult NS_GetUnicharPreferenceWithDefault(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch + const char *prefName, + const nsAString& defValue, + nsAString& prefValue) +{ + NS_ENSURE_ARG(prefName); + + nsCOMPtr<nsIPrefBranch> pbr; + if(!prefBranch) { + pbr = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefBranch = pbr; + } + + nsCOMPtr<nsISupportsString> str; + nsresult rv = prefBranch->GetComplexValue(prefName, NS_GET_IID(nsISupportsString), getter_AddRefs(str)); + if (NS_SUCCEEDED(rv)) + str->GetData(prefValue); + else + prefValue = defValue; + return NS_OK; +} + +NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreferenceWithDefault(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch + const char *prefName, + const nsAString& defValue, + nsAString& prefValue) +{ + NS_ENSURE_ARG(prefName); + + nsCOMPtr<nsIPrefBranch> pbr; + if(!prefBranch) { + pbr = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefBranch = pbr; + } + + nsCOMPtr<nsIPrefLocalizedString> str; + nsresult rv = prefBranch->GetComplexValue(prefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str)); + if (NS_SUCCEEDED(rv)) + { + nsString tmpValue; + str->ToString(getter_Copies(tmpValue)); + prefValue.Assign(tmpValue); + } + else + prefValue = defValue; + return NS_OK; +} + +NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreference(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch + const char *prefName, + nsAString& prefValue) +{ + NS_ENSURE_ARG_POINTER(prefName); + + nsCOMPtr<nsIPrefBranch> pbr; + if (!prefBranch) { + pbr = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefBranch = pbr; + } + + nsCOMPtr<nsIPrefLocalizedString> str; + nsresult rv = prefBranch->GetComplexValue(prefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString tmpValue; + str->ToString(getter_Copies(tmpValue)); + prefValue.Assign(tmpValue); + return NS_OK; +} + +void PRTime2Seconds(PRTime prTime, uint32_t *seconds) +{ + *seconds = (uint32_t)(prTime / PR_USEC_PER_SEC); +} + +void PRTime2Seconds(PRTime prTime, int32_t *seconds) +{ + *seconds = (int32_t)(prTime / PR_USEC_PER_SEC); +} + +void Seconds2PRTime(uint32_t seconds, PRTime *prTime) +{ + *prTime = (PRTime)seconds * PR_USEC_PER_SEC; +} + +nsresult GetSummaryFileLocation(nsIFile* fileLocation, nsIFile** summaryLocation) +{ + nsresult rv; + nsCOMPtr <nsIFile> newSummaryLocation = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + newSummaryLocation->InitWithFile(fileLocation); + nsString fileName; + + rv = newSummaryLocation->GetLeafName(fileName); + if (NS_FAILED(rv)) + return rv; + + fileName.Append(NS_LITERAL_STRING(SUMMARY_SUFFIX)); + rv = newSummaryLocation->SetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*summaryLocation = newSummaryLocation); + return NS_OK; +} + +void MsgGenerateNowStr(nsACString &nowStr) +{ + char dateBuf[100]; + dateBuf[0] = '\0'; + PRExplodedTime exploded; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y", &exploded); + nowStr.Assign(dateBuf); +} + + +// Gets a special directory and appends the supplied file name onto it. +nsresult GetSpecialDirectoryWithFileName(const char* specialDirName, + const char* fileName, + nsIFile** result) +{ + nsresult rv = NS_GetSpecialDirectory(specialDirName, result); + NS_ENSURE_SUCCESS(rv, rv); + + return (*result)->AppendNative(nsDependentCString(fileName)); +} + +// Cleans up temp files with matching names +nsresult MsgCleanupTempFiles(const char *fileName, const char *extension) +{ + nsCOMPtr<nsIFile> tmpFile; + nsCString rootName(fileName); + rootName.Append("."); + rootName.Append(extension); + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + rootName.get(), + getter_AddRefs(tmpFile)); + + NS_ENSURE_SUCCESS(rv, rv); + int index = 1; + bool exists; + do + { + tmpFile->Exists(&exists); + if (exists) + { + tmpFile->Remove(false); + nsCString leafName(fileName); + leafName.Append("-"); + leafName.AppendInt(index); + leafName.Append("."); + leafName.Append(extension); + // start with "Picture-1.jpg" after "Picture.jpg" exists + tmpFile->SetNativeLeafName(leafName); + } + } + while (exists && ++index < 10000); + return NS_OK; +} + +nsresult MsgGetFileStream(nsIFile *file, nsIOutputStream **fileStream) +{ + nsMsgFileStream *newFileStream = new nsMsgFileStream; + NS_ENSURE_TRUE(newFileStream, NS_ERROR_OUT_OF_MEMORY); + nsresult rv = newFileStream->InitWithFile(file); + if (NS_SUCCEEDED(rv)) + rv = newFileStream->QueryInterface(NS_GET_IID(nsIOutputStream), (void **) fileStream); + return rv; +} + +nsresult MsgReopenFileStream(nsIFile *file, nsIInputStream *fileStream) +{ + nsMsgFileStream *msgFileStream = static_cast<nsMsgFileStream *>(fileStream); + if (msgFileStream) + return msgFileStream->InitWithFile(file); + else + return NS_ERROR_FAILURE; +} + +nsresult MsgNewBufferedFileOutputStream(nsIOutputStream **aResult, + nsIFile* aFile, + int32_t aIOFlags, + int32_t aPerm) +{ + nsCOMPtr<nsIOutputStream> stream; + nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile, aIOFlags, aPerm); + if (NS_SUCCEEDED(rv)) + rv = NS_NewBufferedOutputStream(aResult, stream, FILE_IO_BUFFER_SIZE); + return rv; +} + +nsresult MsgNewSafeBufferedFileOutputStream(nsIOutputStream **aResult, + nsIFile* aFile, + int32_t aIOFlags, + int32_t aPerm) +{ + nsCOMPtr<nsIOutputStream> stream; + nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), aFile, aIOFlags, aPerm); + if (NS_SUCCEEDED(rv)) + rv = NS_NewBufferedOutputStream(aResult, stream, FILE_IO_BUFFER_SIZE); + return rv; +} + +bool MsgFindKeyword(const nsCString &keyword, nsCString &keywords, int32_t *aStartOfKeyword, int32_t *aLength) +{ +#ifdef MOZILLA_INTERNAL_API +// nsTString_CharT::Find(const nsCString& aString, +// bool aIgnoreCase=false, +// int32_t aOffset=0, +// int32_t aCount=-1 ) const; +#define FIND_KEYWORD(keywords,keyword,offset) ((keywords).Find((keyword), false, (offset))) +#else +// nsAString::Find(const self_type& aStr, +// uint32_t aOffset, +// ComparatorFunc c = DefaultComparator) const; +#define FIND_KEYWORD(keywords,keyword,offset) ((keywords).Find((keyword), static_cast<uint32_t>(offset))) +#endif + // 'keyword' is the single keyword we're looking for + // 'keywords' is a space delimited list of keywords to be searched, + // which may be just a single keyword or even be empty + const int32_t kKeywordLen = keyword.Length(); + const char* start = keywords.BeginReading(); + const char* end = keywords.EndReading(); + *aStartOfKeyword = FIND_KEYWORD(keywords, keyword, 0); + while (*aStartOfKeyword >= 0) + { + const char* matchStart = start + *aStartOfKeyword; + const char* matchEnd = matchStart + kKeywordLen; + // For a real match, matchStart must be the start of keywords or preceded + // by a space and matchEnd must be the end of keywords or point to a space. + if ((matchStart == start || *(matchStart - 1) == ' ') && + (matchEnd == end || *matchEnd == ' ')) + { + *aLength = kKeywordLen; + return true; + } + *aStartOfKeyword = FIND_KEYWORD(keywords, keyword, *aStartOfKeyword + kKeywordLen); + } + + *aLength = 0; + return false; +#undef FIND_KEYWORD +} + +bool MsgHostDomainIsTrusted(nsCString &host, nsCString &trustedMailDomains) +{ + const char *end; + uint32_t hostLen, domainLen; + bool domainIsTrusted = false; + + const char *domain = trustedMailDomains.BeginReading(); + const char *domainEnd = trustedMailDomains.EndReading(); + const char *hostStart = host.BeginReading(); + hostLen = host.Length(); + + do { + // skip any whitespace + while (*domain == ' ' || *domain == '\t') + ++domain; + + // find end of this domain in the string + end = strchr(domain, ','); + if (!end) + end = domainEnd; + + // to see if the hostname is in the domain, check if the domain + // matches the end of the hostname. + domainLen = end - domain; + if (domainLen && hostLen >= domainLen) { + const char *hostTail = hostStart + hostLen - domainLen; + if (PL_strncasecmp(domain, hostTail, domainLen) == 0) + { + // now, make sure either that the hostname is a direct match or + // that the hostname begins with a dot. + if (hostLen == domainLen || *hostTail == '.' || *(hostTail - 1) == '.') + { + domainIsTrusted = true; + break; + } + } + } + + domain = end + 1; + } while (*end); + return domainIsTrusted; +} + +nsresult MsgGetLocalFileFromURI(const nsACString &aUTF8Path, nsIFile **aFile) +{ + nsresult rv; + nsCOMPtr<nsIURI> argURI; + rv = NS_NewURI(getter_AddRefs(argURI), aUTF8Path); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFileURL> argFileURL(do_QueryInterface(argURI, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> argFile; + rv = argFileURL->GetFile(getter_AddRefs(argFile)); + NS_ENSURE_SUCCESS(rv, rv); + + argFile.forget(aFile); + return NS_OK; +} + +#ifndef MOZILLA_INTERNAL_API +/* + * Function copied from nsReadableUtils. + * Migrating to frozen linkage is the only change done + */ +NS_MSG_BASE bool MsgIsUTF8(const nsACString& aString) +{ + const char *done_reading = aString.EndReading(); + + int32_t state = 0; + bool overlong = false; + bool surrogate = false; + bool nonchar = false; + uint16_t olupper = 0; // overlong byte upper bound. + uint16_t slower = 0; // surrogate byte lower bound. + + const char *ptr = aString.BeginReading(); + + while (ptr < done_reading) { + uint8_t c; + + if (0 == state) { + + c = *ptr++; + + if ((c & 0x80) == 0x00) + continue; + + if ( c <= 0xC1 ) // [80-BF] where not expected, [C0-C1] for overlong. + return false; + else if ((c & 0xE0) == 0xC0) + state = 1; + else if ((c & 0xF0) == 0xE0) { + state = 2; + if ( c == 0xE0 ) { // to exclude E0[80-9F][80-BF] + overlong = true; + olupper = 0x9F; + } else if ( c == 0xED ) { // ED[A0-BF][80-BF] : surrogate codepoint + surrogate = true; + slower = 0xA0; + } else if ( c == 0xEF ) // EF BF [BE-BF] : non-character + nonchar = true; + } else if ( c <= 0xF4 ) { // XXX replace /w UTF8traits::is4byte when it's updated to exclude [F5-F7].(bug 199090) + state = 3; + nonchar = true; + if ( c == 0xF0 ) { // to exclude F0[80-8F][80-BF]{2} + overlong = true; + olupper = 0x8F; + } + else if ( c == 0xF4 ) { // to exclude F4[90-BF][80-BF] + // actually not surrogates but codepoints beyond 0x10FFFF + surrogate = true; + slower = 0x90; + } + } else + return false; // Not UTF-8 string + } + + while (ptr < done_reading && state) { + c = *ptr++; + --state; + + // non-character : EF BF [BE-BF] or F[0-7] [89AB]F BF [BE-BF] + if ( nonchar && ( !state && c < 0xBE || + state == 1 && c != 0xBF || + state == 2 && 0x0F != (0x0F & c) )) + nonchar = false; + + if ((c & 0xC0) != 0x80 || overlong && c <= olupper || + surrogate && slower <= c || nonchar && !state ) + return false; // Not UTF-8 string + overlong = surrogate = false; + } + } + return !state; // state != 0 at the end indicates an invalid UTF-8 seq. +} + +#endif + +NS_MSG_BASE void MsgStripQuotedPrintable (unsigned char *src) +{ + // decode quoted printable text in place + + if (!*src) + return; + unsigned char *dest = src; + int srcIdx = 0, destIdx = 0; + + while (src[srcIdx] != 0) + { + // Decode sequence of '=XY' into a character with code XY. + if (src[srcIdx] == '=') + { + if (MsgIsHex((const char*)src + srcIdx + 1, 2)) { + // If we got here, we successfully decoded a quoted printable sequence, + // so bump each pointer past it and move on to the next char. + dest[destIdx++] = MsgUnhex((const char*)src + srcIdx + 1, 2); + srcIdx += 3; + } + else + { + // If first char after '=' isn't hex check if it's a normal char + // or a soft line break. If it's a soft line break, eat the + // CR/LF/CRLF. + if (src[srcIdx + 1] == '\r' || src[srcIdx + 1] == '\n') + { + srcIdx++; // soft line break, ignore the '='; + if (src[srcIdx] == '\r' || src[srcIdx] == '\n') + { + srcIdx++; + if (src[srcIdx] == '\n') + srcIdx++; + } + } + else // The first or second char after '=' isn't hex, just copy the '='. + { + dest[destIdx++] = src[srcIdx++]; + } + continue; + } + } + else + dest[destIdx++] = src[srcIdx++]; + } + + dest[destIdx] = src[srcIdx]; // null terminate +} + +NS_MSG_BASE nsresult MsgEscapeString(const nsACString &aStr, + uint32_t aType, nsACString &aResult) +{ + nsresult rv; + nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return nu->EscapeString(aStr, aType, aResult); +} + +NS_MSG_BASE nsresult MsgUnescapeString(const nsACString &aStr, uint32_t aFlags, + nsACString &aResult) +{ + nsresult rv; + nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return nu->UnescapeString(aStr, aFlags, aResult); +} + +NS_MSG_BASE nsresult MsgEscapeURL(const nsACString &aStr, uint32_t aFlags, + nsACString &aResult) +{ + nsresult rv; + nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return nu->EscapeURL(aStr, aFlags, aResult); +} + +#ifndef MOZILLA_INTERNAL_API + +NS_MSG_BASE char *MsgEscapeHTML(const char *string) +{ + char *rv = nullptr; + /* XXX Hardcoded max entity len. The +1 is for the trailing null. */ + uint32_t len = PL_strlen(string); + if (len >= (PR_UINT32_MAX / 6)) + return nullptr; + + rv = (char *)NS_Alloc( (6 * len) + 1 ); + char *ptr = rv; + + if (rv) + { + for(; *string != '\0'; string++) + { + if (*string == '<') + { + *ptr++ = '&'; + *ptr++ = 'l'; + *ptr++ = 't'; + *ptr++ = ';'; + } + else if (*string == '>') + { + *ptr++ = '&'; + *ptr++ = 'g'; + *ptr++ = 't'; + *ptr++ = ';'; + } + else if (*string == '&') + { + *ptr++ = '&'; + *ptr++ = 'a'; + *ptr++ = 'm'; + *ptr++ = 'p'; + *ptr++ = ';'; + } + else if (*string == '"') + { + *ptr++ = '&'; + *ptr++ = 'q'; + *ptr++ = 'u'; + *ptr++ = 'o'; + *ptr++ = 't'; + *ptr++ = ';'; + } + else if (*string == '\'') + { + *ptr++ = '&'; + *ptr++ = '#'; + *ptr++ = '3'; + *ptr++ = '9'; + *ptr++ = ';'; + } + else + { + *ptr++ = *string; + } + } + *ptr = '\0'; + } + return(rv); +} + +NS_MSG_BASE char16_t *MsgEscapeHTML2(const char16_t *aSourceBuffer, + int32_t aSourceBufferLen) +{ + // if the caller didn't calculate the length + if (aSourceBufferLen == -1) { + aSourceBufferLen = NS_strlen(aSourceBuffer); // ...then I will + } + + /* XXX Hardcoded max entity len. */ + if (aSourceBufferLen >= + ((PR_UINT32_MAX - sizeof(char16_t)) / (6 * sizeof(char16_t))) ) + return nullptr; + + char16_t *resultBuffer = (char16_t *)moz_xmalloc(aSourceBufferLen * + 6 * sizeof(char16_t) + sizeof(char16_t('\0'))); + + char16_t *ptr = resultBuffer; + + if (resultBuffer) { + int32_t i; + + for(i = 0; i < aSourceBufferLen; i++) { + if(aSourceBuffer[i] == '<') { + *ptr++ = '&'; + *ptr++ = 'l'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if(aSourceBuffer[i] == '>') { + *ptr++ = '&'; + *ptr++ = 'g'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if(aSourceBuffer[i] == '&') { + *ptr++ = '&'; + *ptr++ = 'a'; + *ptr++ = 'm'; + *ptr++ = 'p'; + *ptr++ = ';'; + } else if (aSourceBuffer[i] == '"') { + *ptr++ = '&'; + *ptr++ = 'q'; + *ptr++ = 'u'; + *ptr++ = 'o'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (aSourceBuffer[i] == '\'') { + *ptr++ = '&'; + *ptr++ = '#'; + *ptr++ = '3'; + *ptr++ = '9'; + *ptr++ = ';'; + } else { + *ptr++ = aSourceBuffer[i]; + } + } + *ptr = 0; + } + + return resultBuffer; +} + +NS_MSG_BASE void MsgCompressWhitespace(nsCString& aString) +{ + // This code is frozen linkage specific + aString.Trim(" \f\n\r\t\v"); + + char *start, *end; + aString.BeginWriting(&start, &end); + + for (char *cur = start; cur < end; ++cur) { + if (!IS_SPACE(*cur)) + continue; + + *cur = ' '; + + if (!IS_SPACE(*(cur + 1))) + continue; + + // Loop through the white space + char *wend = cur + 2; + while (IS_SPACE(*wend)) + ++wend; + + uint32_t wlen = wend - cur - 1; + + // fix "end" + end -= wlen; + + // move everything forwards a bit + for (char *m = cur + 1; m < end; ++m) { + *m = *(m + wlen); + } + } + + // Set the new length. + aString.SetLength(end - start); +} + + +NS_MSG_BASE void MsgReplaceChar(nsString& str, const char *set, const char16_t replacement) +{ + char16_t *c_str = str.BeginWriting(); + while (*set) { + int32_t pos = 0; + while ((pos = str.FindChar(*set, pos)) != -1) { + c_str[pos++] = replacement; + } + set++; + } +} + +NS_MSG_BASE void MsgReplaceChar(nsCString& str, const char needle, const char replacement) +{ + char *c_str = str.BeginWriting(); + while ((c_str = strchr(c_str, needle))) { + *c_str = replacement; + c_str++; + } +} + +NS_MSG_BASE already_AddRefed<nsIAtom> MsgNewAtom(const char* aString) +{ + nsCOMPtr<nsIAtomService> atomService(do_GetService("@mozilla.org/atom-service;1")); + nsCOMPtr<nsIAtom> atom; + + if (atomService) + atomService->GetAtomUTF8(aString, getter_AddRefs(atom)); + return atom.forget(); +} + +NS_MSG_BASE void MsgReplaceSubstring(nsAString &str, const nsAString &what, const nsAString &replacement) +{ + const char16_t* replacement_str; + uint32_t replacementLength = replacement.BeginReading(&replacement_str); + uint32_t whatLength = what.Length(); + int32_t i = 0; + + while ((i = str.Find(what, i)) != kNotFound) + { + str.Replace(i, whatLength, replacement_str, replacementLength); + i += replacementLength; + } +} + +NS_MSG_BASE void MsgReplaceSubstring(nsACString &str, const char *what, const char *replacement) +{ + uint32_t replacementLength = strlen(replacement); + uint32_t whatLength = strlen(what); + int32_t i = 0; + + /* We have to create nsDependentCString from 'what' because there's no + * str.Find(char *what, int offset) but there is only + * str.Find(char *what, int length) */ + nsDependentCString what_dependent(what); + while ((i = str.Find(what_dependent, i)) != kNotFound) + { + str.Replace(i, whatLength, replacement, replacementLength); + i += replacementLength; + } +} + +/* This class is based on nsInterfaceRequestorAgg from nsInterfaceRequestorAgg.h */ +class MsgInterfaceRequestorAgg : public nsIInterfaceRequestor +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + + MsgInterfaceRequestorAgg(nsIInterfaceRequestor *aFirst, + nsIInterfaceRequestor *aSecond) + : mFirst(aFirst) + , mSecond(aSecond) {} + + nsCOMPtr<nsIInterfaceRequestor> mFirst, mSecond; +}; + +// XXX This needs to support threadsafe refcounting until we fix bug 243591. +NS_IMPL_ISUPPORTS(MsgInterfaceRequestorAgg, nsIInterfaceRequestor) + +NS_IMETHODIMP +MsgInterfaceRequestorAgg::GetInterface(const nsIID &aIID, void **aResult) +{ + nsresult rv = NS_ERROR_NO_INTERFACE; + if (mFirst) + rv = mFirst->GetInterface(aIID, aResult); + if (mSecond && NS_FAILED(rv)) + rv = mSecond->GetInterface(aIID, aResult); + return rv; +} + +/* This function is based on NS_NewInterfaceRequestorAggregation from + * nsInterfaceRequestorAgg.h */ +NS_MSG_BASE nsresult +MsgNewInterfaceRequestorAggregation(nsIInterfaceRequestor *aFirst, + nsIInterfaceRequestor *aSecond, + nsIInterfaceRequestor **aResult) +{ + *aResult = new MsgInterfaceRequestorAgg(aFirst, aSecond); + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult NS_FASTCALL MsgQueryElementAt::operator()( const nsIID& aIID, void** aResult ) const + { + nsresult status = mArray + ? mArray->QueryElementAt(mIndex, aIID, aResult) + : NS_ERROR_NULL_POINTER; + + if ( mErrorPtr ) + *mErrorPtr = status; + + return status; + } + +#endif + +NS_MSG_BASE nsresult MsgGetHeadersFromKeys(nsIMsgDatabase *aDB, const nsTArray<nsMsgKey> &aMsgKeys, + nsIMutableArray *aHeaders) +{ + NS_ENSURE_ARG_POINTER(aDB); + + uint32_t count = aMsgKeys.Length(); + nsresult rv = NS_OK; + + for (uint32_t kindex = 0; kindex < count; kindex++) + { + nsMsgKey key = aMsgKeys.ElementAt(kindex); + + bool hasKey; + rv = aDB->ContainsKey(key, &hasKey); + NS_ENSURE_SUCCESS(rv, rv); + + // This function silently skips when the key is not found. This is an expected case. + if (hasKey) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = aDB->GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + aHeaders->AppendElement(msgHdr, false); + } + } + + return rv; +} + +NS_MSG_BASE nsresult MsgGetHdrsFromKeys(nsIMsgDatabase *aDB, nsMsgKey *aMsgKeys, + uint32_t aNumKeys, nsIMutableArray **aHeaders) +{ + NS_ENSURE_ARG_POINTER(aDB); + NS_ENSURE_ARG_POINTER(aMsgKeys); + NS_ENSURE_ARG_POINTER(aHeaders); + + nsresult rv; + nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t kindex = 0; kindex < aNumKeys; kindex++) { + nsMsgKey key = aMsgKeys[kindex]; + bool hasKey; + rv = aDB->ContainsKey(key, &hasKey); + // This function silently skips when the key is not found. This is an expected case. + if (NS_SUCCEEDED(rv) && hasKey) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = aDB->GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv)) + messages->AppendElement(msgHdr, false); + } + } + + messages.forget(aHeaders); + return NS_OK; +} + +bool MsgAdvanceToNextLine(const char *buffer, uint32_t &bufferOffset, uint32_t maxBufferOffset) +{ + bool result = false; + for (; bufferOffset < maxBufferOffset; bufferOffset++) + { + if (buffer[bufferOffset] == '\r' || buffer[bufferOffset] == '\n') + { + bufferOffset++; + if (buffer[bufferOffset- 1] == '\r' && buffer[bufferOffset] == '\n') + bufferOffset++; + result = true; + break; + } + } + return result; +} + +NS_MSG_BASE nsresult +MsgExamineForProxy(nsIChannel *channel, nsIProxyInfo **proxyInfo) +{ + nsresult rv; + +#ifdef DEBUG + nsCOMPtr<nsIURI> uri; + rv = channel->GetURI(getter_AddRefs(uri)); + NS_ASSERTION(NS_SUCCEEDED(rv) && uri, + "The URI needs to be set before calling the proxy service"); +#endif + + nsCOMPtr<nsIProtocolProxyService> proxyService = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX: This "interface" ID is exposed, but it's not hooked up to the QI. + // Until it is, use a static_cast for now. +#if 0 + RefPtr<nsProtocolProxyService> rawProxyService = do_QueryObject(proxyService, &rv); + NS_ENSURE_SUCCESS(rv, rv); +#else + nsProtocolProxyService *rawProxyService = static_cast<nsProtocolProxyService*>(proxyService.get()); +#endif + + return rawProxyService->DeprecatedBlockingResolve(channel, 0, proxyInfo); +} + +NS_MSG_BASE nsresult MsgPromptLoginFailed(nsIMsgWindow *aMsgWindow, + const nsCString &aHostname, + int32_t *aResult) +{ + + nsCOMPtr<nsIPrompt> dialog; + if (aMsgWindow) + aMsgWindow->GetPromptDialog(getter_AddRefs(dialog)); + + nsresult rv; + + // If we haven't got one, use a default dialog. + if (!dialog) + { + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = wwatch->GetNewPrompter(0, getter_AddRefs(dialog)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString message; + NS_ConvertUTF8toUTF16 hostNameUTF16(aHostname); + const char16_t *formatStrings[] = { hostNameUTF16.get() }; + + rv = bundle->FormatStringFromName(u"mailServerLoginFailed", + formatStrings, 1, + getter_Copies(message)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString title; + rv = bundle->GetStringFromName( + u"mailServerLoginFailedTitle", getter_Copies(title)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString button0; + rv = bundle->GetStringFromName( + u"mailServerLoginFailedRetryButton", + getter_Copies(button0)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString button2; + rv = bundle->GetStringFromName( + u"mailServerLoginFailedEnterNewPasswordButton", + getter_Copies(button2)); + NS_ENSURE_SUCCESS(rv, rv); + + bool dummyValue = false; + return dialog->ConfirmEx( + title.get(), message.get(), + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1) + + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2), + button0.get(), nullptr, button2.get(), nullptr, &dummyValue, aResult); +} + +NS_MSG_BASE PRTime MsgConvertAgeInDaysToCutoffDate(int32_t ageInDays) +{ + PRTime now = PR_Now(); + + return now - PR_USEC_PER_DAY * ageInDays; +} + +NS_MSG_BASE nsresult MsgTermListToString(nsISupportsArray *aTermList, nsCString &aOutString) +{ + uint32_t count; + aTermList->Count(&count); + nsresult rv = NS_OK; + + for (uint32_t searchIndex = 0; searchIndex < count; + searchIndex++) + { + nsAutoCString stream; + + nsCOMPtr<nsIMsgSearchTerm> term; + aTermList->QueryElementAt(searchIndex, NS_GET_IID(nsIMsgSearchTerm), + (void **)getter_AddRefs(term)); + if (!term) + continue; + + if (aOutString.Length() > 1) + aOutString += ' '; + + bool booleanAnd; + bool matchAll; + term->GetBooleanAnd(&booleanAnd); + term->GetMatchAll(&matchAll); + if (matchAll) + { + aOutString += "ALL"; + continue; + } + else if (booleanAnd) + aOutString += "AND ("; + else + aOutString += "OR ("; + + rv = term->GetTermAsString(stream); + NS_ENSURE_SUCCESS(rv, rv); + + aOutString += stream; + aOutString += ')'; + } + return rv; +} + +NS_MSG_BASE uint64_t ParseUint64Str(const char *str) +{ +#ifdef XP_WIN + { + char *endPtr; + return _strtoui64(str, &endPtr, 10); + } +#else + return strtoull(str, nullptr, 10); +#endif +} + +NS_MSG_BASE uint64_t MsgUnhex(const char *aHexString, size_t aNumChars) +{ + // Large numbers will not fit into uint64_t. + NS_ASSERTION(aNumChars <= 16, "Hex literal too long to convert!"); + + uint64_t result = 0; + for (size_t i = 0; i < aNumChars; i++) + { + unsigned char c = aHexString[i]; + uint8_t digit; + if ((c >= '0') && (c <= '9')) + digit = (c - '0'); + else if ((c >= 'a') && (c <= 'f')) + digit = ((c - 'a') + 10); + else if ((c >= 'A') && (c <= 'F')) + digit = ((c - 'A') + 10); + else + break; + + result = (result << 4) | digit; + } + + return result; +} + +NS_MSG_BASE bool MsgIsHex(const char *aHexString, size_t aNumChars) +{ + for (size_t i = 0; i < aNumChars; i++) + { + if (!isxdigit(aHexString[i])) + return false; + } + return true; +} + + +NS_MSG_BASE nsresult +MsgStreamMsgHeaders(nsIInputStream *aInputStream, nsIStreamListener *aConsumer) +{ + nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>); + NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv; + + nsAutoCString msgHeaders; + nsAutoCString curLine; + + bool more = true; + + // We want to NS_ReadLine until we get to a blank line (the end of the headers) + while (more) + { + rv = NS_ReadLine(aInputStream, lineBuffer.get(), curLine, &more); + NS_ENSURE_SUCCESS(rv, rv); + if (curLine.IsEmpty()) + break; + msgHeaders.Append(curLine); + msgHeaders.Append(NS_LITERAL_CSTRING("\r\n")); + } + lineBuffer = nullptr; + nsCOMPtr<nsIStringInputStream> hdrsStream = + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + hdrsStream->SetData(msgHeaders.get(), msgHeaders.Length()); + nsCOMPtr<nsIInputStreamPump> pump; + rv = NS_NewInputStreamPump(getter_AddRefs(pump), hdrsStream); + NS_ENSURE_SUCCESS(rv, rv); + + return pump->AsyncRead(aConsumer, nullptr); +} + +class CharsetDetectionObserver : public nsICharsetDetectionObserver +{ +public: + NS_DECL_ISUPPORTS + CharsetDetectionObserver() {}; + NS_IMETHOD Notify(const char* aCharset, nsDetectionConfident aConf) override + { + mCharset = aCharset; + return NS_OK; + }; + const char *GetDetectedCharset() { return mCharset.get(); } + +private: + virtual ~CharsetDetectionObserver() {} + nsCString mCharset; +}; + +NS_IMPL_ISUPPORTS(CharsetDetectionObserver, nsICharsetDetectionObserver) + +NS_MSG_BASE nsresult +MsgDetectCharsetFromFile(nsIFile *aFile, nsACString &aCharset) +{ + // First try the universal charset detector + nsCOMPtr<nsICharsetDetector> detector + = do_CreateInstance(NS_CHARSET_DETECTOR_CONTRACTID_BASE + "universal_charset_detector"); + if (!detector) { + // No universal charset detector, try the default charset detector + nsString detectorName; + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "intl.charset.detector", + EmptyString(), detectorName); + if (!detectorName.IsEmpty()) { + nsAutoCString detectorContractID; + detectorContractID.AssignLiteral(NS_CHARSET_DETECTOR_CONTRACTID_BASE); + AppendUTF16toUTF8(detectorName, detectorContractID); + detector = do_CreateInstance(detectorContractID.get()); + } + } + + nsresult rv; + nsCOMPtr<nsIInputStream> inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + + if (detector) { + nsAutoCString buffer; + + RefPtr<CharsetDetectionObserver> observer = new CharsetDetectionObserver(); + + rv = detector->Init(observer); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILineInputStream> lineInputStream; + lineInputStream = do_QueryInterface(inputStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool isMore = true; + bool dontFeed = false; + while (isMore && + NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore)) && + buffer.Length() > 0) { + detector->DoIt(buffer.get(), buffer.Length(), &dontFeed); + NS_ENSURE_SUCCESS(rv, rv); + if (dontFeed) + break; + } + rv = detector->Done(); + NS_ENSURE_SUCCESS(rv, rv); + + aCharset = observer->GetDetectedCharset(); + } else { + // no charset detector available, check the BOM + char sniffBuf[3]; + uint32_t numRead; + rv = inputStream->Read(sniffBuf, sizeof(sniffBuf), &numRead); + + if (numRead >= 2 && + sniffBuf[0] == (char)0xfe && + sniffBuf[1] == (char)0xff) { + aCharset = "UTF-16BE"; + } else if (numRead >= 2 && + sniffBuf[0] == (char)0xff && + sniffBuf[1] == (char)0xfe) { + aCharset = "UTF-16LE"; + } else if (numRead >= 3 && + sniffBuf[0] == (char)0xef && + sniffBuf[1] == (char)0xbb && + sniffBuf[2] == (char)0xbf) { + aCharset = "UTF-8"; + } + } + + if (aCharset.IsEmpty()) { // No sniffed or charset. + nsAutoCString buffer; + nsCOMPtr<nsILineInputStream> lineInputStream = + do_QueryInterface(inputStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool isMore = true; + bool isUTF8Compat = true; + while (isMore && isUTF8Compat && + NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) { + isUTF8Compat = MsgIsUTF8(buffer); + } + + // If the file content is UTF-8 compatible, use that. Otherwise let's not + // make a bad guess. + if (isUTF8Compat) + aCharset.AssignLiteral("UTF-8"); + } + + if (aCharset.IsEmpty()) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* + * Converts a buffer to plain text. Some conversions may + * or may not work with certain end charsets which is why we + * need that as an argument to the function. If charset is + * unknown or deemed of no importance NULL could be passed. + */ +NS_MSG_BASE nsresult +ConvertBufToPlainText(nsString &aConBuf, bool formatFlowed, bool delsp, + bool formatOutput, bool disallowBreaks) +{ + if (aConBuf.IsEmpty()) + return NS_OK; + + int32_t wrapWidth = 72; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + if (pPrefBranch) + { + pPrefBranch->GetIntPref("mailnews.wraplength", &wrapWidth); + // Let sanity reign! + if (wrapWidth == 0 || wrapWidth > 990) + wrapWidth = 990; + else if (wrapWidth < 10) + wrapWidth = 10; + } + + uint32_t converterFlags = nsIDocumentEncoder::OutputPersistNBSP; + if (formatFlowed) + converterFlags |= nsIDocumentEncoder::OutputFormatFlowed; + if (delsp) + converterFlags |= nsIDocumentEncoder::OutputFormatDelSp; + if (formatOutput) + converterFlags |= nsIDocumentEncoder::OutputFormatted; + if (disallowBreaks) + converterFlags |= nsIDocumentEncoder::OutputDisallowLineBreaking; + + nsCOMPtr<nsIParserUtils> utils = + do_GetService(NS_PARSERUTILS_CONTRACTID); + return utils->ConvertToPlainText(aConBuf, + converterFlags, + wrapWidth, + aConBuf); +} + +NS_MSG_BASE nsMsgKey msgKeyFromInt(uint32_t aValue) +{ + return aValue; +} + +NS_MSG_BASE nsMsgKey msgKeyFromInt(uint64_t aValue) +{ + NS_ASSERTION(aValue <= PR_UINT32_MAX, "Msg key value too big!"); + return aValue; +} + +// Helper function to extract a query qualifier. +nsAutoCString MsgExtractQueryPart(nsAutoCString spec, const char* queryToExtract) +{ + nsAutoCString queryPart; + int32_t queryIndex = spec.Find(queryToExtract); + if (queryIndex == kNotFound) + return queryPart; + + int32_t queryEnd = Substring(spec, queryIndex + 1).FindChar('&'); + if (queryEnd == kNotFound) + queryEnd = Substring(spec, queryIndex + 1).FindChar('?'); + if (queryEnd == kNotFound) { + // Nothing follows, so return from where the query qualifier started. + queryPart.Assign(Substring(spec, queryIndex)); + } else { + // Return the substring that represents the query qualifier. + queryPart.Assign(Substring(spec, queryIndex, queryEnd + 1)); + } + return queryPart; +} diff --git a/mailnews/base/util/nsMsgUtils.h b/mailnews/base/util/nsMsgUtils.h new file mode 100644 index 000000000..97f5010ae --- /dev/null +++ b/mailnews/base/util/nsMsgUtils.h @@ -0,0 +1,589 @@ +/* -*- 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 _NSMSGUTILS_H +#define _NSMSGUTILS_H + +#include "nsIURL.h" +#include "nsStringGlue.h" +#include "msgCore.h" +#include "nsCOMPtr.h" +#include "MailNewsTypes2.h" +#include "nsTArray.h" +#include "nsInterfaceRequestorAgg.h" +#include "nsILoadGroup.h" +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" +#include "nsIAtom.h" +#include "nsINetUtil.h" +#include "nsIRequest.h" +#include "nsILoadInfo.h" +#include "nsServiceManagerUtils.h" +#include "nsUnicharUtils.h" +#include "nsIFile.h" + +class nsIChannel; +class nsIFile; +class nsIPrefBranch; +class nsIMsgFolder; +class nsIMsgMessageService; +class nsIUrlListener; +class nsIOutputStream; +class nsIInputStream; +class nsIMsgDatabase; +class nsIMutableArray; +class nsIProxyInfo; +class nsIMsgWindow; +class nsISupportsArray; +class nsIStreamListener; + +#define FILE_IO_BUFFER_SIZE (16*1024) +#define MSGS_URL "chrome://messenger/locale/messenger.properties" + +//These are utility functions that can used throughout the mailnews code + +NS_MSG_BASE nsresult GetMessageServiceContractIDForURI(const char *uri, nsCString &contractID); + +NS_MSG_BASE nsresult GetMessageServiceFromURI(const nsACString& uri, nsIMsgMessageService **aMessageService); + +NS_MSG_BASE nsresult GetMsgDBHdrFromURI(const char *uri, nsIMsgDBHdr **msgHdr); + +NS_MSG_BASE nsresult CreateStartupUrl(const char *uri, nsIURI** aUrl); + +NS_MSG_BASE nsresult NS_MsgGetPriorityFromString( + const char * const priority, + nsMsgPriorityValue & outPriority); + +NS_MSG_BASE nsresult NS_MsgGetPriorityValueString( + const nsMsgPriorityValue p, + nsACString & outValueString); + +NS_MSG_BASE nsresult NS_MsgGetUntranslatedPriorityName( + const nsMsgPriorityValue p, + nsACString & outName); + +NS_MSG_BASE nsresult NS_MsgHashIfNecessary(nsAutoString &name); +NS_MSG_BASE nsresult NS_MsgHashIfNecessary(nsAutoCString &name); + +NS_MSG_BASE nsresult FormatFileSize(int64_t size, bool useKB, nsAString &formattedSize); + + +/** + * given a folder uri, return the path to folder in the user profile directory. + * + * @param aFolderURI uri of folder we want the path to, without the scheme + * @param[out] aPathString result path string + * @param aScheme scheme of the uri + * @param[optional] aIsNewsFolder is this a news folder? + */ +NS_MSG_BASE nsresult +NS_MsgCreatePathStringFromFolderURI(const char *aFolderURI, + nsCString& aPathString, + const nsCString &aScheme, + bool aIsNewsFolder=false); + +/** + * Given a string and a length, removes any "Re:" strings from the front. + * It also deals with that dumbass "Re[2]:" thing that some losing mailers do. + * + * If mailnews.localizedRe is set, it will also remove localized "Re:" strings. + * + * @return true if it made a change (in which case the caller should look to + * modifiedSubject for the result) and false otherwise (in which + * case the caller should look at subject for the result) + */ +NS_MSG_BASE bool NS_MsgStripRE(const nsCString& subject, nsCString& modifiedSubject); + +NS_MSG_BASE char * NS_MsgSACopy(char **destination, const char *source); + +NS_MSG_BASE char * NS_MsgSACat(char **destination, const char *source); + +NS_MSG_BASE nsresult NS_MsgEscapeEncodeURLPath(const nsAString& aStr, + nsCString& aResult); + +NS_MSG_BASE nsresult NS_MsgDecodeUnescapeURLPath(const nsACString& aPath, + nsAString& aResult); + +NS_MSG_BASE bool WeAreOffline(); + +// Check if a folder with aFolderUri exists +NS_MSG_BASE nsresult GetExistingFolder(const nsCString& aFolderURI, nsIMsgFolder **aFolder); + +// Escape lines starting with "From ", ">From ", etc. in a buffer. +NS_MSG_BASE nsresult EscapeFromSpaceLine(nsIOutputStream *ouputStream, char *start, const char *end); +NS_MSG_BASE bool IsAFromSpaceLine(char *start, const char *end); + +NS_MSG_BASE nsresult NS_GetPersistentFile(const char *relPrefName, + const char *absPrefName, + const char *dirServiceProp, // Can be NULL + bool& gotRelPref, + nsIFile **aFile, + nsIPrefBranch *prefBranch = nullptr); + +NS_MSG_BASE nsresult NS_SetPersistentFile(const char *relPrefName, + const char *absPrefName, + nsIFile *aFile, + nsIPrefBranch *prefBranch = nullptr); + +NS_MSG_BASE nsresult IsRFC822HeaderFieldName(const char *aHdr, bool *aResult); + +NS_MSG_BASE nsresult NS_GetUnicharPreferenceWithDefault(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch + const char *prefName, + const nsAString& defValue, + nsAString& prefValue); + +NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreferenceWithDefault(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch + const char *prefName, + const nsAString& defValue, + nsAString& prefValue); + +NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreference(nsIPrefBranch *prefBranch, //can be null, if so uses the root branch + const char *prefName, + nsAString& prefValue); + + /** + * this needs a listener, because we might have to create the folder + * on the server, and that is asynchronous + */ +NS_MSG_BASE nsresult GetOrCreateFolder(const nsACString & aURI, nsIUrlListener *aListener); + +// Returns true if the nsIURI is a message under an RSS account +NS_MSG_BASE nsresult IsRSSArticle(nsIURI * aMsgURI, bool *aIsRSSArticle); + +// digest needs to be a pointer to a 16 byte buffer +#define DIGEST_LENGTH 16 + +NS_MSG_BASE nsresult MSGCramMD5(const char *text, int32_t text_len, const char *key, int32_t key_len, unsigned char *digest); +NS_MSG_BASE nsresult MSGApopMD5(const char *text, int32_t text_len, const char *password, int32_t password_len, unsigned char *digest); + +// helper functions to convert a 64bits PRTime into a 32bits value (compatible time_t) and vice versa. +NS_MSG_BASE void PRTime2Seconds(PRTime prTime, uint32_t *seconds); +NS_MSG_BASE void PRTime2Seconds(PRTime prTime, int32_t *seconds); +NS_MSG_BASE void Seconds2PRTime(uint32_t seconds, PRTime *prTime); +// helper function to generate current date+time as a string +NS_MSG_BASE void MsgGenerateNowStr(nsACString &nowStr); + +// Appends the correct summary file extension onto the supplied fileLocation +// and returns it in summaryLocation. +NS_MSG_BASE nsresult GetSummaryFileLocation(nsIFile* fileLocation, + nsIFile** summaryLocation); + +// Gets a special directory and appends the supplied file name onto it. +NS_MSG_BASE nsresult GetSpecialDirectoryWithFileName(const char* specialDirName, + const char* fileName, + nsIFile** result); + +// cleanup temp files with the given filename and extension, including +// the consecutive -NNNN ones that we can find. If there are holes, e.g., +// <filename>-1-10,12.<extension> exist, but <filename>-11.<extension> does not +// we'll clean up 1-10. If the leaks are common, I think the gaps will tend to +// be filled. +NS_MSG_BASE nsresult MsgCleanupTempFiles(const char *fileName, const char *extension); + +NS_MSG_BASE nsresult MsgGetFileStream(nsIFile *file, nsIOutputStream **fileStream); + +NS_MSG_BASE nsresult MsgReopenFileStream(nsIFile *file, nsIInputStream *fileStream); + +// Automatically creates an output stream with a suitable buffer +NS_MSG_BASE nsresult MsgNewBufferedFileOutputStream(nsIOutputStream **aResult, nsIFile *aFile, int32_t aIOFlags = -1, int32_t aPerm = -1); + +// Automatically creates an output stream with a suitable buffer, but write to a temporary file first, then rename to aFile +NS_MSG_BASE nsresult MsgNewSafeBufferedFileOutputStream(nsIOutputStream **aResult, nsIFile *aFile, int32_t aIOFlags = -1, int32_t aPerm = -1); + +// fills in the position of the passed in keyword in the passed in keyword list +// and returns false if the keyword isn't present +NS_MSG_BASE bool MsgFindKeyword(const nsCString &keyword, nsCString &keywords, int32_t *aStartOfKeyword, int32_t *aLength); + +NS_MSG_BASE bool MsgHostDomainIsTrusted(nsCString &host, nsCString &trustedMailDomains); + +// gets an nsIFile from a UTF-8 file:// path +NS_MSG_BASE nsresult MsgGetLocalFileFromURI(const nsACString &aUTF8Path, nsIFile **aFile); + +NS_MSG_BASE void MsgStripQuotedPrintable (unsigned char *src); + +/* + * Utility function copied from nsReadableUtils + */ +NS_MSG_BASE bool MsgIsUTF8(const nsACString& aString); + +/* + * Utility functions that call functions from nsINetUtil + */ + +NS_MSG_BASE nsresult MsgEscapeString(const nsACString &aStr, + uint32_t aType, nsACString &aResult); + +NS_MSG_BASE nsresult MsgUnescapeString(const nsACString &aStr, + uint32_t aFlags, nsACString &aResult); + +NS_MSG_BASE nsresult MsgEscapeURL(const nsACString &aStr, uint32_t aFlags, + nsACString &aResult); + +// Converts an nsTArray of nsMsgKeys plus a database, to an array of nsIMsgDBHdrs. +NS_MSG_BASE nsresult MsgGetHeadersFromKeys(nsIMsgDatabase *aDB, + const nsTArray<nsMsgKey> &aKeys, + nsIMutableArray *aHeaders); +// Converts an array of nsMsgKeys plus a database, to an array of nsIMsgDBHdrs. +NS_MSG_BASE nsresult MsgGetHdrsFromKeys(nsIMsgDatabase *aDB, + nsMsgKey *aKeys, + uint32_t aNumKeys, + nsIMutableArray **aHeaders); + +NS_MSG_BASE nsresult MsgExamineForProxy(nsIChannel *channel, + nsIProxyInfo **proxyInfo); + +NS_MSG_BASE int32_t MsgFindCharInSet(const nsCString &aString, + const char* aChars, uint32_t aOffset = 0); +NS_MSG_BASE int32_t MsgFindCharInSet(const nsString &aString, + const char* aChars, uint32_t aOffset = 0); + + +// advances bufferOffset to the beginning of the next line, if we don't +// get to maxBufferOffset first. Returns false if we didn't get to the +// next line. +NS_MSG_BASE bool MsgAdvanceToNextLine(const char *buffer, uint32_t &bufferOffset, + uint32_t maxBufferOffset); + +/** + * Alerts the user that the login to the server failed. Asks whether the + * connection should: retry, cancel, or request a new password. + * + * @param aMsgWindow The message window associated with this action (cannot + * be null). + * @param aHostname The hostname of the server for which the login failed. + * @param aResult The button pressed. 0 for retry, 1 for cancel, + * 2 for enter a new password. + * @return NS_OK for success, NS_ERROR_* if there was a failure in + * creating the dialog. + */ +NS_MSG_BASE nsresult MsgPromptLoginFailed(nsIMsgWindow *aMsgWindow, + const nsCString &aHostname, + int32_t *aResult); + +/** + * Calculate a PRTime value used to determine if a date is XX + * days ago. This is used by various retention setting algorithms. + */ +NS_MSG_BASE PRTime MsgConvertAgeInDaysToCutoffDate(int32_t ageInDays); + +/** + * Converts the passed in term list to its string representation. + * + * @param aTermList Array of nsIMsgSearchTerms + * @param[out] aOutString result representation of search terms. + * + */ +NS_MSG_BASE nsresult MsgTermListToString(nsISupportsArray *aTermList, nsCString &aOutString); + +NS_MSG_BASE nsresult +MsgStreamMsgHeaders(nsIInputStream *aInputStream, nsIStreamListener *aConsumer); + +/** + * convert string to uint64_t + * + * @param str conveted string + * @returns uint64_t vaule for success, 0 for parse failure + */ +NS_MSG_BASE uint64_t ParseUint64Str(const char *str); + +/** + * Detect charset of file + * + * @param aFile The target of nsIFile + * @param[out] aCharset The charset string + */ +NS_MSG_BASE nsresult MsgDetectCharsetFromFile(nsIFile *aFile, nsACString &aCharset); + +/* + * Converts a buffer to plain text. Some conversions may + * or may not work with certain end charsets which is why we + * need that as an argument to the function. If charset is + * unknown or deemed of no importance NULL could be passed. + * @param[in/out] aConBuf Variable with the text to convert + * @param formatFlowed Use format flowed? + * @param delsp Use delsp=yes when flowed + * @param formatOutput Reformat the output? + & @param disallowBreaks Disallow breaks when formatting + */ +NS_MSG_BASE nsresult +ConvertBufToPlainText(nsString &aConBuf, bool formatFlowed, bool delsp, + bool formatOutput, bool disallowBreaks); + +/** + * The following definitons exist for compatibility between the internal and + * external APIs. Where possible they just forward to the existing API. + */ + +#ifdef MOZILLA_INTERNAL_API +#include "nsEscape.h" + +/** + * The internal API expects nsCaseInsensitiveC?StringComparator() and true. + * Redefine CaseInsensitiveCompare so that Find works. + */ +#define CaseInsensitiveCompare true +/** + * The following methods are not exposed to the external API, but when we're + * using the internal API we can simply redirect the calls appropriately. + */ +#define MsgLowerCaseEqualsLiteral(str, l) \ + (str).LowerCaseEqualsLiteral(l) +#define MsgRFindChar(str, ch, len) \ + (str).RFindChar(ch, len) +#define MsgCompressWhitespace(str) \ + (str).CompressWhitespace() +#define MsgEscapeHTML(str) \ + nsEscapeHTML(str) +#define MsgEscapeHTML2(buffer, len) \ + nsEscapeHTML2(buffer, len) +#define MsgReplaceSubstring(str, what, replacement) \ + (str).ReplaceSubstring(what, replacement) +#define MsgIsUTF8(str) \ + IsUTF8(str) +#define MsgNewInterfaceRequestorAggregation(aFirst, aSecond, aResult) \ + NS_NewInterfaceRequestorAggregation(aFirst, aSecond, aResult) +#define MsgNewNotificationCallbacksAggregation(aCallbacks, aLoadGroup, aResult) \ + NS_NewNotificationCallbacksAggregation(aCallbacks, aLoadGroup, aResult) +#define MsgGetAtom(aString) \ + NS_Atomize(aString) +#define MsgNewAtom(aString) \ + NS_Atomize(aString) +#define MsgReplaceChar(aString, aNeedle, aReplacement) \ + (aString).ReplaceChar(aNeedle, aReplacement) +#define MsgFind(str, what, ignore_case, offset) \ + (str).Find(what, ignore_case, offset) +#define MsgCountChar(aString, aChar) \ + (aString).CountChar(aChar) + +#else + +/** + * The external API expects CaseInsensitiveCompare. Redefine + * nsCaseInsensitiveC?StringComparator() so that Equals works. + */ +#define nsCaseInsensitiveCStringComparator() \ + CaseInsensitiveCompare +#define nsCaseInsensitiveStringComparator() \ + CaseInsensitiveCompare +/// The external API does not provide kNotFound. +#define kNotFound -1 +/** + * The external API does not provide the following methods. While we can + * reasonably easily define them in terms of existing methods, we only want + * to do this when using the external API. + */ +#define AppendASCII \ + AppendLiteral +#define AppendUTF16toUTF8(source, dest) \ + (dest).Append(NS_ConvertUTF16toUTF8(source)) +#define AppendUTF8toUTF16(source, dest) \ + (dest).Append(NS_ConvertUTF8toUTF16(source)) +#define AppendASCIItoUTF16(source, dest) \ + (dest).Append(NS_ConvertASCIItoUTF16(source)) +#define Compare(str1, str2, comp) \ + (str1).Compare(str2, comp) +#define CaseInsensitiveFindInReadable(what, str) \ + ((str).Find(what, CaseInsensitiveCompare) != kNotFound) +#define LossyAppendUTF16toASCII(source, dest) \ + (dest).Append(NS_LossyConvertUTF16toASCII(source)) +#define Last() \ + EndReading()[-1] +#define SetCharAt(ch, index) \ + Replace(index, 1, ch) +#define NS_NewISupportsArray(result) \ + CallCreateInstance(NS_SUPPORTSARRAY_CONTRACTID, static_cast<nsISupportsArray**>(result)) +/** + * The internal and external methods expect the parameters in a different order. + * The internal API also always expects a flag rather than a comparator. + */ +inline int32_t MsgFind(nsAString &str, const char *what, bool ignore_case, uint32_t offset) +{ + return str.Find(what, offset, ignore_case); +} + +inline int32_t MsgFind(nsACString &str, const char *what, bool ignore_case, int32_t offset) +{ + /* See Find_ComputeSearchRange from nsStringObsolete.cpp */ + if (offset < 0) { + offset = 0; + } + if (ignore_case) + return str.Find(nsDependentCString(what), offset, CaseInsensitiveCompare); + return str.Find(nsDependentCString(what), offset); +} + +inline int32_t MsgFind(nsACString &str, const nsACString &what, bool ignore_case, int32_t offset) +{ + /* See Find_ComputeSearchRange from nsStringObsolete.cpp */ + if (offset < 0) { + offset = 0; + } + if (ignore_case) + return str.Find(what, offset, CaseInsensitiveCompare); + return str.Find(what, offset); +} + +/** + * The following methods are not exposed to the external API so we define + * equivalent versions here. + */ +/// Equivalent of LowerCaseEqualsLiteral(literal) +#define MsgLowerCaseEqualsLiteral(str, literal) \ + (str).Equals(literal, CaseInsensitiveCompare) +/// Equivalent of RFindChar(ch, len) +#define MsgRFindChar(str, ch, len) \ + StringHead(str, len).RFindChar(ch) +/// Equivalent of aString.CompressWhitespace() +NS_MSG_BASE void MsgCompressWhitespace(nsCString& aString); +/// Equivalent of nsEscapeHTML(aString) +NS_MSG_BASE char *MsgEscapeHTML(const char *aString); +/// Equivalent of nsEscapeHTML2(aBuffer, aLen) +NS_MSG_BASE char16_t *MsgEscapeHTML2(const char16_t *aBuffer, int32_t aLen); +// Existing replacement for IsUTF8 +NS_MSG_BASE bool MsgIsUTF8(const nsACString& aString); +/// Equivalent of NS_Atomize(aUTF8String) +NS_MSG_BASE already_AddRefed<nsIAtom> MsgNewAtom(const char* aString); +/// Equivalent of NS_Atomize(aUTF8String) +inline already_AddRefed<nsIAtom> MsgGetAtom(const char* aUTF8String) +{ + return MsgNewAtom(aUTF8String); +} +/// Equivalent of ns(C)String::ReplaceSubstring(what, replacement) +NS_MSG_BASE void MsgReplaceSubstring(nsAString &str, const nsAString &what, const nsAString &replacement); +NS_MSG_BASE void MsgReplaceSubstring(nsACString &str, const char *what, const char *replacement); +/// Equivalent of ns(C)String::ReplaceChar(what, replacement) +NS_MSG_BASE void MsgReplaceChar(nsString& str, const char *set, const char16_t replacement); +NS_MSG_BASE void MsgReplaceChar(nsCString& str, const char needle, const char replacement); +// Equivalent of NS_NewInterfaceRequestorAggregation(aFirst, aSecond, aResult) +NS_MSG_BASE nsresult MsgNewInterfaceRequestorAggregation(nsIInterfaceRequestor *aFirst, + nsIInterfaceRequestor *aSecond, + nsIInterfaceRequestor **aResult); + +/** + * This function is based on NS_NewNotificationCallbacksAggregation from + * nsNetUtil.h + * + * This function returns a nsIInterfaceRequestor instance that returns the + * same result as NS_QueryNotificationCallbacks when queried. + */ +inline nsresult +MsgNewNotificationCallbacksAggregation(nsIInterfaceRequestor *callbacks, + nsILoadGroup *loadGroup, + nsIInterfaceRequestor **result) +{ + nsCOMPtr<nsIInterfaceRequestor> cbs; + if (loadGroup) + loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); + return MsgNewInterfaceRequestorAggregation(callbacks, cbs, result); +} + +/** + * Count occurences of specified character in string. + * + */ +inline +uint32_t MsgCountChar(nsACString &aString, char16_t aChar) { + const char *begin, *end; + uint32_t num_chars = 0; + aString.BeginReading(&begin, &end); + for (const char *current = begin; current < end; ++current) { + if (*current == aChar) + ++num_chars; + } + return num_chars; +} + +inline +uint32_t MsgCountChar(nsAString &aString, char16_t aChar) { + const char16_t *begin, *end; + uint32_t num_chars = 0; + aString.BeginReading(&begin, &end); + for (const char16_t *current = begin; current < end; ++current) { + if (*current == aChar) + ++num_chars; + } + return num_chars; +} + +#endif + +/** + * Converts a hex string into an integer. + * Processes up to aNumChars characters or the first non-hex char. + * It is not an error if less than aNumChars valid hex digits are found. + */ +NS_MSG_BASE uint64_t MsgUnhex(const char *aHexString, size_t aNumChars); + +/** + * Checks if a string is a valid hex literal containing at least aNumChars digits. + */ +NS_MSG_BASE bool MsgIsHex(const char *aHexString, size_t aNumChars); + +/** + * Convert an uint32_t to a nsMsgKey. + * Currently they are mostly the same but we need to preserve the notion that + * nsMsgKey is an opaque value that can't be treated as a generic integer + * (except when storing it into the database). It enables type safety checks and + * may prevent coding errors. + */ +NS_MSG_BASE nsMsgKey msgKeyFromInt(uint32_t aValue); + +NS_MSG_BASE nsMsgKey msgKeyFromInt(uint64_t aValue); + +/** + * Helper function to extract query part from URL spec. + */ +nsAutoCString MsgExtractQueryPart(nsAutoCString spec, const char* queryToExtract); + +/** + * Helper macro for defining getter/setters. Ported from nsISupportsObsolete.h + */ +#define NS_IMPL_GETSET(clazz, attr, type, member) \ + NS_IMETHODIMP clazz::Get##attr(type *result) \ + { \ + NS_ENSURE_ARG_POINTER(result); \ + *result = member; \ + return NS_OK; \ + } \ + NS_IMETHODIMP clazz::Set##attr(type aValue) \ + { \ + member = aValue; \ + return NS_OK; \ + } + +#endif + + /** + * Macro and helper function for reporting an error, warning or + * informational message to the Error Console + * + * This will require the inclusion of the following files in the source file + * #include "nsIScriptError.h" + * #include "nsIConsoleService.h" + * + */ + +NS_MSG_BASE +void MsgLogToConsole4(const nsAString &aErrorText, const nsAString &aFilename, + uint32_t aLine, uint32_t flags); + +// Macro with filename and line number +#define MSG_LOG_TO_CONSOLE(_text, _flag) MsgLogToConsole4(NS_LITERAL_STRING(_text), NS_LITERAL_STRING(__FILE__), __LINE__, _flag) +#define MSG_LOG_ERR_TO_CONSOLE(_text) MSG_LOG_TO_CONSOLE(_text, nsIScriptError::errorFlag) +#define MSG_LOG_WARN_TO_CONSOLE(_text) MSG_LOG_TO_CONSOLE(_text, nsIScriptError::warningFlag) +#define MSG_LOG_INFO_TO_CONSOLE(_text) MSG_LOG_TO_CONSOLE(_text, nsIScriptError::infoFlag) + +// Helper macros to cope with shoddy I/O error reporting (or lack thereof) +#define MSG_NS_ERROR(_txt) do { NS_ERROR(_txt); MSG_LOG_ERR_TO_CONSOLE(_txt); } while(0) +#define MSG_NS_WARNING(_txt) do { NS_WARNING(_txt); MSG_LOG_WARN_TO_CONSOLE(_txt); } while (0) +#define MSG_NS_WARN_IF_FALSE(_val, _txt) do { if (!(_val)) { NS_WARNING(_txt); MSG_LOG_WARN_TO_CONSOLE(_txt); } } while (0) +#define MSG_NS_INFO(_txt) do { MSG_LOCAL_INFO_TO_CONSOLE(_txt); \ + fprintf(stderr,"(info) %s (%s:%d)\n", _txt, __FILE__, __LINE__); } while(0) diff --git a/mailnews/base/util/nsStopwatch.cpp b/mailnews/base/util/nsStopwatch.cpp new file mode 100644 index 000000000..137c456e7 --- /dev/null +++ b/mailnews/base/util/nsStopwatch.cpp @@ -0,0 +1,183 @@ +/* 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 "nsStopwatch.h" + +#include <stdio.h> +#include <time.h> +#if defined(XP_UNIX) +#include <unistd.h> +#include <sys/times.h> +#include <sys/time.h> +#include <errno.h> +#elif defined(XP_WIN) +#include "windows.h" +#endif // elif defined(XP_WIN) + +#include "nsMemory.h" +/* + * This basis for the logic in this file comes from (will used to come from): + * (mozilla/)modules/libutil/public/stopwatch.cpp. + * + * It was no longer used in the mozilla tree, and is being migrated to + * comm-central where we actually have a need for it. ("Being" in the sense + * that it will not be removed immediately from mozilla-central.) + * + * Simplification and general clean-up has been performed and the fix for + * bug 96669 has been integrated. + */ + +NS_IMPL_ISUPPORTS(nsStopwatch, nsIStopwatch) + +#if defined(XP_UNIX) +/** the number of ticks per second */ +static double gTicks = 0; +#define MICRO_SECONDS_TO_SECONDS_MULT static_cast<double>(1.0e-6) +#elif defined(WIN32) +#ifdef DEBUG +#ifdef MOZILLA_INTERNAL_API +#include "nsPrintfCString.h" +#endif +#endif +// 1 tick per 100ns = 10 per us = 10 * 1,000 per ms = 10 * 1,000 * 1,000 per sec. +#define WIN32_TICK_RESOLUTION static_cast<double>(1.0e-7) +// subtract off to get to the unix epoch +#define UNIX_EPOCH_IN_FILE_TIME 116444736000000000L +#endif // elif defined(WIN32) + +nsStopwatch::nsStopwatch() + : fTotalRealTimeSecs(0.0) + , fTotalCpuTimeSecs(0.0) + , fRunning(false) +{ +#if defined(XP_UNIX) + // idempotent in the event of a race under all coherency models + if (!gTicks) + { + // we need to clear errno because sysconf's spec says it leaves it the same + // on success and only sets it on failure. + errno = 0; + gTicks = (clock_t)sysconf(_SC_CLK_TCK); + // in event of failure, pick an arbitrary value so we don't divide by zero. + if (errno) + gTicks = 1000000L; + } +#endif +} + +nsStopwatch::~nsStopwatch() +{ +} + +NS_IMETHODIMP nsStopwatch::Start() +{ + fTotalRealTimeSecs = 0.0; + fTotalCpuTimeSecs = 0.0; + return Resume(); +} + +NS_IMETHODIMP nsStopwatch::Stop() +{ + fStopRealTimeSecs = GetRealTime(); + fStopCpuTimeSecs = GetCPUTime(); + if (fRunning) + { + fTotalCpuTimeSecs += fStopCpuTimeSecs - fStartCpuTimeSecs; + fTotalRealTimeSecs += fStopRealTimeSecs - fStartRealTimeSecs; + } + fRunning = false; + return NS_OK; +} + +NS_IMETHODIMP nsStopwatch::Resume() +{ + if (!fRunning) + { + fStartRealTimeSecs = GetRealTime(); + fStartCpuTimeSecs = GetCPUTime(); + } + fRunning = true; + return NS_OK; +} + +NS_IMETHODIMP nsStopwatch::GetCpuTimeSeconds(double *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = fTotalCpuTimeSecs; + return NS_OK; +} + +NS_IMETHODIMP nsStopwatch::GetRealTimeSeconds(double *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = fTotalRealTimeSecs; + return NS_OK; +} + +double nsStopwatch::GetRealTime() +{ +#if defined(XP_UNIX) + struct timeval t; + gettimeofday(&t, NULL); + return t.tv_sec + t.tv_usec * MICRO_SECONDS_TO_SECONDS_MULT; +#elif defined(WIN32) + union {FILETIME ftFileTime; + __int64 ftInt64; + } ftRealTime; // time the process has spent in kernel mode + SYSTEMTIME st; + GetSystemTime(&st); + SystemTimeToFileTime(&st, &ftRealTime.ftFileTime); + return (ftRealTime.ftInt64 - UNIX_EPOCH_IN_FILE_TIME) * WIN32_TICK_RESOLUTION; +#else +#error "nsStopwatch not supported on this platform." +#endif +} + +double nsStopwatch::GetCPUTime() +{ +#if defined(XP_UNIX) + struct tms cpt; + times(&cpt); + return (double)(cpt.tms_utime+cpt.tms_stime) / gTicks; +#elif defined(WIN32) + FILETIME ftCreate, // when the process was created + ftExit; // when the process exited + + union {FILETIME ftFileTime; + __int64 ftInt64; + } ftKernel; // time the process has spent in kernel mode + + union {FILETIME ftFileTime; + __int64 ftInt64; + } ftUser; // time the process has spent in user mode + + HANDLE hProcess = GetCurrentProcess(); +#ifdef DEBUG + BOOL ret = +#endif + GetProcessTimes(hProcess, &ftCreate, &ftExit, + &ftKernel.ftFileTime, &ftUser.ftFileTime); +#ifdef DEBUG +#ifdef MOZILLA_INTERNAL_API + if (!ret) + NS_ERROR(nsPrintfCString("GetProcessTimes() failed, error=0x%lx.", GetLastError()).get()); +#else + if (!ret) { + // nsPrintfCString() is unavailable to report GetLastError(). + NS_ERROR("GetProcessTimes() failed."); + } +#endif +#endif + + /* + * Process times are returned in a 64-bit structure, as the number of + * 100 nanosecond ticks since 1 January 1601. User mode and kernel mode + * times for this process are in separate 64-bit structures. + * Add them and convert the result to seconds. + */ + return (ftKernel.ftInt64 + ftUser.ftInt64) * WIN32_TICK_RESOLUTION; +#else +#error "nsStopwatch not supported on this platform." +#endif +} diff --git a/mailnews/base/util/nsStopwatch.h b/mailnews/base/util/nsStopwatch.h new file mode 100644 index 000000000..62b1c52b8 --- /dev/null +++ b/mailnews/base/util/nsStopwatch.h @@ -0,0 +1,50 @@ +/* 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 _nsStopwatch_h_ +#define _nsStopwatch_h_ + +#include "nsIStopwatch.h" + +#include "msgCore.h" + +#define NS_STOPWATCH_CID \ +{0x6ef7eafd, 0x72d0, 0x4c56, {0x94, 0x09, 0x67, 0xe1, 0x6d, 0x0f, 0x25, 0x5b}} + +#define NS_STOPWATCH_CONTRACTID "@mozilla.org/stopwatch;1" + +#undef IMETHOD_VISIBILITY +#define IMETHOD_VISIBILITY NS_VISIBILITY_DEFAULT + +class NS_MSG_BASE nsStopwatch : public nsIStopwatch +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTOPWATCH + + nsStopwatch(); +private: + virtual ~nsStopwatch(); + + /// Wall-clock start time in seconds since unix epoch. + double fStartRealTimeSecs; + /// Wall-clock stop time in seconds since unix epoch. + double fStopRealTimeSecs; + /// CPU-clock start time in seconds (of CPU time used since app start) + double fStartCpuTimeSecs; + /// CPU-clock stop time in seconds (of CPU time used since app start) + double fStopCpuTimeSecs; + /// Total wall-clock time elapsed in seconds. + double fTotalRealTimeSecs; + /// Total CPU time elapsed in seconds. + double fTotalCpuTimeSecs; + + /// Is the timer running? + bool fRunning; + + static double GetRealTime(); + static double GetCPUTime(); +}; + +#endif // _nsStopwatch_h_ diff --git a/mailnews/base/util/templateUtils.js b/mailnews/base/util/templateUtils.js new file mode 100644 index 000000000..d90f16abd --- /dev/null +++ b/mailnews/base/util/templateUtils.js @@ -0,0 +1,90 @@ +/* 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 = ["PluralStringFormatter", "makeFriendlyDateAgo"]; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; + +Cu.import("resource://gre/modules/PluralForm.jsm"); +Cu.import("resource:///modules/StringBundle.js"); + +function PluralStringFormatter(aBundleURI) { + this._bundle = new StringBundle(aBundleURI); +} + +PluralStringFormatter.prototype = { + get: function(aStringName, aReplacements, aPluralCount) { + let str = this._bundle.get(aStringName); + if (aPluralCount !== undefined) + str = PluralForm.get(aPluralCount, str); + if (aReplacements !== undefined) { + for (let i = 0; i < aReplacements.length; i++) + str = str.replace("#" + (i+1), aReplacements[i]); + } + return str; + }, +}; + + +var gTemplateUtilsStrings = new PluralStringFormatter( + "chrome://messenger/locale/templateUtils.properties" +); + +/** + * Helper function to generate a localized "friendly" representation of + * time relative to the present. If the time input is "today", it returns + * a string corresponding to just the time. If it's yesterday, it returns + * "yesterday" (localized). If it's in the last week, it returns the day + * of the week. If it's before that, it returns the date. + * + * @param time + * the time (better be in the past!) + * @return The string with a "human-friendly" representation of that time + * relative to now. + */ +function makeFriendlyDateAgo(time) +{ + let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"] + .getService(Ci.nsIScriptableDateFormat); + + // Figure out when today begins + let now = new Date(); + let today = new Date(now.getFullYear(), now.getMonth(), + now.getDate()); + + // Get the end time to display + let end = time; + + // Figure out if the end time is from today, yesterday, + // this week, etc. + let dateTime; + let kDayInMsecs = 24 * 60 * 60 * 1000; + let k6DaysInMsecs = 6 * kDayInMsecs; + if (end >= today) { + // activity finished after today started, show the time + dateTime = dts.FormatTime("", dts.timeFormatNoSeconds, + end.getHours(), end.getMinutes(),0); + } else if (today - end < kDayInMsecs) { + // activity finished after yesterday started, show yesterday + dateTime = gTemplateUtilsStrings.get("yesterday"); + } else if (today - end < k6DaysInMsecs) { + // activity finished after last week started, show day of week + dateTime = end.toLocaleFormat("%A"); + } else if (now.getFullYear() == end.getFullYear()) { + // activity must have been from some time ago.. show month/day + let month = end.toLocaleFormat("%B"); + // Remove leading 0 by converting the date string to a number + let date = Number(end.toLocaleFormat("%d")); + dateTime = gTemplateUtilsStrings.get("monthDate", [month, date]); + } else { + // not this year, so show full date format + dateTime = dts.FormatDate("", dts.dateFormatShort, + end.getFullYear(), end.getMonth() + 1, + end.getDate()); + } + return dateTime; +} diff --git a/mailnews/base/util/traceHelper.js b/mailnews/base/util/traceHelper.js new file mode 100644 index 000000000..a69c7e83d --- /dev/null +++ b/mailnews/base/util/traceHelper.js @@ -0,0 +1,113 @@ +/* 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/. */ + +this.EXPORTED_SYMBOLS = ['DebugTraceHelper']; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var SPACES = " "; +var BRIGHT_COLORS = { + red: "\x1b[1;31m", + green: "\x1b[1;32m", + yellow: "\x1b[1;33m", + blue: "\x1b[1;34m", + magenta: "\x1b[1;35m", + cyan: "\x1b[1;36m", + white: "\x1b[1;37m", +}; +var DARK_COLORS = { + red: "\x1b[0;31m", + green: "\x1b[0;32m", + yellow: "\x1b[0;33m", + blue: "\x1b[0;34m", + magenta: "\x1b[0;35m", + cyan: "\x1b[0;36m", + white: "\x1b[0;37m", +}; +var STOP_COLORS = "\x1b[0m"; + + +/** + * Example usages: + * + * Components.utils.import("resource:///modules/traceHelper.js"); + * var debugContext = {color: "cyan"}; + * DebugTraceHelper.tracify(FolderDisplayWidget.prototype, + * "FolderDisplayWidget", /.+/, debugContext); + * DebugTraceHelper.tracify(MessageDisplayWidget.prototype, + * "MessageDisplayWidget", /.+/, debugContext); + * DebugTraceHelper.tracify(StandaloneFolderDisplayWidget.prototype, + * "StandaloneFolderDisplayWidget", /.+/, debugContext); + * DebugTraceHelper.tracify(StandaloneMessageDisplayWidget.prototype, + * "StandaloneMessageDisplayWidget", /.+/, debugContext); + * DebugTraceHelper.tracify(DBViewWrapper.prototype, + * "DBViewWrapper", /.+/, {color: "green"}); + * DebugTraceHelper.tracify(JSTreeSelection.prototype, + * "JSTreeSelection", /.+/, {color: "yellow"}); + */ +var DebugTraceHelper = { + tracify: function(aObj, aDesc, aPat, aContext, aSettings) { + aContext.depth = 0; + let color = aSettings.color || "cyan"; + aSettings.introCode = BRIGHT_COLORS[color]; + aSettings.outroCode = DARK_COLORS[color]; + for (let key in aObj) { + if (aPat.test(key)) { + // ignore properties! + if (aObj.__lookupGetter__(key) || aObj.__lookupSetter__(key)) + continue; + // ignore non-functions! + if (typeof(aObj[key]) != "function") + continue; + let name = key; + let prev = aObj[name]; + aObj[name] = function() { + let argstr = ""; + for (let i = 0; i < arguments.length; i++) { + let arg = arguments[i]; + if (arg == null) + argstr += " null"; + else if (typeof(arg) == "function") + argstr += " function "+ arg.name; + else + argstr += " " + arguments[i].toString(); + } + + let indent = SPACES.substr(0, aContext.depth++ * 2); + dump(indent + "--> " + aSettings.introCode + aDesc + "::" + name + + ":" + argstr + + STOP_COLORS + "\n"); + let ret; + try { + ret = prev.apply(this, arguments); + } + catch (ex) { + if (ex.stack) { + dump(BRIGHT_COLORS.red + "Exception: " + ex + "\n " + + ex.stack.replace("\n", "\n ") + STOP_COLORS + "\n"); + } + else { + dump(BRIGHT_COLORS.red + "Exception: " + ex.fileName + ":" + + ex.lineNumber + ": " + ex + STOP_COLORS + "\n"); + } + aContext.depth--; + dump(indent + "<-- " + aSettings.outroCode + aDesc + "::" + name + + STOP_COLORS + "\n"); + throw ex; + } + aContext.depth--; + dump(indent + "<-- " + aSettings.outroCode + aDesc + "::" + name + + ": " + (ret != null ? ret.toString() : "null") + + STOP_COLORS + "\n"); + return ret; + }; + } + } + } +}; |