@@ -8,109 +8,78 @@ import { pipeline } from 'node:stream/promises';
8
8
import { fileURLToPath } from 'node:url' ;
9
9
import { parseArgs } from 'node:util' ;
10
10
11
- // Constants for NSS release metadata.
12
- const kNSSVersion = 'version' ;
13
- const kNSSDate = 'date' ;
14
- const kFirefoxVersion = 'firefoxVersion' ;
15
- const kFirefoxDate = 'firefoxDate' ;
16
-
17
11
const __filename = fileURLToPath ( import . meta. url ) ;
18
- const now = new Date ( ) ;
19
-
20
- const formatDate = ( d ) => {
21
- const iso = d . toISOString ( ) ;
22
- return iso . substring ( 0 , iso . indexOf ( 'T' ) ) ;
23
- } ;
24
12
25
13
const getCertdataURL = ( version ) => {
26
14
const tag = `NSS_${ version . replaceAll ( '.' , '_' ) } _RTM` ;
27
- const certdataURL = `https://hg.mozilla.org/projects /nss/raw-file /${ tag } /lib/ckfw/builtins/certdata.txt` ;
15
+ const certdataURL = `https://raw.githubusercontent.com/nss-dev /nss/refs/tags /${ tag } /lib/ckfw/builtins/certdata.txt` ;
28
16
return certdataURL ;
29
17
} ;
30
18
31
- const normalizeTD = ( text = '' ) => {
32
- // Remove whitespace and any HTML tags.
33
- return text ?. trim ( ) . replace ( / < .* ?> / g, '' ) ;
34
- } ;
35
- const getReleases = ( text ) => {
36
- const releases = [ ] ;
37
- const tableRE = / < t a b l e [ ^ > ] + > ( [ \S \s ] * ?) < \/ t a b l e > / g;
38
- const tableRowRE = / < t r ? [ ^ > ] * > ( [ \S \s ] * ?) < \/ t r > / g;
39
- const tableHeaderRE = / < t h ? [ ^ > ] * > ( [ \S \s ] * ?) < \/ t h > / g;
40
- const tableDataRE = / < t d ? [ ^ > ] * > ( [ \S \s ] * ?) < \/ t d > / g;
41
- for ( const table of text . matchAll ( tableRE ) ) {
42
- const columns = { } ;
43
- const matches = table [ 1 ] . matchAll ( tableRowRE ) ;
44
- // First row has the table header.
45
- let row = matches . next ( ) ;
46
- if ( row . done ) {
47
- continue ;
48
- }
49
- const headers = Array . from ( row . value [ 1 ] . matchAll ( tableHeaderRE ) , ( m ) => m [ 1 ] ) ;
50
- if ( headers . length > 0 ) {
51
- for ( let i = 0 ; i < headers . length ; i ++ ) {
52
- if ( / N S S v e r s i o n / i. test ( headers [ i ] ) ) {
53
- columns [ kNSSVersion ] = i ;
54
- } else if ( / R e l e a s e .* f r o m b r a n c h / i. test ( headers [ i ] ) ) {
55
- columns [ kNSSDate ] = i ;
56
- } else if ( / F i r e f o x v e r s i o n / i. test ( headers [ i ] ) ) {
57
- columns [ kFirefoxVersion ] = i ;
58
- } else if ( / F i r e f o x r e l e a s e d a t e / i. test ( headers [ i ] ) ) {
59
- columns [ kFirefoxDate ] = i ;
60
- }
61
- }
62
- }
63
- // Filter out "NSS Certificate bugs" table.
64
- if ( columns [ kNSSDate ] === undefined ) {
65
- continue ;
66
- }
67
- // Scrape releases.
68
- row = matches . next ( ) ;
69
- while ( ! row . done ) {
70
- const cells = Array . from ( row . value [ 1 ] . matchAll ( tableDataRE ) , ( m ) => m [ 1 ] ) ;
71
- const release = { } ;
72
- release [ kNSSVersion ] = normalizeTD ( cells [ columns [ kNSSVersion ] ] ) ;
73
- release [ kNSSDate ] = new Date ( normalizeTD ( cells [ columns [ kNSSDate ] ] ) ) ;
74
- release [ kFirefoxVersion ] = normalizeTD ( cells [ columns [ kFirefoxVersion ] ] ) ;
75
- release [ kFirefoxDate ] = new Date ( normalizeTD ( cells [ columns [ kFirefoxDate ] ] ) ) ;
76
- releases . push ( release ) ;
77
- row = matches . next ( ) ;
78
- }
19
+ const getFirefoxReleases = async ( everything = false ) => {
20
+ const releaseDataURL = `https://nucleus.mozilla.org/rna/all-releases.json${ everything ? '?all=true' : '' } ` ;
21
+ if ( values . verbose ) {
22
+ console . log ( `Fetching Firefox release data from ${ releaseDataURL } .` ) ;
23
+ }
24
+ const releaseData = await fetch ( releaseDataURL ) ;
25
+ if ( ! releaseData . ok ) {
26
+ console . error ( `Failed to fetch ${ releaseDataURL } : ${ releaseData . status } : ${ releaseData . statusText } .` ) ;
27
+ process . exit ( - 1 ) ;
79
28
}
80
- return releases ;
29
+ return ( await releaseData . json ( ) ) . filter ( ( release ) => {
30
+ // We're only interested in public releases of Firefox.
31
+ return ( release . product === 'Firefox' && release . channel === 'Release' && release . is_public === true ) ;
32
+ } ) . sort ( ( a , b ) => {
33
+ // Sort results by release date.
34
+ return new Date ( b . release_date ) - new Date ( a . release_date ) ;
35
+ } ) ;
81
36
} ;
82
37
83
- const getLatestVersion = async ( releases ) => {
84
- const arrayNumberSortDescending = ( x , y , i ) => {
85
- if ( x [ i ] === undefined && y [ i ] === undefined ) {
86
- return 0 ;
87
- } else if ( x [ i ] === y [ i ] ) {
88
- return arrayNumberSortDescending ( x , y , i + 1 ) ;
89
- }
90
- return ( y [ i ] ?? 0 ) - ( x [ i ] ?? 0 ) ;
91
- } ;
92
- const extractVersion = ( t ) => {
93
- return t [ kNSSVersion ] . split ( '.' ) . map ( ( n ) => parseInt ( n ) ) ;
94
- } ;
95
- const releaseSorter = ( x , y ) => {
96
- return arrayNumberSortDescending ( extractVersion ( x ) , extractVersion ( y ) , 0 ) ;
97
- } ;
98
- // Return the most recent certadata.txt that exists on the server.
99
- const sortedReleases = releases . sort ( releaseSorter ) . filter ( pastRelease ) ;
100
- for ( const candidate of sortedReleases ) {
101
- const candidateURL = getCertdataURL ( candidate [ kNSSVersion ] ) ;
102
- if ( values . verbose ) {
103
- console . log ( `Trying ${ candidateURL } ` ) ;
38
+ const getFirefoxRelease = async ( version ) => {
39
+ let releases = await getFirefoxReleases ( ) ;
40
+ let found ;
41
+ if ( version === undefined ) {
42
+ // No version specified. Find the most recent.
43
+ if ( releases . length > 0 ) {
44
+ found = releases [ 0 ] ;
45
+ } else {
46
+ if ( values . verbose ) {
47
+ console . log ( 'Unable to find release data for Firefox. Searching full release data.' ) ;
48
+ }
49
+ releases = await getFirefoxReleases ( true ) ;
50
+ found = releases [ 0 ] ;
104
51
}
105
- const response = await fetch ( candidateURL , { method : 'HEAD' } ) ;
106
- if ( response . ok ) {
107
- return candidate [ kNSSVersion ] ;
52
+ } else {
53
+ // Search for the specified release.
54
+ found = releases . find ( ( release ) => release . version === version ) ;
55
+ if ( found === undefined ) {
56
+ if ( values . verbose ) {
57
+ console . log ( `Unable to find release data for Firefox ${ version } . Searching full release data.` ) ;
58
+ }
59
+ releases = await getFirefoxReleases ( true ) ;
60
+ found = releases . find ( ( release ) => release . version === version ) ;
108
61
}
109
62
}
63
+ return found ;
110
64
} ;
111
65
112
- const pastRelease = ( r ) => {
113
- return r [ kNSSDate ] < now ;
66
+ const getNSSVersion = async ( release ) => {
67
+ const latestFirefox = release . version ;
68
+ const firefoxTag = `FIREFOX_${ latestFirefox . replace ( '.' , '_' ) } _RELEASE` ;
69
+ const tagInfoURL = `https://hg.mozilla.org/releases/mozilla-release/raw-file/${ firefoxTag } /security/nss/TAG-INFO` ;
70
+ if ( values . verbose ) {
71
+ console . log ( `Fetching NSS tag from ${ tagInfoURL } .` ) ;
72
+ }
73
+ const tagInfo = await fetch ( tagInfoURL ) ;
74
+ if ( ! tagInfo . ok ) {
75
+ console . error ( `Failed to fetch ${ tagInfoURL } : ${ tagInfo . status } : ${ tagInfo . statusText } ` ) ;
76
+ }
77
+ const tag = await tagInfo . text ( ) ;
78
+ if ( values . verbose ) {
79
+ console . log ( `Found tag ${ tag } .` ) ;
80
+ }
81
+ // Tag will be of form `NSS_x_y_RTM`. Convert to `x.y`.
82
+ return tag . split ( '_' ) . slice ( 1 , - 1 ) . join ( '.' ) ;
114
83
} ;
115
84
116
85
const options = {
@@ -135,9 +104,9 @@ const {
135
104
} ) ;
136
105
137
106
if ( values . help ) {
138
- console . log ( `Usage: ${ basename ( __filename ) } [OPTION]... [VERSION ]...` ) ;
107
+ console . log ( `Usage: ${ basename ( __filename ) } [OPTION]... [RELEASE ]...` ) ;
139
108
console . log ( ) ;
140
- console . log ( 'Updates certdata.txt to NSS VERSION ( most recent release by default ).' ) ;
109
+ console . log ( 'Updates certdata.txt to NSS version contained in Firefox RELEASE (default: most recent release).' ) ;
141
110
console . log ( '' ) ;
142
111
console . log ( ' -f, --file=FILE writes a commit message reflecting the change to the' ) ;
143
112
console . log ( ' specified FILE' ) ;
@@ -146,29 +115,11 @@ if (values.help) {
146
115
process . exit ( 0 ) ;
147
116
}
148
117
149
- const scheduleURL = 'https://wiki.mozilla.org/NSS:Release_Versions' ;
150
- if ( values . verbose ) {
151
- console . log ( `Fetching NSS release schedule from ${ scheduleURL } ` ) ;
152
- }
153
- const schedule = await fetch ( scheduleURL ) ;
154
- if ( ! schedule . ok ) {
155
- console . error ( `Failed to fetch ${ scheduleURL } : ${ schedule . status } : ${ schedule . statusText } ` ) ;
156
- process . exit ( - 1 ) ;
157
- }
158
- const scheduleText = await schedule . text ( ) ;
159
- const nssReleases = getReleases ( scheduleText ) ;
160
-
118
+ const firefoxRelease = await getFirefoxRelease ( positionals [ 0 ] ) ;
161
119
// Retrieve metadata for the NSS release being updated to.
162
- const version = positionals [ 0 ] ?? await getLatestVersion ( nssReleases ) ;
163
- const release = nssReleases . find ( ( r ) => {
164
- return new RegExp ( `^${ version . replace ( '.' , '\\.' ) } \\b` ) . test ( r [ kNSSVersion ] ) ;
165
- } ) ;
166
- if ( ! pastRelease ( release ) ) {
167
- console . warn ( `Warning: NSS ${ version } is not due to be released until ${ formatDate ( release [ kNSSDate ] ) } ` ) ;
168
- }
120
+ const version = await getNSSVersion ( firefoxRelease ) ;
169
121
if ( values . verbose ) {
170
- console . log ( 'Found NSS version:' ) ;
171
- console . log ( release ) ;
122
+ console . log ( `Updating to NSS version ${ version } ` ) ;
172
123
}
173
124
174
125
// Fetch certdata.txt and overwrite the local copy.
@@ -213,14 +164,15 @@ const added = [ ...diff.matchAll(certsAddedRE) ].map((m) => m[1]);
213
164
const removed = [ ...diff . matchAll ( certsRemovedRE ) ] . map ( ( m ) => m [ 1 ] ) ;
214
165
215
166
const commitMsg = [
216
- `crypto: update root certificates to NSS ${ release [ kNSSVersion ] } ` ,
167
+ `crypto: update root certificates to NSS ${ version } ` ,
217
168
'' ,
218
- `This is the certdata.txt[0] from NSS ${ release [ kNSSVersion ] } , released on ${ formatDate ( release [ kNSSDate ] ) } .` ,
219
- '' ,
220
- `This is the version of NSS that ${ release [ kFirefoxDate ] < now ? 'shipped' : 'will ship' } in Firefox ${ release [ kFirefoxVersion ] } on` ,
221
- `${ formatDate ( release [ kFirefoxDate ] ) } .` ,
169
+ `This is the certdata.txt[0] from NSS ${ version } .` ,
222
170
'' ,
223
171
] ;
172
+ if ( firefoxRelease ) {
173
+ commitMsg . push ( `This is the version of NSS that shipped in Firefox ${ firefoxRelease . version } on ${ firefoxRelease . release_date } .` ) ;
174
+ commitMsg . push ( '' ) ;
175
+ }
224
176
if ( added . length > 0 ) {
225
177
commitMsg . push ( 'Certificates added:' ) ;
226
178
commitMsg . push ( ...added . map ( ( cert ) => `- ${ cert } ` ) ) ;
@@ -234,7 +186,7 @@ if (removed.length > 0) {
234
186
commitMsg . push ( `[0] ${ certdataURL } ` ) ;
235
187
const delimiter = randomUUID ( ) ;
236
188
const properties = [
237
- `NEW_VERSION=${ release [ kNSSVersion ] } ` ,
189
+ `NEW_VERSION=${ version } ` ,
238
190
`COMMIT_MSG<<${ delimiter } ` ,
239
191
...commitMsg ,
240
192
delimiter ,
0 commit comments