@vryzel/file-next 0.5.2 → 0.6.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 +2 -1
- package/dist/{filesystem-Djiiu6YU.d.ts → filesystem-CF4TFF58.d.ts} +18 -122
- package/dist/{filesystem-C0vVDtg4.d.cts → filesystem-CFRC-gky.d.cts} +18 -122
- package/dist/index.cjs +52 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +52 -35
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +186 -78
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +34 -110
- package/dist/server/index.d.ts +34 -110
- package/dist/server/index.js +186 -78
- package/dist/server/index.js.map +1 -1
- package/dist/sync/index.cjs +33 -10
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.js +33 -10
- package/dist/sync/index.js.map +1 -1
- package/dist/{with-auth-OZNiZGga.d.ts → with-auth-BvFHyioA.d.ts} +1 -1
- package/dist/{with-auth-Dtn57B_S.d.cts → with-auth-UBz99KZJ.d.cts} +1 -1
- package/package.json +5 -5
package/dist/server/index.cjs
CHANGED
|
@@ -160,13 +160,44 @@ var FileSystemError = class extends Error {
|
|
|
160
160
|
var asS3Key = (s) => s;
|
|
161
161
|
var asTenantId = (s) => s;
|
|
162
162
|
|
|
163
|
+
// src/storage/content-disposition.ts
|
|
164
|
+
var NEVER_INLINE = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml", "image/svg+xml"]);
|
|
165
|
+
var DENIED_UPLOAD = /* @__PURE__ */ new Set([
|
|
166
|
+
"text/html",
|
|
167
|
+
"application/xhtml+xml",
|
|
168
|
+
"image/svg+xml",
|
|
169
|
+
"text/xml",
|
|
170
|
+
"application/xml"
|
|
171
|
+
]);
|
|
172
|
+
function mimeBase(contentType) {
|
|
173
|
+
return (contentType ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
174
|
+
}
|
|
175
|
+
function isDeniedUploadType(contentType) {
|
|
176
|
+
return DENIED_UPLOAD.has(mimeBase(contentType));
|
|
177
|
+
}
|
|
178
|
+
function matchesAllowedContentType(pattern, actual) {
|
|
179
|
+
const mime = mimeBase(actual);
|
|
180
|
+
if (!mime || isDeniedUploadType(mime)) return false;
|
|
181
|
+
if (pattern === mime) return true;
|
|
182
|
+
if (pattern.endsWith("/*")) return mime.startsWith(pattern.slice(0, -1));
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
function signedContentDisposition(requested, contentType) {
|
|
186
|
+
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
187
|
+
if (!mime || NEVER_INLINE.has(mime)) return "attachment";
|
|
188
|
+
if (mime === "application/pdf" || mime === "text/plain" || mime === "text/csv" || mime === "text/markdown" || mime.startsWith("image/") || mime.startsWith("video/") || mime.startsWith("audio/")) {
|
|
189
|
+
return "inline";
|
|
190
|
+
}
|
|
191
|
+
return "attachment";
|
|
192
|
+
}
|
|
193
|
+
|
|
163
194
|
// src/server/actions.ts
|
|
164
195
|
var NodeIdSchema = zod.z.string().min(1, "id is required");
|
|
165
196
|
var PathSchema = zod.z.string().nullable();
|
|
166
197
|
var ListFilesInputSchema = zod.z.object({
|
|
167
198
|
parentId: PathSchema,
|
|
168
|
-
limit: zod.z.number().int().positive().optional(),
|
|
169
|
-
cursor: zod.z.string().optional()
|
|
199
|
+
limit: zod.z.number().int().positive().max(500).optional(),
|
|
200
|
+
cursor: zod.z.string().max(256).optional()
|
|
170
201
|
});
|
|
171
202
|
var DeleteFileInputSchema = zod.z.object({
|
|
172
203
|
id: NodeIdSchema,
|
|
@@ -184,7 +215,7 @@ var CopyFileInputSchema = zod.z.object({
|
|
|
184
215
|
});
|
|
185
216
|
var SetMetadataInputSchema = zod.z.object({
|
|
186
217
|
id: NodeIdSchema,
|
|
187
|
-
metadata: zod.z.record(zod.z.string()),
|
|
218
|
+
metadata: zod.z.record(zod.z.string(), zod.z.string()),
|
|
188
219
|
replace: zod.z.boolean().optional()
|
|
189
220
|
});
|
|
190
221
|
var CreateFolderInputSchema = zod.z.object({
|
|
@@ -205,9 +236,13 @@ var ConfirmUploadInputSchema = zod.z.object({
|
|
|
205
236
|
size: zod.z.number().int().nonnegative().optional()
|
|
206
237
|
});
|
|
207
238
|
var SearchFilesInputSchema = zod.z.object({
|
|
208
|
-
query: zod.z.string(),
|
|
209
|
-
parentId: zod.z.string().optional(),
|
|
210
|
-
limit: zod.z.number().int().positive().optional()
|
|
239
|
+
query: zod.z.string().max(256),
|
|
240
|
+
parentId: zod.z.string().max(256).optional(),
|
|
241
|
+
limit: zod.z.number().int().positive().max(500).optional()
|
|
242
|
+
});
|
|
243
|
+
var ListTrashInputSchema = zod.z.object({
|
|
244
|
+
limit: zod.z.number().int().positive().max(500).optional(),
|
|
245
|
+
cursor: zod.z.string().max(256).optional()
|
|
211
246
|
});
|
|
212
247
|
var RestoreNodeInputSchema = zod.z.object({
|
|
213
248
|
id: NodeIdSchema
|
|
@@ -236,7 +271,7 @@ var createServerActions = (deps) => {
|
|
|
236
271
|
if (e instanceof FileSystemError) return e;
|
|
237
272
|
return new FileSystemError({
|
|
238
273
|
code,
|
|
239
|
-
message
|
|
274
|
+
message,
|
|
240
275
|
retryable: true
|
|
241
276
|
});
|
|
242
277
|
};
|
|
@@ -337,12 +372,34 @@ var createServerActions = (deps) => {
|
|
|
337
372
|
const prepareUpload = async (input) => {
|
|
338
373
|
const parsed = PrepareUploadInputSchema.safeParse(input);
|
|
339
374
|
if (!parsed.success) return zodErr("Invalid prepareUpload input", parsed);
|
|
375
|
+
const maxBytes = deps.maxBytes ?? fs.maxBytes;
|
|
376
|
+
if (maxBytes != null && parsed.data.contentLength > maxBytes) {
|
|
377
|
+
return err(
|
|
378
|
+
new FileSystemError({
|
|
379
|
+
code: "PayloadTooLarge",
|
|
380
|
+
message: `contentLength exceeds maxBytes ${maxBytes}`,
|
|
381
|
+
retryable: false
|
|
382
|
+
})
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (isDeniedUploadType(parsed.data.contentType) || deps.allowedContentTypes != null && !deps.allowedContentTypes.some(
|
|
386
|
+
(pattern) => matchesAllowedContentType(pattern, parsed.data.contentType)
|
|
387
|
+
)) {
|
|
388
|
+
return err(
|
|
389
|
+
new FileSystemError({
|
|
390
|
+
code: "UnsupportedMediaType",
|
|
391
|
+
message: "Content type is not allowed",
|
|
392
|
+
retryable: false
|
|
393
|
+
})
|
|
394
|
+
);
|
|
395
|
+
}
|
|
340
396
|
try {
|
|
341
397
|
const auth = await authTenant();
|
|
342
398
|
const id = typeof globalThis.crypto?.randomUUID === "function" ? globalThis.crypto.randomUUID() : `upl-${Date.now()}`;
|
|
343
399
|
const signed = await fs.forTenant(auth.tenantId).adapter.createPresignedUploadUrl({
|
|
344
400
|
key: asS3Key(id),
|
|
345
401
|
contentType: parsed.data.contentType,
|
|
402
|
+
contentLength: parsed.data.contentLength,
|
|
346
403
|
expiresIn: 900
|
|
347
404
|
});
|
|
348
405
|
if (!signed.ok) return signed;
|
|
@@ -392,12 +449,14 @@ var createServerActions = (deps) => {
|
|
|
392
449
|
}
|
|
393
450
|
};
|
|
394
451
|
const listTrash = async (input = {}) => {
|
|
452
|
+
const parsed = ListTrashInputSchema.safeParse(input);
|
|
453
|
+
if (!parsed.success) return zodErr("Invalid listTrash input", parsed);
|
|
395
454
|
try {
|
|
396
455
|
const auth = await authTenant();
|
|
397
456
|
return store.listTrash({
|
|
398
457
|
tenantId: asTenantId(auth.tenantId),
|
|
399
|
-
limit:
|
|
400
|
-
cursor:
|
|
458
|
+
limit: parsed.data.limit,
|
|
459
|
+
cursor: parsed.data.cursor
|
|
401
460
|
});
|
|
402
461
|
} catch (e) {
|
|
403
462
|
return err(wrap(e, "InternalError", "listTrash failed"));
|
|
@@ -499,18 +558,12 @@ var createServerActions = (deps) => {
|
|
|
499
558
|
revokeShare
|
|
500
559
|
};
|
|
501
560
|
};
|
|
502
|
-
|
|
503
|
-
// src/server/route-handlers/upload.ts
|
|
504
561
|
var MAX_EXPIRES_IN_SECONDS = 7 * 24 * 60 * 60;
|
|
505
562
|
var DEFAULT_EXPIRES_IN_SECONDS = 900;
|
|
506
|
-
var
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
return actual.startsWith(prefix);
|
|
511
|
-
}
|
|
512
|
-
return false;
|
|
513
|
-
};
|
|
563
|
+
var UploadBody = zod.z.object({
|
|
564
|
+
contentType: zod.z.string().min(1).max(255),
|
|
565
|
+
contentLength: zod.z.number().int().nonnegative()
|
|
566
|
+
});
|
|
514
567
|
var assertExpiresIn = (expiresIn) => {
|
|
515
568
|
if (!Number.isFinite(expiresIn) || expiresIn < 1 || expiresIn > MAX_EXPIRES_IN_SECONDS) {
|
|
516
569
|
throw new FileSystemError({
|
|
@@ -522,65 +575,76 @@ var assertExpiresIn = (expiresIn) => {
|
|
|
522
575
|
}
|
|
523
576
|
};
|
|
524
577
|
var createUploadRouteHandler = (opts) => {
|
|
578
|
+
if (!opts.getAuth && opts.unsafeNoAuth !== true) {
|
|
579
|
+
throw new Error("file-next: getAuth is required");
|
|
580
|
+
}
|
|
525
581
|
const expiresIn = opts.expiresIn ?? DEFAULT_EXPIRES_IN_SECONDS;
|
|
526
582
|
assertExpiresIn(expiresIn);
|
|
527
583
|
return async (req) => {
|
|
528
|
-
let
|
|
584
|
+
let fs = opts.fs;
|
|
585
|
+
if (opts.getAuth) {
|
|
586
|
+
const auth = await opts.getAuth(req);
|
|
587
|
+
if (!auth) {
|
|
588
|
+
return Response.json(
|
|
589
|
+
{ ok: false, error: { code: "Unauthorized", message: "Not authenticated" } },
|
|
590
|
+
{ status: 401 }
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
fs = opts.fs.forTenant(auth.tenantId);
|
|
594
|
+
}
|
|
595
|
+
let raw;
|
|
529
596
|
try {
|
|
530
|
-
|
|
597
|
+
raw = await req.json();
|
|
531
598
|
} catch {
|
|
532
599
|
return Response.json(
|
|
533
600
|
{ ok: false, error: { code: "InternalError", message: "Request body must be valid JSON" } },
|
|
534
601
|
{ status: 400 }
|
|
535
602
|
);
|
|
536
603
|
}
|
|
604
|
+
const parsed = UploadBody.safeParse(raw);
|
|
605
|
+
if (!parsed.success) {
|
|
606
|
+
return Response.json(
|
|
607
|
+
{ ok: false, error: { code: "InternalError", message: "Invalid upload request" } },
|
|
608
|
+
{ status: 400 }
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
const body = parsed.data;
|
|
537
612
|
if (typeof opts.maxBytes === "number" && body.contentLength > opts.maxBytes) {
|
|
538
613
|
return Response.json(
|
|
539
614
|
{
|
|
540
615
|
ok: false,
|
|
541
616
|
error: {
|
|
542
617
|
code: "PayloadTooLarge",
|
|
543
|
-
message: `contentLength
|
|
618
|
+
message: `contentLength exceeds maxBytes ${opts.maxBytes}`
|
|
544
619
|
}
|
|
545
620
|
},
|
|
546
621
|
{ status: 413 }
|
|
547
622
|
);
|
|
548
623
|
}
|
|
549
|
-
if (opts.allowedContentTypes && opts.allowedContentTypes.length > 0
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
let fs = opts.fs;
|
|
565
|
-
if (opts.getAuth) {
|
|
566
|
-
const auth = await opts.getAuth(req);
|
|
567
|
-
if (!auth) {
|
|
568
|
-
return Response.json(
|
|
569
|
-
{ ok: false, error: { code: "Unauthorized", message: "Not authenticated" } },
|
|
570
|
-
{ status: 401 }
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
fs = opts.fs.forTenant(auth.tenantId);
|
|
624
|
+
if (isDeniedUploadType(body.contentType) || opts.allowedContentTypes && opts.allowedContentTypes.length > 0 && !opts.allowedContentTypes.some(
|
|
625
|
+
(pattern) => matchesAllowedContentType(pattern, body.contentType)
|
|
626
|
+
)) {
|
|
627
|
+
return Response.json(
|
|
628
|
+
{
|
|
629
|
+
ok: false,
|
|
630
|
+
error: {
|
|
631
|
+
code: "UnsupportedMediaType",
|
|
632
|
+
message: "Content type is not allowed"
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
{ status: 415 }
|
|
636
|
+
);
|
|
574
637
|
}
|
|
575
638
|
const id = typeof globalThis.crypto?.randomUUID === "function" ? globalThis.crypto.randomUUID() : `upl-${Date.now()}`;
|
|
576
639
|
const r = await fs.adapter.createPresignedUploadUrl({
|
|
577
640
|
key: asS3Key(id),
|
|
578
641
|
contentType: body.contentType,
|
|
642
|
+
contentLength: body.contentLength,
|
|
579
643
|
expiresIn
|
|
580
644
|
});
|
|
581
645
|
if (!r.ok) {
|
|
582
646
|
return Response.json(
|
|
583
|
-
{ ok: false, error: { code: r.error.code, message:
|
|
647
|
+
{ ok: false, error: { code: r.error.code, message: "Upload failed" } },
|
|
584
648
|
{ status: 500 }
|
|
585
649
|
);
|
|
586
650
|
}
|
|
@@ -613,9 +677,28 @@ var assertExpiresIn2 = (expiresIn) => {
|
|
|
613
677
|
}
|
|
614
678
|
};
|
|
615
679
|
var createDownloadRouteHandler = (opts) => {
|
|
680
|
+
if (!opts.getAuth && opts.unsafeNoAuth !== true) {
|
|
681
|
+
throw new Error("file-next: getAuth is required");
|
|
682
|
+
}
|
|
683
|
+
if (opts.getAuth && !opts.store) {
|
|
684
|
+
throw new Error("file-next: store is required when getAuth is set");
|
|
685
|
+
}
|
|
616
686
|
const expiresIn = opts.expiresIn ?? DEFAULT_EXPIRES_IN_SECONDS2;
|
|
617
687
|
assertExpiresIn2(expiresIn);
|
|
618
688
|
return async (req) => {
|
|
689
|
+
let fs = opts.fs;
|
|
690
|
+
let tenantId;
|
|
691
|
+
if (opts.getAuth) {
|
|
692
|
+
const auth = await opts.getAuth(req);
|
|
693
|
+
if (!auth) {
|
|
694
|
+
return Response.json(
|
|
695
|
+
{ ok: false, error: { code: "Unauthorized", message: "Not authenticated" } },
|
|
696
|
+
{ status: 401 }
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
tenantId = auth.tenantId;
|
|
700
|
+
fs = opts.fs.forTenant(auth.tenantId);
|
|
701
|
+
}
|
|
619
702
|
const url = new URL(req.url);
|
|
620
703
|
const key = url.searchParams.get("key");
|
|
621
704
|
if (!key) {
|
|
@@ -624,16 +707,17 @@ var createDownloadRouteHandler = (opts) => {
|
|
|
624
707
|
{ status: 400 }
|
|
625
708
|
);
|
|
626
709
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
710
|
+
if (opts.store && tenantId) {
|
|
711
|
+
const node = await opts.store.findByS3Key({
|
|
712
|
+
tenantId: asTenantId(tenantId),
|
|
713
|
+
s3Key: key
|
|
714
|
+
});
|
|
715
|
+
if (!node.ok || !node.value) {
|
|
631
716
|
return Response.json(
|
|
632
|
-
{ ok: false, error: { code: "
|
|
633
|
-
{ status:
|
|
717
|
+
{ ok: false, error: { code: "NotFound", message: "Not found" } },
|
|
718
|
+
{ status: 404 }
|
|
634
719
|
);
|
|
635
720
|
}
|
|
636
|
-
fs = opts.fs.forTenant(auth.tenantId);
|
|
637
721
|
}
|
|
638
722
|
const r = await fs.adapter.createPresignedDownloadUrl({
|
|
639
723
|
key: asS3Key(key),
|
|
@@ -641,7 +725,7 @@ var createDownloadRouteHandler = (opts) => {
|
|
|
641
725
|
});
|
|
642
726
|
if (!r.ok) {
|
|
643
727
|
return Response.json(
|
|
644
|
-
{ ok: false, error: { code: r.error.code, message:
|
|
728
|
+
{ ok: false, error: { code: r.error.code, message: "Download failed" } },
|
|
645
729
|
{ status: 500 }
|
|
646
730
|
);
|
|
647
731
|
}
|
|
@@ -653,18 +737,17 @@ var createDownloadRouteHandler = (opts) => {
|
|
|
653
737
|
};
|
|
654
738
|
};
|
|
655
739
|
|
|
656
|
-
// src/storage/content-disposition.ts
|
|
657
|
-
var NEVER_INLINE = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml", "image/svg+xml"]);
|
|
658
|
-
function signedContentDisposition(requested, contentType) {
|
|
659
|
-
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
660
|
-
if (!mime || NEVER_INLINE.has(mime)) return "attachment";
|
|
661
|
-
if (mime === "application/pdf" || mime === "text/plain" || mime === "text/csv" || mime === "text/markdown" || mime.startsWith("image/") || mime.startsWith("video/") || mime.startsWith("audio/")) {
|
|
662
|
-
return "inline";
|
|
663
|
-
}
|
|
664
|
-
return "attachment";
|
|
665
|
-
}
|
|
666
|
-
|
|
667
740
|
// src/server/route-handlers/share.ts
|
|
741
|
+
var DEFAULT_MAX_SHARE_BYTES = 32 * 1024 * 1024;
|
|
742
|
+
var shareHeaders = () => {
|
|
743
|
+
const headers = new Headers();
|
|
744
|
+
headers.set("X-Content-Type-Options", "nosniff");
|
|
745
|
+
headers.set("Content-Security-Policy", "sandbox; default-src 'none'");
|
|
746
|
+
headers.set("Cross-Origin-Resource-Policy", "same-origin");
|
|
747
|
+
headers.set("Cache-Control", "private, no-store");
|
|
748
|
+
headers.set("Referrer-Policy", "no-referrer");
|
|
749
|
+
return headers;
|
|
750
|
+
};
|
|
668
751
|
var tokenFromRequest = (req) => {
|
|
669
752
|
const url = new URL(req.url);
|
|
670
753
|
const query = url.searchParams.get("token");
|
|
@@ -686,7 +769,7 @@ var createShareRouteHandler = (opts) => {
|
|
|
686
769
|
const resolved = await opts.store.resolveShare({ token });
|
|
687
770
|
if (!resolved.ok) {
|
|
688
771
|
return Response.json(
|
|
689
|
-
{ ok: false, error: { code:
|
|
772
|
+
{ ok: false, error: { code: "InternalError", message: "Share lookup failed" } },
|
|
690
773
|
{ status: 500 }
|
|
691
774
|
);
|
|
692
775
|
}
|
|
@@ -698,29 +781,54 @@ var createShareRouteHandler = (opts) => {
|
|
|
698
781
|
);
|
|
699
782
|
}
|
|
700
783
|
const adapter = opts.fs.forTenant(node.tenantId).adapter;
|
|
701
|
-
const
|
|
784
|
+
const download = new URL(req.url).searchParams.get("download") === "1";
|
|
785
|
+
const key = asS3Key(node.s3Key);
|
|
786
|
+
const st = await adapter.stat({ key });
|
|
787
|
+
if (!st.ok) {
|
|
788
|
+
return Response.json(
|
|
789
|
+
{ ok: false, error: { code: "NotFound", message: "Share not found" } },
|
|
790
|
+
{ status: 404 }
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_SHARE_BYTES;
|
|
794
|
+
if (st.value.size > maxBytes) {
|
|
795
|
+
const signed = await adapter.createPresignedDownloadUrl({
|
|
796
|
+
key,
|
|
797
|
+
expiresIn: 60,
|
|
798
|
+
disposition: download ? "attachment" : "inline",
|
|
799
|
+
contentType: st.value.contentType || node.mimeType
|
|
800
|
+
});
|
|
801
|
+
if (!signed.ok) {
|
|
802
|
+
return Response.json(
|
|
803
|
+
{ ok: false, error: { code: "InternalError", message: "Share failed" } },
|
|
804
|
+
{ status: 500 }
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
const headers2 = shareHeaders();
|
|
808
|
+
headers2.set("Location", signed.value.url);
|
|
809
|
+
return new Response(null, { status: 302, headers: headers2 });
|
|
810
|
+
}
|
|
702
811
|
const read = await adapter.read({
|
|
703
|
-
key
|
|
704
|
-
range:
|
|
812
|
+
key,
|
|
813
|
+
range: req.headers.get("range") ?? void 0
|
|
705
814
|
});
|
|
706
815
|
if (!read.ok) {
|
|
707
816
|
return Response.json(
|
|
708
|
-
{ ok: false, error: { code:
|
|
817
|
+
{ ok: false, error: { code: "NotFound", message: "Share not found" } },
|
|
709
818
|
{ status: 404 }
|
|
710
819
|
);
|
|
711
820
|
}
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
const mime = node.mimeType || read.value.contentType || "application/octet-stream";
|
|
821
|
+
const headers = shareHeaders();
|
|
822
|
+
const mime = st.value.contentType || node.mimeType || read.value.contentType || "application/octet-stream";
|
|
715
823
|
headers.set("Content-Type", mime);
|
|
716
824
|
const disposition = download ? "attachment" : signedContentDisposition("inline", mime);
|
|
717
825
|
headers.set(
|
|
718
826
|
"Content-Disposition",
|
|
719
827
|
`${disposition}; filename*=UTF-8''${encodeURIComponent(node.name)}`
|
|
720
828
|
);
|
|
721
|
-
|
|
722
|
-
headers.set("Content-Length", String(
|
|
723
|
-
return new Response(
|
|
829
|
+
const bytes = Uint8Array.from(read.value.body);
|
|
830
|
+
headers.set("Content-Length", String(bytes.byteLength));
|
|
831
|
+
return new Response(bytes.buffer, { status: 200, headers });
|
|
724
832
|
};
|
|
725
833
|
};
|
|
726
834
|
|