@beechcms/core 0.6.7 → 0.8.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/dist/.tsbuildinfo +1 -1
- package/dist/antivirus/antivirus.interface.d.ts +12 -0
- package/dist/antivirus/antivirus.interface.d.ts.map +1 -0
- package/dist/antivirus/antivirus.interface.js +3 -0
- package/dist/antivirus/noop-antivirus.provider.d.ts +6 -0
- package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -0
- package/dist/antivirus/noop-antivirus.provider.js +8 -0
- package/dist/antivirus/virustotal-antivirus.provider.d.ts +8 -0
- package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -0
- package/dist/antivirus/virustotal-antivirus.provider.js +48 -0
- package/dist/common/hooks.d.ts +3 -0
- package/dist/common/hooks.d.ts.map +1 -1
- package/dist/content/richtext/richtext-render.d.ts.map +1 -1
- package/dist/content/richtext/richtext-render.js +14 -1
- package/dist/content/seed.repository.d.ts +22 -0
- package/dist/content/seed.repository.d.ts.map +1 -1
- package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
- package/dist/dashboard-layout/seed-layout.js +13 -6
- package/dist/engine/ddl.d.ts +15 -0
- package/dist/engine/ddl.d.ts.map +1 -1
- package/dist/engine/ddl.js +30 -1
- package/dist/engine/policies.d.ts.map +1 -1
- package/dist/engine/policies.js +5 -0
- package/dist/engine/seed-registry.d.ts.map +1 -1
- package/dist/engine/seed-registry.js +23 -4
- package/dist/engine/seed-types-generator.d.ts.map +1 -1
- package/dist/engine/seed-types-generator.js +8 -5
- package/dist/engine/seed-validation.d.ts +7 -0
- package/dist/engine/seed-validation.d.ts.map +1 -1
- package/dist/engine/seed-validation.js +59 -1
- package/dist/engine/types.d.ts +7 -0
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
- package/dist/engine/validation/richtext-sanitizer.js +2 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1
- package/dist/media/file-types.d.ts +103 -9
- package/dist/media/file-types.d.ts.map +1 -1
- package/dist/media/file-types.js +336 -52
- package/dist/media/magic-bytes.d.ts +21 -0
- package/dist/media/magic-bytes.d.ts.map +1 -0
- package/dist/media/magic-bytes.js +66 -0
- package/dist/oauth/authorization-code.repository.d.ts +33 -0
- package/dist/oauth/authorization-code.repository.d.ts.map +1 -0
- package/dist/oauth/authorization-code.repository.js +3 -0
- package/dist/oauth/client.repository.d.ts +17 -0
- package/dist/oauth/client.repository.d.ts.map +1 -0
- package/dist/oauth/client.repository.js +3 -0
- package/dist/oauth/consent.repository.d.ts +25 -0
- package/dist/oauth/consent.repository.d.ts.map +1 -0
- package/dist/oauth/consent.repository.js +3 -0
- package/dist/oauth/pkce.d.ts +21 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +47 -0
- package/dist/oauth/role-guard.d.ts +32 -0
- package/dist/oauth/role-guard.d.ts.map +1 -0
- package/dist/oauth/role-guard.js +14 -0
- package/dist/oauth/scopes.d.ts +27 -0
- package/dist/oauth/scopes.d.ts.map +1 -0
- package/dist/oauth/scopes.js +41 -0
- package/dist/oauth/token.repository.d.ts +46 -0
- package/dist/oauth/token.repository.d.ts.map +1 -0
- package/dist/oauth/token.repository.js +3 -0
- package/dist/observability/activity-logger.d.ts +1 -1
- package/dist/observability/activity-logger.d.ts.map +1 -1
- package/dist/rate-limit/rate-limiter.d.ts +13 -5
- package/dist/rate-limit/rate-limiter.d.ts.map +1 -1
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts +20 -0
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -0
- package/dist/rate-limit/token-bucket-rate-limiter.js +63 -0
- package/dist/search/vector-extractor.d.ts +11 -0
- package/dist/search/vector-extractor.d.ts.map +1 -0
- package/dist/search/vector-extractor.js +25 -0
- package/dist/search/vector.repository.d.ts +13 -0
- package/dist/search/vector.repository.d.ts.map +1 -0
- package/dist/search/vector.repository.js +3 -0
- package/dist/security/time-trap-token.repository.d.ts +9 -0
- package/dist/security/time-trap-token.repository.d.ts.map +1 -0
- package/dist/security/time-trap-token.repository.js +3 -0
- package/dist/security/time-trap.d.ts +7 -0
- package/dist/security/time-trap.d.ts.map +1 -0
- package/dist/security/time-trap.js +49 -0
- package/dist/webhook-crypto.d.ts +2 -0
- package/dist/webhook-crypto.d.ts.map +1 -1
- package/dist/webhook-crypto.js +2 -2
- package/dist/webhooks/webhook-crypto.d.ts +2 -0
- package/dist/webhooks/webhook-crypto.d.ts.map +1 -1
- package/dist/webhooks/webhook-crypto.js +2 -2
- package/package.json +20 -12
|
@@ -9,11 +9,12 @@ const ALLOWED_RICHTEXT_NODE_TYPES = new Set([
|
|
|
9
9
|
'listItem', 'codeBlock', 'horizontalRule', 'hardBreak', 'image',
|
|
10
10
|
'table', 'tableRow', 'tableHeader', 'tableCell',
|
|
11
11
|
'inlineMath', 'blockMath', // Mathematics extension
|
|
12
|
+
'taskList', 'taskItem',
|
|
12
13
|
]);
|
|
13
14
|
/** Allowlisted TipTap mark `type` values. */
|
|
14
15
|
const ALLOWED_RICHTEXT_MARK_TYPES = new Set([
|
|
15
16
|
'bold', 'italic', 'strike', 'code', 'link', 'highlight',
|
|
16
|
-
'superscript', 'subscript', 'textStyle',
|
|
17
|
+
'superscript', 'subscript', 'textStyle', 'underline',
|
|
17
18
|
]);
|
|
18
19
|
/** Keys that may carry URLs inside a node/mark attrs. */
|
|
19
20
|
const URL_LIKE_RICHTEXT_KEYS = new Set(['href', 'src']);
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,6 @@ export * from './engine/seeds.js';
|
|
|
14
14
|
export * from './engine/engine.js';
|
|
15
15
|
export * from './engine/validation/index.js';
|
|
16
16
|
export * from './content/richtext/richtext.js';
|
|
17
|
-
export * from './content/richtext/richtext-render.js';
|
|
18
17
|
export * from './content/slug-utils.js';
|
|
19
18
|
export * from './content/content.repository.js';
|
|
20
19
|
export * from './common/hooks.js';
|
|
@@ -28,6 +27,13 @@ export * from './auth/token-service.js';
|
|
|
28
27
|
export * from './auth/user.repository.js';
|
|
29
28
|
export * from './auth/session.repository.js';
|
|
30
29
|
export * from './auth/password-reset-token.repository.js';
|
|
30
|
+
export * from './oauth/scopes.js';
|
|
31
|
+
export * from './oauth/pkce.js';
|
|
32
|
+
export * from './oauth/role-guard.js';
|
|
33
|
+
export * from './oauth/client.repository.js';
|
|
34
|
+
export * from './oauth/authorization-code.repository.js';
|
|
35
|
+
export * from './oauth/token.repository.js';
|
|
36
|
+
export * from './oauth/consent.repository.js';
|
|
31
37
|
export * from './rate-limit/rate-limiter.js';
|
|
32
38
|
export * from './observability/activity-logger.js';
|
|
33
39
|
export * from './observability/activity-log.repository.js';
|
|
@@ -36,6 +42,8 @@ export * from './notifications/notification.repository.js';
|
|
|
36
42
|
export * from './notifications/notification-service.js';
|
|
37
43
|
export * from './widget/widget.repository.js';
|
|
38
44
|
export * from './search/search.repository.js';
|
|
45
|
+
export * from './search/vector-extractor.js';
|
|
46
|
+
export * from './search/vector.repository.js';
|
|
39
47
|
export * from './content/content-scan.repository.js';
|
|
40
48
|
export * from './common/clock.js';
|
|
41
49
|
export * from './common/id-generator.js';
|
|
@@ -69,4 +77,11 @@ export * from './dashboard-layout/kanban/kanban.js';
|
|
|
69
77
|
export * from './dashboard-layout/view-authorization.js';
|
|
70
78
|
export * from './dashboard-layout/kanban/kanban-position.repository.js';
|
|
71
79
|
export * from './common/email.provider.js';
|
|
80
|
+
export * from './media/magic-bytes.js';
|
|
81
|
+
export * from './security/time-trap.js';
|
|
82
|
+
export * from './antivirus/antivirus.interface.js';
|
|
83
|
+
export * from './antivirus/virustotal-antivirus.provider.js';
|
|
84
|
+
export * from './antivirus/noop-antivirus.provider.js';
|
|
85
|
+
export * from './rate-limit/token-bucket-rate-limiter.js';
|
|
86
|
+
export * from './security/time-trap-token.repository.js';
|
|
72
87
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAE9C,cAAc,yBAAyB,CAAA;AACvC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,mBAAmB,CAAA;AACjC,cAAc,qCAAqC,CAAA;AACnD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2CAA2C,CAAA;AACzD,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,0CAA0C,CAAA;AACxD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,oCAAoC,CAAA;AAClD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,sCAAsC,CAAA;AACpD,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,2BAA2B,CAAA;AACzC,cAAc,oCAAoC,CAAA;AAClD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,+CAA+C,CAAA;AAC7D,cAAc,mDAAmD,CAAA;AACjE,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,kCAAkC,CAAA;AAChD,cAAc,uBAAuB,CAAA;AACrC,cAAc,wCAAwC,CAAA;AACtD,cAAc,mCAAmC,CAAA;AACjD,cAAc,mCAAmC,CAAA;AACjD,cAAc,8CAA8C,CAAA;AAC5D,cAAc,0CAA0C,CAAA;AACxD,cAAc,wCAAwC,CAAA;AACtD,cAAc,wCAAwC,CAAA;AACtD,cAAc,6CAA6C,CAAA;AAC3D,cAAc,mDAAmD,CAAA;AACjE,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,sBAAsB,CAAA;AACpC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kCAAkC,CAAA;AAChD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,qCAAqC,CAAA;AACnD,cAAc,0CAA0C,CAAA;AACxD,cAAc,yDAAyD,CAAA;AACvE,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oCAAoC,CAAA;AAClD,cAAc,8CAA8C,CAAA;AAC5D,cAAc,wCAAwC,CAAA;AACtD,cAAc,2CAA2C,CAAA;AACzD,cAAc,0CAA0C,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,6 @@ export * from './engine/seeds.js';
|
|
|
16
16
|
export * from './engine/engine.js';
|
|
17
17
|
export * from './engine/validation/index.js';
|
|
18
18
|
export * from './content/richtext/richtext.js';
|
|
19
|
-
export * from './content/richtext/richtext-render.js';
|
|
20
19
|
export * from './content/slug-utils.js';
|
|
21
20
|
export * from './content/content.repository.js';
|
|
22
21
|
export * from './common/hooks.js';
|
|
@@ -30,6 +29,13 @@ export * from './auth/token-service.js';
|
|
|
30
29
|
export * from './auth/user.repository.js';
|
|
31
30
|
export * from './auth/session.repository.js';
|
|
32
31
|
export * from './auth/password-reset-token.repository.js';
|
|
32
|
+
export * from './oauth/scopes.js';
|
|
33
|
+
export * from './oauth/pkce.js';
|
|
34
|
+
export * from './oauth/role-guard.js';
|
|
35
|
+
export * from './oauth/client.repository.js';
|
|
36
|
+
export * from './oauth/authorization-code.repository.js';
|
|
37
|
+
export * from './oauth/token.repository.js';
|
|
38
|
+
export * from './oauth/consent.repository.js';
|
|
33
39
|
export * from './rate-limit/rate-limiter.js';
|
|
34
40
|
export * from './observability/activity-logger.js';
|
|
35
41
|
export * from './observability/activity-log.repository.js';
|
|
@@ -38,6 +44,8 @@ export * from './notifications/notification.repository.js';
|
|
|
38
44
|
export * from './notifications/notification-service.js';
|
|
39
45
|
export * from './widget/widget.repository.js';
|
|
40
46
|
export * from './search/search.repository.js';
|
|
47
|
+
export * from './search/vector-extractor.js';
|
|
48
|
+
export * from './search/vector.repository.js';
|
|
41
49
|
export * from './content/content-scan.repository.js';
|
|
42
50
|
export * from './common/clock.js';
|
|
43
51
|
export * from './common/id-generator.js';
|
|
@@ -71,3 +79,10 @@ export * from './dashboard-layout/kanban/kanban.js';
|
|
|
71
79
|
export * from './dashboard-layout/view-authorization.js';
|
|
72
80
|
export * from './dashboard-layout/kanban/kanban-position.repository.js';
|
|
73
81
|
export * from './common/email.provider.js';
|
|
82
|
+
export * from './media/magic-bytes.js';
|
|
83
|
+
export * from './security/time-trap.js';
|
|
84
|
+
export * from './antivirus/antivirus.interface.js';
|
|
85
|
+
export * from './antivirus/virustotal-antivirus.provider.js';
|
|
86
|
+
export * from './antivirus/noop-antivirus.provider.js';
|
|
87
|
+
export * from './rate-limit/token-bucket-rate-limiter.js';
|
|
88
|
+
export * from './security/time-trap-token.repository.js';
|
|
@@ -1,13 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File acceptance filter categories used by branch schemas and forms.
|
|
3
|
+
*/
|
|
1
4
|
export type FileAccept = 'image' | 'document' | 'any';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
/**
|
|
6
|
+
* High-level categorization of supported file types.
|
|
7
|
+
*/
|
|
8
|
+
export type FileCategory = 'image' | 'document' | 'archive';
|
|
9
|
+
/**
|
|
10
|
+
* Definition of a supported file format in BeechCMS, including MIME types,
|
|
11
|
+
* binary classification, and binary signature verification rules.
|
|
12
|
+
*/
|
|
13
|
+
export interface FileTypeDefinition {
|
|
14
|
+
/** File extension without leading dot (e.g. 'jpg', 'png', 'pdf'). */
|
|
15
|
+
readonly extension: string;
|
|
16
|
+
/** Canonical primary MIME type (e.g. 'image/jpeg'). */
|
|
17
|
+
readonly primaryMime: string;
|
|
18
|
+
/** All valid MIME types associated with this format. */
|
|
19
|
+
readonly mimeTypes: readonly string[];
|
|
20
|
+
/** High-level file category. */
|
|
21
|
+
readonly category: FileCategory;
|
|
22
|
+
/** Whether the file is a binary format requiring magic byte signature validation. */
|
|
23
|
+
readonly isBinary: boolean;
|
|
24
|
+
/** Expected magic bytes prefix if the format uses a static prefix. */
|
|
25
|
+
readonly magicBytes?: readonly number[];
|
|
26
|
+
/** Custom signature matching function for formats with complex, variable, or offset headers. */
|
|
27
|
+
readonly matchSignature?: (bytes: Uint8Array) => boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Checks whether a byte buffer starts with a specified byte prefix.
|
|
31
|
+
*
|
|
32
|
+
* @param bytes - The byte buffer to inspect.
|
|
33
|
+
* @param prefix - The sequence of bytes to match against.
|
|
34
|
+
* @returns True if the buffer starts with the given prefix, false otherwise.
|
|
35
|
+
*/
|
|
36
|
+
export declare function matchBytePrefix(bytes: Uint8Array, prefix: readonly number[]): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Evaluates whether the given byte buffer matches the binary signature of a file definition.
|
|
39
|
+
*
|
|
40
|
+
* @param def - The file type definition to check against.
|
|
41
|
+
* @param bytes - The raw byte buffer to inspect.
|
|
42
|
+
* @returns True if the bytes match the definition's signature rules, false otherwise.
|
|
43
|
+
*/
|
|
44
|
+
export declare function matchesFileSignature(def: FileTypeDefinition, bytes: Uint8Array): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Single source of truth registry for all supported file types in BeechCMS.
|
|
47
|
+
*/
|
|
48
|
+
export declare const SUPPORTED_FILE_TYPES: Record<string, FileTypeDefinition>;
|
|
49
|
+
/**
|
|
50
|
+
* SVG is intentionally blocked from general file upload as it can embed executable scripts
|
|
51
|
+
* creating a stored XSS vulnerability when served directly to browsers.
|
|
52
|
+
*/
|
|
53
|
+
export declare const BLOCKED_IMAGE_EXTENSIONS: ReadonlySet<string>;
|
|
54
|
+
/**
|
|
55
|
+
* Blocked MIME types associated with unsafe image formats.
|
|
56
|
+
*/
|
|
57
|
+
export declare const BLOCKED_IMAGE_MIME_TYPES: readonly string[];
|
|
58
|
+
/** Set of supported image file extensions. */
|
|
59
|
+
export declare const IMAGE_EXTENSIONS: ReadonlySet<string>;
|
|
60
|
+
/** Set of supported document file extensions. */
|
|
61
|
+
export declare const DOCUMENT_EXTENSIONS: ReadonlySet<string>;
|
|
62
|
+
/** Set of supported archive file extensions. */
|
|
63
|
+
export declare const ARCHIVE_EXTENSIONS: ReadonlySet<string>;
|
|
64
|
+
/** List of supported image MIME types. */
|
|
65
|
+
export declare const IMAGE_MIME_TYPES: readonly string[];
|
|
66
|
+
/** List of supported document MIME types. */
|
|
67
|
+
export declare const DOCUMENT_MIME_TYPES: readonly string[];
|
|
68
|
+
/** List of supported archive MIME types. */
|
|
69
|
+
export declare const ARCHIVE_MIME_TYPES: readonly string[];
|
|
70
|
+
/**
|
|
71
|
+
* Look up a supported file type definition by its extension.
|
|
72
|
+
*
|
|
73
|
+
* @param ext - The extension string with or without leading dot.
|
|
74
|
+
* @returns The matching FileTypeDefinition, or undefined if unsupported.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getFileTypeByExtension(ext: string | null | undefined): FileTypeDefinition | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Look up a supported file type definition by its declared MIME type.
|
|
79
|
+
*
|
|
80
|
+
* @param mime - The MIME string (e.g. 'image/png' or 'image/jpeg; charset=utf-8').
|
|
81
|
+
* @returns The matching FileTypeDefinition, or undefined if unsupported.
|
|
82
|
+
*/
|
|
83
|
+
export declare function getFileTypeByMime(mime: string | null | undefined): FileTypeDefinition | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Extracts a lowercase file extension from a URL string or query parameters.
|
|
86
|
+
*
|
|
87
|
+
* @param url - The URL string to parse.
|
|
88
|
+
* @returns The extracted extension, or null if none was found.
|
|
89
|
+
*/
|
|
10
90
|
export declare function extensionFromUrl(url: string): string | null;
|
|
11
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Checks whether a given file extension satisfies an acceptance policy ('image' | 'document' | 'any').
|
|
93
|
+
*
|
|
94
|
+
* @param ext - The file extension.
|
|
95
|
+
* @param accept - The acceptance mode.
|
|
96
|
+
* @returns True if accepted, false otherwise.
|
|
97
|
+
*/
|
|
98
|
+
export declare function isExtensionAccepted(ext: string | null | undefined, accept: FileAccept): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Checks whether a given MIME type satisfies an acceptance policy ('image' | 'document' | 'any').
|
|
101
|
+
*
|
|
102
|
+
* @param mime - The declared MIME string.
|
|
103
|
+
* @param accept - The acceptance mode.
|
|
104
|
+
* @returns True if accepted, false otherwise.
|
|
105
|
+
*/
|
|
12
106
|
export declare function isMimeAccepted(mime: string, accept: FileAccept): boolean;
|
|
13
107
|
//# sourceMappingURL=file-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-types.d.ts","sourceRoot":"","sources":["../../src/media/file-types.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"file-types.d.ts","sourceRoot":"","sources":["../../src/media/file-types.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,KAAK,CAAA;AAErD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAA;AAE3D;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,qFAAqF;IACrF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,gGAAgG;IAChG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAA;CACzD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAMrF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAKxF;AA6CD;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAgMnE,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAA4B,CAAA;AAErF;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAAmC,CAAA;AAIzF,8CAA8C;AAC9C,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAEhD,CAAA;AAED,iDAAiD;AACjD,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAEnD,CAAA;AAED,gDAAgD;AAChD,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAElD,CAAA;AAED,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAE7C,CAAA;AAED,6CAA6C;AAC7C,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAEhD,CAAA;AAED,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAE/C,CAAA;AAMD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,CAIrG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,CAMjG;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB3D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAO/F;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CASxE"}
|
package/dist/media/file-types.js
CHANGED
|
@@ -1,47 +1,322 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Checks whether a byte buffer starts with a specified byte prefix.
|
|
5
|
+
*
|
|
6
|
+
* @param bytes - The byte buffer to inspect.
|
|
7
|
+
* @param prefix - The sequence of bytes to match against.
|
|
8
|
+
* @returns True if the buffer starts with the given prefix, false otherwise.
|
|
9
|
+
*/
|
|
10
|
+
export function matchBytePrefix(bytes, prefix) {
|
|
11
|
+
if (bytes.length < prefix.length)
|
|
12
|
+
return false;
|
|
13
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
14
|
+
if (bytes[i] !== prefix[i])
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Evaluates whether the given byte buffer matches the binary signature of a file definition.
|
|
21
|
+
*
|
|
22
|
+
* @param def - The file type definition to check against.
|
|
23
|
+
* @param bytes - The raw byte buffer to inspect.
|
|
24
|
+
* @returns True if the bytes match the definition's signature rules, false otherwise.
|
|
25
|
+
*/
|
|
26
|
+
export function matchesFileSignature(def, bytes) {
|
|
27
|
+
if (!def.isBinary)
|
|
28
|
+
return true;
|
|
29
|
+
if (def.matchSignature)
|
|
30
|
+
return def.matchSignature(bytes);
|
|
31
|
+
if (def.magicBytes)
|
|
32
|
+
return matchBytePrefix(bytes, def.magicBytes);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
/** OLE Compound File Binary Format (CFBF) header used by legacy MS Office files (.doc, .xls, .ppt). */
|
|
36
|
+
const OLE_MAGIC = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
|
|
37
|
+
/** Matcher for OLE CFBF binary files. */
|
|
38
|
+
function matchOleSignature(bytes) {
|
|
39
|
+
return matchBytePrefix(bytes, OLE_MAGIC);
|
|
40
|
+
}
|
|
41
|
+
/** Standard ZIP header matcher used by .zip and Office OpenXML (.docx, .xlsx, .pptx). */
|
|
42
|
+
function matchZipSignature(bytes) {
|
|
43
|
+
if (bytes.length < 4)
|
|
44
|
+
return false;
|
|
45
|
+
return ((bytes[0] === 0x50 && bytes[1] === 0x4B && bytes[2] === 0x03 && bytes[3] === 0x04) || // standard zip
|
|
46
|
+
(bytes[0] === 0x50 && bytes[1] === 0x4B && bytes[2] === 0x05 && bytes[3] === 0x06) || // empty zip
|
|
47
|
+
(bytes[0] === 0x50 && bytes[1] === 0x4B && bytes[2] === 0x07 && bytes[3] === 0x08) // spanned zip
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
/** Common shared definition for JPEG files. */
|
|
51
|
+
const JPEG_DEFINITION = {
|
|
52
|
+
primaryMime: 'image/jpeg',
|
|
53
|
+
mimeTypes: ['image/jpeg', 'image/jpg'],
|
|
54
|
+
category: 'image',
|
|
55
|
+
isBinary: true,
|
|
56
|
+
magicBytes: [0xFF, 0xD8, 0xFF],
|
|
57
|
+
};
|
|
58
|
+
/** Common shared definition for legacy MS Office files. */
|
|
59
|
+
const OLE_DOCUMENT_DEFINITION = {
|
|
60
|
+
category: 'document',
|
|
61
|
+
isBinary: true,
|
|
62
|
+
magicBytes: [...OLE_MAGIC],
|
|
63
|
+
matchSignature: matchOleSignature,
|
|
64
|
+
};
|
|
65
|
+
/** Common shared definition for OpenXML Office files. */
|
|
66
|
+
const OPENXML_DOCUMENT_DEFINITION = {
|
|
67
|
+
category: 'document',
|
|
68
|
+
isBinary: true,
|
|
69
|
+
magicBytes: [0x50, 0x4B, 0x03, 0x04],
|
|
70
|
+
matchSignature: matchZipSignature,
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Single source of truth registry for all supported file types in BeechCMS.
|
|
74
|
+
*/
|
|
75
|
+
export const SUPPORTED_FILE_TYPES = {
|
|
76
|
+
// --- IMAGES ---
|
|
77
|
+
jpg: {
|
|
78
|
+
extension: 'jpg',
|
|
79
|
+
...JPEG_DEFINITION,
|
|
80
|
+
},
|
|
81
|
+
jpeg: {
|
|
82
|
+
extension: 'jpeg',
|
|
83
|
+
...JPEG_DEFINITION,
|
|
84
|
+
},
|
|
85
|
+
png: {
|
|
86
|
+
extension: 'png',
|
|
87
|
+
primaryMime: 'image/png',
|
|
88
|
+
mimeTypes: ['image/png'],
|
|
89
|
+
category: 'image',
|
|
90
|
+
isBinary: true,
|
|
91
|
+
magicBytes: [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],
|
|
92
|
+
},
|
|
93
|
+
gif: {
|
|
94
|
+
extension: 'gif',
|
|
95
|
+
primaryMime: 'image/gif',
|
|
96
|
+
mimeTypes: ['image/gif'],
|
|
97
|
+
category: 'image',
|
|
98
|
+
isBinary: true,
|
|
99
|
+
magicBytes: [0x47, 0x49, 0x46, 0x38],
|
|
100
|
+
matchSignature: (bytes) => bytes.length >= 6 &&
|
|
101
|
+
bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38 &&
|
|
102
|
+
(bytes[4] === 0x37 || bytes[4] === 0x39) && bytes[5] === 0x61,
|
|
103
|
+
},
|
|
104
|
+
webp: {
|
|
105
|
+
extension: 'webp',
|
|
106
|
+
primaryMime: 'image/webp',
|
|
107
|
+
mimeTypes: ['image/webp'],
|
|
108
|
+
category: 'image',
|
|
109
|
+
isBinary: true,
|
|
110
|
+
magicBytes: [0x52, 0x49, 0x46, 0x46],
|
|
111
|
+
matchSignature: (bytes) => bytes.length >= 12 &&
|
|
112
|
+
bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&
|
|
113
|
+
bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50,
|
|
114
|
+
},
|
|
115
|
+
avif: {
|
|
116
|
+
extension: 'avif',
|
|
117
|
+
primaryMime: 'image/avif',
|
|
118
|
+
mimeTypes: ['image/avif'],
|
|
119
|
+
category: 'image',
|
|
120
|
+
isBinary: true,
|
|
121
|
+
magicBytes: [0x66, 0x74, 0x79, 0x70],
|
|
122
|
+
matchSignature: (bytes) => {
|
|
123
|
+
if (bytes.length < 12)
|
|
124
|
+
return false;
|
|
125
|
+
// Check for 'ftyp' at offset 4
|
|
126
|
+
const isFtyp = bytes[4] === 0x66 && bytes[5] === 0x74 && bytes[6] === 0x79 && bytes[7] === 0x70;
|
|
127
|
+
if (!isFtyp)
|
|
128
|
+
return false;
|
|
129
|
+
// Brand check: 'avif', 'avis', 'mif1', 'msf1'
|
|
130
|
+
const brand = String.fromCharCode(bytes[8], bytes[9], bytes[10], bytes[11]);
|
|
131
|
+
return brand === 'avif' || brand === 'avis' || brand === 'mif1' || brand === 'msf1';
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
bmp: {
|
|
135
|
+
extension: 'bmp',
|
|
136
|
+
primaryMime: 'image/bmp',
|
|
137
|
+
mimeTypes: ['image/bmp'],
|
|
138
|
+
category: 'image',
|
|
139
|
+
isBinary: true,
|
|
140
|
+
magicBytes: [0x42, 0x4D],
|
|
141
|
+
},
|
|
142
|
+
ico: {
|
|
143
|
+
extension: 'ico',
|
|
144
|
+
primaryMime: 'image/x-icon',
|
|
145
|
+
mimeTypes: ['image/x-icon', 'image/vnd.microsoft.icon'],
|
|
146
|
+
category: 'image',
|
|
147
|
+
isBinary: true,
|
|
148
|
+
magicBytes: [0x00, 0x00, 0x01, 0x00],
|
|
149
|
+
},
|
|
150
|
+
// --- DOCUMENTS ---
|
|
151
|
+
pdf: {
|
|
152
|
+
extension: 'pdf',
|
|
153
|
+
primaryMime: 'application/pdf',
|
|
154
|
+
mimeTypes: ['application/pdf'],
|
|
155
|
+
category: 'document',
|
|
156
|
+
isBinary: true,
|
|
157
|
+
magicBytes: [0x25, 0x50, 0x44, 0x46],
|
|
158
|
+
},
|
|
159
|
+
docx: {
|
|
160
|
+
extension: 'docx',
|
|
161
|
+
primaryMime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
162
|
+
mimeTypes: ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
|
|
163
|
+
...OPENXML_DOCUMENT_DEFINITION,
|
|
164
|
+
},
|
|
165
|
+
doc: {
|
|
166
|
+
extension: 'doc',
|
|
167
|
+
primaryMime: 'application/msword',
|
|
168
|
+
mimeTypes: ['application/msword'],
|
|
169
|
+
...OLE_DOCUMENT_DEFINITION,
|
|
170
|
+
},
|
|
171
|
+
xlsx: {
|
|
172
|
+
extension: 'xlsx',
|
|
173
|
+
primaryMime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
174
|
+
mimeTypes: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
175
|
+
...OPENXML_DOCUMENT_DEFINITION,
|
|
176
|
+
},
|
|
177
|
+
xls: {
|
|
178
|
+
extension: 'xls',
|
|
179
|
+
primaryMime: 'application/vnd.ms-excel',
|
|
180
|
+
mimeTypes: ['application/vnd.ms-excel'],
|
|
181
|
+
...OLE_DOCUMENT_DEFINITION,
|
|
182
|
+
},
|
|
183
|
+
pptx: {
|
|
184
|
+
extension: 'pptx',
|
|
185
|
+
primaryMime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
186
|
+
mimeTypes: ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
|
|
187
|
+
...OPENXML_DOCUMENT_DEFINITION,
|
|
188
|
+
},
|
|
189
|
+
ppt: {
|
|
190
|
+
extension: 'ppt',
|
|
191
|
+
primaryMime: 'application/vnd.ms-powerpoint',
|
|
192
|
+
mimeTypes: ['application/vnd.ms-powerpoint'],
|
|
193
|
+
...OLE_DOCUMENT_DEFINITION,
|
|
194
|
+
},
|
|
195
|
+
txt: {
|
|
196
|
+
extension: 'txt',
|
|
197
|
+
primaryMime: 'text/plain',
|
|
198
|
+
mimeTypes: ['text/plain'],
|
|
199
|
+
category: 'document',
|
|
200
|
+
isBinary: false,
|
|
201
|
+
},
|
|
202
|
+
csv: {
|
|
203
|
+
extension: 'csv',
|
|
204
|
+
primaryMime: 'text/csv',
|
|
205
|
+
mimeTypes: ['text/csv'],
|
|
206
|
+
category: 'document',
|
|
207
|
+
isBinary: false,
|
|
208
|
+
},
|
|
209
|
+
md: {
|
|
210
|
+
extension: 'md',
|
|
211
|
+
primaryMime: 'text/markdown',
|
|
212
|
+
mimeTypes: ['text/markdown'],
|
|
213
|
+
category: 'document',
|
|
214
|
+
isBinary: false,
|
|
215
|
+
},
|
|
216
|
+
// --- ARCHIVES & DATA ---
|
|
217
|
+
zip: {
|
|
218
|
+
extension: 'zip',
|
|
219
|
+
primaryMime: 'application/zip',
|
|
220
|
+
mimeTypes: ['application/zip', 'application/x-zip-compressed'],
|
|
221
|
+
category: 'archive',
|
|
222
|
+
isBinary: true,
|
|
223
|
+
magicBytes: [0x50, 0x4B, 0x03, 0x04],
|
|
224
|
+
matchSignature: matchZipSignature,
|
|
225
|
+
},
|
|
226
|
+
'7z': {
|
|
227
|
+
extension: '7z',
|
|
228
|
+
primaryMime: 'application/x-7z-compressed',
|
|
229
|
+
mimeTypes: ['application/x-7z-compressed'],
|
|
230
|
+
category: 'archive',
|
|
231
|
+
isBinary: true,
|
|
232
|
+
magicBytes: [0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C],
|
|
233
|
+
},
|
|
234
|
+
tar: {
|
|
235
|
+
extension: 'tar',
|
|
236
|
+
primaryMime: 'application/x-tar',
|
|
237
|
+
mimeTypes: ['application/x-tar'],
|
|
238
|
+
category: 'archive',
|
|
239
|
+
isBinary: true,
|
|
240
|
+
magicBytes: [0x75, 0x73, 0x74, 0x61, 0x72], // 'ustar'
|
|
241
|
+
matchSignature: (bytes) => {
|
|
242
|
+
if (bytes.length < 512)
|
|
243
|
+
return false;
|
|
244
|
+
// Check for POSIX 'ustar' at offset 257
|
|
245
|
+
return (bytes[257] === 0x75 && bytes[258] === 0x73 && bytes[259] === 0x74 &&
|
|
246
|
+
bytes[260] === 0x61 && bytes[261] === 0x72);
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
gz: {
|
|
250
|
+
extension: 'gz',
|
|
251
|
+
primaryMime: 'application/gzip',
|
|
252
|
+
mimeTypes: ['application/gzip'],
|
|
253
|
+
category: 'archive',
|
|
254
|
+
isBinary: true,
|
|
255
|
+
magicBytes: [0x1F, 0x8B],
|
|
256
|
+
},
|
|
257
|
+
json: {
|
|
258
|
+
extension: 'json',
|
|
259
|
+
primaryMime: 'application/json',
|
|
260
|
+
mimeTypes: ['application/json'],
|
|
261
|
+
category: 'archive',
|
|
262
|
+
isBinary: false,
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* SVG is intentionally blocked from general file upload as it can embed executable scripts
|
|
267
|
+
* creating a stored XSS vulnerability when served directly to browsers.
|
|
268
|
+
*/
|
|
7
269
|
export const BLOCKED_IMAGE_EXTENSIONS = new Set(['svg', 'svgz']);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// `image/*` prefix, which would permit `image/svg+xml` and other XSS vectors.
|
|
12
|
-
export const IMAGE_MIME_TYPES = [
|
|
13
|
-
'image/jpeg',
|
|
14
|
-
'image/png',
|
|
15
|
-
'image/gif',
|
|
16
|
-
'image/webp',
|
|
17
|
-
'image/avif',
|
|
18
|
-
'image/bmp',
|
|
19
|
-
'image/x-icon',
|
|
20
|
-
'image/vnd.microsoft.icon',
|
|
21
|
-
];
|
|
22
|
-
// Blocked image MIME types — listed explicitly so upload code can reject them
|
|
23
|
-
// with a clear error rather than a generic "file type not allowed".
|
|
270
|
+
/**
|
|
271
|
+
* Blocked MIME types associated with unsafe image formats.
|
|
272
|
+
*/
|
|
24
273
|
export const BLOCKED_IMAGE_MIME_TYPES = ['image/svg+xml', 'image/svg'];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export const ARCHIVE_MIME_TYPES =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
274
|
+
const BLOCKED_IMAGE_MIME_SET = new Set(BLOCKED_IMAGE_MIME_TYPES);
|
|
275
|
+
/** Set of supported image file extensions. */
|
|
276
|
+
export const IMAGE_EXTENSIONS = new Set(Object.values(SUPPORTED_FILE_TYPES).filter((def) => def.category === 'image').map((def) => def.extension));
|
|
277
|
+
/** Set of supported document file extensions. */
|
|
278
|
+
export const DOCUMENT_EXTENSIONS = new Set(Object.values(SUPPORTED_FILE_TYPES).filter((def) => def.category === 'document').map((def) => def.extension));
|
|
279
|
+
/** Set of supported archive file extensions. */
|
|
280
|
+
export const ARCHIVE_EXTENSIONS = new Set(Object.values(SUPPORTED_FILE_TYPES).filter((def) => def.category === 'archive').map((def) => def.extension));
|
|
281
|
+
/** List of supported image MIME types. */
|
|
282
|
+
export const IMAGE_MIME_TYPES = Array.from(new Set(Object.values(SUPPORTED_FILE_TYPES).filter((def) => def.category === 'image').flatMap((def) => def.mimeTypes)));
|
|
283
|
+
/** List of supported document MIME types. */
|
|
284
|
+
export const DOCUMENT_MIME_TYPES = Array.from(new Set(Object.values(SUPPORTED_FILE_TYPES).filter((def) => def.category === 'document').flatMap((def) => def.mimeTypes)));
|
|
285
|
+
/** List of supported archive MIME types. */
|
|
286
|
+
export const ARCHIVE_MIME_TYPES = Array.from(new Set(Object.values(SUPPORTED_FILE_TYPES).filter((def) => def.category === 'archive').flatMap((def) => def.mimeTypes)));
|
|
287
|
+
const IMAGE_MIME_SET = new Set(IMAGE_MIME_TYPES);
|
|
288
|
+
const DOCUMENT_MIME_SET = new Set(DOCUMENT_MIME_TYPES);
|
|
289
|
+
const ARCHIVE_MIME_SET = new Set(ARCHIVE_MIME_TYPES);
|
|
290
|
+
/**
|
|
291
|
+
* Look up a supported file type definition by its extension.
|
|
292
|
+
*
|
|
293
|
+
* @param ext - The extension string with or without leading dot.
|
|
294
|
+
* @returns The matching FileTypeDefinition, or undefined if unsupported.
|
|
295
|
+
*/
|
|
296
|
+
export function getFileTypeByExtension(ext) {
|
|
297
|
+
if (!ext)
|
|
298
|
+
return undefined;
|
|
299
|
+
const normalised = ext.replace(/^\./, '').toLowerCase().trim();
|
|
300
|
+
return SUPPORTED_FILE_TYPES[normalised];
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Look up a supported file type definition by its declared MIME type.
|
|
304
|
+
*
|
|
305
|
+
* @param mime - The MIME string (e.g. 'image/png' or 'image/jpeg; charset=utf-8').
|
|
306
|
+
* @returns The matching FileTypeDefinition, or undefined if unsupported.
|
|
307
|
+
*/
|
|
308
|
+
export function getFileTypeByMime(mime) {
|
|
309
|
+
if (!mime)
|
|
310
|
+
return undefined;
|
|
311
|
+
const normalised = mime.split(';')[0].trim().toLowerCase();
|
|
312
|
+
return Object.values(SUPPORTED_FILE_TYPES).find((def) => def.mimeTypes.some((m) => m.toLowerCase() === normalised));
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Extracts a lowercase file extension from a URL string or query parameters.
|
|
316
|
+
*
|
|
317
|
+
* @param url - The URL string to parse.
|
|
318
|
+
* @returns The extracted extension, or null if none was found.
|
|
319
|
+
*/
|
|
45
320
|
export function extensionFromUrl(url) {
|
|
46
321
|
try {
|
|
47
322
|
const parsed = new URL(url);
|
|
@@ -50,9 +325,9 @@ export function extensionFromUrl(url) {
|
|
|
50
325
|
if (dot >= 0 && dot < path.length - 1) {
|
|
51
326
|
return path.slice(dot + 1).toLowerCase();
|
|
52
327
|
}
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
return
|
|
328
|
+
const param = parsed.searchParams.get('fm') || parsed.searchParams.get('format') || parsed.searchParams.get('ext');
|
|
329
|
+
if (param && /^[a-z0-9]+$/i.test(param)) {
|
|
330
|
+
return param.toLowerCase();
|
|
56
331
|
}
|
|
57
332
|
return null;
|
|
58
333
|
}
|
|
@@ -60,6 +335,13 @@ export function extensionFromUrl(url) {
|
|
|
60
335
|
return null;
|
|
61
336
|
}
|
|
62
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Checks whether a given file extension satisfies an acceptance policy ('image' | 'document' | 'any').
|
|
340
|
+
*
|
|
341
|
+
* @param ext - The file extension.
|
|
342
|
+
* @param accept - The acceptance mode.
|
|
343
|
+
* @returns True if accepted, false otherwise.
|
|
344
|
+
*/
|
|
63
345
|
export function isExtensionAccepted(ext, accept) {
|
|
64
346
|
if (accept === 'any')
|
|
65
347
|
return true;
|
|
@@ -73,21 +355,23 @@ export function isExtensionAccepted(ext, accept) {
|
|
|
73
355
|
return DOCUMENT_EXTENSIONS.has(ext);
|
|
74
356
|
return false;
|
|
75
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Checks whether a given MIME type satisfies an acceptance policy ('image' | 'document' | 'any').
|
|
360
|
+
*
|
|
361
|
+
* @param mime - The declared MIME string.
|
|
362
|
+
* @param accept - The acceptance mode.
|
|
363
|
+
* @returns True if accepted, false otherwise.
|
|
364
|
+
*/
|
|
76
365
|
export function isMimeAccepted(mime, accept) {
|
|
77
|
-
// Normalise: strip parameters (e.g. "image/jpeg; charset=utf-8" → "image/jpeg")
|
|
78
366
|
const normalised = mime.split(';')[0].trim().toLowerCase();
|
|
79
|
-
|
|
80
|
-
// embedded scripts and is rendered by browsers when served inline.
|
|
81
|
-
if (BLOCKED_IMAGE_MIME_TYPES.includes(normalised))
|
|
367
|
+
if (BLOCKED_IMAGE_MIME_SET.has(normalised))
|
|
82
368
|
return false;
|
|
83
369
|
if (accept === 'any') {
|
|
84
|
-
return
|
|
85
|
-
|| DOCUMENT_MIME_TYPES.includes(normalised)
|
|
86
|
-
|| ARCHIVE_MIME_TYPES.includes(normalised);
|
|
370
|
+
return IMAGE_MIME_SET.has(normalised) || DOCUMENT_MIME_SET.has(normalised) || ARCHIVE_MIME_SET.has(normalised);
|
|
87
371
|
}
|
|
88
372
|
if (accept === 'image')
|
|
89
|
-
return
|
|
373
|
+
return IMAGE_MIME_SET.has(normalised);
|
|
90
374
|
if (accept === 'document')
|
|
91
|
-
return
|
|
375
|
+
return DOCUMENT_MIME_SET.has(normalised);
|
|
92
376
|
return false;
|
|
93
377
|
}
|