allowedMimeTypes : jokers de préfixe
isAllowedMedia gère désormais "video/*", "*/*" et "*" en plus des types
exacts (comparaison insensible à la casse). parseConfig valide la forme des
entrées et lève ConfigError sur un joker malformé ("video/**", "vi*deo/mp4",
"video/") au lieu de laisser passer une liste qui ne matche rien.
Schéma + doc mis à jour. 62 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
5bff28fefe
commit
0f5bdb7080
6 changed files with 66 additions and 20 deletions
|
|
@ -166,7 +166,7 @@ Un seul fichier JSON (voir [`config.example.json`](config.example.json), schéma
|
||||||
[`config.schema.json`](config.schema.json)). Chemin résolu depuis le dossier courant.
|
[`config.schema.json`](config.schema.json)). Chemin résolu depuis le dossier courant.
|
||||||
|
|
||||||
| Clé JSON | Défaut | Rôle |
|
| Clé JSON | Défaut | Rôle |
|
||||||
| ---------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
| ---------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------- |
|
||||||
| `port` | `3000` | Port d'écoute |
|
| `port` | `3000` | Port d'écoute |
|
||||||
| `host` | `0.0.0.0` | Interface d'écoute |
|
| `host` | `0.0.0.0` | Interface d'écoute |
|
||||||
| `publicUrl` | — | **Obligatoire.** URL externe de base (redirect_uri, post-logout) |
|
| `publicUrl` | — | **Obligatoire.** URL externe de base (redirect_uri, post-logout) |
|
||||||
|
|
@ -174,7 +174,7 @@ Un seul fichier JSON (voir [`config.example.json`](config.example.json), schéma
|
||||||
| `targets[].allowDirectories` | `false` | Autoriser le dépôt de dossiers (arborescence recréée sous `dir`) |
|
| `targets[].allowDirectories` | `false` | Autoriser le dépôt de dossiers (arborescence recréée sous `dir`) |
|
||||||
| `maxFileSizeMb` | `512` | Taille maximale par fichier, en Mo (`10240` = 10 Go) |
|
| `maxFileSizeMb` | `512` | Taille maximale par fichier, en Mo (`10240` = 10 Go) |
|
||||||
| `logLevel` | `info` | `fatal` \| `error` \| `warn` \| `info` \| `debug` \| `trace` \| `silent` |
|
| `logLevel` | `info` | `fatal` \| `error` \| `warn` \| `info` \| `debug` \| `trace` \| `silent` |
|
||||||
| `allowedMimeTypes` | _(liste média)_ | Tableau de types MIME, ou `"all"` ; absent => liste par défaut |
|
| `allowedMimeTypes` | _(liste média)_ | `"all"`, ou tableau de types exacts et/ou jokers de préfixe (`"video/*"`, `"*"`) ; absent => liste par défaut |
|
||||||
| `session.key` | — | **Obligatoire.** 32 octets base64 (`openssl rand -base64 32`) |
|
| `session.key` | — | **Obligatoire.** 32 octets base64 (`openssl rand -base64 32`) |
|
||||||
| `session.maxAgeHours` | `12` | Durée de vie absolue de la session |
|
| `session.maxAgeHours` | `12` | Durée de vie absolue de la session |
|
||||||
| `oidc.issuer` | — | **Obligatoire.** Issuer OIDC |
|
| `oidc.issuer` | — | **Obligatoire.** Issuer OIDC |
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,14 @@
|
||||||
"default": "info"
|
"default": "info"
|
||||||
},
|
},
|
||||||
"allowedMimeTypes": {
|
"allowedMimeTypes": {
|
||||||
"description": "Liste blanche de types MIME, ou \"all\". Absent => liste média par défaut.",
|
"description": "\"all\", ou liste de types exacts et/ou jokers de préfixe (\"video/*\", \"*/*\", \"*\"). Absent => liste média par défaut.",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{ "const": "all" },
|
{ "const": "all" },
|
||||||
{ "type": "array", "items": { "type": "string" }, "minItems": 1 }
|
{
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": { "type": "string", "pattern": "^(\\*|\\*/\\*|[^/*\\s]+/(\\*|[^/*\\s]+))$" }
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"session": {
|
"session": {
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,15 @@ function parseMimePolicy(value: unknown): MimePolicy {
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
throw new ConfigError('allowedMimeTypes ne contient aucun type MIME valide');
|
throw new ConfigError('allowedMimeTypes ne contient aucun type MIME valide');
|
||||||
}
|
}
|
||||||
|
for (const entry of list) {
|
||||||
|
const valid = entry === '*' || entry === '*/*' || /^[^/*\s]+\/(\*|[^/*\s]+)$/.test(entry);
|
||||||
|
if (!valid) {
|
||||||
|
throw new ConfigError(
|
||||||
|
`allowedMimeTypes : entrée invalide « ${entry} » ` +
|
||||||
|
'(formes acceptées : "type/sous-type", "type/*", "*/*", "*")',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,30 @@ const MIME_TO_EXTENSION: Readonly<Record<string, string>> = {
|
||||||
'audio/mp4': '.m4a',
|
'audio/mp4': '.m4a',
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Politique de filtrage : une liste blanche explicite, ou `'all'` pour tout accepter. */
|
/**
|
||||||
|
* Politique de filtrage : `'all'`, ou une liste d'entrées exactes (ex. `video/mp4`)
|
||||||
|
* et/ou de jokers de préfixe (ex. `video/` suivi d'une étoile), l'étoile seule
|
||||||
|
* autorisant tout.
|
||||||
|
*/
|
||||||
export type MimePolicy = readonly string[] | 'all';
|
export type MimePolicy = readonly string[] | 'all';
|
||||||
|
|
||||||
|
function matchesEntry(mimeType: string, entry: string): boolean {
|
||||||
|
if (entry === '*' || entry === '*/*') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (entry.endsWith('/*')) {
|
||||||
|
return mimeType.startsWith(entry.slice(0, -1));
|
||||||
|
}
|
||||||
|
return entry === mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
/** Indique si un type MIME est accepté selon la politique fournie. */
|
/** Indique si un type MIME est accepté selon la politique fournie. */
|
||||||
export function isAllowedMedia(mimeType: string, policy: MimePolicy): boolean {
|
export function isAllowedMedia(mimeType: string, policy: MimePolicy): boolean {
|
||||||
if (policy === 'all') {
|
if (policy === 'all') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return policy.includes(mimeType);
|
const normalized = mimeType.toLowerCase();
|
||||||
|
return policy.some((entry) => matchesEntry(normalized, entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,15 @@ describe('parseConfig', () => {
|
||||||
expect(parseConfig({ ...BASE, allowedMimeTypes: 'all' }).allowedMimeTypes).toBe('all');
|
expect(parseConfig({ ...BASE, allowedMimeTypes: 'all' }).allowedMimeTypes).toBe('all');
|
||||||
expect(() => parseConfig({ ...BASE, allowedMimeTypes: 'image/png' })).toThrow(ConfigError);
|
expect(() => parseConfig({ ...BASE, allowedMimeTypes: 'image/png' })).toThrow(ConfigError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('accepte les jokers valides, rejette les jokers malformés', () => {
|
||||||
|
expect(
|
||||||
|
parseConfig({ ...BASE, allowedMimeTypes: ['video/*', 'image/png', '*/*'] }).allowedMimeTypes,
|
||||||
|
).toEqual(['video/*', 'image/png', '*/*']);
|
||||||
|
expect(() => parseConfig({ ...BASE, allowedMimeTypes: ['video/**'] })).toThrow(ConfigError);
|
||||||
|
expect(() => parseConfig({ ...BASE, allowedMimeTypes: ['vi*deo/mp4'] })).toThrow(ConfigError);
|
||||||
|
expect(() => parseConfig({ ...BASE, allowedMimeTypes: ['video/'] })).toThrow(ConfigError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadConfig', () => {
|
describe('loadConfig', () => {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,15 @@ describe('isAllowedMedia', () => {
|
||||||
it('accepte tout avec la politique "all"', () => {
|
it('accepte tout avec la politique "all"', () => {
|
||||||
expect(isAllowedMedia('application/x-msdownload', 'all')).toBe(true);
|
expect(isAllowedMedia('application/x-msdownload', 'all')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('gère les jokers de préfixe', () => {
|
||||||
|
expect(isAllowedMedia('video/x-matroska', ['video/*'])).toBe(true);
|
||||||
|
expect(isAllowedMedia('VIDEO/QUICKTIME', ['video/*'])).toBe(true);
|
||||||
|
expect(isAllowedMedia('image/png', ['video/*'])).toBe(false);
|
||||||
|
expect(isAllowedMedia('image/png', ['video/*', 'image/png'])).toBe(true);
|
||||||
|
expect(isAllowedMedia('application/octet-stream', ['*/*'])).toBe(true);
|
||||||
|
expect(isAllowedMedia('n-importe/quoi', ['*'])).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('extensionForMime', () => {
|
describe('extensionForMime', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue