Skip to content

Commit

Permalink
fix: remove spread of defaultOpts (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
H4ad committed Apr 3, 2023
1 parent 61d92a1 commit 6e6877d
Showing 1 changed file with 25 additions and 60 deletions.
85 changes: 25 additions & 60 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const crypto = require('crypto')
const MiniPass = require('minipass')

const SPEC_ALGORITHMS = ['sha256', 'sha384', 'sha512']
const DEFAULT_ALGORITHMS = ['sha512']

// TODO: this should really be a hardcoded list of algorithms we support,
// rather than [a-z0-9].
Expand All @@ -12,21 +13,7 @@ const SRI_REGEX = /^([a-z0-9]+)-([^?]+)([?\S*]*)$/
const STRICT_SRI_REGEX = /^([a-z0-9]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)?$/
const VCHAR_REGEX = /^[\x21-\x7E]+$/

const defaultOpts = {
algorithms: ['sha512'],
error: false,
options: [],
pickAlgorithm: getPrioritizedHash,
sep: ' ',
single: false,
strict: false,
}

const ssriOpts = (opts = {}) => ({ ...defaultOpts, ...opts })

const getOptString = options => !options || !options.length
? ''
: `?${options.join('?')}`
const getOptString = options => options?.length ? `?${options.join('?')}` : ''

const _onEnd = Symbol('_onEnd')
const _getOptions = Symbol('_getOptions')
Expand All @@ -44,27 +31,21 @@ class IntegrityStream extends MiniPass {
this[_getOptions]()

// options used for calculating stream. can't be changed.
const { algorithms = defaultOpts.algorithms } = opts
const algorithms = opts?.algorithms || DEFAULT_ALGORITHMS
this.algorithms = Array.from(
new Set(algorithms.concat(this.algorithm ? [this.algorithm] : []))
)
this.hashes = this.algorithms.map(crypto.createHash)
}

[_getOptions] () {
const {
integrity,
size,
options,
} = { ...defaultOpts, ...this.opts }

// For verification
this.sri = integrity ? parse(integrity, this.opts) : null
this.expectedSize = size
this.sri = this.opts?.integrity ? parse(this.opts?.integrity, this.opts) : null
this.expectedSize = this.opts?.size
this.goodSri = this.sri ? !!Object.keys(this.sri).length : false
this.algorithm = this.goodSri ? this.sri.pickAlgorithm(this.opts) : null
this.digests = this.goodSri ? this.sri[this.algorithm] : null
this.optString = getOptString(options)
this.optString = getOptString(this.opts?.options)
}

on (ev, handler) {
Expand Down Expand Up @@ -141,8 +122,7 @@ class Hash {
}

constructor (hash, opts) {
opts = ssriOpts(opts)
const strict = !!opts.strict
const strict = opts?.strict
this.source = hash.trim()

// set default values so that we make V8 happy to
Expand All @@ -161,7 +141,7 @@ class Hash {
if (!match) {
return
}
if (strict && !SPEC_ALGORITHMS.some(a => a === match[1])) {
if (strict && !SPEC_ALGORITHMS.includes(match[1])) {
return
}
this.algorithm = match[1]
Expand All @@ -182,14 +162,13 @@ class Hash {
}

toString (opts) {
opts = ssriOpts(opts)
if (opts.strict) {
if (opts?.strict) {
// Strict mode enforces the standard as close to the foot of the
// letter as it can.
if (!(
// The spec has very restricted productions for algorithms.
// https://www.w3.org/TR/CSP2/#source-list-syntax
SPEC_ALGORITHMS.some(x => x === this.algorithm) &&
SPEC_ALGORITHMS.includes(this.algorithm) &&
// Usually, if someone insists on using a "different" base64, we
// leave it as-is, since there's multiple standards, and the
// specified is not a URL-safe variant.
Expand All @@ -203,10 +182,7 @@ class Hash {
return ''
}
}
const options = this.options && this.options.length
? `?${this.options.join('?')}`
: ''
return `${this.algorithm}-${this.digest}${options}`
return `${this.algorithm}-${this.digest}${getOptString(this.options)}`
}
}

Expand All @@ -224,9 +200,8 @@ class Integrity {
}

toString (opts) {
opts = ssriOpts(opts)
let sep = opts.sep || ' '
if (opts.strict) {
let sep = opts?.sep || ' '
if (opts?.strict) {
// Entries must be separated by whitespace, according to spec.
sep = sep.replace(/\S+/g, ' ')
}
Expand All @@ -238,7 +213,6 @@ class Integrity {
}

concat (integrity, opts) {
opts = ssriOpts(opts)
const other = typeof integrity === 'string'
? integrity
: stringify(integrity, opts)
Expand All @@ -252,7 +226,6 @@ class Integrity {
// add additional hashes to an integrity value, but prevent
// *changing* an existing integrity hash.
merge (integrity, opts) {
opts = ssriOpts(opts)
const other = parse(integrity, opts)
for (const algo in other) {
if (this[algo]) {
Expand All @@ -268,7 +241,6 @@ class Integrity {
}

match (integrity, opts) {
opts = ssriOpts(opts)
const other = parse(integrity, opts)
if (!other) {
return false
Expand All @@ -286,8 +258,7 @@ class Integrity {
}

pickAlgorithm (opts) {
opts = ssriOpts(opts)
const pickAlgorithm = opts.pickAlgorithm
const pickAlgorithm = opts?.pickAlgorithm || getPrioritizedHash
const keys = Object.keys(this)
return keys.reduce((acc, algo) => {
return pickAlgorithm(acc, algo) || acc
Expand All @@ -300,7 +271,6 @@ function parse (sri, opts) {
if (!sri) {
return null
}
opts = ssriOpts(opts)
if (typeof sri === 'string') {
return _parse(sri, opts)
} else if (sri.algorithm && sri.digest) {
Expand All @@ -315,7 +285,7 @@ function parse (sri, opts) {
function _parse (integrity, opts) {
// 3.4.3. Parse metadata
// https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
if (opts.single) {
if (opts?.single) {
return new Hash(integrity, opts)
}
const hashes = integrity.trim().split(/\s+/).reduce((acc, string) => {
Expand All @@ -334,7 +304,6 @@ function _parse (integrity, opts) {

module.exports.stringify = stringify
function stringify (obj, opts) {
opts = ssriOpts(opts)
if (obj.algorithm && obj.digest) {
return Hash.prototype.toString.call(obj, opts)
} else if (typeof obj === 'string') {
Expand All @@ -346,8 +315,7 @@ function stringify (obj, opts) {

module.exports.fromHex = fromHex
function fromHex (hexDigest, algorithm, opts) {
opts = ssriOpts(opts)
const optString = getOptString(opts.options)
const optString = getOptString(opts?.options)
return parse(
`${algorithm}-${
Buffer.from(hexDigest, 'hex').toString('base64')
Expand All @@ -357,9 +325,8 @@ function fromHex (hexDigest, algorithm, opts) {

module.exports.fromData = fromData
function fromData (data, opts) {
opts = ssriOpts(opts)
const algorithms = opts.algorithms
const optString = getOptString(opts.options)
const algorithms = opts?.algorithms || DEFAULT_ALGORITHMS
const optString = getOptString(opts?.options)
return algorithms.reduce((acc, algo) => {
const digest = crypto.createHash(algo).update(data).digest('base64')
const hash = new Hash(
Expand All @@ -382,7 +349,6 @@ function fromData (data, opts) {

module.exports.fromStream = fromStream
function fromStream (stream, opts) {
opts = ssriOpts(opts)
const istream = integrityStream(opts)
return new Promise((resolve, reject) => {
stream.pipe(istream)
Expand All @@ -399,10 +365,9 @@ function fromStream (stream, opts) {

module.exports.checkData = checkData
function checkData (data, sri, opts) {
opts = ssriOpts(opts)
sri = parse(sri, opts)
if (!sri || !Object.keys(sri).length) {
if (opts.error) {
if (opts?.error) {
throw Object.assign(
new Error('No valid integrity hashes to check against'), {
code: 'EINTEGRITY',
Expand All @@ -416,7 +381,8 @@ function checkData (data, sri, opts) {
const digest = crypto.createHash(algorithm).update(data).digest('base64')
const newSri = parse({ algorithm, digest })
const match = newSri.match(sri, opts)
if (match || !opts.error) {
opts = opts || Object.create(null)
if (match || !(opts.error)) {
return match
} else if (typeof opts.size === 'number' && (data.length !== opts.size)) {
/* eslint-disable-next-line max-len */
Expand All @@ -440,7 +406,7 @@ function checkData (data, sri, opts) {

module.exports.checkStream = checkStream
function checkStream (stream, sri, opts) {
opts = ssriOpts(opts)
opts = opts || Object.create(null)
opts.integrity = sri
sri = parse(sri, opts)
if (!sri || !Object.keys(sri).length) {
Expand All @@ -465,15 +431,14 @@ function checkStream (stream, sri, opts) {
}

module.exports.integrityStream = integrityStream
function integrityStream (opts = {}) {
function integrityStream (opts = Object.create(null)) {
return new IntegrityStream(opts)
}

module.exports.create = createIntegrity
function createIntegrity (opts) {
opts = ssriOpts(opts)
const algorithms = opts.algorithms
const optString = getOptString(opts.options)
const algorithms = opts?.algorithms || DEFAULT_ALGORITHMS
const optString = getOptString(opts?.options)

const hashes = algorithms.map(crypto.createHash)

Expand Down

0 comments on commit 6e6877d

Please sign in to comment.