Limit the maximum size of string/bytes values (#121)
* add tengo.MaxStringLen and tengo.MaxBytesLen to limit the maximum byte-length of string/bytes values * add couple more tests
This commit is contained in:
parent
880ee04ffe
commit
0c5e80b057
22 changed files with 600 additions and 96 deletions
|
@ -5,6 +5,7 @@ import (
|
|||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/compiler/ast"
|
||||
"github.com/d5/tengo/compiler/source"
|
||||
"github.com/d5/tengo/compiler/token"
|
||||
|
@ -195,6 +196,10 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||
}
|
||||
|
||||
case *ast.StringLit:
|
||||
if len(node.Value) > tengo.MaxStringLen {
|
||||
return c.error(node, objects.ErrStringLimit)
|
||||
}
|
||||
|
||||
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value}))
|
||||
|
||||
case *ast.CharLit:
|
||||
|
@ -332,6 +337,9 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||
case *ast.MapLit:
|
||||
for _, elt := range node.Elements {
|
||||
// key
|
||||
if len(elt.Key) > tengo.MaxStringLen {
|
||||
return c.error(node, objects.ErrStringLimit)
|
||||
}
|
||||
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key}))
|
||||
|
||||
// value
|
||||
|
@ -507,6 +515,10 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||
|
||||
case *ast.ImportExpr:
|
||||
if c.builtinModules[node.ModuleName] {
|
||||
if len(node.ModuleName) > tengo.MaxStringLen {
|
||||
return c.error(node, objects.ErrStringLimit)
|
||||
}
|
||||
|
||||
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName}))
|
||||
c.emit(node, OpGetBuiltinModule)
|
||||
} else {
|
||||
|
@ -610,6 +622,14 @@ func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *Symbo
|
|||
return child
|
||||
}
|
||||
|
||||
func (c *Compiler) error(node ast.Node, err error) error {
|
||||
return &Error{
|
||||
fileSet: c.file.Set(),
|
||||
node: node,
|
||||
error: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error {
|
||||
return &Error{
|
||||
fileSet: c.file.Set(),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package objects
|
||||
|
||||
import "github.com/d5/tengo"
|
||||
|
||||
func builtinString(args ...Object) (Object, error) {
|
||||
argsLen := len(args)
|
||||
if !(argsLen == 1 || argsLen == 2) {
|
||||
|
@ -12,6 +14,10 @@ func builtinString(args ...Object) (Object, error) {
|
|||
|
||||
v, ok := ToString(args[0])
|
||||
if ok {
|
||||
if len(v) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
|
||||
return &String{Value: v}, nil
|
||||
}
|
||||
|
||||
|
@ -117,11 +123,19 @@ func builtinBytes(args ...Object) (Object, error) {
|
|||
|
||||
// bytes(N) => create a new bytes with given size N
|
||||
if n, ok := args[0].(*Int); ok {
|
||||
if n.Value > int64(tengo.MaxBytesLen) {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
|
||||
return &Bytes{Value: make([]byte, int(n.Value))}, nil
|
||||
}
|
||||
|
||||
v, ok := ToByteSlice(args[0])
|
||||
if ok {
|
||||
if len(v) > tengo.MaxBytesLen {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
|
||||
return &Bytes{Value: v}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package objects
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
)
|
||||
|
||||
// to_json(v object) => bytes
|
||||
|
@ -15,6 +17,10 @@ func builtinToJSON(args ...Object) (Object, error) {
|
|||
return &Error{Value: &String{Value: err.Error()}}, nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxBytesLen {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
|
||||
return &Bytes{Value: res}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package objects
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
)
|
||||
|
||||
// print(args...)
|
||||
|
@ -71,5 +73,11 @@ func builtinSprintf(args ...Object) (Object, error) {
|
|||
formatArgs[idx] = objectToInterface(arg)
|
||||
}
|
||||
|
||||
return &String{Value: fmt.Sprintf(format.Value, formatArgs...)}, nil
|
||||
s := fmt.Sprintf(format.Value, formatArgs...)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
|
||||
return &String{Value: s}, nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package objects
|
|||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/compiler/token"
|
||||
)
|
||||
|
||||
|
@ -27,6 +28,10 @@ func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
|||
case token.Add:
|
||||
switch rhs := rhs.(type) {
|
||||
case *Bytes:
|
||||
if len(o.Value)+len(rhs.Value) > tengo.MaxBytesLen {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
|
||||
return &Bytes{Value: append(o.Value, rhs.Value...)}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
)
|
||||
|
||||
// ToString will try to convert object o to string value.
|
||||
|
@ -194,6 +196,9 @@ func FromInterface(v interface{}) (Object, error) {
|
|||
case nil:
|
||||
return UndefinedValue, nil
|
||||
case string:
|
||||
if len(v) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
return &String{Value: v}, nil
|
||||
case int64:
|
||||
return &Int{Value: v}, nil
|
||||
|
@ -211,6 +216,9 @@ func FromInterface(v interface{}) (Object, error) {
|
|||
case float64:
|
||||
return &Float{Value: v}, nil
|
||||
case []byte:
|
||||
if len(v) > tengo.MaxBytesLen {
|
||||
return nil, ErrBytesLimit
|
||||
}
|
||||
return &Bytes{Value: v}, nil
|
||||
case error:
|
||||
return &Error{Value: &String{Value: v.Error()}}, nil
|
||||
|
|
|
@ -20,6 +20,12 @@ var ErrInvalidOperator = errors.New("invalid operator")
|
|||
// ErrWrongNumArguments represents a wrong number of arguments error.
|
||||
var ErrWrongNumArguments = errors.New("wrong number of arguments")
|
||||
|
||||
// ErrBytesLimit represents an error where the size of bytes value exceeds the limit.
|
||||
var ErrBytesLimit = errors.New("exceeding bytes size limit")
|
||||
|
||||
// ErrStringLimit represents an error where the size of string value exceeds the limit.
|
||||
var ErrStringLimit = errors.New("exceeding string size limit")
|
||||
|
||||
// ErrInvalidArgumentType represents an invalid argument value type error.
|
||||
type ErrInvalidArgumentType struct {
|
||||
Name string
|
||||
|
|
|
@ -3,6 +3,7 @@ package objects
|
|||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/compiler/token"
|
||||
)
|
||||
|
||||
|
@ -28,9 +29,16 @@ func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) {
|
|||
case token.Add:
|
||||
switch rhs := rhs.(type) {
|
||||
case *String:
|
||||
if len(o.Value)+len(rhs.Value) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
return &String{Value: o.Value + rhs.Value}, nil
|
||||
default:
|
||||
return &String{Value: o.Value + rhs.String()}, nil
|
||||
rhsStr := rhs.String()
|
||||
if len(o.Value)+len(rhsStr) > tengo.MaxStringLen {
|
||||
return nil, ErrStringLimit
|
||||
}
|
||||
return &String{Value: o.Value + rhsStr}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -193,8 +193,8 @@ out = func() {
|
|||
`, 136)
|
||||
|
||||
// assigning different type value
|
||||
expect(t, `a := 1; a = "foo"; out = a`, "foo") // global
|
||||
expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local
|
||||
expect(t, `a := 1; a = "foo"; out = a`, "foo") // global
|
||||
expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local
|
||||
expect(t, `
|
||||
out = func() {
|
||||
a := 5
|
||||
|
|
|
@ -3,6 +3,7 @@ package runtime_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
|
@ -193,3 +194,17 @@ func TestBuiltinFunction(t *testing.T) {
|
|||
expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, true) // closure
|
||||
expectWithSymbols(t, `out = is_callable(x)`, true, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object
|
||||
}
|
||||
|
||||
func TestBytesN(t *testing.T) {
|
||||
curMaxBytesLen := tengo.MaxBytesLen
|
||||
defer func() { tengo.MaxBytesLen = curMaxBytesLen }()
|
||||
tengo.MaxBytesLen = 10
|
||||
|
||||
expect(t, `out = bytes(0)`, make([]byte, 0))
|
||||
expect(t, `out = bytes(10)`, make([]byte, 10))
|
||||
expectError(t, `bytes(11)`, "bytes size limit")
|
||||
|
||||
tengo.MaxBytesLen = 1000
|
||||
expect(t, `out = bytes(1000)`, make([]byte, 1000))
|
||||
expectError(t, `bytes(1001)`, "bytes size limit")
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package stdlib
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
|
@ -124,7 +125,13 @@ func FuncARS(fn func() string) objects.CallableFunc {
|
|||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn()}, nil
|
||||
s := fn()
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,6 +148,10 @@ func FuncARSE(fn func() (string, error)) objects.CallableFunc {
|
|||
return wrapError(err), nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: res}, nil
|
||||
}
|
||||
}
|
||||
|
@ -158,6 +169,10 @@ func FuncARYE(fn func() ([]byte, error)) objects.CallableFunc {
|
|||
return wrapError(err), nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxBytesLen {
|
||||
return nil, objects.ErrBytesLimit
|
||||
}
|
||||
|
||||
return &objects.Bytes{Value: res}, nil
|
||||
}
|
||||
}
|
||||
|
@ -183,8 +198,12 @@ func FuncARSs(fn func() []string) objects.CallableFunc {
|
|||
}
|
||||
|
||||
arr := &objects.Array{}
|
||||
for _, osArg := range fn() {
|
||||
arr.Value = append(arr.Value, &objects.String{Value: osArg})
|
||||
for _, elem := range fn() {
|
||||
if len(elem) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: elem})
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
|
@ -493,7 +512,13 @@ func FuncASRS(fn func(string) string) objects.CallableFunc {
|
|||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(s1)}, nil
|
||||
s := fn(s1)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -516,8 +541,12 @@ func FuncASRSs(fn func(string) []string) objects.CallableFunc {
|
|||
res := fn(s1)
|
||||
|
||||
arr := &objects.Array{}
|
||||
for _, osArg := range res {
|
||||
arr.Value = append(arr.Value, &objects.String{Value: osArg})
|
||||
for _, elem := range res {
|
||||
if len(elem) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: elem})
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
|
@ -546,6 +575,10 @@ func FuncASRSE(fn func(string) (string, error)) objects.CallableFunc {
|
|||
return wrapError(err), nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: res}, nil
|
||||
}
|
||||
}
|
||||
|
@ -628,6 +661,10 @@ func FuncASSRSs(fn func(string, string) []string) objects.CallableFunc {
|
|||
|
||||
arr := &objects.Array{}
|
||||
for _, res := range fn(s1, s2) {
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: res})
|
||||
}
|
||||
|
||||
|
@ -671,6 +708,10 @@ func FuncASSIRSs(fn func(string, string, int) []string) objects.CallableFunc {
|
|||
|
||||
arr := &objects.Array{}
|
||||
for _, res := range fn(s1, s2, i3) {
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: res})
|
||||
}
|
||||
|
||||
|
@ -732,7 +773,13 @@ func FuncASSRS(fn func(string, string) string) objects.CallableFunc {
|
|||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(s1, s2)}, nil
|
||||
s := fn(s1, s2)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -819,7 +866,12 @@ func FuncASsSRS(fn func([]string, string) string) objects.CallableFunc {
|
|||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(ss1, s2)}, nil
|
||||
s := fn(ss1, s2)
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -909,7 +961,13 @@ func FuncASIRS(fn func(string, int) string) objects.CallableFunc {
|
|||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(s1, i2)}, nil
|
||||
s := fn(s1, i2)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1028,6 +1086,10 @@ func FuncAIRSsE(fn func(int) ([]string, error)) objects.CallableFunc {
|
|||
|
||||
arr := &objects.Array{}
|
||||
for _, r := range res {
|
||||
if len(r) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: r})
|
||||
}
|
||||
|
||||
|
@ -1052,6 +1114,12 @@ func FuncAIRS(fn func(int) string) objects.CallableFunc {
|
|||
}
|
||||
}
|
||||
|
||||
return &objects.String{Value: fn(i1)}, nil
|
||||
s := fn(i1)
|
||||
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
}
|
||||
|
|
85
stdlib/os.go
85
stdlib/os.go
|
@ -7,6 +7,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
|
@ -39,14 +40,14 @@ var osModule = map[string]objects.Object{
|
|||
"seek_set": &objects.Int{Value: int64(io.SeekStart)},
|
||||
"seek_cur": &objects.Int{Value: int64(io.SeekCurrent)},
|
||||
"seek_end": &objects.Int{Value: int64(io.SeekEnd)},
|
||||
"args": &objects.UserFunction{Value: osArgs}, // args() => array(string)
|
||||
"args": &objects.UserFunction{Name: "args", Value: osArgs}, // args() => array(string)
|
||||
"chdir": &objects.UserFunction{Name: "chdir", Value: FuncASRE(os.Chdir)}, // chdir(dir string) => error
|
||||
"chmod": osFuncASFmRE(os.Chmod), // chmod(name string, mode int) => error
|
||||
"chmod": osFuncASFmRE("chmod", os.Chmod), // chmod(name string, mode int) => error
|
||||
"chown": &objects.UserFunction{Name: "chown", Value: FuncASIIRE(os.Chown)}, // chown(name string, uid int, gid int) => error
|
||||
"clearenv": &objects.UserFunction{Name: "clearenv", Value: FuncAR(os.Clearenv)}, // clearenv()
|
||||
"environ": &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)}, // environ() => array(string)
|
||||
"exit": &objects.UserFunction{Name: "exit", Value: FuncAIR(os.Exit)}, // exit(code int)
|
||||
"expand_env": &objects.UserFunction{Name: "expand_env", Value: FuncASRS(os.ExpandEnv)}, // expand_env(s string) => string
|
||||
"expand_env": &objects.UserFunction{Name: "expand_env", Value: osExpandEnv}, // expand_env(s string) => string
|
||||
"getegid": &objects.UserFunction{Name: "getegid", Value: FuncARI(os.Getegid)}, // getegid() => int
|
||||
"getenv": &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)}, // getenv(s string) => string
|
||||
"geteuid": &objects.UserFunction{Name: "geteuid", Value: FuncARI(os.Geteuid)}, // geteuid() => int
|
||||
|
@ -60,9 +61,9 @@ var osModule = map[string]objects.Object{
|
|||
"hostname": &objects.UserFunction{Name: "hostname", Value: FuncARSE(os.Hostname)}, // hostname() => string/error
|
||||
"lchown": &objects.UserFunction{Name: "lchown", Value: FuncASIIRE(os.Lchown)}, // lchown(name string, uid int, gid int) => error
|
||||
"link": &objects.UserFunction{Name: "link", Value: FuncASSRE(os.Link)}, // link(oldname string, newname string) => error
|
||||
"lookup_env": &objects.UserFunction{Value: osLookupEnv}, // lookup_env(key string) => string/false
|
||||
"mkdir": osFuncASFmRE(os.Mkdir), // mkdir(name string, perm int) => error
|
||||
"mkdir_all": osFuncASFmRE(os.MkdirAll), // mkdir_all(name string, perm int) => error
|
||||
"lookup_env": &objects.UserFunction{Name: "lookup_env", Value: osLookupEnv}, // lookup_env(key string) => string/false
|
||||
"mkdir": osFuncASFmRE("mkdir", os.Mkdir), // mkdir(name string, perm int) => error
|
||||
"mkdir_all": osFuncASFmRE("mkdir_all", os.MkdirAll), // mkdir_all(name string, perm int) => error
|
||||
"readlink": &objects.UserFunction{Name: "readlink", Value: FuncASRSE(os.Readlink)}, // readlink(name string) => string/error
|
||||
"remove": &objects.UserFunction{Name: "remove", Value: FuncASRE(os.Remove)}, // remove(name string) => error
|
||||
"remove_all": &objects.UserFunction{Name: "remove_all", Value: FuncASRE(os.RemoveAll)}, // remove_all(name string) => error
|
||||
|
@ -72,15 +73,15 @@ var osModule = map[string]objects.Object{
|
|||
"temp_dir": &objects.UserFunction{Name: "temp_dir", Value: FuncARS(os.TempDir)}, // temp_dir() => string
|
||||
"truncate": &objects.UserFunction{Name: "truncate", Value: FuncASI64RE(os.Truncate)}, // truncate(name string, size int) => error
|
||||
"unsetenv": &objects.UserFunction{Name: "unsetenv", Value: FuncASRE(os.Unsetenv)}, // unsetenv(key string) => error
|
||||
"create": &objects.UserFunction{Value: osCreate}, // create(name string) => imap(file)/error
|
||||
"open": &objects.UserFunction{Value: osOpen}, // open(name string) => imap(file)/error
|
||||
"open_file": &objects.UserFunction{Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error
|
||||
"find_process": &objects.UserFunction{Value: osFindProcess}, // find_process(pid int) => imap(process)/error
|
||||
"start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
|
||||
"create": &objects.UserFunction{Name: "create", Value: osCreate}, // create(name string) => imap(file)/error
|
||||
"open": &objects.UserFunction{Name: "open", Value: osOpen}, // open(name string) => imap(file)/error
|
||||
"open_file": &objects.UserFunction{Name: "open_file", Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error
|
||||
"find_process": &objects.UserFunction{Name: "find_process", Value: osFindProcess}, // find_process(pid int) => imap(process)/error
|
||||
"start_process": &objects.UserFunction{Name: "start_process", Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error
|
||||
"exec_look_path": &objects.UserFunction{Name: "exec_look_path", Value: FuncASRSE(exec.LookPath)}, // exec_look_path(file) => string/error
|
||||
"exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command
|
||||
"stat": &objects.UserFunction{Value: osStat}, // stat(name) => imap(fileinfo)/error
|
||||
"read_file": &objects.UserFunction{Value: osReadFile}, // readfile(name) => array(byte)/error
|
||||
"exec": &objects.UserFunction{Name: "exec", Value: osExec}, // exec(name, args...) => command
|
||||
"stat": &objects.UserFunction{Name: "stat", Value: osStat}, // stat(name) => imap(fileinfo)/error
|
||||
"read_file": &objects.UserFunction{Name: "read_file", Value: osReadFile}, // readfile(name) => array(byte)/error
|
||||
}
|
||||
|
||||
func osReadFile(args ...objects.Object) (ret objects.Object, err error) {
|
||||
|
@ -102,6 +103,10 @@ func osReadFile(args ...objects.Object) (ret objects.Object, err error) {
|
|||
return wrapError(err), nil
|
||||
}
|
||||
|
||||
if len(bytes) > tengo.MaxBytesLen {
|
||||
return nil, objects.ErrBytesLimit
|
||||
}
|
||||
|
||||
return &objects.Bytes{Value: bytes}, nil
|
||||
}
|
||||
|
||||
|
@ -233,14 +238,19 @@ func osArgs(args ...objects.Object) (objects.Object, error) {
|
|||
|
||||
arr := &objects.Array{}
|
||||
for _, osArg := range os.Args {
|
||||
if len(osArg) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
arr.Value = append(arr.Value, &objects.String{Value: osArg})
|
||||
}
|
||||
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction {
|
||||
func osFuncASFmRE(name string, fn func(string, os.FileMode) error) *objects.UserFunction {
|
||||
return &objects.UserFunction{
|
||||
Name: name,
|
||||
Value: func(args ...objects.Object) (objects.Object, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -287,9 +297,54 @@ func osLookupEnv(args ...objects.Object) (objects.Object, error) {
|
|||
return objects.FalseValue, nil
|
||||
}
|
||||
|
||||
if len(res) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: res}, nil
|
||||
}
|
||||
|
||||
func osExpandEnv(args ...objects.Object) (objects.Object, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
s1, ok := objects.ToString(args[0])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
var vlen int
|
||||
var failed bool
|
||||
s := os.Expand(s1, func(k string) string {
|
||||
if failed {
|
||||
return ""
|
||||
}
|
||||
|
||||
v := os.Getenv(k)
|
||||
|
||||
// this does not count the other texts that are not being replaced
|
||||
// but the code checks the final length at the end
|
||||
vlen += len(v)
|
||||
if vlen > tengo.MaxStringLen {
|
||||
failed = true
|
||||
return ""
|
||||
}
|
||||
|
||||
return v
|
||||
})
|
||||
|
||||
if failed || len(s) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: s}, nil
|
||||
}
|
||||
|
||||
func osExec(args ...objects.Object) (objects.Object, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
|
|
@ -21,6 +21,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
|
|||
"wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, //
|
||||
// set_path(path string)
|
||||
"set_path": &objects.UserFunction{
|
||||
Name: "set_path",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -42,6 +43,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
|
|||
},
|
||||
// set_dir(dir string)
|
||||
"set_dir": &objects.UserFunction{
|
||||
Name: "set_dir",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -63,6 +65,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
|
|||
},
|
||||
// set_env(env array(string))
|
||||
"set_env": &objects.UserFunction{
|
||||
Name: "set_env",
|
||||
Value: func(args ...objects.Object) (objects.Object, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -96,6 +99,7 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
|
|||
},
|
||||
// process() => imap(process)
|
||||
"process": &objects.UserFunction{
|
||||
Name: "process",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
|
|
@ -29,6 +29,7 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
|
|||
"read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, //
|
||||
// chmod(mode int) => error
|
||||
"chmod": &objects.UserFunction{
|
||||
Name: "chmod",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -48,6 +49,7 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
|
|||
},
|
||||
// seek(offset int, whence int) => int/error
|
||||
"seek": &objects.UserFunction{
|
||||
Name: "seek",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 2 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -80,6 +82,7 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
|
|||
},
|
||||
// stat() => imap(fileinfo)/error
|
||||
"stat": &objects.UserFunction{
|
||||
Name: "start",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
|
|
@ -24,6 +24,7 @@ func makeOSProcess(proc *os.Process) *objects.ImmutableMap {
|
|||
"kill": &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)}, //
|
||||
"release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, //
|
||||
"signal": &objects.UserFunction{
|
||||
Name: "signal",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -42,6 +43,7 @@ func makeOSProcess(proc *os.Process) *objects.ImmutableMap {
|
|||
},
|
||||
},
|
||||
"wait": &objects.UserFunction{
|
||||
Name: "wait",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 0 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/assert"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
@ -86,3 +87,33 @@ func TestFileStatDir(t *testing.T) {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestOSExpandEnv(t *testing.T) {
|
||||
curMaxStringLen := tengo.MaxStringLen
|
||||
defer func() { tengo.MaxStringLen = curMaxStringLen }()
|
||||
tengo.MaxStringLen = 12
|
||||
|
||||
_ = os.Setenv("TENGO", "FOO BAR")
|
||||
module(t, "os").call("expand_env", "$TENGO").expect("FOO BAR")
|
||||
|
||||
_ = os.Setenv("TENGO", "FOO")
|
||||
module(t, "os").call("expand_env", "$TENGO $TENGO").expect("FOO FOO")
|
||||
|
||||
_ = os.Setenv("TENGO", "123456789012")
|
||||
module(t, "os").call("expand_env", "$TENGO").expect("123456789012")
|
||||
|
||||
_ = os.Setenv("TENGO", "1234567890123")
|
||||
module(t, "os").call("expand_env", "$TENGO").expectError()
|
||||
|
||||
_ = os.Setenv("TENGO", "123456")
|
||||
module(t, "os").call("expand_env", "$TENGO$TENGO").expect("123456123456")
|
||||
|
||||
_ = os.Setenv("TENGO", "123456")
|
||||
module(t, "os").call("expand_env", "${TENGO}${TENGO}").expect("123456123456")
|
||||
|
||||
_ = os.Setenv("TENGO", "123456")
|
||||
module(t, "os").call("expand_env", "$TENGO $TENGO").expectError()
|
||||
|
||||
_ = os.Setenv("TENGO", "123456")
|
||||
module(t, "os").call("expand_env", "${TENGO} ${TENGO}").expectError()
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ var randModule = map[string]objects.Object{
|
|||
"perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(rand.Perm)},
|
||||
"seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)},
|
||||
"read": &objects.UserFunction{
|
||||
Name: "read",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -39,6 +40,7 @@ var randModule = map[string]objects.Object{
|
|||
},
|
||||
},
|
||||
"rand": &objects.UserFunction{
|
||||
Name: "rand",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
@ -71,6 +73,7 @@ func randRand(r *rand.Rand) *objects.ImmutableMap {
|
|||
"perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)},
|
||||
"seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)},
|
||||
"read": &objects.UserFunction{
|
||||
Name: "read",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
|
|
184
stdlib/text.go
184
stdlib/text.go
|
@ -1,19 +1,22 @@
|
|||
package stdlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
var textModule = map[string]objects.Object{
|
||||
"re_match": &objects.UserFunction{Value: textREMatch}, // re_match(pattern, text) => bool/error
|
||||
"re_find": &objects.UserFunction{Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined
|
||||
"re_replace": &objects.UserFunction{Value: textREReplace}, // re_replace(pattern, text, repl) => string/error
|
||||
"re_split": &objects.UserFunction{Value: textRESplit}, // re_split(pattern, text, count) => [string]/error
|
||||
"re_compile": &objects.UserFunction{Value: textRECompile}, // re_compile(pattern) => Regexp/error
|
||||
"re_match": &objects.UserFunction{Name: "re_match", Value: textREMatch}, // re_match(pattern, text) => bool/error
|
||||
"re_find": &objects.UserFunction{Name: "re_find", Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined
|
||||
"re_replace": &objects.UserFunction{Name: "re_replace", Value: textREReplace}, // re_replace(pattern, text, repl) => string/error
|
||||
"re_split": &objects.UserFunction{Name: "re_split", Value: textRESplit}, // re_split(pattern, text, count) => [string]/error
|
||||
"re_compile": &objects.UserFunction{Name: "re_compile", Value: textRECompile}, // re_compile(pattern) => Regexp/error
|
||||
"compare": &objects.UserFunction{Name: "compare", Value: FuncASSRI(strings.Compare)}, // compare(a, b) => int
|
||||
"contains": &objects.UserFunction{Name: "contains", Value: FuncASSRB(strings.Contains)}, // contains(s, substr) => bool
|
||||
"contains_any": &objects.UserFunction{Name: "contains_any", Value: FuncASSRB(strings.ContainsAny)}, // contains_any(s, chars) => bool
|
||||
|
@ -24,11 +27,11 @@ var textModule = map[string]objects.Object{
|
|||
"has_suffix": &objects.UserFunction{Name: "has_suffix", Value: FuncASSRB(strings.HasSuffix)}, // has_suffix(s, suffix) => bool
|
||||
"index": &objects.UserFunction{Name: "index", Value: FuncASSRI(strings.Index)}, // index(s, substr) => int
|
||||
"index_any": &objects.UserFunction{Name: "index_any", Value: FuncASSRI(strings.IndexAny)}, // index_any(s, chars) => int
|
||||
"join": &objects.UserFunction{Name: "join", Value: FuncASsSRS(strings.Join)}, // join(arr, sep) => string
|
||||
"join": &objects.UserFunction{Name: "join", Value: textJoin}, // join(arr, sep) => string
|
||||
"last_index": &objects.UserFunction{Name: "last_index", Value: FuncASSRI(strings.LastIndex)}, // last_index(s, substr) => int
|
||||
"last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int
|
||||
"repeat": &objects.UserFunction{Name: "repeat", Value: FuncASIRS(strings.Repeat)}, // repeat(s, count) => string
|
||||
"replace": &objects.UserFunction{Value: textReplace}, // replace(s, old, new, n) => string
|
||||
"repeat": &objects.UserFunction{Name: "repeat", Value: textRepeat}, // repeat(s, count) => string
|
||||
"replace": &objects.UserFunction{Name: "replace", Value: textReplace}, // replace(s, old, new, n) => string
|
||||
"split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string]
|
||||
"split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string]
|
||||
"split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string]
|
||||
|
@ -43,13 +46,13 @@ var textModule = map[string]objects.Object{
|
|||
"trim_space": &objects.UserFunction{Name: "trim_space", Value: FuncASRS(strings.TrimSpace)}, // trim_space(s) => string
|
||||
"trim_suffix": &objects.UserFunction{Name: "trim_suffix", Value: FuncASSRS(strings.TrimSuffix)}, // trim_suffix(s, suffix) => string
|
||||
"atoi": &objects.UserFunction{Name: "atoi", Value: FuncASRIE(strconv.Atoi)}, // atoi(str) => int/error
|
||||
"format_bool": &objects.UserFunction{Value: textFormatBool}, // format_bool(b) => string
|
||||
"format_float": &objects.UserFunction{Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string
|
||||
"format_int": &objects.UserFunction{Value: textFormatInt}, // format_int(i, base) => string
|
||||
"format_bool": &objects.UserFunction{Name: "format_bool", Value: textFormatBool}, // format_bool(b) => string
|
||||
"format_float": &objects.UserFunction{Name: "format_float", Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string
|
||||
"format_int": &objects.UserFunction{Name: "format_int", Value: textFormatInt}, // format_int(i, base) => string
|
||||
"itoa": &objects.UserFunction{Name: "itoa", Value: FuncAIRS(strconv.Itoa)}, // itoa(i) => string
|
||||
"parse_bool": &objects.UserFunction{Value: textParseBool}, // parse_bool(str) => bool/error
|
||||
"parse_float": &objects.UserFunction{Value: textParseFloat}, // parse_float(str, bits) => float/error
|
||||
"parse_int": &objects.UserFunction{Value: textParseInt}, // parse_int(str, base, bits) => int/error
|
||||
"parse_bool": &objects.UserFunction{Name: "parse_bool", Value: textParseBool}, // parse_bool(str) => bool/error
|
||||
"parse_float": &objects.UserFunction{Name: "parse_float", Value: textParseFloat}, // parse_float(str, bits) => float/error
|
||||
"parse_int": &objects.UserFunction{Name: "parse_int", Value: textParseInt}, // parse_int(str, base, bits) => int/error
|
||||
"quote": &objects.UserFunction{Name: "quote", Value: FuncASRS(strconv.Quote)}, // quote(str) => string
|
||||
"unquote": &objects.UserFunction{Name: "unquote", Value: FuncASRSE(strconv.Unquote)}, // unquote(str) => string/error
|
||||
}
|
||||
|
@ -223,7 +226,12 @@ func textREReplace(args ...objects.Object) (ret objects.Object, err error) {
|
|||
if err != nil {
|
||||
ret = wrapError(err)
|
||||
} else {
|
||||
ret = &objects.String{Value: re.ReplaceAllString(s2, s3)}
|
||||
s, ok := doTextRegexpReplace(re, s2, s3)
|
||||
if !ok {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: s}
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -357,11 +365,106 @@ func textReplace(args ...objects.Object) (ret objects.Object, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)}
|
||||
s, ok := doTextReplace(s1, s2, s3, i4)
|
||||
if !ok {
|
||||
err = objects.ErrStringLimit
|
||||
return
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: s}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func textRepeat(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 2 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
s1, ok := objects.ToString(args[0])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
i2, ok := objects.ToInt(args[1])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "second",
|
||||
Expected: "int(compatible)",
|
||||
Found: args[1].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
if len(s1)*i2 > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: strings.Repeat(s1, i2)}, nil
|
||||
}
|
||||
|
||||
func textJoin(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 2 {
|
||||
return nil, objects.ErrWrongNumArguments
|
||||
}
|
||||
|
||||
var slen int
|
||||
var ss1 []string
|
||||
switch arg0 := args[0].(type) {
|
||||
case *objects.Array:
|
||||
for idx, a := range arg0.Value {
|
||||
as, ok := objects.ToString(a)
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: fmt.Sprintf("first[%d]", idx),
|
||||
Expected: "string(compatible)",
|
||||
Found: a.TypeName(),
|
||||
}
|
||||
}
|
||||
slen += len(as)
|
||||
ss1 = append(ss1, as)
|
||||
}
|
||||
case *objects.ImmutableArray:
|
||||
for idx, a := range arg0.Value {
|
||||
as, ok := objects.ToString(a)
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: fmt.Sprintf("first[%d]", idx),
|
||||
Expected: "string(compatible)",
|
||||
Found: a.TypeName(),
|
||||
}
|
||||
}
|
||||
slen += len(as)
|
||||
ss1 = append(ss1, as)
|
||||
}
|
||||
default:
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "array",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
s2, ok := objects.ToString(args[1])
|
||||
if !ok {
|
||||
return nil, objects.ErrInvalidArgumentType{
|
||||
Name: "second",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[1].TypeName(),
|
||||
}
|
||||
}
|
||||
|
||||
// make sure output length does not exceed the limit
|
||||
if slen+len(s2)*(len(ss1)-1) > tengo.MaxStringLen {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
return &objects.String{Value: strings.Join(ss1, s2)}, nil
|
||||
}
|
||||
|
||||
func textFormatBool(args ...objects.Object) (ret objects.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
err = objects.ErrWrongNumArguments
|
||||
|
@ -583,3 +686,52 @@ func textParseInt(args ...objects.Object) (ret objects.Object, err error) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// Modified implementation of strings.Replace
|
||||
// to limit the maximum length of output string.
|
||||
func doTextReplace(s, old, new string, n int) (string, bool) {
|
||||
if old == new || n == 0 {
|
||||
return s, true // avoid allocation
|
||||
}
|
||||
|
||||
// Compute number of replacements.
|
||||
if m := strings.Count(s, old); m == 0 {
|
||||
return s, true // avoid allocation
|
||||
} else if n < 0 || m < n {
|
||||
n = m
|
||||
}
|
||||
|
||||
// Apply replacements to buffer.
|
||||
t := make([]byte, len(s)+n*(len(new)-len(old)))
|
||||
w := 0
|
||||
start := 0
|
||||
for i := 0; i < n; i++ {
|
||||
j := start
|
||||
if len(old) == 0 {
|
||||
if i > 0 {
|
||||
_, wid := utf8.DecodeRuneInString(s[start:])
|
||||
j += wid
|
||||
}
|
||||
} else {
|
||||
j += strings.Index(s[start:], old)
|
||||
}
|
||||
|
||||
ssj := s[start:j]
|
||||
if w+len(ssj)+len(new) > tengo.MaxStringLen {
|
||||
return "", false
|
||||
}
|
||||
|
||||
w += copy(t[w:], ssj)
|
||||
w += copy(t[w:], new)
|
||||
start = j + len(old)
|
||||
}
|
||||
|
||||
ss := s[start:]
|
||||
if w+len(ss) > tengo.MaxStringLen {
|
||||
return "", false
|
||||
}
|
||||
|
||||
w += copy(t[w:], ss)
|
||||
|
||||
return string(t[0:w]), true
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package stdlib
|
|||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
|
@ -141,7 +142,12 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
|
|||
return
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: re.ReplaceAllString(s1, s2)}
|
||||
s, ok := doTextRegexpReplace(re, s1, s2)
|
||||
if !ok {
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: s}
|
||||
|
||||
return
|
||||
},
|
||||
|
@ -193,3 +199,30 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Size-limit checking implementation of regexp.ReplaceAllString.
|
||||
func doTextRegexpReplace(re *regexp.Regexp, src, repl string) (string, bool) {
|
||||
idx := 0
|
||||
out := ""
|
||||
|
||||
for _, m := range re.FindAllStringSubmatchIndex(src, -1) {
|
||||
var exp []byte
|
||||
exp = re.ExpandString(exp, repl, src, m)
|
||||
|
||||
if len(out)+m[0]-idx+len(exp) > tengo.MaxStringLen {
|
||||
return "", false
|
||||
}
|
||||
|
||||
out += src[idx:m[0]] + string(exp)
|
||||
idx = m[1]
|
||||
}
|
||||
if idx < len(src) {
|
||||
if len(out)+len(src)-idx > tengo.MaxStringLen {
|
||||
return "", false
|
||||
}
|
||||
|
||||
out += src[idx:]
|
||||
}
|
||||
|
||||
return string(out), true
|
||||
}
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
package stdlib_test
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
func TestTextRE(t *testing.T) {
|
||||
// re_match(pattern, text)
|
||||
for _, d := range []struct {
|
||||
pattern string
|
||||
text string
|
||||
expected interface{}
|
||||
pattern string
|
||||
text string
|
||||
}{
|
||||
{"abc", "", false},
|
||||
{"abc", "abc", true},
|
||||
{"a", "abc", true},
|
||||
{"b", "abc", true},
|
||||
{"^a", "abc", true},
|
||||
{"^b", "abc", false},
|
||||
{"abc", ""},
|
||||
{"abc", "abc"},
|
||||
{"a", "abc"},
|
||||
{"b", "abc"},
|
||||
{"^a", "abc"},
|
||||
{"^b", "abc"},
|
||||
} {
|
||||
module(t, "text").call("re_match", d.pattern, d.text).expect(d.expected, "pattern: %q, src: %q", d.pattern, d.text)
|
||||
module(t, "text").call("re_compile", d.pattern).call("match", d.text).expect(d.expected, "patter: %q, src: %q", d.pattern, d.text)
|
||||
expected := regexp.MustCompile(d.pattern).MatchString(d.text)
|
||||
module(t, "text").call("re_match", d.pattern, d.text).expect(expected, "pattern: %q, src: %q", d.pattern, d.text)
|
||||
module(t, "text").call("re_compile", d.pattern).call("match", d.text).expect(expected, "patter: %q, src: %q", d.pattern, d.text)
|
||||
}
|
||||
|
||||
// re_find(pattern, text)
|
||||
|
@ -106,58 +108,71 @@ func TestTextRE(t *testing.T) {
|
|||
|
||||
// re_replace(pattern, text, repl)
|
||||
for _, d := range []struct {
|
||||
pattern string
|
||||
text string
|
||||
repl string
|
||||
expected interface{}
|
||||
pattern string
|
||||
text string
|
||||
repl string
|
||||
}{
|
||||
{"a", "", "b", ""},
|
||||
{"a", "a", "b", "b"},
|
||||
{"a", "acac", "b", "bcbc"},
|
||||
{"a", "acac", "123", "123c123c"},
|
||||
{"ac", "acac", "99", "9999"},
|
||||
{"ac$", "acac", "foo", "acfoo"},
|
||||
{"a", "", "b"},
|
||||
{"a", "a", "b"},
|
||||
{"a", "acac", "b"},
|
||||
{"b", "acac", "x"},
|
||||
{"a", "acac", "123"},
|
||||
{"ac", "acac", "99"},
|
||||
{"ac$", "acac", "foo"},
|
||||
{"a(b)", "ababab", "$1"},
|
||||
{"a(b)(c)", "abcabcabc", "$2$1"},
|
||||
{"(a(b)c)", "abcabcabc", "$1$2"},
|
||||
{"(일(2)삼)", "일2삼12삼일23", "$1$2"},
|
||||
{"((일)(2)3)", "일23\n일이3\n일23", "$1$2$3"},
|
||||
{"(a(b)c)", "abc\nabc\nabc", "$1$2"},
|
||||
} {
|
||||
module(t, "text").call("re_replace", d.pattern, d.text, d.repl).expect(d.expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl)
|
||||
module(t, "text").call("re_compile", d.pattern).call("replace", d.text, d.repl).expect(d.expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl)
|
||||
expected := regexp.MustCompile(d.pattern).ReplaceAllString(d.text, d.repl)
|
||||
module(t, "text").call("re_replace", d.pattern, d.text, d.repl).expect(expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl)
|
||||
module(t, "text").call("re_compile", d.pattern).call("replace", d.text, d.repl).expect(expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl)
|
||||
}
|
||||
|
||||
// re_split(pattern, text)
|
||||
for _, d := range []struct {
|
||||
pattern string
|
||||
text string
|
||||
expected interface{}
|
||||
pattern string
|
||||
text string
|
||||
}{
|
||||
{"a", "", ARR{""}},
|
||||
{"a", "abcabc", ARR{"", "bc", "bc"}},
|
||||
{"ab", "abcabc", ARR{"", "c", "c"}},
|
||||
{"^a", "abcabc", ARR{"", "bcabc"}},
|
||||
{"a", ""},
|
||||
{"a", "abcabc"},
|
||||
{"ab", "abcabc"},
|
||||
{"^a", "abcabc"},
|
||||
} {
|
||||
module(t, "text").call("re_split", d.pattern, d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
|
||||
module(t, "text").call("re_compile", d.pattern).call("split", d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
|
||||
var expected []interface{}
|
||||
for _, ex := range regexp.MustCompile(d.pattern).Split(d.text, -1) {
|
||||
expected = append(expected, ex)
|
||||
}
|
||||
module(t, "text").call("re_split", d.pattern, d.text).expect(expected, "pattern: %q, text: %q", d.pattern, d.text)
|
||||
module(t, "text").call("re_compile", d.pattern).call("split", d.text).expect(expected, "pattern: %q, text: %q", d.pattern, d.text)
|
||||
}
|
||||
|
||||
// re_split(pattern, text, count))
|
||||
for _, d := range []struct {
|
||||
pattern string
|
||||
text string
|
||||
count int
|
||||
expected interface{}
|
||||
pattern string
|
||||
text string
|
||||
count int
|
||||
}{
|
||||
{"a", "", -1, ARR{""}},
|
||||
{"a", "abcabc", -1, ARR{"", "bc", "bc"}},
|
||||
{"ab", "abcabc", -1, ARR{"", "c", "c"}},
|
||||
{"^a", "abcabc", -1, ARR{"", "bcabc"}},
|
||||
{"a", "abcabc", 0, ARR{}},
|
||||
{"a", "abcabc", 1, ARR{"abcabc"}},
|
||||
{"a", "abcabc", 2, ARR{"", "bcabc"}},
|
||||
{"a", "abcabc", 3, ARR{"", "bc", "bc"}},
|
||||
{"b", "abcabc", 1, ARR{"abcabc"}},
|
||||
{"b", "abcabc", 2, ARR{"a", "cabc"}},
|
||||
{"b", "abcabc", 3, ARR{"a", "ca", "c"}},
|
||||
{"a", "", -1},
|
||||
{"a", "abcabc", -1},
|
||||
{"ab", "abcabc", -1},
|
||||
{"^a", "abcabc", -1},
|
||||
{"a", "abcabc", 0},
|
||||
{"a", "abcabc", 1},
|
||||
{"a", "abcabc", 2},
|
||||
{"a", "abcabc", 3},
|
||||
{"b", "abcabc", 1},
|
||||
{"b", "abcabc", 2},
|
||||
{"b", "abcabc", 3},
|
||||
} {
|
||||
module(t, "text").call("re_split", d.pattern, d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
|
||||
module(t, "text").call("re_compile", d.pattern).call("split", d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text)
|
||||
var expected []interface{}
|
||||
for _, ex := range regexp.MustCompile(d.pattern).Split(d.text, d.count) {
|
||||
expected = append(expected, ex)
|
||||
}
|
||||
module(t, "text").call("re_split", d.pattern, d.text, d.count).expect(expected, "pattern: %q, text: %q", d.pattern, d.text)
|
||||
module(t, "text").call("re_compile", d.pattern).call("split", d.text, d.count).expect(expected, "pattern: %q, text: %q", d.pattern, d.text)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,3 +213,34 @@ func TestText(t *testing.T) {
|
|||
module(t, "text").call("parse_float", "-19.84", 64).expect(-19.84)
|
||||
module(t, "text").call("parse_int", "-1984", 10, 64).expect(-1984)
|
||||
}
|
||||
|
||||
func TestReplaceLimit(t *testing.T) {
|
||||
curMaxStringLen := tengo.MaxStringLen
|
||||
defer func() { tengo.MaxStringLen = curMaxStringLen }()
|
||||
tengo.MaxStringLen = 12
|
||||
|
||||
module(t, "text").call("replace", "123456789012", "1", "x", -1).expect("x234567890x2")
|
||||
module(t, "text").call("replace", "123456789012", "12", "x", -1).expect("x34567890x")
|
||||
module(t, "text").call("replace", "123456789012", "1", "xy", -1).expectError()
|
||||
module(t, "text").call("replace", "123456789012", "0", "xy", -1).expectError()
|
||||
module(t, "text").call("replace", "123456789012", "012", "xyz", -1).expect("123456789xyz")
|
||||
module(t, "text").call("replace", "123456789012", "012", "xyzz", -1).expectError()
|
||||
|
||||
module(t, "text").call("re_replace", "1", "123456789012", "x").expect("x234567890x2")
|
||||
module(t, "text").call("re_replace", "12", "123456789012", "x").expect("x34567890x")
|
||||
module(t, "text").call("re_replace", "1", "123456789012", "xy").expectError()
|
||||
module(t, "text").call("re_replace", "1(2)", "123456789012", "x$1").expect("x234567890x2")
|
||||
module(t, "text").call("re_replace", "(1)(2)", "123456789012", "$2$1").expect("213456789021")
|
||||
module(t, "text").call("re_replace", "(1)(2)", "123456789012", "${2}${1}x").expectError()
|
||||
}
|
||||
|
||||
func TestTextRepeat(t *testing.T) {
|
||||
curMaxStringLen := tengo.MaxStringLen
|
||||
defer func() { tengo.MaxStringLen = curMaxStringLen }()
|
||||
tengo.MaxStringLen = 12
|
||||
|
||||
module(t, "text").call("repeat", "1234", "3").expect("123412341234")
|
||||
module(t, "text").call("repeat", "1234", "4").expectError()
|
||||
module(t, "text").call("repeat", "1", "12").expect("111111111111")
|
||||
module(t, "text").call("repeat", "1", "13").expectError()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package stdlib
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/d5/tengo"
|
||||
"github.com/d5/tengo/objects"
|
||||
)
|
||||
|
||||
|
@ -867,7 +868,13 @@ func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: t1.Format(s2)}
|
||||
s := t1.Format(s2)
|
||||
if len(s) > tengo.MaxStringLen {
|
||||
|
||||
return nil, objects.ErrStringLimit
|
||||
}
|
||||
|
||||
ret = &objects.String{Value: s}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
10
tengo.go
10
tengo.go
|
@ -1 +1,11 @@
|
|||
package tengo
|
||||
|
||||
var (
|
||||
// MaxStringLen is the maximum byte-length for string value.
|
||||
// Note this limit applies to all compiler/VM instances in the process.
|
||||
MaxStringLen = 2147483647
|
||||
|
||||
// MaxBytesLen is the maximum length for bytes value.
|
||||
// Note this limit applies to all compiler/VM instances in the process.
|
||||
MaxBytesLen = 2147483647
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue