@cedarjs/uploads 7.0.1-next.314 → 7.0.1-next.466

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.
Files changed (59) hide show
  1. package/README.md +11 -0
  2. package/dist/authenticator.d.ts +2 -2
  3. package/dist/authenticator.d.ts.map +1 -1
  4. package/dist/cleanupStaleUploads.d.ts +4 -3
  5. package/dist/cleanupStaleUploads.d.ts.map +1 -1
  6. package/dist/cleanupStaleUploads.js +8 -2
  7. package/dist/contentTypes.d.ts +74 -0
  8. package/dist/contentTypes.d.ts.map +1 -0
  9. package/dist/contentTypes.js +78 -0
  10. package/dist/deleteFile.d.ts +19 -4
  11. package/dist/deleteFile.d.ts.map +1 -1
  12. package/dist/deleteFile.js +49 -8
  13. package/dist/detectContentType.d.ts +168 -0
  14. package/dist/detectContentType.d.ts.map +1 -0
  15. package/dist/detectContentType.js +443 -0
  16. package/dist/errors.d.ts +1 -1
  17. package/dist/errors.d.ts.map +1 -1
  18. package/dist/errors.js +2 -1
  19. package/dist/fastify/plugin.d.ts +30 -2
  20. package/dist/fastify/plugin.d.ts.map +1 -1
  21. package/dist/fastify/plugin.js +38 -10
  22. package/dist/index.d.ts +6 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +35 -0
  25. package/dist/keys.d.ts +3 -1
  26. package/dist/keys.d.ts.map +1 -1
  27. package/dist/keys.js +2 -1
  28. package/dist/profiles.d.ts +8 -2
  29. package/dist/profiles.d.ts.map +1 -1
  30. package/dist/profiles.js +5 -4
  31. package/dist/providers/s3.d.ts.map +1 -1
  32. package/dist/providers/s3.js +16 -0
  33. package/dist/services.d.ts +24 -6
  34. package/dist/services.d.ts.map +1 -1
  35. package/dist/services.js +45 -10
  36. package/dist/types.d.ts +116 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/variants.d.ts +207 -0
  39. package/dist/variants.d.ts.map +1 -0
  40. package/dist/variants.js +498 -0
  41. package/dist/verifyStoredContentType.d.ts +19 -0
  42. package/dist/verifyStoredContentType.d.ts.map +1 -0
  43. package/dist/verifyStoredContentType.js +41 -0
  44. package/dist/web/createUppy.d.ts +19 -2
  45. package/dist/web/createUppy.d.ts.map +1 -1
  46. package/dist/web/createUppy.js +37 -15
  47. package/dist/web/hooks/useFsUpload.d.ts.map +1 -1
  48. package/dist/web/hooks/useFsUpload.js +21 -9
  49. package/dist/web/hooks/useS3Upload.d.ts.map +1 -1
  50. package/dist/web/hooks/useS3Upload.js +12 -5
  51. package/dist/web/hooks/useUploadToken.d.ts +27 -1
  52. package/dist/web/hooks/useUploadToken.d.ts.map +1 -1
  53. package/dist/web/hooks/useUploadToken.js +72 -3
  54. package/dist/web/hooks/useUppyUpload.d.ts.map +1 -1
  55. package/dist/web/hooks/useUppyUpload.js +2 -8
  56. package/dist/webhooks/s3.d.ts +17 -8
  57. package/dist/webhooks/s3.d.ts.map +1 -1
  58. package/dist/webhooks/s3.js +22 -3
  59. package/package.json +18 -18
@@ -0,0 +1,443 @@
1
+ import {
2
+ canonicalContentType,
3
+ isContentTypeListed,
4
+ isExplicitOnlyContentType
5
+ } from "./contentTypes.js";
6
+ import { isMimeTypeAllowed } from "./profiles.js";
7
+ const DETECT_CONTENT_TYPE_BYTES = 4096;
8
+ const DETECT_CONTENT_TYPE_TAIL_BYTES = 64 * 1024;
9
+ const MAX_ZIP_DIRECTORY_BYTES = 4 * 1024 * 1024;
10
+ const DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
11
+ const XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
12
+ const PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
13
+ const DOCM = "application/vnd.ms-word.document.macroenabled.12";
14
+ const XLSM = "application/vnd.ms-excel.sheet.macroenabled.12";
15
+ const PPTM = "application/vnd.ms-powerpoint.presentation.macroenabled.12";
16
+ const ODT = "application/vnd.oasis.opendocument.text";
17
+ const ODS = "application/vnd.oasis.opendocument.spreadsheet";
18
+ const ODP = "application/vnd.oasis.opendocument.presentation";
19
+ const DETECTABLE_CONTENT_TYPES = /* @__PURE__ */ new Set([
20
+ "image/jpeg",
21
+ "image/png",
22
+ "image/gif",
23
+ "image/webp",
24
+ "image/bmp",
25
+ "image/tiff",
26
+ "image/avif",
27
+ "image/heic",
28
+ "image/heic-sequence",
29
+ "image/heif",
30
+ "image/heif-sequence",
31
+ "application/pdf",
32
+ "video/mp4",
33
+ "audio/mp4",
34
+ "video/quicktime",
35
+ "video/webm",
36
+ "video/matroska",
37
+ DOCX,
38
+ XLSX,
39
+ PPTX,
40
+ DOCM,
41
+ XLSM,
42
+ PPTM,
43
+ ODT,
44
+ ODS,
45
+ ODP
46
+ ]);
47
+ const PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
48
+ const EBML_SIGNATURE = [26, 69, 223, 163];
49
+ const BMP_INFO_HEADER_SIZES = /* @__PURE__ */ new Set([12, 40, 52, 56, 64, 108, 124]);
50
+ const AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
51
+ const HEIC_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "heim", "heis"]);
52
+ const HEIC_SEQUENCE_BRANDS = /* @__PURE__ */ new Set(["hevc", "hevx", "hevm", "hevs"]);
53
+ const MP4_BRANDS = /* @__PURE__ */ new Set([
54
+ "isom",
55
+ "iso2",
56
+ "iso3",
57
+ "iso4",
58
+ "iso5",
59
+ "iso6",
60
+ "iso7",
61
+ "iso8",
62
+ "iso9",
63
+ "mp41",
64
+ "mp42",
65
+ "mp71",
66
+ "avc1",
67
+ "dash",
68
+ "M4V "
69
+ ]);
70
+ const MP4_AUDIO_BRANDS = /* @__PURE__ */ new Set(["M4A ", "M4B "]);
71
+ const QUICKTIME_BRAND = "qt ";
72
+ const MAX_FTYP_BOX_SIZE = 1024;
73
+ const EBML_DOC_TYPE_ID = 17026;
74
+ const MAX_DOC_TYPE_LENGTH = 32;
75
+ function hasBytesAt(bytes, offset, expected) {
76
+ if (bytes.length < offset + expected.length) {
77
+ return false;
78
+ }
79
+ return expected.every((b, i) => bytes[offset + i] === b);
80
+ }
81
+ function ascii(bytes, start, end) {
82
+ if (end > bytes.length) {
83
+ return null;
84
+ }
85
+ let text = "";
86
+ for (let i = start; i < end; i++) {
87
+ text += String.fromCharCode(bytes[i]);
88
+ }
89
+ return text;
90
+ }
91
+ function readUint32BE(bytes, offset) {
92
+ if (bytes.length < offset + 4) {
93
+ return null;
94
+ }
95
+ return bytes[offset] * 16777216 + bytes[offset + 1] * 65536 + bytes[offset + 2] * 256 + bytes[offset + 3];
96
+ }
97
+ function readUint32LE(bytes, offset) {
98
+ if (bytes.length < offset + 4) {
99
+ return null;
100
+ }
101
+ return bytes[offset] + bytes[offset + 1] * 256 + bytes[offset + 2] * 65536 + bytes[offset + 3] * 16777216;
102
+ }
103
+ function detectIsoBmff(bytes) {
104
+ if (ascii(bytes, 4, 8) !== "ftyp") {
105
+ return null;
106
+ }
107
+ const boxSize = readUint32BE(bytes, 0);
108
+ if (boxSize === null || boxSize < 16 || boxSize % 4 !== 0 || boxSize > MAX_FTYP_BOX_SIZE) {
109
+ return null;
110
+ }
111
+ const major = ascii(bytes, 8, 12);
112
+ if (major === null) {
113
+ return null;
114
+ }
115
+ const fromMajor = classifyMajorBrand(major);
116
+ if (fromMajor) {
117
+ return fromMajor;
118
+ }
119
+ if (bytes.length < boxSize) {
120
+ return null;
121
+ }
122
+ const compatible = [];
123
+ for (let offset = 16; offset + 4 <= boxSize; offset += 4) {
124
+ const brand = ascii(bytes, offset, offset + 4);
125
+ if (brand !== null) {
126
+ compatible.push(brand);
127
+ }
128
+ }
129
+ return classifyCompatibleBrands(major, compatible);
130
+ }
131
+ function classifyMajorBrand(major) {
132
+ if (AVIF_BRANDS.has(major)) {
133
+ return "image/avif";
134
+ }
135
+ if (HEIC_BRANDS.has(major)) {
136
+ return "image/heic";
137
+ }
138
+ if (HEIC_SEQUENCE_BRANDS.has(major)) {
139
+ return "image/heic-sequence";
140
+ }
141
+ if (major === QUICKTIME_BRAND) {
142
+ return "video/quicktime";
143
+ }
144
+ if (MP4_AUDIO_BRANDS.has(major)) {
145
+ return "audio/mp4";
146
+ }
147
+ if (MP4_BRANDS.has(major)) {
148
+ return "video/mp4";
149
+ }
150
+ return null;
151
+ }
152
+ function classifyCompatibleBrands(major, compatible) {
153
+ const has = (brands) => compatible.some((brand) => brands.has(brand));
154
+ if (has(AVIF_BRANDS)) {
155
+ return "image/avif";
156
+ }
157
+ if (has(HEIC_BRANDS)) {
158
+ return "image/heic";
159
+ }
160
+ if (has(HEIC_SEQUENCE_BRANDS)) {
161
+ return "image/heic-sequence";
162
+ }
163
+ if (major === "mif1" || major === "mif2" || compatible.includes("mif1")) {
164
+ return "image/heif";
165
+ }
166
+ if (major === "msf1" || compatible.includes("msf1")) {
167
+ return "image/heif-sequence";
168
+ }
169
+ if (compatible.includes(QUICKTIME_BRAND)) {
170
+ return "video/quicktime";
171
+ }
172
+ if (has(MP4_AUDIO_BRANDS)) {
173
+ return "audio/mp4";
174
+ }
175
+ if (has(MP4_BRANDS)) {
176
+ return "video/mp4";
177
+ }
178
+ return null;
179
+ }
180
+ function readVint(bytes, offset, keepMarker) {
181
+ if (offset >= bytes.length) {
182
+ return null;
183
+ }
184
+ const first = bytes[offset];
185
+ if (first === 0) {
186
+ return null;
187
+ }
188
+ let length = 1;
189
+ while (!(first & 128 >> length - 1)) {
190
+ length++;
191
+ }
192
+ if (offset + length > bytes.length) {
193
+ return null;
194
+ }
195
+ const marker = 128 >> length - 1;
196
+ let value = keepMarker ? first : first & marker - 1;
197
+ let allOnes = (first & marker - 1) === marker - 1;
198
+ for (let i = 1; i < length; i++) {
199
+ const byte = bytes[offset + i];
200
+ value = value * 256 + byte;
201
+ allOnes = allOnes && byte === 255;
202
+ }
203
+ return { value, length, unknown: !keepMarker && allOnes };
204
+ }
205
+ function detectEbml(bytes) {
206
+ if (!hasBytesAt(bytes, 0, EBML_SIGNATURE)) {
207
+ return null;
208
+ }
209
+ const headerSize = readVint(bytes, EBML_SIGNATURE.length, false);
210
+ if (!headerSize || headerSize.unknown) {
211
+ return null;
212
+ }
213
+ const headerStart = EBML_SIGNATURE.length + headerSize.length;
214
+ const headerEnd = headerStart + headerSize.value;
215
+ let offset = headerStart;
216
+ while (offset < Math.min(headerEnd, bytes.length)) {
217
+ const id = readVint(bytes, offset, true);
218
+ if (!id || id.length > 4 || offset + id.length > headerEnd) {
219
+ return null;
220
+ }
221
+ const size = readVint(bytes, offset + id.length, false);
222
+ if (!size || size.unknown) {
223
+ return null;
224
+ }
225
+ const dataStart = offset + id.length + size.length;
226
+ if (dataStart + size.value > headerEnd) {
227
+ return null;
228
+ }
229
+ if (id.value === EBML_DOC_TYPE_ID) {
230
+ let docTypeEnd = dataStart + size.value;
231
+ if (size.value > MAX_DOC_TYPE_LENGTH || docTypeEnd > bytes.length) {
232
+ return null;
233
+ }
234
+ while (docTypeEnd > dataStart && bytes[docTypeEnd - 1] === 0) {
235
+ docTypeEnd--;
236
+ }
237
+ switch (ascii(bytes, dataStart, docTypeEnd)) {
238
+ case "webm":
239
+ return "video/webm";
240
+ case "matroska":
241
+ return "video/matroska";
242
+ default:
243
+ return null;
244
+ }
245
+ }
246
+ offset = dataStart + size.value;
247
+ }
248
+ return null;
249
+ }
250
+ function detectBmp(bytes) {
251
+ if (ascii(bytes, 0, 2) !== "BM") {
252
+ return null;
253
+ }
254
+ const reserved = readUint32LE(bytes, 6);
255
+ const infoHeaderSize = readUint32LE(bytes, 14);
256
+ if (reserved === 0 && infoHeaderSize !== null && BMP_INFO_HEADER_SIZES.has(infoHeaderSize)) {
257
+ return "image/bmp";
258
+ }
259
+ return null;
260
+ }
261
+ const ZIP_LOCAL_HEADER = [80, 75, 3, 4];
262
+ const ZIP_CENTRAL_HEADER = [80, 75, 1, 2];
263
+ const ZIP_END_OF_DIRECTORY = [80, 75, 5, 6];
264
+ const ZIP_LOCAL_HEADER_SIZE = 30;
265
+ const ZIP_CENTRAL_HEADER_SIZE = 46;
266
+ const ZIP_END_OF_DIRECTORY_SIZE = 22;
267
+ const ZIP_MAX_COMMENT_LENGTH = 65535;
268
+ const OPEN_DOCUMENT_TYPES = /* @__PURE__ */ new Set([ODT, ODS, ODP]);
269
+ function readUint16LE(bytes, offset) {
270
+ if (bytes.length < offset + 2) {
271
+ return null;
272
+ }
273
+ return bytes[offset] + bytes[offset + 1] * 256;
274
+ }
275
+ function detectOpenDocument(bytes) {
276
+ const flags = readUint16LE(bytes, 6);
277
+ const method = readUint16LE(bytes, 8);
278
+ const compressedSize = readUint32LE(bytes, 18);
279
+ const size = readUint32LE(bytes, 22);
280
+ const nameLength = readUint16LE(bytes, 26);
281
+ const extraLength = readUint16LE(bytes, 28);
282
+ if (flags === null || method === null || compressedSize === null || size === null || nameLength === null || extraLength === null) {
283
+ return null;
284
+ }
285
+ if (method !== 0 || flags & 8 || compressedSize !== size || size > 64) {
286
+ return null;
287
+ }
288
+ const nameEnd = ZIP_LOCAL_HEADER_SIZE + nameLength;
289
+ if (ascii(bytes, ZIP_LOCAL_HEADER_SIZE, nameEnd) !== "mimetype") {
290
+ return null;
291
+ }
292
+ const contentStart = nameEnd + extraLength;
293
+ const content = ascii(bytes, contentStart, contentStart + size);
294
+ return content !== null && OPEN_DOCUMENT_TYPES.has(content) ? content : null;
295
+ }
296
+ const MAX_ZIP_END_RECORD_CANDIDATES = 16;
297
+ function* zipEndRecords(end) {
298
+ const last = end.length - ZIP_END_OF_DIRECTORY_SIZE;
299
+ const first = Math.max(0, last - ZIP_MAX_COMMENT_LENGTH);
300
+ let candidates = 0;
301
+ for (let offset = last; offset >= first; offset--) {
302
+ if (!hasBytesAt(end, offset, ZIP_END_OF_DIRECTORY) || readUint16LE(end, offset + 20) !== last - offset) {
303
+ continue;
304
+ }
305
+ if (++candidates > MAX_ZIP_END_RECORD_CANDIDATES) {
306
+ return;
307
+ }
308
+ const disk = readUint16LE(end, offset + 4);
309
+ const directoryDisk = readUint16LE(end, offset + 6);
310
+ const entries = readUint16LE(end, offset + 10);
311
+ const directorySize = readUint32LE(end, offset + 12);
312
+ const directoryOffset = readUint32LE(end, offset + 16);
313
+ if (disk !== 0 || directoryDisk !== 0 || entries === null || entries === 65535 || directorySize === null || directorySize === 4294967295 || directorySize > MAX_ZIP_DIRECTORY_BYTES || directoryOffset === null || directoryOffset === 4294967295) {
314
+ continue;
315
+ }
316
+ yield { recordOffset: offset, entries, directorySize, directoryOffset };
317
+ }
318
+ }
319
+ function findZipCentralDirectory(tail) {
320
+ for (const record of zipEndRecords(tail)) {
321
+ if (record.entries > 0 && record.directorySize > 0) {
322
+ return { offset: record.directoryOffset, size: record.directorySize };
323
+ }
324
+ }
325
+ return null;
326
+ }
327
+ function readZipEntryNames(end, { recordOffset, entries, directorySize }) {
328
+ let offset = recordOffset - directorySize;
329
+ if (offset < 0) {
330
+ return null;
331
+ }
332
+ const names = [];
333
+ for (let i = 0; i < entries; i++) {
334
+ if (!hasBytesAt(end, offset, ZIP_CENTRAL_HEADER)) {
335
+ return null;
336
+ }
337
+ const nameLength = readUint16LE(end, offset + 28);
338
+ const extraLength = readUint16LE(end, offset + 30);
339
+ const commentLength = readUint16LE(end, offset + 32);
340
+ if (nameLength === null || extraLength === null || commentLength === null) {
341
+ return null;
342
+ }
343
+ const nameStart = offset + ZIP_CENTRAL_HEADER_SIZE;
344
+ const name = ascii(end, nameStart, nameStart + nameLength);
345
+ offset = nameStart + nameLength + extraLength + commentLength;
346
+ if (name === null || offset > recordOffset) {
347
+ return null;
348
+ }
349
+ names.push(name);
350
+ }
351
+ return offset === recordOffset ? names : null;
352
+ }
353
+ function classifyOfficeOpenXml(entryNames) {
354
+ const names = entryNames.map((name) => name.toLowerCase());
355
+ if (!names.includes("[content_types].xml")) {
356
+ return null;
357
+ }
358
+ const families = [
359
+ { prefix: "word/", plain: DOCX, macro: DOCM },
360
+ { prefix: "xl/", plain: XLSX, macro: XLSM },
361
+ { prefix: "ppt/", plain: PPTX, macro: PPTM }
362
+ ].filter(({ prefix }) => names.some((name) => name.startsWith(prefix)));
363
+ if (families.length !== 1) {
364
+ return null;
365
+ }
366
+ const hasMacros = names.some(
367
+ (name) => name.endsWith("vbaproject.bin") || name.endsWith("vbadata.xml") || name.startsWith("xl/macrosheets/")
368
+ );
369
+ return hasMacros ? families[0].macro : families[0].plain;
370
+ }
371
+ function detectZip(bytes, tail) {
372
+ if (!hasBytesAt(bytes, 0, ZIP_LOCAL_HEADER)) {
373
+ return null;
374
+ }
375
+ const openDocument = detectOpenDocument(bytes);
376
+ if (openDocument) {
377
+ return openDocument;
378
+ }
379
+ const end = tail ?? bytes;
380
+ for (const record of zipEndRecords(end)) {
381
+ const names = readZipEntryNames(end, record);
382
+ const type = names && classifyOfficeOpenXml(names);
383
+ if (type) {
384
+ return type;
385
+ }
386
+ }
387
+ return null;
388
+ }
389
+ function detectContentType(bytes, options = {}) {
390
+ if (hasBytesAt(bytes, 0, [255, 216, 255])) {
391
+ return "image/jpeg";
392
+ }
393
+ if (hasBytesAt(bytes, 0, PNG_SIGNATURE)) {
394
+ return "image/png";
395
+ }
396
+ const head6 = ascii(bytes, 0, 6);
397
+ if (head6 === "GIF87a" || head6 === "GIF89a") {
398
+ return "image/gif";
399
+ }
400
+ if (ascii(bytes, 0, 4) === "RIFF" && ascii(bytes, 8, 12) === "WEBP") {
401
+ return "image/webp";
402
+ }
403
+ if (ascii(bytes, 0, 5) === "%PDF-") {
404
+ return "application/pdf";
405
+ }
406
+ const head4 = ascii(bytes, 0, 4);
407
+ if (head4 === "II*\0" || head4 === "MM\0*") {
408
+ return "image/tiff";
409
+ }
410
+ return detectIsoBmff(bytes) ?? detectEbml(bytes) ?? detectBmp(bytes) ?? detectZip(bytes, options.tail);
411
+ }
412
+ function isClaimedContentTypeAllowed(accept, claimedType) {
413
+ const claimed = canonicalContentType(claimedType);
414
+ if (!claimed || claimed.includes("*")) {
415
+ return false;
416
+ }
417
+ return isExplicitOnlyContentType(claimed) ? isContentTypeListed(accept, claimed) : isMimeTypeAllowed(accept, claimed);
418
+ }
419
+ function acceptedContentType(bytes, accept, claimedType, options = {}) {
420
+ const detected = detectContentType(bytes, options);
421
+ if (detected) {
422
+ const allowed = isExplicitOnlyContentType(detected) ? isContentTypeListed(accept, detected) : isMimeTypeAllowed(accept, detected);
423
+ return allowed ? detected : null;
424
+ }
425
+ if (!claimedType) {
426
+ return null;
427
+ }
428
+ const claimed = canonicalContentType(claimedType);
429
+ if (DETECTABLE_CONTENT_TYPES.has(claimed)) {
430
+ return null;
431
+ }
432
+ return isClaimedContentTypeAllowed(accept, claimed) ? claimed : null;
433
+ }
434
+ export {
435
+ DETECTABLE_CONTENT_TYPES,
436
+ DETECT_CONTENT_TYPE_BYTES,
437
+ DETECT_CONTENT_TYPE_TAIL_BYTES,
438
+ MAX_ZIP_DIRECTORY_BYTES,
439
+ acceptedContentType,
440
+ detectContentType,
441
+ findZipCentralDirectory,
442
+ isClaimedContentTypeAllowed
443
+ };
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type UploadErrorCode = 'CONFIGURATION' | 'UNKNOWN_TARGET' | 'UNKNOWN_PROFILE' | 'UNAUTHENTICATED' | 'INVALID_TOKEN' | 'TOKEN_USER_MISMATCH' | 'TOKEN_ORGANIZATION_MISMATCH' | 'MIME_TYPE_NOT_ALLOWED' | 'FILE_TOO_LARGE' | 'TOO_MANY_FILES' | 'PRESIGN_NOT_SUPPORTED' | 'NOT_SUPPORTED' | 'NOT_FOUND' | 'FORBIDDEN' | 'NOT_PENDING' | 'NOT_IN_STORAGE' | 'SIZE_MISMATCH' | 'INVALID_KEY';
1
+ export type UploadErrorCode = 'CONFIGURATION' | 'UNKNOWN_TARGET' | 'UNKNOWN_PROFILE' | 'UNAUTHENTICATED' | 'INVALID_TOKEN' | 'TOKEN_USER_MISMATCH' | 'TOKEN_ORGANIZATION_MISMATCH' | 'MIME_TYPE_NOT_ALLOWED' | 'FILE_TOO_LARGE' | 'TOO_MANY_FILES' | 'PRESIGN_NOT_SUPPORTED' | 'NOT_SUPPORTED' | 'NOT_FOUND' | 'FORBIDDEN' | 'NOT_PENDING' | 'NOT_IN_STORAGE' | 'SIZE_MISMATCH' | 'INVALID_KEY' | 'HAS_VARIANTS';
2
2
  /**
3
3
  * Thrown by every part of the uploads package when a request cannot be
4
4
  * honored. `code` is stable for programmatic handling and `statusCode` is the
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,6BAA6B,GAC7B,uBAAuB,GACvB,gBAAgB,GAChB,gBAAgB,GAChB,uBAAuB,GACvB,eAAe,GACf,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,eAAe,GACf,aAAa,CAAA;AAuBjB;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,IAAI,EAAE,eAAe,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;gBAEN,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAMpE"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,6BAA6B,GAC7B,uBAAuB,GACvB,gBAAgB,GAChB,gBAAgB,GAChB,uBAAuB,GACvB,eAAe,GACf,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,eAAe,GACf,aAAa,GACb,cAAc,CAAA;AAwBlB;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,IAAI,EAAE,eAAe,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;gBAEN,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAMpE"}
package/dist/errors.js CHANGED
@@ -16,7 +16,8 @@ const STATUS_CODES = {
16
16
  NOT_PENDING: 409,
17
17
  NOT_IN_STORAGE: 409,
18
18
  SIZE_MISMATCH: 409,
19
- INVALID_KEY: 400
19
+ INVALID_KEY: 400,
20
+ HAS_VARIANTS: 409
20
21
  };
21
22
  class UploadError extends Error {
22
23
  code;
@@ -3,8 +3,12 @@ import type { UploadAuthenticator } from '../authenticator.js';
3
3
  import type { StorageTargets, UploadDatabase } from '../types.js';
4
4
  import type { FetchCertificate } from '../webhooks/sns.js';
5
5
  export interface UploadPluginOptions {
6
- /** Secret upload tokens and serve URLs are signed with. */
7
- tokenSecret: string;
6
+ /**
7
+ * Secret upload tokens and serve URLs are signed with. Usually
8
+ * `process.env.UPLOAD_TOKEN_SECRET`; the plugin throws a configuration
9
+ * error when it's missing.
10
+ */
11
+ tokenSecret: string | undefined;
8
12
  /** The app's storage targets. */
9
13
  targets: StorageTargets;
10
14
  db: UploadDatabase;
@@ -35,6 +39,30 @@ export interface UploadPluginOptions {
35
39
  };
36
40
  /** `Cache-Control` for served files. Defaults to `private, max-age=3600`. */
37
41
  serveCacheControl?: string;
42
+ /**
43
+ * Active-content MIME types the serve route may send inline when the
44
+ * signed URL asks for `inline`, e.g. `['application/pdf']` to preview PDFs
45
+ * in the browser. Every other active type is always served as an
46
+ * attachment. Only list types you're comfortable rendering from the app's
47
+ * origin, keeping in mind that the stored MIME type is client-asserted.
48
+ */
49
+ inlineTypes?: string[];
50
+ /**
51
+ * Checks each file's type by what its bytes are, using
52
+ * `acceptedContentType()`, instead of trusting the type the client
53
+ * claimed. On `POST {prefix}/fs`, a file the detector recognises is
54
+ * accepted when its detected type is in the profile's `allowedMimeTypes`;
55
+ * one it doesn't recognise is accepted on its claimed type, through a
56
+ * wildcard too, except that a claimed type the detector would have
57
+ * recognised is refused, and a type that can carry script (SVG, HTML,
58
+ * JavaScript, XML) must be listed exactly. Files are stored under the
59
+ * canonical spelling of the accepted type (`image/jpeg` for a claimed
60
+ * `image/jpg`). The S3 webhook checks objects' bytes against their
61
+ * declared type the same way `confirmUpload` does.
62
+ *
63
+ * Defaults to `true`. `false` checks the claimed type only.
64
+ */
65
+ verifyContentType?: boolean;
38
66
  }
39
67
  /**
40
68
  * Registers the upload routes on a Fastify server. Register it on the
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/fastify/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAA;AAE5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAgB9D,OAAO,KAAK,EAEV,cAAc,EACd,cAAc,EAEf,MAAM,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AA2B1D,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAA;IACnB,iCAAiC;IACjC,OAAO,EAAE,cAAc,CAAA;IACvB,EAAE,EAAE,cAAc,CAAA;IAClB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAA;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,MAAM,CAAA;QAChB,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;QACnC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;KACrB,CAAA;IACD,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAgCD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAwQf"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/fastify/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAA;AAE5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAiB9D,OAAO,KAAK,EAEV,cAAc,EACd,cAAc,EAEf,MAAM,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AA4B1D,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,iCAAiC;IACjC,OAAO,EAAE,cAAc,CAAA;IACvB,EAAE,EAAE,cAAc,CAAA;IAClB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAA;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,MAAM,CAAA;QAChB,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;QACnC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;KACrB,CAAA;IACD,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AA+DD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA2Rf"}
@@ -1,5 +1,6 @@
1
1
  import fastifyMultipart from "@fastify/multipart";
2
2
  import { deleteFile } from "../deleteFile.js";
3
+ import { acceptedContentType, detectContentType } from "../detectContentType.js";
3
4
  import { UploadError } from "../errors.js";
4
5
  import { generateStorageKey, trimTrailingSlashes } from "../keys.js";
5
6
  import {
@@ -40,8 +41,24 @@ function headerValue(req, name) {
40
41
  const value = req.headers[name];
41
42
  return Array.isArray(value) ? value[0] : value;
42
43
  }
43
- function isActiveContent(mimeType) {
44
- return ACTIVE_CONTENT_TYPES.has(mimeType.split(";")[0].trim().toLowerCase());
44
+ function normalizeMimeType(mimeType) {
45
+ return mimeType.split(";")[0].trim().toLowerCase();
46
+ }
47
+ function verifiedContentType(payload, data, claimedType) {
48
+ const mimeType = acceptedContentType(
49
+ data,
50
+ payload.allowedMimeTypes,
51
+ claimedType
52
+ );
53
+ if (mimeType) {
54
+ return mimeType;
55
+ }
56
+ const detected = detectContentType(data);
57
+ const what = detected ? `'${detected}'` : `of a type that couldn't be recognised (claimed '${claimedType}')`;
58
+ throw new UploadError(
59
+ "MIME_TYPE_NOT_ALLOWED",
60
+ `File is ${what}, which is not allowed for upload profile '${payload.profile}'.`
61
+ );
45
62
  }
46
63
  function contentDispositionHeader(disposition, filename) {
47
64
  const ascii = filename.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
@@ -57,8 +74,11 @@ async function cedarUploadsPlugin(fastify, options) {
57
74
  bodyLimit = FIVE_HUNDRED_MB,
58
75
  authenticate,
59
76
  s3Webhook,
60
- serveCacheControl = "private, max-age=3600"
77
+ serveCacheControl = "private, max-age=3600",
78
+ inlineTypes = [],
79
+ verifyContentType = true
61
80
  } = options;
81
+ const allowedInlineTypes = new Set(inlineTypes.map(normalizeMimeType));
62
82
  if (!tokenSecret) {
63
83
  throw new UploadError(
64
84
  "CONFIGURATION",
@@ -118,7 +138,12 @@ async function cedarUploadsPlugin(fastify, options) {
118
138
  if (part.type !== "file") {
119
139
  continue;
120
140
  }
121
- assertTokenAllowsFile(payload, { contentType: part.mimetype, size: 0n });
141
+ if (!verifyContentType) {
142
+ assertTokenAllowsFile(payload, {
143
+ contentType: part.mimetype,
144
+ size: 0n
145
+ });
146
+ }
122
147
  let data;
123
148
  try {
124
149
  data = await part.toBuffer();
@@ -132,18 +157,19 @@ async function cedarUploadsPlugin(fastify, options) {
132
157
  throw e;
133
158
  }
134
159
  const size = BigInt(data.byteLength);
135
- assertTokenAllowsFile(payload, { contentType: part.mimetype, size });
136
- const storageKey = generateStorageKey(part.mimetype);
160
+ const mimeType = verifyContentType ? verifiedContentType(payload, data, part.mimetype) : part.mimetype;
161
+ assertTokenAllowsFile(payload, { contentType: mimeType, size });
162
+ const storageKey = generateStorageKey(mimeType);
137
163
  const upload = await createPendingUpload({
138
164
  db,
139
165
  payload,
140
166
  filename: part.filename,
141
- mimeType: part.mimetype,
167
+ mimeType,
142
168
  size,
143
169
  storageKey
144
170
  });
145
171
  stored.push({ target: payload.target, upload });
146
- await target.write(storageKey, data, { contentType: part.mimetype });
172
+ await target.write(storageKey, data, { contentType: mimeType });
147
173
  const { count } = await db.upload.updateMany({
148
174
  where: { id: upload.id, status: "pending" },
149
175
  data: { status: "completed" }
@@ -202,7 +228,8 @@ async function cedarUploadsPlugin(fastify, options) {
202
228
  } catch (e) {
203
229
  throw new UploadError("NOT_FOUND", "File not found.", e);
204
230
  }
205
- const disposition = isActiveContent(upload.mimeType) ? "attachment" : requested;
231
+ const mimeType = normalizeMimeType(upload.mimeType);
232
+ const disposition = ACTIVE_CONTENT_TYPES.has(mimeType) && !allowedInlineTypes.has(mimeType) ? "attachment" : requested;
206
233
  return reply.code(200).header("Content-Type", upload.mimeType).header("Content-Length", upload.size.toString()).header(
207
234
  "Content-Disposition",
208
235
  contentDispositionHeader(disposition, upload.filename)
@@ -226,7 +253,8 @@ async function cedarUploadsPlugin(fastify, options) {
226
253
  targets,
227
254
  topicArn: s3Webhook.topicArn,
228
255
  fetchCertificate: s3Webhook.fetchCertificate,
229
- fetch: s3Webhook.fetch
256
+ fetch: s3Webhook.fetch,
257
+ verifyContentType
230
258
  });
231
259
  return reply.code(200).send(result);
232
260
  } catch (e) {
package/dist/index.d.ts CHANGED
@@ -4,6 +4,9 @@ export type { UploadErrorCode } from './errors.js';
4
4
  export { defineStorageTargets, resolveTarget } from './targets.js';
5
5
  export { DB_MAX_FILE_SIZE, DEFAULT_UPLOAD_TOKEN_EXPIRES_IN, defineUploadProfiles, isMimeTypeAllowed, resolveProfile, } from './profiles.js';
6
6
  export type { ResolvedUploadProfile, UploadProfile, UploadProfiles, } from './profiles.js';
7
+ export { acceptedContentType, DETECT_CONTENT_TYPE_BYTES, DETECT_CONTENT_TYPE_TAIL_BYTES, DETECTABLE_CONTENT_TYPES, detectContentType, findZipCentralDirectory, isClaimedContentTypeAllowed, MAX_ZIP_DIRECTORY_BYTES, } from './detectContentType.js';
8
+ export type { DetectContentTypeOptions } from './detectContentType.js';
9
+ export { canonicalContentType, CONTENT_TYPE_ALIASES, isExplicitOnlyContentType, isScriptableContentType, MACRO_CONTENT_TYPES, SCRIPTABLE_CONTENT_TYPES, } from './contentTypes.js';
7
10
  export { createFsProvider } from './providers/fs.js';
8
11
  export type { FsProviderOptions } from './providers/fs.js';
9
12
  export { createDbProvider } from './providers/db.js';
@@ -12,6 +15,8 @@ export { storeFile } from './storeFile.js';
12
15
  export type { StoreFileOptions } from './storeFile.js';
13
16
  export { deleteFile } from './deleteFile.js';
14
17
  export type { DeleteFileOptions } from './deleteFile.js';
18
+ export { defineUploadVariants, deleteVariants } from './variants.js';
19
+ export type { DefineUploadVariantsOptions, RegenerateVariantsResult, UploadVariantContext, UploadVariantDefinition, UploadVariantFile, UploadVariantGenerator, UploadVariants, UploadVariantSource, } from './variants.js';
15
20
  export { cleanupStaleUploads } from './cleanupStaleUploads.js';
16
21
  export type { CleanupStaleUploadsOptions, CleanupStaleUploadsResult, } from './cleanupStaleUploads.js';
17
22
  export { createUploadToken, UPLOAD_TOKEN_HEADER, UPLOAD_TOKEN_PURPOSE, verifyUploadToken, } from './uploadToken.js';
@@ -28,7 +33,7 @@ export type { CreateUploadAuthenticatorOptions, UploadAuthenticator, UploadReque
28
33
  export { cedarUploadsPlugin } from './fastify/plugin.js';
29
34
  export type { UploadPluginOptions } from './fastify/plugin.js';
30
35
  export { handleS3Webhook, processS3EventRecord } from './webhooks/s3.js';
31
- export type { S3Event, S3EventOutcome, S3EventRecord, S3WebhookOptions, S3WebhookResult, } from './webhooks/s3.js';
36
+ export type { ProcessS3EventRecordOptions, S3Event, S3EventOutcome, S3EventRecord, S3WebhookOptions, S3WebhookResult, } from './webhooks/s3.js';
32
37
  export { verifySnsMessage } from './webhooks/sns.js';
33
38
  export type { SnsMessage } from './webhooks/sns.js';
34
39
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAClE,OAAO,EACL,gBAAgB,EAChB,+BAA+B,EAC/B,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,qBAAqB,EACrB,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,YAAY,EACV,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AACxB,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC3D,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,oBAAoB,EACpB,0BAA0B,EAC1B,4BAA4B,EAC5B,uBAAuB,EACvB,uBAAuB,EACvB,WAAW,EACX,mBAAmB,GACpB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,iCAAiC,EACjC,0BAA0B,EAC1B,4BAA4B,EAC5B,qBAAqB,EACrB,wBAAwB,EACxB,wBAAwB,EACxB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,kCAAkC,EAClC,yBAAyB,EACzB,6BAA6B,GAC9B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAC9D,YAAY,EACV,gCAAgC,EAChC,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACxE,YAAY,EACV,OAAO,EACP,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}