/* 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 "nsCertTree.h"

#include "ScopedNSSTypes.h"
#include "mozilla/Logging.h"
#include "mozilla/Maybe.h"
#include "mozilla/intl/AppDateTimeFormat.h"
#include "nsArray.h"
#include "nsArrayUtils.h"
#include "nsHashKeys.h"
#include "nsISupportsPrimitives.h"
#include "nsIX509CertDB.h"
#include "nsIX509Cert.h"
#include "nsIX509CertValidity.h"
#include "nsNSSCertHelper.h"
#include "nsNSSCertificate.h"
#include "nsComponentManagerUtils.h"
#include "nsNSSCertificateDB.h"
#include "nsReadableUtils.h"
#include "nsTHashtable.h"
#include "nsUnicharUtils.h"
#include "nsXPCOMCID.h"
#include "nsString.h"
#include "nsTreeColumns.h"
#include "mozpkix/pkixtypes.h"

using namespace mozilla;

extern LazyLogModule gPIPNSSLog;

// treeArrayElStr
//
// structure used to hold map of tree.  Each thread (an organization
// field from a cert) has an element in the array.  The numChildren field
// stores the number of certs corresponding to that thread.
struct treeArrayElStr {
  nsString orgName;    /* heading for thread                   */
  bool open;           /* toggle open state for thread         */
  int32_t certIndex;   /* index into cert array for 1st cert   */
  int32_t numChildren; /* number of chidren (certs) for thread */
};

CompareCacheEntry::CompareCacheEntry() : mCritInit() {
  for (int i = 0; i < max_criterions; ++i) {
    mCritInit[i] = false;
    mCrit[i].SetIsVoid(true);
  }
}

NS_IMPL_ISUPPORTS(nsCertTreeDispInfo, nsICertTreeItem)

nsCertTreeDispInfo::~nsCertTreeDispInfo() = default;

NS_IMETHODIMP
nsCertTreeDispInfo::GetCert(nsIX509Cert** aCert) {
  NS_ENSURE_ARG(aCert);
  nsCOMPtr<nsIX509Cert> cert = mCert;
  cert.forget(aCert);
  return NS_OK;
}

NS_IMPL_ISUPPORTS(nsCertTree, nsICertTree, nsITreeView)

nsCertTree::nsCertTree()
    : mTreeArray(nullptr),
      mNumOrgs(0),
      mNumRows(0),
      mCompareCache(kInitialCacheLength) {
  mCellText = nullptr;
}

void nsCertTree::ClearCompareHash() { mCompareCache.Clear(); }

nsCertTree::~nsCertTree() { delete[] mTreeArray; }

void nsCertTree::FreeCertArray() { mDispInfo.Clear(); }

CompareCacheEntry* nsCertTree::getCacheEntry(CompareCache* aCache,
                                             nsIX509Cert* aCert) {
  return aCache
      ->LookupOrInsertWith(aCert,
                           [] { return std::make_unique<CompareCacheEntry>(); })
      .get();
}

void nsCertTree::RemoveCacheEntry(nsIX509Cert* key) {
  mCompareCache.Remove(key);
}

// CountOrganizations
//
// Count the number of different organizations encountered in the cert
// list.
int32_t nsCertTree::CountOrganizations() {
  uint32_t i, certCount;
  certCount = mDispInfo.Length();
  if (certCount == 0) return 0;
  nsCOMPtr<nsIX509Cert> orgCert = mDispInfo.ElementAt(0)->mCert;
  nsCOMPtr<nsIX509Cert> nextCert = nullptr;
  int32_t orgCount = 1;
  for (i = 1; i < certCount; i++) {
    nextCert = mDispInfo.SafeElementAt(i, nullptr)->mCert;
    // XXX we assume issuer org is always criterion 1
    if (CmpBy(&mCompareCache, orgCert, nextCert, sort_IssuerOrg, sort_None,
              sort_None) != 0) {
      orgCert = nextCert;
      orgCount++;
    }
  }
  return orgCount;
}

// GetThreadDescAtIndex
//
// If the row at index is an organization thread, return the collection
// associated with that thread.  Otherwise, return null.
treeArrayEl* nsCertTree::GetThreadDescAtIndex(int32_t index) {
  int i, idx = 0;
  if (index < 0) return nullptr;
  for (i = 0; i < mNumOrgs; i++) {
    if (index == idx) {
      return &mTreeArray[i];
    }
    if (mTreeArray[i].open) {
      idx += mTreeArray[i].numChildren;
    }
    idx++;
    if (idx > index) break;
  }
  return nullptr;
}

//  GetCertAtIndex
//
//  If the row at index is a cert, return that cert.  Otherwise, return null.
already_AddRefed<nsIX509Cert> nsCertTree::GetCertAtIndex(
    int32_t index, int32_t* outAbsoluteCertOffset) {
  RefPtr<nsCertTreeDispInfo> certdi(
      GetDispInfoAtIndex(index, outAbsoluteCertOffset));
  if (!certdi) return nullptr;

  nsCOMPtr<nsIX509Cert> ret = certdi->mCert;
  return ret.forget();
}

//  If the row at index is a cert, return that cert.  Otherwise, return null.
already_AddRefed<nsCertTreeDispInfo> nsCertTree::GetDispInfoAtIndex(
    int32_t index, int32_t* outAbsoluteCertOffset) {
  int i, idx = 0, cIndex = 0, nc;
  if (index < 0) return nullptr;
  // Loop over the threads
  for (i = 0; i < mNumOrgs; i++) {
    if (index == idx) return nullptr;  // index is for thread
    idx++;                             // get past the thread
    nc = (mTreeArray[i].open) ? mTreeArray[i].numChildren : 0;
    if (index < idx + nc) {  // cert is within range of this thread
      int32_t certIndex = cIndex + index - idx;
      if (outAbsoluteCertOffset) *outAbsoluteCertOffset = certIndex;
      RefPtr<nsCertTreeDispInfo> certdi(
          mDispInfo.SafeElementAt(certIndex, nullptr));
      if (certdi) {
        return certdi.forget();
      }
      break;
    }
    if (mTreeArray[i].open) idx += mTreeArray[i].numChildren;
    cIndex += mTreeArray[i].numChildren;
    if (idx > index) break;
  }
  return nullptr;
}

nsCertTree::nsCertCompareFunc nsCertTree::GetCompareFuncFromCertType(
    uint32_t aType) {
  switch (aType) {
    case nsIX509Cert::ANY_CERT:
    case nsIX509Cert::USER_CERT:
      return CmpUserCert;
    case nsIX509Cert::EMAIL_CERT:
      return CmpEmailCert;
    case nsIX509Cert::CA_CERT:
    default:
      return CmpCACert;
  }
}

nsresult nsCertTree::GetCertsByTypeFromCertList(
    const nsTArray<RefPtr<nsIX509Cert>>& aCertList, uint32_t aWantedType,
    nsCertCompareFunc aCertCmpFn, CompareCache* aCertCmpFnArg) {
  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetCertsByTypeFromCertList"));

  nsTHashtable<nsCStringHashKey> allHostPortOverrideKeys;

  if (aWantedType == nsIX509Cert::SERVER_CERT) {
    return NS_ERROR_INVALID_ARG;
  }

  int count = 0;
  for (const auto& cert : aCertList) {
    bool wantThisCert = (aWantedType == nsIX509Cert::ANY_CERT);

    if (!wantThisCert) {
      uint32_t thisCertType;
      nsresult rv = cert->GetCertType(&thisCertType);
      if (NS_FAILED(rv)) {
        return rv;
      }
      if (thisCertType == aWantedType) {
        wantThisCert = true;
      }
    }

    if (wantThisCert) {
      int InsertPosition = 0;
      for (; InsertPosition < count; ++InsertPosition) {
        nsCOMPtr<nsIX509Cert> otherCert = nullptr;
        RefPtr<nsCertTreeDispInfo> elem(
            mDispInfo.SafeElementAt(InsertPosition, nullptr));
        if (elem) {
          otherCert = elem->mCert;
        }
        if ((*aCertCmpFn)(aCertCmpFnArg, cert, otherCert) < 0) {
          break;
        }
      }
      nsCertTreeDispInfo* certdi = new nsCertTreeDispInfo(cert);
      mDispInfo.InsertElementAt(InsertPosition, certdi);
      ++count;
      ++InsertPosition;
    }
  }

  return NS_OK;
}

// LoadCerts
//
// Load all of the certificates in the DB for this type.  Sort them
// by token, organization, then common name.
NS_IMETHODIMP
nsCertTree::LoadCertsFromCache(const nsTArray<RefPtr<nsIX509Cert>>& aCache,
                               uint32_t aType) {
  if (mTreeArray) {
    FreeCertArray();
    delete[] mTreeArray;
    mTreeArray = nullptr;
    mNumRows = 0;
  }
  ClearCompareHash();

  nsresult rv = GetCertsByTypeFromCertList(
      aCache, aType, GetCompareFuncFromCertType(aType), &mCompareCache);
  if (NS_FAILED(rv)) {
    return rv;
  }
  return UpdateUIContents();
}

nsresult nsCertTree::UpdateUIContents() {
  uint32_t count = mDispInfo.Length();
  mNumOrgs = CountOrganizations();
  mTreeArray = new treeArrayEl[mNumOrgs];

  mCellText = nsArrayBase::Create();

  if (count) {
    uint32_t j = 0;
    nsCOMPtr<nsIX509Cert> orgCert = mDispInfo.ElementAt(j)->mCert;
    for (int32_t i = 0; i < mNumOrgs; i++) {
      nsString& orgNameRef = mTreeArray[i].orgName;
      if (!orgCert) {
        GetPIPNSSBundleString("CertOrgUnknown", orgNameRef);
      } else {
        orgCert->GetIssuerOrganization(orgNameRef);
        if (orgNameRef.IsEmpty()) orgCert->GetCommonName(orgNameRef);
      }
      mTreeArray[i].open = true;
      mTreeArray[i].certIndex = j;
      mTreeArray[i].numChildren = 1;
      if (++j >= count) break;
      nsCOMPtr<nsIX509Cert> nextCert =
          mDispInfo.SafeElementAt(j, nullptr)->mCert;
      while (0 == CmpBy(&mCompareCache, orgCert, nextCert, sort_IssuerOrg,
                        sort_None, sort_None)) {
        mTreeArray[i].numChildren++;
        if (++j >= count) break;
        nextCert = mDispInfo.SafeElementAt(j, nullptr)->mCert;
      }
      orgCert = nextCert;
    }
  }
  if (mTree) {
    mTree->BeginUpdateBatch();
    mTree->RowCountChanged(0, -mNumRows);
  }
  mNumRows = count + mNumOrgs;
  if (mTree) mTree->EndUpdateBatch();
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::DeleteEntryObject(uint32_t index) {
  if (!mTreeArray) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIX509CertDB> certdb =
      do_GetService("@mozilla.org/security/x509certdb;1");
  if (!certdb) {
    return NS_ERROR_FAILURE;
  }

  int i;
  uint32_t idx = 0, cIndex = 0, nc;
  // Loop over the threads
  for (i = 0; i < mNumOrgs; i++) {
    if (index == idx) return NS_OK;  // index is for thread
    idx++;                           // get past the thread
    nc = (mTreeArray[i].open) ? mTreeArray[i].numChildren : 0;
    if (index < idx + nc) {  // cert is within range of this thread
      int32_t certIndex = cIndex + index - idx;

      RefPtr<nsCertTreeDispInfo> certdi(
          mDispInfo.SafeElementAt(certIndex, nullptr));
      if (certdi) {
        nsCOMPtr<nsIX509Cert> cert = certdi->mCert;
        RemoveCacheEntry(cert);
        certdb->DeleteCertificate(cert);
      }

      mDispInfo.RemoveElementAt(certIndex);

      delete[] mTreeArray;
      mTreeArray = nullptr;
      return UpdateUIContents();
    }
    if (mTreeArray[i].open) idx += mTreeArray[i].numChildren;
    cIndex += mTreeArray[i].numChildren;
    if (idx > index) break;
  }
  return NS_ERROR_FAILURE;
}

//////////////////////////////////////////////////////////////////////////////
//
//  Begin nsITreeView methods
//
/////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
nsCertTree::GetCert(uint32_t aIndex, nsIX509Cert** _cert) {
  NS_ENSURE_ARG(_cert);
  *_cert = GetCertAtIndex(aIndex).take();
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetTreeItem(uint32_t aIndex, nsICertTreeItem** _treeitem) {
  NS_ENSURE_ARG(_treeitem);

  RefPtr<nsCertTreeDispInfo> certdi(GetDispInfoAtIndex(aIndex));
  if (!certdi) return NS_ERROR_FAILURE;

  *_treeitem = certdi;
  NS_IF_ADDREF(*_treeitem);
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetRowCount(int32_t* aRowCount) {
  if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED;
  uint32_t count = 0;
  for (int32_t i = 0; i < mNumOrgs; i++) {
    if (mTreeArray[i].open) {
      count += mTreeArray[i].numChildren;
    }
    count++;
  }
  *aRowCount = count;
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetSelection(nsITreeSelection** aSelection) {
  *aSelection = mSelection;
  NS_IF_ADDREF(*aSelection);
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::SetSelection(nsITreeSelection* aSelection) {
  mSelection = aSelection;
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetRowProperties(int32_t index, nsAString& aProps) { return NS_OK; }

NS_IMETHODIMP
nsCertTree::GetCellProperties(int32_t row, nsTreeColumn* col,
                              nsAString& aProps) {
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetColumnProperties(nsTreeColumn* col, nsAString& aProps) {
  return NS_OK;
}
NS_IMETHODIMP
nsCertTree::IsContainer(int32_t index, bool* _retval) {
  if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED;
  treeArrayEl* el = GetThreadDescAtIndex(index);
  if (el) {
    *_retval = true;
  } else {
    *_retval = false;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::IsContainerOpen(int32_t index, bool* _retval) {
  if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED;
  treeArrayEl* el = GetThreadDescAtIndex(index);
  if (el && el->open) {
    *_retval = true;
  } else {
    *_retval = false;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::IsContainerEmpty(int32_t index, bool* _retval) {
  *_retval = !mTreeArray;
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::IsSeparator(int32_t index, bool* _retval) {
  *_retval = false;
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetParentIndex(int32_t rowIndex, int32_t* _retval) {
  if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED;
  int i, idx = 0;
  for (i = 0; i < mNumOrgs && idx < rowIndex; i++, idx++) {
    if (mTreeArray[i].open) {
      if (rowIndex <= idx + mTreeArray[i].numChildren) {
        *_retval = idx;
        return NS_OK;
      }
      idx += mTreeArray[i].numChildren;
    }
  }
  *_retval = -1;
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::HasNextSibling(int32_t rowIndex, int32_t afterIndex,
                           bool* _retval) {
  if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED;

  int i, idx = 0;
  for (i = 0; i < mNumOrgs && idx <= rowIndex; i++, idx++) {
    if (mTreeArray[i].open) {
      idx += mTreeArray[i].numChildren;
      if (afterIndex <= idx) {
        *_retval = afterIndex < idx;
        return NS_OK;
      }
    }
  }
  *_retval = false;
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetLevel(int32_t index, int32_t* _retval) {
  if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED;
  treeArrayEl* el = GetThreadDescAtIndex(index);
  if (el) {
    *_retval = 0;
  } else {
    *_retval = 1;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetImageSrc(int32_t row, nsTreeColumn* col, nsAString& _retval) {
  _retval.Truncate();
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::GetCellValue(int32_t row, nsTreeColumn* col, nsAString& _retval) {
  _retval.Truncate();
  return NS_OK;
}

static void PRTimeToLocalDateString(PRTime time, nsAString& result) {
  PRExplodedTime explodedTime;
  PR_ExplodeTime(time, PR_LocalTimeParameters, &explodedTime);
  intl::DateTimeFormat::StyleBag style;
  style.date = Some(intl::DateTimeFormat::Style::Long);
  style.time = Nothing();
  (void)intl::AppDateTimeFormat::Format(style, &explodedTime, result);
}

NS_IMETHODIMP
nsCertTree::GetCellText(int32_t row, nsTreeColumn* col, nsAString& _retval) {
  if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED;

  nsresult rv = NS_OK;
  _retval.Truncate();

  const nsAString& colID = col->GetId();

  treeArrayEl* el = GetThreadDescAtIndex(row);
  if (el) {
    if (u"certcol"_ns.Equals(colID))
      _retval.Assign(el->orgName);
    else
      _retval.Truncate();
    return NS_OK;
  }

  int32_t absoluteCertOffset;
  RefPtr<nsCertTreeDispInfo> certdi(
      GetDispInfoAtIndex(row, &absoluteCertOffset));
  if (!certdi) return NS_ERROR_FAILURE;

  nsCOMPtr<nsIX509Cert> cert = certdi->mCert;

  int32_t colIndex = col->Index();
  uint32_t arrayIndex = absoluteCertOffset + colIndex * (mNumRows - mNumOrgs);
  uint32_t arrayLength = 0;
  if (mCellText) {
    mCellText->GetLength(&arrayLength);
  }
  if (arrayIndex < arrayLength) {
    nsCOMPtr<nsISupportsString> myString(
        do_QueryElementAt(mCellText, arrayIndex));
    if (myString) {
      myString->GetData(_retval);
      return NS_OK;
    }
  }

  if (u"certcol"_ns.Equals(colID)) {
    if (!cert) {
      rv = GetPIPNSSBundleString("CertNotStored", _retval);
    } else {
      rv = cert->GetDisplayName(_retval);
    }
  } else if (u"tokencol"_ns.Equals(colID) && cert) {
    rv = cert->GetTokenName(_retval);
  } else if (u"emailcol"_ns.Equals(colID) && cert) {
    rv = cert->GetEmailAddress(_retval);
  } else if (u"issuedcol"_ns.Equals(colID) && cert) {
    nsCOMPtr<nsIX509CertValidity> validity;

    rv = cert->GetValidity(getter_AddRefs(validity));
    if (NS_SUCCEEDED(rv)) {
      PRTime notBefore;
      rv = validity->GetNotBefore(&notBefore);
      if (NS_SUCCEEDED(rv)) {
        PRTimeToLocalDateString(notBefore, _retval);
      }
    }
  } else if (u"expiredcol"_ns.Equals(colID) && cert) {
    nsCOMPtr<nsIX509CertValidity> validity;

    rv = cert->GetValidity(getter_AddRefs(validity));
    if (NS_SUCCEEDED(rv)) {
      PRTime notAfter;
      rv = validity->GetNotAfter(&notAfter);
      if (NS_SUCCEEDED(rv)) {
        PRTimeToLocalDateString(notAfter, _retval);
      }
    }
  } else if (u"serialnumcol"_ns.Equals(colID) && cert) {
    rv = cert->GetSerialNumber(_retval);
  } else {
    return NS_ERROR_FAILURE;
  }
  if (mCellText) {
    nsCOMPtr<nsISupportsString> text(
        do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    text->SetData(_retval);
    mCellText->ReplaceElementAt(text, arrayIndex);
  }
  return rv;
}

NS_IMETHODIMP
nsCertTree::SetTree(mozilla::dom::XULTreeElement* tree) {
  mTree = tree;
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::ToggleOpenState(int32_t index) {
  if (!mTreeArray) return NS_ERROR_NOT_INITIALIZED;
  treeArrayEl* el = GetThreadDescAtIndex(index);
  if (el) {
    el->open = !el->open;
    int32_t newChildren = (el->open) ? el->numChildren : -el->numChildren;
    if (mTree) {
      mTree->RowCountChanged(index + 1, newChildren);
      mTree->InvalidateRow(index);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::CycleHeader(nsTreeColumn* col) { return NS_OK; }

NS_IMETHODIMP
nsCertTree::SelectionChangedXPCOM() { return NS_ERROR_NOT_IMPLEMENTED; }

NS_IMETHODIMP
nsCertTree::CycleCell(int32_t row, nsTreeColumn* col) { return NS_OK; }

NS_IMETHODIMP
nsCertTree::IsEditable(int32_t row, nsTreeColumn* col, bool* _retval) {
  *_retval = false;
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::SetCellValue(int32_t row, nsTreeColumn* col,
                         const nsAString& value) {
  return NS_OK;
}

NS_IMETHODIMP
nsCertTree::SetCellText(int32_t row, nsTreeColumn* col,
                        const nsAString& value) {
  return NS_OK;
}

//
// CanDrop
//
NS_IMETHODIMP nsCertTree::CanDrop(int32_t index, int32_t orientation,
                                  mozilla::dom::DataTransfer* aDataTransfer,
                                  bool* _retval) {
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = false;

  return NS_OK;
}

//
// Drop
//
NS_IMETHODIMP nsCertTree::Drop(int32_t row, int32_t orient,
                               mozilla::dom::DataTransfer* aDataTransfer) {
  return NS_OK;
}

//
// IsSorted
//
// ...
//
NS_IMETHODIMP nsCertTree::IsSorted(bool* _retval) {
  *_retval = false;
  return NS_OK;
}

#define RETURN_NOTHING

void nsCertTree::CmpInitCriterion(nsIX509Cert* cert, CompareCacheEntry* entry,
                                  sortCriterion crit, int32_t level) {
  NS_ENSURE_TRUE(cert && entry, RETURN_NOTHING);

  entry->mCritInit[level] = true;
  nsString& str = entry->mCrit[level];

  switch (crit) {
    case sort_IssuerOrg:
      cert->GetIssuerOrganization(str);
      if (str.IsEmpty()) cert->GetCommonName(str);
      break;
    case sort_Org:
      cert->GetOrganization(str);
      break;
    case sort_Token:
      cert->GetTokenName(str);
      break;
    case sort_CommonName:
      cert->GetCommonName(str);
      break;
    case sort_IssuedDateDescending: {
      nsresult rv;
      nsCOMPtr<nsIX509CertValidity> validity;
      PRTime notBefore;

      rv = cert->GetValidity(getter_AddRefs(validity));
      if (NS_SUCCEEDED(rv)) {
        rv = validity->GetNotBefore(&notBefore);
      }

      if (NS_SUCCEEDED(rv)) {
        PRExplodedTime explodedTime;
        PR_ExplodeTime(notBefore, PR_GMTParameters, &explodedTime);
        char datebuf[20];  // 4 + 2 + 2 + 2 + 2 + 2 + 1 = 15
        if (0 != PR_FormatTime(datebuf, sizeof(datebuf), "%Y%m%d%H%M%S",
                               &explodedTime)) {
          str = NS_ConvertASCIItoUTF16(nsDependentCString(datebuf));
        }
      }
    } break;
    case sort_Email:
      cert->GetEmailAddress(str);
      break;
    case sort_None:
    default:
      break;
  }
}

int32_t nsCertTree::CmpByCrit(nsIX509Cert* a, CompareCacheEntry* ace,
                              nsIX509Cert* b, CompareCacheEntry* bce,
                              sortCriterion crit, int32_t level) {
  NS_ENSURE_TRUE(a && ace && b && bce, 0);

  if (!ace->mCritInit[level]) {
    CmpInitCriterion(a, ace, crit, level);
  }

  if (!bce->mCritInit[level]) {
    CmpInitCriterion(b, bce, crit, level);
  }

  nsString& str_a = ace->mCrit[level];
  nsString& str_b = bce->mCrit[level];

  int32_t result;
  if (!str_a.IsVoid() && !str_b.IsVoid())
    result = Compare(str_a, str_b, nsCaseInsensitiveStringComparator);
  else
    result = str_a.IsVoid() ? (str_b.IsVoid() ? 0 : -1) : 1;

  if (sort_IssuedDateDescending == crit) result *= -1;  // reverse compare order

  return result;
}

int32_t nsCertTree::CmpBy(CompareCache* cache, nsIX509Cert* a, nsIX509Cert* b,
                          sortCriterion c0, sortCriterion c1,
                          sortCriterion c2) {
  // This will be called when comparing items for display sorting.
  // Some items might have no cert associated, so either a or b is null.
  // We want all those orphans show at the top of the list,
  // so we treat a null cert as "smaller" by returning -1.
  // We don't try to sort within the group of no-cert entries,
  // so we treat them as equal wrt sort order.

  if (!a && !b) return 0;

  if (!a) return -1;

  if (!b) return 1;

  NS_ENSURE_TRUE(cache && a && b, 0);

  CompareCacheEntry* ace = getCacheEntry(cache, a);
  CompareCacheEntry* bce = getCacheEntry(cache, b);

  int32_t cmp;
  cmp = CmpByCrit(a, ace, b, bce, c0, 0);
  if (cmp != 0) return cmp;

  if (c1 != sort_None) {
    cmp = CmpByCrit(a, ace, b, bce, c1, 1);
    if (cmp != 0) return cmp;

    if (c2 != sort_None) {
      return CmpByCrit(a, ace, b, bce, c2, 2);
    }
  }

  return cmp;
}

int32_t nsCertTree::CmpCACert(CompareCache* cache, nsIX509Cert* a,
                              nsIX509Cert* b) {
  // XXX we assume issuer org is always criterion 1
  return CmpBy(cache, a, b, sort_IssuerOrg, sort_Org, sort_Token);
}

int32_t nsCertTree::CmpUserCert(CompareCache* cache, nsIX509Cert* a,
                                nsIX509Cert* b) {
  // XXX we assume issuer org is always criterion 1
  return CmpBy(cache, a, b, sort_IssuerOrg, sort_Token,
               sort_IssuedDateDescending);
}

int32_t nsCertTree::CmpEmailCert(CompareCache* cache, nsIX509Cert* a,
                                 nsIX509Cert* b) {
  // XXX we assume issuer org is always criterion 1
  return CmpBy(cache, a, b, sort_IssuerOrg, sort_Email, sort_CommonName);
}
