Comparing version 10.0.1 to 10.0.2
177
lib/index.js
@@ -6,3 +6,4 @@ 'use strict' | ||
const SPEC_ALGORITHMS = ['sha256', 'sha384', 'sha512'] | ||
const SPEC_ALGORITHMS = ['sha512', 'sha384', 'sha256'] | ||
const DEFAULT_ALGORITHMS = ['sha512'] | ||
@@ -16,25 +17,9 @@ // TODO: this should really be a hardcoded list of algorithms we support, | ||
const defaultOpts = { | ||
algorithms: ['sha512'], | ||
error: false, | ||
options: [], | ||
pickAlgorithm: getPrioritizedHash, | ||
sep: ' ', | ||
single: false, | ||
strict: false, | ||
} | ||
const getOptString = options => options?.length ? `?${options.join('?')}` : '' | ||
const ssriOpts = (opts = {}) => ({ ...defaultOpts, ...opts }) | ||
class IntegrityStream extends MiniPass { | ||
#emittedIntegrity | ||
#emittedSize | ||
#emittedVerified | ||
const getOptString = options => !options || !options.length | ||
? '' | ||
: `?${options.join('?')}` | ||
const _onEnd = Symbol('_onEnd') | ||
const _getOptions = Symbol('_getOptions') | ||
const _emittedSize = Symbol('_emittedSize') | ||
const _emittedIntegrity = Symbol('_emittedIntegrity') | ||
const _emittedVerified = Symbol('_emittedVerified') | ||
class IntegrityStream extends MiniPass { | ||
constructor (opts) { | ||
@@ -46,6 +31,6 @@ super() | ||
// may be overridden later, but set now for class consistency | ||
this[_getOptions]() | ||
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( | ||
@@ -57,29 +42,23 @@ new Set(algorithms.concat(this.algorithm ? [this.algorithm] : [])) | ||
[_getOptions] () { | ||
const { | ||
integrity, | ||
size, | ||
options, | ||
} = { ...defaultOpts, ...this.opts } | ||
#getOptions () { | ||
// 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) { | ||
if (ev === 'size' && this[_emittedSize]) { | ||
return handler(this[_emittedSize]) | ||
if (ev === 'size' && this.#emittedSize) { | ||
return handler(this.#emittedSize) | ||
} | ||
if (ev === 'integrity' && this[_emittedIntegrity]) { | ||
return handler(this[_emittedIntegrity]) | ||
if (ev === 'integrity' && this.#emittedIntegrity) { | ||
return handler(this.#emittedIntegrity) | ||
} | ||
if (ev === 'verified' && this[_emittedVerified]) { | ||
return handler(this[_emittedVerified]) | ||
if (ev === 'verified' && this.#emittedVerified) { | ||
return handler(this.#emittedVerified) | ||
} | ||
@@ -92,3 +71,3 @@ | ||
if (ev === 'end') { | ||
this[_onEnd]() | ||
this.#onEnd() | ||
} | ||
@@ -104,5 +83,5 @@ return super.emit(ev, data) | ||
[_onEnd] () { | ||
#onEnd () { | ||
if (!this.goodSri) { | ||
this[_getOptions]() | ||
this.#getOptions() | ||
} | ||
@@ -132,8 +111,8 @@ const newSri = parse(this.hashes.map((h, i) => { | ||
} else { | ||
this[_emittedSize] = this.size | ||
this.#emittedSize = this.size | ||
this.emit('size', this.size) | ||
this[_emittedIntegrity] = newSri | ||
this.#emittedIntegrity = newSri | ||
this.emit('integrity', newSri) | ||
if (match) { | ||
this[_emittedVerified] = match | ||
this.#emittedVerified = match | ||
this.emit('verified', match) | ||
@@ -151,4 +130,3 @@ } | ||
constructor (hash, opts) { | ||
opts = ssriOpts(opts) | ||
const strict = !!opts.strict | ||
const strict = opts?.strict | ||
this.source = hash.trim() | ||
@@ -172,3 +150,3 @@ | ||
} | ||
if (strict && !SPEC_ALGORITHMS.some(a => a === match[1])) { | ||
if (strict && !SPEC_ALGORITHMS.includes(match[1])) { | ||
return | ||
@@ -194,4 +172,3 @@ } | ||
toString (opts) { | ||
opts = ssriOpts(opts) | ||
if (opts.strict) { | ||
if (opts?.strict) { | ||
// Strict mode enforces the standard as close to the foot of the | ||
@@ -202,3 +179,3 @@ // letter as it can. | ||
// 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 | ||
@@ -217,9 +194,39 @@ // leave it as-is, since there's multiple standards, and the | ||
} | ||
const options = this.options && this.options.length | ||
? `?${this.options.join('?')}` | ||
: '' | ||
return `${this.algorithm}-${this.digest}${options}` | ||
return `${this.algorithm}-${this.digest}${getOptString(this.options)}` | ||
} | ||
} | ||
function integrityHashToString (toString, sep, opts, hashes) { | ||
const toStringIsNotEmpty = toString !== '' | ||
let shouldAddFirstSep = false | ||
let complement = '' | ||
const lastIndex = hashes.length - 1 | ||
for (let i = 0; i < lastIndex; i++) { | ||
const hashString = Hash.prototype.toString.call(hashes[i], opts) | ||
if (hashString) { | ||
shouldAddFirstSep = true | ||
complement += hashString | ||
complement += sep | ||
} | ||
} | ||
const finalHashString = Hash.prototype.toString.call(hashes[lastIndex], opts) | ||
if (finalHashString) { | ||
shouldAddFirstSep = true | ||
complement += finalHashString | ||
} | ||
if (toStringIsNotEmpty && shouldAddFirstSep) { | ||
return toString + sep + complement | ||
} | ||
return toString + complement | ||
} | ||
class Integrity { | ||
@@ -239,17 +246,24 @@ get isIntegrity () { | ||
toString (opts) { | ||
opts = ssriOpts(opts) | ||
let sep = opts.sep || ' ' | ||
if (opts.strict) { | ||
let sep = opts?.sep || ' ' | ||
let toString = '' | ||
if (opts?.strict) { | ||
// Entries must be separated by whitespace, according to spec. | ||
sep = sep.replace(/\S+/g, ' ') | ||
for (const hash of SPEC_ALGORITHMS) { | ||
if (this[hash]) { | ||
toString = integrityHashToString(toString, sep, opts, this[hash]) | ||
} | ||
} | ||
} else { | ||
for (const hash of Object.keys(this)) { | ||
toString = integrityHashToString(toString, sep, opts, this[hash]) | ||
} | ||
} | ||
return Object.keys(this).map(k => { | ||
return this[k].map(hash => { | ||
return Hash.prototype.toString.call(hash, opts) | ||
}).filter(x => x.length).join(sep) | ||
}).filter(x => x.length).join(sep) | ||
return toString | ||
} | ||
concat (integrity, opts) { | ||
opts = ssriOpts(opts) | ||
const other = typeof integrity === 'string' | ||
@@ -268,3 +282,2 @@ ? integrity | ||
merge (integrity, opts) { | ||
opts = ssriOpts(opts) | ||
const other = parse(integrity, opts) | ||
@@ -285,3 +298,2 @@ for (const algo in other) { | ||
match (integrity, opts) { | ||
opts = ssriOpts(opts) | ||
const other = parse(integrity, opts) | ||
@@ -304,4 +316,3 @@ if (!other) { | ||
pickAlgorithm (opts) { | ||
opts = ssriOpts(opts) | ||
const pickAlgorithm = opts.pickAlgorithm | ||
const pickAlgorithm = opts?.pickAlgorithm || getPrioritizedHash | ||
const keys = Object.keys(this) | ||
@@ -319,3 +330,2 @@ return keys.reduce((acc, algo) => { | ||
} | ||
opts = ssriOpts(opts) | ||
if (typeof sri === 'string') { | ||
@@ -335,3 +345,3 @@ return _parse(sri, opts) | ||
// https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata | ||
if (opts.single) { | ||
if (opts?.single) { | ||
return new Hash(integrity, opts) | ||
@@ -355,3 +365,2 @@ } | ||
function stringify (obj, opts) { | ||
opts = ssriOpts(opts) | ||
if (obj.algorithm && obj.digest) { | ||
@@ -368,4 +377,3 @@ return Hash.prototype.toString.call(obj, opts) | ||
function fromHex (hexDigest, algorithm, opts) { | ||
opts = ssriOpts(opts) | ||
const optString = getOptString(opts.options) | ||
const optString = getOptString(opts?.options) | ||
return parse( | ||
@@ -380,5 +388,4 @@ `${algorithm}-${ | ||
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) => { | ||
@@ -406,3 +413,2 @@ const digest = crypto.createHash(algo).update(data).digest('base64') | ||
function fromStream (stream, opts) { | ||
opts = ssriOpts(opts) | ||
const istream = integrityStream(opts) | ||
@@ -424,6 +430,5 @@ return new Promise((resolve, reject) => { | ||
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( | ||
@@ -442,3 +447,4 @@ new Error('No valid integrity hashes to check against'), { | ||
const match = newSri.match(sri, opts) | ||
if (match || !opts.error) { | ||
opts = opts || {} | ||
if (match || !(opts.error)) { | ||
return match | ||
@@ -467,3 +473,3 @@ } else if (typeof opts.size === 'number' && (data.length !== opts.size)) { | ||
function checkStream (stream, sri, opts) { | ||
opts = ssriOpts(opts) | ||
opts = opts || Object.create(null) | ||
opts.integrity = sri | ||
@@ -493,3 +499,3 @@ sri = parse(sri, opts) | ||
module.exports.integrityStream = integrityStream | ||
function integrityStream (opts = {}) { | ||
function integrityStream (opts = Object.create(null)) { | ||
return new IntegrityStream(opts) | ||
@@ -500,5 +506,4 @@ } | ||
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) | ||
@@ -505,0 +510,0 @@ const hashes = algorithms.map(crypto.createHash) |
{ | ||
"name": "ssri", | ||
"version": "10.0.1", | ||
"version": "10.0.2", | ||
"description": "Standard Subresource Integrity library -- parses, serializes, generates, and verifies integrity metadata according to the SRI spec.", | ||
@@ -54,3 +54,3 @@ "main": "lib/index.js", | ||
"@npmcli/eslint-config": "^4.0.0", | ||
"@npmcli/template-oss": "4.10.0", | ||
"@npmcli/template-oss": "4.13.0", | ||
"tap": "^16.0.1" | ||
@@ -63,4 +63,5 @@ }, | ||
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", | ||
"version": "4.10.0" | ||
"version": "4.13.0", | ||
"publish": "true" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
37640
470
1