refactor: abstract parts of ExtensionBasedMapper into MapperUtil

This commit is contained in:
Ruben Taelman 2020-11-18 16:21:35 +01:00 committed by Joachim Van Herwegen
parent 2c46d70780
commit 971e4178d1
2 changed files with 64 additions and 41 deletions

View File

@ -7,13 +7,13 @@ import { APPLICATION_OCTET_STREAM, TEXT_TURTLE } from '../../util/ContentTypes';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError'; import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { import {
decodeUriPathComponents,
encodeUriPathComponents, encodeUriPathComponents,
ensureTrailingSlash, ensureTrailingSlash,
isContainerIdentifier, isContainerIdentifier,
trimTrailingSlashes, trimTrailingSlashes,
} from '../../util/PathUtil'; } from '../../util/PathUtil';
import type { FileIdentifierMapper, ResourceLink } from '../FileIdentifierMapper'; import type { FileIdentifierMapper, ResourceLink } from '../FileIdentifierMapper';
import { getAbsolutePath, getRelativePath, validateRelativePath } from './MapperUtil';
const { join: joinPath, normalize: normalizePath } = posix; const { join: joinPath, normalize: normalizePath } = posix;
@ -60,19 +60,10 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {
* @returns A ResourceLink with all the necessary metadata. * @returns A ResourceLink with all the necessary metadata.
*/ */
public async mapUrlToFilePath(identifier: ResourceIdentifier, contentType?: string): Promise<ResourceLink> { public async mapUrlToFilePath(identifier: ResourceIdentifier, contentType?: string): Promise<ResourceLink> {
const path = this.getRelativePath(identifier); const path = getRelativePath(this.baseRequestURI, identifier, this.logger);
validateRelativePath(path, identifier, this.logger);
if (!path.startsWith('/')) { let filePath = getAbsolutePath(this.rootFilepath, path);
this.logger.warn(`URL ${identifier.path} needs a / after the base`);
throw new UnsupportedHttpError('URL needs a / after the base');
}
if (path.includes('/..')) {
this.logger.warn(`Disallowed /.. segment in URL ${identifier.path}.`);
throw new UnsupportedHttpError('Disallowed /.. segment in URL');
}
let filePath = this.getAbsolutePath(path);
// Container // Container
if (isContainerIdentifier(identifier)) { if (isContainerIdentifier(identifier)) {
@ -200,32 +191,4 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {
const extension = /\.([^./]+)$/u.exec(path); const extension = /\.([^./]+)$/u.exec(path);
return extension && extension[1]; return extension && extension[1];
} }
/**
* Get the absolute file path based on the rootFilepath of the store.
* @param path - The relative file path.
* @param identifier - Optional identifier to add to the path.
*
* @returns Absolute path of the file.
*/
private getAbsolutePath(path: string, identifier = ''): string {
return joinPath(this.rootFilepath, path, identifier);
}
/**
* Strips the baseRequestURI from the identifier and checks if the stripped base URI matches the store's one.
* @param identifier - Incoming identifier.
*
* @throws {@link NotFoundHttpError}
* If the identifier does not match the baseRequestURI path of the store.
*
* @returns A string representing the relative path.
*/
private getRelativePath(identifier: ResourceIdentifier): string {
if (!identifier.path.startsWith(this.baseRequestURI)) {
this.logger.warn(`The URL ${identifier.path} is outside of the scope ${this.baseRequestURI}`);
throw new NotFoundHttpError();
}
return decodeUriPathComponents(identifier.path.slice(this.baseRequestURI.length));
}
} }

View File

@ -0,0 +1,60 @@
import { posix } from 'path';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import type { Logger } from '../../logging/Logger';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { decodeUriPathComponents } from '../../util/Util';
const { join: joinPath } = posix;
/**
* Get the absolute file path based on the rootFilepath of the store.
* @param rootFilepath - The root file path.
* @param path - The relative file path.
* @param identifier - Optional identifier to add to the path.
*
* @returns Absolute path of the file.
*/
export const getAbsolutePath = (rootFilepath: string, path: string, identifier = ''): string =>
joinPath(rootFilepath, path, identifier);
/**
* Strips the baseRequestURI from the identifier and checks if the stripped base URI matches the store's one.
* @param baseRequestURI - Base URL for requests.
* @param identifier - Incoming identifier.
* @param logger - A logger instance.
*
* @throws {@link NotFoundHttpError}
* If the identifier does not match the baseRequestURI path of the store.
*
* @returns A string representing the relative path.
*/
export const getRelativePath = (baseRequestURI: string, identifier: ResourceIdentifier, logger: Logger): string => {
if (!identifier.path.startsWith(baseRequestURI)) {
logger.warn(`The URL ${identifier.path} is outside of the scope ${baseRequestURI}`);
throw new NotFoundHttpError();
}
return decodeUriPathComponents(identifier.path.slice(baseRequestURI.length));
};
/**
* Check if the given relative path is valid.
*
* @throws {@link UnsupportedHttpError}
* If the relative path is invalid.
*
* @param path - A relative path, as generated by {@link getRelativePath}.
* @param identifier - A resource identifier.
* @param logger - A logger instance.
*/
export const validateRelativePath = (path: string, identifier: ResourceIdentifier, logger: Logger): void => {
if (!path.startsWith('/')) {
logger.warn(`URL ${identifier.path} needs a / after the base`);
throw new UnsupportedHttpError('URL needs a / after the base');
}
if (path.includes('/..')) {
logger.warn(`Disallowed /.. segment in URL ${identifier.path}.`);
throw new UnsupportedHttpError('Disallowed /.. segment in URL');
}
};