@@ -8,109 +8,78 @@ import { pipeline } from 'node:stream/promises';
88import { fileURLToPath } from 'node:url' ;
99import { parseArgs } from 'node:util' ;
1010
11- // Constants for NSS release metadata.
12- const kNSSVersion = 'version' ;
13- const kNSSDate = 'date' ;
14- const kFirefoxVersion = 'firefoxVersion' ;
15- const kFirefoxDate = 'firefoxDate' ;
16-
1711const __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- } ;
2412
2513const getCertdataURL = ( version ) => {
2614 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` ;
2816 return certdataURL ;
2917} ;
3018
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 ) ;
7928 }
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+ } ) ;
8136} ;
8237
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 ] ;
10451 }
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 ) ;
10861 }
10962 }
63+ return found ;
11064} ;
11165
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 ( '.' ) ;
11483} ;
11584
11685const options = {
@@ -135,9 +104,9 @@ const {
135104} ) ;
136105
137106if ( values . help ) {
138- console . log ( `Usage: ${ basename ( __filename ) } [OPTION]... [VERSION ]...` ) ;
107+ console . log ( `Usage: ${ basename ( __filename ) } [OPTION]... [RELEASE ]...` ) ;
139108 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).' ) ;
141110 console . log ( '' ) ;
142111 console . log ( ' -f, --file=FILE writes a commit message reflecting the change to the' ) ;
143112 console . log ( ' specified FILE' ) ;
@@ -146,29 +115,11 @@ if (values.help) {
146115 process . exit ( 0 ) ;
147116}
148117
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 ] ) ;
161119// 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 ) ;
169121if ( values . verbose ) {
170- console . log ( 'Found NSS version:' ) ;
171- console . log ( release ) ;
122+ console . log ( `Updating to NSS version ${ version } ` ) ;
172123}
173124
174125// Fetch certdata.txt and overwrite the local copy.
@@ -213,14 +164,15 @@ const added = [ ...diff.matchAll(certsAddedRE) ].map((m) => m[1]);
213164const removed = [ ...diff . matchAll ( certsRemovedRE ) ] . map ( ( m ) => m [ 1 ] ) ;
214165
215166const commitMsg = [
216- `crypto: update root certificates to NSS ${ release [ kNSSVersion ] } ` ,
167+ `crypto: update root certificates to NSS ${ version } ` ,
217168 '' ,
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 } .` ,
222170 '' ,
223171] ;
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+ }
224176if ( added . length > 0 ) {
225177 commitMsg . push ( 'Certificates added:' ) ;
226178 commitMsg . push ( ...added . map ( ( cert ) => `- ${ cert } ` ) ) ;
@@ -234,7 +186,7 @@ if (removed.length > 0) {
234186commitMsg . push ( `[0] ${ certdataURL } ` ) ;
235187const delimiter = randomUUID ( ) ;
236188const properties = [
237- `NEW_VERSION=${ release [ kNSSVersion ] } ` ,
189+ `NEW_VERSION=${ version } ` ,
238190 `COMMIT_MSG<<${ delimiter } ` ,
239191 ...commitMsg ,
240192 delimiter ,
0 commit comments