@streamscloud/kit 0.84.0 → 0.84.1

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.
@@ -28,10 +28,11 @@ export declare class FileHelper {
28
28
  * Checks a file's real type against its declared one. The bytes identify images (PNG, JPEG, WebP, GIF, AVIF, HEIC, SVG),
29
29
  * MP4, audio (WAV, MP3, M4A, AAC, FLAC), PDF, DOCX, XLSX, ZIP and fonts (WOFF2, WOFF, TTF, OTF); other formats are typed
30
30
  * as declared. An equivalent extension (`.jpeg` for `jpg`) and any member of the declared ISO media container family
31
- * (`.m4a` holding an `isom` MP4, `.heif` for HEIC) is a match, as is any ZIP archive named `.zip`. When the name lacks
32
- * the right extension, it is appended and the original name stays intact: `photo.jpg` holding a PNG → `photo.jpg.png`.
33
- * Binary formats read only the head; SVG is looked for only when something is declared, and a markup-looking file is
34
- * then read whole to confirm an SVG root.
31
+ * (`.m4a` holding an `isom` MP4, `.heif` for HEIC) is a match, as is a declared ZIP, PNG or font whose header carries its
32
+ * format's signature (any ZIP, APNG, Apple `true` TrueType, WOFF / WOFF2 of any flavor). When the name lacks the right
33
+ * extension, it is appended and the original name stays intact: `photo.jpg` holding a PNG → `photo.jpg.png`. Binary
34
+ * formats read only the head; SVG is looked for only when something is declared, and a markup-looking file is then read
35
+ * whole to confirm an SVG root.
35
36
  */
36
37
  static checkType(file: File): Promise<FileTypeCheck>;
37
38
  static truncateFileName(fileName: string, maxNameLength: number | null | undefined): string;
@@ -23,17 +23,19 @@ const CONTENT_DETECTABLE_EXTENSIONS = new Set([
23
23
  'otf'
24
24
  ]);
25
25
  const PREFERRED_EXTENSIONS = { 'video/quicktime': 'mov', 'audio/mpeg': 'mp3', 'audio/aac': 'aac', 'audio/flac': 'flac' };
26
+ const PREFERRED_TYPES = { flac: 'audio/flac' };
27
+ const typeOf = (extension) => PREFERRED_TYPES[extension] ?? mime.getType(extension);
26
28
  const SVG_TYPE = { mime: 'image/svg+xml', extension: 'svg' };
27
29
  const ISO_MEDIA_CONTAINERS = [new Set(['mp4', 'm4v', 'm4a', 'm4b', 'mov', '3gp', '3g2', 'f4v']), new Set(['heic', 'heif'])];
28
- const CONTENT_DETECTABLE_MIMES = new Set([...CONTENT_DETECTABLE_EXTENSIONS, 'svg', ...ISO_MEDIA_CONTAINERS.flatMap((family) => [...family])].map((extension) => mime.getType(extension)));
30
+ const CONTENT_DETECTABLE_MIMES = new Set([...CONTENT_DETECTABLE_EXTENSIONS, 'svg', ...ISO_MEDIA_CONTAINERS.flatMap((family) => [...family])].map(typeOf));
29
31
  const sameFormat = (extensionA, extensionB) => {
30
- const type = mime.getType(extensionA);
31
- return type !== null && type === mime.getType(extensionB);
32
+ const type = typeOf(extensionA);
33
+ return type !== null && type === typeOf(extensionB);
32
34
  };
33
35
  const sameContainer = (extensionA, extensionB) => ISO_MEDIA_CONTAINERS.some((family) => family.has(extensionA) && family.has(extensionB));
34
36
  const declaredType = (file) => {
35
37
  const extension = FileHelper.getFileExtension(file.name).toLowerCase();
36
- const fromName = extension ? mime.getType(extension) : null;
38
+ const fromName = extension ? typeOf(extension) : null;
37
39
  if (fromName) {
38
40
  return { mime: fromName, extension, fromName: true };
39
41
  }
@@ -47,9 +49,28 @@ const startsWithMarkup = async (blob) => {
47
49
  return head.trimStart().startsWith('<');
48
50
  };
49
51
  const isSvgDocument = async (blob) => (await startsWithMarkup(blob)) && HtmlHelper.sanitizeSvg(await blob.text()) !== null;
50
- const isZipArchive = async (blob) => (await blob.slice(0, 4).text()) === 'PK\u0003\u0004';
52
+ const DECLARED_SIGNATURES = {
53
+ zip: [[0x50, 0x4b, 0x03, 0x04]],
54
+ png: [[0x89, 0x50, 0x4e, 0x47]],
55
+ ttf: [
56
+ [0x00, 0x01, 0x00, 0x00],
57
+ [0x74, 0x72, 0x75, 0x65]
58
+ ],
59
+ otf: [[0x4f, 0x54, 0x54, 0x4f]],
60
+ woff: [[0x77, 0x4f, 0x46, 0x46]],
61
+ woff2: [[0x77, 0x4f, 0x46, 0x32]]
62
+ };
63
+ const SIGNATURE_BYTES = 4;
64
+ const hasDeclaredSignature = async (blob, extension) => {
65
+ const signatures = DECLARED_SIGNATURES[extension];
66
+ if (!signatures) {
67
+ return false;
68
+ }
69
+ const head = new Uint8Array(await blob.slice(0, SIGNATURE_BYTES).arrayBuffer());
70
+ return signatures.some((signature) => signature.every((byte, index) => head[index] === byte));
71
+ };
51
72
  const contentType = async (blob, declared) => {
52
- if (declared?.extension === 'zip' && (await isZipArchive(blob))) {
73
+ if (declared && (await hasDeclaredSignature(blob, declared.extension))) {
53
74
  return declared;
54
75
  }
55
76
  const { fileTypeFromBlob } = await import('file-type');
@@ -57,7 +78,7 @@ const contentType = async (blob, declared) => {
57
78
  if (detected && declared && sameContainer(detected.ext, declared.extension)) {
58
79
  return declared;
59
80
  }
60
- const detectedMime = detected && CONTENT_DETECTABLE_EXTENSIONS.has(detected.ext) ? mime.getType(detected.ext) : null;
81
+ const detectedMime = detected && CONTENT_DETECTABLE_EXTENSIONS.has(detected.ext) ? typeOf(detected.ext) : null;
61
82
  if (detected && detectedMime) {
62
83
  return { mime: detectedMime, extension: detected.ext };
63
84
  }
@@ -83,10 +104,11 @@ export class FileHelper {
83
104
  * Checks a file's real type against its declared one. The bytes identify images (PNG, JPEG, WebP, GIF, AVIF, HEIC, SVG),
84
105
  * MP4, audio (WAV, MP3, M4A, AAC, FLAC), PDF, DOCX, XLSX, ZIP and fonts (WOFF2, WOFF, TTF, OTF); other formats are typed
85
106
  * as declared. An equivalent extension (`.jpeg` for `jpg`) and any member of the declared ISO media container family
86
- * (`.m4a` holding an `isom` MP4, `.heif` for HEIC) is a match, as is any ZIP archive named `.zip`. When the name lacks
87
- * the right extension, it is appended and the original name stays intact: `photo.jpg` holding a PNG → `photo.jpg.png`.
88
- * Binary formats read only the head; SVG is looked for only when something is declared, and a markup-looking file is
89
- * then read whole to confirm an SVG root.
107
+ * (`.m4a` holding an `isom` MP4, `.heif` for HEIC) is a match, as is a declared ZIP, PNG or font whose header carries its
108
+ * format's signature (any ZIP, APNG, Apple `true` TrueType, WOFF / WOFF2 of any flavor). When the name lacks the right
109
+ * extension, it is appended and the original name stays intact: `photo.jpg` holding a PNG → `photo.jpg.png`. Binary
110
+ * formats read only the head; SVG is looked for only when something is declared, and a markup-looking file is then read
111
+ * whole to confirm an SVG root.
90
112
  */
91
113
  static async checkType(file) {
92
114
  const declared = declaredType(file);
@@ -101,7 +123,7 @@ export class FileHelper {
101
123
  if (!declared) {
102
124
  return { status: 'unknown' };
103
125
  }
104
- if (CONTENT_DETECTABLE_MIMES.has(mime.getType(declared.extension))) {
126
+ if (CONTENT_DETECTABLE_MIMES.has(typeOf(declared.extension))) {
105
127
  return { status: 'contradicted', declaredExtension: declared.extension };
106
128
  }
107
129
  return { status: 'unverifiable', file: retyped(file, declared.fromName ? file.name : appended(declared.extension), declared.mime) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.84.0",
3
+ "version": "0.84.1",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",