@easy-cms/core 0.38.0 → 0.39.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/index.d.ts +20 -4
- package/dist/index.js +181 -102
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2705,9 +2705,20 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<ResolvedConfig
|
|
|
2705
2705
|
|
|
2706
2706
|
/** File types Easy CMS recognizes from their contents (FR-UPL-03). */
|
|
2707
2707
|
declare const EXTENSIONS: Readonly<Record<string, string>>;
|
|
2708
|
-
/**
|
|
2709
|
-
|
|
2710
|
-
|
|
2708
|
+
/**
|
|
2709
|
+
* Names for several types at once, usable in `upload.mimeTypes` and an upload field's
|
|
2710
|
+
* `mimeTypes`: `documents` (PDF, Word, Excel, PowerPoint, OpenDocument, CSV, text), `office`
|
|
2711
|
+
* (docx, xlsx, pptx) and `archives` (zip).
|
|
2712
|
+
*/
|
|
2713
|
+
declare const MIME_GROUPS: Readonly<Record<string, readonly string[]>>;
|
|
2714
|
+
/** Patterns with group names (`documents`…) replaced by their types. */
|
|
2715
|
+
declare function expandMimeTypes(patterns: readonly string[]): string[];
|
|
2716
|
+
/**
|
|
2717
|
+
* Detects the MIME type from file contents, ignoring the type the client sent. The name only
|
|
2718
|
+
* tells CSV from plain text, which have the same contents.
|
|
2719
|
+
*/
|
|
2720
|
+
declare function sniffMimeType(data: Uint8Array, name?: string): string | undefined;
|
|
2721
|
+
/** `image/*` style matching; group names (`documents`…) stand for their types. */
|
|
2711
2722
|
declare function mimeAllowed(type: string, allowed: readonly string[]): boolean;
|
|
2712
2723
|
/** Width and height of PNG, GIF, JPEG and WebP images without decoding them. */
|
|
2713
2724
|
declare function imageDimensions(data: Uint8Array, type: string): {
|
|
@@ -2949,6 +2960,11 @@ interface AdminSchema {
|
|
|
2949
2960
|
modules: string[];
|
|
2950
2961
|
/** Files can be uploaded from links (`upload.fromURL`). */
|
|
2951
2962
|
uploadFromURL: boolean;
|
|
2963
|
+
/** What the media library takes (`upload.maxFileSize`, `upload.mimeTypes` with groups expanded). */
|
|
2964
|
+
upload: {
|
|
2965
|
+
maxFileSize: number;
|
|
2966
|
+
mimeTypes: string[];
|
|
2967
|
+
};
|
|
2952
2968
|
/**
|
|
2953
2969
|
* The media library has folders (`upload.folders`); `permissions`: this user may choose which
|
|
2954
2970
|
* roles use each folder (admins, with `auth.rbac`).
|
|
@@ -3089,4 +3105,4 @@ declare function validateConfig(config: Config): ConfigIssue[];
|
|
|
3089
3105
|
/** The version of `@easy-cms/core`, set when the package is built. */
|
|
3090
3106
|
declare const VERSION: string;
|
|
3091
3107
|
|
|
3092
|
-
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminAttention, type AdminBackup, type AdminBackups, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminDeliveries, type AdminDelivery, type AdminEmail, type AdminEmailDelivery, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminPage, type AdminPageRef, type AdminRole, type AdminRoleChange, type AdminRoleField, type AdminRoleTarget, type AdminRoleView, type AdminRoles, type AdminSchema, type AdminSso, type AdminStatus, type AdminViewAccess, type AdminWebhookDelivery, type AdminWidgetRef, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, type AuditChange, type AuditConfig, type AuditEntry, AuditLog, type AuditPage, type AuditVerification, Auth, type AuthConfig, type AuthProvider, type AuthProviderProfile, type AuthProviderRequest, type AuthUser, BUILTIN_COLLECTIONS, type BackupState, type BackupsConfig, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type DashboardWidget, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DeliveryKind, type DeliveryState, type DepthOptions, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailCheck, type EmailField, type EmailMessage, type EmailSetting, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, FOLDER_LEVELS, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldRule, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, type FolderLevel, type FolderPermissions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MEDIA_FOLDERS, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, MediaFolders, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginInfo, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, ROLE_OPERATIONS, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoleOperation, type RolePermissions, Roles, type RoutesConfig, SESSION_COOKIE, SSO_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type SsoOutcome, type SsoProviderRef, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, type UploadFromURLConfig, type UserIdentity, VERSION, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword, writeBackupFile };
|
|
3108
|
+
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminAttention, type AdminBackup, type AdminBackups, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminDeliveries, type AdminDelivery, type AdminEmail, type AdminEmailDelivery, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminPage, type AdminPageRef, type AdminRole, type AdminRoleChange, type AdminRoleField, type AdminRoleTarget, type AdminRoleView, type AdminRoles, type AdminSchema, type AdminSso, type AdminStatus, type AdminViewAccess, type AdminWebhookDelivery, type AdminWidgetRef, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, type AuditChange, type AuditConfig, type AuditEntry, AuditLog, type AuditPage, type AuditVerification, Auth, type AuthConfig, type AuthProvider, type AuthProviderProfile, type AuthProviderRequest, type AuthUser, BUILTIN_COLLECTIONS, type BackupState, type BackupsConfig, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type DashboardWidget, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DeliveryKind, type DeliveryState, type DepthOptions, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailCheck, type EmailField, type EmailMessage, type EmailSetting, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, FOLDER_LEVELS, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldRule, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, type FolderLevel, type FolderPermissions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MEDIA_FOLDERS, MIME_GROUPS, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, MediaFolders, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginInfo, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, ROLE_OPERATIONS, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoleOperation, type RolePermissions, Roles, type RoutesConfig, SESSION_COOKIE, SSO_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type SsoOutcome, type SsoProviderRef, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, type UploadFromURLConfig, type UserIdentity, VERSION, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, expandMimeTypes, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword, writeBackupFile };
|
package/dist/index.js
CHANGED
|
@@ -917,11 +917,162 @@ var PayloadTooLargeError = class extends EasyCMSError {
|
|
|
917
917
|
}
|
|
918
918
|
};
|
|
919
919
|
|
|
920
|
+
// src/media.ts
|
|
921
|
+
var EXTENSIONS = {
|
|
922
|
+
"image/png": "png",
|
|
923
|
+
"image/jpeg": "jpg",
|
|
924
|
+
"image/gif": "gif",
|
|
925
|
+
"image/webp": "webp",
|
|
926
|
+
"image/avif": "avif",
|
|
927
|
+
"image/svg+xml": "svg",
|
|
928
|
+
"application/pdf": "pdf",
|
|
929
|
+
"video/mp4": "mp4",
|
|
930
|
+
"video/webm": "webm",
|
|
931
|
+
"video/quicktime": "mov",
|
|
932
|
+
"audio/mpeg": "mp3",
|
|
933
|
+
"audio/wav": "wav",
|
|
934
|
+
"audio/ogg": "ogg",
|
|
935
|
+
"audio/mp4": "m4a",
|
|
936
|
+
"text/plain": "txt",
|
|
937
|
+
"text/csv": "csv",
|
|
938
|
+
"application/zip": "zip",
|
|
939
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
940
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
|
941
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
|
|
942
|
+
"application/vnd.oasis.opendocument.text": "odt",
|
|
943
|
+
"application/vnd.oasis.opendocument.spreadsheet": "ods",
|
|
944
|
+
"application/vnd.oasis.opendocument.presentation": "odp"
|
|
945
|
+
};
|
|
946
|
+
var OFFICE = [
|
|
947
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
948
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
949
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
950
|
+
];
|
|
951
|
+
var MIME_GROUPS = {
|
|
952
|
+
office: OFFICE,
|
|
953
|
+
documents: [
|
|
954
|
+
"application/pdf",
|
|
955
|
+
...OFFICE,
|
|
956
|
+
"application/vnd.oasis.opendocument.text",
|
|
957
|
+
"application/vnd.oasis.opendocument.spreadsheet",
|
|
958
|
+
"application/vnd.oasis.opendocument.presentation",
|
|
959
|
+
"text/csv",
|
|
960
|
+
"text/plain"
|
|
961
|
+
],
|
|
962
|
+
archives: ["application/zip"]
|
|
963
|
+
};
|
|
964
|
+
function expandMimeTypes(patterns) {
|
|
965
|
+
return [...new Set(patterns.flatMap((p) => MIME_GROUPS[p.toLowerCase()] ?? [p]))];
|
|
966
|
+
}
|
|
967
|
+
var startsWith = (data, bytes, offset = 0) => bytes.every((b, i) => data[offset + i] === b);
|
|
968
|
+
var ascii = (data, start, end) => String.fromCharCode(...data.subarray(start, end));
|
|
969
|
+
var contains = (data, text2) => Buffer.from(data.buffer, data.byteOffset, data.byteLength).includes(text2, 0, "latin1");
|
|
970
|
+
function zipType(data) {
|
|
971
|
+
if (ascii(data, 30, 38) === "mimetype") {
|
|
972
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
973
|
+
const start = 30 + view.getUint16(26, true) + view.getUint16(28, true);
|
|
974
|
+
const type = ascii(data, start, start + Math.min(view.getUint32(18, true), 80));
|
|
975
|
+
if (EXTENSIONS[type]?.startsWith("od")) return type;
|
|
976
|
+
}
|
|
977
|
+
if (contains(data, "word/document.xml")) return OFFICE[0];
|
|
978
|
+
if (contains(data, "xl/workbook.xml")) return OFFICE[1];
|
|
979
|
+
if (contains(data, "ppt/presentation.xml")) return OFFICE[2];
|
|
980
|
+
return "application/zip";
|
|
981
|
+
}
|
|
982
|
+
function sniffMimeType(data, name = "") {
|
|
983
|
+
if (startsWith(data, [137, 80, 78, 71, 13, 10, 26, 10])) return "image/png";
|
|
984
|
+
if (startsWith(data, [255, 216, 255])) return "image/jpeg";
|
|
985
|
+
if (ascii(data, 0, 6) === "GIF87a" || ascii(data, 0, 6) === "GIF89a") return "image/gif";
|
|
986
|
+
if (ascii(data, 0, 4) === "RIFF" && ascii(data, 8, 12) === "WEBP") return "image/webp";
|
|
987
|
+
if (ascii(data, 0, 4) === "RIFF" && ascii(data, 8, 12) === "WAVE") return "audio/wav";
|
|
988
|
+
if (ascii(data, 4, 8) === "ftyp") {
|
|
989
|
+
const brand = ascii(data, 8, 12);
|
|
990
|
+
if (brand === "avif" || brand === "avis") return "image/avif";
|
|
991
|
+
if (["isom", "iso2", "mp41", "mp42", "avc1", "M4V "].includes(brand)) return "video/mp4";
|
|
992
|
+
if (brand === "M4A ") return "audio/mp4";
|
|
993
|
+
if (brand === "qt ") return "video/quicktime";
|
|
994
|
+
}
|
|
995
|
+
if (ascii(data, 0, 5) === "%PDF-") return "application/pdf";
|
|
996
|
+
if (startsWith(data, [80, 75, 3, 4])) return zipType(data);
|
|
997
|
+
if (ascii(data, 0, 4) === "OggS") return "audio/ogg";
|
|
998
|
+
if (startsWith(data, [26, 69, 223, 163]) && ascii(data, 0, 64).includes("webm"))
|
|
999
|
+
return "video/webm";
|
|
1000
|
+
if (ascii(data, 0, 3) === "ID3" || data[0] === 255 && ((data[1] ?? 0) & 230) === 226)
|
|
1001
|
+
return "audio/mpeg";
|
|
1002
|
+
const head = data.subarray(0, 4096);
|
|
1003
|
+
if (head.includes(0)) return void 0;
|
|
1004
|
+
let text2;
|
|
1005
|
+
try {
|
|
1006
|
+
text2 = new TextDecoder("utf-8", { fatal: true }).decode(head);
|
|
1007
|
+
} catch {
|
|
1008
|
+
return void 0;
|
|
1009
|
+
}
|
|
1010
|
+
const trimmed = text2.replace(/^/, "").trimStart();
|
|
1011
|
+
if (/^(<\?xml[^>]*>\s*)?(<!--[\s\S]*?-->\s*)*(<!DOCTYPE svg[^>]*>\s*)?<svg[\s>]/i.test(trimmed))
|
|
1012
|
+
return "image/svg+xml";
|
|
1013
|
+
return /\.csv$/i.test(name) ? "text/csv" : "text/plain";
|
|
1014
|
+
}
|
|
1015
|
+
function mimeAllowed(type, allowed2) {
|
|
1016
|
+
return expandMimeTypes(allowed2).some(
|
|
1017
|
+
(pattern) => pattern.endsWith("/*") ? type.startsWith(pattern.slice(0, -1)) : pattern === type
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
function imageDimensions(data, type) {
|
|
1021
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1022
|
+
try {
|
|
1023
|
+
switch (type) {
|
|
1024
|
+
case "image/png":
|
|
1025
|
+
return { width: view.getUint32(16), height: view.getUint32(20) };
|
|
1026
|
+
case "image/gif":
|
|
1027
|
+
return { width: view.getUint16(6, true), height: view.getUint16(8, true) };
|
|
1028
|
+
case "image/webp": {
|
|
1029
|
+
const chunk = ascii(data, 12, 16);
|
|
1030
|
+
if (chunk === "VP8 ")
|
|
1031
|
+
return {
|
|
1032
|
+
width: view.getUint16(26, true) & 16383,
|
|
1033
|
+
height: view.getUint16(28, true) & 16383
|
|
1034
|
+
};
|
|
1035
|
+
if (chunk === "VP8L") {
|
|
1036
|
+
const bits = view.getUint32(21, true);
|
|
1037
|
+
return { width: (bits & 16383) + 1, height: (bits >> 14 & 16383) + 1 };
|
|
1038
|
+
}
|
|
1039
|
+
if (chunk === "VP8X") {
|
|
1040
|
+
const w = view.getUint16(24, true) | view.getUint8(26) << 16;
|
|
1041
|
+
const h = view.getUint16(27, true) | view.getUint8(29) << 16;
|
|
1042
|
+
return { width: w + 1, height: h + 1 };
|
|
1043
|
+
}
|
|
1044
|
+
return void 0;
|
|
1045
|
+
}
|
|
1046
|
+
case "image/jpeg": {
|
|
1047
|
+
let offset = 2;
|
|
1048
|
+
while (offset + 9 < data.length) {
|
|
1049
|
+
if (data[offset] !== 255) return void 0;
|
|
1050
|
+
const marker = data[offset + 1];
|
|
1051
|
+
const length = view.getUint16(offset + 2);
|
|
1052
|
+
if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
|
|
1053
|
+
return { width: view.getUint16(offset + 7), height: view.getUint16(offset + 5) };
|
|
1054
|
+
}
|
|
1055
|
+
offset += 2 + length;
|
|
1056
|
+
}
|
|
1057
|
+
return void 0;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
} catch {
|
|
1061
|
+
return void 0;
|
|
1062
|
+
}
|
|
1063
|
+
return void 0;
|
|
1064
|
+
}
|
|
1065
|
+
function storageKey(originalName, type, random2) {
|
|
1066
|
+
const name = originalName.split(/[\\/]/).pop() ?? "";
|
|
1067
|
+
const base = name.replace(/(?<=.)\.[^.]*$/, "").normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "file";
|
|
1068
|
+
return `${base}-${random2}.${EXTENSIONS[type] ?? "bin"}`;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
920
1071
|
// src/fields.ts
|
|
921
1072
|
var isHasMany = (field) => (field.type === "select" || field.type === "relationship" || field.type === "upload") && field.hasMany === true;
|
|
922
1073
|
function mimeAllowedBy(mimeType, patterns) {
|
|
923
1074
|
const type = mimeType.toLowerCase();
|
|
924
|
-
return patterns.some((pattern) => {
|
|
1075
|
+
return expandMimeTypes(patterns).some((pattern) => {
|
|
925
1076
|
const p = pattern.toLowerCase();
|
|
926
1077
|
return p.endsWith("/*") ? type.startsWith(p.slice(0, -1)) : type === p;
|
|
927
1078
|
});
|
|
@@ -4359,6 +4510,13 @@ function validateUpload(config, add) {
|
|
|
4359
4510
|
if (upload.dir !== void 0 && (typeof upload.dir !== "string" || upload.dir.length === 0)) {
|
|
4360
4511
|
add("upload.dir", "must be a non-empty string");
|
|
4361
4512
|
}
|
|
4513
|
+
const mimeTypes = upload.mimeTypes;
|
|
4514
|
+
if (mimeTypes !== void 0 && (!Array.isArray(mimeTypes) || mimeTypes.length === 0 || !mimeTypes.every((t) => typeof t === "string" && MIME_PATTERN.test(t))))
|
|
4515
|
+
add(
|
|
4516
|
+
"upload.mimeTypes",
|
|
4517
|
+
"must be a list of MIME types or groups (documents, office, archives)",
|
|
4518
|
+
"e.g. mimeTypes: ['image/*', 'documents']"
|
|
4519
|
+
);
|
|
4362
4520
|
if (upload.folders !== void 0 && typeof upload.folders !== "boolean")
|
|
4363
4521
|
add("upload.folders", "must be true or false");
|
|
4364
4522
|
const fromURL = upload.fromURL;
|
|
@@ -4386,6 +4544,7 @@ function validateUpload(config, add) {
|
|
|
4386
4544
|
if (allowPrivate !== void 0 && typeof allowPrivate !== "boolean")
|
|
4387
4545
|
add("upload.fromURL.allowPrivate", "must be true or false");
|
|
4388
4546
|
}
|
|
4547
|
+
var MIME_PATTERN = /^([\w.+-]+\/(\*|[\w.+-]+)|documents|office|archives)$/i;
|
|
4389
4548
|
var HOST_PATTERN = /^(\*|(\*\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*)$/i;
|
|
4390
4549
|
function validateAudit(config, add) {
|
|
4391
4550
|
const audit = config.audit;
|
|
@@ -4635,8 +4794,12 @@ function validateField(field, path, siblings, collectionSlugs, add) {
|
|
|
4635
4794
|
validateManyRange(field, path, add);
|
|
4636
4795
|
if (field.mimeTypes !== void 0) {
|
|
4637
4796
|
const types = field.mimeTypes;
|
|
4638
|
-
if (!Array.isArray(types) || types.length === 0 || !types.every((t) => typeof t === "string" &&
|
|
4639
|
-
add(
|
|
4797
|
+
if (!Array.isArray(types) || types.length === 0 || !types.every((t) => typeof t === "string" && MIME_PATTERN.test(t))) {
|
|
4798
|
+
add(
|
|
4799
|
+
`${path}.mimeTypes`,
|
|
4800
|
+
"must be a list of MIME types or groups (documents, office, archives)",
|
|
4801
|
+
"e.g. mimeTypes: ['image/*', 'documents']"
|
|
4802
|
+
);
|
|
4640
4803
|
}
|
|
4641
4804
|
}
|
|
4642
4805
|
break;
|
|
@@ -5119,100 +5282,6 @@ var silentLogger = { info() {
|
|
|
5119
5282
|
}, error() {
|
|
5120
5283
|
} };
|
|
5121
5284
|
|
|
5122
|
-
// src/media.ts
|
|
5123
|
-
var EXTENSIONS = {
|
|
5124
|
-
"image/png": "png",
|
|
5125
|
-
"image/jpeg": "jpg",
|
|
5126
|
-
"image/gif": "gif",
|
|
5127
|
-
"image/webp": "webp",
|
|
5128
|
-
"image/avif": "avif",
|
|
5129
|
-
"image/svg+xml": "svg",
|
|
5130
|
-
"application/pdf": "pdf",
|
|
5131
|
-
"video/mp4": "mp4",
|
|
5132
|
-
"text/plain": "txt"
|
|
5133
|
-
};
|
|
5134
|
-
var startsWith = (data, bytes, offset = 0) => bytes.every((b, i) => data[offset + i] === b);
|
|
5135
|
-
var ascii = (data, start, end) => String.fromCharCode(...data.subarray(start, end));
|
|
5136
|
-
function sniffMimeType(data) {
|
|
5137
|
-
if (startsWith(data, [137, 80, 78, 71, 13, 10, 26, 10])) return "image/png";
|
|
5138
|
-
if (startsWith(data, [255, 216, 255])) return "image/jpeg";
|
|
5139
|
-
if (ascii(data, 0, 6) === "GIF87a" || ascii(data, 0, 6) === "GIF89a") return "image/gif";
|
|
5140
|
-
if (ascii(data, 0, 4) === "RIFF" && ascii(data, 8, 12) === "WEBP") return "image/webp";
|
|
5141
|
-
if (ascii(data, 4, 8) === "ftyp") {
|
|
5142
|
-
const brand = ascii(data, 8, 12);
|
|
5143
|
-
if (brand === "avif" || brand === "avis") return "image/avif";
|
|
5144
|
-
if (["isom", "iso2", "mp41", "mp42", "avc1", "M4V "].includes(brand)) return "video/mp4";
|
|
5145
|
-
}
|
|
5146
|
-
if (ascii(data, 0, 5) === "%PDF-") return "application/pdf";
|
|
5147
|
-
const head = data.subarray(0, 4096);
|
|
5148
|
-
if (head.includes(0)) return void 0;
|
|
5149
|
-
let text2;
|
|
5150
|
-
try {
|
|
5151
|
-
text2 = new TextDecoder("utf-8", { fatal: true }).decode(head);
|
|
5152
|
-
} catch {
|
|
5153
|
-
return void 0;
|
|
5154
|
-
}
|
|
5155
|
-
const trimmed = text2.replace(/^/, "").trimStart();
|
|
5156
|
-
if (/^(<\?xml[^>]*>\s*)?(<!--[\s\S]*?-->\s*)*(<!DOCTYPE svg[^>]*>\s*)?<svg[\s>]/i.test(trimmed))
|
|
5157
|
-
return "image/svg+xml";
|
|
5158
|
-
return "text/plain";
|
|
5159
|
-
}
|
|
5160
|
-
function mimeAllowed(type, allowed2) {
|
|
5161
|
-
return allowed2.some(
|
|
5162
|
-
(pattern) => pattern.endsWith("/*") ? type.startsWith(pattern.slice(0, -1)) : pattern === type
|
|
5163
|
-
);
|
|
5164
|
-
}
|
|
5165
|
-
function imageDimensions(data, type) {
|
|
5166
|
-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
5167
|
-
try {
|
|
5168
|
-
switch (type) {
|
|
5169
|
-
case "image/png":
|
|
5170
|
-
return { width: view.getUint32(16), height: view.getUint32(20) };
|
|
5171
|
-
case "image/gif":
|
|
5172
|
-
return { width: view.getUint16(6, true), height: view.getUint16(8, true) };
|
|
5173
|
-
case "image/webp": {
|
|
5174
|
-
const chunk = ascii(data, 12, 16);
|
|
5175
|
-
if (chunk === "VP8 ")
|
|
5176
|
-
return {
|
|
5177
|
-
width: view.getUint16(26, true) & 16383,
|
|
5178
|
-
height: view.getUint16(28, true) & 16383
|
|
5179
|
-
};
|
|
5180
|
-
if (chunk === "VP8L") {
|
|
5181
|
-
const bits = view.getUint32(21, true);
|
|
5182
|
-
return { width: (bits & 16383) + 1, height: (bits >> 14 & 16383) + 1 };
|
|
5183
|
-
}
|
|
5184
|
-
if (chunk === "VP8X") {
|
|
5185
|
-
const w = view.getUint16(24, true) | view.getUint8(26) << 16;
|
|
5186
|
-
const h = view.getUint16(27, true) | view.getUint8(29) << 16;
|
|
5187
|
-
return { width: w + 1, height: h + 1 };
|
|
5188
|
-
}
|
|
5189
|
-
return void 0;
|
|
5190
|
-
}
|
|
5191
|
-
case "image/jpeg": {
|
|
5192
|
-
let offset = 2;
|
|
5193
|
-
while (offset + 9 < data.length) {
|
|
5194
|
-
if (data[offset] !== 255) return void 0;
|
|
5195
|
-
const marker = data[offset + 1];
|
|
5196
|
-
const length = view.getUint16(offset + 2);
|
|
5197
|
-
if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
|
|
5198
|
-
return { width: view.getUint16(offset + 7), height: view.getUint16(offset + 5) };
|
|
5199
|
-
}
|
|
5200
|
-
offset += 2 + length;
|
|
5201
|
-
}
|
|
5202
|
-
return void 0;
|
|
5203
|
-
}
|
|
5204
|
-
}
|
|
5205
|
-
} catch {
|
|
5206
|
-
return void 0;
|
|
5207
|
-
}
|
|
5208
|
-
return void 0;
|
|
5209
|
-
}
|
|
5210
|
-
function storageKey(originalName, type, random2) {
|
|
5211
|
-
const name = originalName.split(/[\\/]/).pop() ?? "";
|
|
5212
|
-
const base = name.replace(/(?<=.)\.[^.]*$/, "").normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "file";
|
|
5213
|
-
return `${base}-${random2}.${EXTENSIONS[type] ?? "bin"}`;
|
|
5214
|
-
}
|
|
5215
|
-
|
|
5216
5285
|
// src/media-folders.ts
|
|
5217
5286
|
var FOLDER_LEVELS = ["view", "edit", "manage"];
|
|
5218
5287
|
var RANK = { none: 0, view: 1, edit: 2, manage: 3 };
|
|
@@ -5816,7 +5885,7 @@ var EasyCMS = class {
|
|
|
5816
5885
|
}
|
|
5817
5886
|
if (file.data.byteLength === 0)
|
|
5818
5887
|
throw new ValidationError(MEDIA, [{ field: "file", message: "is empty" }]);
|
|
5819
|
-
const mimeType = sniffMimeType(file.data);
|
|
5888
|
+
const mimeType = sniffMimeType(file.data, file.name);
|
|
5820
5889
|
if (!mimeType || !mimeAllowed(mimeType, mimeTypes)) {
|
|
5821
5890
|
throw new ValidationError(MEDIA, [
|
|
5822
5891
|
{
|
|
@@ -7718,7 +7787,7 @@ async function serializeFields(fields, checks, localized) {
|
|
|
7718
7787
|
if (field.hasMany) f.hasMany = true;
|
|
7719
7788
|
if (field.minRows !== void 0) f.minRows = field.minRows;
|
|
7720
7789
|
if (field.maxRows !== void 0) f.maxRows = field.maxRows;
|
|
7721
|
-
if (field.mimeTypes) f.mimeTypes =
|
|
7790
|
+
if (field.mimeTypes) f.mimeTypes = expandMimeTypes(field.mimeTypes);
|
|
7722
7791
|
break;
|
|
7723
7792
|
case "array":
|
|
7724
7793
|
if (field.minRows !== void 0) f.minRows = field.minRows;
|
|
@@ -7842,6 +7911,10 @@ async function adminSchema(cms, user, origin) {
|
|
|
7842
7911
|
globals: await Promise.all(cms.config.globals.map((g) => global(cms, g, user, localized))),
|
|
7843
7912
|
modules: adminModuleUrls(cms),
|
|
7844
7913
|
uploadFromURL: cms.config.upload.fromURL !== void 0,
|
|
7914
|
+
upload: {
|
|
7915
|
+
maxFileSize: cms.config.upload.maxFileSize,
|
|
7916
|
+
mimeTypes: expandMimeTypes(cms.config.upload.mimeTypes)
|
|
7917
|
+
},
|
|
7845
7918
|
...cms.folders.enabled ? { folders: { permissions: admin && cms.roles.enabled } } : {},
|
|
7846
7919
|
views,
|
|
7847
7920
|
rbac: cms.roles.enabled,
|
|
@@ -7891,7 +7964,7 @@ async function widgets(cms, user) {
|
|
|
7891
7964
|
}
|
|
7892
7965
|
|
|
7893
7966
|
// src/version.ts
|
|
7894
|
-
var VERSION = true ? "0.
|
|
7967
|
+
var VERSION = true ? "0.39.0" : "0.0.0-dev";
|
|
7895
7968
|
|
|
7896
7969
|
// src/rest/admin-status.ts
|
|
7897
7970
|
var DAY2 = 864e5;
|
|
@@ -8886,16 +8959,20 @@ async function serveFile(cms, key2, head) {
|
|
|
8886
8959
|
const file = await cms.storage.get(key2);
|
|
8887
8960
|
if (!file) throw new HttpError("Not found", 404);
|
|
8888
8961
|
const extension = key2.slice(key2.lastIndexOf(".") + 1);
|
|
8962
|
+
const type = TYPE_BY_EXTENSION[extension] ?? "application/octet-stream";
|
|
8889
8963
|
return new Response(
|
|
8890
8964
|
head ? null : file.body,
|
|
8891
8965
|
{
|
|
8892
8966
|
status: 200,
|
|
8893
8967
|
headers: {
|
|
8894
|
-
"content-type":
|
|
8968
|
+
"content-type": type,
|
|
8895
8969
|
"content-length": String(file.size),
|
|
8896
8970
|
"cache-control": "public, max-age=31536000, immutable",
|
|
8897
8971
|
"x-content-type-options": "nosniff",
|
|
8898
|
-
|
|
8972
|
+
// Browsers won't show a PDF in a sandbox; their PDF viewers run apart from the site.
|
|
8973
|
+
...type === "application/pdf" ? {} : {
|
|
8974
|
+
"content-security-policy": "default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; sandbox"
|
|
8975
|
+
},
|
|
8899
8976
|
"cross-origin-resource-policy": "cross-origin"
|
|
8900
8977
|
}
|
|
8901
8978
|
}
|
|
@@ -9245,6 +9322,7 @@ export {
|
|
|
9245
9322
|
MAX_DEPTH,
|
|
9246
9323
|
MEDIA,
|
|
9247
9324
|
MEDIA_FOLDERS,
|
|
9325
|
+
MIME_GROUPS,
|
|
9248
9326
|
MIN_PASSWORD_LENGTH,
|
|
9249
9327
|
MIN_SECRET_LENGTH,
|
|
9250
9328
|
MediaFolders,
|
|
@@ -9274,6 +9352,7 @@ export {
|
|
|
9274
9352
|
defineConfig,
|
|
9275
9353
|
defineFieldType,
|
|
9276
9354
|
definePlugin,
|
|
9355
|
+
expandMimeTypes,
|
|
9277
9356
|
fillMissing,
|
|
9278
9357
|
findConfigFile,
|
|
9279
9358
|
generateSlugs,
|