@@ -57,12 +57,13 @@ export const DEFAULT_SHELL_CANDIDATES = Object.freeze([
|
||||
/**
|
||||
* One-shot script: from a working sh, pick the best available interactive shell.
|
||||
* Used when /bin/sh (or equivalent) exists but we still want bash if present.
|
||||
*
|
||||
* Must be valid when passed to `sh -c`. Join with newlines (not spaces):
|
||||
* space-joining turns `done\\nfor` into `done for`, which dash/sh reject
|
||||
* as `Syntax error: "if" unexpected (expecting "done")`.
|
||||
*/
|
||||
const SHELL_PROBE_SCRIPT = [
|
||||
'for s in /bin/bash /usr/bin/bash bash /bin/zsh /usr/bin/zsh zsh',
|
||||
'/bin/ash /usr/bin/ash ash /bin/dash /usr/bin/dash dash',
|
||||
'/bin/ksh /usr/bin/ksh ksh /bin/mksh /usr/bin/mksh mksh',
|
||||
'/bin/fish /usr/bin/fish fish; do',
|
||||
'for s in /bin/bash /usr/bin/bash bash /bin/zsh /usr/bin/zsh zsh /bin/ash /usr/bin/ash ash /bin/dash /usr/bin/dash dash /bin/ksh /usr/bin/ksh ksh /bin/mksh /usr/bin/mksh mksh /bin/fish /usr/bin/fish fish; do',
|
||||
' if [ -x "$s" ]; then exec "$s"; fi',
|
||||
' if command -v "$s" >/dev/null 2>&1; then exec "$s"; fi',
|
||||
'done',
|
||||
@@ -73,7 +74,7 @@ const SHELL_PROBE_SCRIPT = [
|
||||
'if [ -x /bin/sh ]; then exec /bin/sh; fi',
|
||||
'if [ -x /usr/bin/sh ]; then exec /usr/bin/sh; fi',
|
||||
'exit 127',
|
||||
].join(' ')
|
||||
].join('\n')
|
||||
|
||||
/** Bootstrap wrappers that run SHELL_PROBE_SCRIPT. */
|
||||
const PROBE_WRAPPERS = Object.freeze([
|
||||
|
||||
@@ -7,6 +7,21 @@ import {
|
||||
buildShellCandidates,
|
||||
} from '../server/handlers/terminal.js'
|
||||
|
||||
test('probe wrapper -c script keeps newlines (valid sh -c)', (t) => {
|
||||
const list = buildShellCandidates({})
|
||||
const probe = list.find((c) => c.includes('-c') && c.some((p) => String(p).includes('exec')))
|
||||
t.ok(probe, 'probe candidate exists')
|
||||
const script = probe.find(
|
||||
(p) => typeof p === 'string' && p.includes('exec') && p.includes('for s in')
|
||||
)
|
||||
t.ok(script, 'probe script payload present')
|
||||
t.ok(String(script).includes('\n'), 'script uses newlines between statements')
|
||||
// Space-joined scripts become `done for` / `done if` and break dash/sh -c
|
||||
t.absent(/\bdone for\b/.test(String(script)))
|
||||
t.absent(/\bdone if\b/.test(String(script)))
|
||||
t.ok(/\ndone\nfor\b/.test(String(script)), 'second loop follows done on a new line')
|
||||
})
|
||||
|
||||
test('DEFAULT_SHELL_CANDIDATES includes bash, sh, ash, busybox', (t) => {
|
||||
const flat = DEFAULT_SHELL_CANDIDATES.map((c) => c.join(' '))
|
||||
t.ok(flat.some((s) => s.includes('bash')))
|
||||
|
||||
Reference in New Issue
Block a user