diff --git a/packages/php-wasm/universal/src/lib/sandboxed-spawn-handler-factory.ts b/packages/php-wasm/universal/src/lib/sandboxed-spawn-handler-factory.ts index 9e1eb44918..923161d283 100644 --- a/packages/php-wasm/universal/src/lib/sandboxed-spawn-handler-factory.ts +++ b/packages/php-wasm/universal/src/lib/sandboxed-spawn-handler-factory.ts @@ -4,6 +4,10 @@ import type { PHPWorker } from './php-worker'; import type { Remote } from './comlink-sync'; import { logger } from '@php-wasm/logger'; +function wait(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + /** * An isomorphic proc_open() handler that implements typical shell in TypeScript * without relying on a server runtime. It can be used in the browser and Node.js @@ -81,7 +85,7 @@ export function sandboxedSpawnHandlerFactory( return; } - if (!['php', 'ls', 'pwd'].includes(binaryName ?? '')) { + if (!['php', 'ls', 'pwd', 'rm'].includes(binaryName ?? '')) { // 127 is the exit code "for command not found". processApi.exit(127); return; @@ -142,7 +146,7 @@ export function sandboxedSpawnHandlerFactory( // Technical limitation of subprocesses – we need to // wait before exiting to give consumer a chance to read // the output. - await new Promise((resolve) => setTimeout(resolve, 10)); + await wait(10); processApi.exit(0); break; } @@ -151,10 +155,54 @@ export function sandboxedSpawnHandlerFactory( // Technical limitation of subprocesses – we need to // wait before exiting to give consumer a chance to read // the output. - await new Promise((resolve) => setTimeout(resolve, 10)); + await wait(10); processApi.exit(0); break; } + case 'rm': { + const target = args[args.length - 1]; + const flags = args.slice(1, -1).join(' '); + + if (args.length < 2 || target.startsWith('-')) { + processApi.stderr('usage: rm [-rf] file\n'); + // Technical limitation of subprocesses – we need to + // wait before exiting to give consumer a chance to read + // the output. + await wait(10); + processApi.exit(1); + break; + } + + const isRecursive = flags.includes('r'); + const isForce = flags.includes('f'); + + let errorMessage = ''; + + if (await php.isDir(target)) { + if (isRecursive) { + await php.rmdir(target, { recursive: true }); + } else { + errorMessage = `rm: cannot remove '${target}': Is a directory\n`; + } + } else if (await php.isFile(target)) { + await php.unlink(target); + } + // Target doesn't exist and -f flag is not set + else if (!isForce) { + errorMessage = `rm: cannot remove '${target}': No such file or directory\n`; + } + + if (errorMessage) { + processApi.stderr(errorMessage); + // Technical limitation of subprocesses – we need to + // wait before exiting to give consumer a chance to read + // the output. + await wait(10); + } + + processApi.exit(errorMessage ? 1 : 0); + break; + } } } catch (e) { // An exception here means the PHP runtime has crashed.