From bc235d791ecf9a5e1a027df04e8459664bda13f1 Mon Sep 17 00:00:00 2001 From: Utsav-Ladani <201901076@daiict.ac.in> Date: Wed, 10 Dec 2025 14:41:06 +0530 Subject: [PATCH 1/2] [php-wasm] Add support for `rm` command in sandboxed spawn handler --- .../src/lib/sandboxed-spawn-handler-factory.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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..0b95dbfe04 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 @@ -81,7 +81,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; @@ -155,6 +155,20 @@ export function sandboxedSpawnHandlerFactory( processApi.exit(0); break; } + case 'rm': { + const target = args[args.length - 1]; + if (await php.isDir(target)) { + await php.rmdir(target, { recursive: true }); + } else if (await php.isFile(target)) { + await php.unlink(target); + } + // 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)); + processApi.exit(0); + break; + } } } catch (e) { // An exception here means the PHP runtime has crashed. From 89d198e5bafd69cc274d8f6c615a38a0cd60a1c2 Mon Sep 17 00:00:00 2001 From: Utsav-Ladani <201901076@daiict.ac.in> Date: Fri, 12 Dec 2025 14:06:58 +0530 Subject: [PATCH 2/2] Add support for `-r` and `-f` command flags --- .../lib/sandboxed-spawn-handler-factory.ts | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) 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 0b95dbfe04..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 @@ -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,22 +155,52 @@ 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)) { - await php.rmdir(target, { recursive: true }); + 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); } - // 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)); - processApi.exit(0); + // 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; } }