Skip to content

Commit 6a54b1c

Browse files
falsandtruJonathan Ginsburg
authored and
Jonathan Ginsburg
committedJun 14, 2022
feat: support SRI verification of script tags
1 parent 5e71cf5 commit 6a54b1c

File tree

6 files changed

+32
-12
lines changed

6 files changed

+32
-12
lines changed
 

‎lib/config.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ try {
3232
} catch {}
3333

3434
class Pattern {
35-
constructor (pattern, served, included, watched, nocache, type, isBinary) {
35+
constructor (pattern, served, included, watched, nocache, type, isBinary, integrity) {
3636
this.pattern = pattern
3737
this.served = helper.isDefined(served) ? served : true
3838
this.included = helper.isDefined(included) ? included : true
@@ -41,6 +41,7 @@ class Pattern {
4141
this.weight = helper.mmPatternWeight(pattern)
4242
this.type = type
4343
this.isBinary = isBinary
44+
this.integrity = integrity
4445
}
4546

4647
compare (other) {
@@ -49,8 +50,8 @@ class Pattern {
4950
}
5051

5152
class UrlPattern extends Pattern {
52-
constructor (url, type) {
53-
super(url, false, true, false, false, type)
53+
constructor (url, type, integrity) {
54+
super(url, false, true, false, false, type, undefined, integrity)
5455
}
5556
}
5657

‎lib/file-list.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ class FileList {
6262

6363
let lastCompletedRefresh = this._refreshing
6464
lastCompletedRefresh = Promise.all(
65-
this._patterns.map(async ({ pattern, type, nocache, isBinary }) => {
65+
this._patterns.map(async ({ pattern, type, nocache, isBinary, integrity }) => {
6666
if (helper.isUrlAbsolute(pattern)) {
67-
this.buckets.set(pattern, [new Url(pattern, type)])
67+
this.buckets.set(pattern, [new Url(pattern, type, integrity)])
6868
return
6969
}
7070

‎lib/file.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const path = require('path')
66
* File object used for tracking files in `file-list.js`.
77
*/
88
class File {
9-
constructor (path, mtime, doNotCache, type, isBinary) {
9+
constructor (path, mtime, doNotCache, type, isBinary, integrity) {
1010
// used for serving (processed path, eg some/file.coffee -> some/file.coffee.js)
1111
this.path = path
1212

@@ -29,6 +29,8 @@ class File {
2929

3030
// Tri state: null means probe file for binary.
3131
this.isBinary = isBinary === undefined ? null : isBinary
32+
33+
this.integrity = integrity
3234
}
3335

3436
/**

‎lib/middleware/karma.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,12 @@ function createKarmaMiddleware (
190190
scriptTags.push(`<link href="${filePath}" rel="import">`)
191191
} else {
192192
const scriptType = (SCRIPT_TYPE[fileType] || 'text/javascript')
193-
const crossOriginAttribute = includeCrossOriginAttribute ? 'crossorigin="anonymous"' : ''
193+
const crossOriginAttribute = includeCrossOriginAttribute ? ' crossorigin="anonymous"' : ''
194+
const integrityAttribute = file.integrity ? ` integrity="${file.integrity}"` : ''
194195
if (fileType === 'module') {
195-
scriptTags.push(`<script onerror="throw 'Error loading ${filePath}'" type="${scriptType}" src="${filePath}" ${crossOriginAttribute}></script>`)
196+
scriptTags.push(`<script onerror="throw 'Error loading ${filePath}'" type="${scriptType}" src="${filePath}"${integrityAttribute}${crossOriginAttribute}></script>`)
196197
} else {
197-
scriptTags.push(`<script type="${scriptType}" src="${filePath}" ${crossOriginAttribute}></script>`)
198+
scriptTags.push(`<script type="${scriptType}" src="${filePath}"${integrityAttribute}${crossOriginAttribute}></script>`)
198199
}
199200
}
200201
}

‎lib/url.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ const { URL } = require('url')
77
* Url object used for tracking files in `file-list.js`.
88
*/
99
class Url {
10-
constructor (path, type) {
10+
constructor (path, type, integrity) {
1111
this.path = path
1212
this.originalPath = path
1313
this.type = type
14+
this.integrity = integrity
1415
this.isUrl = true
1516
}
1617

‎test/unit/middleware/karma.spec.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ describe('middleware.karma', () => {
1717
let response
1818

1919
class MockFile extends File {
20-
constructor (path, sha, type, content) {
21-
super(path, undefined, undefined, type)
20+
constructor (path, sha, type, content, integrity) {
21+
super(path, undefined, undefined, type, undefined, integrity)
2222
this.sha = sha || 'sha-default'
2323
this.content = content
2424
}
@@ -230,6 +230,21 @@ describe('middleware.karma', () => {
230230
callHandlerWith('/__karma__/context.html')
231231
})
232232

233+
it('should serve context.html with script tags with integrity checking', (done) => {
234+
includedFiles([
235+
new MockFile('/first.js', 'sha123'),
236+
new MockFile('/second.js', 'sha456', undefined, undefined, 'sha256-XXX')
237+
])
238+
239+
response.once('end', () => {
240+
expect(nextSpy).not.to.have.been.called
241+
expect(response).to.beServedAs(200, 'CONTEXT\n<script type="text/javascript" src="/__proxy__/__karma__/absolute/first.js?sha123" crossorigin="anonymous"></script>\n<script type="text/javascript" src="/__proxy__/__karma__/absolute/second.js?sha456" integrity="sha256-XXX" crossorigin="anonymous"></script>')
242+
done()
243+
})
244+
245+
callHandlerWith('/__karma__/context.html')
246+
})
247+
233248
it('should serve context.html with replaced link tags', (done) => {
234249
includedFiles([
235250
new MockFile('/first.css', 'sha007'),

0 commit comments

Comments
 (0)