mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Update ExtensionBasedMapper custom types
This commit is contained in:
committed by
Joachim Van Herwegen
parent
c01e33ecd9
commit
3f8f822d81
@@ -19,9 +19,7 @@
|
||||
"@type": "SubdomainExtensionBasedMapper",
|
||||
"base": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"rootFilepath": { "@id": "urn:solid-server:default:variable:rootFilePath" },
|
||||
"baseSubdomain": "www",
|
||||
"overrideTypes_acl": "text/turtle",
|
||||
"overrideTypes_meta": "text/turtle"
|
||||
"baseSubdomain": "www"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
"@id": "urn:solid-server:default:FileIdentifierMapper",
|
||||
"@type": "ExtensionBasedMapper",
|
||||
"base": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"rootFilepath": { "@id": "urn:solid-server:default:variable:rootFilePath" },
|
||||
"overrideTypes_acl": "text/turtle",
|
||||
"overrideTypes_meta": "text/turtle"
|
||||
"rootFilepath": { "@id": "urn:solid-server:default:variable:rootFilePath" }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,18 +1,40 @@
|
||||
import { promises as fsPromises } from 'fs';
|
||||
import * as mime from 'mime-types';
|
||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||
import { TEXT_TURTLE } from '../../util/ContentTypes';
|
||||
import { DEFAULT_CUSTOM_TYPES } from '../../util/ContentTypes';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { joinFilePath, getExtension } from '../../util/PathUtil';
|
||||
import { BaseFileIdentifierMapper } from './BaseFileIdentifierMapper';
|
||||
import type { FileIdentifierMapperFactory, ResourceLink } from './FileIdentifierMapper';
|
||||
|
||||
/**
|
||||
* Supports the behaviour described in https://www.w3.org/DesignIssues/HTTPFilenameMapping.html
|
||||
* Determines content-type based on the file extension.
|
||||
* In case an identifier does not end on an extension matching its content-type,
|
||||
* the corresponding file will be appended with the correct extension, preceded by $.
|
||||
*/
|
||||
export class ExtensionBasedMapper extends BaseFileIdentifierMapper {
|
||||
private readonly types: Record<string, any>;
|
||||
private readonly customTypes: Record<string, string>;
|
||||
private readonly customExtensions: Record<string, string>;
|
||||
|
||||
public constructor(base: string, rootFilepath: string, overrideTypes = { acl: TEXT_TURTLE, meta: TEXT_TURTLE }) {
|
||||
public constructor(
|
||||
base: string,
|
||||
rootFilepath: string,
|
||||
customTypes?: Record<string, string>,
|
||||
) {
|
||||
super(base, rootFilepath);
|
||||
this.types = { ...mime.types, ...overrideTypes };
|
||||
|
||||
// Workaround for https://github.com/LinkedSoftwareDependencies/Components.js/issues/20
|
||||
if (!customTypes || Object.keys(customTypes).length === 0) {
|
||||
this.customTypes = DEFAULT_CUSTOM_TYPES;
|
||||
} else {
|
||||
this.customTypes = customTypes;
|
||||
}
|
||||
|
||||
this.customExtensions = {};
|
||||
for (const [ extension, contentType ] of Object.entries(this.customTypes)) {
|
||||
this.customExtensions[contentType] = extension;
|
||||
}
|
||||
}
|
||||
|
||||
protected async mapUrlToDocumentPath(identifier: ResourceIdentifier, filePath: string, contentType?: string):
|
||||
@@ -42,7 +64,7 @@ export class ExtensionBasedMapper extends BaseFileIdentifierMapper {
|
||||
// If the extension of the identifier matches a different content-type than the one that is given,
|
||||
// we need to add a new extension to match the correct type.
|
||||
} else if (contentType !== await this.getContentTypeFromPath(filePath)) {
|
||||
const extension = mime.extension(contentType);
|
||||
const extension: string = mime.extension(contentType) || this.customExtensions[contentType];
|
||||
if (!extension) {
|
||||
this.logger.warn(`No extension found for ${contentType}`);
|
||||
throw new NotImplementedHttpError(`Unsupported content type ${contentType}`);
|
||||
@@ -57,8 +79,10 @@ export class ExtensionBasedMapper extends BaseFileIdentifierMapper {
|
||||
}
|
||||
|
||||
protected async getContentTypeFromPath(filePath: string): Promise<string> {
|
||||
return this.types[getExtension(filePath).toLowerCase()] ||
|
||||
super.getContentTypeFromPath(filePath);
|
||||
const extension = getExtension(filePath).toLowerCase();
|
||||
return mime.lookup(extension) ||
|
||||
this.customTypes[extension] ||
|
||||
await super.getContentTypeFromPath(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { toASCII, toUnicode } from 'punycode/';
|
||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||
import { TEXT_TURTLE } from '../../util/ContentTypes';
|
||||
import { ForbiddenHttpError } from '../../util/errors/ForbiddenHttpError';
|
||||
import { InternalServerError } from '../../util/errors/InternalServerError';
|
||||
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
|
||||
@@ -36,8 +35,8 @@ export class SubdomainExtensionBasedMapper extends ExtensionBasedMapper {
|
||||
private readonly baseParts: { scheme: string; rest: string };
|
||||
|
||||
public constructor(base: string, rootFilepath: string, baseSubdomain = 'www',
|
||||
overrideTypes = { acl: TEXT_TURTLE, meta: TEXT_TURTLE }) {
|
||||
super(base, rootFilepath, overrideTypes);
|
||||
customTypes?: Record<string, string>) {
|
||||
super(base, rootFilepath, customTypes);
|
||||
this.baseSubdomain = baseSubdomain;
|
||||
this.regex = createSubdomainRegexp(ensureTrailingSlash(base));
|
||||
this.baseParts = extractScheme(ensureTrailingSlash(base));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
export const APPLICATION_JSON = 'application/json';
|
||||
export const APPLICATION_OCTET_STREAM = 'application/octet-stream';
|
||||
export const APPLICATION_SPARQL_UPDATE = 'application/sparql-update';
|
||||
export const APPLICATION_TRIG = 'application/trig';
|
||||
export const APPLICATION_X_WWW_FORM_URLENCODED = 'application/x-www-form-urlencoded';
|
||||
export const TEXT_HTML = 'text/html';
|
||||
export const TEXT_MARKDOWN = 'text/markdown';
|
||||
@@ -11,3 +12,10 @@ export const TEXT_TURTLE = 'text/turtle';
|
||||
export const INTERNAL_ALL = 'internal/*';
|
||||
export const INTERNAL_QUADS = 'internal/quads';
|
||||
export const INTERNAL_ERROR = 'internal/error';
|
||||
|
||||
// Trig can be removed once the mime-types library is updated with the latest mime-db version
|
||||
export const DEFAULT_CUSTOM_TYPES = {
|
||||
acl: TEXT_TURTLE,
|
||||
meta: TEXT_TURTLE,
|
||||
trig: APPLICATION_TRIG,
|
||||
};
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
},
|
||||
"rootFilepath": {
|
||||
"@id": "urn:solid-server:template:variable:rootFilePath"
|
||||
},
|
||||
"overrideTypes_acl": "text/turtle",
|
||||
"overrideTypes_meta": "text/turtle"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -130,6 +130,28 @@ describe('An ExtensionBasedMapper', (): void => {
|
||||
await expect(result).rejects.toThrow(NotImplementedHttpError);
|
||||
await expect(result).rejects.toThrow('Unsupported content type fake/data');
|
||||
});
|
||||
|
||||
it('supports custom types.', async(): Promise<void> => {
|
||||
const customMapper = new ExtensionBasedMapper(base, rootFilepath, { cstm: 'text/custom' });
|
||||
await expect(customMapper.mapUrlToFilePath({ path: `${base}test.cstm` }, false))
|
||||
.resolves.toEqual({
|
||||
identifier: { path: `${base}test.cstm` },
|
||||
filePath: `${rootFilepath}test.cstm`,
|
||||
contentType: 'text/custom',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('supports custom extensions.', async(): Promise<void> => {
|
||||
const customMapper = new ExtensionBasedMapper(base, rootFilepath, { cstm: 'text/custom' });
|
||||
await expect(customMapper.mapUrlToFilePath({ path: `${base}test` }, false, 'text/custom'))
|
||||
.resolves.toEqual({
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test$.cstm`,
|
||||
contentType: 'text/custom',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapFilePathToUrl', (): void => {
|
||||
@@ -180,6 +202,17 @@ describe('An ExtensionBasedMapper', (): void => {
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('supports custom extensions.', async(): Promise<void> => {
|
||||
const customMapper = new ExtensionBasedMapper(base, rootFilepath, { cstm: 'text/custom' });
|
||||
await expect(customMapper.mapFilePathToUrl(`${rootFilepath}test$.cstm`, false))
|
||||
.resolves.toEqual({
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test$.cstm`,
|
||||
contentType: 'text/custom',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('An ExtensionBasedMapperFactory', (): void => {
|
||||
|
||||
Reference in New Issue
Block a user