summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/addrbook/modules/AddrBookCard.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/addrbook/modules/AddrBookCard.jsm')
-rw-r--r--comm/mailnews/addrbook/modules/AddrBookCard.jsm481
1 files changed, 481 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/modules/AddrBookCard.jsm b/comm/mailnews/addrbook/modules/AddrBookCard.jsm
new file mode 100644
index 0000000000..23f387f921
--- /dev/null
+++ b/comm/mailnews/addrbook/modules/AddrBookCard.jsm
@@ -0,0 +1,481 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["AddrBookCard"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ BANISHED_PROPERTIES: "resource:///modules/VCardUtils.jsm",
+ newUID: "resource:///modules/AddrBookUtils.jsm",
+ VCardProperties: "resource:///modules/VCardUtils.jsm",
+ VCardPropertyEntry: "resource:///modules/VCardUtils.jsm",
+});
+
+/**
+ * Prototype for nsIAbCard objects that are not mailing lists.
+ *
+ * @implements {nsIAbCard}
+ */
+function AddrBookCard() {
+ this._directoryUID = "";
+ this._properties = new Map([
+ ["PopularityIndex", 0],
+ ["LastModifiedDate", 0],
+ ]);
+
+ this._hasVCard = false;
+ XPCOMUtils.defineLazyGetter(this, "_vCardProperties", () => {
+ // Lazy creation of the VCardProperties object. Change the `_properties`
+ // object as much as you like (e.g. loading in properties from a database)
+ // before running this code. After it runs, the `_vCardProperties` object
+ // takes over and anything in `_properties` which could be stored in the
+ // vCard will be ignored!
+
+ this._hasVCard = true;
+
+ let vCard = this.getProperty("_vCard", "");
+ try {
+ if (vCard) {
+ let vCardProperties = lazy.VCardProperties.fromVCard(vCard, {
+ isGoogleCardDAV: this._isGoogleCardDAV,
+ });
+ // Custom1..4 properties could still exist as nsIAbCard properties.
+ // Migrate them now.
+ for (let key of ["Custom1", "Custom2", "Custom3", "Custom4"]) {
+ let value = this.getProperty(key, "");
+ if (
+ value &&
+ vCardProperties.getFirstEntry(`x-${key.toLowerCase()}`) === null
+ ) {
+ vCardProperties.addEntry(
+ new lazy.VCardPropertyEntry(
+ `x-${key.toLowerCase()}`,
+ {},
+ "text",
+ value
+ )
+ );
+ }
+ this.deleteProperty(key);
+ }
+ return vCardProperties;
+ }
+ return lazy.VCardProperties.fromPropertyMap(this._properties);
+ } catch (error) {
+ console.error("Error creating vCard properties", error);
+ // Return an empty VCardProperties object if parsing failed
+ // catastrophically.
+ return new lazy.VCardProperties("4.0");
+ }
+ });
+}
+
+AddrBookCard.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIAbCard"]),
+ classID: Components.ID("{1143991d-31cd-4ea6-9c97-c587d990d724}"),
+
+ /* nsIAbCard */
+
+ generateName(generateFormat, bundle) {
+ let result = "";
+ switch (generateFormat) {
+ case Ci.nsIAbCard.GENERATE_DISPLAY_NAME:
+ result = this.displayName;
+ break;
+
+ case Ci.nsIAbCard.GENERATE_LAST_FIRST_ORDER:
+ if (this.lastName) {
+ let otherNames = [
+ this.prefixName,
+ this.firstName,
+ this.middleName,
+ this.suffixName,
+ ]
+ .filter(Boolean)
+ .join(" ");
+ if (!otherNames) {
+ // Only use the lastName if we don't have anything to add after the
+ // comma, in order to avoid for the string to finish with ", ".
+ result = this.lastName;
+ } else {
+ result =
+ bundle?.formatStringFromName("lastFirstFormat", [
+ this.lastName,
+ otherNames,
+ ]) ?? `${this.lastName}, ${otherNames}`;
+ }
+ }
+ break;
+
+ default:
+ let startNames = [this.prefixName, this.firstName, this.middleName]
+ .filter(Boolean)
+ .join(" ");
+ let endNames = [this.lastName, this.suffixName]
+ .filter(Boolean)
+ .join(" ");
+ result =
+ bundle?.formatStringFromName("firstLastFormat", [
+ startNames,
+ endNames,
+ ]) ?? `${startNames} ${endNames}`;
+ break;
+ }
+
+ // Remove any leftover blank spaces.
+ result = result.trim();
+
+ if (result == "" || result == ",") {
+ result =
+ this.displayName ||
+ [
+ this.prefixName,
+ this.firstName,
+ this.middleName,
+ this.lastName,
+ this.suffixName,
+ ]
+ .filter(Boolean)
+ .join(" ")
+ .trim();
+
+ if (!result) {
+ // So far we don't have anything to show as a contact name.
+
+ if (this.primaryEmail) {
+ // Let's use the primary email localpart.
+ result = this.primaryEmail.split("@", 1)[0];
+ } else {
+ // We don't have a primary email either, let's try with the
+ // organization name.
+ result = !this._hasVCard
+ ? this.getProperty("Company", "")
+ : this._vCardProperties.getFirstValue("org");
+ }
+ }
+ }
+ return result || "";
+ },
+ get directoryUID() {
+ return this._directoryUID;
+ },
+ set directoryUID(value) {
+ this._directoryUID = value;
+ },
+ get UID() {
+ if (!this._uid) {
+ this._uid = lazy.newUID();
+ }
+ return this._uid;
+ },
+ set UID(value) {
+ if (this._uid && value != this._uid) {
+ throw Components.Exception(
+ `Bad UID: got ${value} != ${this.uid}`,
+ Cr.NS_ERROR_UNEXPECTED
+ );
+ }
+ this._uid = value;
+ },
+ get properties() {
+ let props = [];
+ for (const [name, value] of this._properties) {
+ props.push({
+ get name() {
+ return name;
+ },
+ get value() {
+ return value;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIProperty"]),
+ });
+ }
+ return props;
+ },
+ get supportsVCard() {
+ return true;
+ },
+ get vCardProperties() {
+ return this._vCardProperties;
+ },
+ get firstName() {
+ if (!this._hasVCard) {
+ return this.getProperty("FirstName", "");
+ }
+ let name = this._vCardProperties.getFirstValue("n");
+ if (!Array.isArray(name)) {
+ return "";
+ }
+ name = name[1];
+ if (Array.isArray(name)) {
+ name = name.join(" ");
+ }
+ return name;
+ },
+ set firstName(value) {
+ let n = this._vCardProperties.getFirstEntry("n");
+ if (n) {
+ n.value[1] = value;
+ } else {
+ this._vCardProperties.addEntry(
+ new lazy.VCardPropertyEntry("n", {}, "text", ["", value, "", "", ""])
+ );
+ }
+ },
+ get lastName() {
+ if (!this._hasVCard) {
+ return this.getProperty("LastName", "");
+ }
+ let name = this._vCardProperties.getFirstValue("n");
+ if (!Array.isArray(name)) {
+ return "";
+ }
+ name = name[0];
+ if (Array.isArray(name)) {
+ name = name.join(" ");
+ }
+ return name;
+ },
+ set lastName(value) {
+ let n = this._vCardProperties.getFirstEntry("n");
+ if (n) {
+ n.value[0] = value;
+ } else {
+ this._vCardProperties.addEntry(
+ new lazy.VCardPropertyEntry("n", {}, "text", [value, "", "", "", ""])
+ );
+ }
+ },
+ get displayName() {
+ if (!this._hasVCard) {
+ return this.getProperty("DisplayName", "");
+ }
+ return this._vCardProperties.getFirstValue("fn") || "";
+ },
+ set displayName(value) {
+ let fn = this._vCardProperties.getFirstEntry("fn");
+ if (fn) {
+ fn.value = value;
+ } else {
+ this._vCardProperties.addEntry(
+ new lazy.VCardPropertyEntry("fn", {}, "text", value)
+ );
+ }
+ },
+ get primaryEmail() {
+ if (!this._hasVCard) {
+ return this.getProperty("PrimaryEmail", "");
+ }
+ return this._vCardProperties.getAllValuesSorted("email")[0] ?? "";
+ },
+ set primaryEmail(value) {
+ let entries = this._vCardProperties.getAllEntriesSorted("email");
+ if (entries.length && entries[0].value != value) {
+ this._vCardProperties.removeEntry(entries[0]);
+ entries.shift();
+ }
+
+ if (value) {
+ let existing = entries.find(e => e.value == value);
+ if (existing) {
+ existing.params.pref = "1";
+ } else {
+ this._vCardProperties.addEntry(
+ new lazy.VCardPropertyEntry("email", { pref: "1" }, "text", value)
+ );
+ }
+ } else if (entries.length) {
+ entries[0].params.pref = "1";
+ }
+ },
+ get isMailList() {
+ return false;
+ },
+ get mailListURI() {
+ return "";
+ },
+ get emailAddresses() {
+ return this._vCardProperties.getAllValuesSorted("email");
+ },
+ get photoURL() {
+ let photoEntry = this.vCardProperties.getFirstEntry("photo");
+ if (photoEntry?.value) {
+ if (photoEntry.value?.startsWith("data:image/")) {
+ // This is a version 4.0 card
+ // OR a version 3.0 card with the URI type set (uncommon)
+ // OR a version 3.0 card that is lying about its type.
+ return photoEntry.value;
+ }
+ if (photoEntry.type == "binary" && photoEntry.value.startsWith("iVBO")) {
+ // This is a version 3.0 card.
+ // The first 3 bytes say this image is PNG.
+ return `data:image/png;base64,${photoEntry.value}`;
+ }
+ if (photoEntry.type == "binary" && photoEntry.value.startsWith("/9j/")) {
+ // This is a version 3.0 card.
+ // The first 3 bytes say this image is JPEG.
+ return `data:image/jpeg;base64,${photoEntry.value}`;
+ }
+ if (photoEntry.type == "uri" && /^https?:\/\//.test(photoEntry.value)) {
+ // A remote URI.
+ return photoEntry.value;
+ }
+ }
+
+ let photoName = this.getProperty("PhotoName", "");
+ if (photoName) {
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append("Photos");
+ file.append(photoName);
+ return Services.io.newFileURI(file).spec;
+ }
+
+ return "";
+ },
+
+ getProperty(name, defaultValue) {
+ if (this._properties.has(name)) {
+ return this._properties.get(name);
+ }
+ return defaultValue;
+ },
+ getPropertyAsAString(name) {
+ if (!this._properties.has(name)) {
+ return "";
+ }
+ return this.getProperty(name);
+ },
+ getPropertyAsAUTF8String(name) {
+ if (!this._properties.has(name)) {
+ throw Components.Exception(`${name} N/A`, Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ return this.getProperty(name);
+ },
+ getPropertyAsUint32(name) {
+ let value = this.getProperty(name);
+ if (!isNaN(parseInt(value, 10))) {
+ return parseInt(value, 10);
+ }
+ if (!isNaN(parseInt(value, 16))) {
+ return parseInt(value, 16);
+ }
+ throw Components.Exception(
+ `${name}: ${value} - not an int`,
+ Cr.NS_ERROR_NOT_AVAILABLE
+ );
+ },
+ getPropertyAsBool(name, defaultValue) {
+ let value = this.getProperty(name);
+ switch (value) {
+ case false:
+ case 0:
+ case "0":
+ return false;
+ case true:
+ case 1:
+ case "1":
+ return true;
+ case undefined:
+ return defaultValue;
+ }
+ throw Components.Exception(
+ `${name}: ${value} - not a boolean`,
+ Cr.NS_ERROR_NOT_AVAILABLE
+ );
+ },
+ setProperty(name, value) {
+ if (lazy.BANISHED_PROPERTIES.includes(name)) {
+ throw new Components.Exception(
+ `Unable to set ${name} as a property, use vCardProperties`,
+ Cr.NS_ERROR_UNEXPECTED
+ );
+ }
+ if ([null, undefined, ""].includes(value)) {
+ this._properties.delete(name);
+ return;
+ }
+ if (typeof value == "boolean") {
+ value = value ? "1" : "0";
+ }
+ this._properties.set(name, "" + value);
+ },
+ setPropertyAsAString(name, value) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ setPropertyAsAUTF8String(name, value) {
+ this.setProperty(name, value);
+ },
+ setPropertyAsUint32(name, value) {
+ this.setProperty(name, value);
+ },
+ setPropertyAsBool(name, value) {
+ this.setProperty(name, value ? "1" : "0");
+ },
+ deleteProperty(name) {
+ this._properties.delete(name);
+ },
+ hasEmailAddress(emailAddress) {
+ emailAddress = emailAddress.toLowerCase();
+ return this.emailAddresses.some(e => e.toLowerCase() == emailAddress);
+ },
+ translateTo(type) {
+ if (type == "vcard") {
+ if (!this._vCardProperties.getFirstValue("uid")) {
+ this._vCardProperties.addValue("uid", this.UID);
+ }
+ return encodeURIComponent(this._vCardProperties.toVCard());
+ }
+ // Get nsAbCardProperty to do the work, the code is in C++ anyway.
+ let cardCopy = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
+ Ci.nsIAbCard
+ );
+ cardCopy.UID = this.UID;
+ cardCopy.copy(this);
+ return cardCopy.translateTo(type);
+ },
+ generatePhoneticName(lastNameFirst) {
+ if (lastNameFirst) {
+ return (
+ this.getProperty("PhoneticLastName", "") +
+ this.getProperty("PhoneticFirstName", "")
+ );
+ }
+ return (
+ this.getProperty("PhoneticFirstName", "") +
+ this.getProperty("PhoneticLastName", "")
+ );
+ },
+ generateChatName() {
+ for (let name of [
+ "_GoogleTalk",
+ "_AimScreenName",
+ "_Yahoo",
+ "_Skype",
+ "_QQ",
+ "_MSN",
+ "_ICQ",
+ "_JabberId",
+ "_IRC",
+ ]) {
+ if (this._properties.has(name)) {
+ return this._properties.get(name);
+ }
+ }
+ return "";
+ },
+ copy(srcCard) {
+ throw Components.Exception(
+ "nsIAbCard.copy() not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ },
+ equals(card) {
+ return this.UID == card.UID;
+ },
+};