@@ -57,12 +57,13 @@ export const DEFAULT_SHELL_CANDIDATES = Object.freeze([
|
|||||||
/**
|
/**
|
||||||
* One-shot script: from a working sh, pick the best available interactive shell.
|
* 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.
|
* 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 = [
|
const SHELL_PROBE_SCRIPT = [
|
||||||
'for s in /bin/bash /usr/bin/bash bash /bin/zsh /usr/bin/zsh zsh',
|
'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',
|
||||||
'/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 [ -x "$s" ]; then exec "$s"; fi',
|
||||||
' if command -v "$s" >/dev/null 2>&1; then exec "$s"; fi',
|
' if command -v "$s" >/dev/null 2>&1; then exec "$s"; fi',
|
||||||
'done',
|
'done',
|
||||||
@@ -73,7 +74,7 @@ const SHELL_PROBE_SCRIPT = [
|
|||||||
'if [ -x /bin/sh ]; then exec /bin/sh; fi',
|
'if [ -x /bin/sh ]; then exec /bin/sh; fi',
|
||||||
'if [ -x /usr/bin/sh ]; then exec /usr/bin/sh; fi',
|
'if [ -x /usr/bin/sh ]; then exec /usr/bin/sh; fi',
|
||||||
'exit 127',
|
'exit 127',
|
||||||
].join(' ')
|
].join('\n')
|
||||||
|
|
||||||
/** Bootstrap wrappers that run SHELL_PROBE_SCRIPT. */
|
/** Bootstrap wrappers that run SHELL_PROBE_SCRIPT. */
|
||||||
const PROBE_WRAPPERS = Object.freeze([
|
const PROBE_WRAPPERS = Object.freeze([
|
||||||
|
|||||||
@@ -7,6 +7,21 @@ import {
|
|||||||
buildShellCandidates,
|
buildShellCandidates,
|
||||||
} from '../server/handlers/terminal.js'
|
} 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) => {
|
test('DEFAULT_SHELL_CANDIDATES includes bash, sh, ash, busybox', (t) => {
|
||||||
const flat = DEFAULT_SHELL_CANDIDATES.map((c) => c.join(' '))
|
const flat = DEFAULT_SHELL_CANDIDATES.map((c) => c.join(' '))
|
||||||
t.ok(flat.some((s) => s.includes('bash')))
|
t.ok(flat.some((s) => s.includes('bash')))
|
||||||
|
|||||||
Reference in New Issue
Block a user