diff --git a/src/storage/conversion/ConversionUtil.ts b/src/storage/conversion/ConversionUtil.ts index 188fbbc38..b13d2fc0d 100644 --- a/src/storage/conversion/ConversionUtil.ts +++ b/src/storage/conversion/ConversionUtil.ts @@ -1,5 +1,6 @@ import type { RepresentationPreference } from '../../ldp/representation/RepresentationPreference'; import type { RepresentationPreferences } from '../../ldp/representation/RepresentationPreferences'; +import { INTERNAL_ALL } from '../../util/ContentTypes'; import { InternalServerError } from '../../util/errors/InternalServerError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { matchingMediaType } from '../../util/Util'; @@ -8,6 +9,9 @@ import type { RepresentationConverterArgs } from './RepresentationConverter'; /** * Filters media types based on the given preferences. * Based on RFC 7231 - Content negotiation. + * Will add a default `internal/*;q=0` to the preferences to prevent accidental use of internal types. + * Since more specific media ranges override less specific ones, + * this will be ignored if there is a specific internal type preference. * * @param preferences - Preferences for output type. * @param types - Media types to compare to the preferences. @@ -31,6 +35,11 @@ RepresentationPreference[] => { return map; }, {}); + // Prevent accidental use of internal types + if (!prefMap[INTERNAL_ALL]) { + prefMap[INTERNAL_ALL] = 0; + } + // 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 diff --git a/src/util/ContentTypes.ts b/src/util/ContentTypes.ts index 3c22a6e75..f4fdf0f48 100644 --- a/src/util/ContentTypes.ts +++ b/src/util/ContentTypes.ts @@ -4,4 +4,5 @@ export const APPLICATION_OCTET_STREAM = 'application/octet-stream'; export const APPLICATION_SPARQL_UPDATE = 'application/sparql-update'; // Internal (non-exposed) content types +export const INTERNAL_ALL = 'internal/*'; export const INTERNAL_QUADS = 'internal/quads'; diff --git a/test/unit/storage/conversion/ConversionUtil.test.ts b/test/unit/storage/conversion/ConversionUtil.test.ts index b4f39346e..d2007f033 100644 --- a/test/unit/storage/conversion/ConversionUtil.test.ts +++ b/test/unit/storage/conversion/ConversionUtil.test.ts @@ -19,22 +19,22 @@ describe('A ConversionUtil', (): void => { describe('#checkRequest', (): void => { it('requires an input type.', async(): Promise => { const preferences: RepresentationPreferences = {}; - expect((): any => checkRequest({ identifier, representation, preferences }, [ '*/*' ], [ '*/*' ])) + expect((): any => checkRequest({ identifier, representation, preferences }, [ 'a/x' ], [ 'a/x' ])) .toThrow('Input type required for conversion.'); }); it('requires a matching input type.', async(): Promise => { metadata.contentType = 'a/x'; const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]}; - expect((): any => checkRequest({ identifier, representation, preferences }, [ 'c/x' ], [ '*/*' ])) - .toThrow('Can only convert from c/x to */*.'); + expect((): any => checkRequest({ identifier, representation, preferences }, [ 'c/x' ], [ 'a/x' ])) + .toThrow('Can only convert from c/x to a/x.'); }); it('requires a matching output type.', async(): Promise => { metadata.contentType = 'a/x'; const preferences: RepresentationPreferences = { type: [{ value: 'b/x', weight: 1 }]}; - expect((): any => checkRequest({ identifier, representation, preferences }, [ '*/*' ], [ 'c/x' ])) - .toThrow('Can only convert from */* to c/x.'); + expect((): any => checkRequest({ identifier, representation, preferences }, [ 'a/x' ], [ 'c/x' ])) + .toThrow('Can only convert from a/x to c/x.'); }); it('succeeds with a valid input and output type.', async(): Promise => { @@ -48,7 +48,7 @@ describe('A ConversionUtil', (): void => { describe('#matchingTypes', (): void => { it('requires type preferences.', async(): Promise => { const preferences: RepresentationPreferences = {}; - expect((): any => matchingTypes(preferences, [ '*/*' ])) + expect((): any => matchingTypes(preferences, [ 'a/b' ])) .toThrow('Output type required for conversion.'); }); @@ -71,5 +71,24 @@ describe('A ConversionUtil', (): void => { expect((): any => matchingTypes(preferences, [ 'noType' ])) .toThrow(new InternalServerError(`Unexpected type preference: noType`)); }); + + it('filters out internal types.', async(): Promise => { + const preferences: RepresentationPreferences = { type: [{ value: '*/*', weight: 1 }]}; + expect(matchingTypes(preferences, [ 'a/x', 'internal/quads' ])).toEqual([{ value: 'a/x', weight: 1 }]); + }); + + it('keeps internal types that are specifically requested.', async(): Promise => { + const preferences: RepresentationPreferences = + { type: [{ value: '*/*', weight: 1 }, { value: 'internal/*', weight: 0.5 }]}; + expect(matchingTypes(preferences, [ 'a/x', 'internal/quads' ])) + .toEqual([{ value: 'a/x', weight: 1 }, { value: 'internal/quads', weight: 0.5 }]); + }); + + it('takes the most relevant weight for a type.', async(): Promise => { + const preferences: RepresentationPreferences = + { type: [{ value: '*/*', weight: 1 }, { value: 'internal/quads', weight: 0.5 }]}; + expect(matchingTypes(preferences, [ 'a/x', 'internal/quads' ])) + .toEqual([{ value: 'a/x', weight: 1 }, { value: 'internal/quads', weight: 0.5 }]); + }); }); });