summaryrefslogtreecommitdiffstats
path: root/mailnews/base/util
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/util')
-rw-r--r--mailnews/base/util/ABQueryUtils.jsm130
-rw-r--r--mailnews/base/util/IOUtils.js136
-rw-r--r--mailnews/base/util/JXON.js180
-rw-r--r--mailnews/base/util/OAuth2.jsm234
-rw-r--r--mailnews/base/util/OAuth2Providers.jsm77
-rw-r--r--mailnews/base/util/ServiceList.h40
-rw-r--r--mailnews/base/util/Services.cpp106
-rw-r--r--mailnews/base/util/Services.h26
-rw-r--r--mailnews/base/util/StringBundle.js189
-rw-r--r--mailnews/base/util/errUtils.js309
-rw-r--r--mailnews/base/util/folderUtils.jsm234
-rw-r--r--mailnews/base/util/hostnameUtils.jsm341
-rw-r--r--mailnews/base/util/iteratorUtils.jsm166
-rw-r--r--mailnews/base/util/jsTreeSelection.js654
-rw-r--r--mailnews/base/util/mailServices.js73
-rw-r--r--mailnews/base/util/mailnewsMigrator.js203
-rw-r--r--mailnews/base/util/moz.build82
-rw-r--r--mailnews/base/util/msgDBCacheManager.js175
-rw-r--r--mailnews/base/util/nsImapMoveCoalescer.cpp233
-rw-r--r--mailnews/base/util/nsImapMoveCoalescer.h73
-rw-r--r--mailnews/base/util/nsMsgCompressIStream.cpp227
-rw-r--r--mailnews/base/util/nsMsgCompressIStream.h35
-rw-r--r--mailnews/base/util/nsMsgCompressOStream.cpp145
-rw-r--r--mailnews/base/util/nsMsgCompressOStream.h28
-rw-r--r--mailnews/base/util/nsMsgDBFolder.cpp6063
-rw-r--r--mailnews/base/util/nsMsgDBFolder.h291
-rw-r--r--mailnews/base/util/nsMsgDBFolderAtomList.h26
-rw-r--r--mailnews/base/util/nsMsgFileStream.cpp196
-rw-r--r--mailnews/base/util/nsMsgFileStream.h33
-rw-r--r--mailnews/base/util/nsMsgI18N.cpp479
-rw-r--r--mailnews/base/util/nsMsgI18N.h198
-rw-r--r--mailnews/base/util/nsMsgIdentity.cpp669
-rw-r--r--mailnews/base/util/nsMsgIdentity.h97
-rw-r--r--mailnews/base/util/nsMsgIncomingServer.cpp2332
-rw-r--r--mailnews/base/util/nsMsgIncomingServer.h101
-rw-r--r--mailnews/base/util/nsMsgKeyArray.cpp77
-rw-r--r--mailnews/base/util/nsMsgKeyArray.h33
-rw-r--r--mailnews/base/util/nsMsgKeySet.cpp1520
-rw-r--r--mailnews/base/util/nsMsgKeySet.h108
-rw-r--r--mailnews/base/util/nsMsgLineBuffer.cpp439
-rw-r--r--mailnews/base/util/nsMsgLineBuffer.h110
-rw-r--r--mailnews/base/util/nsMsgMailNewsUrl.cpp1060
-rw-r--r--mailnews/base/util/nsMsgMailNewsUrl.h80
-rw-r--r--mailnews/base/util/nsMsgProtocol.cpp1550
-rw-r--r--mailnews/base/util/nsMsgProtocol.h233
-rw-r--r--mailnews/base/util/nsMsgReadStateTxn.cpp66
-rw-r--r--mailnews/base/util/nsMsgReadStateTxn.h48
-rw-r--r--mailnews/base/util/nsMsgTxn.cpp294
-rw-r--r--mailnews/base/util/nsMsgTxn.h67
-rw-r--r--mailnews/base/util/nsMsgUtils.cpp2520
-rw-r--r--mailnews/base/util/nsMsgUtils.h589
-rw-r--r--mailnews/base/util/nsStopwatch.cpp183
-rw-r--r--mailnews/base/util/nsStopwatch.h47
-rw-r--r--mailnews/base/util/templateUtils.js90
-rw-r--r--mailnews/base/util/traceHelper.js113
55 files changed, 23808 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..0f1295ffe
--- /dev/null
+++ b/mailnews/base/util/moz.build
@@ -0,0 +1,82 @@
+# 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',
+ 'StringBundle.js',
+ 'templateUtils.js',
+ 'traceHelper.js',
+]
+
+if CONFIG['MOZ_MAILNEWS_OAUTH2']:
+ EXTRA_JS_MODULES += [
+ 'OAuth2.jsm',
+ 'OAuth2Providers.jsm',
+ ]
+
+LOCAL_INCLUDES += [
+ '/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..b2a7013cf
--- /dev/null
+++ b/mailnews/base/util/nsMsgCompressIStream.cpp
@@ -0,0 +1,227 @@
+/* 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 <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..c6356f83d
--- /dev/null
+++ b/mailnews/base/util/nsMsgDBFolder.cpp
@@ -0,0 +1,6063 @@
+/* -*- 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"
+#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()
+{
+ ForceDBClosed();
+
+ // Delete the .msf file.
+ // NOTE: this doesn't remove .msf files in subfolders, but
+ // both nsMsgBrkMBoxStore::DeleteFolder() and
+ // nsMsgMaildirStore::DeleteFolder() will remove those .msf files
+ // as a side-effect of deleting the .sbd directory.
+ nsCOMPtr<nsIFile> summaryFile;
+ nsresult rv = GetSummaryFile(getter_AddRefs(summaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists = false;
+ summaryFile->Exists(&exists);
+ if (exists) {
+ rv = summaryFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Ask the msgStore to delete the actual storage (mbox, maildir or whatever
+ // else may be supported in future).
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgStore->DeleteFolder(this);
+
+ return rv;
+}
+
+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..0c96ee8f0
--- /dev/null
+++ b/mailnews/base/util/nsMsgDBFolder.h
@@ -0,0 +1,291 @@
+/* -*- 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
+ */
+
+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;
+};
+
+#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(&macro_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(&macro_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..2677e149a
--- /dev/null
+++ b/mailnews/base/util/nsMsgIncomingServer.cpp
@@ -0,0 +1,2332 @@
+/* -*- 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"
+#include "nsIObserverService.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)
+{
+}
+
+nsresult nsMsgIncomingServer::Init()
+{
+ // We need to know when the password manager changes.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ observerService->AddObserver(this, "passwordmgr-storage-changed", false);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ return NS_OK;
+}
+
+nsMsgIncomingServer::~nsMsgIncomingServer()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMsgIncomingServer, nsIMsgIncomingServer,
+ nsISupportsWeakReference, nsIObserver)
+
+NS_IMETHODIMP
+nsMsgIncomingServer::Observe(nsISupports *aSubject, const char* aTopic,
+ const char16_t *aData)
+{
+ nsresult rv;
+
+ // When the state of the password manager changes we need to clear the
+ // password from the cache in case the user just removed it.
+ if (strcmp(aTopic, "passwordmgr-storage-changed") == 0)
+ {
+ rv = ForgetSessionPassword();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0)
+ {
+ // Now remove ourselves from the observer service as well.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ observerService->RemoveObserver(this, "passwordmgr-storage-changed");
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ return NS_OK;
+}
+
+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..58502e224
--- /dev/null
+++ b/mailnews/base/util/nsMsgIncomingServer.h
@@ -0,0 +1,101 @@
+/* -*- 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"
+#include "nsIObserver.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.
+ */
+
+class NS_MSG_BASE nsMsgIncomingServer : public nsIMsgIncomingServer,
+ public nsSupportsWeakReference,
+ public nsIObserver
+{
+ public:
+ nsMsgIncomingServer();
+ nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGINCOMINGSERVER
+ NS_DECL_NSIOBSERVER
+
+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;
+};
+
+#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..f38dbd013
--- /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 */);
+ explicit 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..56f8a9504
--- /dev/null
+++ b/mailnews/base/util/nsMsgLineBuffer.cpp
@@ -0,0 +1,439 @@
+/* -*- 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 "nsMsgUtils.h"
+#include "nsIInputStream.h" // used by nsMsgLineStreamBuffer
+
+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..6bacd45af
--- /dev/null
+++ b/mailnews/base/util/nsMsgLineBuffer.h
@@ -0,0 +1,110 @@
+/* -*- 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:
+ explicit 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:
+ NS_INLINE_DECL_REFCOUNTING(nsMsgLineStreamBuffer)
+
+ // 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....
+
+ // 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();
+private:
+ virtual ~nsMsgLineStreamBuffer();
+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..2c80cb692
--- /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 "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(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);
+}
+
+// NS_IMPL_ISUPPORTS(nsMsgMailNewsUrl, nsIMsgMailNewsUrl, nsIURL, nsIURI)
+
+////////////////////////////////////////////////////////////////////////////////////
+// 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..3cd3b2ef2
--- /dev/null
+++ b/mailnews/base/util/nsMsgMailNewsUrl.h
@@ -0,0 +1,80 @@
+/* -*- 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.
+//
+//////////////////////////////////////////////////////////////////////////////////
+
+class NS_MSG_BASE nsMsgMailNewsUrl : public nsIMsgMailNewsUrl,
+ public nsIURIWithPrincipal
+{
+public:
+ nsMsgMailNewsUrl();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGMAILNEWSURL
+ NS_DECL_NSIURI
+ 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;
+};
+
+#endif /* nsMsgMailNewsUrl_h___ */
diff --git a/mailnews/base/util/nsMsgProtocol.cpp b/mailnews/base/util/nsMsgProtocol.cpp
new file mode 100644
index 000000000..c86995189
--- /dev/null
+++ b/mailnews/base/util/nsMsgProtocol.cpp
@@ -0,0 +1,1550 @@
+/* -*- 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 "mozilla/Services.h"
+#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..f0675d6ff
--- /dev/null
+++ b/mailnews/base/util/nsMsgProtocol.h
@@ -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/. */
+
+#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;
+
+// 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;
+};
+
+#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..e925b60ea
--- /dev/null
+++ b/mailnews/base/util/nsMsgTxn.h
@@ -0,0 +1,67 @@
+/* -*- 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.
+ */
+
+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);
+};
+
+#endif
diff --git a/mailnews/base/util/nsMsgUtils.cpp b/mailnews/base/util/nsMsgUtils.cpp
new file mode 100644
index 000000000..44609bea0
--- /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 = spec.FindChar('&', queryIndex + 1);
+ if (queryEnd == kNotFound)
+ queryEnd = spec.FindChar('?', queryIndex + 1);
+ 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 - queryIndex));
+ }
+ 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..108518870
--- /dev/null
+++ b/mailnews/base/util/nsStopwatch.h
@@ -0,0 +1,47 @@
+/* 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"
+
+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;
+ };
+ }
+ }
+ }
+};