@pnpm/fetching.tarball-fetcher 1102.1.7 → 1102.1.8
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/CHANGELOG.md +14 -0
- package/lib/localTarballFetcher.js +5 -2
- package/lib/remoteTarballFetcher.d.ts +1 -0
- package/lib/remoteTarballFetcher.js +93 -48
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @pnpm/tarball-fetcher
|
|
2
2
|
|
|
3
|
+
## 1102.1.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Large package downloads and large files inside gzip and bzip2 package archives now use bounded memory during installation. Package manifests and archive metadata larger than 64 MiB are rejected.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies:
|
|
10
|
+
- @pnpm/error@1100.2.2
|
|
11
|
+
- @pnpm/exec.prepare-package@1100.0.42
|
|
12
|
+
- @pnpm/fs.graceful-fs@1100.2.5
|
|
13
|
+
- @pnpm/fs.packlist@1100.0.8
|
|
14
|
+
- @pnpm/network.fetch@1100.1.22
|
|
15
|
+
- @pnpm/store.index@1101.0.1
|
|
16
|
+
|
|
3
17
|
## 1102.1.7
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import gfs from '@pnpm/fs.graceful-fs';
|
|
3
3
|
import { addFilesFromTarball } from '@pnpm/worker';
|
|
4
|
+
import { MAX_BUFFERED_DOWNLOAD_SIZE } from './remoteTarballFetcher.js';
|
|
4
5
|
const isAbsolutePath = /^\/|^[A-Z]:/i;
|
|
5
6
|
export function createLocalTarballFetcher(storeIndex) {
|
|
6
7
|
const fetch = async (cafs, resolution, opts) => {
|
|
7
8
|
const tarball = resolvePath(opts.lockfileDir, resolution.tarball.slice(5));
|
|
8
|
-
const
|
|
9
|
+
const source = gfs.statSync(tarball).size > MAX_BUFFERED_DOWNLOAD_SIZE
|
|
10
|
+
? { tarballFile: tarball }
|
|
11
|
+
: { buffer: gfs.readFileSync(tarball) };
|
|
9
12
|
const result = await addFilesFromTarball({
|
|
10
13
|
storeDir: cafs.storeDir,
|
|
11
14
|
storeIndex,
|
|
12
|
-
|
|
15
|
+
...source,
|
|
13
16
|
filesIndexFile: opts.filesIndexFile,
|
|
14
17
|
integrity: resolution.integrity,
|
|
15
18
|
readManifest: opts.readManifest,
|
|
@@ -3,6 +3,7 @@ import type { FetchOptions, FetchResult } from '@pnpm/fetching.fetcher-base';
|
|
|
3
3
|
import type { FetchFromRegistry, GetAuthHeader, RetryTimeoutOptions } from '@pnpm/fetching.types';
|
|
4
4
|
import type { Cafs } from '@pnpm/store.cafs-types';
|
|
5
5
|
import type { StoreIndex } from '@pnpm/store.index';
|
|
6
|
+
export declare const MAX_BUFFERED_DOWNLOAD_SIZE: number;
|
|
6
7
|
export interface HttpResponse {
|
|
7
8
|
body: string;
|
|
8
9
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
1
2
|
import { isIP } from 'node:net';
|
|
3
|
+
import path from 'node:path';
|
|
2
4
|
import { requestRetryLogger } from '@pnpm/core-loggers';
|
|
3
5
|
import { FetchError, FetchTimeoutError, isError, isFetchTimeoutError, redactUrlForDisplay } from '@pnpm/error';
|
|
4
6
|
import { globalWarn } from '@pnpm/logger';
|
|
@@ -7,6 +9,7 @@ import { addFilesFromTarball } from '@pnpm/worker';
|
|
|
7
9
|
import * as retry from '@zkochan/retry';
|
|
8
10
|
import throttle from 'lodash.throttle';
|
|
9
11
|
import { BadTarballError } from './errorTypes/index.js';
|
|
12
|
+
export const MAX_BUFFERED_DOWNLOAD_SIZE = 64 * 1024 * 1024;
|
|
10
13
|
const BIG_TARBALL_SIZE = 1024 * 1024 * 5; // 5 MB
|
|
11
14
|
export function createDownloader(fetchFromRegistry, gotOpts) {
|
|
12
15
|
const defaultRetryOpts = {
|
|
@@ -88,25 +91,35 @@ async function fetchTarball(ctx) {
|
|
|
88
91
|
fetchMinSpeedKiBps: ctx.fetchMinSpeedKiBps,
|
|
89
92
|
onProgress,
|
|
90
93
|
size,
|
|
94
|
+
storeDir: ctx.opts.cafs.storeDir,
|
|
91
95
|
url: ctx.url,
|
|
92
96
|
});
|
|
93
97
|
}
|
|
94
98
|
catch (err) {
|
|
95
99
|
throw wrapFetchError(err, ctx.url, ctx.attempt, ctx.gotOpts.timeout);
|
|
96
100
|
}
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
return importDownloadedTarball(ctx, data);
|
|
102
|
+
}
|
|
103
|
+
async function importDownloadedTarball(ctx, data) {
|
|
104
|
+
try {
|
|
105
|
+
return await addFilesFromTarball({
|
|
106
|
+
buffer: data.buffer,
|
|
107
|
+
tarballFile: data.tarballFile,
|
|
108
|
+
storeDir: ctx.opts.cafs.storeDir,
|
|
109
|
+
storeIndex: ctx.opts.storeIndex,
|
|
110
|
+
readManifest: ctx.opts.readManifest,
|
|
111
|
+
integrity: ctx.opts.integrity,
|
|
112
|
+
filesIndexFile: ctx.opts.filesIndexFile,
|
|
113
|
+
pkgId: ctx.opts.pkgId,
|
|
114
|
+
url: ctx.url,
|
|
115
|
+
pkg: ctx.opts.pkg,
|
|
116
|
+
appendManifest: ctx.opts.appendManifest,
|
|
117
|
+
ignoreFilePattern: ctx.opts.ignoreFilePattern,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await data.cleanup?.();
|
|
122
|
+
}
|
|
110
123
|
}
|
|
111
124
|
function parseTarballContentLength(res) {
|
|
112
125
|
const isEncoded = isContentEncoded(res.headers.get('content-encoding'));
|
|
@@ -116,54 +129,85 @@ function parseTarballContentLength(res) {
|
|
|
116
129
|
}
|
|
117
130
|
async function readResponseBody(opts) {
|
|
118
131
|
const startTime = Date.now();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
downloaded = opts.size;
|
|
132
|
+
if (opts.size != null && opts.size <= MAX_BUFFERED_DOWNLOAD_SIZE) {
|
|
133
|
+
const buffer = await readKnownSizeBody(opts);
|
|
134
|
+
checkDownloadSpeed(opts.size, startTime, opts.fetchMinSpeedKiBps, opts.url);
|
|
135
|
+
return { buffer };
|
|
124
136
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
const state = { chunks: [], downloaded: 0 };
|
|
138
|
+
try {
|
|
139
|
+
for await (const chunk of opts.body) {
|
|
140
|
+
const bytes = chunk;
|
|
141
|
+
state.downloaded += bytes.byteLength;
|
|
142
|
+
checkDownloadedSize(opts, state.downloaded, false);
|
|
143
|
+
await bufferDownloadChunk(state, bytes, opts.storeDir);
|
|
144
|
+
opts.onProgress?.(state.downloaded);
|
|
145
|
+
}
|
|
146
|
+
checkDownloadedSize(opts, state.downloaded, true);
|
|
147
|
+
checkDownloadSpeed(state.downloaded, startTime, opts.fetchMinSpeedKiBps, opts.url);
|
|
148
|
+
await state.file?.close();
|
|
149
|
+
return finishDownloadBuffer(state);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
await state.file?.close().catch(() => { });
|
|
153
|
+
if (state.directory)
|
|
154
|
+
await fs.rm(state.directory, { recursive: true, force: true });
|
|
155
|
+
throw err;
|
|
129
156
|
}
|
|
130
|
-
checkDownloadSpeed(downloaded, startTime, opts.fetchMinSpeedKiBps, opts.url);
|
|
131
|
-
return data;
|
|
132
157
|
}
|
|
133
|
-
async function readKnownSizeBody(
|
|
134
|
-
const
|
|
158
|
+
async function readKnownSizeBody(opts) {
|
|
159
|
+
const buffer = Buffer.from(new SharedArrayBuffer(opts.size));
|
|
135
160
|
let downloaded = 0;
|
|
136
|
-
for await (const chunk of body) {
|
|
161
|
+
for await (const chunk of opts.body) {
|
|
137
162
|
const bytes = chunk;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
checkDownloadedSize(opts, downloaded + bytes.byteLength, false);
|
|
164
|
+
buffer.set(bytes, downloaded);
|
|
165
|
+
downloaded += bytes.byteLength;
|
|
166
|
+
opts.onProgress?.(downloaded);
|
|
167
|
+
}
|
|
168
|
+
checkDownloadedSize(opts, downloaded, true);
|
|
169
|
+
return buffer;
|
|
170
|
+
}
|
|
171
|
+
function checkDownloadedSize(opts, downloaded, finished) {
|
|
172
|
+
if (opts.size == null)
|
|
173
|
+
return;
|
|
174
|
+
if (downloaded > opts.size || (finished && downloaded !== opts.size)) {
|
|
175
|
+
throw new BadTarballError({ expectedSize: opts.size, receivedSize: downloaded, tarballUrl: opts.url });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function bufferDownloadChunk(state, bytes, storeDir) {
|
|
179
|
+
if (!state.file && state.downloaded > MAX_BUFFERED_DOWNLOAD_SIZE) {
|
|
180
|
+
await fs.mkdir(storeDir, { recursive: true });
|
|
181
|
+
state.directory = await fs.mkdtemp(path.join(storeDir, 'download-'));
|
|
182
|
+
state.file = await fs.open(path.join(state.directory, 'archive'), 'wx', 0o600);
|
|
183
|
+
for (const buffered of state.chunks) {
|
|
184
|
+
// eslint-disable-next-line no-await-in-loop -- writes preserve tarball byte order
|
|
185
|
+
await state.file.writeFile(buffered);
|
|
141
186
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
187
|
+
state.chunks.length = 0;
|
|
188
|
+
}
|
|
189
|
+
if (state.file) {
|
|
190
|
+
await state.file.writeFile(bytes);
|
|
145
191
|
}
|
|
146
|
-
|
|
147
|
-
|
|
192
|
+
else {
|
|
193
|
+
state.chunks.push(bytes);
|
|
148
194
|
}
|
|
149
|
-
return data;
|
|
150
195
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
onProgress?.(downloaded);
|
|
196
|
+
function finishDownloadBuffer(state) {
|
|
197
|
+
if (state.directory) {
|
|
198
|
+
const tempDirectory = state.directory;
|
|
199
|
+
return {
|
|
200
|
+
tarballFile: path.join(tempDirectory, 'archive'),
|
|
201
|
+
cleanup: async () => fs.rm(tempDirectory, { recursive: true, force: true }),
|
|
202
|
+
};
|
|
159
203
|
}
|
|
160
|
-
const
|
|
204
|
+
const buffer = Buffer.from(new SharedArrayBuffer(state.downloaded));
|
|
161
205
|
let offset = 0;
|
|
162
|
-
for (const chunk of chunks) {
|
|
163
|
-
|
|
206
|
+
for (const chunk of state.chunks) {
|
|
207
|
+
buffer.set(chunk, offset);
|
|
164
208
|
offset += chunk.byteLength;
|
|
165
209
|
}
|
|
166
|
-
return {
|
|
210
|
+
return { buffer };
|
|
167
211
|
}
|
|
168
212
|
function checkDownloadSpeed(downloaded, startTime, fetchMinSpeedKiBps, url) {
|
|
169
213
|
const elapsedSec = (Date.now() - startTime) / 1000;
|
|
@@ -191,6 +235,7 @@ function handleDownloadRetry(opts) {
|
|
|
191
235
|
status === 403 ||
|
|
192
236
|
status === 404 ||
|
|
193
237
|
opts.error.code === 'ERR_PNPM_PREPARE_PKG_FAILURE' ||
|
|
238
|
+
opts.error.code === 'ERR_PNPM_TARBALL_ENTRY_TOO_LARGE' ||
|
|
194
239
|
isNonRetryableError(opts.error);
|
|
195
240
|
if (isFatal) {
|
|
196
241
|
opts.reject(opts.error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/fetching.tarball-fetcher",
|
|
3
|
-
"version": "1102.1.
|
|
3
|
+
"version": "1102.1.8",
|
|
4
4
|
"description": "Fetcher for packages hosted as tarballs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@pnpm/core-loggers": "1101.0.2",
|
|
33
|
-
"@pnpm/error": "1100.2.
|
|
34
|
-
"@pnpm/exec.prepare-package": "1100.0.
|
|
33
|
+
"@pnpm/error": "1100.2.2",
|
|
34
|
+
"@pnpm/exec.prepare-package": "1100.0.42",
|
|
35
35
|
"@pnpm/fetching.fetcher-base": "1100.2.12",
|
|
36
36
|
"@pnpm/fetching.types": "1100.0.3",
|
|
37
|
-
"@pnpm/fs.graceful-fs": "1100.2.
|
|
38
|
-
"@pnpm/fs.packlist": "1100.0.
|
|
39
|
-
"@pnpm/network.fetch": "1100.1.
|
|
40
|
-
"@pnpm/store.index": "1101.0.
|
|
37
|
+
"@pnpm/fs.graceful-fs": "1100.2.5",
|
|
38
|
+
"@pnpm/fs.packlist": "1100.0.8",
|
|
39
|
+
"@pnpm/network.fetch": "1100.1.22",
|
|
40
|
+
"@pnpm/store.index": "1101.0.1",
|
|
41
41
|
"@pnpm/types": "1102.1.1",
|
|
42
42
|
"@zkochan/retry": "^0.2.0",
|
|
43
43
|
"lodash.throttle": "4.1.1",
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@pnpm/logger": "^1100.0.0",
|
|
48
|
-
"@pnpm/worker": "^1100.4.
|
|
48
|
+
"@pnpm/worker": "^1100.4.8"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@jest/globals": "30.4.1",
|
|
52
|
-
"@pnpm/fetching.tarball-fetcher": "1102.1.
|
|
52
|
+
"@pnpm/fetching.tarball-fetcher": "1102.1.8",
|
|
53
53
|
"@pnpm/logger": "1100.0.0",
|
|
54
54
|
"@pnpm/store.cafs-types": "1100.1.1",
|
|
55
|
-
"@pnpm/store.create-cafs-store": "1100.0.
|
|
55
|
+
"@pnpm/store.create-cafs-store": "1100.0.34",
|
|
56
56
|
"@pnpm/test-fixtures": "1100.0.1",
|
|
57
57
|
"@pnpm/text.ordinal-comparator": "1100.0.0",
|
|
58
58
|
"@pnpm/types": "1102.1.1",
|