This commit is contained in:
@@ -87,6 +87,230 @@ function bareOsEmitRaw(ctx, chunk) {
|
||||
return false
|
||||
}
|
||||
|
||||
/** Session env map (`vfs.env`, then `ctx.env`). Never throws. */
|
||||
function bareOsEnv(ctx) {
|
||||
const v = ctx && ctx.vfs && ctx.vfs.env
|
||||
if (v && typeof v === 'object') return v
|
||||
const e = ctx && ctx.env
|
||||
if (e && typeof e === 'object') return e
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strict POSIX-ish decimal integer (no octal, no exponent, no empty).
|
||||
* @param {unknown} s
|
||||
* @returns {number}
|
||||
*/
|
||||
function bareOsParseDecInt(s) {
|
||||
const t = String(s == null ? '' : s).trim()
|
||||
if (!/^[+-]?(?:0|[1-9][0-9]*)$/.test(t)) return NaN
|
||||
const n = Number.parseInt(t, 10)
|
||||
return Number.isSafeInteger(n) ? n : NaN
|
||||
}
|
||||
|
||||
/** @param {unknown} s */
|
||||
function bareOsParseNonNegInt(s) {
|
||||
const n = bareOsParseDecInt(s)
|
||||
return n >= 0 ? n : NaN
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} name
|
||||
* @param {number} fallback
|
||||
* @param {number} [min]
|
||||
* @param {number} [max]
|
||||
*/
|
||||
function bareOsEnvInt(ctx, name, fallback, min, max) {
|
||||
const raw = bareOsEnv(ctx)[name]
|
||||
if (raw == null || raw === '') return fallback
|
||||
const n = Number.parseInt(String(raw), 10)
|
||||
if (!Number.isFinite(n)) return fallback
|
||||
let v = n
|
||||
if (min != null && v < min) v = min
|
||||
if (max != null && v > max) v = max
|
||||
return v
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} msg
|
||||
* @param {number} [code]
|
||||
*/
|
||||
function bareOsFail(ctx, msg, code) {
|
||||
if (msg) ctx.console.error(msg)
|
||||
ctx.exitCode = code == null ? 1 : code
|
||||
}
|
||||
|
||||
/** @param {unknown} e */
|
||||
function bareOsIsNotFoundErr(e) {
|
||||
const code = e && typeof e === 'object' ? e.code : ''
|
||||
if (code === 'ENOENT') return true
|
||||
const msg = String((e && e.message) || e || '')
|
||||
return /ENOENT|No such file|not found/i.test(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {unknown} buf
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function bareOsToU8(ctx, buf) {
|
||||
if (!buf) return new Uint8Array(0)
|
||||
if (buf instanceof Uint8Array) return buf
|
||||
if (ctx && ctx.b4a && typeof ctx.b4a.from === 'function') return ctx.b4a.from(buf)
|
||||
return new Uint8Array(buf)
|
||||
}
|
||||
|
||||
/** @param {string} dir @param {string} name */
|
||||
function bareOsJoinPath(dir, name) {
|
||||
const d = String(dir || '').replace(/\/+$/, '')
|
||||
const n = String(name || '').replace(/^\/+/, '')
|
||||
if (!d || d === '/') return '/' + n
|
||||
return d + '/' + n
|
||||
}
|
||||
|
||||
/** @param {string} p */
|
||||
function bareOsBaseName(p) {
|
||||
const t = String(p || '').replace(/\/+$/, '')
|
||||
if (!t || t === '/') return t === '/' ? '/' : ''
|
||||
const i = t.lastIndexOf('/')
|
||||
return i < 0 ? t : t.slice(i + 1) || t
|
||||
}
|
||||
|
||||
/** @param {string} p */
|
||||
function bareOsParentDir(p) {
|
||||
const t = String(p || '').replace(/\/+$/, '') || '/'
|
||||
if (t === '/') return '/'
|
||||
const i = t.lastIndexOf('/')
|
||||
return i <= 0 ? '/' : t.slice(0, i) || '/'
|
||||
}
|
||||
|
||||
/** @param {string} p */
|
||||
function bareOsNormPath(p) {
|
||||
return String(p || '').replace(/\/+$/, '') || '/'
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} p
|
||||
*/
|
||||
function bareOsResolvePath(ctx, p) {
|
||||
if (ctx && ctx.vfs && typeof ctx.vfs.resolveLogical === 'function') {
|
||||
try {
|
||||
return String(ctx.vfs.resolveLogical(p) || p)
|
||||
} catch {
|
||||
/* fall through */
|
||||
}
|
||||
}
|
||||
return String(p || '')
|
||||
}
|
||||
|
||||
/**
|
||||
* True when dest is src or lives under src (self-copy / self-move).
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} src
|
||||
* @param {string} dest
|
||||
*/
|
||||
function bareOsDestInsideSrc(ctx, src, dest) {
|
||||
const s = bareOsNormPath(bareOsResolvePath(ctx, src))
|
||||
const d = bareOsNormPath(bareOsResolvePath(ctx, dest))
|
||||
if (s === d) return true
|
||||
if (s === '/') return d !== '/'
|
||||
return d === s || d.startsWith(s + '/')
|
||||
}
|
||||
|
||||
const BARE_OS_B64_ALPH =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
|
||||
/** @param {Uint8Array} u8 */
|
||||
function bareOsB64Encode(u8) {
|
||||
let out = ''
|
||||
let i = 0
|
||||
for (; i + 2 < u8.length; i += 3) {
|
||||
const n = (u8[i] << 16) | (u8[i + 1] << 8) | u8[i + 2]
|
||||
out +=
|
||||
BARE_OS_B64_ALPH[(n >> 18) & 63] +
|
||||
BARE_OS_B64_ALPH[(n >> 12) & 63] +
|
||||
BARE_OS_B64_ALPH[(n >> 6) & 63] +
|
||||
BARE_OS_B64_ALPH[n & 63]
|
||||
}
|
||||
const rest = u8.length - i
|
||||
if (rest === 1) {
|
||||
const n = u8[i] << 16
|
||||
out += BARE_OS_B64_ALPH[(n >> 18) & 63] + BARE_OS_B64_ALPH[(n >> 12) & 63] + '=='
|
||||
} else if (rest === 2) {
|
||||
const n = (u8[i] << 16) | (u8[i + 1] << 8)
|
||||
out +=
|
||||
BARE_OS_B64_ALPH[(n >> 18) & 63] +
|
||||
BARE_OS_B64_ALPH[(n >> 12) & 63] +
|
||||
BARE_OS_B64_ALPH[(n >> 6) & 63] +
|
||||
'='
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC 4648 Base64 decode (also accepts URL-safe alphabet). Rejects junk.
|
||||
* @param {string} s
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function bareOsB64Decode(s) {
|
||||
const t = String(s).replace(/\s+/g, '')
|
||||
if (!t) return new Uint8Array(0)
|
||||
if (t.length % 4 === 1) throw new Error('invalid base64 length')
|
||||
let pad = 0
|
||||
if (t.endsWith('==')) pad = 2
|
||||
else if (t.endsWith('=')) pad = 1
|
||||
const body = pad ? t.slice(0, t.length - pad) : t
|
||||
const bytes = []
|
||||
let buf = 0
|
||||
let bits = 0
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const c = body[i]
|
||||
let v = BARE_OS_B64_ALPH.indexOf(c)
|
||||
if (v < 0) {
|
||||
if (c === '-') v = 62
|
||||
else if (c === '_') v = 63
|
||||
else throw new Error('invalid base64 character')
|
||||
}
|
||||
buf = (buf << 6) | v
|
||||
bits += 6
|
||||
if (bits >= 8) {
|
||||
bits -= 8
|
||||
bytes.push((buf >> bits) & 255)
|
||||
}
|
||||
}
|
||||
if (pad) {
|
||||
const want = Math.floor((body.length * 6) / 8)
|
||||
if (bytes.length > want) bytes.length = want
|
||||
}
|
||||
return new Uint8Array(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function bareOsHexDecode(s) {
|
||||
const t = String(s).replace(/\s+/g, '')
|
||||
if (t.length % 2 !== 0) throw new Error('odd hex length')
|
||||
const out = new Uint8Array(t.length / 2)
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
const pair = t.slice(i * 2, i * 2 + 2)
|
||||
if (!/^[0-9a-fA-F]{2}$/.test(pair)) throw new Error('invalid hex')
|
||||
out[i] = Number.parseInt(pair, 16)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** @param {Uint8Array} u8 */
|
||||
function bareOsHexEncode(u8) {
|
||||
let s = ''
|
||||
for (let i = 0; i < u8.length; i++) s += u8[i].toString(16).padStart(2, '0')
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain-stream masked line input (same pattern as agent --config / bareAgentPromptSetupLine).
|
||||
* Used by /bin/login so the Fish REPL is suspended and stdin/stdout are the TTY streams.
|
||||
|
||||
Reference in New Issue
Block a user