3
3
const crypto = require ( 'crypto' )
4
4
const Transform = require ( 'stream' ) . Transform
5
5
6
- const SRI_REGEX = / ( [ ^ - ] + ) - ( [ ^ ? ] + ) ( [ ? \S * ] * ) /
6
+ const SPEC_ALGORITHMS = [ 'sha256' , 'sha384' , 'sha512' ]
7
+
8
+ const BASE64_REGEX = / [ a - z 0 - 9 + / ] + (?: = ? = ? ) / i
9
+ const SRI_REGEX = / ^ ( [ ^ - ] + ) - ( [ ^ ? ] + ) ( [ ? \S * ] * ) $ /
10
+ const STRICT_SRI_REGEX = / ^ ( [ ^ - ] + ) - ( [ A - Z a - z 0 - 9 + / ] + (?: = ? = ? ) ) ( [ ? \x21 - \x7E ] * ) $ /
11
+ const VCHAR_REGEX = / [ \x21 - \x7E ] + /
7
12
8
13
class IntegrityMetadata {
9
- constructor ( metadata ) {
10
- this . source = metadata
14
+ constructor ( metadata , opts ) {
15
+ const strict = ! ! ( opts && opts . strict )
16
+ this . source = metadata . trim ( )
11
17
// 3.1. Integrity metadata
12
18
// https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description
13
- const match = metadata . match ( SRI_REGEX )
19
+ const match = this . source . match (
20
+ strict
21
+ ? STRICT_SRI_REGEX
22
+ : SRI_REGEX
23
+ )
14
24
if ( ! match ) { return }
25
+ if ( strict && ! SPEC_ALGORITHMS . some ( a => a === match [ 1 ] ) ) { return }
15
26
this . algorithm = match [ 1 ]
16
27
this . digest = match [ 2 ]
17
28
18
29
const rawOpts = match [ 3 ]
19
30
this . options = rawOpts ? rawOpts . slice ( 1 ) . split ( '?' ) : [ ]
20
31
}
21
- toString ( ) {
22
- const opts = this . options && this . options . length
32
+ toString ( opts ) {
33
+ if ( opts && opts . strict ) {
34
+ // Strict mode enforces the standard as close to the foot of the
35
+ // letter as it can.
36
+ if ( ! (
37
+ // The spec has very restricted productions for algorithms.
38
+ // https://www.w3.org/TR/CSP2/#source-list-syntax
39
+ SPEC_ALGORITHMS . some ( x => x === this . algorithm ) &&
40
+ // Usually, if someone insists on using a "different" base64, we
41
+ // leave it as-is, since there's multiple standards, and the
42
+ // specified is not a URL-safe variant.
43
+ // https://www.w3.org/TR/CSP2/#base64_value
44
+ this . digest . match ( BASE64_REGEX ) &&
45
+ // Option syntax is strictly visual chars.
46
+ // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-option-expression
47
+ // https://tools.ietf.org/html/rfc5234#appendix-B.1
48
+ ( this . options || [ ] ) . every ( opt => opt . match ( VCHAR_REGEX ) )
49
+ ) ) {
50
+ return ''
51
+ }
52
+ }
53
+ const options = this . options && this . options . length
23
54
? `?${ this . options . join ( '?' ) } `
24
55
: ''
25
- return `${ this . algorithm } -${ this . digest } ${ opts } `
56
+ return `${ this . algorithm } -${ this . digest } ${ options } `
26
57
}
27
58
}
28
59
29
60
class Integrity {
30
- toString ( sep ) {
31
- sep = sep || ' '
61
+ toString ( opts ) {
62
+ opts = opts || { }
63
+ let sep = opts . sep || ' '
64
+ if ( opts . strict ) {
65
+ // Entries must be separated by whitespace, according to spec.
66
+ sep = sep . replace ( / \S + / g, ' ' )
67
+ }
32
68
return Object . keys ( this ) . map ( k => {
33
69
return this [ k ] . map ( meta => {
34
- return IntegrityMetadata . prototype . toString . call ( meta )
35
- } )
36
- } ) . join ( sep )
70
+ return IntegrityMetadata . prototype . toString . call ( meta , opts )
71
+ } ) . filter ( x => x . length ) . join ( sep )
72
+ } ) . filter ( x => x . length ) . join ( sep )
37
73
}
38
- concat ( integrity ) {
74
+ concat ( integrity , opts ) {
39
75
const other = typeof integrity === 'string'
40
76
? integrity
41
77
: serialize ( integrity )
42
- return parse ( `${ this . toString ( ) } ${ other } ` )
78
+ return parse ( `${ this . toString ( ) } ${ other } ` , opts )
43
79
}
44
80
}
45
81
46
82
module . exports . parse = parse
47
- function parse ( integrity ) {
83
+ function parse ( sri , opts ) {
84
+ opts = opts || { }
85
+ if ( typeof sri === 'string' ) {
86
+ return _parse ( sri , opts )
87
+ } else if ( sri . algorithm && sri . digest ) {
88
+ const fullSri = new Integrity ( )
89
+ fullSri [ sri . algorithm ] = [ sri ]
90
+ return _parse ( serialize ( fullSri , opts ) , opts )
91
+ } else {
92
+ return _parse ( serialize ( sri , opts ) , opts )
93
+ }
94
+ }
95
+
96
+ function _parse ( integrity , opts ) {
48
97
// 3.4.3. Parse metadata
49
98
// https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
50
99
return integrity . trim ( ) . split ( / \s + / ) . reduce ( ( acc , string ) => {
51
- const metadata = new IntegrityMetadata ( string )
100
+ const metadata = new IntegrityMetadata ( string , opts )
52
101
if ( metadata . algorithm && metadata . digest ) {
53
102
const algo = metadata . algorithm
54
103
if ( ! acc [ algo ] ) { acc [ algo ] = [ ] }
@@ -60,11 +109,11 @@ function parse (integrity) {
60
109
61
110
module . exports . serialize = serialize
62
111
module . exports . unparse = serialize
63
- function serialize ( obj , sep ) {
112
+ function serialize ( obj , opts ) {
64
113
if ( obj . algorithm && obj . digest ) {
65
- return IntegrityMetadata . prototype . toString . call ( obj )
114
+ return IntegrityMetadata . prototype . toString . call ( obj , opts )
66
115
} else {
67
- return Integrity . prototype . toString . call ( obj , sep )
116
+ return Integrity . prototype . toString . call ( obj , opts )
68
117
}
69
118
}
70
119
@@ -77,7 +126,10 @@ function fromData (data, opts) {
77
126
: ''
78
127
return algorithms . reduce ( ( acc , algo ) => {
79
128
const digest = crypto . createHash ( algo ) . update ( data ) . digest ( 'base64' )
80
- const meta = new IntegrityMetadata ( `${ algo } -${ digest } ${ optString } ` )
129
+ const meta = new IntegrityMetadata (
130
+ `${ algo } -${ digest } ${ optString } ` ,
131
+ opts
132
+ )
81
133
if ( meta . algorithm && meta . digest ) {
82
134
const algo = meta . algorithm
83
135
if ( ! acc [ algo ] ) { acc [ algo ] = [ ] }
@@ -103,7 +155,10 @@ function fromStream (stream, opts) {
103
155
resolve ( algorithms . reduce ( ( acc , algo , i ) => {
104
156
const hash = hashes [ i ]
105
157
const digest = hash . digest ( 'base64' )
106
- const meta = new IntegrityMetadata ( `${ algo } -${ digest } ${ optString } ` )
158
+ const meta = new IntegrityMetadata (
159
+ `${ algo } -${ digest } ${ optString } ` ,
160
+ opts
161
+ )
107
162
if ( meta . algorithm && meta . digest ) {
108
163
const algo = meta . algorithm
109
164
if ( ! acc [ algo ] ) { acc [ algo ] = [ ] }
@@ -118,13 +173,7 @@ function fromStream (stream, opts) {
118
173
module . exports . checkData = checkData
119
174
function checkData ( data , sri , opts ) {
120
175
opts = opts || { }
121
- if ( typeof sri === 'string' ) {
122
- sri = parse ( sri )
123
- } else if ( sri . algorithm && sri . digest ) {
124
- const fullSri = new Integrity ( )
125
- fullSri [ sri . algorithm ] = [ sri ]
126
- sri = fullSri
127
- }
176
+ sri = parse ( sri , opts )
128
177
const pickAlgorithm = opts . pickAlgorithm || getPrioritizedHash
129
178
const algorithm = Object . keys ( sri ) . reduce ( ( acc , algo ) => {
130
179
return pickAlgorithm ( acc , algo ) || acc
@@ -152,13 +201,7 @@ function checkStream (stream, sri, opts) {
152
201
module . exports . createCheckerStream = createCheckerStream
153
202
function createCheckerStream ( sri , opts ) {
154
203
opts = opts || { }
155
- if ( typeof sri === 'string' ) {
156
- sri = parse ( sri )
157
- } else if ( sri . algorithm && sri . digest ) {
158
- const fullSri = new Integrity ( )
159
- fullSri [ sri . algorithm ] = [ sri ]
160
- sri = fullSri
161
- }
204
+ sri = parse ( sri , opts )
162
205
const pickAlgorithm = opts . pickAlgorithm || getPrioritizedHash
163
206
const algorithm = Object . keys ( sri ) . reduce ( ( acc , algo ) => {
164
207
return pickAlgorithm ( acc , algo ) || acc
0 commit comments