Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactors JailFS #6161

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 36 additions & 0 deletions .yarn/versions/0edae3a3.yml
@@ -0,0 +1,36 @@
releases:
"@yarnpkg/fslib": major

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-essentials"
- "@yarnpkg/plugin-exec"
- "@yarnpkg/plugin-file"
- "@yarnpkg/plugin-git"
- "@yarnpkg/plugin-github"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-link"
- "@yarnpkg/plugin-nm"
- "@yarnpkg/plugin-npm"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-pnpm"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- vscode-zipfs
- "@yarnpkg/builder"
- "@yarnpkg/cli"
- "@yarnpkg/core"
- "@yarnpkg/doctor"
- "@yarnpkg/libzip"
- "@yarnpkg/nm"
- "@yarnpkg/pnp"
- "@yarnpkg/pnpify"
- "@yarnpkg/sdks"
- "@yarnpkg/shell"
16 changes: 8 additions & 8 deletions packages/plugin-pack/sources/packUtils.ts
@@ -1,11 +1,11 @@
import {Manifest, Report, Workspace, scriptUtils} from '@yarnpkg/core';
import {FakeFS, JailFS, xfs, PortablePath, ppath, Filename, npath, constants} from '@yarnpkg/fslib';
import {Hooks as StageHooks} from '@yarnpkg/plugin-stage';
import mm from 'micromatch';
import tar from 'tar-stream';
import {createGzip} from 'zlib';
import {Manifest, Report, Workspace, scriptUtils} from '@yarnpkg/core';
import {FakeFS, SubFS, xfs, PortablePath, ppath, Filename, npath, constants} from '@yarnpkg/fslib';
import {Hooks as StageHooks} from '@yarnpkg/plugin-stage';
import mm from 'micromatch';
import tar from 'tar-stream';
import {createGzip} from 'zlib';

import {Hooks} from './';
import {Hooks} from './';

const NEVER_IGNORE = [
`/package.json`,
Expand Down Expand Up @@ -249,7 +249,7 @@ export async function genPackList(workspace: Workspace) {
async function walk(initialCwd: PortablePath, {hasExplicitFileList, globalList, ignoreList}: {hasExplicitFileList: boolean, globalList: IgnoreList, ignoreList: IgnoreList}) {
const list: Array<PortablePath> = [];

const cwdFs = new JailFS(initialCwd);
const cwdFs = new SubFS(initialCwd);
const cwdList: Array<[PortablePath, Array<IgnoreList>]> = [[PortablePath.root, [ignoreList]]];

while (cwdList.length > 0) {
Expand Down
15 changes: 1 addition & 14 deletions packages/yarnpkg-fslib/sources/CwdFS.ts
Expand Up @@ -16,31 +16,18 @@ export class CwdFS extends ProxiedFS<PortablePath, PortablePath> {
super(ppath);

this.target = this.pathUtils.normalize(target);

this.baseFs = baseFs;
}

getRealPath() {
return this.pathUtils.resolve(this.baseFs.getRealPath(), this.target);
}

resolve(p: PortablePath) {
if (this.pathUtils.isAbsolute(p)) {
return ppath.normalize(p);
} else {
return this.baseFs.resolve(ppath.join(this.target, p));
}
}

mapFromBase(path: PortablePath) {
return path;
}

mapToBase(path: PortablePath) {
if (this.pathUtils.isAbsolute(path)) {
return path;
} else {
return this.pathUtils.join(this.target, path);
}
return ppath.resolve(this.target, path);
}
}
24 changes: 5 additions & 19 deletions packages/yarnpkg-fslib/sources/JailFS.ts
Expand Up @@ -7,8 +7,6 @@ export type JailFSOptions = {
baseFs?: FakeFS<PortablePath>;
};

const JAIL_ROOT = PortablePath.root;

export class JailFS extends ProxiedFS<PortablePath, PortablePath> {
private readonly target: PortablePath;

Expand All @@ -18,35 +16,23 @@ export class JailFS extends ProxiedFS<PortablePath, PortablePath> {
super(ppath);

this.target = this.pathUtils.resolve(PortablePath.root, target);

this.baseFs = baseFs;
}

getRealPath() {
return this.pathUtils.resolve(this.baseFs.getRealPath(), this.pathUtils.relative(PortablePath.root, this.target));
}

getTarget() {
return this.target;
}

getBaseFs() {
return this.baseFs;
}

protected mapToBase(p: PortablePath): PortablePath {
const normalized = this.pathUtils.normalize(p);

if (this.pathUtils.isAbsolute(p))
return this.pathUtils.resolve(this.target, this.pathUtils.relative(JAIL_ROOT, p));
const normalized = ppath.resolve(this.target, p);

if (normalized.match(/^\.\.\/?/))
throw new Error(`Resolving this path (${p}) would escape the jail`);
if (!ppath.contains(this.target, normalized))
throw new Error(`Resolving this path (${p}, resolved as ${normalized}) would escape the jail (${this.target})`);

return this.pathUtils.resolve(this.target, p);
return normalized;
}

protected mapFromBase(p: PortablePath): PortablePath {
return this.pathUtils.resolve(JAIL_ROOT, this.pathUtils.relative(this.target, p));
return p;
}
}
2 changes: 1 addition & 1 deletion packages/yarnpkg-fslib/sources/ProxiedFS.ts
Expand Up @@ -22,7 +22,7 @@ export abstract class ProxiedFS<P extends Path, IP extends Path> extends FakeFS<
return this.baseFs.getExtractHint(hints);
}

resolve(path: P) {
resolve(path: P) {
return this.mapFromBase(this.baseFs.resolve(this.mapToBase(path)));
}

Expand Down
38 changes: 38 additions & 0 deletions packages/yarnpkg-fslib/sources/SubFS.ts
@@ -0,0 +1,38 @@
import {FakeFS} from './FakeFS';
import {NodeFS} from './NodeFS';
import {ProxiedFS} from './ProxiedFS';
import {ppath, PortablePath} from './path';

export type SubFSOptions = {
baseFs?: FakeFS<PortablePath>;
};

export class SubFS extends ProxiedFS<PortablePath, PortablePath> {
private readonly target: PortablePath;

protected readonly baseFs: FakeFS<PortablePath>;

constructor(target: PortablePath, {baseFs = new NodeFS()}: SubFSOptions = {}) {
super(ppath);

this.target = ppath.resolve(PortablePath.root, target);
this.baseFs = baseFs;
}

getRealPath() {
return ppath.resolve(this.baseFs.getRealPath(), ppath.relative(PortablePath.root, this.target));
}

protected mapToBase(p: PortablePath): PortablePath {
return ppath.resolve(this.target, ppath.relative(PortablePath.root, ppath.resolve(PortablePath.root, p)));
}

protected mapFromBase(p: PortablePath): PortablePath {
const relPath = ppath.relative(this.target, p);

if (relPath.match(/^\.\.\/?/))
throw new Error(`Path ${p} is outside of the jail`);

return ppath.join(PortablePath.root, relPath);
}
}
3 changes: 2 additions & 1 deletion packages/yarnpkg-fslib/sources/index.ts
Expand Up @@ -34,7 +34,7 @@ export type {Stats, BigIntStats} from './FakeFS';
export {PortablePath, Filename} from './path';
export type {FSPath, Path, NativePath} from './path';
export type {ParsedPath, PathUtils, FormatInputPathObject} from './path';
export {npath, ppath} from './path';
export {npath, ppath} from './path';

export {AliasFS} from './AliasFS';
export {FakeFS, BasePortableFakeFS} from './FakeFS';
Expand All @@ -47,6 +47,7 @@ export {NoFS} from './NoFS';
export {NodeFS} from './NodeFS';
export {PosixFS} from './PosixFS';
export {ProxiedFS} from './ProxiedFS';
export {SubFS} from './SubFS';
export {VirtualFS} from './VirtualFS';

export {patchFs, extendFs} from './patchFs/patchFs';
Expand Down
8 changes: 4 additions & 4 deletions packages/yarnpkg-fslib/tests/JailFS.test.ts
@@ -1,5 +1,5 @@
import {JailFS} from '../sources/JailFS';
import {xfs, ppath, PortablePath, Filename} from '../sources';
import {JailFS} from '../sources/JailFS';
import {xfs, ppath, Filename} from '../sources';

describe(`JailFS`, () => {
it(`should not throw an error when the accessed path is inside the target folder (relative)`, async () => {
Expand All @@ -19,7 +19,7 @@ describe(`JailFS`, () => {
await xfs.mkdirPromise(jailedFolder);

const jailFs = new JailFS(jailedFolder);
await jailFs.writeFilePromise(ppath.join(PortablePath.root, `text.txt`), `Hello World`);
await jailFs.writeFilePromise(ppath.join(jailedFolder, `text.txt`), `Hello World`);
});

it(`should throw an error when the accessed path is not inside the target folder`, async () => {
Expand All @@ -30,6 +30,6 @@ describe(`JailFS`, () => {
await xfs.mkdirpPromise(jailedFolder);

const jailFs = new JailFS(jailedFolder);
await expect(jailFs.writeFilePromise(`../text.txt` as Filename, `Hello World`)).rejects.toThrow(`Resolving this path (../text.txt) would escape the jail`);
await expect(jailFs.writeFilePromise(`../text.txt` as Filename, `Hello World`)).rejects.toThrow(/Resolving this path \(\.\.\/text.txt, resolved as .*\) would escape the jail \(.*\)/);
});
});
102 changes: 102 additions & 0 deletions packages/yarnpkg-fslib/tests/compare.test.ts
@@ -0,0 +1,102 @@
import {CwdFS, JailFS, NodeFS, SubFS, npath, ppath} from '../sources';

const nodeFs = new NodeFS();
const pkgDir = ppath.dirname(npath.toPortablePath(__dirname));

const drives = {
CwdFS,
JailFS,
SubFS,
};

const tests = {
resolve: [{
args: [pkgDir],
results: {
CwdFS: pkgDir,
JailFS: pkgDir,
},
}, {
args: [ppath.join(pkgDir, `tests`)],
results: {
CwdFS: ppath.join(pkgDir, `tests`),
JailFS: ppath.join(pkgDir, `tests`),
},
}, {
args: [ppath.dirname(pkgDir)],
results: {
CwdFS: ppath.dirname(pkgDir),
JailFS: Error,
},
}, {
args: [`tests`],
results: {
CwdFS: ppath.join(pkgDir, `tests`),
JailFS: ppath.join(pkgDir, `tests`),
},
}, {
args: [`../`],
results: {
CwdFS: ppath.dirname(pkgDir),
JailFS: Error,
},
}, {
args: [`../yarnpkg-fslib`],
results: {
CwdFS: pkgDir,
JailFS: pkgDir,
},
}, {
args: [`/`],
results: {
CwdFS: `/`,
JailFS: Error,
},
}],
readFileSync: [{
args: [`/package.json`, `utf8`],
results: {
CwdFS: Error,
JailFS: Error,
SubFS: expect.stringContaining(`"name": "@yarnpkg/fslib"`),
},
}, {
args: [`package.json`, `utf8`],
results: {
CwdFS: expect.stringContaining(`"name": "@yarnpkg/fslib"`),
JailFS: expect.stringContaining(`"name": "@yarnpkg/fslib"`),
SubFS: expect.stringContaining(`"name": "@yarnpkg/fslib"`),
},
}, {
args: [`../../package.json`, `utf8`],
results: {
CwdFS: expect.stringContaining(`"name": "@yarnpkg/monorepo"`),
JailFS: Error,
SubFS: expect.stringContaining(`"name": "@yarnpkg/fslib"`),
},
}],
realpathSync: [{
args: [`package.json`],
results: {
SubFS: `/package.json`,
},
}],
};

describe(`FsLib comparison table`, () => {
for (const [fnName, specs] of Object.entries(tests)) {
for (const {args, results} of specs) {
for (const [driveName, expectedResult] of Object.entries(results)) {
test(`${fnName}(${args.join(`, `)}) => ${expectedResult} (${driveName})`, () => {
const drive = new (drives as any)[driveName](pkgDir, {baseFs: nodeFs});

if (expectedResult === Error) {
expect(() => drive[fnName](...args)).toThrow();
} else {
expect(drive[fnName](...args)).toEqual(expectedResult);
}
});
}
}
}
});