@serve.zone/coremail 1.0.0
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/.smartconfig.json +50 -0
- package/changelog.md +10 -0
- package/cli.js +4 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.auth.d.ts +45 -0
- package/dist_ts/classes.auth.js +204 -0
- package/dist_ts/classes.config.d.ts +2 -0
- package/dist_ts/classes.config.js +72 -0
- package/dist_ts/classes.coremail.d.ts +41 -0
- package/dist_ts/classes.coremail.js +155 -0
- package/dist_ts/classes.desiredstate.d.ts +40 -0
- package/dist_ts/classes.desiredstate.js +382 -0
- package/dist_ts/classes.gateway.d.ts +62 -0
- package/dist_ts/classes.gateway.js +669 -0
- package/dist_ts/classes.inbound.d.ts +49 -0
- package/dist_ts/classes.inbound.js +702 -0
- package/dist_ts/classes.maintenance.d.ts +16 -0
- package/dist_ts/classes.maintenance.js +339 -0
- package/dist_ts/classes.models.d.ts +161 -0
- package/dist_ts/classes.models.js +595 -0
- package/dist_ts/classes.server.d.ts +40 -0
- package/dist_ts/classes.server.js +309 -0
- package/dist_ts/classes.storage.d.ts +32 -0
- package/dist_ts/classes.storage.js +317 -0
- package/dist_ts/classes.submissions.d.ts +33 -0
- package/dist_ts/classes.submissions.js +385 -0
- package/dist_ts/classes.transfer.d.ts +41 -0
- package/dist_ts/classes.transfer.js +289 -0
- package/dist_ts/coremail.cursor.d.ts +20 -0
- package/dist_ts/coremail.cursor.js +191 -0
- package/dist_ts/coremail.log.d.ts +3 -0
- package/dist_ts/coremail.log.js +11 -0
- package/dist_ts/coremail.mime.d.ts +8 -0
- package/dist_ts/coremail.mime.js +63 -0
- package/dist_ts/coremail.persistence.d.ts +148 -0
- package/dist_ts/coremail.persistence.js +695 -0
- package/dist_ts/coremail.quota.d.ts +29 -0
- package/dist_ts/coremail.quota.js +149 -0
- package/dist_ts/coremail.selectors.d.ts +3 -0
- package/dist_ts/coremail.selectors.js +21 -0
- package/dist_ts/coremail.validation.d.ts +9 -0
- package/dist_ts/coremail.validation.js +325 -0
- package/dist_ts/index.d.ts +4 -0
- package/dist_ts/index.js +39 -0
- package/dist_ts/interfaces.d.ts +92 -0
- package/dist_ts/interfaces.js +2 -0
- package/dist_ts/plugins.d.ts +11 -0
- package/dist_ts/plugins.js +12 -0
- package/license.md +19 -0
- package/package.json +48 -0
- package/readme.md +199 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.auth.ts +321 -0
- package/ts/classes.config.ts +104 -0
- package/ts/classes.coremail.ts +249 -0
- package/ts/classes.desiredstate.ts +508 -0
- package/ts/classes.gateway.ts +811 -0
- package/ts/classes.inbound.ts +898 -0
- package/ts/classes.maintenance.ts +335 -0
- package/ts/classes.models.ts +530 -0
- package/ts/classes.server.ts +444 -0
- package/ts/classes.storage.ts +448 -0
- package/ts/classes.submissions.ts +554 -0
- package/ts/classes.transfer.ts +379 -0
- package/ts/coremail.cursor.ts +328 -0
- package/ts/coremail.log.ts +21 -0
- package/ts/coremail.mime.ts +94 -0
- package/ts/coremail.persistence.ts +1156 -0
- package/ts/coremail.quota.ts +214 -0
- package/ts/coremail.selectors.ts +35 -0
- package/ts/coremail.validation.ts +446 -0
- package/ts/index.ts +38 -0
- package/ts/interfaces.ts +109 -0
- package/ts/plugins.ts +11 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
type TCoreMailSha256 = plugins.serveZoneInterfaces.data.TCoreMailSha256;
|
|
4
|
+
|
|
5
|
+
const digestBuffer = (bufferArg: Uint8Array): TCoreMailSha256 =>
|
|
6
|
+
plugins.serveZoneInterfaces.data.normalizeCoreMailSha256(
|
|
7
|
+
`sha256:${plugins.nodeCrypto.createHash('sha256').update(bufferArg).digest('hex')}`,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
const hashAndBoundStream = (
|
|
11
|
+
streamArg: ReadableStream<Uint8Array>,
|
|
12
|
+
maximumBytesArg: number,
|
|
13
|
+
expectedLengthArg?: number,
|
|
14
|
+
expectedSha256Arg?: TCoreMailSha256,
|
|
15
|
+
signalArg?: AbortSignal,
|
|
16
|
+
idleTimeoutMsArg?: number,
|
|
17
|
+
): {
|
|
18
|
+
stream: ReadableStream<Uint8Array>;
|
|
19
|
+
completion: Promise<{ lengthBytes: number; sha256: TCoreMailSha256 }>;
|
|
20
|
+
cancel: (reasonArg?: unknown) => Promise<void>;
|
|
21
|
+
} => {
|
|
22
|
+
const hash = plugins.nodeCrypto.createHash('sha256');
|
|
23
|
+
let lengthBytes = 0;
|
|
24
|
+
let resolveCompletion!: (valueArg: {
|
|
25
|
+
lengthBytes: number;
|
|
26
|
+
sha256: TCoreMailSha256;
|
|
27
|
+
}) => void;
|
|
28
|
+
let rejectCompletion!: (errorArg: Error) => void;
|
|
29
|
+
const completion = new Promise<{
|
|
30
|
+
lengthBytes: number;
|
|
31
|
+
sha256: TCoreMailSha256;
|
|
32
|
+
}>((resolveArg, rejectArg) => {
|
|
33
|
+
resolveCompletion = resolveArg;
|
|
34
|
+
rejectCompletion = rejectArg;
|
|
35
|
+
});
|
|
36
|
+
const reader = streamArg.getReader();
|
|
37
|
+
let settled = false;
|
|
38
|
+
let readerReleased = false;
|
|
39
|
+
let idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
40
|
+
const releaseReader = () => {
|
|
41
|
+
if (!readerReleased) {
|
|
42
|
+
try {
|
|
43
|
+
reader.releaseLock();
|
|
44
|
+
readerReleased = true;
|
|
45
|
+
} catch {
|
|
46
|
+
// A pending read releases the lock after cancellation settles.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const cancelReader = async (reasonArg: Error): Promise<void> => {
|
|
51
|
+
const cancellation = reader.cancel(reasonArg).catch(() => undefined);
|
|
52
|
+
releaseReader();
|
|
53
|
+
const timeout = new Promise<void>((resolveArg) => {
|
|
54
|
+
const timer = setTimeout(resolveArg, 1_000);
|
|
55
|
+
timer.unref?.();
|
|
56
|
+
void cancellation.finally(() => clearTimeout(timer));
|
|
57
|
+
});
|
|
58
|
+
await Promise.race([cancellation, timeout]);
|
|
59
|
+
releaseReader();
|
|
60
|
+
void cancellation.finally(releaseReader);
|
|
61
|
+
};
|
|
62
|
+
const settleError = (errorArg: Error) => {
|
|
63
|
+
if (!settled) {
|
|
64
|
+
settled = true;
|
|
65
|
+
rejectCompletion(errorArg);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const cancel = async (reasonArg?: unknown): Promise<void> => {
|
|
69
|
+
signalArg?.removeEventListener('abort', onAbort);
|
|
70
|
+
if (settled) {
|
|
71
|
+
releaseReader();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const error = reasonArg instanceof Error
|
|
75
|
+
? reasonArg
|
|
76
|
+
: new Error('CoreMail transfer was cancelled.');
|
|
77
|
+
settleError(error);
|
|
78
|
+
if (idleTimer) {
|
|
79
|
+
clearTimeout(idleTimer);
|
|
80
|
+
idleTimer = null;
|
|
81
|
+
}
|
|
82
|
+
await cancelReader(error);
|
|
83
|
+
};
|
|
84
|
+
const onAbort = () => {
|
|
85
|
+
void cancel(signalArg?.reason);
|
|
86
|
+
};
|
|
87
|
+
if (signalArg) {
|
|
88
|
+
if (signalArg.aborted) {
|
|
89
|
+
void cancel(signalArg.reason);
|
|
90
|
+
} else {
|
|
91
|
+
signalArg.addEventListener('abort', onAbort, { once: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
95
|
+
async pull(controllerArg) {
|
|
96
|
+
try {
|
|
97
|
+
signalArg?.throwIfAborted();
|
|
98
|
+
if (idleTimeoutMsArg !== undefined) {
|
|
99
|
+
idleTimer = setTimeout(() => {
|
|
100
|
+
void cancel(new Error('CoreMail transfer exceeded its idle deadline.'));
|
|
101
|
+
}, idleTimeoutMsArg);
|
|
102
|
+
idleTimer.unref?.();
|
|
103
|
+
}
|
|
104
|
+
const next = await reader.read();
|
|
105
|
+
if (idleTimer) {
|
|
106
|
+
clearTimeout(idleTimer);
|
|
107
|
+
idleTimer = null;
|
|
108
|
+
}
|
|
109
|
+
if (!next.done) {
|
|
110
|
+
if (!(next.value instanceof Uint8Array)) {
|
|
111
|
+
throw new Error('CoreMail transfer emitted a non-byte chunk.');
|
|
112
|
+
}
|
|
113
|
+
lengthBytes += next.value.byteLength;
|
|
114
|
+
if (lengthBytes > maximumBytesArg) {
|
|
115
|
+
throw new Error('CoreMail transfer exceeded its byte budget.');
|
|
116
|
+
}
|
|
117
|
+
hash.update(next.value);
|
|
118
|
+
controllerArg.enqueue(next.value);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const sha256 = plugins.serveZoneInterfaces.data.normalizeCoreMailSha256(
|
|
122
|
+
`sha256:${hash.digest('hex')}`,
|
|
123
|
+
);
|
|
124
|
+
if (
|
|
125
|
+
(expectedLengthArg !== undefined && lengthBytes !== expectedLengthArg)
|
|
126
|
+
|| (expectedSha256Arg !== undefined && sha256 !== expectedSha256Arg)
|
|
127
|
+
) {
|
|
128
|
+
throw new Error('CoreMail object integrity verification failed.');
|
|
129
|
+
}
|
|
130
|
+
settled = true;
|
|
131
|
+
resolveCompletion({ lengthBytes, sha256 });
|
|
132
|
+
controllerArg.close();
|
|
133
|
+
signalArg?.removeEventListener('abort', onAbort);
|
|
134
|
+
releaseReader();
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
137
|
+
if (idleTimer) {
|
|
138
|
+
clearTimeout(idleTimer);
|
|
139
|
+
idleTimer = null;
|
|
140
|
+
}
|
|
141
|
+
settleError(normalized);
|
|
142
|
+
controllerArg.error(normalized);
|
|
143
|
+
await cancelReader(normalized);
|
|
144
|
+
signalArg?.removeEventListener('abort', onAbort);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
async cancel(reasonArg) {
|
|
148
|
+
signalArg?.removeEventListener('abort', onAbort);
|
|
149
|
+
await cancel(reasonArg);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
void completion.catch(() => undefined);
|
|
153
|
+
return {
|
|
154
|
+
stream,
|
|
155
|
+
completion,
|
|
156
|
+
cancel,
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export class CoreMailStorage {
|
|
161
|
+
public readonly smartBucket: plugins.smartbucket.SmartBucket;
|
|
162
|
+
public bucket!: plugins.smartbucket.Bucket;
|
|
163
|
+
private exactUploadCapability?:
|
|
164
|
+
plugins.smartbucket.IExactUploadCapability;
|
|
165
|
+
private exactPurgeCapability?:
|
|
166
|
+
plugins.smartbucket.IExactPathPurgeCapability;
|
|
167
|
+
|
|
168
|
+
public constructor(
|
|
169
|
+
storageDescriptorArg: Record<string, unknown>,
|
|
170
|
+
public readonly bucketName: string,
|
|
171
|
+
) {
|
|
172
|
+
this.smartBucket = new plugins.smartbucket.SmartBucket(
|
|
173
|
+
storageDescriptorArg as never,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public async start(): Promise<void> {
|
|
178
|
+
try {
|
|
179
|
+
const readiness = await this.smartBucket.checkReadiness(this.bucketName, {
|
|
180
|
+
timeoutMs: 5_000,
|
|
181
|
+
});
|
|
182
|
+
if (!readiness.ready) {
|
|
183
|
+
throw new Error(`CoreMail object storage is unavailable: ${readiness.reason}`);
|
|
184
|
+
}
|
|
185
|
+
this.bucket = await this.smartBucket.getBucketByName(this.bucketName);
|
|
186
|
+
const exactUpload = await this.bucket.probeExactUploadCapability({
|
|
187
|
+
signal: AbortSignal.timeout(30_000),
|
|
188
|
+
});
|
|
189
|
+
if (!exactUpload.supported) {
|
|
190
|
+
throw new Error(`CoreMail requires exact object publication: ${exactUpload.reason}`);
|
|
191
|
+
}
|
|
192
|
+
this.exactUploadCapability = exactUpload.capability;
|
|
193
|
+
const exactPurge = await this.bucket.probeExactPathPurgeCapability({
|
|
194
|
+
signal: AbortSignal.timeout(30_000),
|
|
195
|
+
});
|
|
196
|
+
if (!exactPurge.supported) {
|
|
197
|
+
throw new Error(`CoreMail requires exact object retention: ${exactPurge.reason}`);
|
|
198
|
+
}
|
|
199
|
+
this.exactPurgeCapability = exactPurge.capability;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
await this.smartBucket.close().catch(() => undefined);
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public async checkReadiness(): Promise<boolean> {
|
|
207
|
+
const readiness = await this.smartBucket.checkReadiness(this.bucketName, {
|
|
208
|
+
timeoutMs: 1_500,
|
|
209
|
+
});
|
|
210
|
+
return readiness.ready
|
|
211
|
+
&& Boolean(this.exactUploadCapability)
|
|
212
|
+
&& Boolean(this.exactPurgeCapability);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public async stop(): Promise<void> {
|
|
216
|
+
await this.smartBucket.close();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public async purgeExact(
|
|
220
|
+
objectKeyArg: string,
|
|
221
|
+
signalArg?: AbortSignal,
|
|
222
|
+
): Promise<void> {
|
|
223
|
+
if (
|
|
224
|
+
!this.exactPurgeCapability
|
|
225
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._/-]{0,1023}$/.test(objectKeyArg)
|
|
226
|
+
|| objectKeyArg.startsWith('/')
|
|
227
|
+
|| objectKeyArg.includes('//')
|
|
228
|
+
|| objectKeyArg.split('/').some((partArg) => partArg === '..')
|
|
229
|
+
) {
|
|
230
|
+
throw new Error('CoreMail exact purge capability or object key is invalid.');
|
|
231
|
+
}
|
|
232
|
+
await this.bucket.purgeExactPath({
|
|
233
|
+
capability: this.exactPurgeCapability,
|
|
234
|
+
path: objectKeyArg,
|
|
235
|
+
timeoutMs: 30_000,
|
|
236
|
+
signal: signalArg,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public async putExact(
|
|
241
|
+
objectKeyArg: string,
|
|
242
|
+
streamArg: ReadableStream<Uint8Array>,
|
|
243
|
+
lengthBytesArg: number,
|
|
244
|
+
sha256Arg: TCoreMailSha256,
|
|
245
|
+
contentTypeArg: string,
|
|
246
|
+
maximumBytesArg: number,
|
|
247
|
+
signalArg?: AbortSignal,
|
|
248
|
+
): Promise<void> {
|
|
249
|
+
if (!this.exactUploadCapability) {
|
|
250
|
+
throw new Error('CoreMail exact upload capability is unavailable.');
|
|
251
|
+
}
|
|
252
|
+
if (
|
|
253
|
+
!Number.isSafeInteger(lengthBytesArg)
|
|
254
|
+
|| lengthBytesArg < 0
|
|
255
|
+
|| lengthBytesArg > maximumBytesArg
|
|
256
|
+
) {
|
|
257
|
+
throw new Error('CoreMail exact upload length is outside its byte budget.');
|
|
258
|
+
}
|
|
259
|
+
const monitored = hashAndBoundStream(
|
|
260
|
+
streamArg,
|
|
261
|
+
maximumBytesArg,
|
|
262
|
+
lengthBytesArg,
|
|
263
|
+
sha256Arg,
|
|
264
|
+
signalArg,
|
|
265
|
+
plugins.serveZoneInterfaces.data.coreMailLimits.transferIdleTimeoutMs,
|
|
266
|
+
);
|
|
267
|
+
let uploadError: unknown;
|
|
268
|
+
try {
|
|
269
|
+
await this.bucket.fastPutStreamExact({
|
|
270
|
+
capability: this.exactUploadCapability,
|
|
271
|
+
path: objectKeyArg,
|
|
272
|
+
readableStream: monitored.stream,
|
|
273
|
+
contentLength: lengthBytesArg,
|
|
274
|
+
nativeMetadata: {
|
|
275
|
+
'coremail-sha256': sha256Arg.slice('sha256:'.length),
|
|
276
|
+
},
|
|
277
|
+
nativeObjectProperties: {
|
|
278
|
+
contentType: contentTypeArg,
|
|
279
|
+
},
|
|
280
|
+
requestedPartSize: 5 * 1024 * 1024,
|
|
281
|
+
requestedConcurrency: 2,
|
|
282
|
+
failureSettlementTimeoutMs: 30_000,
|
|
283
|
+
signal: signalArg,
|
|
284
|
+
});
|
|
285
|
+
await monitored.completion;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
uploadError = error;
|
|
288
|
+
await monitored.cancel(error);
|
|
289
|
+
await monitored.completion.catch(() => undefined);
|
|
290
|
+
}
|
|
291
|
+
if (uploadError) {
|
|
292
|
+
try {
|
|
293
|
+
await this.verifyObject(
|
|
294
|
+
objectKeyArg,
|
|
295
|
+
lengthBytesArg,
|
|
296
|
+
sha256Arg,
|
|
297
|
+
maximumBytesArg,
|
|
298
|
+
signalArg,
|
|
299
|
+
);
|
|
300
|
+
return;
|
|
301
|
+
} catch {
|
|
302
|
+
throw uploadError;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
await this.verifyObject(
|
|
306
|
+
objectKeyArg,
|
|
307
|
+
lengthBytesArg,
|
|
308
|
+
sha256Arg,
|
|
309
|
+
maximumBytesArg,
|
|
310
|
+
signalArg,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
public async putBytesExact(
|
|
315
|
+
objectKeyArg: string,
|
|
316
|
+
bytesArg: Uint8Array,
|
|
317
|
+
contentTypeArg: string,
|
|
318
|
+
maximumBytesArg: number,
|
|
319
|
+
signalArg?: AbortSignal,
|
|
320
|
+
): Promise<{ lengthBytes: number; sha256: TCoreMailSha256 }> {
|
|
321
|
+
if (bytesArg.byteLength > maximumBytesArg) {
|
|
322
|
+
throw new Error('CoreMail object exceeds its byte budget.');
|
|
323
|
+
}
|
|
324
|
+
const sha256 = digestBuffer(bytesArg);
|
|
325
|
+
await this.putExact(
|
|
326
|
+
objectKeyArg,
|
|
327
|
+
new ReadableStream<Uint8Array>({
|
|
328
|
+
start(controllerArg) {
|
|
329
|
+
controllerArg.enqueue(bytesArg);
|
|
330
|
+
controllerArg.close();
|
|
331
|
+
},
|
|
332
|
+
}),
|
|
333
|
+
bytesArg.byteLength,
|
|
334
|
+
sha256,
|
|
335
|
+
contentTypeArg,
|
|
336
|
+
maximumBytesArg,
|
|
337
|
+
signalArg,
|
|
338
|
+
);
|
|
339
|
+
return { lengthBytes: bytesArg.byteLength, sha256 };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
public async getVerifiedStream(
|
|
343
|
+
objectKeyArg: string,
|
|
344
|
+
lengthBytesArg: number,
|
|
345
|
+
sha256Arg: TCoreMailSha256,
|
|
346
|
+
maximumBytesArg: number,
|
|
347
|
+
signalArg?: AbortSignal,
|
|
348
|
+
): Promise<{
|
|
349
|
+
stream: ReadableStream<Uint8Array>;
|
|
350
|
+
completion: Promise<{ lengthBytes: number; sha256: TCoreMailSha256 }>;
|
|
351
|
+
cancel: (reasonArg?: unknown) => Promise<void>;
|
|
352
|
+
}> {
|
|
353
|
+
signalArg?.throwIfAborted();
|
|
354
|
+
const stat = await this.bucket.fastStat({
|
|
355
|
+
path: objectKeyArg,
|
|
356
|
+
signal: signalArg,
|
|
357
|
+
});
|
|
358
|
+
if (stat.ContentLength !== lengthBytesArg || lengthBytesArg > maximumBytesArg) {
|
|
359
|
+
throw new Error('CoreMail object length does not match persisted integrity.');
|
|
360
|
+
}
|
|
361
|
+
return hashAndBoundStream(
|
|
362
|
+
await this.bucket.fastGetStream({
|
|
363
|
+
path: objectKeyArg,
|
|
364
|
+
signal: signalArg,
|
|
365
|
+
}, 'webstream') as
|
|
366
|
+
ReadableStream<Uint8Array>,
|
|
367
|
+
maximumBytesArg,
|
|
368
|
+
lengthBytesArg,
|
|
369
|
+
sha256Arg,
|
|
370
|
+
signalArg,
|
|
371
|
+
plugins.serveZoneInterfaces.data.coreMailLimits.transferIdleTimeoutMs,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public async getVerifiedBytes(
|
|
376
|
+
objectKeyArg: string,
|
|
377
|
+
lengthBytesArg: number,
|
|
378
|
+
sha256Arg: TCoreMailSha256,
|
|
379
|
+
maximumBytesArg: number,
|
|
380
|
+
): Promise<Uint8Array> {
|
|
381
|
+
const verified = await this.getVerifiedStream(
|
|
382
|
+
objectKeyArg,
|
|
383
|
+
lengthBytesArg,
|
|
384
|
+
sha256Arg,
|
|
385
|
+
maximumBytesArg,
|
|
386
|
+
AbortSignal.timeout(
|
|
387
|
+
plugins.serveZoneInterfaces.data.coreMailLimits.transferOverallTimeoutMs,
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
const reader = verified.stream.getReader();
|
|
391
|
+
const chunks: Uint8Array[] = [];
|
|
392
|
+
let total = 0;
|
|
393
|
+
try {
|
|
394
|
+
while (true) {
|
|
395
|
+
const next = await reader.read();
|
|
396
|
+
if (next.done) break;
|
|
397
|
+
chunks.push(next.value);
|
|
398
|
+
total += next.value.byteLength;
|
|
399
|
+
}
|
|
400
|
+
await verified.completion;
|
|
401
|
+
} finally {
|
|
402
|
+
await reader.cancel().catch(() => undefined);
|
|
403
|
+
reader.releaseLock();
|
|
404
|
+
}
|
|
405
|
+
const result = new Uint8Array(total);
|
|
406
|
+
let offset = 0;
|
|
407
|
+
for (const chunk of chunks) {
|
|
408
|
+
result.set(chunk, offset);
|
|
409
|
+
offset += chunk.byteLength;
|
|
410
|
+
}
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
public async verifyObject(
|
|
415
|
+
objectKeyArg: string,
|
|
416
|
+
lengthBytesArg: number,
|
|
417
|
+
sha256Arg: TCoreMailSha256,
|
|
418
|
+
maximumBytesArg: number,
|
|
419
|
+
signalArg?: AbortSignal,
|
|
420
|
+
): Promise<void> {
|
|
421
|
+
signalArg?.throwIfAborted();
|
|
422
|
+
const verified = await this.getVerifiedStream(
|
|
423
|
+
objectKeyArg,
|
|
424
|
+
lengthBytesArg,
|
|
425
|
+
sha256Arg,
|
|
426
|
+
maximumBytesArg,
|
|
427
|
+
signalArg,
|
|
428
|
+
);
|
|
429
|
+
const reader = verified.stream.getReader();
|
|
430
|
+
try {
|
|
431
|
+
while (!(await reader.read()).done) {
|
|
432
|
+
signalArg?.throwIfAborted();
|
|
433
|
+
}
|
|
434
|
+
await verified.completion;
|
|
435
|
+
} finally {
|
|
436
|
+
await reader.cancel().catch(() => undefined);
|
|
437
|
+
reader.releaseLock();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export const createSha256 = (valueArg: Uint8Array | string): TCoreMailSha256 =>
|
|
443
|
+
plugins.serveZoneInterfaces.data.normalizeCoreMailSha256(
|
|
444
|
+
`sha256:${plugins.nodeCrypto.createHash('sha256').update(valueArg).digest('hex')}`,
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
export const createOpaqueToken = (): string =>
|
|
448
|
+
plugins.nodeCrypto.randomBytes(32).toString('base64url');
|