@vltpkg/registry-client 0.0.0-3 → 0.0.0-31
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/README.md +50 -53
- package/dist/esm/cache-entry.d.ts +84 -14
- package/dist/esm/cache-entry.d.ts.map +1 -1
- package/dist/esm/cache-entry.js +284 -90
- package/dist/esm/cache-entry.js.map +1 -1
- package/dist/esm/cache-revalidate.d.ts +2 -0
- package/dist/esm/cache-revalidate.d.ts.map +1 -0
- package/dist/esm/cache-revalidate.js +66 -0
- package/dist/esm/cache-revalidate.js.map +1 -0
- package/dist/esm/handle-304-response.js +1 -1
- package/dist/esm/handle-304-response.js.map +1 -1
- package/dist/esm/index.d.ts +40 -9
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +100 -55
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/otplease.d.ts.map +1 -1
- package/dist/esm/otplease.js +33 -25
- package/dist/esm/otplease.js.map +1 -1
- package/dist/esm/raw-header.d.ts +3 -3
- package/dist/esm/raw-header.d.ts.map +1 -1
- package/dist/esm/raw-header.js +12 -11
- package/dist/esm/raw-header.js.map +1 -1
- package/dist/esm/redirect.d.ts +1 -1
- package/dist/esm/redirect.d.ts.map +1 -1
- package/dist/esm/redirect.js +5 -1
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/revalidate.d.ts +5 -0
- package/dist/esm/revalidate.d.ts.map +1 -0
- package/dist/esm/revalidate.js +55 -0
- package/dist/esm/revalidate.js.map +1 -0
- package/dist/esm/set-raw-header.d.ts +1 -1
- package/dist/esm/set-raw-header.d.ts.map +1 -1
- package/dist/esm/set-raw-header.js +6 -4
- package/dist/esm/set-raw-header.js.map +1 -1
- package/dist/esm/string-encoding.d.ts +9 -0
- package/dist/esm/string-encoding.d.ts.map +1 -0
- package/dist/esm/string-encoding.js +25 -0
- package/dist/esm/string-encoding.js.map +1 -0
- package/dist/esm/token-response.d.ts +1 -1
- package/dist/esm/token-response.d.ts.map +1 -1
- package/dist/esm/token-response.js +5 -2
- package/dist/esm/token-response.js.map +1 -1
- package/dist/esm/web-auth-challenge.d.ts +2 -2
- package/dist/esm/web-auth-challenge.d.ts.map +1 -1
- package/dist/esm/web-auth-challenge.js +13 -6
- package/dist/esm/web-auth-challenge.js.map +1 -1
- package/package.json +20 -20
package/dist/esm/cache-entry.js
CHANGED
|
@@ -20,10 +20,11 @@
|
|
|
20
20
|
// of the file.
|
|
21
21
|
import { error } from '@vltpkg/error-cause';
|
|
22
22
|
import ccp from 'cache-control-parser';
|
|
23
|
-
import { createHash } from 'crypto';
|
|
24
|
-
import { inspect } from 'util';
|
|
25
|
-
import { gunzipSync } from 'zlib';
|
|
23
|
+
import { createHash } from 'node:crypto';
|
|
24
|
+
import { inspect } from 'node:util';
|
|
25
|
+
import { gunzipSync } from 'node:zlib';
|
|
26
26
|
import { getRawHeader, setRawHeader } from "./raw-header.js";
|
|
27
|
+
import { getDecodedValue, getEncondedValue, } from "./string-encoding.js";
|
|
27
28
|
const readSize = (buf, offset) => {
|
|
28
29
|
const a = buf[offset];
|
|
29
30
|
const b = buf[offset + 1];
|
|
@@ -45,66 +46,181 @@ const readSize = (buf, offset) => {
|
|
|
45
46
|
const kCustomInspect = Symbol.for('nodejs.util.inspect.custom');
|
|
46
47
|
export class CacheEntry {
|
|
47
48
|
#statusCode;
|
|
49
|
+
/** The raw headers as an array of buffers */
|
|
48
50
|
#headers;
|
|
49
|
-
|
|
51
|
+
/** The body buffer, used if the content length is known. */
|
|
52
|
+
#body;
|
|
53
|
+
/**
|
|
54
|
+
* If the content length is unknown we save the body in multiple parts
|
|
55
|
+
* in order to only concatenate once at the end and save extra memory copies.
|
|
56
|
+
*/
|
|
57
|
+
#bodyParts = [];
|
|
58
|
+
/** Used to track the length of the body while reading chunks */
|
|
50
59
|
#bodyLength = 0;
|
|
60
|
+
/** The total length of the body, if known */
|
|
61
|
+
#contentLength;
|
|
51
62
|
#integrity;
|
|
52
63
|
#integrityActual;
|
|
53
64
|
#json;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
#trustIntegrity;
|
|
66
|
+
#staleWhileRevalidateFactor;
|
|
67
|
+
constructor(statusCode, headers, { body, integrity, trustIntegrity = false, 'stale-while-revalidate-factor': staleWhileRevalidateFactor = 60, contentLength, } = {}) {
|
|
57
68
|
this.#headers = headers;
|
|
69
|
+
this.#statusCode = statusCode;
|
|
70
|
+
this.#trustIntegrity = trustIntegrity;
|
|
71
|
+
this.#staleWhileRevalidateFactor = staleWhileRevalidateFactor;
|
|
72
|
+
if (integrity)
|
|
73
|
+
this.integrity = integrity;
|
|
74
|
+
// if content-legnth is known then we'll only allocate that much memory
|
|
75
|
+
// and we'll avoid copying memory around when adding new chunks.
|
|
76
|
+
if (contentLength != null && typeof contentLength === 'number') {
|
|
77
|
+
this.#contentLength = contentLength;
|
|
78
|
+
}
|
|
79
|
+
// if a body is provided then use that, in this case the `addBody`
|
|
80
|
+
// method should no longer be used.
|
|
81
|
+
if (body) {
|
|
82
|
+
const buffer = new ArrayBuffer(body.byteLength);
|
|
83
|
+
this.#body = new Uint8Array(buffer, 0, body.byteLength);
|
|
84
|
+
this.#body.set(body, 0);
|
|
85
|
+
this.#bodyLength = body.byteLength;
|
|
86
|
+
/* c8 ignore start */
|
|
87
|
+
}
|
|
88
|
+
else if (this.#contentLength) {
|
|
89
|
+
const buffer = new ArrayBuffer(this.#contentLength);
|
|
90
|
+
this.#body = new Uint8Array(buffer, 0, this.#contentLength);
|
|
91
|
+
this.#bodyLength = 0;
|
|
92
|
+
}
|
|
93
|
+
/* c8 ignore stop */
|
|
58
94
|
}
|
|
59
95
|
get #headersAsObject() {
|
|
60
96
|
const ret = [];
|
|
61
97
|
for (let i = 0; i < this.#headers.length - 1; i += 2) {
|
|
62
|
-
const key =
|
|
63
|
-
const val =
|
|
98
|
+
const key = getDecodedValue(this.#headers[i]);
|
|
99
|
+
const val = getDecodedValue(this.#headers[i + 1]);
|
|
64
100
|
ret.push([key, val]);
|
|
65
101
|
}
|
|
66
102
|
return ret;
|
|
67
103
|
}
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
104
|
+
toJSON() {
|
|
105
|
+
const { statusCode, valid, staleWhileRevalidate, cacheControl, date, contentType, integrity, maxAge, isGzip, isJSON, } = this;
|
|
106
|
+
/* c8 ignore start */
|
|
107
|
+
const age = date ?
|
|
108
|
+
Math.floor((Date.now() - date.getTime()) / 1000)
|
|
109
|
+
: undefined;
|
|
110
|
+
const expires = date ? new Date(date.getTime() + this.maxAge * 1000) : undefined;
|
|
111
|
+
/* c8 ignore end */
|
|
112
|
+
return Object.fromEntries(Object.entries({
|
|
113
|
+
statusCode,
|
|
71
114
|
headers: this.#headersAsObject,
|
|
72
|
-
|
|
73
|
-
|
|
115
|
+
contentType,
|
|
116
|
+
integrity,
|
|
117
|
+
date,
|
|
118
|
+
expires,
|
|
119
|
+
cacheControl,
|
|
120
|
+
valid,
|
|
121
|
+
staleWhileRevalidate,
|
|
122
|
+
age,
|
|
123
|
+
maxAge,
|
|
124
|
+
isGzip,
|
|
125
|
+
isJSON,
|
|
126
|
+
}).filter(([_, v]) => v !== undefined));
|
|
127
|
+
}
|
|
128
|
+
[kCustomInspect](depth, options) {
|
|
129
|
+
const str = inspect(this.toJSON(), {
|
|
74
130
|
depth,
|
|
75
131
|
...options,
|
|
76
132
|
});
|
|
77
133
|
return `@vltpkg/registry-client.CacheEntry ${str}`;
|
|
78
134
|
}
|
|
135
|
+
#date;
|
|
136
|
+
get date() {
|
|
137
|
+
if (this.#date)
|
|
138
|
+
return this.#date;
|
|
139
|
+
const dh = this.getHeaderString('date');
|
|
140
|
+
if (dh)
|
|
141
|
+
this.#date = new Date(dh);
|
|
142
|
+
return this.#date;
|
|
143
|
+
}
|
|
144
|
+
#maxAge;
|
|
145
|
+
get maxAge() {
|
|
146
|
+
if (this.#maxAge !== undefined)
|
|
147
|
+
return this.#maxAge;
|
|
148
|
+
// see if the max-age has not yet been crossed
|
|
149
|
+
// default to 5m if maxage is not set, as some registries
|
|
150
|
+
// do not set a cache control header at all.
|
|
151
|
+
const cc = this.cacheControl;
|
|
152
|
+
this.#maxAge = cc['max-age'] || cc['s-maxage'] || 300;
|
|
153
|
+
return this.#maxAge;
|
|
154
|
+
}
|
|
155
|
+
#cacheControl;
|
|
156
|
+
get cacheControl() {
|
|
157
|
+
if (this.#cacheControl)
|
|
158
|
+
return this.#cacheControl;
|
|
159
|
+
const cc = this.getHeaderString('cache-control');
|
|
160
|
+
this.#cacheControl = cc ? ccp.parse(cc) : {};
|
|
161
|
+
return this.#cacheControl;
|
|
162
|
+
}
|
|
163
|
+
#staleWhileRevalidate;
|
|
164
|
+
get staleWhileRevalidate() {
|
|
165
|
+
if (this.#staleWhileRevalidate !== undefined)
|
|
166
|
+
return this.#staleWhileRevalidate;
|
|
167
|
+
if (this.valid || !this.date)
|
|
168
|
+
return true;
|
|
169
|
+
const swv = this.cacheControl['stale-while-revalidate'] ??
|
|
170
|
+
this.maxAge * this.#staleWhileRevalidateFactor;
|
|
171
|
+
this.#staleWhileRevalidate =
|
|
172
|
+
this.date.getTime() + swv * 1000 > Date.now();
|
|
173
|
+
return this.#staleWhileRevalidate;
|
|
174
|
+
}
|
|
175
|
+
#contentType;
|
|
176
|
+
get contentType() {
|
|
177
|
+
if (this.#contentType !== undefined)
|
|
178
|
+
return this.#contentType;
|
|
179
|
+
this.#contentType = this.getHeaderString('content-type') ?? '';
|
|
180
|
+
return this.#contentType;
|
|
181
|
+
}
|
|
79
182
|
/**
|
|
80
183
|
* `true` if the entry represents a cached response that is still
|
|
81
184
|
* valid to use.
|
|
82
185
|
*/
|
|
186
|
+
#valid;
|
|
83
187
|
get valid() {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const ct = this.getHeader('content-type')?.toString() ?? '';
|
|
87
|
-
const dh = this.getHeader('date')?.toString();
|
|
188
|
+
if (this.#valid !== undefined)
|
|
189
|
+
return this.#valid;
|
|
88
190
|
// immutable = never changes
|
|
89
|
-
if (
|
|
90
|
-
return true;
|
|
191
|
+
if (this.cacheControl.immutable)
|
|
192
|
+
return (this.#valid = true);
|
|
91
193
|
// some registries do text/json, some do application/json,
|
|
92
194
|
// some do application/vnd.npm.install-v1+json
|
|
93
195
|
// If it's NOT json, it's an immutable tarball
|
|
94
|
-
|
|
95
|
-
|
|
196
|
+
const ct = this.contentType;
|
|
197
|
+
if (ct && !/\bjson\b/.test(ct))
|
|
198
|
+
return (this.#valid = true);
|
|
96
199
|
// see if the max-age has not yet been crossed
|
|
97
200
|
// default to 5m if maxage is not set, as some registries
|
|
98
201
|
// do not set a cache control header at all.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
202
|
+
if (!this.date)
|
|
203
|
+
return (this.#valid = false);
|
|
204
|
+
this.#valid =
|
|
205
|
+
this.date.getTime() + this.maxAge * 1000 > Date.now();
|
|
206
|
+
return this.#valid;
|
|
104
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Add contents to the entry body.
|
|
210
|
+
*/
|
|
105
211
|
addBody(b) {
|
|
106
|
-
|
|
107
|
-
|
|
212
|
+
// when the content length is uknown we store each chunk in an array that
|
|
213
|
+
// later on is concatenate into a single buffer, otherwise we just append
|
|
214
|
+
// the new chunk of bytes to the already allocated buffer keeping track
|
|
215
|
+
// of the current offset in the `this.#bodyLength` property.
|
|
216
|
+
if (!this.#body) {
|
|
217
|
+
this.#bodyParts.push(b);
|
|
218
|
+
this.#bodyLength += b.byteLength;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
this.#body.set(b, this.#bodyLength);
|
|
222
|
+
this.#bodyLength += b.byteLength;
|
|
223
|
+
}
|
|
108
224
|
}
|
|
109
225
|
get statusCode() {
|
|
110
226
|
return this.#statusCode;
|
|
@@ -113,27 +229,68 @@ export class CacheEntry {
|
|
|
113
229
|
return this.#headers;
|
|
114
230
|
}
|
|
115
231
|
/**
|
|
116
|
-
*
|
|
232
|
+
* Returns the body as a single Uint8Array, concatenating parts if needed.
|
|
233
|
+
*/
|
|
234
|
+
get _body() {
|
|
235
|
+
// if the body is known we'll just use that
|
|
236
|
+
if (this.#body)
|
|
237
|
+
return this.#body;
|
|
238
|
+
// otherwise we concatenate the body parts into a single buffer
|
|
239
|
+
const buffer = new ArrayBuffer(this.#bodyLength);
|
|
240
|
+
const b = new Uint8Array(buffer, 0, this.#bodyLength);
|
|
241
|
+
let off = 0;
|
|
242
|
+
for (const part of this.#bodyParts) {
|
|
243
|
+
b.set(part, off);
|
|
244
|
+
off += part.byteLength;
|
|
245
|
+
}
|
|
246
|
+
return b;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Check that the sri integrity string that was provided to the ctor
|
|
117
250
|
* matches the body that we actually received. This should only be called
|
|
118
251
|
* AFTER the entire body has been completely downloaded.
|
|
119
252
|
*
|
|
253
|
+
* This method **will throw** if the integrity values do not match.
|
|
254
|
+
*
|
|
120
255
|
* Note that this will *usually* not be true if the value is coming out of
|
|
121
256
|
* the cache, because the cache entries are un-gzipped in place. It should
|
|
122
257
|
* _only_ be called for artifacts that come from an actual http response.
|
|
258
|
+
*
|
|
259
|
+
* Returns true if anything was actually verified.
|
|
123
260
|
*/
|
|
124
|
-
checkIntegrity() {
|
|
261
|
+
checkIntegrity(context = {}) {
|
|
125
262
|
if (!this.#integrity)
|
|
126
263
|
return false;
|
|
127
|
-
|
|
264
|
+
if (this.integrityActual !== this.#integrity) {
|
|
265
|
+
throw error('Integrity check failure', {
|
|
266
|
+
code: 'EINTEGRITY',
|
|
267
|
+
response: this,
|
|
268
|
+
wanted: this.#integrity,
|
|
269
|
+
found: this.integrityActual,
|
|
270
|
+
...context,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
128
274
|
}
|
|
129
275
|
get integrityActual() {
|
|
130
276
|
if (this.#integrityActual)
|
|
131
277
|
return this.#integrityActual;
|
|
132
278
|
const hash = createHash('sha512');
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this
|
|
136
|
-
return
|
|
279
|
+
hash.update(this._body);
|
|
280
|
+
const i = `sha512-${hash.digest('base64')}`;
|
|
281
|
+
this.integrityActual = i;
|
|
282
|
+
return i;
|
|
283
|
+
}
|
|
284
|
+
set integrityActual(i) {
|
|
285
|
+
this.#integrityActual = i;
|
|
286
|
+
this.setHeader('integrity', i);
|
|
287
|
+
}
|
|
288
|
+
set integrity(i) {
|
|
289
|
+
if (!this.#integrity && i) {
|
|
290
|
+
this.#integrity = i;
|
|
291
|
+
if (this.#trustIntegrity)
|
|
292
|
+
this.integrityActual = i;
|
|
293
|
+
}
|
|
137
294
|
}
|
|
138
295
|
get integrity() {
|
|
139
296
|
return this.#integrity;
|
|
@@ -144,6 +301,15 @@ export class CacheEntry {
|
|
|
144
301
|
getHeader(h) {
|
|
145
302
|
return getRawHeader(this.#headers, h);
|
|
146
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Give it a key, and it'll return the decoded string of that header value
|
|
306
|
+
*/
|
|
307
|
+
getHeaderString(h) {
|
|
308
|
+
const value = getRawHeader(this.#headers, h);
|
|
309
|
+
if (value) {
|
|
310
|
+
return getDecodedValue(value);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
147
313
|
/**
|
|
148
314
|
* Set a header to a specific value
|
|
149
315
|
*/
|
|
@@ -154,14 +320,7 @@ export class CacheEntry {
|
|
|
154
320
|
* Return the body of the entry as a Buffer
|
|
155
321
|
*/
|
|
156
322
|
buffer() {
|
|
157
|
-
|
|
158
|
-
if (!b)
|
|
159
|
-
return Buffer.allocUnsafe(0);
|
|
160
|
-
if (this.#body.length === 1)
|
|
161
|
-
return b;
|
|
162
|
-
const cat = Buffer.concat(this.#body, this.#bodyLength);
|
|
163
|
-
this.#body = [cat];
|
|
164
|
-
return cat;
|
|
323
|
+
return Buffer.from(this._body.buffer, this._body.byteOffset, this._body.byteLength);
|
|
165
324
|
}
|
|
166
325
|
// return the buffer if it's a tarball, or the parsed
|
|
167
326
|
// JSON if it's not.
|
|
@@ -172,7 +331,7 @@ export class CacheEntry {
|
|
|
172
331
|
get isJSON() {
|
|
173
332
|
if (this.#isJSON !== undefined)
|
|
174
333
|
return this.#isJSON;
|
|
175
|
-
const ct = this.
|
|
334
|
+
const ct = this.getHeaderString('content-type');
|
|
176
335
|
// if it says it's json, assume json
|
|
177
336
|
if (ct)
|
|
178
337
|
return (this.#isJSON = /\bjson\b/.test(ct));
|
|
@@ -190,10 +349,10 @@ export class CacheEntry {
|
|
|
190
349
|
get isGzip() {
|
|
191
350
|
if (this.#isGzip !== undefined)
|
|
192
351
|
return this.#isGzip;
|
|
193
|
-
const ce = this.
|
|
352
|
+
const ce = this.getHeaderString('content-encoding');
|
|
194
353
|
if (ce && !/\bgzip\b/.test(ce))
|
|
195
354
|
return (this.#isGzip = false);
|
|
196
|
-
const buf = this.
|
|
355
|
+
const buf = this._body;
|
|
197
356
|
if (buf.length < 2)
|
|
198
357
|
return false;
|
|
199
358
|
this.#isGzip = buf[0] === 0x1f && buf[1] === 0x8b;
|
|
@@ -216,14 +375,16 @@ export class CacheEntry {
|
|
|
216
375
|
// we know that if we know it's gzip, that the body has been
|
|
217
376
|
// flattened to a single buffer, so save the extra call.
|
|
218
377
|
/* c8 ignore start */
|
|
219
|
-
if (this
|
|
378
|
+
if (this._body.length === 0)
|
|
220
379
|
throw error('Invalid buffer, cant unzip');
|
|
221
380
|
/* c8 ignore stop */
|
|
222
|
-
const b = gunzipSync(this
|
|
381
|
+
const b = gunzipSync(this._body);
|
|
223
382
|
this.setHeader('content-encoding', 'identity');
|
|
224
|
-
|
|
225
|
-
this.#
|
|
226
|
-
this
|
|
383
|
+
const u8 = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
|
|
384
|
+
this.#body = u8;
|
|
385
|
+
this.#bodyLength = u8.byteLength;
|
|
386
|
+
this.#contentLength = u8.byteLength;
|
|
387
|
+
this.setHeader('content-length', String(this.#contentLength));
|
|
227
388
|
this.#isGzip = false;
|
|
228
389
|
return true;
|
|
229
390
|
}
|
|
@@ -235,7 +396,7 @@ export class CacheEntry {
|
|
|
235
396
|
*/
|
|
236
397
|
text() {
|
|
237
398
|
this.unzip();
|
|
238
|
-
return this.
|
|
399
|
+
return getDecodedValue(this._body);
|
|
239
400
|
}
|
|
240
401
|
/**
|
|
241
402
|
* Parse the entry body as JSON and return the result
|
|
@@ -243,7 +404,8 @@ export class CacheEntry {
|
|
|
243
404
|
json() {
|
|
244
405
|
if (this.#json !== undefined)
|
|
245
406
|
return this.#json;
|
|
246
|
-
const
|
|
407
|
+
const text = this.text();
|
|
408
|
+
const obj = JSON.parse(text || '{}');
|
|
247
409
|
this.#json = obj;
|
|
248
410
|
return obj;
|
|
249
411
|
}
|
|
@@ -254,71 +416,103 @@ export class CacheEntry {
|
|
|
254
416
|
*/
|
|
255
417
|
static decode(buffer) {
|
|
256
418
|
if (buffer.length < 4) {
|
|
257
|
-
return
|
|
419
|
+
return emptyCacheEntry;
|
|
258
420
|
}
|
|
259
421
|
const headSize = readSize(buffer, 0);
|
|
260
422
|
if (buffer.length < headSize) {
|
|
261
|
-
return
|
|
423
|
+
return emptyCacheEntry;
|
|
262
424
|
}
|
|
263
|
-
const statusCode = Number(buffer.subarray(4, 7)
|
|
425
|
+
const statusCode = Number(getDecodedValue(buffer.subarray(4, 7)));
|
|
264
426
|
const headersBuffer = buffer.subarray(7, headSize);
|
|
265
|
-
// walk through the headers array, building up the rawHeaders
|
|
427
|
+
// walk through the headers array, building up the rawHeaders
|
|
266
428
|
const headers = [];
|
|
267
429
|
let i = 0;
|
|
430
|
+
let integrity = undefined;
|
|
268
431
|
while (i < headersBuffer.length - 4) {
|
|
269
432
|
const size = readSize(headersBuffer, i);
|
|
270
|
-
|
|
433
|
+
const val = headersBuffer.subarray(i + 4, i + size);
|
|
434
|
+
// if the last one was the key integrity, then this one is the value
|
|
435
|
+
if (headers.length % 2 === 1) {
|
|
436
|
+
const k = getDecodedValue(headers[headers.length - 1]).toLowerCase();
|
|
437
|
+
if (k === 'integrity')
|
|
438
|
+
integrity = getDecodedValue(val);
|
|
439
|
+
}
|
|
440
|
+
headers.push(val);
|
|
271
441
|
i += size;
|
|
272
442
|
}
|
|
273
|
-
const c = new CacheEntry(statusCode, headers);
|
|
274
443
|
const body = buffer.subarray(headSize);
|
|
275
|
-
c
|
|
276
|
-
|
|
277
|
-
|
|
444
|
+
const c = new CacheEntry(statusCode, setRawHeader(headers, 'content-length', String(body.byteLength)), {
|
|
445
|
+
body,
|
|
446
|
+
integrity,
|
|
447
|
+
trustIntegrity: true,
|
|
448
|
+
contentLength: body.byteLength,
|
|
449
|
+
});
|
|
278
450
|
if (c.isJSON) {
|
|
279
451
|
try {
|
|
280
452
|
c.json();
|
|
281
453
|
}
|
|
282
454
|
catch {
|
|
283
|
-
return
|
|
455
|
+
return emptyCacheEntry;
|
|
284
456
|
}
|
|
285
457
|
}
|
|
286
458
|
return c;
|
|
287
459
|
}
|
|
460
|
+
static isGzipEntry(buffer) {
|
|
461
|
+
if (buffer.length < 4)
|
|
462
|
+
return false;
|
|
463
|
+
const headSize = readSize(buffer, 0);
|
|
464
|
+
const gzipBytes = buffer.subarray(headSize, headSize + 2);
|
|
465
|
+
return gzipBytes[0] === 0x1f && gzipBytes[1] === 0x8b;
|
|
466
|
+
}
|
|
288
467
|
/**
|
|
289
468
|
* Encode the entry as a single Buffer for writing to the cache
|
|
290
469
|
*/
|
|
291
|
-
// TODO: should this maybe not concat, and just return Buffer[]?
|
|
292
|
-
// Then we can writev it to the cache file and save the memory copy
|
|
293
470
|
encode() {
|
|
294
|
-
// store json results as a serialized object.
|
|
295
471
|
if (this.isJSON)
|
|
296
472
|
this.json();
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
|
|
473
|
+
const statusStr = String(this.#statusCode);
|
|
474
|
+
const statusBytes = getEncondedValue(statusStr);
|
|
475
|
+
// compute headLength = 4 (length field itself) + statusBytes + Σ(4 + headerLen) for each header item
|
|
476
|
+
let headLength = 4 + statusBytes.byteLength;
|
|
477
|
+
for (const h of this.#headers)
|
|
478
|
+
headLength += 4 + h.byteLength;
|
|
479
|
+
// allocate and fill head length prefix (big-endian)
|
|
480
|
+
const headLenBytes = new Uint8Array(4);
|
|
481
|
+
headLenBytes[0] = (headLength >> 24) & 0xff;
|
|
482
|
+
headLenBytes[1] = (headLength >> 16) & 0xff;
|
|
483
|
+
headLenBytes[2] = (headLength >> 8) & 0xff;
|
|
484
|
+
headLenBytes[3] = headLength & 0xff;
|
|
485
|
+
// header chunks: [len, bytes] for each header item
|
|
486
|
+
const headerChunks = [];
|
|
300
487
|
for (const h of this.#headers) {
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
488
|
+
const l = headLenBytes.byteLength + h.byteLength;
|
|
489
|
+
const lb = new Uint8Array(4);
|
|
490
|
+
lb[0] = (l >> 24) & 0xff;
|
|
491
|
+
lb[1] = (l >> 16) & 0xff;
|
|
492
|
+
lb[2] = (l >> 8) & 0xff;
|
|
493
|
+
lb[3] = l & 0xff;
|
|
494
|
+
headerChunks.push(lb, h);
|
|
495
|
+
}
|
|
496
|
+
// total size
|
|
497
|
+
const total = headLenBytes.byteLength +
|
|
498
|
+
statusBytes.byteLength +
|
|
499
|
+
headerChunks.reduce((n, b) => n + b.byteLength, 0) +
|
|
500
|
+
this._body.byteLength;
|
|
501
|
+
// returns the concatenate buffer with all the pieces
|
|
502
|
+
const outBuffer = new ArrayBuffer(total);
|
|
503
|
+
const out = Buffer.from(outBuffer, 0, total);
|
|
504
|
+
let off = 0;
|
|
505
|
+
out.set(headLenBytes, off);
|
|
506
|
+
off += headLenBytes.byteLength;
|
|
507
|
+
out.set(statusBytes, off);
|
|
508
|
+
off += statusBytes.byteLength;
|
|
509
|
+
for (const chunk of headerChunks) {
|
|
510
|
+
out.set(chunk, off);
|
|
511
|
+
off += chunk.byteLength;
|
|
311
512
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
(headLength >> 24) & 0xff,
|
|
315
|
-
(headLength >> 16) & 0xff,
|
|
316
|
-
(headLength >> 8) & 0xff,
|
|
317
|
-
headLength & 0xff,
|
|
318
|
-
], 0);
|
|
319
|
-
chunks.unshift(hlBuf);
|
|
320
|
-
chunks.push(...this.#body);
|
|
321
|
-
return Buffer.concat(chunks, headLength + this.#bodyLength);
|
|
513
|
+
out.set(this._body, off);
|
|
514
|
+
return out;
|
|
322
515
|
}
|
|
323
516
|
}
|
|
517
|
+
const emptyCacheEntry = new CacheEntry(0, [], { contentLength: 0 });
|
|
324
518
|
//# sourceMappingURL=cache-entry.js.map
|