22'use strict' ;
33
44const https = require ( 'https' ) ;
5- const http = require ( 'http' ) ;
65const fs = require ( 'fs' ) ;
76const path = require ( 'path' ) ;
87const os = require ( 'os' ) ;
8+ const crypto = require ( 'crypto' ) ;
99const { execFileSync } = require ( 'child_process' ) ;
1010
1111const pkg = require ( './package.json' ) ;
12+ const checksums = require ( './checksums.json' ) ;
1213const VERSION = pkg . version ;
1314const REPO = 'OpenCoven/coven-code' ;
1415const BASE_URL = `https://github.com/${ REPO } /releases/download/v${ VERSION } ` ;
@@ -41,13 +42,29 @@ function getPlatform() {
4142
4243function download ( url , dest ) {
4344 return new Promise ( ( resolve , reject ) => {
45+ const parsed = new URL ( url ) ;
46+ if ( parsed . protocol !== 'https:' ) {
47+ reject ( new Error ( `Refusing to download non-HTTPS URL: ${ url } ` ) ) ;
48+ return ;
49+ }
50+
4451 const file = fs . createWriteStream ( dest ) ;
45- const get = url . startsWith ( 'https' ) ? https : http ;
46- get . get ( url , ( res ) => {
47- if ( res . statusCode === 301 || res . statusCode === 302 ) {
52+ https . get ( url , ( res ) => {
53+ if ( [ 301 , 302 , 303 , 307 , 308 ] . includes ( res . statusCode ) ) {
4854 file . close ( ) ;
4955 try { fs . unlinkSync ( dest ) ; } catch ( _ ) { }
50- download ( res . headers . location , dest ) . then ( resolve ) . catch ( reject ) ;
56+ if ( ! res . headers . location ) {
57+ reject ( new Error ( `Redirect without Location header downloading ${ url } ` ) ) ;
58+ return ;
59+ }
60+ let location ;
61+ try {
62+ location = new URL ( res . headers . location , url ) . toString ( ) ;
63+ } catch ( err ) {
64+ reject ( err ) ;
65+ return ;
66+ }
67+ download ( location , dest ) . then ( resolve ) . catch ( reject ) ;
5168 return ;
5269 }
5370 if ( res . statusCode !== 200 ) {
@@ -69,6 +86,31 @@ function download(url, dest) {
6986 } ) ;
7087}
7188
89+ function sha256File ( filePath ) {
90+ const hash = crypto . createHash ( 'sha256' ) ;
91+ const bytes = fs . readFileSync ( filePath ) ;
92+ hash . update ( bytes ) ;
93+ return hash . digest ( 'hex' ) ;
94+ }
95+
96+ function expectedSha256 ( archiveName ) {
97+ const entry = checksums [ archiveName ] ;
98+ if ( ! entry || typeof entry . sha256 !== 'string' ) {
99+ throw new Error ( `Missing SHA-256 checksum for ${ archiveName } in checksums.json` ) ;
100+ }
101+ return entry . sha256 ;
102+ }
103+
104+ function verifyChecksum ( filePath , archiveName ) {
105+ const expected = expectedSha256 ( archiveName ) . toLowerCase ( ) ;
106+ const actual = sha256File ( filePath ) . toLowerCase ( ) ;
107+ if ( actual !== expected ) {
108+ throw new Error (
109+ `Checksum mismatch for ${ archiveName } : expected ${ expected } , got ${ actual } `
110+ ) ;
111+ }
112+ }
113+
72114async function main ( ) {
73115 const { artifact, ext, archive } = getPlatform ( ) ;
74116 const archiveName = `${ artifact } ${ archive } ` ;
@@ -87,6 +129,9 @@ async function main() {
87129 console . log ( ` ${ url } ` ) ;
88130 await download ( url , tmpPath ) ;
89131
132+ console . log ( 'coven-code: verifying checksum...' ) ;
133+ verifyChecksum ( tmpPath , archiveName ) ;
134+
90135 console . log ( 'coven-code: extracting...' ) ;
91136 if ( archive === '.zip' ) {
92137 execFileSync ( 'powershell' , [
0 commit comments