@ccpocket/bridge 1.74.0 → 1.76.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/README.md +4 -0
- package/dist/index.js +36 -6
- package/dist/index.js.map +1 -1
- package/dist/media-store.d.ts +2 -1
- package/dist/media-store.js +39 -3
- package/dist/media-store.js.map +1 -1
- package/dist/parser.d.ts +44 -0
- package/dist/parser.js +58 -0
- package/dist/parser.js.map +1 -1
- package/dist/upload-store.d.ts +84 -0
- package/dist/upload-store.js +559 -0
- package/dist/upload-store.js.map +1 -0
- package/dist/websocket.d.ts +15 -0
- package/dist/websocket.js +215 -2
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { link, lstat, open, readdir, realpath, rename, stat, unlink, } from "node:fs/promises";
|
|
4
|
+
import { basename, extname, join } from "node:path";
|
|
5
|
+
const DEFAULT_TTL_MS = 60 * 60 * 1000;
|
|
6
|
+
const DEFAULT_MAX_ENTRIES = 100;
|
|
7
|
+
const DEFAULT_MAX_RESERVED_BYTES = 2 * 1024 * 1024 * 1024;
|
|
8
|
+
const DEFAULT_MAX_CONCURRENT_RECEIVES = 4;
|
|
9
|
+
const DEFAULT_RECEIVE_TIMEOUT_MS = 60 * 60 * 1000;
|
|
10
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 60 * 1000;
|
|
11
|
+
const UPLOAD_PATH_PATTERN = /^\/api\/uploads\/([a-f0-9]{48})$/;
|
|
12
|
+
const INTERNAL_PREFIX = ".ccpocket-upload-";
|
|
13
|
+
export class UploadStoreError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
constructor(code, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Stores short-lived upload capabilities and publishes verified files. */
|
|
21
|
+
export class UploadStore {
|
|
22
|
+
entries = new Map();
|
|
23
|
+
completed = new Map();
|
|
24
|
+
finalizations = new Map();
|
|
25
|
+
registrations = new Set();
|
|
26
|
+
removals = new Map();
|
|
27
|
+
ttlMs;
|
|
28
|
+
maxEntries;
|
|
29
|
+
maxReservedBytes;
|
|
30
|
+
maxConcurrentReceives;
|
|
31
|
+
maxCompletedEntries;
|
|
32
|
+
receiveTimeoutMs;
|
|
33
|
+
idleTimeoutMs;
|
|
34
|
+
now;
|
|
35
|
+
cleanupTimer;
|
|
36
|
+
reservedBytes = 0;
|
|
37
|
+
pendingRegistrations = 0;
|
|
38
|
+
activeReceives = 0;
|
|
39
|
+
disposed = false;
|
|
40
|
+
disposePromise = null;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
43
|
+
this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
44
|
+
this.maxReservedBytes =
|
|
45
|
+
options.maxReservedBytes ?? DEFAULT_MAX_RESERVED_BYTES;
|
|
46
|
+
this.maxConcurrentReceives =
|
|
47
|
+
options.maxConcurrentReceives ?? DEFAULT_MAX_CONCURRENT_RECEIVES;
|
|
48
|
+
this.maxCompletedEntries = options.maxCompletedEntries ?? this.maxEntries;
|
|
49
|
+
this.receiveTimeoutMs =
|
|
50
|
+
options.receiveTimeoutMs ?? DEFAULT_RECEIVE_TIMEOUT_MS;
|
|
51
|
+
this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
52
|
+
this.now = options.now ?? Date.now;
|
|
53
|
+
this.cleanupTimer = setInterval(() => void this.removeExpired(), options.cleanupIntervalMs ?? Math.min(this.ttlMs, 60_000));
|
|
54
|
+
this.cleanupTimer.unref();
|
|
55
|
+
}
|
|
56
|
+
register(options) {
|
|
57
|
+
if (this.disposed) {
|
|
58
|
+
return Promise.reject(new UploadStoreError("file_upload_failed", "The Bridge is shutting down and cannot accept uploads."));
|
|
59
|
+
}
|
|
60
|
+
const promise = this.registerActive(options);
|
|
61
|
+
this.registrations.add(promise);
|
|
62
|
+
void promise.then(() => this.registrations.delete(promise), () => this.registrations.delete(promise));
|
|
63
|
+
return promise;
|
|
64
|
+
}
|
|
65
|
+
async registerActive(options) {
|
|
66
|
+
await this.removeExpired();
|
|
67
|
+
if (this.entries.size + this.pendingRegistrations >= this.maxEntries ||
|
|
68
|
+
options.sizeBytes > this.maxReservedBytes - this.reservedBytes) {
|
|
69
|
+
throw new UploadStoreError("file_upload_capacity_exceeded", "Bridge upload capacity is currently full. Try again after active uploads finish.");
|
|
70
|
+
}
|
|
71
|
+
this.pendingRegistrations += 1;
|
|
72
|
+
this.reservedBytes += options.sizeBytes;
|
|
73
|
+
let committed = false;
|
|
74
|
+
let fileHandle = null;
|
|
75
|
+
let stagingPath = null;
|
|
76
|
+
try {
|
|
77
|
+
const directoryPath = await realpath(options.directoryPath);
|
|
78
|
+
const directoryStat = await stat(directoryPath);
|
|
79
|
+
if (!directoryStat.isDirectory()) {
|
|
80
|
+
throw new Error("Upload destination is not a directory");
|
|
81
|
+
}
|
|
82
|
+
await this.scavengeDirectory(directoryPath);
|
|
83
|
+
const token = randomBytes(24).toString("hex");
|
|
84
|
+
stagingPath = join(directoryPath, `${INTERNAL_PREFIX}${randomBytes(24).toString("hex")}.part`);
|
|
85
|
+
const noFollow = process.platform === "win32" ? 0 : constants.O_NOFOLLOW;
|
|
86
|
+
fileHandle = await open(stagingPath, constants.O_CREAT |
|
|
87
|
+
constants.O_EXCL |
|
|
88
|
+
constants.O_RDWR |
|
|
89
|
+
noFollow, 0o600);
|
|
90
|
+
const stagingStat = await fileHandle.stat();
|
|
91
|
+
const now = this.now();
|
|
92
|
+
this.entries.set(token, {
|
|
93
|
+
directoryPath,
|
|
94
|
+
relativeDirectoryPath: options.relativeDirectoryPath,
|
|
95
|
+
directoryDevice: directoryStat.dev,
|
|
96
|
+
directoryInode: directoryStat.ino,
|
|
97
|
+
fileName: options.fileName,
|
|
98
|
+
sizeBytes: options.sizeBytes,
|
|
99
|
+
conflictPolicy: options.conflictPolicy,
|
|
100
|
+
stagingPath,
|
|
101
|
+
stagingDevice: stagingStat.dev,
|
|
102
|
+
stagingInode: stagingStat.ino,
|
|
103
|
+
fileHandle,
|
|
104
|
+
receiving: false,
|
|
105
|
+
expiresAt: now + this.ttlMs,
|
|
106
|
+
});
|
|
107
|
+
committed = true;
|
|
108
|
+
return { token, url: `/api/uploads/${token}` };
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
await fileHandle?.close().catch(() => undefined);
|
|
112
|
+
if (stagingPath)
|
|
113
|
+
await unlink(stagingPath).catch(() => undefined);
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
this.pendingRegistrations = Math.max(0, this.pendingRegistrations - 1);
|
|
118
|
+
if (!committed) {
|
|
119
|
+
this.reservedBytes = Math.max(0, this.reservedBytes - options.sizeBytes);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
handleRequest(req, res, onReceiveStarted) {
|
|
124
|
+
const url = new URL(req.url ?? "", "http://localhost");
|
|
125
|
+
const match = UPLOAD_PATH_PATTERN.exec(url.pathname);
|
|
126
|
+
if (!match)
|
|
127
|
+
return false;
|
|
128
|
+
if (this.disposed) {
|
|
129
|
+
this.sendText(res, 503, "Bridge is shutting down.");
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (req.method !== "PUT") {
|
|
133
|
+
res.writeHead(405, { Allow: "PUT", "Content-Type": "text/plain" });
|
|
134
|
+
res.end("Method Not Allowed");
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
const token = match[1];
|
|
138
|
+
const entry = this.getActiveEntry(token);
|
|
139
|
+
if (!entry || entry.receiving || entry.incomingSha256 !== undefined) {
|
|
140
|
+
this.sendText(res, 404, "Not Found");
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (this.activeReceives >= this.maxConcurrentReceives) {
|
|
144
|
+
this.sendText(res, 429, "Too many uploads are currently in progress.", {
|
|
145
|
+
"Retry-After": "5",
|
|
146
|
+
});
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
void this.receive(req, res, token, entry, onReceiveStarted);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
async finalize(token, expectedSha256) {
|
|
153
|
+
if (this.disposed) {
|
|
154
|
+
throw new UploadStoreError("file_upload_failed", "The Bridge is shutting down and cannot finish uploads.");
|
|
155
|
+
}
|
|
156
|
+
const normalizedSha256 = expectedSha256.toLowerCase();
|
|
157
|
+
const previous = this.completed.get(token);
|
|
158
|
+
if (previous && previous.expiresAt > this.now()) {
|
|
159
|
+
if (previous.sha256 !== normalizedSha256) {
|
|
160
|
+
throw new UploadStoreError("file_upload_integrity_failed", "The uploaded file failed integrity verification.");
|
|
161
|
+
}
|
|
162
|
+
return previous.result;
|
|
163
|
+
}
|
|
164
|
+
const ongoing = this.finalizations.get(token);
|
|
165
|
+
if (ongoing) {
|
|
166
|
+
if (ongoing.sha256 !== normalizedSha256) {
|
|
167
|
+
throw new UploadStoreError("file_upload_integrity_failed", "The uploaded file failed integrity verification.");
|
|
168
|
+
}
|
|
169
|
+
return ongoing.promise;
|
|
170
|
+
}
|
|
171
|
+
const promise = this.finalizeActive(token, normalizedSha256);
|
|
172
|
+
this.finalizations.set(token, { sha256: normalizedSha256, promise });
|
|
173
|
+
try {
|
|
174
|
+
return await promise;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
if (this.finalizations.get(token)?.promise === promise) {
|
|
178
|
+
this.finalizations.delete(token);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async finalizeActive(token, normalizedSha256) {
|
|
183
|
+
const entry = this.getActiveEntry(token);
|
|
184
|
+
if (!entry) {
|
|
185
|
+
throw new UploadStoreError("file_upload_not_found", "Upload expired or was not found.");
|
|
186
|
+
}
|
|
187
|
+
if (entry.receiving ||
|
|
188
|
+
!entry.incomingSha256 ||
|
|
189
|
+
entry.receivedBytes !== entry.sizeBytes) {
|
|
190
|
+
throw new UploadStoreError("file_upload_incomplete", "The upload has not completed.");
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
await this.verifyDirectory(entry);
|
|
194
|
+
const pinnedSha256 = await this.hashPinnedFile(entry);
|
|
195
|
+
if (entry.incomingSha256 !== normalizedSha256 ||
|
|
196
|
+
pinnedSha256 !== normalizedSha256) {
|
|
197
|
+
throw new UploadStoreError("file_upload_integrity_failed", "The uploaded file failed integrity verification.");
|
|
198
|
+
}
|
|
199
|
+
await this.verifyStagingPath(entry);
|
|
200
|
+
await this.closeEntryHandle(entry);
|
|
201
|
+
await this.verifyDirectory(entry);
|
|
202
|
+
await this.verifyStagingPath(entry);
|
|
203
|
+
const published = await this.publish(entry);
|
|
204
|
+
const result = {
|
|
205
|
+
fileName: published.fileName,
|
|
206
|
+
filePath: entry.relativeDirectoryPath
|
|
207
|
+
? `${entry.relativeDirectoryPath}/${published.fileName}`
|
|
208
|
+
: published.fileName,
|
|
209
|
+
sizeBytes: entry.sizeBytes,
|
|
210
|
+
sha256: pinnedSha256,
|
|
211
|
+
skipped: published.skipped,
|
|
212
|
+
};
|
|
213
|
+
this.releaseEntry(token, entry);
|
|
214
|
+
this.completed.set(token, {
|
|
215
|
+
sha256: normalizedSha256,
|
|
216
|
+
result,
|
|
217
|
+
expiresAt: this.now() + this.ttlMs,
|
|
218
|
+
});
|
|
219
|
+
this.trimCompleted();
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
await this.removeEntry(token);
|
|
224
|
+
if (error instanceof UploadStoreError)
|
|
225
|
+
throw error;
|
|
226
|
+
throw new UploadStoreError("file_upload_failed", "Unable to publish the uploaded file.");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async cancel(token) {
|
|
230
|
+
this.completed.delete(token);
|
|
231
|
+
await this.removeEntry(token);
|
|
232
|
+
}
|
|
233
|
+
dispose() {
|
|
234
|
+
this.disposed = true;
|
|
235
|
+
this.disposePromise ??= this.disposeActive();
|
|
236
|
+
return this.disposePromise;
|
|
237
|
+
}
|
|
238
|
+
async disposeActive() {
|
|
239
|
+
clearInterval(this.cleanupTimer);
|
|
240
|
+
await Promise.allSettled([...this.registrations]);
|
|
241
|
+
await Promise.all([...this.entries.keys()].map((token) => this.removeEntry(token)));
|
|
242
|
+
await Promise.allSettled([...this.finalizations.values()].map((value) => value.promise));
|
|
243
|
+
this.completed.clear();
|
|
244
|
+
}
|
|
245
|
+
trimCompleted() {
|
|
246
|
+
while (this.completed.size > this.maxCompletedEntries) {
|
|
247
|
+
const oldestToken = this.completed.keys().next().value;
|
|
248
|
+
if (!oldestToken)
|
|
249
|
+
return;
|
|
250
|
+
this.completed.delete(oldestToken);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async receive(req, res, token, entry, onReceiveStarted) {
|
|
254
|
+
const contentLength = Number(req.headers["content-length"]);
|
|
255
|
+
if (!Number.isSafeInteger(contentLength) ||
|
|
256
|
+
contentLength !== entry.sizeBytes) {
|
|
257
|
+
await this.removeEntry(token);
|
|
258
|
+
this.sendText(res, contentLength > entry.sizeBytes ? 413 : 400, "Content-Length does not match the prepared upload.");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const fileHandle = entry.fileHandle;
|
|
262
|
+
if (!fileHandle) {
|
|
263
|
+
await this.removeEntry(token);
|
|
264
|
+
this.sendText(res, 404, "Not Found");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
entry.receiving = true;
|
|
268
|
+
entry.request = req;
|
|
269
|
+
this.activeReceives += 1;
|
|
270
|
+
const hash = createHash("sha256");
|
|
271
|
+
let receivedBytes = 0;
|
|
272
|
+
let timedOut = false;
|
|
273
|
+
let idleTimer;
|
|
274
|
+
const abortForTimeout = () => {
|
|
275
|
+
timedOut = true;
|
|
276
|
+
req.destroy(new Error("Upload timed out"));
|
|
277
|
+
};
|
|
278
|
+
const totalTimer = setTimeout(abortForTimeout, this.receiveTimeoutMs);
|
|
279
|
+
const resetIdleTimer = () => {
|
|
280
|
+
clearTimeout(idleTimer);
|
|
281
|
+
idleTimer = setTimeout(abortForTimeout, this.idleTimeoutMs);
|
|
282
|
+
};
|
|
283
|
+
idleTimer = setTimeout(abortForTimeout, this.idleTimeoutMs);
|
|
284
|
+
onReceiveStarted?.();
|
|
285
|
+
const finishReceive = () => {
|
|
286
|
+
clearTimeout(totalTimer);
|
|
287
|
+
clearTimeout(idleTimer);
|
|
288
|
+
entry.receiving = false;
|
|
289
|
+
entry.request = undefined;
|
|
290
|
+
this.activeReceives = Math.max(0, this.activeReceives - 1);
|
|
291
|
+
};
|
|
292
|
+
try {
|
|
293
|
+
await fileHandle.truncate(0);
|
|
294
|
+
for await (const value of req) {
|
|
295
|
+
resetIdleTimer();
|
|
296
|
+
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
297
|
+
if (receivedBytes + chunk.length > entry.sizeBytes) {
|
|
298
|
+
throw new Error("Upload exceeds the prepared size");
|
|
299
|
+
}
|
|
300
|
+
let written = 0;
|
|
301
|
+
while (written < chunk.length) {
|
|
302
|
+
const result = await fileHandle.write(chunk, written, chunk.length - written, receivedBytes + written);
|
|
303
|
+
if (result.bytesWritten <= 0) {
|
|
304
|
+
throw new Error("Upload write did not make progress");
|
|
305
|
+
}
|
|
306
|
+
written += result.bytesWritten;
|
|
307
|
+
}
|
|
308
|
+
receivedBytes += chunk.length;
|
|
309
|
+
hash.update(chunk);
|
|
310
|
+
}
|
|
311
|
+
if (receivedBytes !== entry.sizeBytes) {
|
|
312
|
+
throw new Error("Upload did not match the prepared size");
|
|
313
|
+
}
|
|
314
|
+
finishReceive();
|
|
315
|
+
entry.receivedBytes = receivedBytes;
|
|
316
|
+
entry.incomingSha256 = hash.digest("hex");
|
|
317
|
+
res.writeHead(201, {
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
"Cache-Control": "private, no-store",
|
|
320
|
+
"X-File-SHA256": entry.incomingSha256,
|
|
321
|
+
"X-Received-Bytes": String(receivedBytes),
|
|
322
|
+
});
|
|
323
|
+
res.end(JSON.stringify({
|
|
324
|
+
sha256: entry.incomingSha256,
|
|
325
|
+
receivedBytes,
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
finishReceive();
|
|
330
|
+
await this.removeEntry(token);
|
|
331
|
+
if (!res.headersSent) {
|
|
332
|
+
this.sendText(res, timedOut ? 408 : 400, timedOut ? "Upload timed out." : "Upload did not complete.");
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
res.destroy();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async hashPinnedFile(entry) {
|
|
340
|
+
const fileHandle = entry.fileHandle;
|
|
341
|
+
if (!fileHandle) {
|
|
342
|
+
throw new UploadStoreError("file_upload_integrity_failed", "The staged upload is unavailable.");
|
|
343
|
+
}
|
|
344
|
+
const fileStat = await fileHandle.stat();
|
|
345
|
+
if (!fileStat.isFile() ||
|
|
346
|
+
fileStat.size !== entry.sizeBytes ||
|
|
347
|
+
fileStat.dev !== entry.stagingDevice ||
|
|
348
|
+
fileStat.ino !== entry.stagingInode) {
|
|
349
|
+
throw new UploadStoreError("file_upload_integrity_failed", "The staged upload changed before it could be published.");
|
|
350
|
+
}
|
|
351
|
+
const hash = createHash("sha256");
|
|
352
|
+
if (fileStat.size === 0)
|
|
353
|
+
return hash.digest("hex");
|
|
354
|
+
const stream = fileHandle.createReadStream({
|
|
355
|
+
autoClose: false,
|
|
356
|
+
start: 0,
|
|
357
|
+
end: fileStat.size - 1,
|
|
358
|
+
});
|
|
359
|
+
for await (const chunk of stream)
|
|
360
|
+
hash.update(chunk);
|
|
361
|
+
return hash.digest("hex");
|
|
362
|
+
}
|
|
363
|
+
async publish(entry) {
|
|
364
|
+
const destination = join(entry.directoryPath, entry.fileName);
|
|
365
|
+
if (entry.conflictPolicy === "overwrite") {
|
|
366
|
+
try {
|
|
367
|
+
const existing = await lstat(destination);
|
|
368
|
+
if (!existing.isFile() || existing.isSymbolicLink()) {
|
|
369
|
+
throw new UploadStoreError("file_upload_conflict", "The destination is not a regular file.");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
if (error.code !== "ENOENT")
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
await this.replaceFile(entry, destination);
|
|
377
|
+
return { fileName: entry.fileName, skipped: false };
|
|
378
|
+
}
|
|
379
|
+
const candidates = entry.conflictPolicy === "skip"
|
|
380
|
+
? [entry.fileName]
|
|
381
|
+
: this.renameCandidates(entry.fileName);
|
|
382
|
+
for (const candidate of candidates) {
|
|
383
|
+
const candidatePath = join(entry.directoryPath, candidate);
|
|
384
|
+
try {
|
|
385
|
+
await link(entry.stagingPath, candidatePath);
|
|
386
|
+
await unlink(entry.stagingPath);
|
|
387
|
+
return { fileName: candidate, skipped: false };
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (error.code !== "EEXIST")
|
|
391
|
+
throw error;
|
|
392
|
+
if (entry.conflictPolicy === "skip") {
|
|
393
|
+
await unlink(entry.stagingPath).catch(() => undefined);
|
|
394
|
+
return { fileName: entry.fileName, skipped: true };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
throw new UploadStoreError("file_upload_conflict", "Unable to find an available file name.");
|
|
399
|
+
}
|
|
400
|
+
async replaceFile(entry, destination) {
|
|
401
|
+
// Node's rename uses the platform's replace-existing primitive. If the OS
|
|
402
|
+
// cannot perform that atomic operation (for example because an antivirus
|
|
403
|
+
// holds the destination on Windows), fail without moving the original to a
|
|
404
|
+
// hidden backup or attempting a non-atomic fallback.
|
|
405
|
+
await rename(entry.stagingPath, destination);
|
|
406
|
+
}
|
|
407
|
+
*renameCandidates(fileName) {
|
|
408
|
+
yield fileName;
|
|
409
|
+
const extension = extname(fileName);
|
|
410
|
+
const stem = extension ? fileName.slice(0, -extension.length) : fileName;
|
|
411
|
+
for (let index = 1; index <= 10_000; index += 1) {
|
|
412
|
+
const suffix = ` (${index})`;
|
|
413
|
+
const fixed = `${suffix}${extension}`;
|
|
414
|
+
const availableStemBytes = 255 - Buffer.byteLength(fixed, "utf8");
|
|
415
|
+
if (availableStemBytes > 0) {
|
|
416
|
+
yield `${truncateUtf8(stem, availableStemBytes)}${fixed}`;
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
yield `${truncateUtf8(stem || "file", 255 - Buffer.byteLength(suffix, "utf8"))}${suffix}`;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async verifyDirectory(entry) {
|
|
424
|
+
const currentRealPath = await realpath(entry.directoryPath).catch(() => "");
|
|
425
|
+
const currentStat = currentRealPath
|
|
426
|
+
? await stat(currentRealPath).catch(() => null)
|
|
427
|
+
: null;
|
|
428
|
+
if (currentRealPath !== entry.directoryPath ||
|
|
429
|
+
!currentStat?.isDirectory() ||
|
|
430
|
+
currentStat.dev !== entry.directoryDevice ||
|
|
431
|
+
currentStat.ino !== entry.directoryInode) {
|
|
432
|
+
throw new UploadStoreError("file_upload_directory_changed", "The upload destination changed during transfer.");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async verifyStagingPath(entry) {
|
|
436
|
+
const stagingStat = await lstat(entry.stagingPath).catch(() => null);
|
|
437
|
+
if (!stagingStat?.isFile() ||
|
|
438
|
+
stagingStat.isSymbolicLink() ||
|
|
439
|
+
stagingStat.dev !== entry.stagingDevice ||
|
|
440
|
+
stagingStat.ino !== entry.stagingInode) {
|
|
441
|
+
throw new UploadStoreError("file_upload_integrity_failed", "The staged upload changed before it could be published.");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
getActiveEntry(token) {
|
|
445
|
+
const entry = this.entries.get(token);
|
|
446
|
+
if (!entry)
|
|
447
|
+
return null;
|
|
448
|
+
if (entry.expiresAt <= this.now()) {
|
|
449
|
+
void this.removeEntry(token);
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
return entry;
|
|
453
|
+
}
|
|
454
|
+
async removeExpired() {
|
|
455
|
+
const now = this.now();
|
|
456
|
+
await Promise.all([...this.entries.entries()]
|
|
457
|
+
.filter(([, entry]) => entry.expiresAt <= now)
|
|
458
|
+
.map(([token]) => this.removeEntry(token)));
|
|
459
|
+
for (const [token, value] of this.completed) {
|
|
460
|
+
if (value.expiresAt <= now)
|
|
461
|
+
this.completed.delete(token);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async removeEntry(token) {
|
|
465
|
+
const ongoing = this.removals.get(token);
|
|
466
|
+
if (ongoing)
|
|
467
|
+
return ongoing;
|
|
468
|
+
const entry = this.entries.get(token);
|
|
469
|
+
if (!entry)
|
|
470
|
+
return;
|
|
471
|
+
const promise = this.removeEntryActive(token, entry);
|
|
472
|
+
this.removals.set(token, promise);
|
|
473
|
+
try {
|
|
474
|
+
await promise;
|
|
475
|
+
}
|
|
476
|
+
finally {
|
|
477
|
+
if (this.removals.get(token) === promise)
|
|
478
|
+
this.removals.delete(token);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async removeEntryActive(token, entry) {
|
|
482
|
+
this.releaseEntry(token, entry);
|
|
483
|
+
entry.request?.destroy();
|
|
484
|
+
await this.closeEntryHandle(entry);
|
|
485
|
+
if (await this.stagingPathMatches(entry)) {
|
|
486
|
+
await unlink(entry.stagingPath).catch(() => undefined);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
releaseEntry(token, entry) {
|
|
490
|
+
if (!this.entries.delete(token))
|
|
491
|
+
return;
|
|
492
|
+
this.reservedBytes = Math.max(0, this.reservedBytes - entry.sizeBytes);
|
|
493
|
+
}
|
|
494
|
+
async closeEntryHandle(entry) {
|
|
495
|
+
const fileHandle = entry.fileHandle;
|
|
496
|
+
entry.fileHandle = null;
|
|
497
|
+
if (fileHandle)
|
|
498
|
+
await fileHandle.close().catch(() => undefined);
|
|
499
|
+
}
|
|
500
|
+
async stagingPathMatches(entry) {
|
|
501
|
+
const fileStat = await lstat(entry.stagingPath).catch(() => null);
|
|
502
|
+
return (fileStat?.isFile() === true &&
|
|
503
|
+
!fileStat.isSymbolicLink() &&
|
|
504
|
+
fileStat.dev === entry.stagingDevice &&
|
|
505
|
+
fileStat.ino === entry.stagingInode);
|
|
506
|
+
}
|
|
507
|
+
async scavengeDirectory(directoryPath) {
|
|
508
|
+
const cutoff = this.now() - this.ttlMs;
|
|
509
|
+
const names = await readdir(directoryPath).catch(() => []);
|
|
510
|
+
await Promise.all(names
|
|
511
|
+
.filter((name) => name.startsWith(INTERNAL_PREFIX) &&
|
|
512
|
+
name.endsWith(".part"))
|
|
513
|
+
.map(async (name) => {
|
|
514
|
+
const path = join(directoryPath, name);
|
|
515
|
+
const fileStat = await lstat(path).catch(() => null);
|
|
516
|
+
if (fileStat?.isFile() &&
|
|
517
|
+
!fileStat.isSymbolicLink() &&
|
|
518
|
+
fileStat.mtimeMs <= cutoff) {
|
|
519
|
+
await unlink(path).catch(() => undefined);
|
|
520
|
+
}
|
|
521
|
+
}));
|
|
522
|
+
}
|
|
523
|
+
sendText(res, statusCode, body, extraHeaders = {}) {
|
|
524
|
+
res.writeHead(statusCode, {
|
|
525
|
+
"Content-Type": "text/plain",
|
|
526
|
+
"Cache-Control": "private, no-store",
|
|
527
|
+
...extraHeaders,
|
|
528
|
+
});
|
|
529
|
+
res.end(body);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
export function isSafeUploadFileName(fileName) {
|
|
533
|
+
if (!fileName ||
|
|
534
|
+
fileName === "." ||
|
|
535
|
+
fileName === ".." ||
|
|
536
|
+
basename(fileName) !== fileName ||
|
|
537
|
+
Buffer.byteLength(fileName, "utf8") > 255 ||
|
|
538
|
+
fileName.toLowerCase().startsWith(INTERNAL_PREFIX)) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
if (/[\\/\0\x00-\x1f<>:"|?*]/.test(fileName) || /[. ]$/.test(fileName)) {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
const stem = fileName.split(".")[0].toUpperCase();
|
|
545
|
+
return !/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(stem);
|
|
546
|
+
}
|
|
547
|
+
function truncateUtf8(value, maxBytes) {
|
|
548
|
+
let result = "";
|
|
549
|
+
let usedBytes = 0;
|
|
550
|
+
for (const character of value) {
|
|
551
|
+
const characterBytes = Buffer.byteLength(character, "utf8");
|
|
552
|
+
if (usedBytes + characterBytes > maxBytes)
|
|
553
|
+
break;
|
|
554
|
+
result += character;
|
|
555
|
+
usedBytes += characterBytes;
|
|
556
|
+
}
|
|
557
|
+
return result;
|
|
558
|
+
}
|
|
559
|
+
//# sourceMappingURL=upload-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-store.js","sourceRoot":"","sources":["../src/upload-store.ts"],"names":[],"mappings":"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"}
|
package/dist/websocket.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Server as HttpServer } from "node:http";
|
|
2
2
|
import type { ImageStore } from "./image-store.js";
|
|
3
3
|
import type { MediaStore } from "./media-store.js";
|
|
4
|
+
import { type UploadStore } from "./upload-store.js";
|
|
4
5
|
import type { GalleryStore } from "./gallery-store.js";
|
|
5
6
|
import type { ProjectHistory } from "./project-history.js";
|
|
6
7
|
import { DebugTraceStore } from "./debug-trace-store.js";
|
|
@@ -8,12 +9,14 @@ import { RecordingStore } from "./recording-store.js";
|
|
|
8
9
|
import type { FirebaseAuthClient } from "./firebase-auth.js";
|
|
9
10
|
import type { PromptHistoryBackupStore } from "./prompt-history-backup.js";
|
|
10
11
|
import type { PromptHistoryStore } from "./prompt-history-store.js";
|
|
12
|
+
export declare function downloadMimeType(filePath: string): string;
|
|
11
13
|
export interface BridgeServerOptions {
|
|
12
14
|
server: HttpServer;
|
|
13
15
|
apiKey?: string;
|
|
14
16
|
allowedDirs?: string[];
|
|
15
17
|
imageStore?: ImageStore;
|
|
16
18
|
mediaStore?: MediaStore;
|
|
19
|
+
uploadStore?: UploadStore;
|
|
17
20
|
galleryStore?: GalleryStore;
|
|
18
21
|
projectHistory?: ProjectHistory;
|
|
19
22
|
debugTraceStore?: DebugTraceStore;
|
|
@@ -24,6 +27,8 @@ export interface BridgeServerOptions {
|
|
|
24
27
|
platform?: NodeJS.Platform;
|
|
25
28
|
fileListMaxEntries?: number;
|
|
26
29
|
fileListMaxBytes?: number;
|
|
30
|
+
fileDownloadMaxBytes?: number;
|
|
31
|
+
fileUploadMaxBytes?: number;
|
|
27
32
|
deltaBatchMs?: number;
|
|
28
33
|
deltaBatchMaxChars?: number;
|
|
29
34
|
}
|
|
@@ -33,6 +38,8 @@ export declare class BridgeWebSocketServer {
|
|
|
33
38
|
private static readonly CONNECT_METADATA_REFRESH_COOLDOWN_MS;
|
|
34
39
|
private static readonly DEFAULT_FILE_LIST_MAX_ENTRIES;
|
|
35
40
|
private static readonly DEFAULT_FILE_LIST_MAX_BYTES;
|
|
41
|
+
private static readonly DEFAULT_FILE_DOWNLOAD_MAX_BYTES;
|
|
42
|
+
private static readonly DEFAULT_FILE_UPLOAD_MAX_BYTES;
|
|
36
43
|
private static readonly DEFAULT_DELTA_BATCH_MS;
|
|
37
44
|
private static readonly DEFAULT_DELTA_BATCH_MAX_CHARS;
|
|
38
45
|
private wss;
|
|
@@ -41,6 +48,7 @@ export declare class BridgeWebSocketServer {
|
|
|
41
48
|
private allowedDirs;
|
|
42
49
|
private imageStore;
|
|
43
50
|
private mediaStore;
|
|
51
|
+
private uploadStore;
|
|
44
52
|
private galleryStore;
|
|
45
53
|
private projectHistory;
|
|
46
54
|
private debugTraceStore;
|
|
@@ -76,6 +84,8 @@ export declare class BridgeWebSocketServer {
|
|
|
76
84
|
private failSetSandboxMode;
|
|
77
85
|
private readonly fileListMaxEntries;
|
|
78
86
|
private readonly fileListMaxBytes;
|
|
87
|
+
private readonly fileDownloadMaxBytes;
|
|
88
|
+
private readonly fileUploadMaxBytes;
|
|
79
89
|
private readonly deltaBatchMs;
|
|
80
90
|
private readonly deltaBatchMaxChars;
|
|
81
91
|
private deltaBatches;
|
|
@@ -90,6 +100,11 @@ export declare class BridgeWebSocketServer {
|
|
|
90
100
|
*/
|
|
91
101
|
private isPathAllowed;
|
|
92
102
|
private isCanonicalPathAllowed;
|
|
103
|
+
private sendFileDownloadError;
|
|
104
|
+
private prepareFileDownload;
|
|
105
|
+
private sendFileUploadError;
|
|
106
|
+
private prepareFileUpload;
|
|
107
|
+
private finalizeFileUpload;
|
|
93
108
|
/** Build a user-friendly error for disallowed project paths. */
|
|
94
109
|
private buildPathNotAllowedError;
|
|
95
110
|
private sendToolActionError;
|