// The Passpet XPCOM component provides access to the array of personas
// and provides other services common to all personas.

// Shorthand for getting XPConnect interfaces, classes, objects, and services.
const XPI = Components.interfaces, XPC = Components.classes;
function XPC_(c) { return XPC[c.match(/^@/) ? c : '@mozilla.org/' + c]; }
function XPO(c, i) { return XPC_(c) ? XPC_(c).createInstance(i) : null; }
function XPS(c, i) { return XPC_(c) ? XPC_(c).getService(i) : null; }
function XQI(o, i) { return o.QueryInterface(i); }

const XPR = Components.results;
const CID = Components.ID('{aec9dc61-f26d-505e-963a-13c65e7800b9}');
const NAME = 'Passpet Service';
const CONTRACT = '@passpet.org/passpet;1';

// The module registers and unregisters the Passpet component interface.
function NSGetModule(manager, filespec) {
    return new function() {
        this.registerSelf = function(manager, filespec, location, type) {
            manager = XQI(manager, XPI.nsIComponentRegistrar);
            manager.registerFactoryLocation(
                CID, NAME, CONTRACT, filespec, location, type);
        };

        this.unregisterSelf = function(manager, location, type) {
            manager = XQI(manager, XPI.nsIComponentRegistrar);
            manager.unregisterFactoryLocation(CID, location);
        };

        this.canUnload = function(manager) {
            return true;
        };

        this.getClassObject = function(manager, cid, iid) {
            if (!iid.equals(XPI.nsIFactory)) throw XPR.NS_ERROR_NOT_IMPLEMENTED;
            if (!cid.equals(CID)) throw XPR.NS_ERROR_NO_INTERFACE;
            return new function() {
                this.createInstance = function(outer, iid) {
                    if (outer != null) throw XPR.NS_ERROR_NO_AGGREGATION;
                    return XQI(new service(), iid);
                };
            };
        };
    };
};


// ---------------------------------------------------------- two-part TLDs
// This list comes from http://spamcheck.freeapp.net/two-level-tlds .

const twoPartTLDs = [
    'com.ac', 'edu.ac', 'gov.ac', 'net.ac', 'mil.ac', 'org.ac', 'com.ae',
    'net.ae', 'org.ae', 'gov.ae', 'ac.ae', 'co.ae', 'sch.ae', 'pro.ae',
    'com.ai', 'org.ai', 'edu.ai', 'gov.ai', 'com.ar', 'net.ar', 'org.ar',
    'gov.ar', 'mil.ar', 'edu.ar', 'int.ar', 'co.at', 'ac.at', 'or.at',
    'gv.at', 'priv.at', 'com.au', 'gov.au', 'org.au', 'edu.au', 'id.au',
    'oz.au', 'info.au', 'net.au', 'asn.au', 'csiro.au', 'telememo.au',
    'conf.au', 'otc.au', 'com.az', 'net.az', 'org.az', 'com.bb', 'net.bb',
    'org.bb', 'ac.be', 'belgie.be', 'dns.be', 'fgov.be', 'com.bh', 'gov.bh',
    'net.bh', 'edu.bh', 'org.bh', 'com.bm', 'edu.bm', 'gov.bm', 'org.bm',
    'net.bm', 'adm.br', 'adv.br', 'agr.br', 'am.br', 'arq.br', 'art.br',
    'ato.br', 'bio.br', 'bmd.br', 'cim.br', 'cng.br', 'cnt.br', 'com.br',
    'coop.br', 'ecn.br', 'edu.br', 'eng.br', 'esp.br', 'etc.br', 'eti.br',
    'far.br', 'fm.br', 'fnd.br', 'fot.br', 'fst.br', 'g12.br', 'ggf.br',
    'gov.br', 'imb.br', 'ind.br', 'inf.br', 'jor.br', 'lel.br', 'mat.br',
    'med.br', 'mil.br', 'mus.br', 'net.br', 'nom.br', 'not.br', 'ntr.br',
    'odo.br', 'org.br', 'ppg.br', 'pro.br', 'psc.br', 'psi.br', 'qsl.br',
    'rec.br', 'slg.br', 'srv.br', 'tmp.br', 'trd.br', 'tur.br', 'tv.br',
    'vet.br', 'zlg.br', 'com.bs', 'net.bs', 'org.bs', 'ab.ca', 'bc.ca',
    'mb.ca', 'nb.ca', 'nf.ca', 'nl.ca', 'ns.ca', 'nt.ca', 'nu.ca', 'on.ca',
    'pe.ca', 'qc.ca', 'sk.ca', 'yk.ca', 'gc.ca', 'co.ck', 'net.ck',
    'org.ck', 'edu.ck', 'gov.ck', 'com.cn', 'edu.cn', 'gov.cn', 'net.cn',
    'org.cn', 'ac.cn', 'ah.cn', 'bj.cn', 'cq.cn', 'gd.cn', 'gs.cn', 'gx.cn',
    'gz.cn', 'hb.cn', 'he.cn', 'hi.cn', 'hk.cn', 'hl.cn', 'hn.cn', 'jl.cn',
    'js.cn', 'ln.cn', 'mo.cn', 'nm.cn', 'nx.cn', 'qh.cn', 'sc.cn', 'sn.cn',
    'sh.cn', 'sx.cn', 'tj.cn', 'tw.cn', 'xj.cn', 'xz.cn', 'yn.cn', 'zj.cn',
    'arts.co', 'com.co', 'edu.co', 'firm.co', 'gov.co', 'info.co', 'int.co',
    'nom.co', 'mil.co', 'org.co', 'rec.co', 'store.co', 'web.co', 'ac.cr',
    'co.cr', 'ed.cr', 'fi.cr', 'go.cr', 'or.cr', 'sa.cr', 'com.cu',
    'net.cu', 'org.cu', 'ac.cy', 'com.cy', 'gov.cy', 'net.cy', 'org.cy',
    'co.dk', 'art.do', 'com.do', 'edu.do', 'gov.do', 'gob.do', 'org.do',
    'mil.do', 'net.do', 'sld.do', 'web.do', 'com.dz', 'org.dz', 'net.dz',
    'gov.dz', 'edu.dz', 'ass.dz', 'pol.dz', 'art.dz', 'com.ec', 'k12.ec',
    'edu.ec', 'fin.ec', 'med.ec', 'gov.ec', 'mil.ec', 'org.ec', 'net.ec',
    'com.ee', 'pri.ee', 'fie.ee', 'org.ee', 'med.ee', 'com.eg', 'edu.eg',
    'eun.eg', 'gov.eg', 'net.eg', 'org.eg', 'sci.eg', 'com.er', 'net.er',
    'org.er', 'edu.er', 'mil.er', 'gov.er', 'ind.er', 'com.es', 'org.es',
    'gob.es', 'edu.es ', 'nom.es', 'com.et', 'gov.et', 'org.et', 'edu.et',
    'net.et', 'biz.et', 'name.et', 'info.et', 'ac.fj', 'com.fj', 'gov.fj',
    'id.fj', 'org.fj', 'school.fj', 'com.fk', 'ac.fk', 'gov.fk', 'net.fk',
    'nom.fk', 'org.fk', 'asso.fr', 'nom.fr', 'barreau.fr', 'com.fr',
    'prd.fr', 'presse.fr', 'tm.fr', 'aeroport.fr', 'assedic.fr',
    'avocat.fr', 'avoues.fr', 'cci.fr', 'chambagri.fr',
    'chirurgiens-dentistes.fr', 'experts-comptables.fr',
    'geometre-expert.fr', 'gouv.fr', 'greta.fr', 'huissier-justice.fr',
    'medecin.fr', 'notaires.fr', 'pharmacien.fr', 'port.fr',
    'veterinaire.fr', 'com.ge', 'edu.ge', 'gov.ge', 'mil.ge', 'net.ge',
    'org.ge', 'pvt.ge', 'co.gg', 'org.gg', 'sch.gg', 'ac.gg', 'gov.gg',
    'ltd.gg', 'ind.gg', 'net.gg', 'alderney.gg', 'guernsey.gg', 'sark.gg',
    'com.gt', 'edu.gt', 'net.gt', 'gob.gt', 'org.gt', 'mil.gt', 'ind.gt',
    'com.gu', 'edu.gu', 'net.gu', 'org.gu', 'gov.gu', 'mil.gu', 'com.hk',
    'net.hk', 'org.hk', 'idv.hk', 'gov.hk', 'edu.hk', 'co.hu', '2000.hu',
    'erotika.hu', 'jogasz.hu', 'sex.hu', 'video.hu', 'info.hu', 'agrar.hu',
    'film.hu', 'konyvelo.hu', 'shop.hu', 'org.hu', 'bolt.hu', 'forum.hu',
    'lakas.hu', 'suli.hu', 'priv.hu', 'casino.hu', 'games.hu', 'media.hu',
    'szex.hu', 'sport.hu', 'city.hu', 'hotel.hu', 'news.hu', 'tozsde.hu',
    'tm.hu', 'erotica.hu', 'ingatlan.hu', 'reklam.hu', 'utazas.hu', 'ac.id',
    'co.id', 'go.id', 'mil.id', 'net.id', 'or.id', 'co.il', 'net.il',
    'org.il', 'ac.il', 'gov.il', 'k12.il', 'muni.il', 'idf.il', 'co.im',
    'net.im', 'org.im', 'ac.im', 'lkd.co.im', 'gov.im', 'nic.im',
    'plc.co.im', 'co.in', 'net.in', 'ac.in', 'ernet.in', 'gov.in', 'nic.in',
    'res.in', 'gen.in', 'firm.in', 'mil.in', 'org.in', 'ind.in', 'ac.je',
    'co.je', 'net.je', 'org.je', 'gov.je', 'ind.je', 'jersey.je', 'ltd.je',
    'sch.je', 'com.jo', 'org.jo', 'net.jo', 'gov.jo', 'edu.jo', 'mil.jo',
    'ad.jp', 'ac.jp', 'co.jp', 'go.jp', 'or.jp', 'ne.jp', 'gr.jp', 'ed.jp',
    'lg.jp', 'net.jp', 'org.jp', 'gov.jp', 'hokkaido.jp', 'aomori.jp',
    'iwate.jp', 'miyagi.jp', 'akita.jp', 'yamagata.jp', 'fukushima.jp',
    'ibaraki.jp', 'tochigi.jp', 'gunma.jp', 'saitama.jp', 'chiba.jp',
    'tokyo.jp', 'kanagawa.jp', 'niigata.jp', 'toyama.jp', 'ishikawa.jp',
    'fukui.jp', 'yamanashi.jp', 'nagano.jp', 'gifu.jp', 'shizuoka.jp',
    'aichi.jp', 'mie.jp', 'shiga.jp', 'kyoto.jp', 'osaka.jp', 'hyogo.jp',
    'nara.jp', 'wakayama.jp', 'tottori.jp', 'shimane.jp', 'okayama.jp',
    'hiroshima.jp', 'yamaguchi.jp', 'tokushima.jp', 'kagawa.jp', 'ehime.jp',
    'kochi.jp', 'fukuoka.jp', 'saga.jp', 'nagasaki.jp', 'kumamoto.jp',
    'oita.jp', 'miyazaki.jp', 'kagoshima.jp', 'okinawa.jp', 'sapporo.jp',
    'sendai.jp', 'yokohama.jp', 'kawasaki.jp', 'nagoya.jp', 'kobe.jp',
    'kitakyushu.jp', 'utsunomiya.jp', 'kanazawa.jp', 'takamatsu.jp',
    'matsuyama.jp', 'com.kh', 'net.kh', 'org.kh', 'per.kh', 'edu.kh',
    'gov.kh', 'mil.kh', 'ac.kr', 'co.kr', 'go.kr', 'ne.kr', 'or.kr',
    'pe.kr', 're.kr', 'seoul.kr', 'kyonggi.kr', 'com.kw', 'net.kw',
    'org.kw', 'edu.kw', 'gov.kw', 'com.la', 'net.la', 'org.la', 'com.lb',
    'org.lb', 'net.lb', 'edu.lb', 'gov.lb', 'mil.lb', 'com.lc', 'edu.lc',
    'gov.lc', 'net.lc', 'org.lc', 'com.lv', 'net.lv', 'org.lv', 'edu.lv',
    'gov.lv', 'mil.lv', 'id.lv', 'asn.lv', 'conf.lv', 'com.ly', 'net.ly',
    'org.ly', 'co.ma', 'net.ma', 'org.ma', 'press.ma', 'ac.ma', 'com.mk',
    'com.mm', 'net.mm', 'org.mm', 'edu.mm', 'gov.mm', 'com.mo', 'net.mo',
    'org.mo', 'edu.mo', 'gov.mo', 'com.mt', 'net.mt', 'org.mt', 'edu.mt',
    'tm.mt', 'uu.mt', 'com.mx', 'net.mx', 'org.mx', 'com.my', 'org.my',
    'gov.my', 'edu.my', 'net.my', 'com.na', 'org.na', 'net.na', 'alt.na',
    'edu.na', 'cul.na', 'unam.na', 'telecom.na', 'com.nc', 'net.nc',
    'org.nc', 'ac.ng', 'edu.ng', 'sch.ng', 'com.ng', 'gov.ng', 'org.ng',
    'net.ng', 'gob.ni', 'com.ni', 'net.ni', 'edu.ni', 'nom.ni', 'org.ni',
    'com.np', 'net.np', 'org.np', 'gov.np', 'edu.np', 'ac.nz', 'co.nz',
    'cri.nz', 'gen.nz', 'geek.nz', 'govt.nz', 'iwi.nz', 'maori.nz',
    'mil.nz', 'net.nz', 'org.nz', 'school.nz', 'com.om', 'co.om', 'edu.om',
    'ac.om', 'gov.om', 'net.om', 'org.om', 'mod.om', 'museum.om', 'biz.om',
    'pro.om', 'med.om', 'com.pa', 'net.pa', 'org.pa', 'edu.pa', 'ac.pa',
    'gob.pa', 'sld.pa', 'edu.pe', 'gob.pe', 'nom.pe', 'mil.pe', 'org.pe',
    'com.pe', 'net.pe', 'com.pg', 'net.pg', 'ac.pg', 'com.ph', 'net.ph',
    'org.ph', 'mil.ph', 'ngo.ph', 'aid.pl', 'agro.pl', 'atm.pl', 'auto.pl',
    'biz.pl', 'com.pl', 'edu.pl', 'gmina.pl', 'gsm.pl', 'info.pl',
    'mail.pl', 'miasta.pl', 'media.pl', 'mil.pl', 'net.pl',
    'nieruchomosci.pl', 'nom.pl', 'org.pl', 'pc.pl', 'powiat.pl', 'priv.pl',
    'realestate.pl', 'rel.pl', 'sex.pl', 'shop.pl', 'sklep.pl', 'sos.pl',
    'szkola.pl', 'targi.pl', 'tm.pl', 'tourism.pl', 'travel.pl',
    'turystyka.pl', 'com.pk', 'net.pk', 'edu.pk', 'org.pk', 'fam.pk',
    'biz.pk', 'web.pk', 'gov.pk', 'gob.pk', 'gok.pk', 'gon.pk', 'gop.pk',
    'gos.pk', 'edu.ps', 'gov.ps', 'plo.ps', 'sec.ps', 'com.py', 'net.py',
    'org.py', 'edu.py', 'com.qa', 'net.qa', 'org.qa', 'edu.qa', 'gov.qa',
    'asso.re', 'com.re', 'nom.re', 'com.ro', 'org.ro', 'tm.ro', 'nt.ro',
    'nom.ro', 'info.ro', 'rec.ro', 'arts.ro', 'firm.ro', 'store.ro',
    'www.ro', 'com.ru', 'net.ru', 'org.ru', 'gov.ru', 'pp.ru', 'com.sa',
    'edu.sa', 'sch.sa', 'med.sa', 'gov.sa', 'net.sa', 'org.sa', 'pub.sa',
    'com.sb', 'net.sb', 'org.sb', 'edu.sb', 'gov.sb', 'com.sd', 'net.sd',
    'org.sd', 'edu.sd', 'sch.sd', 'med.sd', 'gov.sd', 'tm.se', 'press.se',
    'parti.se', 'brand.se', 'fh.se', 'fhsk.se', 'fhv.se', 'komforb.se',
    'kommunalforbund.se', 'komvux.se', 'lanarb.se', 'lanbib.se',
    'naturbruksgymn.se', 'sshn.se', 'org.se', 'pp.se', 'com.sg', 'net.sg',
    'org.sg', 'edu.sg', 'gov.sg', 'per.sg', 'com.sh', 'net.sh', 'org.sh',
    'edu.sh', 'gov.sh', 'mil.sh', 'gov.st', 'saotome.st', 'principe.st',
    'consulado.st', 'embaixada.st', 'org.st', 'edu.st', 'net.st', 'com.st',
    'store.st', 'mil.st', 'co.st', 'com.sv', 'org.sv', 'edu.sv', 'gob.sv',
    'red.sv', 'com.sy', 'net.sy', 'org.sy', 'gov.sy', 'ac.th', 'co.th',
    'go.th', 'net.th', 'or.th', 'com.tn', 'net.tn', 'org.tn', 'edunet.tn',
    'gov.tn', 'ens.tn', 'fin.tn', 'nat.tn', 'ind.tn', 'info.tn', 'intl.tn',
    'rnrt.tn', 'rnu.tn', 'rns.tn', 'tourism.tn', 'com.tr', 'net.tr',
    'org.tr', 'edu.tr', 'gov.tr', 'mil.tr', 'bbs.tr', 'k12.tr', 'gen.tr',
    'co.tt', 'com.tt', 'org.tt', 'net.tt', 'biz.tt', 'info.tt', 'pro.tt',
    'name.tt', 'gov.tt', 'edu.tt', 'nic.tt', 'us.tt', 'uk.tt', 'ca.tt',
    'eu.tt', 'es.tt', 'fr.tt', 'it.tt', 'se.tt', 'dk.tt', 'be.tt', 'de.tt',
    'at.tt', 'au.tt', 'co.tv', 'com.tw', 'net.tw', 'org.tw', 'edu.tw',
    'idv.tw', 'gove.tw', 'com.ua', 'net.ua', 'org.ua', 'edu.ua', 'gov.ua',
    'ac.ug', 'co.ug', 'or.ug', 'go.ug', 'co.uk', 'me.uk', 'org.uk',
    'edu.uk', 'ltd.uk', 'plc.uk', 'net.uk', 'sch.uk', 'nic.uk', 'ac.uk',
    'gov.uk', 'nhs.uk', 'police.uk', 'mod.uk', 'dni.us', 'fed.us', 'com.uy',
    'edu.uy', 'net.uy', 'org.uy', 'gub.uy', 'mil.uy', 'com.ve', 'net.ve',
    'org.ve', 'co.ve', 'edu.ve', 'gov.ve', 'mil.ve', 'arts.ve', 'bib.ve',
    'firm.ve', 'info.ve', 'int.ve', 'nom.ve', 'rec.ve', 'store.ve',
    'tec.ve', 'web.ve', 'co.vi', 'net.vi', 'org.vi', 'com.vn', 'biz.vn',
    'edu.vn', 'gov.vn', 'net.vn', 'org.vn', 'int.vn', 'ac.vn', 'pro.vn',
    'info.vn', 'health.vn', 'name.vn', 'com.vu', 'edu.vu', 'net.vu',
    'org.vu', 'de.vu', 'ch.vu', 'fr.vu', 'com.ws', 'net.ws', 'org.ws',
    'gov.ws', 'edu.ws', 'ac.yu', 'co.yu', 'edu.yu', 'org.yu', 'com.ye',
    'net.ye', 'org.ye', 'gov.ye', 'edu.ye', 'mil.ye', 'ac.za', 'alt.za',
    'bourse.za', 'city.za', 'co.za', 'edu.za', 'gov.za', 'law.za', 'mil.za',
    'net.za', 'ngo.za', 'nom.za', 'org.za', 'school.za', 'tm.za', 'web.za',
    'co.zw', 'ac.zw', 'org.zw', 'gov.zw', 'eu.org', 'au.com', 'br.com',
    'cn.com', 'de.com', 'de.net', 'eu.com', 'gb.com', 'gb.net', 'hu.com',
    'no.com', 'qc.com', 'ru.com', 'sa.com', 'se.com', 'uk.com', 'uk.net',
    'us.com', 'uy.com', 'za.com', 'dk.org', 'tel.no', 'fax.nr', 'mob.nr',
    'mobil.nr', 'mobile.nr', 'tel.nr', 'tlf.nr', 'e164.arpa'];

// ------------------------------------------ access to user preferences

const PrefBranch = function(prefix) {
    const service = XPS('preferences-service;1', XPI.nsIPrefService);
    const branch = service.getBranch(prefix);

    this.set = function(path, value) {
        switch (typeof value) {
            case 'number':
                branch.setIntPref(path, value);
                break;
            case 'boolean':
                branch.setBoolPref(path, value);
                break;
            default:
                branch.setCharPref(path, value.toString());
                break;
        }
    }

    this.get = function(path, value) {
        try {
            switch (branch.getPrefType(path)) {
                case 32:
                    return branch.getCharPref(path);
                case 64:
                    return branch.getIntPref(path);
                case 128:
                    return branch.getBoolPref(path);
            }
        } catch (error) {
        }
        return value;
    }

    this.del = function(path) {
        try {
            branch.clearUserPref(path);
        } catch(error) {
        }
    }
}

// ----------------------------------- implementation of the Passpet service

const service = function() {
    // Create all the persona objects at startup to avoid duplicates.
    var personas = {};

    // First get a list of all the saved persona files.
    const folder = XPS('@passpet.org/folder;1', XPI.IPasspetFolder);
    var filenames = folder.list().split('\0');
    var savedPids = {};
    for (var i = 0; i < filenames.length; i++) {
        if (filenames[i].substr(-3) == '.pp') {
            var pid = filenames[i].substring(0, filenames[i].length - 3);
            savedPids[pid] = 1;
        }
    }

    // Then load personas in the order they appear in passpet.pids.
    const prefs = new PrefBranch('passpet.');
    var listedPids = prefs.get('pids', '').split(' ');
    var pids = [];
    for (var i = 0; i < listedPids.length; i++) {
        if (listedPids[i] in savedPids) {
            var persona = XPO('@passpet.org/persona;1', XPI.IPasspetPersona);
            persona.load(listedPids[i]);
            personas[listedPids[i]] = persona;
            delete savedPids[listedPids[i]];
            pids.push(listedPids[i]);
            var pid = listedPids[i];
        }
    }

    // Then add any other saved personas to the end of the list.
    for (var pid in savedPids) {
        var persona = XPO('@passpet.org/persona;1', XPI.IPasspetPersona);
        persona.load(pid);
        personas[pid] = persona;
        pids.push(pid);
    }

    // Finally, save the list of existing personas.
    prefs.set('pids', pids.join(' '));

    // Get the list of existing persona IDs in creation order.
    this.pidList getter = function() { return pids.join(' '); }

    // Get an existing Passpet persona by its persona ID.
    this.getPersona = function(pid) { return personas[pid]; }

    function getRandomChar(s) {
        return s.charAt(Math.random() * s.length);
    }

    function makeNewPid() {
        const PID_CHARS = '0123456789';
        var pid;
        do {
            pid = '';
            for (var i = 0; i < 8; i++) pid += getRandomChar(PID_CHARS);
        } while (folder.contains(pid + '.pp'));
        return pid;
    }

    // Create a new Passpet persona and return its persona ID.
    this.createPersona = function(address, name, iconName, icon, width) {
        var persona = XPO('@passpet.org/persona;1', XPI.IPasspetPersona);
        var pid = makeNewPid();
        persona.create(pid, address, name, iconName, icon, width);
        personas[pid] = persona;
        pids.push(pid);
        prefs.set('pids', pids.join(' '));
        return pid;
    }

    // Destroy a Passpet persona identified by its persona ID.
    this.deletePersona = function(pid) {
        folder.remove(pid + '.pp');
        delete personas[pid];
        for (var i = 0; i < pids.length; i++) {
            if (pids[i] == pid) {
                pids.splice(i, 1);
                break;
            }
        }
        prefs.set('pids', pids.join(' '));
    }

    // Get the authoritative part of a domain name.
    function getDomainAuthority(domain) {
        var parts = domain.split('.');
        if (parts.length < 3) return domain;
        var lastTwo = parts[parts.length - 2] + '.' + parts[parts.length - 1];
        for (var i = 0; i < twoPartTLDs.length; i++) {
            if (lastTwo == twoPartTLDs[i]) {
                return parts[parts.length - 3] + '.' + lastTwo;
            }
        }
        return lastTwo;
    };

    // Get a string identifying the current site.
    this.getSiteIdentifier = function(shell) {
        var docshell = XQI(shell, XPI.nsIDocShell);
        var status = XQI(docshell.securityUI, XPI.nsISSLStatusProvider);
        if (status.SSLStatus == null) {
            // If there is no SSL information, use the URL.
            var uri = XQI(shell, XPI.nsIWebNavigation).currentURI;
            if (uri.scheme in {http: 1, https: 1}) {
                return '/D=' + getDomainAuthority(uri.host);
            } else if (uri.scheme in {file: 1, about: 1}) {
                return 'local';
            } else {
                return '';
            }
        }
        // Get the server certificate and root CA certificate.
        var cert = XQI(status.SSLStatus, XPI.nsISSLStatus).serverCert;
        for (var root = cert; root.getChain().length > 1; root = root.issuer);
        // Construct the site identifier.
        var fingerprint = root.sha1Fingerprint.replace(/:/g, '').toLowerCase();
        prefs.set('ca.' + fingerprint, root.organization);
        return fingerprint + (cert.organization ?
            '/O=' + cert.organization : '/CN=' + cert.commonName);
    }

    this.getCAName = function(fingerprint) {
        return prefs.get('ca.' + fingerprint);
    }

    // This method implements the nsISupports interface.
    this.QueryInterface = function(iid) {
        if (iid.equals(XPI.IPasspet)) return this;
        if (iid.equals(XPI.nsISupports)) return this;
        throw XPR.NS_ERROR_NO_INTERFACE;
    };
};

