File size: 2,029 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { ErrorCodes, Error2 } from '#/errors';
import { getShellPathBridge } from '#/_base/execEnv/shellPathBridge';

import type { Runtime, RuntimeBinding, RuntimeWorkspaceRoots } from './runtime';

export type { RuntimeWorkspaceRoots } from './runtime';

export class RuntimeWorkspaceView {
  readonly binding: RuntimeBinding;
  readonly generation: string;
  readonly workDir: string;
  readonly additionalDirs: readonly string[];
  readonly roots: readonly string[];

  constructor(
    readonly runtime: Runtime,
    roots: RuntimeWorkspaceRoots,
  ) {
    this.binding = {
      workspaceId: runtime.identity.workspaceId,
      runtimeId: runtime.identity.runtimeId,
    };
    this.generation = runtime.identity.generation;
    const mapped = runtime.workspace.mapRoots(roots);
    this.workDir = runtime.path.resolve(mapped.workDir);
    this.additionalDirs = [...new Set((mapped.additionalDirs ?? []).map((root) => runtime.path.resolve(root)))];
    this.roots = [this.workDir, ...this.additionalDirs];
  }

  resolve(path: string, cwd = this.workDir): string {
    const env = this.runtime.environment;
    const bridged = env.pathClass === 'win32' ? getShellPathBridge(env).fromShellPath(path) : path;
    return this.runtime.path.isAbsolute(bridged)
      ? this.runtime.path.resolve(bridged)
      : this.runtime.path.resolve(cwd, bridged);
  }

  assertAllowed(path: string): string {
    const resolved = this.runtime.path.resolve(path);
    if (this.roots.some((root) => contains(this.runtime, root, resolved))) return resolved;
    throw new Error2(
      ErrorCodes.FS_PATH_ESCAPES,
      `path ${path} is outside runtime workspace ${this.binding.runtimeId}`,
      { details: { path: resolved } },
    );
  }
}

function contains(runtime: Runtime, root: string, candidate: string): boolean {
  const relative = runtime.path.relative(root, candidate);
  if (relative === '') return true;
  return relative !== '..' && !relative.startsWith(`..${runtime.path.separator}`) && !runtime.path.isAbsolute(relative);
}