Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
39 lines
1.1 KiB
Go
39 lines
1.1 KiB
Go
package sandbox
|
|
|
|
import "strings"
|
|
|
|
// ShellQuote renders s as one literal word for /bin/sh.
|
|
//
|
|
// Single quotes are the only construct that keeps every metacharacter inert:
|
|
// inside double quotes `$`, backtick and `\` still expand, so a path chosen by
|
|
// an uploaded archive would execute as a command. The embedded-quote escape is
|
|
// the usual '\” dance. Non-ASCII bytes are passed through unchanged, so a CJK
|
|
// file name still names the same file inside the sandbox.
|
|
func ShellQuote(s string) string {
|
|
if s == "" {
|
|
return "''"
|
|
}
|
|
// Only bare tokens (alnum, dash, underscore, slash, dot, comma, colon,
|
|
// equals, plus) can be passed unquoted; everything else gets single
|
|
// quotes.
|
|
if isShellSafe(s) {
|
|
return s
|
|
}
|
|
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
|
|
}
|
|
|
|
func isShellSafe(s string) bool {
|
|
for _, r := range s {
|
|
switch {
|
|
case r >= 'a' && r <= 'z':
|
|
case r >= 'A' && r <= 'Z':
|
|
case r >= '0' && r <= '9':
|
|
case r == '-' || r == '_' || r == '/' || r == '.' || r == ',' ||
|
|
r == ':' || r == '=' || r == '+':
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|