feat: Reject unacceptable content types

This commit is contained in:
Joachim Van Herwegen
2020-11-09 11:31:09 +01:00
parent c1aa25f314
commit 69ed2e069f
5 changed files with 103 additions and 32 deletions

View File

@@ -1,26 +1,51 @@
import type { RepresentationPreference } from '../../ldp/representation/RepresentationPreference';
import type { RepresentationPreferences } from '../../ldp/representation/RepresentationPreferences';
import { InternalServerError } from '../../util/errors/InternalServerError';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { matchingMediaType } from '../../util/Util';
import type { RepresentationConverterArgs } from './RepresentationConverter';
/**
* Filters out the media types from the preferred types that correspond to one of the supported types.
* Filters media types based on the given preferences.
* Based on RFC 7231 - Content negotiation.
*
* @param preferences - Preferences for output type.
* @param supported - Types supported by the parser.
* @param types - Media types to compare to the preferences.
*
* @throws UnsupportedHttpError
* If the type preferences are undefined.
* If the type preferences are undefined or if there are duplicate preferences.
*
* @returns The filtered list of preferences.
* @returns The weighted and filtered list of matching types.
*/
export const matchingTypes = (preferences: RepresentationPreferences, supported: string[]):
export const matchingTypes = (preferences: RepresentationPreferences, types: string[]):
RepresentationPreference[] => {
if (!Array.isArray(preferences.type)) {
throw new UnsupportedHttpError('Output type required for conversion.');
}
return preferences.type.filter(({ value, weight }): boolean => weight > 0 &&
supported.some((type): boolean => matchingMediaType(value, type)));
const prefMap = preferences.type.reduce((map: Record<string, number>, pref): Record<string, number> => {
if (map[pref.value]) {
throw new UnsupportedHttpError(`Duplicate type preference found: ${pref.value}`);
}
map[pref.value] = pref.weight;
return map;
}, {});
// RFC 7231
// Media ranges can be overridden by more specific media ranges or
// specific media types. If more than one media range applies to a
// given type, the most specific reference has precedence.
const weightedSupported = types.map((type): RepresentationPreference => {
const match = /^([^/]+)\/([^\s;]+)/u.exec(type);
if (!match) {
throw new InternalServerError(`Unexpected type preference: ${type}`);
}
const [ , main, sub ] = match;
const weight = prefMap[type] ?? prefMap[`${main}/${sub}`] ?? prefMap[`${main}/*`] ?? prefMap['*/*'] ?? 0;
return { value: type, weight };
});
return weightedSupported.filter((preference): boolean => preference.weight !== 0);
};
/**