<commit_before>package server
import (
	"net/http"
	"regexp"
	"os/user"
	"os/exec"
	"servant/conf"
	//"io/ioutil"
	"time"
	"syscall"
	"strconv"
	"io"
	"io/ioutil"
)

var argRe, _ = regexp.Compile(`("[^"]*"|'[^']*'|[^\s"']+)`)

type CommandServer struct {
	*Session
}

func NewCommandServer(sess *Session) Handler {
	return CommandServer{
		Session:sess,
	}
}

func (self CommandServer) findCommandConfig() *conf.Command {
	cmdsConf, ok := self.config.Commands[self.group]
	if !ok {
		return nil
	}
	cmdConf, ok := cmdsConf.Commands[self.item]
	if !ok {
		return nil
	}
	return cmdConf
}

func getCmdBashArgs(code string, query ParamFunc) (string, []string) {
	return "bash", []string{"-c", code}
}

// TODO: return error when arg miss
func replaceCmdParams(arg string, query ParamFunc) string {
	return paramRe.ReplaceAllStringFunc(arg, func(s string) string {
		if query == nil {
			return ""
		}
		ret, _ := query(s[2:len(s) - 1])
		return ret
	})
}
func getCmdExecArgs(code string, query ParamFunc) (string, []string) {
	argsMatches := argRe.FindAllStringSubmatch(code, -1)
	args := make([]string, 0, 4)
	for i := 0; i < len(argsMatches); i++ {
		arg := argsMatches[i][1]
		if arg[0] == '\'' || arg[0] == '"' {
			arg = arg[1 : len(arg)-1]
		}
		arg = replaceCmdParams(arg, query)
		args = append(args, arg)
	}
	return args[0], args[1:]
}

func (self CommandServer) serve() {
	urlPath := self.req.URL.Path
	method := self.req.Method

	if method != "GET" && method != "POST" {
		self.ErrorEnd(http.StatusMethodNotAllowed, "not allow method: %s", method)
		return
	}
	cmdConf := self.findCommandConfig()
	if cmdConf == nil {
		self.ErrorEnd(http.StatusNotFound, "command %s not found", urlPath)
		self.resp.WriteHeader(http.StatusNotFound)
		return
	}
	if cmdConf.Lock.Name == "" {
		self.serveCommand(cmdConf)
	} else {
		if cmdConf.Lock.Wait {
			GetLock(cmdConf.Lock.Name).TimeoutWith(time.Duration(cmdConf.Lock.Timeout) * time.Second, func() {
				self.serveCommand(cmdConf)
			})
		} else {
			GetLock(cmdConf.Lock.Name).TryWith(func(){
				self.serveCommand(cmdConf)
			})
		}
	}
}

func setCmdUser(cmd *exec.Cmd, username string) error {
	sysUser, err := user.Lookup(username)
	if err != nil {
		return err
	}
	uid , err := strconv.Atoi(sysUser.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(sysUser.Gid)
	if err != nil {
		return err
	}
	cred := syscall.Credential{ Uid: uint32(uid), Gid: uint32(gid) }

	cmd.SysProcAttr.Credential = &cred
	return nil
}

func (self CommandServer) serveCommand(cmdConf *conf.Command) {
	outBuf, err := self.execCommand(cmdConf)
	if err != nil {
		self.ErrorEnd(err.(ServantError).HttpCode, err.(ServantError).Message)
		return
	}
	_, err = self.resp.Write(outBuf) // may log errors
	if err != nil {
		self.BadEnd("io error: %s", err)
	} else {
		self.GoodEnd("execution done")
	}
}


func cmdFromConf(cmdConf *conf.Command, params ParamFunc, input io.ReadCloser) (cmd *exec.Cmd, out io.ReadCloser, err error) {
	var name string
	var args []string
	if !ValidateParams(cmdConf.Validators, params) {
		return nil, nil, NewServantError(http.StatusBadRequest, "validate params failed")
	}
	switch cmdConf.Lang {
	case "exec":
		name, args = getCmdExecArgs(cmdConf.Code, params)
	case "bash", "":
		name, args = getCmdBashArgs(cmdConf.Code, params)
	default:
		err = NewServantError(http.StatusInternalServerError, "unknown language")
		return
	}
	cmd = exec.Command(name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.Dir = "/"
	if cmdConf.User != "" {
		err = setCmdUser(cmd, cmdConf.User)
		if err != nil {<commit_msg>protect when command code is empty<commit_after>package server
import (
	"net/http"
	"regexp"
	"os/user"
	"os/exec"
	"servant/conf"
	//"io/ioutil"
	"time"
	"syscall"
	"strconv"
	"io"
	"io/ioutil"
	"strings"
)

var argRe, _ = regexp.Compile(`("[^"]*"|'[^']*'|[^\s"']+)`)

type CommandServer struct {
	*Session
}

func NewCommandServer(sess *Session) Handler {
	return CommandServer{
		Session:sess,
	}
}

func (self CommandServer) findCommandConfig() *conf.Command {
	cmdsConf, ok := self.config.Commands[self.group]
	if !ok {
		return nil
	}
	cmdConf, ok := cmdsConf.Commands[self.item]
	if !ok {
		return nil
	}
	return cmdConf
}

func getCmdBashArgs(code string, query ParamFunc) (string, []string) {
	return "bash", []string{"-c", code}
}

// TODO: return error when arg miss
func replaceCmdParams(arg string, query ParamFunc) string {
	return paramRe.ReplaceAllStringFunc(arg, func(s string) string {
		if query == nil {
			return ""
		}
		ret, _ := query(s[2:len(s) - 1])
		return ret
	})
}
func getCmdExecArgs(code string, query ParamFunc) (string, []string) {
	argsMatches := argRe.FindAllStringSubmatch(code, -1)
	args := make([]string, 0, 4)
	for i := 0; i < len(argsMatches); i++ {
		arg := argsMatches[i][1]
		if arg[0] == '\'' || arg[0] == '"' {
			arg = arg[1 : len(arg)-1]
		}
		arg = replaceCmdParams(arg, query)
		args = append(args, arg)
	}
	return args[0], args[1:]
}

func (self CommandServer) serve() {
	urlPath := self.req.URL.Path
	method := self.req.Method

	if method != "GET" && method != "POST" {
		self.ErrorEnd(http.StatusMethodNotAllowed, "not allow method: %s", method)
		return
	}
	cmdConf := self.findCommandConfig()
	if cmdConf == nil {
		self.ErrorEnd(http.StatusNotFound, "command %s not found", urlPath)
		self.resp.WriteHeader(http.StatusNotFound)
		return
	}
	if cmdConf.Lock.Name == "" {
		self.serveCommand(cmdConf)
	} else {
		if cmdConf.Lock.Wait {
			GetLock(cmdConf.Lock.Name).TimeoutWith(time.Duration(cmdConf.Lock.Timeout) * time.Second, func() {
				self.serveCommand(cmdConf)
			})
		} else {
			GetLock(cmdConf.Lock.Name).TryWith(func(){
				self.serveCommand(cmdConf)
			})
		}
	}
}

func setCmdUser(cmd *exec.Cmd, username string) error {
	sysUser, err := user.Lookup(username)
	if err != nil {
		return err
	}
	uid , err := strconv.Atoi(sysUser.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(sysUser.Gid)
	if err != nil {
		return err
	}
	cred := syscall.Credential{ Uid: uint32(uid), Gid: uint32(gid) }

	cmd.SysProcAttr.Credential = &cred
	return nil
}

func (self CommandServer) serveCommand(cmdConf *conf.Command) {
	outBuf, err := self.execCommand(cmdConf)
	if err != nil {
		self.ErrorEnd(err.(ServantError).HttpCode, err.(ServantError).Message)
		return
	}
	_, err = self.resp.Write(outBuf) // may log errors
	if err != nil {
		self.BadEnd("io error: %s", err)
	} else {
		self.GoodEnd("execution done")
	}
}


func cmdFromConf(cmdConf *conf.Command, params ParamFunc, input io.ReadCloser) (cmd *exec.Cmd, out io.ReadCloser, err error) {
	var name string
	var args []string
	if !ValidateParams(cmdConf.Validators, params) {
		return nil, nil, NewServantError(http.StatusBadRequest, "validate params failed")
	}
	code := strings.TrimSpace(cmdConf.Code)
	if code == "" {
		return nil, nil, NewServantError(http.StatusInternalServerError, "command code is empty")
	}
	switch cmdConf.Lang {
	case "exec":
		name, args = getCmdExecArgs(code, params)
	case "bash", "":
		name, args = getCmdBashArgs(code, params)
	default:
		err = NewServantError(http.StatusInternalServerError, "unknown language")
		return
	}
	cmd = exec.Command(name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.Dir = "/"
	if cmdConf.User != "" {
		err = setCmdUser(cmd, cmdConf.User)
		if err != nil {
{'fraction_non_alphanumeric': 0.15755758484394167, 'fraction_numerical': 0.004634046613057108, 'mean_word_length': 4.085239085239086, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '791668', 'n_tokens_mistral': 2889, 'n_tokens_neox': 2747, 'n_words': 620}