From 6a54b1c2a1df8214c470b8a5cc8036912874637e Mon Sep 17 00:00:00 2001 From: falsandtru Date: Mon, 16 May 2022 12:33:52 +0900 Subject: [PATCH] feat: support SRI verification of script tags --- lib/config.js | 7 ++++--- lib/file-list.js | 4 ++-- lib/file.js | 4 +++- lib/middleware/karma.js | 7 ++++--- lib/url.js | 3 ++- test/unit/middleware/karma.spec.js | 19 +++++++++++++++++-- 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/config.js b/lib/config.js index 240e1c305..0dff33c99 100644 --- a/lib/config.js +++ b/lib/config.js @@ -32,7 +32,7 @@ try { } catch {} class Pattern { - constructor (pattern, served, included, watched, nocache, type, isBinary) { + constructor (pattern, served, included, watched, nocache, type, isBinary, integrity) { this.pattern = pattern this.served = helper.isDefined(served) ? served : true this.included = helper.isDefined(included) ? included : true @@ -41,6 +41,7 @@ class Pattern { this.weight = helper.mmPatternWeight(pattern) this.type = type this.isBinary = isBinary + this.integrity = integrity } compare (other) { @@ -49,8 +50,8 @@ class Pattern { } class UrlPattern extends Pattern { - constructor (url, type) { - super(url, false, true, false, false, type) + constructor (url, type, integrity) { + super(url, false, true, false, false, type, undefined, integrity) } } diff --git a/lib/file-list.js b/lib/file-list.js index 6c9e93d16..b4ec01d29 100644 --- a/lib/file-list.js +++ b/lib/file-list.js @@ -62,9 +62,9 @@ class FileList { let lastCompletedRefresh = this._refreshing lastCompletedRefresh = Promise.all( - this._patterns.map(async ({ pattern, type, nocache, isBinary }) => { + this._patterns.map(async ({ pattern, type, nocache, isBinary, integrity }) => { if (helper.isUrlAbsolute(pattern)) { - this.buckets.set(pattern, [new Url(pattern, type)]) + this.buckets.set(pattern, [new Url(pattern, type, integrity)]) return } diff --git a/lib/file.js b/lib/file.js index 2342fd3ab..698aca7dc 100644 --- a/lib/file.js +++ b/lib/file.js @@ -6,7 +6,7 @@ const path = require('path') * File object used for tracking files in `file-list.js`. */ class File { - constructor (path, mtime, doNotCache, type, isBinary) { + constructor (path, mtime, doNotCache, type, isBinary, integrity) { // used for serving (processed path, eg some/file.coffee -> some/file.coffee.js) this.path = path @@ -29,6 +29,8 @@ class File { // Tri state: null means probe file for binary. this.isBinary = isBinary === undefined ? null : isBinary + + this.integrity = integrity } /** diff --git a/lib/middleware/karma.js b/lib/middleware/karma.js index a5c94f399..20051eec4 100644 --- a/lib/middleware/karma.js +++ b/lib/middleware/karma.js @@ -190,11 +190,12 @@ function createKarmaMiddleware ( scriptTags.push(``) } else { const scriptType = (SCRIPT_TYPE[fileType] || 'text/javascript') - const crossOriginAttribute = includeCrossOriginAttribute ? 'crossorigin="anonymous"' : '' + const crossOriginAttribute = includeCrossOriginAttribute ? ' crossorigin="anonymous"' : '' + const integrityAttribute = file.integrity ? ` integrity="${file.integrity}"` : '' if (fileType === 'module') { - scriptTags.push(``) + scriptTags.push(``) } else { - scriptTags.push(``) + scriptTags.push(``) } } } diff --git a/lib/url.js b/lib/url.js index ebe078619..fce5bd1d3 100644 --- a/lib/url.js +++ b/lib/url.js @@ -7,10 +7,11 @@ const { URL } = require('url') * Url object used for tracking files in `file-list.js`. */ class Url { - constructor (path, type) { + constructor (path, type, integrity) { this.path = path this.originalPath = path this.type = type + this.integrity = integrity this.isUrl = true } diff --git a/test/unit/middleware/karma.spec.js b/test/unit/middleware/karma.spec.js index 4f6b873c2..7479b202e 100644 --- a/test/unit/middleware/karma.spec.js +++ b/test/unit/middleware/karma.spec.js @@ -17,8 +17,8 @@ describe('middleware.karma', () => { let response class MockFile extends File { - constructor (path, sha, type, content) { - super(path, undefined, undefined, type) + constructor (path, sha, type, content, integrity) { + super(path, undefined, undefined, type, undefined, integrity) this.sha = sha || 'sha-default' this.content = content } @@ -230,6 +230,21 @@ describe('middleware.karma', () => { callHandlerWith('/__karma__/context.html') }) + it('should serve context.html with script tags with integrity checking', (done) => { + includedFiles([ + new MockFile('/first.js', 'sha123'), + new MockFile('/second.js', 'sha456', undefined, undefined, 'sha256-XXX') + ]) + + response.once('end', () => { + expect(nextSpy).not.to.have.been.called + expect(response).to.beServedAs(200, 'CONTEXT\n\n') + done() + }) + + callHandlerWith('/__karma__/context.html') + }) + it('should serve context.html with replaced link tags', (done) => { includedFiles([ new MockFile('/first.css', 'sha007'),