11cb0ef41Sopenharmony_ci'use strict'
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ciconst crypto = require('crypto')
41cb0ef41Sopenharmony_ciconst { Minipass } = require('minipass')
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ciconst SPEC_ALGORITHMS = ['sha512', 'sha384', 'sha256']
71cb0ef41Sopenharmony_ciconst DEFAULT_ALGORITHMS = ['sha512']
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ci// TODO: this should really be a hardcoded list of algorithms we support,
101cb0ef41Sopenharmony_ci// rather than [a-z0-9].
111cb0ef41Sopenharmony_ciconst BASE64_REGEX = /^[a-z0-9+/]+(?:=?=?)$/i
121cb0ef41Sopenharmony_ciconst SRI_REGEX = /^([a-z0-9]+)-([^?]+)([?\S*]*)$/
131cb0ef41Sopenharmony_ciconst STRICT_SRI_REGEX = /^([a-z0-9]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)?$/
141cb0ef41Sopenharmony_ciconst VCHAR_REGEX = /^[\x21-\x7E]+$/
151cb0ef41Sopenharmony_ci
161cb0ef41Sopenharmony_ciconst getOptString = options => options?.length ? `?${options.join('?')}` : ''
171cb0ef41Sopenharmony_ci
181cb0ef41Sopenharmony_ciclass IntegrityStream extends Minipass {
191cb0ef41Sopenharmony_ci  #emittedIntegrity
201cb0ef41Sopenharmony_ci  #emittedSize
211cb0ef41Sopenharmony_ci  #emittedVerified
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_ci  constructor (opts) {
241cb0ef41Sopenharmony_ci    super()
251cb0ef41Sopenharmony_ci    this.size = 0
261cb0ef41Sopenharmony_ci    this.opts = opts
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_ci    // may be overridden later, but set now for class consistency
291cb0ef41Sopenharmony_ci    this.#getOptions()
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ci    // options used for calculating stream.  can't be changed.
321cb0ef41Sopenharmony_ci    if (opts?.algorithms) {
331cb0ef41Sopenharmony_ci      this.algorithms = [...opts.algorithms]
341cb0ef41Sopenharmony_ci    } else {
351cb0ef41Sopenharmony_ci      this.algorithms = [...DEFAULT_ALGORITHMS]
361cb0ef41Sopenharmony_ci    }
371cb0ef41Sopenharmony_ci    if (this.algorithm !== null && !this.algorithms.includes(this.algorithm)) {
381cb0ef41Sopenharmony_ci      this.algorithms.push(this.algorithm)
391cb0ef41Sopenharmony_ci    }
401cb0ef41Sopenharmony_ci
411cb0ef41Sopenharmony_ci    this.hashes = this.algorithms.map(crypto.createHash)
421cb0ef41Sopenharmony_ci  }
431cb0ef41Sopenharmony_ci
441cb0ef41Sopenharmony_ci  #getOptions () {
451cb0ef41Sopenharmony_ci    // For verification
461cb0ef41Sopenharmony_ci    this.sri = this.opts?.integrity ? parse(this.opts?.integrity, this.opts) : null
471cb0ef41Sopenharmony_ci    this.expectedSize = this.opts?.size
481cb0ef41Sopenharmony_ci
491cb0ef41Sopenharmony_ci    if (!this.sri) {
501cb0ef41Sopenharmony_ci      this.algorithm = null
511cb0ef41Sopenharmony_ci    } else if (this.sri.isHash) {
521cb0ef41Sopenharmony_ci      this.goodSri = true
531cb0ef41Sopenharmony_ci      this.algorithm = this.sri.algorithm
541cb0ef41Sopenharmony_ci    } else {
551cb0ef41Sopenharmony_ci      this.goodSri = !this.sri.isEmpty()
561cb0ef41Sopenharmony_ci      this.algorithm = this.sri.pickAlgorithm(this.opts)
571cb0ef41Sopenharmony_ci    }
581cb0ef41Sopenharmony_ci
591cb0ef41Sopenharmony_ci    this.digests = this.goodSri ? this.sri[this.algorithm] : null
601cb0ef41Sopenharmony_ci    this.optString = getOptString(this.opts?.options)
611cb0ef41Sopenharmony_ci  }
621cb0ef41Sopenharmony_ci
631cb0ef41Sopenharmony_ci  on (ev, handler) {
641cb0ef41Sopenharmony_ci    if (ev === 'size' && this.#emittedSize) {
651cb0ef41Sopenharmony_ci      return handler(this.#emittedSize)
661cb0ef41Sopenharmony_ci    }
671cb0ef41Sopenharmony_ci
681cb0ef41Sopenharmony_ci    if (ev === 'integrity' && this.#emittedIntegrity) {
691cb0ef41Sopenharmony_ci      return handler(this.#emittedIntegrity)
701cb0ef41Sopenharmony_ci    }
711cb0ef41Sopenharmony_ci
721cb0ef41Sopenharmony_ci    if (ev === 'verified' && this.#emittedVerified) {
731cb0ef41Sopenharmony_ci      return handler(this.#emittedVerified)
741cb0ef41Sopenharmony_ci    }
751cb0ef41Sopenharmony_ci
761cb0ef41Sopenharmony_ci    return super.on(ev, handler)
771cb0ef41Sopenharmony_ci  }
781cb0ef41Sopenharmony_ci
791cb0ef41Sopenharmony_ci  emit (ev, data) {
801cb0ef41Sopenharmony_ci    if (ev === 'end') {
811cb0ef41Sopenharmony_ci      this.#onEnd()
821cb0ef41Sopenharmony_ci    }
831cb0ef41Sopenharmony_ci    return super.emit(ev, data)
841cb0ef41Sopenharmony_ci  }
851cb0ef41Sopenharmony_ci
861cb0ef41Sopenharmony_ci  write (data) {
871cb0ef41Sopenharmony_ci    this.size += data.length
881cb0ef41Sopenharmony_ci    this.hashes.forEach(h => h.update(data))
891cb0ef41Sopenharmony_ci    return super.write(data)
901cb0ef41Sopenharmony_ci  }
911cb0ef41Sopenharmony_ci
921cb0ef41Sopenharmony_ci  #onEnd () {
931cb0ef41Sopenharmony_ci    if (!this.goodSri) {
941cb0ef41Sopenharmony_ci      this.#getOptions()
951cb0ef41Sopenharmony_ci    }
961cb0ef41Sopenharmony_ci    const newSri = parse(this.hashes.map((h, i) => {
971cb0ef41Sopenharmony_ci      return `${this.algorithms[i]}-${h.digest('base64')}${this.optString}`
981cb0ef41Sopenharmony_ci    }).join(' '), this.opts)
991cb0ef41Sopenharmony_ci    // Integrity verification mode
1001cb0ef41Sopenharmony_ci    const match = this.goodSri && newSri.match(this.sri, this.opts)
1011cb0ef41Sopenharmony_ci    if (typeof this.expectedSize === 'number' && this.size !== this.expectedSize) {
1021cb0ef41Sopenharmony_ci      /* eslint-disable-next-line max-len */
1031cb0ef41Sopenharmony_ci      const err = new Error(`stream size mismatch when checking ${this.sri}.\n  Wanted: ${this.expectedSize}\n  Found: ${this.size}`)
1041cb0ef41Sopenharmony_ci      err.code = 'EBADSIZE'
1051cb0ef41Sopenharmony_ci      err.found = this.size
1061cb0ef41Sopenharmony_ci      err.expected = this.expectedSize
1071cb0ef41Sopenharmony_ci      err.sri = this.sri
1081cb0ef41Sopenharmony_ci      this.emit('error', err)
1091cb0ef41Sopenharmony_ci    } else if (this.sri && !match) {
1101cb0ef41Sopenharmony_ci      /* eslint-disable-next-line max-len */
1111cb0ef41Sopenharmony_ci      const err = new Error(`${this.sri} integrity checksum failed when using ${this.algorithm}: wanted ${this.digests} but got ${newSri}. (${this.size} bytes)`)
1121cb0ef41Sopenharmony_ci      err.code = 'EINTEGRITY'
1131cb0ef41Sopenharmony_ci      err.found = newSri
1141cb0ef41Sopenharmony_ci      err.expected = this.digests
1151cb0ef41Sopenharmony_ci      err.algorithm = this.algorithm
1161cb0ef41Sopenharmony_ci      err.sri = this.sri
1171cb0ef41Sopenharmony_ci      this.emit('error', err)
1181cb0ef41Sopenharmony_ci    } else {
1191cb0ef41Sopenharmony_ci      this.#emittedSize = this.size
1201cb0ef41Sopenharmony_ci      this.emit('size', this.size)
1211cb0ef41Sopenharmony_ci      this.#emittedIntegrity = newSri
1221cb0ef41Sopenharmony_ci      this.emit('integrity', newSri)
1231cb0ef41Sopenharmony_ci      if (match) {
1241cb0ef41Sopenharmony_ci        this.#emittedVerified = match
1251cb0ef41Sopenharmony_ci        this.emit('verified', match)
1261cb0ef41Sopenharmony_ci      }
1271cb0ef41Sopenharmony_ci    }
1281cb0ef41Sopenharmony_ci  }
1291cb0ef41Sopenharmony_ci}
1301cb0ef41Sopenharmony_ci
1311cb0ef41Sopenharmony_ciclass Hash {
1321cb0ef41Sopenharmony_ci  get isHash () {
1331cb0ef41Sopenharmony_ci    return true
1341cb0ef41Sopenharmony_ci  }
1351cb0ef41Sopenharmony_ci
1361cb0ef41Sopenharmony_ci  constructor (hash, opts) {
1371cb0ef41Sopenharmony_ci    const strict = opts?.strict
1381cb0ef41Sopenharmony_ci    this.source = hash.trim()
1391cb0ef41Sopenharmony_ci
1401cb0ef41Sopenharmony_ci    // set default values so that we make V8 happy to
1411cb0ef41Sopenharmony_ci    // always see a familiar object template.
1421cb0ef41Sopenharmony_ci    this.digest = ''
1431cb0ef41Sopenharmony_ci    this.algorithm = ''
1441cb0ef41Sopenharmony_ci    this.options = []
1451cb0ef41Sopenharmony_ci
1461cb0ef41Sopenharmony_ci    // 3.1. Integrity metadata (called "Hash" by ssri)
1471cb0ef41Sopenharmony_ci    // https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description
1481cb0ef41Sopenharmony_ci    const match = this.source.match(
1491cb0ef41Sopenharmony_ci      strict
1501cb0ef41Sopenharmony_ci        ? STRICT_SRI_REGEX
1511cb0ef41Sopenharmony_ci        : SRI_REGEX
1521cb0ef41Sopenharmony_ci    )
1531cb0ef41Sopenharmony_ci    if (!match) {
1541cb0ef41Sopenharmony_ci      return
1551cb0ef41Sopenharmony_ci    }
1561cb0ef41Sopenharmony_ci    if (strict && !SPEC_ALGORITHMS.includes(match[1])) {
1571cb0ef41Sopenharmony_ci      return
1581cb0ef41Sopenharmony_ci    }
1591cb0ef41Sopenharmony_ci    this.algorithm = match[1]
1601cb0ef41Sopenharmony_ci    this.digest = match[2]
1611cb0ef41Sopenharmony_ci
1621cb0ef41Sopenharmony_ci    const rawOpts = match[3]
1631cb0ef41Sopenharmony_ci    if (rawOpts) {
1641cb0ef41Sopenharmony_ci      this.options = rawOpts.slice(1).split('?')
1651cb0ef41Sopenharmony_ci    }
1661cb0ef41Sopenharmony_ci  }
1671cb0ef41Sopenharmony_ci
1681cb0ef41Sopenharmony_ci  hexDigest () {
1691cb0ef41Sopenharmony_ci    return this.digest && Buffer.from(this.digest, 'base64').toString('hex')
1701cb0ef41Sopenharmony_ci  }
1711cb0ef41Sopenharmony_ci
1721cb0ef41Sopenharmony_ci  toJSON () {
1731cb0ef41Sopenharmony_ci    return this.toString()
1741cb0ef41Sopenharmony_ci  }
1751cb0ef41Sopenharmony_ci
1761cb0ef41Sopenharmony_ci  match (integrity, opts) {
1771cb0ef41Sopenharmony_ci    const other = parse(integrity, opts)
1781cb0ef41Sopenharmony_ci    if (!other) {
1791cb0ef41Sopenharmony_ci      return false
1801cb0ef41Sopenharmony_ci    }
1811cb0ef41Sopenharmony_ci    if (other.isIntegrity) {
1821cb0ef41Sopenharmony_ci      const algo = other.pickAlgorithm(opts, [this.algorithm])
1831cb0ef41Sopenharmony_ci
1841cb0ef41Sopenharmony_ci      if (!algo) {
1851cb0ef41Sopenharmony_ci        return false
1861cb0ef41Sopenharmony_ci      }
1871cb0ef41Sopenharmony_ci
1881cb0ef41Sopenharmony_ci      const foundHash = other[algo].find(hash => hash.digest === this.digest)
1891cb0ef41Sopenharmony_ci
1901cb0ef41Sopenharmony_ci      if (foundHash) {
1911cb0ef41Sopenharmony_ci        return foundHash
1921cb0ef41Sopenharmony_ci      }
1931cb0ef41Sopenharmony_ci
1941cb0ef41Sopenharmony_ci      return false
1951cb0ef41Sopenharmony_ci    }
1961cb0ef41Sopenharmony_ci    return other.digest === this.digest ? other : false
1971cb0ef41Sopenharmony_ci  }
1981cb0ef41Sopenharmony_ci
1991cb0ef41Sopenharmony_ci  toString (opts) {
2001cb0ef41Sopenharmony_ci    if (opts?.strict) {
2011cb0ef41Sopenharmony_ci      // Strict mode enforces the standard as close to the foot of the
2021cb0ef41Sopenharmony_ci      // letter as it can.
2031cb0ef41Sopenharmony_ci      if (!(
2041cb0ef41Sopenharmony_ci        // The spec has very restricted productions for algorithms.
2051cb0ef41Sopenharmony_ci        // https://www.w3.org/TR/CSP2/#source-list-syntax
2061cb0ef41Sopenharmony_ci        SPEC_ALGORITHMS.includes(this.algorithm) &&
2071cb0ef41Sopenharmony_ci        // Usually, if someone insists on using a "different" base64, we
2081cb0ef41Sopenharmony_ci        // leave it as-is, since there's multiple standards, and the
2091cb0ef41Sopenharmony_ci        // specified is not a URL-safe variant.
2101cb0ef41Sopenharmony_ci        // https://www.w3.org/TR/CSP2/#base64_value
2111cb0ef41Sopenharmony_ci        this.digest.match(BASE64_REGEX) &&
2121cb0ef41Sopenharmony_ci        // Option syntax is strictly visual chars.
2131cb0ef41Sopenharmony_ci        // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-option-expression
2141cb0ef41Sopenharmony_ci        // https://tools.ietf.org/html/rfc5234#appendix-B.1
2151cb0ef41Sopenharmony_ci        this.options.every(opt => opt.match(VCHAR_REGEX))
2161cb0ef41Sopenharmony_ci      )) {
2171cb0ef41Sopenharmony_ci        return ''
2181cb0ef41Sopenharmony_ci      }
2191cb0ef41Sopenharmony_ci    }
2201cb0ef41Sopenharmony_ci    return `${this.algorithm}-${this.digest}${getOptString(this.options)}`
2211cb0ef41Sopenharmony_ci  }
2221cb0ef41Sopenharmony_ci}
2231cb0ef41Sopenharmony_ci
2241cb0ef41Sopenharmony_cifunction integrityHashToString (toString, sep, opts, hashes) {
2251cb0ef41Sopenharmony_ci  const toStringIsNotEmpty = toString !== ''
2261cb0ef41Sopenharmony_ci
2271cb0ef41Sopenharmony_ci  let shouldAddFirstSep = false
2281cb0ef41Sopenharmony_ci  let complement = ''
2291cb0ef41Sopenharmony_ci
2301cb0ef41Sopenharmony_ci  const lastIndex = hashes.length - 1
2311cb0ef41Sopenharmony_ci
2321cb0ef41Sopenharmony_ci  for (let i = 0; i < lastIndex; i++) {
2331cb0ef41Sopenharmony_ci    const hashString = Hash.prototype.toString.call(hashes[i], opts)
2341cb0ef41Sopenharmony_ci
2351cb0ef41Sopenharmony_ci    if (hashString) {
2361cb0ef41Sopenharmony_ci      shouldAddFirstSep = true
2371cb0ef41Sopenharmony_ci
2381cb0ef41Sopenharmony_ci      complement += hashString
2391cb0ef41Sopenharmony_ci      complement += sep
2401cb0ef41Sopenharmony_ci    }
2411cb0ef41Sopenharmony_ci  }
2421cb0ef41Sopenharmony_ci
2431cb0ef41Sopenharmony_ci  const finalHashString = Hash.prototype.toString.call(hashes[lastIndex], opts)
2441cb0ef41Sopenharmony_ci
2451cb0ef41Sopenharmony_ci  if (finalHashString) {
2461cb0ef41Sopenharmony_ci    shouldAddFirstSep = true
2471cb0ef41Sopenharmony_ci    complement += finalHashString
2481cb0ef41Sopenharmony_ci  }
2491cb0ef41Sopenharmony_ci
2501cb0ef41Sopenharmony_ci  if (toStringIsNotEmpty && shouldAddFirstSep) {
2511cb0ef41Sopenharmony_ci    return toString + sep + complement
2521cb0ef41Sopenharmony_ci  }
2531cb0ef41Sopenharmony_ci
2541cb0ef41Sopenharmony_ci  return toString + complement
2551cb0ef41Sopenharmony_ci}
2561cb0ef41Sopenharmony_ci
2571cb0ef41Sopenharmony_ciclass Integrity {
2581cb0ef41Sopenharmony_ci  get isIntegrity () {
2591cb0ef41Sopenharmony_ci    return true
2601cb0ef41Sopenharmony_ci  }
2611cb0ef41Sopenharmony_ci
2621cb0ef41Sopenharmony_ci  toJSON () {
2631cb0ef41Sopenharmony_ci    return this.toString()
2641cb0ef41Sopenharmony_ci  }
2651cb0ef41Sopenharmony_ci
2661cb0ef41Sopenharmony_ci  isEmpty () {
2671cb0ef41Sopenharmony_ci    return Object.keys(this).length === 0
2681cb0ef41Sopenharmony_ci  }
2691cb0ef41Sopenharmony_ci
2701cb0ef41Sopenharmony_ci  toString (opts) {
2711cb0ef41Sopenharmony_ci    let sep = opts?.sep || ' '
2721cb0ef41Sopenharmony_ci    let toString = ''
2731cb0ef41Sopenharmony_ci
2741cb0ef41Sopenharmony_ci    if (opts?.strict) {
2751cb0ef41Sopenharmony_ci      // Entries must be separated by whitespace, according to spec.
2761cb0ef41Sopenharmony_ci      sep = sep.replace(/\S+/g, ' ')
2771cb0ef41Sopenharmony_ci
2781cb0ef41Sopenharmony_ci      for (const hash of SPEC_ALGORITHMS) {
2791cb0ef41Sopenharmony_ci        if (this[hash]) {
2801cb0ef41Sopenharmony_ci          toString = integrityHashToString(toString, sep, opts, this[hash])
2811cb0ef41Sopenharmony_ci        }
2821cb0ef41Sopenharmony_ci      }
2831cb0ef41Sopenharmony_ci    } else {
2841cb0ef41Sopenharmony_ci      for (const hash of Object.keys(this)) {
2851cb0ef41Sopenharmony_ci        toString = integrityHashToString(toString, sep, opts, this[hash])
2861cb0ef41Sopenharmony_ci      }
2871cb0ef41Sopenharmony_ci    }
2881cb0ef41Sopenharmony_ci
2891cb0ef41Sopenharmony_ci    return toString
2901cb0ef41Sopenharmony_ci  }
2911cb0ef41Sopenharmony_ci
2921cb0ef41Sopenharmony_ci  concat (integrity, opts) {
2931cb0ef41Sopenharmony_ci    const other = typeof integrity === 'string'
2941cb0ef41Sopenharmony_ci      ? integrity
2951cb0ef41Sopenharmony_ci      : stringify(integrity, opts)
2961cb0ef41Sopenharmony_ci    return parse(`${this.toString(opts)} ${other}`, opts)
2971cb0ef41Sopenharmony_ci  }
2981cb0ef41Sopenharmony_ci
2991cb0ef41Sopenharmony_ci  hexDigest () {
3001cb0ef41Sopenharmony_ci    return parse(this, { single: true }).hexDigest()
3011cb0ef41Sopenharmony_ci  }
3021cb0ef41Sopenharmony_ci
3031cb0ef41Sopenharmony_ci  // add additional hashes to an integrity value, but prevent
3041cb0ef41Sopenharmony_ci  // *changing* an existing integrity hash.
3051cb0ef41Sopenharmony_ci  merge (integrity, opts) {
3061cb0ef41Sopenharmony_ci    const other = parse(integrity, opts)
3071cb0ef41Sopenharmony_ci    for (const algo in other) {
3081cb0ef41Sopenharmony_ci      if (this[algo]) {
3091cb0ef41Sopenharmony_ci        if (!this[algo].find(hash =>
3101cb0ef41Sopenharmony_ci          other[algo].find(otherhash =>
3111cb0ef41Sopenharmony_ci            hash.digest === otherhash.digest))) {
3121cb0ef41Sopenharmony_ci          throw new Error('hashes do not match, cannot update integrity')
3131cb0ef41Sopenharmony_ci        }
3141cb0ef41Sopenharmony_ci      } else {
3151cb0ef41Sopenharmony_ci        this[algo] = other[algo]
3161cb0ef41Sopenharmony_ci      }
3171cb0ef41Sopenharmony_ci    }
3181cb0ef41Sopenharmony_ci  }
3191cb0ef41Sopenharmony_ci
3201cb0ef41Sopenharmony_ci  match (integrity, opts) {
3211cb0ef41Sopenharmony_ci    const other = parse(integrity, opts)
3221cb0ef41Sopenharmony_ci    if (!other) {
3231cb0ef41Sopenharmony_ci      return false
3241cb0ef41Sopenharmony_ci    }
3251cb0ef41Sopenharmony_ci    const algo = other.pickAlgorithm(opts, Object.keys(this))
3261cb0ef41Sopenharmony_ci    return (
3271cb0ef41Sopenharmony_ci      !!algo &&
3281cb0ef41Sopenharmony_ci      this[algo] &&
3291cb0ef41Sopenharmony_ci      other[algo] &&
3301cb0ef41Sopenharmony_ci      this[algo].find(hash =>
3311cb0ef41Sopenharmony_ci        other[algo].find(otherhash =>
3321cb0ef41Sopenharmony_ci          hash.digest === otherhash.digest
3331cb0ef41Sopenharmony_ci        )
3341cb0ef41Sopenharmony_ci      )
3351cb0ef41Sopenharmony_ci    ) || false
3361cb0ef41Sopenharmony_ci  }
3371cb0ef41Sopenharmony_ci
3381cb0ef41Sopenharmony_ci  // Pick the highest priority algorithm present, optionally also limited to a
3391cb0ef41Sopenharmony_ci  // set of hashes found in another integrity.  When limiting it may return
3401cb0ef41Sopenharmony_ci  // nothing.
3411cb0ef41Sopenharmony_ci  pickAlgorithm (opts, hashes) {
3421cb0ef41Sopenharmony_ci    const pickAlgorithm = opts?.pickAlgorithm || getPrioritizedHash
3431cb0ef41Sopenharmony_ci    const keys = Object.keys(this).filter(k => {
3441cb0ef41Sopenharmony_ci      if (hashes?.length) {
3451cb0ef41Sopenharmony_ci        return hashes.includes(k)
3461cb0ef41Sopenharmony_ci      }
3471cb0ef41Sopenharmony_ci      return true
3481cb0ef41Sopenharmony_ci    })
3491cb0ef41Sopenharmony_ci    if (keys.length) {
3501cb0ef41Sopenharmony_ci      return keys.reduce((acc, algo) => pickAlgorithm(acc, algo) || acc)
3511cb0ef41Sopenharmony_ci    }
3521cb0ef41Sopenharmony_ci    // no intersection between this and hashes,
3531cb0ef41Sopenharmony_ci    return null
3541cb0ef41Sopenharmony_ci  }
3551cb0ef41Sopenharmony_ci}
3561cb0ef41Sopenharmony_ci
3571cb0ef41Sopenharmony_cimodule.exports.parse = parse
3581cb0ef41Sopenharmony_cifunction parse (sri, opts) {
3591cb0ef41Sopenharmony_ci  if (!sri) {
3601cb0ef41Sopenharmony_ci    return null
3611cb0ef41Sopenharmony_ci  }
3621cb0ef41Sopenharmony_ci  if (typeof sri === 'string') {
3631cb0ef41Sopenharmony_ci    return _parse(sri, opts)
3641cb0ef41Sopenharmony_ci  } else if (sri.algorithm && sri.digest) {
3651cb0ef41Sopenharmony_ci    const fullSri = new Integrity()
3661cb0ef41Sopenharmony_ci    fullSri[sri.algorithm] = [sri]
3671cb0ef41Sopenharmony_ci    return _parse(stringify(fullSri, opts), opts)
3681cb0ef41Sopenharmony_ci  } else {
3691cb0ef41Sopenharmony_ci    return _parse(stringify(sri, opts), opts)
3701cb0ef41Sopenharmony_ci  }
3711cb0ef41Sopenharmony_ci}
3721cb0ef41Sopenharmony_ci
3731cb0ef41Sopenharmony_cifunction _parse (integrity, opts) {
3741cb0ef41Sopenharmony_ci  // 3.4.3. Parse metadata
3751cb0ef41Sopenharmony_ci  // https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
3761cb0ef41Sopenharmony_ci  if (opts?.single) {
3771cb0ef41Sopenharmony_ci    return new Hash(integrity, opts)
3781cb0ef41Sopenharmony_ci  }
3791cb0ef41Sopenharmony_ci  const hashes = integrity.trim().split(/\s+/).reduce((acc, string) => {
3801cb0ef41Sopenharmony_ci    const hash = new Hash(string, opts)
3811cb0ef41Sopenharmony_ci    if (hash.algorithm && hash.digest) {
3821cb0ef41Sopenharmony_ci      const algo = hash.algorithm
3831cb0ef41Sopenharmony_ci      if (!acc[algo]) {
3841cb0ef41Sopenharmony_ci        acc[algo] = []
3851cb0ef41Sopenharmony_ci      }
3861cb0ef41Sopenharmony_ci      acc[algo].push(hash)
3871cb0ef41Sopenharmony_ci    }
3881cb0ef41Sopenharmony_ci    return acc
3891cb0ef41Sopenharmony_ci  }, new Integrity())
3901cb0ef41Sopenharmony_ci  return hashes.isEmpty() ? null : hashes
3911cb0ef41Sopenharmony_ci}
3921cb0ef41Sopenharmony_ci
3931cb0ef41Sopenharmony_cimodule.exports.stringify = stringify
3941cb0ef41Sopenharmony_cifunction stringify (obj, opts) {
3951cb0ef41Sopenharmony_ci  if (obj.algorithm && obj.digest) {
3961cb0ef41Sopenharmony_ci    return Hash.prototype.toString.call(obj, opts)
3971cb0ef41Sopenharmony_ci  } else if (typeof obj === 'string') {
3981cb0ef41Sopenharmony_ci    return stringify(parse(obj, opts), opts)
3991cb0ef41Sopenharmony_ci  } else {
4001cb0ef41Sopenharmony_ci    return Integrity.prototype.toString.call(obj, opts)
4011cb0ef41Sopenharmony_ci  }
4021cb0ef41Sopenharmony_ci}
4031cb0ef41Sopenharmony_ci
4041cb0ef41Sopenharmony_cimodule.exports.fromHex = fromHex
4051cb0ef41Sopenharmony_cifunction fromHex (hexDigest, algorithm, opts) {
4061cb0ef41Sopenharmony_ci  const optString = getOptString(opts?.options)
4071cb0ef41Sopenharmony_ci  return parse(
4081cb0ef41Sopenharmony_ci    `${algorithm}-${
4091cb0ef41Sopenharmony_ci      Buffer.from(hexDigest, 'hex').toString('base64')
4101cb0ef41Sopenharmony_ci    }${optString}`, opts
4111cb0ef41Sopenharmony_ci  )
4121cb0ef41Sopenharmony_ci}
4131cb0ef41Sopenharmony_ci
4141cb0ef41Sopenharmony_cimodule.exports.fromData = fromData
4151cb0ef41Sopenharmony_cifunction fromData (data, opts) {
4161cb0ef41Sopenharmony_ci  const algorithms = opts?.algorithms || [...DEFAULT_ALGORITHMS]
4171cb0ef41Sopenharmony_ci  const optString = getOptString(opts?.options)
4181cb0ef41Sopenharmony_ci  return algorithms.reduce((acc, algo) => {
4191cb0ef41Sopenharmony_ci    const digest = crypto.createHash(algo).update(data).digest('base64')
4201cb0ef41Sopenharmony_ci    const hash = new Hash(
4211cb0ef41Sopenharmony_ci      `${algo}-${digest}${optString}`,
4221cb0ef41Sopenharmony_ci      opts
4231cb0ef41Sopenharmony_ci    )
4241cb0ef41Sopenharmony_ci    /* istanbul ignore else - it would be VERY strange if the string we
4251cb0ef41Sopenharmony_ci     * just calculated with an algo did not have an algo or digest.
4261cb0ef41Sopenharmony_ci     */
4271cb0ef41Sopenharmony_ci    if (hash.algorithm && hash.digest) {
4281cb0ef41Sopenharmony_ci      const hashAlgo = hash.algorithm
4291cb0ef41Sopenharmony_ci      if (!acc[hashAlgo]) {
4301cb0ef41Sopenharmony_ci        acc[hashAlgo] = []
4311cb0ef41Sopenharmony_ci      }
4321cb0ef41Sopenharmony_ci      acc[hashAlgo].push(hash)
4331cb0ef41Sopenharmony_ci    }
4341cb0ef41Sopenharmony_ci    return acc
4351cb0ef41Sopenharmony_ci  }, new Integrity())
4361cb0ef41Sopenharmony_ci}
4371cb0ef41Sopenharmony_ci
4381cb0ef41Sopenharmony_cimodule.exports.fromStream = fromStream
4391cb0ef41Sopenharmony_cifunction fromStream (stream, opts) {
4401cb0ef41Sopenharmony_ci  const istream = integrityStream(opts)
4411cb0ef41Sopenharmony_ci  return new Promise((resolve, reject) => {
4421cb0ef41Sopenharmony_ci    stream.pipe(istream)
4431cb0ef41Sopenharmony_ci    stream.on('error', reject)
4441cb0ef41Sopenharmony_ci    istream.on('error', reject)
4451cb0ef41Sopenharmony_ci    let sri
4461cb0ef41Sopenharmony_ci    istream.on('integrity', s => {
4471cb0ef41Sopenharmony_ci      sri = s
4481cb0ef41Sopenharmony_ci    })
4491cb0ef41Sopenharmony_ci    istream.on('end', () => resolve(sri))
4501cb0ef41Sopenharmony_ci    istream.resume()
4511cb0ef41Sopenharmony_ci  })
4521cb0ef41Sopenharmony_ci}
4531cb0ef41Sopenharmony_ci
4541cb0ef41Sopenharmony_cimodule.exports.checkData = checkData
4551cb0ef41Sopenharmony_cifunction checkData (data, sri, opts) {
4561cb0ef41Sopenharmony_ci  sri = parse(sri, opts)
4571cb0ef41Sopenharmony_ci  if (!sri || !Object.keys(sri).length) {
4581cb0ef41Sopenharmony_ci    if (opts?.error) {
4591cb0ef41Sopenharmony_ci      throw Object.assign(
4601cb0ef41Sopenharmony_ci        new Error('No valid integrity hashes to check against'), {
4611cb0ef41Sopenharmony_ci          code: 'EINTEGRITY',
4621cb0ef41Sopenharmony_ci        }
4631cb0ef41Sopenharmony_ci      )
4641cb0ef41Sopenharmony_ci    } else {
4651cb0ef41Sopenharmony_ci      return false
4661cb0ef41Sopenharmony_ci    }
4671cb0ef41Sopenharmony_ci  }
4681cb0ef41Sopenharmony_ci  const algorithm = sri.pickAlgorithm(opts)
4691cb0ef41Sopenharmony_ci  const digest = crypto.createHash(algorithm).update(data).digest('base64')
4701cb0ef41Sopenharmony_ci  const newSri = parse({ algorithm, digest })
4711cb0ef41Sopenharmony_ci  const match = newSri.match(sri, opts)
4721cb0ef41Sopenharmony_ci  opts = opts || {}
4731cb0ef41Sopenharmony_ci  if (match || !(opts.error)) {
4741cb0ef41Sopenharmony_ci    return match
4751cb0ef41Sopenharmony_ci  } else if (typeof opts.size === 'number' && (data.length !== opts.size)) {
4761cb0ef41Sopenharmony_ci    /* eslint-disable-next-line max-len */
4771cb0ef41Sopenharmony_ci    const err = new Error(`data size mismatch when checking ${sri}.\n  Wanted: ${opts.size}\n  Found: ${data.length}`)
4781cb0ef41Sopenharmony_ci    err.code = 'EBADSIZE'
4791cb0ef41Sopenharmony_ci    err.found = data.length
4801cb0ef41Sopenharmony_ci    err.expected = opts.size
4811cb0ef41Sopenharmony_ci    err.sri = sri
4821cb0ef41Sopenharmony_ci    throw err
4831cb0ef41Sopenharmony_ci  } else {
4841cb0ef41Sopenharmony_ci    /* eslint-disable-next-line max-len */
4851cb0ef41Sopenharmony_ci    const err = new Error(`Integrity checksum failed when using ${algorithm}: Wanted ${sri}, but got ${newSri}. (${data.length} bytes)`)
4861cb0ef41Sopenharmony_ci    err.code = 'EINTEGRITY'
4871cb0ef41Sopenharmony_ci    err.found = newSri
4881cb0ef41Sopenharmony_ci    err.expected = sri
4891cb0ef41Sopenharmony_ci    err.algorithm = algorithm
4901cb0ef41Sopenharmony_ci    err.sri = sri
4911cb0ef41Sopenharmony_ci    throw err
4921cb0ef41Sopenharmony_ci  }
4931cb0ef41Sopenharmony_ci}
4941cb0ef41Sopenharmony_ci
4951cb0ef41Sopenharmony_cimodule.exports.checkStream = checkStream
4961cb0ef41Sopenharmony_cifunction checkStream (stream, sri, opts) {
4971cb0ef41Sopenharmony_ci  opts = opts || Object.create(null)
4981cb0ef41Sopenharmony_ci  opts.integrity = sri
4991cb0ef41Sopenharmony_ci  sri = parse(sri, opts)
5001cb0ef41Sopenharmony_ci  if (!sri || !Object.keys(sri).length) {
5011cb0ef41Sopenharmony_ci    return Promise.reject(Object.assign(
5021cb0ef41Sopenharmony_ci      new Error('No valid integrity hashes to check against'), {
5031cb0ef41Sopenharmony_ci        code: 'EINTEGRITY',
5041cb0ef41Sopenharmony_ci      }
5051cb0ef41Sopenharmony_ci    ))
5061cb0ef41Sopenharmony_ci  }
5071cb0ef41Sopenharmony_ci  const checker = integrityStream(opts)
5081cb0ef41Sopenharmony_ci  return new Promise((resolve, reject) => {
5091cb0ef41Sopenharmony_ci    stream.pipe(checker)
5101cb0ef41Sopenharmony_ci    stream.on('error', reject)
5111cb0ef41Sopenharmony_ci    checker.on('error', reject)
5121cb0ef41Sopenharmony_ci    let verified
5131cb0ef41Sopenharmony_ci    checker.on('verified', s => {
5141cb0ef41Sopenharmony_ci      verified = s
5151cb0ef41Sopenharmony_ci    })
5161cb0ef41Sopenharmony_ci    checker.on('end', () => resolve(verified))
5171cb0ef41Sopenharmony_ci    checker.resume()
5181cb0ef41Sopenharmony_ci  })
5191cb0ef41Sopenharmony_ci}
5201cb0ef41Sopenharmony_ci
5211cb0ef41Sopenharmony_cimodule.exports.integrityStream = integrityStream
5221cb0ef41Sopenharmony_cifunction integrityStream (opts = Object.create(null)) {
5231cb0ef41Sopenharmony_ci  return new IntegrityStream(opts)
5241cb0ef41Sopenharmony_ci}
5251cb0ef41Sopenharmony_ci
5261cb0ef41Sopenharmony_cimodule.exports.create = createIntegrity
5271cb0ef41Sopenharmony_cifunction createIntegrity (opts) {
5281cb0ef41Sopenharmony_ci  const algorithms = opts?.algorithms || [...DEFAULT_ALGORITHMS]
5291cb0ef41Sopenharmony_ci  const optString = getOptString(opts?.options)
5301cb0ef41Sopenharmony_ci
5311cb0ef41Sopenharmony_ci  const hashes = algorithms.map(crypto.createHash)
5321cb0ef41Sopenharmony_ci
5331cb0ef41Sopenharmony_ci  return {
5341cb0ef41Sopenharmony_ci    update: function (chunk, enc) {
5351cb0ef41Sopenharmony_ci      hashes.forEach(h => h.update(chunk, enc))
5361cb0ef41Sopenharmony_ci      return this
5371cb0ef41Sopenharmony_ci    },
5381cb0ef41Sopenharmony_ci    digest: function (enc) {
5391cb0ef41Sopenharmony_ci      const integrity = algorithms.reduce((acc, algo) => {
5401cb0ef41Sopenharmony_ci        const digest = hashes.shift().digest('base64')
5411cb0ef41Sopenharmony_ci        const hash = new Hash(
5421cb0ef41Sopenharmony_ci          `${algo}-${digest}${optString}`,
5431cb0ef41Sopenharmony_ci          opts
5441cb0ef41Sopenharmony_ci        )
5451cb0ef41Sopenharmony_ci        /* istanbul ignore else - it would be VERY strange if the hash we
5461cb0ef41Sopenharmony_ci         * just calculated with an algo did not have an algo or digest.
5471cb0ef41Sopenharmony_ci         */
5481cb0ef41Sopenharmony_ci        if (hash.algorithm && hash.digest) {
5491cb0ef41Sopenharmony_ci          const hashAlgo = hash.algorithm
5501cb0ef41Sopenharmony_ci          if (!acc[hashAlgo]) {
5511cb0ef41Sopenharmony_ci            acc[hashAlgo] = []
5521cb0ef41Sopenharmony_ci          }
5531cb0ef41Sopenharmony_ci          acc[hashAlgo].push(hash)
5541cb0ef41Sopenharmony_ci        }
5551cb0ef41Sopenharmony_ci        return acc
5561cb0ef41Sopenharmony_ci      }, new Integrity())
5571cb0ef41Sopenharmony_ci
5581cb0ef41Sopenharmony_ci      return integrity
5591cb0ef41Sopenharmony_ci    },
5601cb0ef41Sopenharmony_ci  }
5611cb0ef41Sopenharmony_ci}
5621cb0ef41Sopenharmony_ci
5631cb0ef41Sopenharmony_ciconst NODE_HASHES = crypto.getHashes()
5641cb0ef41Sopenharmony_ci
5651cb0ef41Sopenharmony_ci// This is a Best Effort™ at a reasonable priority for hash algos
5661cb0ef41Sopenharmony_ciconst DEFAULT_PRIORITY = [
5671cb0ef41Sopenharmony_ci  'md5', 'whirlpool', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
5681cb0ef41Sopenharmony_ci  // TODO - it's unclear _which_ of these Node will actually use as its name
5691cb0ef41Sopenharmony_ci  //        for the algorithm, so we guesswork it based on the OpenSSL names.
5701cb0ef41Sopenharmony_ci  'sha3',
5711cb0ef41Sopenharmony_ci  'sha3-256', 'sha3-384', 'sha3-512',
5721cb0ef41Sopenharmony_ci  'sha3_256', 'sha3_384', 'sha3_512',
5731cb0ef41Sopenharmony_ci].filter(algo => NODE_HASHES.includes(algo))
5741cb0ef41Sopenharmony_ci
5751cb0ef41Sopenharmony_cifunction getPrioritizedHash (algo1, algo2) {
5761cb0ef41Sopenharmony_ci  /* eslint-disable-next-line max-len */
5771cb0ef41Sopenharmony_ci  return DEFAULT_PRIORITY.indexOf(algo1.toLowerCase()) >= DEFAULT_PRIORITY.indexOf(algo2.toLowerCase())
5781cb0ef41Sopenharmony_ci    ? algo1
5791cb0ef41Sopenharmony_ci    : algo2
5801cb0ef41Sopenharmony_ci}
581