1package main23// GOMEMLIMIT4// runtime/debug.SetMemoryLimit56import (7 "context"8 "errors"9 "flag"10 "fmt"11 "io"12 "io/fs"13 "os"14 "regexp"15 "runtime"16 "slices"17 "strings"1819 "mvdan.cc/sh/v3/expand"20 "mvdan.cc/sh/v3/interp"21 "mvdan.cc/sh/v3/syntax"22)2324var command = flag.String("c", "", "command to be executed")25var fname = flag.String("f", "", "")26var validShellKey = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)27var ignoredEnvs = []string{28 "PWD", "OLDPWD", "PATH", "HOME",29 "UID", "GID", "EUID", "PPID",30 "SHELL", "IFS", "OPTIND",31}3233func main() {34 flag.Parse()3536 r, err := interp.New(37 interp.Env(expand.ListEnviron()),38 interp.Interactive(false),39 // interp.StdIO(os.Stdin, os.Stdout, os.Stderr),40 interp.ExecHandlers(noExec),41 interp.OpenHandler(noOpen),42 interp.ReadDirHandler2(noReadDir),43 interp.StatHandler(noStat),44 )4546 if err != nil {47 panic(err)48 }4950 err = runAll(r)51 if es, ok := errors.AsType[interp.ExitStatus](err); ok {52 os.Exit(int(es))53 }54 if err != nil {55 fmt.Fprintln(os.Stderr, err)56 os.Exit(1)57 }5859 for k, v := range r.Vars {60 if !validShellKey.MatchString(k) {61 continue62 }63 if v.Local {64 continue65 }6667 if slices.ContainsFunc(ignoredEnvs, func(envname string) bool {68 if runtime.GOOS == "windows" {69 return strings.ToUpper(k) == envname70 }71 return k == envname72 }) {73 continue74 }7576 var s strings.Builder77 if v.Exported {78 s.WriteString("export ")79 }80 s.WriteString(k + "=")81 s.WriteString("'" + strings.ReplaceAll(v.String(), "'", "'\\''") + "'")82 fmt.Println(s.String())83 }84}8586func noExec(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {87 return func(_ context.Context, args []string) error {88 cmd := ""89 if len(args) > 0 {90 cmd = args[0]91 }92 return fmt.Errorf("exec `%s` is not allowed", cmd)93 }94}9596func noOpen(_ context.Context, _ string, _ int, _ os.FileMode) (io.ReadWriteCloser, error) {97 return nil, fmt.Errorf("open is not allowed")98}99func noReadDir(_ context.Context, _ string) ([]fs.DirEntry, error) {100 return nil, fmt.Errorf("read dir is not allowed")101}102func noStat(_ context.Context, _ string, _ bool) (fs.FileInfo, error) {103 return nil, fmt.Errorf("read files' info is not allowed")104}105106func runAll(r *interp.Runner) error {107 if *command != "" {108 if err := run(r, strings.NewReader(*command), ""); err != nil {109 return err110 }111 } else if flag.NArg() == 0 {112 if err := run(r, os.Stdin, ""); err != nil {113 return err114 }115 } else {116 for _, path := range flag.Args() {117 if err := runPath(r, path); err != nil {118 return err119 }120 }121 }122123 if *fname != "" {124 fn, ok := r.Funcs[*fname]125 if !ok {126 return fmt.Errorf("function %s undefined", *fname)127 }128129 return runNode(r, fn)130 }131 return nil132}133134func run(r *interp.Runner, reader io.Reader, name string) error {135 prog, err := syntax.NewParser().Parse(reader, name)136 if err != nil {137 return err138 }139 r.Reset()140 ctx := context.Background()141 return r.Run(ctx, prog)142}143144func runNode(r *interp.Runner, n syntax.Node) error {145 ctx := context.Background()146 return r.Run(ctx, n)147}148149func runPath(r *interp.Runner, path string) error {150 f, err := os.Open(path)151 if err != nil {152 return err153 }154 defer f.Close()155 return run(r, f, path)156}