UNPKG

@solid/community-server

Version:

Community Solid Server: an open and modular implementation of the Solid specifications

207 lines 8.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseFileIdentifierMapper = void 0; const global_logger_factory_1 = require("global-logger-factory"); const ContentTypes_1 = require("../../util/ContentTypes"); const BadRequestHttpError_1 = require("../../util/errors/BadRequestHttpError"); const InternalServerError_1 = require("../../util/errors/InternalServerError"); const NotFoundHttpError_1 = require("../../util/errors/NotFoundHttpError"); const PathUtil_1 = require("../../util/PathUtil"); /** * Base class for {@link FileIdentifierMapper} implementations. */ class BaseFileIdentifierMapper { logger = (0, global_logger_factory_1.getLoggerFor)(this); baseRequestURI; rootFilepath; // Extension to use as a fallback when the media type is not supported (could be made configurable). unknownMediaTypeExtension = 'unknown'; // Path suffix for metadata metadataSuffix = '.meta'; constructor(base, rootFilepath) { this.baseRequestURI = (0, PathUtil_1.trimTrailingSlashes)(base); this.rootFilepath = (0, PathUtil_1.trimTrailingSlashes)((0, PathUtil_1.normalizeFilePath)(rootFilepath)); } /** * Maps the given resource identifier / URL to a file path. * Determines the content type if none was provided. * For containers the content-type input is ignored. * * @param identifier - The input identifier. * @param isMetadata - If we need the data or metadata file path. * @param contentType - The content-type provided with the request. * * @returns A ResourceLink with all the necessary metadata. */ async mapUrlToFilePath(identifier, isMetadata, contentType) { let path = this.getRelativePath(identifier); if (isMetadata) { path += this.metadataSuffix; } this.validateRelativePath(path, identifier); const filePath = this.getAbsolutePath(path); return (0, PathUtil_1.isContainerIdentifier)(identifier) ? this.mapUrlToContainerPath(identifier, filePath) : this.mapUrlToDocumentPath(identifier, filePath, contentType); } /** * Maps the given container identifier to a file path, * possibly making alterations to the direct translation. * * @param identifier - The input identifier. * @param filePath - The direct translation of the identifier onto the file path. * * @returns A ResourceLink with all the necessary metadata. */ async mapUrlToContainerPath(identifier, filePath) { this.logger.debug(`URL ${identifier.path} points to the container ${filePath}`); return { identifier, filePath, isMetadata: this.isMetadataPath(filePath) }; } /** * Maps the given document identifier to a file path, * possibly making alterations to the direct translation * (for instance, based on its content type)). * Determines the content type if none was provided. * * @param identifier - The input identifier. * @param filePath - The direct translation of the identifier onto the file path. * @param contentType - The content-type provided with the request. * * @returns A ResourceLink with all the necessary metadata. */ async mapUrlToDocumentPath(identifier, filePath, contentType) { // Don't try to get content-type from URL when the file path refers to a document with unknown media type. if (!filePath.endsWith(`.${this.unknownMediaTypeExtension}`)) { contentType = await this.getContentTypeFromUrl(identifier, contentType); } this.logger.debug(`The path for ${identifier.path} is ${filePath}`); return { identifier, filePath, contentType, isMetadata: this.isMetadataPath(filePath) }; } /** * Determines the content type from the document identifier. * * @param identifier - The input identifier. * @param contentType - The content-type provided with the request. * * @returns The content type of the document. */ async getContentTypeFromUrl(identifier, contentType) { return contentType ?? ContentTypes_1.APPLICATION_OCTET_STREAM; } /** * Maps the given file path to a URL and determines its content type. * * @param filePath - The input file path. * @param isContainer - If the path corresponds to a file. * * @returns A ResourceLink with all the necessary metadata. */ async mapFilePathToUrl(filePath, isContainer) { if (!filePath.startsWith(this.rootFilepath)) { this.logger.error(`Trying to access file ${filePath} outside of ${this.rootFilepath}`); throw new InternalServerError_1.InternalServerError(`File ${filePath} is not part of the file storage at ${this.rootFilepath}`); } const relative = filePath.slice(this.rootFilepath.length); let url; let contentType; if (isContainer) { url = await this.getContainerUrl(relative); this.logger.debug(`Container filepath ${filePath} maps to URL ${url}`); } else { url = await this.getDocumentUrl(relative); this.logger.debug(`Document ${filePath} maps to URL ${url}`); contentType = await this.getContentTypeFromPath(filePath); } const isMetadata = this.isMetadataPath(filePath); if (isMetadata) { url = url.slice(0, -this.metadataSuffix.length); } return { identifier: { path: url }, filePath, contentType, isMetadata }; } /** * Maps the given container path to a URL and determines its content type. * * @param relative - The relative container path. * * @returns A ResourceLink with all the necessary metadata. */ async getContainerUrl(relative) { return (0, PathUtil_1.ensureTrailingSlash)(this.baseRequestURI + (0, PathUtil_1.encodeUriPathComponents)(relative)); } /** * Maps the given document path to a URL and determines its content type. * * @param relative - The relative document path. * * @returns A ResourceLink with all the necessary metadata. */ async getDocumentUrl(relative) { return (0, PathUtil_1.trimTrailingSlashes)(this.baseRequestURI + (0, PathUtil_1.encodeUriPathComponents)(relative)); } /** * Determines the content type from the relative path. * * @param filePath - The file path of the document. * * @returns The content type of the document. */ // eslint-disable-next-line unused-imports/no-unused-vars async getContentTypeFromPath(filePath) { return ContentTypes_1.APPLICATION_OCTET_STREAM; } /** * Get the absolute file path based on the rootFilepath. * * @param path - The relative file path. * * @returns Absolute path of the file. */ getAbsolutePath(path) { return (0, PathUtil_1.joinFilePath)(this.rootFilepath, path); } /** * Strips the baseRequestURI from the identifier. * * @param identifier - Incoming identifier. * * @returns A string representing the relative path. * * @throws NotFoundHttpError * If the identifier does not match the baseRequestURI. */ getRelativePath(identifier) { if (!identifier.path.startsWith(this.baseRequestURI)) { this.logger.warn(`The URL ${identifier.path} is outside of the scope ${this.baseRequestURI}`); throw new NotFoundHttpError_1.NotFoundHttpError(); } return (0, PathUtil_1.decodeUriPathComponents)(identifier.path.slice(this.baseRequestURI.length)); } /** * Check whether the given relative path is valid. * * @param path - A relative path, as generated by {@link getRelativePath}. * @param identifier - A resource identifier. * * @throws BadRequestHttpError * If the relative path is invalid. */ validateRelativePath(path, identifier) { if (!path.startsWith('/')) { this.logger.warn(`URL ${identifier.path} needs a / after the base`); throw new BadRequestHttpError_1.BadRequestHttpError('URL needs a / after the base'); } if (path.includes('/../') || path.endsWith('/..')) { this.logger.warn(`Disallowed /../ segment in URL ${identifier.path}.`); throw new BadRequestHttpError_1.BadRequestHttpError('Disallowed /../ segment in URL'); } } /** * Checks if the given path is a metadata path. */ isMetadataPath(path) { return path.endsWith(this.metadataSuffix); } } exports.BaseFileIdentifierMapper = BaseFileIdentifierMapper; //# sourceMappingURL=BaseFileIdentifierMapper.js.map