// HTML escaping for values interpolated into an innerHTML string.
//
// Every view in src/popup/views/ builds markup by string concatenation, so
// this is the only thing standing between a value the wallet did not author
// and the extension's own DOM. The values that reach it are attacker
// controlled by design: an ERC-20's symbol() and name() are whatever the
// contract chooses to return, an ENS name is whatever the resolver returns,
// and both arrive through the block explorer with no schema.
//
// It escapes both quote characters as well as the tag delimiters, because
// the popup interpolates into attribute values as well as into element
// text — copyableHtml() writes data-copy="..." and etherscanLinkHtml()
// writes href="...". A `<`/`>`-only escape leaves an unquoted-attribute
// break-out intact, and the round trip through a detached element's
// textContent that used to implement this was exactly that escape: the
// HTML serializer only escapes `&`, `<`, `>` and U+00A0 in a text node,
// since a text node has no idea it is about to be pasted inside quotes.
//
// Deliberately a pure string function with no DOM dependency: it is called
// on every rendered row, it is unit-testable without a document, and it
// cannot be affected by the state of a document that an attacker-supplied
// string has already been written into.

const HTML_ESCAPES = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&#39;",
};

// `&` is escaped first by virtue of being in the same pass: a sequential
// replace would re-escape the ampersands it had just introduced.
function escapeHtml(s) {
    if (s === null || s === undefined) return "";
    return String(s).replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
}

module.exports = {
    escapeHtml,
};
