Skip to content

Commit

Permalink
fix: Join and normalize paths consistently.
Browse files Browse the repository at this point in the history
  • Loading branch information
RubenVerborgh authored and joachimvh committed Jan 4, 2021
1 parent ee072b0 commit f454b78
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 62 deletions.
20 changes: 10 additions & 10 deletions src/init/CliRunner.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/* eslint-disable unicorn/no-process-exit */

import * as path from 'path';
import type { ReadStream, WriteStream } from 'tty';
import type { LoaderProperties } from 'componentsjs';
import { Loader } from 'componentsjs';
import yargs from 'yargs';
import { getLoggerFor } from '../logging/LogUtil';
import { ensureTrailingSlash } from '../util/PathUtil';
import { joinFilePath, toSystemFilePath, ensureTrailingSlash } from '../util/PathUtil';
import type { Initializer } from './Initializer';

export class CliRunner {
Expand Down Expand Up @@ -44,14 +43,14 @@ export class CliRunner {

// Gather settings for instantiating the server
const loaderProperties: LoaderProperties = {
mainModulePath: this.resolvePath(params.mainModulePath),
mainModulePath: toSystemFilePath(this.resolveFilePath(params.mainModulePath)),
scanGlobal: params.globalModules,
};
const configFile = this.resolvePath(params.config, 'config/config-default.json');
const configFile = this.resolveFilePath(params.config, 'config/config-default.json');
const variables = this.createVariables(params);

// Create and execute the server initializer
this.createInitializer(loaderProperties, configFile, variables)
this.createInitializer(loaderProperties, toSystemFilePath(configFile), variables)
.then(
async(initializer): Promise<void> => initializer.handleSafe(),
(error: Error): void => {
Expand All @@ -70,10 +69,10 @@ export class CliRunner {
* Resolves a path relative to the current working directory,
* falling back to a path relative to this module.
*/
protected resolvePath(cwdPath?: string | null, modulePath = ''): string {
protected resolveFilePath(cwdPath?: string | null, modulePath = ''): string {
return typeof cwdPath === 'string' ?
path.join(process.cwd(), cwdPath) :
path.join(__dirname, '../../', modulePath);
joinFilePath(process.cwd(), cwdPath) :
joinFilePath(__dirname, '../../', modulePath);
}

/**
Expand All @@ -85,10 +84,11 @@ export class CliRunner {
params.baseUrl ? ensureTrailingSlash(params.baseUrl) : `http://localhost:${params.port}/`,
'urn:solid-server:default:variable:loggingLevel': params.loggingLevel,
'urn:solid-server:default:variable:port': params.port,
'urn:solid-server:default:variable:rootFilePath': this.resolvePath(params.rootFilePath),
'urn:solid-server:default:variable:rootFilePath':
this.resolveFilePath(params.rootFilePath),
'urn:solid-server:default:variable:sparqlEndpoint': params.sparqlEndpoint,
'urn:solid-server:default:variable:podTemplateFolder':
params.podTemplateFolder ?? this.resolvePath(null, 'templates'),
this.resolveFilePath(params.podTemplateFolder, 'templates'),
};
}

Expand Down
7 changes: 2 additions & 5 deletions src/pods/generate/TemplatedResourcesGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { promises as fsPromises } from 'fs';
import { posix } from 'path';
import { Parser } from 'n3';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
Expand All @@ -8,14 +7,12 @@ import type {
FileIdentifierMapperFactory,
ResourceLink,
} from '../../storage/mapping/FileIdentifierMapper';
import { isContainerIdentifier } from '../../util/PathUtil';
import { joinFilePath, isContainerIdentifier } from '../../util/PathUtil';
import { guardedStreamFrom } from '../../util/StreamUtil';
import type { Resource, ResourcesGenerator } from './ResourcesGenerator';
import type { TemplateEngine } from './TemplateEngine';
import Dict = NodeJS.Dict;

const { join: joinPath } = posix;

/**
* Generates resources by making use of a template engine.
* The template folder structure will be kept.
Expand Down Expand Up @@ -77,7 +74,7 @@ export class TemplatedResourcesGenerator implements ResourcesGenerator {
private async* generateLinks(folderPath: string, mapper: FileIdentifierMapper): AsyncIterable<ResourceLink> {
const files = await fsPromises.readdir(folderPath);
for (const name of files) {
const filePath = joinPath(folderPath, name);
const filePath = joinFilePath(folderPath, name);
const stats = await fsPromises.lstat(filePath);
yield mapper.mapFilePathToUrl(filePath, stats.isDirectory());
}
Expand Down
9 changes: 3 additions & 6 deletions src/storage/accessors/FileDataAccessor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Stats } from 'fs';
import { createWriteStream, createReadStream, promises as fsPromises } from 'fs';
import { posix } from 'path';
import type { Readable } from 'stream';
import { DataFactory } from 'n3';
import type { NamedNode, Quad } from 'rdf-js';
Expand All @@ -13,16 +12,14 @@ import { isSystemError } from '../../util/errors/SystemError';
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
import { guardStream } from '../../util/GuardedStream';
import type { Guarded } from '../../util/GuardedStream';
import { isContainerIdentifier } from '../../util/PathUtil';
import { joinFilePath, isContainerIdentifier } from '../../util/PathUtil';
import { parseQuads, pushQuad, serializeQuads } from '../../util/QuadUtil';
import { generateContainmentQuads, generateResourceQuads } from '../../util/ResourceUtil';
import { toLiteral } from '../../util/TermUtil';
import { CONTENT_TYPE, DC, LDP, POSIX, RDF, XSD } from '../../util/Vocabularies';
import type { FileIdentifierMapper, ResourceLink } from '../mapping/FileIdentifierMapper';
import type { DataAccessor } from './DataAccessor';

const { join: joinPath } = posix;

/**
* DataAccessor that uses the file system to store documents as files and containers as folders.
*/
Expand Down Expand Up @@ -298,14 +295,14 @@ export class FileDataAccessor implements DataAccessor {
}

// Ignore non-file/directory entries in the folder
const childStats = await fsPromises.lstat(joinPath(link.filePath, childName));
const childStats = await fsPromises.lstat(joinFilePath(link.filePath, childName));
if (!childStats.isFile() && !childStats.isDirectory()) {
continue;
}

// Generate the URI corresponding to the child resource
const childLink = await this.resourceMapper
.mapFilePathToUrl(joinPath(link.filePath, childName), childStats.isDirectory());
.mapFilePathToUrl(joinFilePath(link.filePath, childName), childStats.isDirectory());

// Generate metadata of this specific child
const subject = DataFactory.namedNode(childLink.identifier.path);
Expand Down
9 changes: 4 additions & 5 deletions src/storage/mapping/ExtensionBasedMapper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { promises as fsPromises } from 'fs';
import { posix } from 'path';
import * as mime from 'mime-types';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil';
Expand All @@ -9,13 +8,13 @@ import {
encodeUriPathComponents,
ensureTrailingSlash,
isContainerIdentifier,
joinFilePath,
normalizeFilePath,
trimTrailingSlashes,
} from '../../util/PathUtil';
import type { FileIdentifierMapper, FileIdentifierMapperFactory, ResourceLink } from './FileIdentifierMapper';
import { getAbsolutePath, getRelativePath, validateRelativePath } from './MapperUtil';

const { join: joinPath, normalize: normalizePath } = posix;

export interface ResourcePath {

/**
Expand Down Expand Up @@ -50,7 +49,7 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {

public constructor(base: string, rootFilepath: string, overrideTypes = { acl: TEXT_TURTLE, meta: TEXT_TURTLE }) {
this.baseRequestURI = trimTrailingSlashes(base);
this.rootFilepath = trimTrailingSlashes(normalizePath(rootFilepath));
this.rootFilepath = trimTrailingSlashes(normalizeFilePath(rootFilepath));
this.types = { ...mime.types, ...overrideTypes };
}

Expand Down Expand Up @@ -101,7 +100,7 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {

// Matching file found
if (fileName) {
filePath = joinPath(folder, fileName);
filePath = joinFilePath(folder, fileName);
}

this.logger.info(`The path for ${identifier.path} is ${filePath}`);
Expand Down
9 changes: 4 additions & 5 deletions src/storage/mapping/FixedContentTypeMapper.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { posix } from 'path';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import {
encodeUriPathComponents,
ensureTrailingSlash, isContainerIdentifier,
ensureTrailingSlash,
isContainerIdentifier,
normalizeFilePath,
trimTrailingSlashes,
} from '../../util/PathUtil';
import type { FileIdentifierMapper, ResourceLink } from './FileIdentifierMapper';
import { getAbsolutePath, getRelativePath, validateRelativePath } from './MapperUtil';

const { normalize: normalizePath } = posix;

/**
* A mapper that always returns a fixed content type for files.
*/
Expand All @@ -24,7 +23,7 @@ export class FixedContentTypeMapper implements FileIdentifierMapper {

public constructor(base: string, rootFilepath: string, contentType: string) {
this.baseRequestURI = trimTrailingSlashes(base);
this.rootFilepath = trimTrailingSlashes(normalizePath(rootFilepath));
this.rootFilepath = trimTrailingSlashes(normalizeFilePath(rootFilepath));
this.contentType = contentType;
}

Expand Down
7 changes: 2 additions & 5 deletions src/storage/mapping/MapperUtil.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { posix } from 'path';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../../logging/LogUtil';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { decodeUriPathComponents } from '../../util/PathUtil';

const { join: joinPath } = posix;
import { joinFilePath, decodeUriPathComponents } from '../../util/PathUtil';

const logger = getLoggerFor('MapperUtil');

Expand All @@ -18,7 +15,7 @@ const logger = getLoggerFor('MapperUtil');
* @returns Absolute path of the file.
*/
export const getAbsolutePath = (rootFilepath: string, path: string, identifier = ''): string =>
joinPath(rootFilepath, path, identifier);
joinFilePath(rootFilepath, path, identifier);

/**
* Strips the baseRequestURI from the identifier and checks if the stripped base URI matches the store's one.
Expand Down
41 changes: 41 additions & 0 deletions src/util/PathUtil.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
import platform, { posix } from 'path';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';

/**
* Changes a potential Windows path into a POSIX path.
*
* @param path - Path to check (POSIX or Windows).
*
* @returns The potentially changed path (POSIX).
*/
const windowsToPosixPath = (path: string): string => path.replace(/\\+/gu, '/');

/**
* Resolves relative segments in the path/
*
* @param path - Path to check (POSIX or Windows).
*
* @returns The potentially changed path (POSIX).
*/
export const normalizeFilePath = (path: string): string =>
posix.normalize(windowsToPosixPath(path));

/**
* 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).
*/
export const joinFilePath = (basePath: string, ...paths: string[]): string =>
posix.join(windowsToPosixPath(basePath), ...paths);

/**
* Converts the path into an OS-dependent path.
*
* @param path - Path to check (POSIX).
*
* @returns The potentially changed path (OS-dependent).
*/
export const toSystemFilePath = (path: string): string =>
platform.normalize(path);

/**
* Makes sure the input path has exactly 1 slash at the end.
* Multiple slashes will get merged into one.
Expand Down
9 changes: 4 additions & 5 deletions test/integration/Config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { mkdirSync } from 'fs';
import { join } from 'path';
import * as Path from 'path';
import { Loader } from 'componentsjs';
import * as rimraf from 'rimraf';
import { joinFilePath, toSystemFilePath } from '../../src/util/PathUtil';

export const BASE = 'http://test.com';

Expand All @@ -12,17 +11,17 @@ export const BASE = 'http://test.com';
export const instantiateFromConfig = async(componentUrl: string, configFile: string,
variables?: Record<string, any>): Promise<any> => {
// Initialize the Components.js loader
const mainModulePath = Path.join(__dirname, '../../');
const mainModulePath = joinFilePath(__dirname, '../../');
const loader = new Loader({ mainModulePath });
await loader.registerAvailableModuleResources();

// Instantiate the component from the config
const configPath = Path.join(__dirname, 'config', configFile);
const configPath = toSystemFilePath(joinFilePath(__dirname, 'config', configFile));
return loader.instantiateFromUrl(componentUrl, configPath, undefined, { variables });
};

export const getTestFolder = (name: string): string =>
join(__dirname, '../tmp', name);
joinFilePath(__dirname, '../tmp', name);

export const createFolder = (folder: string): void => {
mkdirSync(folder, { recursive: true });
Expand Down
10 changes: 5 additions & 5 deletions test/integration/LdpHandlerWithAuth.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createReadStream } from 'fs';
import { join } from 'path';
import type { HttpHandler, Initializer, ResourceStore } from '../../src/';
import { RepresentationMetadata } from '../../src/ldp/representation/RepresentationMetadata';
import { guardStream } from '../../src/util/GuardedStream';
import { CONTENT_TYPE, LDP } from '../../src/util/Vocabularies';
import {
CONTENT_TYPE, LDP,
RepresentationMetadata, guardStream, joinFilePath,
} from '../../src/';
import { AclHelper, ResourceHelper } from '../util/TestHelpers';
import { BASE, getTestFolder, createFolder, removeFolder, instantiateFromConfig } from './Config';

Expand Down Expand Up @@ -58,7 +58,7 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeUrn, se
// Write test resource
await store.setRepresentation({ path: `${BASE}/permanent.txt` }, {
binary: true,
data: guardStream(createReadStream(join(__dirname, '../assets/permanent.txt'))),
data: guardStream(createReadStream(joinFilePath(__dirname, '../assets/permanent.txt'))),
metadata: new RepresentationMetadata({ [CONTENT_TYPE]: 'text/plain' }),
});
});
Expand Down
4 changes: 2 additions & 2 deletions test/integration/PodCreation.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Server } from 'http';
import { join } from 'path';
import fetch from 'cross-fetch';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import { joinFilePath } from '../../src/util/PathUtil';
import { readableToString } from '../../src/util/StreamUtil';
import { instantiateFromConfig } from './Config';

Expand All @@ -17,7 +17,7 @@ describe('A server with a pod handler', (): void => {
'urn:solid-server:default:ServerFactory', 'server-without-auth.json', {
'urn:solid-server:default:variable:port': port,
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:podTemplateFolder': join(__dirname, '../assets/templates'),
'urn:solid-server:default:variable:podTemplateFolder': joinFilePath(__dirname, '../assets/templates'),
},
) as HttpServerFactory;
server = factory.startServer(port);
Expand Down
Loading

0 comments on commit f454b78

Please sign in to comment.