workbite 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/download.js CHANGED
@@ -1,53 +1,53 @@
1
- 'use strict';
2
- const https = require('node:https');
3
- const { createWriteStream } = require('node:fs');
4
- const { Transform } = require('node:stream');
5
- const { pipeline } = require('node:stream/promises');
6
- const { LIMITS, fail, hashFile } = require('./common');
7
-
8
- // GitHub release assets redirect to these hosts only; nothing else is followed.
9
- function secureURL(value) {
10
- const url = new URL(value);
11
- if (url.protocol !== 'https:' || url.username || url.password || (url.port && url.port !== '443')
12
- || !['github.com', 'release-assets.githubusercontent.com', 'objects.githubusercontent.com'].includes(url.hostname)) {
13
- fail('Release redirect is not an approved HTTPS origin');
14
- }
15
- return url;
16
- }
17
- async function download(spec, destination, { request = https.get, timeout = LIMITS.timeout } = {}) {
18
- if (!Number.isSafeInteger(spec.size) || spec.size <= 0 || spec.size > LIMITS.download) fail('Invalid pinned download size');
19
- const controller = new AbortController();
20
- const timer = setTimeout(() => controller.abort(), timeout);
21
- let current = secureURL(spec.url);
22
- try {
23
- for (let redirects = 0; redirects <= 5; redirects++) {
24
- const response = await new Promise((resolve, reject) => {
25
- const req = request(current, { signal: controller.signal, headers: {
26
- 'User-Agent': 'WorkBite-npm-installer', 'Accept-Encoding': 'identity', Accept: 'application/octet-stream'
27
- } }, resolve);
28
- req.once('error', () => reject(new Error('Release download failed or timed out')));
29
- });
30
- if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
31
- const location = response.headers.location;
32
- response.destroy();
33
- if (redirects === 5 || typeof location !== 'string' || location.length > 4096) fail('Invalid release redirect');
34
- current = secureURL(new URL(location, current).href);
35
- continue;
36
- }
37
- if (response.statusCode !== 200 || (response.headers['content-encoding'] && response.headers['content-encoding'] !== 'identity')) {
38
- response.destroy(); fail('Release download was not available');
39
- }
40
- const length = response.headers['content-length'];
41
- if (length !== undefined && String(spec.size) !== length) { response.destroy(); fail('Release download size mismatch'); }
42
- let size = 0;
43
- const bounded = new Transform({ transform(chunk, encoding, callback) {
44
- size += chunk.length;
45
- callback(size > spec.size ? new Error('Release download exceeds pinned size') : null, chunk);
46
- } });
47
- await pipeline(response, bounded, createWriteStream(destination, { flags: 'wx', mode: 0o600 }), { signal: controller.signal });
48
- if (size !== spec.size || await hashFile(destination, LIMITS.download) !== spec.sha256) fail('Release SHA-256 or size mismatch');
49
- return;
50
- }
51
- } finally { clearTimeout(timer); }
52
- }
53
- module.exports = { download, secureURL };
1
+ 'use strict';
2
+ const https = require('node:https');
3
+ const { createWriteStream } = require('node:fs');
4
+ const { Transform } = require('node:stream');
5
+ const { pipeline } = require('node:stream/promises');
6
+ const { LIMITS, fail, hashFile } = require('./common');
7
+
8
+ // GitHub release assets redirect to these hosts only; nothing else is followed.
9
+ function secureURL(value) {
10
+ const url = new URL(value);
11
+ if (url.protocol !== 'https:' || url.username || url.password || (url.port && url.port !== '443')
12
+ || !['github.com', 'release-assets.githubusercontent.com', 'objects.githubusercontent.com'].includes(url.hostname)) {
13
+ fail('Release redirect is not an approved HTTPS origin');
14
+ }
15
+ return url;
16
+ }
17
+ async function download(spec, destination, { request = https.get, timeout = LIMITS.timeout } = {}) {
18
+ if (!Number.isSafeInteger(spec.size) || spec.size <= 0 || spec.size > LIMITS.download) fail('Invalid pinned download size');
19
+ const controller = new AbortController();
20
+ const timer = setTimeout(() => controller.abort(), timeout);
21
+ let current = secureURL(spec.url);
22
+ try {
23
+ for (let redirects = 0; redirects <= 5; redirects++) {
24
+ const response = await new Promise((resolve, reject) => {
25
+ const req = request(current, { signal: controller.signal, headers: {
26
+ 'User-Agent': 'WorkBite-npm-installer', 'Accept-Encoding': 'identity', Accept: 'application/octet-stream'
27
+ } }, resolve);
28
+ req.once('error', () => reject(new Error('Release download failed or timed out')));
29
+ });
30
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
31
+ const location = response.headers.location;
32
+ response.destroy();
33
+ if (redirects === 5 || typeof location !== 'string' || location.length > 4096) fail('Invalid release redirect');
34
+ current = secureURL(new URL(location, current).href);
35
+ continue;
36
+ }
37
+ if (response.statusCode !== 200 || (response.headers['content-encoding'] && response.headers['content-encoding'] !== 'identity')) {
38
+ response.destroy(); fail('Release download was not available');
39
+ }
40
+ const length = response.headers['content-length'];
41
+ if (length !== undefined && String(spec.size) !== length) { response.destroy(); fail('Release download size mismatch'); }
42
+ let size = 0;
43
+ const bounded = new Transform({ transform(chunk, encoding, callback) {
44
+ size += chunk.length;
45
+ callback(size > spec.size ? new Error('Release download exceeds pinned size') : null, chunk);
46
+ } });
47
+ await pipeline(response, bounded, createWriteStream(destination, { flags: 'wx', mode: 0o600 }), { signal: controller.signal });
48
+ if (size !== spec.size || await hashFile(destination, LIMITS.download) !== spec.sha256) fail('Release SHA-256 or size mismatch');
49
+ return;
50
+ }
51
+ } finally { clearTimeout(timer); }
52
+ }
53
+ module.exports = { download, secureURL };