/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, curly:true, browser:true, devel:true, indent:2, maxerr:50, moz:true, newcap:false, moz:true */ /* global mozContact: false */ /** * Translate objects created from LDIF files to Contacts * * @param Array of LDIF objects * @return Contact with all fields filled * @throws ValueError for unknown field in the LDIF object * * ContactsAPI: * @see https://wiki.mozilla.org/WebAPI/ContactsAPI * @spec http://www.w3.org/TR/contacts-manager-api/ * * Thunderbid LDAP Schema: * @see https://developer.mozilla.org/docs/Thunderbird/LDAP_Support * @see https://tools.ietf.org/html/rfc4519 */ function translateObjectToContact(inRec) { "use strict"; var contact = {}, year, month, day, curRec = null; function ValueError(message) { this.name = "ValueError"; this.message = message || "Unknown Value"; } ValueError.prototype = new Error(); ValueError.prototype.constructor = ValueError; const cleanPhoneNoRE = /[^0-9+]*/g; /** * Find the proper record (or create new one) in the multi-value * attribute * * @param idx name of the attribute to which the record belongs * @param subClass Function constructor of the record if there is * none * @param subType String with the type of the record * @return record of the proper class * * Uses global variable contact. */ function findSubElement(idx, subType) { var cAddr = null; function createNewContact(subType) { var cont = {}; if (subType) { cont.type = [subType]; } return cont; } // No contact.adr at all if (!contact.hasOwnProperty(idx)) { cAddr = createNewContact(subType); contact[idx] = cAddr; } // Single-element property else if (!Array.isArray(contact[idx])) { if (contact[idx].type.indexOf(subType) === -1) { contact[idx] = [contact[idx]]; cAddr = createNewContact(subType); contact[idx].push(cAddr); } else { cAddr = contact[idx]; } } // Array else { cAddr = contact[idx].filter(function (addr) { return addr.type.indexOf(subType) !== -1; }); if (cAddr.length === 0) { cAddr = createNewContact(subType); contact[idx].push(cAddr); } else { cAddr = cAddr[0]; } } return cAddr; } /** * Manages squeezing two-line address into one value field * * @param type String ['home', 'work'] * @param target String ContactsAddress attribute to be set (e.g., * streetAddress) * @param first String first line index in inRec * @param second String second line index in inRec * @return None * sets variable inRec local to outer function * deletes both fields from inRec so as to avoid duplication */ function secondLineInAddress(type, target, first, second) { var curRec = findSubElement("adr", type); if (second in inRec) { curRec[target] = inRec[first] + "\n" + inRec[second]; } else if (first in inRec) { curRec[target] = inRec[first]; } if (first in inRec) { delete inRec[first]; } if (second in inRec) { delete inRec[second]; } } /** * Firefox OS doesn’t recognize well formatted phone numbers, and * works best with the plain number-only ones. */ function cleanPhoneNumber(inNo) { return inNo.replace(cleanPhoneNoRE, ""); } for (var key in inRec) { if (["birthyear", "birthmonth", "birthday"].indexOf(key) !== -1) { // We have alternatively either whole date in birthyear field, // e.g. 19940221, or we have all three properties set. year = inRec.birthyear || "1970"; // lowest year in Unix time month = inRec.birthmonth || null; day = inRec.birthday || null; if (year.length === 8) { contact.bday = new Date(parseInt(year.slice(0,4), 10), parseInt(year.slice(4,6), 10) - 1, parseInt(year.slice(6,8), 10)); if (inRec.birthday) { delete inRec.birthday; } if (inRec.birthmonth) { delete inRec.birthmonth; } } else if (month && day) { contact.bday = new Date(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10)); delete inRec.birthday; delete inRec.birthmonth; delete inRec.birthyear; } else { throw new ValueError("Wrong value of birthday!"); } } else if (key === "c") { curRec = findSubElement("adr", "work"); curRec.countryName = inRec[key]; } else if (key === "cn") { contact.name = [inRec[key]]; } else if (key === "description") { contact.note = [inRec[key]]; } else if (key === "facsimiletelephonenumber") { curRec = findSubElement("tel", "fax"); curRec.value = cleanPhoneNumber(inRec[key]); } else if (key === "givenName") { contact.givenName = [inRec[key]]; } else if (key === "homePhone") { curRec = findSubElement("tel", "home"); curRec.value = cleanPhoneNumber(inRec[key]); } else if (key === "l") { curRec = findSubElement("adr", "work"); curRec.locality = inRec[key]; } else if (key === "mail") { curRec = findSubElement("email", "PREF"); curRec.value = inRec[key]; } else if (key === "mozillaSecondEmail") { curRec = findSubElement("email"); curRec.value = inRec[key]; } else if (key === "mobile") { curRec = findSubElement("tel", "mobile"); curRec.value = cleanPhoneNumber(inRec[key]); } else if (key === "mozillaHomeCountryName") { curRec = findSubElement("adr", "home"); curRec.countryName = inRec[key]; } else if (key === "mozillaHomeLocalityName") { curRec = findSubElement("adr", "home"); curRec.locality = inRec[key]; } else if (key === "mozillaHomePostalCode") { curRec = findSubElement("adr", "home"); curRec.postalCode = inRec[key]; } else if (key === "mozillaHomeState") { curRec = findSubElement("adr", "home"); curRec.region = inRec[key]; } else if (["mozillaHomeStreet", "mozillaHomeStreet2"]. indexOf(key) !== -1) { secondLineInAddress("home", "streetAddress", "mozillaHomeStreet", "mozillaHomeStreet2"); } else if (key === "mozillaWorkUrl") { curRec = findSubElement("url", "work"); curRec.value = inRec[key]; } else if (key === "mozillaHomeUrl") { curRec = findSubElement("url", "home"); curRec.value = inRec[key]; } else if (key === "mozillaNickname") { contact.nickname = [inRec[key]]; } // Per W3C Contacts API // http://www.w3.org/TR/contacts-manager-api/#widl-ContactProperties-org // org of type array of DOMString // A string or set thereof representing the organization(s) // the contact belongs to. It maps to vCard's ORG attribute // // Per RFC 6350: // ============= // 6.6.4. ORG // // Purpose: To specify the organizational name and units associated // with the vCard. // // Value type: A single structured text value consisting of components // separated by the SEMICOLON character (U+003B). // // Special notes: The property is based on the X.520 Organization Name // and Organization Unit attributes [CCITT.X520.1988]. The property // value is a structured type consisting of the organization name, // followed by zero or more levels of organizational unit names. // // Example: A property value consisting of an organizational name, // organizational unit #1 name, and organizational unit #2 name. // // ORG:ABC\, Inc.;North American Division;Marketing else if (["o", "ou"].indexOf(key) !== -1) { if ('ou' in inRec) { if ('o' in inRec) { contact.org = inRec.o + ";" + inRec.ou; } else { throw new ValueError( "Organizational unit without an organization!"); } delete inRec.ou; } else { contact.org = inRec.o; } delete inRec.o; } else if (key === "postalCode") { curRec = findSubElement("adr", "work"); curRec.postalCode = inRec[key]; } else if (key === "sn") { contact.familyName = [inRec[key]]; } else if (key === "st") { curRec = findSubElement("adr", "work"); curRec.region = inRec[key]; } else if (["street", "mozillaWorkStreet2"]. indexOf(key) !== -1) { secondLineInAddress("work", "streetAddress", "street", "mozillaWorkStreet2"); } else if (key === "telephoneNumber") { curRec = findSubElement("tel", "work"); curRec.value = cleanPhoneNumber(inRec[key]); } else if (key === "title") { contact.jobTitle = [inRec[key]]; } // Unknown attribute else { throw new ValueError("Unknown attribute " + key + " with value:\n" + inRec[key]); } } // Per RFC 4519 section 3.12 cn and sn attributes are always // required if (! contact.hasOwnProperty('name')) { if (contact.hasOwnProperty('familyName') && contact.hasOwnProperty('givenName')) { contact.name = contact.givenName + " " + contact.familyName; } else if (contact.hasOwnProperty("org")) { contact.name = contact.org; } else if (contact.hasOwnProperty("jobTitle")) { contact.name = contact.jobTitle; } } if (Object.keys(contact).length > 0) { var outObj = new mozContact(); outObj.init(contact); return contact; } return null; }