diff --git a/lib/fetch/util.js b/lib/fetch/util.js index 32983720cc0..c165c845277 100644 --- a/lib/fetch/util.js +++ b/lib/fetch/util.js @@ -584,7 +584,7 @@ function bytesMatch (bytes, metadataList) { // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options // https://www.w3.org/TR/CSP2/#source-list-syntax // https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1 -const parseHashWithOptions = /((?sha256|sha384|sha512)-(?[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i +const parseHashWithOptions = /(?sha256|sha384|sha512)-(?[A-Za-z0-9+/]+={0,2}(?=\s|$))( +[!-~]*)?/i /** * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata @@ -1213,5 +1213,6 @@ module.exports = { readAllBytes, normalizeMethodRecord, simpleRangeHeaderValue, - buildContentRange + buildContentRange, + parseMetadata } diff --git a/test/fetch/util.js b/test/fetch/util.js index 02b75bc7783..9991ffbe31f 100644 --- a/test/fetch/util.js +++ b/test/fetch/util.js @@ -5,6 +5,7 @@ const { test } = t const util = require('../../lib/fetch/util') const { HeadersList } = require('../../lib/fetch/headers') +const { createHash } = require('crypto') test('responseURL', (t) => { t.plan(2) @@ -279,3 +280,78 @@ test('setRequestReferrerPolicyOnRedirect', nested => { t.equal(request.referrerPolicy, initial) }) }) + +test('parseMetadata', t => { + t.test('should parse valid metadata with option', t => { + const body = 'Hello world!' + const hash256 = createHash('sha256').update(body).digest('base64') + const hash384 = createHash('sha384').update(body).digest('base64') + const hash512 = createHash('sha512').update(body).digest('base64') + + const validMetadata = `sha256-${hash256} !@ sha384-${hash384} !@ sha512-${hash512} !@` + const result = util.parseMetadata(validMetadata) + + t.same(result, [ + { algo: 'sha256', hash: hash256 }, + { algo: 'sha384', hash: hash384 }, + { algo: 'sha512', hash: hash512 } + ]) + + t.end() + }) + + t.test('should parse valid metadata with non ASCII chars option', t => { + const body = 'Hello world!' + const hash256 = createHash('sha256').update(body).digest('base64') + const hash384 = createHash('sha384').update(body).digest('base64') + const hash512 = createHash('sha512').update(body).digest('base64') + + const validMetadata = `sha256-${hash256} !© sha384-${hash384} !€ sha512-${hash512} !µ` + const result = util.parseMetadata(validMetadata) + + t.same(result, [ + { algo: 'sha256', hash: hash256 }, + { algo: 'sha384', hash: hash384 }, + { algo: 'sha512', hash: hash512 } + ]) + + t.end() + }) + + t.test('should parse valid metadata without option', t => { + const body = 'Hello world!' + const hash256 = createHash('sha256').update(body).digest('base64') + const hash384 = createHash('sha384').update(body).digest('base64') + const hash512 = createHash('sha512').update(body).digest('base64') + + const validMetadata = `sha256-${hash256} sha384-${hash384} sha512-${hash512}` + const result = util.parseMetadata(validMetadata) + + t.same(result, [ + { algo: 'sha256', hash: hash256 }, + { algo: 'sha384', hash: hash384 }, + { algo: 'sha512', hash: hash512 } + ]) + + t.end() + }) + + t.test('should ignore invalid metadata with invalid base64 chars', t => { + const body = 'Hello world!' + const hash256 = createHash('sha256').update(body).digest('base64') + const invalidHash384 = 'zifp5hE1Xl5LQQqQz[]Bq/iaq9Wb6jVb//T7EfTmbXD2aEP5c2ZdJr9YTDfcTE1ZH+' + const hash512 = createHash('sha512').update(body).digest('base64') + + const validMetadata = `sha256-${hash256} sha384-${invalidHash384} sha512-${hash512}` + const result = util.parseMetadata(validMetadata) + + t.same(result, [ + { algo: 'sha256', hash: hash256 }, + { algo: 'sha512', hash: hash512 } + ]) + + t.end() + }) + + t.end() +})