1
0
mirror of https://github.com/go-task/task.git synced 2025-06-06 23:46:46 +02:00

96 lines
2.6 KiB
Go
Raw Normal View History

2018-07-22 18:05:13 -03:00
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package shell
import (
"context"
"fmt"
"io"
"os"
"time"
"mvdan.cc/sh/interp"
"mvdan.cc/sh/syntax"
)
// SourceFile sources a shell file from disk and returns the variables
// declared in it.
//
// A default parser is used; to set custom options, use SourceNode
// instead.
func SourceFile(path string) (map[string]interp.Variable, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("could not open: %v", err)
}
defer f.Close()
p := syntax.NewParser()
file, err := p.Parse(f, path)
if err != nil {
return nil, fmt.Errorf("could not parse: %v", err)
}
return SourceNode(file)
}
// purePrograms holds a list of common programs that do not have side
// effects, or otherwise cannot modify or harm the system that runs
// them.
var purePrograms = []string{
// string handling
"sed", "grep", "tr", "cut", "cat", "head", "tail", "seq", "yes",
"wc",
// paths
"ls", "pwd", "basename", "realpath",
// others
"env", "sleep", "uniq", "sort",
}
var pureRunnerTimeout = 2 * time.Second
func pureRunner() *interp.Runner {
// forbid executing programs that might cause trouble
2018-09-01 11:00:49 -03:00
exec := interp.ModuleExec(func(ctx context.Context, path string, args []string) error {
2018-07-22 18:05:13 -03:00
for _, name := range purePrograms {
if args[0] == name {
return interp.DefaultExec(ctx, path, args)
}
}
return fmt.Errorf("program not in whitelist: %s", args[0])
2018-09-01 11:00:49 -03:00
})
2018-07-22 18:05:13 -03:00
// forbid opening any real files
2018-09-01 11:00:49 -03:00
open := interp.OpenDevImpls(func(ctx context.Context, path string, flags int, mode os.FileMode) (io.ReadWriteCloser, error) {
mc, _ := interp.FromModuleContext(ctx)
return nil, fmt.Errorf("cannot open path: %s", mc.UnixPath(path))
2018-07-22 18:05:13 -03:00
})
2018-09-01 11:00:49 -03:00
r, err := interp.New(interp.Module(exec), interp.Module(open))
if err != nil {
panic(err)
}
2018-07-22 18:05:13 -03:00
return r
}
// SourceNode sources a shell program from a node and returns the
// variables declared in it.
//
// Any side effects or modifications to the system are forbidden when
// interpreting the program. This is enforced via whitelists when
// executing programs and opening paths. The interpreter also has a timeout of
// two seconds.
func SourceNode(node syntax.Node) (map[string]interp.Variable, error) {
r := pureRunner()
ctx, cancel := context.WithTimeout(context.Background(), pureRunnerTimeout)
defer cancel()
2018-09-01 11:00:49 -03:00
if err := r.Run(ctx, node); err != nil {
2018-07-22 18:05:13 -03:00
return nil, fmt.Errorf("could not run: %v", err)
}
// delete the internal shell vars that the user is not
// interested in
delete(r.Vars, "PWD")
delete(r.Vars, "HOME")
delete(r.Vars, "PATH")
delete(r.Vars, "IFS")
delete(r.Vars, "OPTIND")
return r.Vars, nil
}