import { slash } from '@poppinss/utils'; import { relative, normalize } from 'path'; import { PathTraversalDetectedException } from '../exceptions/index.js'; /** * Path prefixer for resolving and prefixing paths for disk drivers */ export default class PathPrefixer { /** * Separator used for dividing path segments is always unix-style forward slash */ separator: '/'; /** * Prefix used for path prefixing. Can be empty string for cloud drivers. */ prefix: string; // constructor(prefix?: string); constructor(prefix: string = '') { /** * Separator used for dividing path segments is always unix-style forward slash */ this.separator = '/'; // strip slashes from the end of the prefix this.prefix = prefix.replace(/\/+$/g, ''); // always end prefix with separator if it is not empty if (this.prefix !== '' || prefix === this.separator) { this.prefix += this.separator; } } /** * Normalize given path to always use `/` as separator and resolve relative paths using `.` and `..`. * It also guards against path traversal beyond the root. */ normalizePath(path: string): string { // const converted = (0, utils_1.slash)(path); const converted = slash(path); const parts = []; for (const part of converted.split(this.separator)) { if (['', '.'].includes(part)) { continue; } if (part === '..') { // if we are traversing beyond the root if (parts.length === 0) { throw PathTraversalDetectedException.invoke(converted); } parts.pop(); } else { parts.push(part); } } return parts.join(this.separator); } /** * Ruturns normalized and prefixed location path. */ prefixPath(location: string): string { return this.prefix + this.normalizePath(location); } /** * Ruturns normalized and prefixed location path for directory so always ending with slash. * Useful for cloud drivers prefix when listitng files. */ prefixDirectoryPath(location: string): string { return this.prefixPath(location) + this.separator; } /** * Returns normalized path after stripping the current prefix from it. * It is a reverse operation of `prefixPath`. */ stripPrefix(location: string): string { // const path = (0, path_1.relative)(this.prefix, (0, utils_1.slash)(location)); const path = relative(this.prefix, slash(location)); return this.normalizePath(path); } /** * Returns a new instance of `PathPrefixer` which is using as prefix stripped prefix from path of current `PathPrefixer`. */ withStrippedPrefix(path: string): PathPrefixer { return new PathPrefixer(this.stripPrefix(path)); } /** * Returns a new instance of `PathPrefixer` which is using as prefix current prefix merged with provided prefix. */ withPrefix(prefix: string): PathPrefixer { return new PathPrefixer(this.prefixPath(prefix)); } /** * Returns a new instance of `PathPrefixer` which is using as prefix provided normalized path. */ static fromPath(path: string): PathPrefixer { // return new this((0, utils_1.slash)((0, path_1.normalize)(path))); return new this(slash(normalize(path))); } }