"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.joinUrl = exports.readPackageJson = exports.resolveAssetPath = exports.resolveModulePath = exports.modulePath = exports.modulePathPlaceholder = exports.getModuleRoot = exports.createSubdomainRegexp = exports.getRelativeUrl = exports.extractScheme = exports.isContainerIdentifier = exports.isContainerPath = exports.encodeUriPathComponents = exports.decodeUriPathComponents = exports.toCanonicalUriPath = exports.getExtension = exports.trimLeadingSlashes = exports.ensureLeadingSlash = exports.trimTrailingSlashes = exports.ensureTrailingSlash = exports.absoluteFilePath = exports.joinFilePath = exports.normalizeFilePath = void 0;
const node_path_1 = require("node:path");
const fs_extra_1 = require("fs-extra");
const url_join_1 = __importDefault(require("url-join"));
const BadRequestHttpError_1 = require("./errors/BadRequestHttpError");
const HttpErrorUtil_1 = require("./errors/HttpErrorUtil");
/**
 * Changes a potential Windows path into a POSIX path.
 *
 * @param path - Path to check (POSIX or Windows).
 *
 * @returns The potentially changed path (POSIX).
 */
function windowsToPosixPath(path) {
    return path.replaceAll(/\\+/gu, '/');
}
/**
 * Resolves relative segments in the path.
 *
 * @param path - Path to check (POSIX or Windows).
 *
 * @returns The potentially changed path (POSIX).
 */
function normalizeFilePath(path) {
    return node_path_1.posix.normalize(windowsToPosixPath(path));
}
exports.normalizeFilePath = normalizeFilePath;
/**
 * Adds the paths to the base path.
 *
 * @param basePath - The base path (POSIX or Windows).
 * @param paths - Subpaths to attach (POSIX).
 *
 * @returns The potentially changed path (POSIX).
 */
function joinFilePath(basePath, ...paths) {
    return node_path_1.posix.join(windowsToPosixPath(basePath), ...paths);
}
exports.joinFilePath = joinFilePath;
/**
 * Resolves a path to its absolute form.
 * Absolute inputs will not be changed (except changing Windows to POSIX).
 * Relative inputs will be interpreted relative to process.cwd().
 *
 * @param path - Path to check (POSIX or Windows).
 *
 * @returns The potentially changed path (POSIX).
 */
function absoluteFilePath(path) {
    if (node_path_1.posix.isAbsolute(path)) {
        return path;
    }
    if (node_path_1.win32.isAbsolute(path)) {
        return windowsToPosixPath(path);
    }
    return joinFilePath(process.cwd(), path);
}
exports.absoluteFilePath = absoluteFilePath;
/**
 * Makes sure the input path has exactly 1 slash at the end.
 * Multiple slashes will get merged into one.
 * If there is no slash it will be added.
 *
 * @param path - Path to check.
 *
 * @returns The potentially changed path.
 */
function ensureTrailingSlash(path) {
    return path.replace(/\/*$/u, '/');
}
exports.ensureTrailingSlash = ensureTrailingSlash;
/**
 * Makes sure the input path has no slashes at the end.
 *
 * @param path - Path to check.
 *
 * @returns The potentially changed path.
 */
function trimTrailingSlashes(path) {
    return path.replace(/\/+$/u, '');
}
exports.trimTrailingSlashes = trimTrailingSlashes;
/**
 * Makes sure the input path has exactly 1 slash at the beginning.
 * Multiple slashes will get merged into one.
 * If there is no slash it will be added.
 *
 * @param path - Path to check.
 *
 * @returns The potentially changed path.
 */
function ensureLeadingSlash(path) {
    return path.replace(/^\/*/u, '/');
}
exports.ensureLeadingSlash = ensureLeadingSlash;
/**
 * Makes sure the input path has no slashes at the beginning.
 *
 * @param path - Path to check.
 *
 * @returns The potentially changed path.
 */
function trimLeadingSlashes(path) {
    return path.replace(/^\/+/u, '');
}
exports.trimLeadingSlashes = trimLeadingSlashes;
/**
 * Extracts the extension (without dot) from a path.
 * Custom function since `path.extname` does not work on all cases (e.g. ".acl")
 * @param path - Input path to parse.
 */
function getExtension(path) {
    const extension = /\.([^./]+)$/u.exec(path);
    return extension ? extension[1] : '';
}
exports.getExtension = getExtension;
/**
 * Performs a transformation on the path components of a URI,
 * preserving but normalizing path delimiters and their escaped forms.
 */
function transformPathComponents(path, transform) {
    const [, base, queryString] = /^([^?]*)(.*)$/u.exec(path);
    const transformed = base
        // We split on actual URI path component delimiters (slash and backslash),
        // but also on things that could be wrongly interpreted as component delimiters,
        // such that they cannot be transformed incorrectly.
        // We thus ensure that encoded slashes (%2F) and backslashes (%5C) are preserved,
        // since they would become _actual_ delimiters if accidentally decoded.
        // Additionally, we need to preserve any encoded percent signs (%25)
        // that precede them, because these might change their interpretation as well.
        .split(/(\/|\\|%(?:25)*(?:2f|5c))/ui)
        // Even parts map to components that need to be transformed,
        // odd parts to (possibly escaped) delimiters that need to be normalized.
        .map((part, index) => index % 2 === 0 ? transform(part) : part.toUpperCase())
        .join('');
    return queryString ? `${transformed}${queryString}` : transformed;
}
/**
 * Converts a URI path to the canonical version by splitting on slashes,
 * decoding any percent-based encodings, and then encoding any special characters.
 * This function is used to clean unwanted characters in the components of
 * the provided path.
 *
 * @param path - The path to convert to its canonical URI path form.
 * @returns The canonical URI path form of the provided path.
 */
function toCanonicalUriPath(path) {
    return transformPathComponents(path, (part) => encodeURIComponent(decodeURIComponent(part)));
}
exports.toCanonicalUriPath = toCanonicalUriPath;
/* eslint-disable ts/naming-convention */
// Characters not allowed in a Windows file path
const forbiddenSymbols = {
    '<': '%3C',
    '>': '%3E',
    ':': '%3A',
    '"': '%22',
    '|': '%7C',
    '?': '%3F',
    // `*` does not get converted by `encodeUriComponent`
    '*': '%2A',
};
/* eslint-enable ts/naming-convention */
const forbiddenRegex = new RegExp(`[${Object.keys(forbiddenSymbols).join('')}]`, 'ug');
/**
 * This function is used when converting a URI to a file path. Decodes all components of a URI path,
 * with the exception of encoded slash characters, as this would lead to unexpected file locations
 * being targeted (resulting in erroneous behaviour of the file based backend).
 * Characters that would result in an illegal file path remain percent encoded.
 *
 * @param path - The path to decode the URI path components of.
 * @returns A decoded copy of the provided URI path (ignoring encoded slash characters).
 */
function decodeUriPathComponents(path) {
    return transformPathComponents(path, (part) => decodeURIComponent(part)
        // The characters replaced below result in illegal Windows file paths so need to be encoded
        .replace(forbiddenRegex, (val) => forbiddenSymbols[val]));
}
exports.decodeUriPathComponents = decodeUriPathComponents;
/**
 * This function is used in the process of converting a file path to a URI. Encodes all (non-slash)
 * special characters in a URI path, with the exception of encoded slash characters, as this would
 * lead to unnecessary double encoding, resulting in a URI that differs from the expected result.
 *
 * @param path - The path to encode the URI path components of.
 * @returns An encoded copy of the provided URI path (ignoring encoded slash characters).
 */
function encodeUriPathComponents(path) {
    return transformPathComponents(path, encodeURIComponent);
}
exports.encodeUriPathComponents = encodeUriPathComponents;
/**
 * Checks if the path corresponds to a container path (ending in a /).
 * @param path - Path to check.
 */
function isContainerPath(path) {
    // Solid, §3.1: "Paths ending with a slash denote a container resource."
    // https://solid.github.io/specification/protocol#uri-slash-semantics
    return path.endsWith('/');
}
exports.isContainerPath = isContainerPath;
/**
 * Checks if the identifier corresponds to a container identifier.
 * @param identifier - Identifier to check.
 */
function isContainerIdentifier(identifier) {
    return isContainerPath(identifier.path);
}
exports.isContainerIdentifier = isContainerIdentifier;
/**
 * Splits a URL (or similar) string into a part containing its scheme and one containing the rest.
 * E.g., `http://test.com/` results in `{ scheme: 'http://', rest: 'test.com/' }`.
 * @param url - String to parse.
 */
function extractScheme(url) {
    const match = /^([^:]+:\/\/)(.*)$/u.exec(url);
    return { scheme: match[1], rest: match[2] };
}
exports.extractScheme = extractScheme;
/**
 * Creates a relative URL by removing the base URL.
 * Will throw an error in case the resulting target is not withing the base URL scope.
 * @param baseUrl - Base URL.
 * @param request - Incoming request of which the target needs to be extracted.
 * @param targetExtractor - Will extract the target from the request.
 */
async function getRelativeUrl(baseUrl, request, targetExtractor) {
    baseUrl = ensureTrailingSlash(baseUrl);
    const target = await targetExtractor.handleSafe({ request });
    if (!target.path.startsWith(baseUrl)) {
        throw new BadRequestHttpError_1.BadRequestHttpError(`The identifier ${target.path} is outside the configured identifier space.`, { errorCode: 'E0001', metadata: (0, HttpErrorUtil_1.errorTermsToMetadata)({ path: target.path }) });
    }
    return target.path.slice(baseUrl.length - 1);
}
exports.getRelativeUrl = getRelativeUrl;
/**
 * Creates a regular expression that matches URLs containing the given baseUrl, or a subdomain of the given baseUrl.
 * In case there is a subdomain, the first match of the regular expression will be that subdomain.
 *
 * Examples with baseUrl `http://test.com/foo/`:
 *  - Will match `http://test.com/foo/`
 *  - Will match `http://test.com/foo/bar/baz`
 *  - Will match `http://alice.bob.test.com/foo/bar/baz`, first match result will be `alice.bob`
 *  - Will not match `http://test.com/`
 *  - Will not match `http://alicetest.com/foo/`
 * @param baseUrl - Base URL for the regular expression.
 */
function createSubdomainRegexp(baseUrl) {
    const { scheme, rest } = extractScheme(baseUrl);
    return new RegExp(`^${scheme}(?:([^/]+)\\.)?${rest}`, 'u');
}
exports.createSubdomainRegexp = createSubdomainRegexp;
/**
 * Returns the folder corresponding to the root of the Community Solid Server module
 */
function getModuleRoot() {
    return joinFilePath(__dirname, '../../');
}
exports.getModuleRoot = getModuleRoot;
/**
 * A placeholder for the path to the `@solid/community-server` module root.
 * The `resolveAssetPath` function will replace this string with the actual path.
 */
exports.modulePathPlaceholder = '@css:';
/**
 * Creates a path starting from the `@solid/community-server` module root,
 * to be resolved by the `resolveAssetPath` function.
 */
function modulePath(relativePath = '') {
    return `${exports.modulePathPlaceholder}${relativePath}`;
}
exports.modulePath = modulePath;
/**
 * Creates an absolute path starting from the `@solid/community-server` module root.
 */
function resolveModulePath(relativePath = '') {
    return joinFilePath(getModuleRoot(), relativePath);
}
exports.resolveModulePath = resolveModulePath;
/**
 * Converts file path inputs into absolute paths.
 * Works similar to `absoluteFilePath` but paths that start with the `modulePathPlaceholder`
 * will be relative to the module directory instead of the cwd.
 */
function resolveAssetPath(path = exports.modulePathPlaceholder) {
    if (path.startsWith(exports.modulePathPlaceholder)) {
        return resolveModulePath(path.slice(exports.modulePathPlaceholder.length));
    }
    return absoluteFilePath(path);
}
exports.resolveAssetPath = resolveAssetPath;
/**
 * Reads the project package.json and returns it.
 */
async function readPackageJson() {
    return (0, fs_extra_1.readJson)(resolveModulePath('package.json'));
}
exports.readPackageJson = readPackageJson;
/**
 * Concatenates all the given strings into a normalized URL.
 * Will place slashes between input strings if necessary.
 */
exports.joinUrl = url_join_1.default;
//# sourceMappingURL=PathUtil.js.map