Skip to content

fix: remove spread of defaultOpts #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 3, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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