diff --git a/Gopkg.lock b/Gopkg.lock index c66f80b..27e4b97 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,18 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "github.com/ThomasRooney/gexpect" + packages = ["."] + revision = "5482f03509440585d13d8f648989e05903001842" + +[[projects]] + branch = "master" + name = "github.com/Xe/eclier" + packages = ["."] + revision = "b586327df9d5a5e5023fa1338f44941745136d68" + [[projects]] branch = "master" name = "github.com/Xe/gopreload" @@ -22,6 +34,15 @@ packages = ["."] revision = "62b230097e9c9534ca2074782b25d738c4b68964" +[[projects]] + branch = "master" + name = "github.com/Xe/x" + packages = [ + "tools/glue/libs/gluaexpect", + "tools/glue/libs/gluasimplebox" + ] + revision = "860ea0dedb8beb93b60717510eabca2ef5ffe150" + [[projects]] branch = "master" name = "github.com/aclements/go-moremath" @@ -43,6 +64,12 @@ revision = "5f10fee965225ac1eecdc234c09daf5cd9e7f7b6" version = "v1.2.1" +[[projects]] + branch = "master" + name = "github.com/ailncode/gluaxmlpath" + packages = ["."] + revision = "6ce478ecb4a60c4fc8929838e0b21b7fb7ca7440" + [[projects]] branch = "master" name = "github.com/alecthomas/template" @@ -160,6 +187,18 @@ revision = "7cd7992b3bc86f920394f8de92c13900da1a46b7" version = "v3.2.0" +[[projects]] + branch = "master" + name = "github.com/cjoudrey/gluahttp" + packages = ["."] + revision = "b4bfe0c50fea948dcbf3966e120996d6607bbd89" + +[[projects]] + branch = "master" + name = "github.com/cjoudrey/gluaurl" + packages = ["."] + revision = "31cbb9bef199454415879f2e6d609d1136d60cad" + [[projects]] name = "github.com/coreos/bbolt" packages = ["."] @@ -354,6 +393,12 @@ packages = ["."] revision = "683f49123a33db61abfb241b7ac5e4af4dc54d55" +[[projects]] + branch = "master" + name = "github.com/howeyc/gopass" + packages = ["."] + revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8" + [[projects]] name = "github.com/jmespath/go-jmespath" packages = ["."] @@ -380,6 +425,12 @@ packages = ["."] revision = "ae77be60afb1dcacde03767a8c37337fad28ac14" +[[projects]] + branch = "master" + name = "github.com/kballard/go-shellquote" + packages = ["."] + revision = "cd60e84ee657ff3dc51de0b4f55dd299a3e136f2" + [[projects]] name = "github.com/klauspost/cpuid" packages = ["."] @@ -392,27 +443,66 @@ revision = "6bb6130ff6a76a904c1841707d65603aec9cc288" version = "v1.6" +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluaenv" + packages = ["."] + revision = "2888db6bbe38923d59c42e443895875cc8ce0820" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluafs" + packages = ["."] + revision = "01391ed2d7ab89dc80157605b073403f960aa223" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluaquestion" + packages = ["."] + revision = "311437c29ba54d027ad2af383661725ae2bfdcdc" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluassh" + packages = ["."] + revision = "2a7bd48d7568de8230c87ac1ef4a4c481e45814d" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluatemplate" + packages = ["."] + revision = "d9e2c9d6b00f069a9da377a9ac529c827c1c7d71" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluayaml" + packages = ["."] + revision = "6fe413d49d73d785510ecf1529991ab0573e96c7" + +[[projects]] + branch = "master" + name = "github.com/kr/fs" + packages = ["."] + revision = "2788f0dbd16903de03cb8186e5c7d97b69ad387b" + [[projects]] branch = "master" name = "github.com/kr/pretty" packages = ["."] revision = "cfb55aafdaf3ec08f0db22699ab822c50091b1c4" +[[projects]] + name = "github.com/kr/pty" + packages = ["."] + revision = "282ce0e5322c82529687d609ee670fac7c7d917c" + version = "v1.1.1" + [[projects]] branch = "master" name = "github.com/kr/text" packages = ["."] revision = "7cafcd837844e784b526369c9bce262804aebc60" -[[projects]] - branch = "master" - name = "github.com/lib/pq" - packages = [ - ".", - "oid" - ] - revision = "27ea5d92de30060e7121ddd543fe14e9a327e0cc" - [[projects]] branch = "master" name = "github.com/lucas-clemente/aes12" @@ -463,18 +553,6 @@ revision = "ab3ca2f6f85577d7ec82e0a6df721147a2e737f9" version = "v2.0.1" -[[projects]] - name = "github.com/mattes/migrate" - packages = [ - ".", - "database", - "database/postgres", - "source", - "source/go-bindata" - ] - revision = "035c07716cd373d88456ec4d701402df52584cb4" - version = "v3.0.1" - [[projects]] name = "github.com/mattn/go-isatty" packages = ["."] @@ -559,12 +637,30 @@ packages = ["."] revision = "96aac992fc8b1a4c83841a6c3e7178d20d989625" +[[projects]] + branch = "master" + name = "github.com/otm/gluaflag" + packages = ["."] + revision = "078088de689148194436293886e8e39809167332" + +[[projects]] + branch = "master" + name = "github.com/otm/gluash" + packages = ["."] + revision = "e145c563986f0b91f740a758a84bca46c163aec7" + [[projects]] name = "github.com/pkg/errors" packages = ["."] revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" +[[projects]] + name = "github.com/pkg/sftp" + packages = ["."] + revision = "f6a9258a0f570c3a76681b897b6ded57cb0dfa88" + version = "1.2.0" + [[projects]] name = "github.com/posener/complete" packages = [ @@ -639,6 +735,35 @@ revision = "ebec7ef2574b42a7088cd7751176483e0a27d458" version = "v1.0.6" +[[projects]] + branch = "master" + name = "github.com/yookoala/realpath" + packages = ["."] + revision = "d19ef9c409d9817c1e685775e53d361b03eabbc8" + +[[projects]] + branch = "master" + name = "github.com/yuin/gluamapper" + packages = ["."] + revision = "d836955830e75240d46ce9f0e6d148d94f2e1d3a" + +[[projects]] + branch = "master" + name = "github.com/yuin/gluare" + packages = ["."] + revision = "d7c94f1a80ede93a621ed100866e6d4745ca8c22" + +[[projects]] + branch = "master" + name = "github.com/yuin/gopher-lua" + packages = [ + ".", + "ast", + "parse", + "pm" + ] + revision = "7d7bc8747e3f614c5c587729a341fe7d8903cdb8" + [[projects]] branch = "master" name = "github.com/zclconf/go-cty" @@ -669,7 +794,10 @@ "blowfish", "cast5", "curve25519", + "ed25519", + "ed25519/internal/edwards25519", "hkdf", + "internal/chacha20", "nacl/secretbox", "openpgp", "openpgp/armor", @@ -681,6 +809,9 @@ "poly1305", "salsa20", "salsa20/salsa", + "ssh", + "ssh/agent", + "ssh/terminal", "tea", "twofish", "xtea" @@ -710,7 +841,10 @@ [[projects]] branch = "master" name = "golang.org/x/sys" - packages = ["unix"] + packages = [ + "unix", + "windows" + ] revision = "2c42eef0765b9837fbdab12011af7830f55f88f0" [[projects]] @@ -777,9 +911,33 @@ revision = "947dcec5ba9c011838740e680966fd7087a71d0d" version = "v2.2.6" +[[projects]] + branch = "v2" + name = "gopkg.in/xmlpath.v2" + packages = ["."] + revision = "860cbeca3ebcc600db0b213c0e83ad6ce91f5739" + +[[projects]] + branch = "v2" + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" + +[[projects]] + branch = "master" + name = "layeh.com/gopher-json" + packages = ["."] + revision = "1aab82196e3b418b56866938f28b6a693f2c6b18" + +[[projects]] + name = "layeh.com/gopher-luar" + packages = ["."] + revision = "7b2b96926970546e504881ee7364c1127508eb4e" + version = "v1.0.2" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "8cd1d7d7f5e846cb0b8d6bf78e6a7ecd32c6e24dd18c886ac99eaeb1b3109aff" + inputs-digest = "e0d2f0d7da737ab5517f0ff1c22588a2f835a8f37ef6b67e491ffd6e1e0defc3" solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/construct/main.go b/cmd/construct/main.go new file mode 100644 index 0000000..77813d8 --- /dev/null +++ b/cmd/construct/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "context" + "flag" + "log" + "net/http" + "os" + "path/filepath" + + "github.com/Xe/eclier" + "github.com/Xe/x/tools/glue/libs/gluaexpect" + "github.com/Xe/x/tools/glue/libs/gluasimplebox" + "github.com/ailncode/gluaxmlpath" + "github.com/cjoudrey/gluahttp" + "github.com/cjoudrey/gluaurl" + "github.com/kohkimakimoto/gluaenv" + "github.com/kohkimakimoto/gluafs" + "github.com/kohkimakimoto/gluaquestion" + "github.com/kohkimakimoto/gluassh" + "github.com/kohkimakimoto/gluatemplate" + "github.com/kohkimakimoto/gluayaml" + homedir "github.com/mitchellh/go-homedir" + "github.com/otm/gluaflag" + "github.com/otm/gluash" + "github.com/yuin/gluare" + lua "github.com/yuin/gopher-lua" + json "layeh.com/gopher-json" +) + +var hDir string +var cfgHome *string +var netrcFile *string + +func init() { + dir, err := homedir.Dir() + if err != nil { + log.Fatal(err) + } + + hDir = dir + + cfgHome = flag.String("home", filepath.Join(hDir, ".construct"), "construct's home directory") + netrcFile = flag.String("netrc", filepath.Join(hDir, ".netrc"), "location of netrc file to use for authentication") +} + +func main() { + flag.Parse() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + pluginLoc := filepath.Join(*cfgHome, "plugins") + scriptsLoc := filepath.Join(*cfgHome, "local", "scripts") + + os.MkdirAll(pluginLoc, 0755) + os.MkdirAll(scriptsLoc, 0755) + fout, err := os.Create(*netrcFile) + if err != nil { + log.Fatal(err) + } + fout.Close() + + opts := []eclier.RouterOption{ + eclier.WithGluaCreationHook(preload), + eclier.WithScriptHome(scriptsLoc), + } + + err = filepath.Walk(pluginLoc, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + opts = append(opts, eclier.WithScriptHome(info.Name())) + } + + return nil + }) + if err != nil { + log.Fatal(err) + } + + r, err := eclier.NewRouter(opts...) + if err != nil { + log.Fatal(err) + } + + r.Run(ctx, flag.Args()) +} + +func preload(L *lua.LState) { + L.PreloadModule("re", gluare.Loader) + L.PreloadModule("sh", gluash.Loader) + L.PreloadModule("fs", gluafs.Loader) + L.PreloadModule("env", gluaenv.Loader) + L.PreloadModule("yaml", gluayaml.Loader) + L.PreloadModule("question", gluaquestion.Loader) + L.PreloadModule("ssh", gluassh.Loader) + L.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader) + L.PreloadModule("flag", gluaflag.Loader) + L.PreloadModule("template", gluatemplate.Loader) + L.PreloadModule("url", gluaurl.Loader) + gluaexpect.Preload(L) + gluasimplebox.Preload(L) + gluaxmlpath.Preload(L) + json.Preload(L) +} diff --git a/mage.go b/mage.go index 7bb9f60..3eba98d 100644 --- a/mage.go +++ b/mage.go @@ -14,11 +14,8 @@ import ( "github.com/magefile/mage/mg" ) -var ( - wd string - arches []string - bins []string -) +var wd string +var arches []string func init() { lwd, err := os.Getwd() @@ -26,15 +23,7 @@ func init() { wd = lwd - arches = []string{"amd64", "ppc64le", "arm64"} - bins = []string{ - "mage", - "route-httpagent", - "route-cli", - "routed", - "route-runmigrations", - "terraform-provider-route", - } + arches = []string{"amd64", "ppc64", "386", "arm", "arm64"} } const pkgBase = "git.xeserv.us/xena/route/" @@ -47,7 +36,7 @@ func buildBins(goos, goarch string) { os.MkdirAll(filepath.Join(d, goos, goarch), 0777) - for _, pkg := range bins { + for _, pkg := range []string{"route-httpagent", "route-cli", "routed", "terraform-provider-route"} { env := []string{"GOOS=" + goos, "GOARCH=" + goarch} goBuild(ctx, env, filepath.Join(d, goos, goarch), "cmd/"+pkg) goInstall(ctx, env, "cmd/"+pkg) @@ -73,12 +62,9 @@ func Docker() { ver, err := gitTag() qod.ANE(err) - shouldWork(ctx, nil, wd, "docker", "build", "-t", "xena/route-core", ".") - - run := filepath.Join(wd, "run") - - shouldWork(ctx, nil, run, "docker", "build", "-t", "xena/routed:"+ver, "-f", "Dockerfile.routed", ".") - shouldWork(ctx, nil, run, "docker", "build", "-t", "xena/route-httpagent:"+ver, "-f", "Dockerfile.agent", ".") + shouldWork(ctx, nil, wd, "docker", "build", "-t", "xena/route-core", "-f", "Dockerfile.core", ".") + shouldWork(ctx, nil, wd, "docker", "build", "-t", "xena/routed:"+ver, "-f", "Dockerfile.routed", ".") + shouldWork(ctx, nil, wd, "docker", "build", "-t", "xena/route-httpagent:"+ver, "-f", "Dockerfile.agent", ".") } // Linux builds binaries for linux @@ -102,6 +88,10 @@ func Darwin() { // Build builds the binaries for route and routed. func Build() { buildBins(runtime.GOOS, runtime.GOARCH) + + if runtime.GOOS == "linux" { + Plugin() + } } // Plugin builds all of the plugins for programs wanting to augment themselves with route. @@ -154,6 +144,13 @@ func Package() { } } +// Version is the version as git reports. +func Version() { + ver, err := gitTag() + qod.ANE(err) + qod.Printlnf("route-%s", ver) +} + // Test runs all of the functional and unit tests for the project. func Test() { ctx, cancel := context.WithCancel(context.Background()) @@ -168,7 +165,7 @@ func Tools(ctx context.Context) { "github.com/golang/dep/cmd/dep", "github.com/golang/protobuf/protoc-gen-go", "github.com/twitchtv/twirp/protoc-gen-twirp", - "github.com/jteeuwen/go-bindata/go-bindata", + "github.com/Xe/twirp-codegens/cmd/protoc-gen-twirp_eclier", } for _, t := range tools { @@ -178,26 +175,17 @@ func Tools(ctx context.Context) { // Generate runs code generators and the like. func Generate(ctx context.Context) { - protoDir := filepath.Join(wd, "proto") - databaseDir := filepath.Join(wd, "internal", "database", "migrations") - - os.Mkdir(filepath.Join(databaseDir, "dmigrations"), 0777) + dir := filepath.Join(wd, "proto") Tools(ctx) - shouldWork(ctx, nil, protoDir, "sh", "./regen.sh") - shouldWork(ctx, nil, databaseDir, "go-bindata", "-pkg", "dmigrations", "-o", "../dmigrations/bindata.go", ".") + shouldWork(ctx, nil, dir, "sh", "./regen.sh") } // Vars shows the various variables that this magefile uses. func Vars() { - ver, err := gitTag() - qod.ANE(err) - qod.Printlnf("arches:\t%v", arches) - qod.Printlnf("bins:\t%v", bins) qod.Printlnf("goarch:\t%s", runtime.GOARCH) qod.Printlnf("goos:\t%s", runtime.GOOS) qod.Printlnf("wd:\t%s", wd) - qod.Printlnf("ver:\t%s", ver) } diff --git a/proto/eclier/route_twirp_eclier_backends_kill.lua b/proto/eclier/route_twirp_eclier_backends_kill.lua new file mode 100644 index 0000000..a200080 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_backends_kill.lua @@ -0,0 +1,31 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "backends:kill" +script.help = "Executes method kill on service backends for twirp package xeserv.us.route.backends" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for BackendId +fs:string("id", "", "value for message arg id") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.backends.kill(flags) + +end diff --git a/proto/eclier/route_twirp_eclier_backends_list.lua b/proto/eclier/route_twirp_eclier_backends_list.lua new file mode 100644 index 0000000..9e8d8bd --- /dev/null +++ b/proto/eclier/route_twirp_eclier_backends_list.lua @@ -0,0 +1,34 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "backends:list" +script.help = "Executes method list on service backends for twirp package xeserv.us.route.backends" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for BackendSelector +fs:string("domain", "", "value for message arg domain") +fs:string("user", "", "value for message arg user") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.backends.list(flags) + + print("bs:\t\t" .. tostring(resp.bs)) + print("backends:\t\t" .. tostring(resp.backends)) +end diff --git a/proto/eclier/route_twirp_eclier_routes_delete.lua b/proto/eclier/route_twirp_eclier_routes_delete.lua new file mode 100644 index 0000000..f48f561 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_routes_delete.lua @@ -0,0 +1,33 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "routes:delete" +script.help = "Executes method delete on service routes for twirp package xeserv.us.route.routes" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for Route +fs:string("id", "", "value for message arg id") +fs:string("creator", "", "value for message arg creator") +fs:string("host", "", "value for message arg host") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.routes.delete(flags) + +end diff --git a/proto/eclier/route_twirp_eclier_routes_get.lua b/proto/eclier/route_twirp_eclier_routes_get.lua new file mode 100644 index 0000000..1bfdac9 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_routes_get.lua @@ -0,0 +1,35 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "routes:get" +script.help = "Executes method get on service routes for twirp package xeserv.us.route.routes" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for GetRouteRequest +fs:string("unused", "", "value for message arg unused") +fs:string("id", "", "value for message arg id") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.routes.get(flags) + + print("id:\t\t" .. tostring(resp.id)) + print("creator:\t\t" .. tostring(resp.creator)) + print("host:\t\t" .. tostring(resp.host)) +end diff --git a/proto/eclier/route_twirp_eclier_routes_get_all.lua b/proto/eclier/route_twirp_eclier_routes_get_all.lua new file mode 100644 index 0000000..3f2be77 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_routes_get_all.lua @@ -0,0 +1,31 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "routes:get_all" +script.help = "Executes method get_all on service routes for twirp package xeserv.us.route.routes" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for Nil + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.routes.get_all(flags) + + print("routes:\t\t" .. tostring(resp.routes)) +end diff --git a/proto/eclier/route_twirp_eclier_routes_put.lua b/proto/eclier/route_twirp_eclier_routes_put.lua new file mode 100644 index 0000000..baad522 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_routes_put.lua @@ -0,0 +1,36 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "routes:put" +script.help = "Executes method put on service routes for twirp package xeserv.us.route.routes" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for Route +fs:string("id", "", "value for message arg id") +fs:string("creator", "", "value for message arg creator") +fs:string("host", "", "value for message arg host") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.routes.put(flags) + + print("id:\t\t" .. tostring(resp.id)) + print("creator:\t\t" .. tostring(resp.creator)) + print("host:\t\t" .. tostring(resp.host)) +end diff --git a/proto/eclier/route_twirp_eclier_tokens_deactivate.lua b/proto/eclier/route_twirp_eclier_tokens_deactivate.lua new file mode 100644 index 0000000..06cd8a0 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_tokens_deactivate.lua @@ -0,0 +1,34 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "tokens:deactivate" +script.help = "Executes method deactivate on service tokens for twirp package xeserv.us.route.tokens" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for Token +fs:string("id", "", "value for message arg id") +fs:string("body", "", "value for message arg body") +fs:strings("scopes", "", "value for message arg scopes") +fs:bool("active", "", "value for message arg active") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.tokens.deactivate(flags) + +end diff --git a/proto/eclier/route_twirp_eclier_tokens_delete.lua b/proto/eclier/route_twirp_eclier_tokens_delete.lua new file mode 100644 index 0000000..70a30e4 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_tokens_delete.lua @@ -0,0 +1,34 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "tokens:delete" +script.help = "Executes method delete on service tokens for twirp package xeserv.us.route.tokens" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for Token +fs:string("id", "", "value for message arg id") +fs:string("body", "", "value for message arg body") +fs:strings("scopes", "", "value for message arg scopes") +fs:bool("active", "", "value for message arg active") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.tokens.delete(flags) + +end diff --git a/proto/eclier/route_twirp_eclier_tokens_get.lua b/proto/eclier/route_twirp_eclier_tokens_get.lua new file mode 100644 index 0000000..e4e242a --- /dev/null +++ b/proto/eclier/route_twirp_eclier_tokens_get.lua @@ -0,0 +1,36 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "tokens:get" +script.help = "Executes method get on service tokens for twirp package xeserv.us.route.tokens" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for GetTokenRequest +fs:string("token", "", "value for message arg token") +fs:string("id", "", "value for message arg id") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.tokens.get(flags) + + print("id:\t\t" .. tostring(resp.id)) + print("body:\t\t" .. tostring(resp.body)) + print("scopes:\t\t" .. tostring(resp.scopes)) + print("active:\t\t" .. tostring(resp.active)) +end diff --git a/proto/eclier/route_twirp_eclier_tokens_get_all.lua b/proto/eclier/route_twirp_eclier_tokens_get_all.lua new file mode 100644 index 0000000..e039709 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_tokens_get_all.lua @@ -0,0 +1,31 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "tokens:get_all" +script.help = "Executes method get_all on service tokens for twirp package xeserv.us.route.tokens" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for Nil + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.tokens.get_all(flags) + + print("tokens:\t\t" .. tostring(resp.tokens)) +end diff --git a/proto/eclier/route_twirp_eclier_tokens_put.lua b/proto/eclier/route_twirp_eclier_tokens_put.lua new file mode 100644 index 0000000..50838b9 --- /dev/null +++ b/proto/eclier/route_twirp_eclier_tokens_put.lua @@ -0,0 +1,38 @@ +-- Code generated by protoc-gen-twirp_eclier v5.0.0DO NOT EDIT +-- source: route.proto + +script.verb = "tokens:put" +script.help = "Executes method put on service tokens for twirp package xeserv.us.route.tokens" +script.author = "machine-generated" +script.version = "v5.0.0" +script.usage = "" + +local flag = require "flag" +local svc = require "svc" + +local fs = flag.new() + +-- flags for Token +fs:string("id", "", "value for message arg id") +fs:string("body", "", "value for message arg body") +fs:strings("scopes", "", "value for message arg scopes") +fs:bool("active", "", "value for message arg active") + +script.usage = fs:usage() + +function run(arg) + if arg[1] == "-help" or arg[1] == "--help" then + print(fs:usage()) + return + end + + arg[0] = script.verb + local flags = fs:parse(arg) + + local resp = svc.tokens.put(flags) + + print("id:\t\t" .. tostring(resp.id)) + print("body:\t\t" .. tostring(resp.body)) + print("scopes:\t\t" .. tostring(resp.scopes)) + print("active:\t\t" .. tostring(resp.active)) +end diff --git a/proto/regen.sh b/proto/regen.sh index 3e1ec53..20f7809 100755 --- a/proto/regen.sh +++ b/proto/regen.sh @@ -3,4 +3,5 @@ protoc -I. \ --go_out=:. \ --twirp_out=. \ + --twirp_eclier_out=./eclier \ route.proto diff --git a/vendor/github.com/ThomasRooney/gexpect/LICENCE b/vendor/github.com/ThomasRooney/gexpect/LICENCE new file mode 100644 index 0000000..50adb0f --- /dev/null +++ b/vendor/github.com/ThomasRooney/gexpect/LICENCE @@ -0,0 +1,7 @@ +Copyright (C) 2014 Thomas Rooney + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/ThomasRooney/gexpect/README.md b/vendor/github.com/ThomasRooney/gexpect/README.md new file mode 100644 index 0000000..c30e0e2 --- /dev/null +++ b/vendor/github.com/ThomasRooney/gexpect/README.md @@ -0,0 +1,64 @@ +# Gexpect + +Gexpect is a pure golang expect-like module. + +It makes it simpler to create and control other terminal applications. + + child, err := gexpect.Spawn("python") + if err != nil { + panic(err) + } + child.Expect(">>>") + child.SendLine("print 'Hello World'") + child.Interact() + child.Close() + +## Examples + +`Spawn` handles the argument parsing from a string + + child.Spawn("/bin/sh -c 'echo \"my complicated command\" | tee log | cat > log2'") + child.ReadLine() // ReadLine() (string, error) + child.ReadUntil(' ') // ReadUntil(delim byte) ([]byte, error) + +`ReadLine`, `ReadUntil` and `SendLine` send strings from/to `stdout/stdin` respectively + + child, _ := gexpect.Spawn("cat") + child.SendLine("echoing process_stdin") // SendLine(command string) (error) + msg, _ := child.ReadLine() // msg = echoing process_stdin + +`Wait` and `Close` allow for graceful and ungraceful termination. + + child.Wait() // Waits until the child terminates naturally. + child.Close() // Sends a kill command + +`AsyncInteractChannels` spawns two go routines to pipe into and from `stdout`/`stdin`, allowing for some usecases to be a little simpler. + + child, _ := gexpect.Spawn("sh") + sender, receiver := child.AsyncInteractChannels() + sender <- "echo Hello World\n" // Send to stdin + line, open := <- receiver // Recieve a line from stdout/stderr + // When the subprocess stops (e.g. with child.Close()) , receiver is closed + if open { + fmt.Printf("Received %s", line) + } + +`ExpectRegex` uses golang's internal regex engine to wait until a match from the process with the given regular expression (or an error on process termination with no match). + + child, _ := gexpect.Spawn("echo accb") + match, _ := child.ExpectRegex("a..b") + // (match=true) + +`ExpectRegexFind` allows for groups to be extracted from process stdout. The first element is an array of containing the total matched text, followed by each subexpression group match. + + child, _ := gexpect.Spawn("echo 123 456 789") + result, _ := child.ExpectRegexFind("\d+ (\d+) (\d+)") + // result = []string{"123 456 789", "456", "789"} + +See `gexpect_test.go` and the `examples` folder for full syntax + +## Credits + + github.com/kballard/go-shellquote + github.com/kr/pty + KMP Algorithm: "http://blog.databigbang.com/searching-for-substrings-in-streams-a-slight-modification-of-the-knuth-morris-pratt-algorithm-in-haxe/" diff --git a/vendor/github.com/ThomasRooney/gexpect/gexpect.go b/vendor/github.com/ThomasRooney/gexpect/gexpect.go new file mode 100644 index 0000000..6e918cb --- /dev/null +++ b/vendor/github.com/ThomasRooney/gexpect/gexpect.go @@ -0,0 +1,449 @@ +// +build !windows + +package gexpect + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "os/exec" + "regexp" + "time" + "unicode/utf8" + + shell "github.com/kballard/go-shellquote" + "github.com/kr/pty" +) + +var ( + ErrEmptySearch = errors.New("empty search string") +) + +type ExpectSubprocess struct { + Cmd *exec.Cmd + buf *buffer + outputBuffer []byte +} + +type buffer struct { + f *os.File + b bytes.Buffer + collect bool + + collection bytes.Buffer +} + +func (buf *buffer) StartCollecting() { + buf.collect = true +} + +func (buf *buffer) StopCollecting() (result string) { + result = string(buf.collection.Bytes()) + buf.collect = false + buf.collection.Reset() + return result +} + +func (buf *buffer) Read(chunk []byte) (int, error) { + nread := 0 + if buf.b.Len() > 0 { + n, err := buf.b.Read(chunk) + if err != nil { + return n, err + } + if n == len(chunk) { + return n, nil + } + nread = n + } + fn, err := buf.f.Read(chunk[nread:]) + return fn + nread, err +} + +func (buf *buffer) ReadRune() (r rune, size int, err error) { + l := buf.b.Len() + + chunk := make([]byte, utf8.UTFMax) + if l > 0 { + n, err := buf.b.Read(chunk) + if err != nil { + return 0, 0, err + } + if utf8.FullRune(chunk[:n]) { + r, rL := utf8.DecodeRune(chunk) + if n > rL { + buf.PutBack(chunk[rL:n]) + } + if buf.collect { + buf.collection.WriteRune(r) + } + return r, rL, nil + } + } + // else add bytes from the file, then try that + for l < utf8.UTFMax { + fn, err := buf.f.Read(chunk[l : l+1]) + if err != nil { + return 0, 0, err + } + l = l + fn + + if utf8.FullRune(chunk[:l]) { + r, rL := utf8.DecodeRune(chunk) + if buf.collect { + buf.collection.WriteRune(r) + } + return r, rL, nil + } + } + return 0, 0, errors.New("File is not a valid UTF=8 encoding") +} + +func (buf *buffer) PutBack(chunk []byte) { + if len(chunk) == 0 { + return + } + if buf.b.Len() == 0 { + buf.b.Write(chunk) + return + } + d := make([]byte, 0, len(chunk)+buf.b.Len()) + d = append(d, chunk...) + d = append(d, buf.b.Bytes()...) + buf.b.Reset() + buf.b.Write(d) +} + +func SpawnAtDirectory(command string, directory string) (*ExpectSubprocess, error) { + expect, err := _spawn(command) + if err != nil { + return nil, err + } + expect.Cmd.Dir = directory + return _start(expect) +} + +func Command(command string) (*ExpectSubprocess, error) { + expect, err := _spawn(command) + if err != nil { + return nil, err + } + return expect, nil +} + +func (expect *ExpectSubprocess) Start() error { + _, err := _start(expect) + return err +} + +func Spawn(command string) (*ExpectSubprocess, error) { + expect, err := _spawn(command) + if err != nil { + return nil, err + } + return _start(expect) +} + +func (expect *ExpectSubprocess) Close() error { + if err := expect.Cmd.Process.Kill(); err != nil { + return err + } + if err := expect.buf.f.Close(); err != nil { + return err + } + return nil +} + +func (expect *ExpectSubprocess) AsyncInteractChannels() (send chan string, receive chan string) { + receive = make(chan string) + send = make(chan string) + + go func() { + for { + str, err := expect.ReadLine() + if err != nil { + close(receive) + return + } + receive <- str + } + }() + + go func() { + for { + select { + case sendCommand, exists := <-send: + { + if !exists { + return + } + err := expect.Send(sendCommand) + if err != nil { + receive <- "gexpect Error: " + err.Error() + return + } + } + } + } + }() + + return +} + +func (expect *ExpectSubprocess) ExpectRegex(regex string) (bool, error) { + return regexp.MatchReader(regex, expect.buf) +} + +func (expect *ExpectSubprocess) expectRegexFind(regex string, output bool) ([]string, string, error) { + re, err := regexp.Compile(regex) + if err != nil { + return nil, "", err + } + expect.buf.StartCollecting() + pairs := re.FindReaderSubmatchIndex(expect.buf) + stringIndexedInto := expect.buf.StopCollecting() + l := len(pairs) + numPairs := l / 2 + result := make([]string, numPairs) + for i := 0; i < numPairs; i += 1 { + result[i] = stringIndexedInto[pairs[i*2]:pairs[i*2+1]] + } + // convert indexes to strings + + if len(result) == 0 { + err = fmt.Errorf("ExpectRegex didn't find regex '%v'.", regex) + } else { + // The number in pairs[1] is an index of a first + // character outside the whole match + putBackIdx := pairs[1] + if len(stringIndexedInto) > putBackIdx { + stringToPutBack := stringIndexedInto[putBackIdx:] + stringIndexedInto = stringIndexedInto[:putBackIdx] + expect.buf.PutBack([]byte(stringToPutBack)) + } + } + return result, stringIndexedInto, err +} + +func (expect *ExpectSubprocess) expectTimeoutRegexFind(regex string, timeout time.Duration) (result []string, out string, err error) { + t := make(chan bool) + go func() { + result, out, err = expect.ExpectRegexFindWithOutput(regex) + t <- false + }() + go func() { + time.Sleep(timeout) + err = fmt.Errorf("ExpectRegex timed out after %v finding '%v'.\nOutput:\n%s", timeout, regex, expect.Collect()) + t <- true + }() + <-t + return result, out, err +} + +func (expect *ExpectSubprocess) ExpectRegexFind(regex string) ([]string, error) { + result, _, err := expect.expectRegexFind(regex, false) + return result, err +} + +func (expect *ExpectSubprocess) ExpectTimeoutRegexFind(regex string, timeout time.Duration) ([]string, error) { + result, _, err := expect.expectTimeoutRegexFind(regex, timeout) + return result, err +} + +func (expect *ExpectSubprocess) ExpectRegexFindWithOutput(regex string) ([]string, string, error) { + return expect.expectRegexFind(regex, true) +} + +func (expect *ExpectSubprocess) ExpectTimeoutRegexFindWithOutput(regex string, timeout time.Duration) ([]string, string, error) { + return expect.expectTimeoutRegexFind(regex, timeout) +} + +func buildKMPTable(searchString string) []int { + pos := 2 + cnd := 0 + length := len(searchString) + + var table []int + if length < 2 { + length = 2 + } + + table = make([]int, length) + table[0] = -1 + table[1] = 0 + + for pos < len(searchString) { + if searchString[pos-1] == searchString[cnd] { + cnd += 1 + table[pos] = cnd + pos += 1 + } else if cnd > 0 { + cnd = table[cnd] + } else { + table[pos] = 0 + pos += 1 + } + } + return table +} + +func (expect *ExpectSubprocess) ExpectTimeout(searchString string, timeout time.Duration) (e error) { + result := make(chan error) + go func() { + result <- expect.Expect(searchString) + }() + select { + case e = <-result: + case <-time.After(timeout): + e = fmt.Errorf("Expect timed out after %v waiting for '%v'.\nOutput:\n%s", timeout, searchString, expect.Collect()) + } + return e +} + +func (expect *ExpectSubprocess) Expect(searchString string) (e error) { + target := len(searchString) + if target < 1 { + return ErrEmptySearch + } + chunk := make([]byte, target*2) + if expect.outputBuffer != nil { + expect.outputBuffer = expect.outputBuffer[0:] + } + m := 0 + i := 0 + // Build KMP Table + table := buildKMPTable(searchString) + + for { + n, err := expect.buf.Read(chunk) + if n == 0 && err != nil { + return err + } + if expect.outputBuffer != nil { + expect.outputBuffer = append(expect.outputBuffer, chunk[:n]...) + } + offset := m + i + for m+i-offset < n { + if searchString[i] == chunk[m+i-offset] { + i += 1 + if i == target { + unreadIndex := m + i - offset + if len(chunk) > unreadIndex { + expect.buf.PutBack(chunk[unreadIndex:n]) + } + return nil + } + } else { + m += i - table[i] + if table[i] > -1 { + i = table[i] + } else { + i = 0 + } + } + } + } +} + +func (expect *ExpectSubprocess) Send(command string) error { + _, err := io.WriteString(expect.buf.f, command) + return err +} + +func (expect *ExpectSubprocess) Capture() { + if expect.outputBuffer == nil { + expect.outputBuffer = make([]byte, 0) + } +} + +func (expect *ExpectSubprocess) Collect() []byte { + collectOutput := make([]byte, len(expect.outputBuffer)) + copy(collectOutput, expect.outputBuffer) + expect.outputBuffer = nil + return collectOutput +} + +func (expect *ExpectSubprocess) SendLine(command string) error { + _, err := io.WriteString(expect.buf.f, command+"\r\n") + return err +} + +func (expect *ExpectSubprocess) Interact() { + defer expect.Cmd.Wait() + io.Copy(os.Stdout, &expect.buf.b) + go io.Copy(os.Stdout, expect.buf.f) + go io.Copy(expect.buf.f, os.Stdin) +} + +func (expect *ExpectSubprocess) ReadUntil(delim byte) ([]byte, error) { + join := make([]byte, 0, 512) + chunk := make([]byte, 255) + + for { + n, err := expect.buf.Read(chunk) + + for i := 0; i < n; i++ { + if chunk[i] == delim { + if len(chunk) > i+1 { + expect.buf.PutBack(chunk[i+1:n]) + } + return join, nil + } else { + join = append(join, chunk[i]) + } + } + + if err != nil { + return join, err + } + } +} + +func (expect *ExpectSubprocess) Wait() error { + return expect.Cmd.Wait() +} + +func (expect *ExpectSubprocess) ReadLine() (string, error) { + str, err := expect.ReadUntil('\n') + return string(str), err +} + +func _start(expect *ExpectSubprocess) (*ExpectSubprocess, error) { + f, err := pty.Start(expect.Cmd) + if err != nil { + return nil, err + } + expect.buf.f = f + + return expect, nil +} + +func _spawn(command string) (*ExpectSubprocess, error) { + wrapper := new(ExpectSubprocess) + + wrapper.outputBuffer = nil + + splitArgs, err := shell.Split(command) + if err != nil { + return nil, err + } + numArguments := len(splitArgs) - 1 + if numArguments < 0 { + return nil, errors.New("gexpect: No command given to spawn") + } + path, err := exec.LookPath(splitArgs[0]) + if err != nil { + return nil, err + } + + if numArguments >= 1 { + wrapper.Cmd = exec.Command(path, splitArgs[1:]...) + } else { + wrapper.Cmd = exec.Command(path) + } + wrapper.buf = new(buffer) + + return wrapper, nil +} diff --git a/vendor/github.com/ThomasRooney/gexpect/gexpect_test.go b/vendor/github.com/ThomasRooney/gexpect/gexpect_test.go new file mode 100644 index 0000000..8d3771a --- /dev/null +++ b/vendor/github.com/ThomasRooney/gexpect/gexpect_test.go @@ -0,0 +1,419 @@ +// +build !windows + +package gexpect + +import ( + "bytes" + "fmt" + "io/ioutil" + "strings" + "testing" + "time" +) + +func TestEmptySearchString(t *testing.T) { + t.Logf("Testing empty search string...") + child, err := Spawn("echo Hello World") + if err != nil { + t.Fatal(err) + } + err = child.Expect("") + if err != ErrEmptySearch { + t.Fatalf("Expected empty search error, got %v", err) + } +} + +func TestHelloWorld(t *testing.T) { + t.Logf("Testing Hello World... ") + child, err := Spawn("echo \"Hello World\"") + if err != nil { + t.Fatal(err) + } + err = child.Expect("Hello World") + if err != nil { + t.Fatal(err) + } +} + +func TestDoubleHelloWorld(t *testing.T) { + t.Logf("Testing Double Hello World... ") + child, err := Spawn(`sh -c "echo Hello World ; echo Hello ; echo Hi"`) + if err != nil { + t.Fatal(err) + } + err = child.Expect("Hello World") + if err != nil { + t.Fatal(err) + } + err = child.Expect("Hello") + if err != nil { + t.Fatal(err) + } + err = child.Expect("Hi") + if err != nil { + t.Fatal(err) + } +} + +func TestHelloWorldFailureCase(t *testing.T) { + t.Logf("Testing Hello World Failure case... ") + child, err := Spawn("echo \"Hello World\"") + if err != nil { + t.Fatal(err) + } + err = child.Expect("YOU WILL NEVER FIND ME") + if err != nil { + return + } + t.Fatal("Expected an error for TestHelloWorldFailureCase") +} + +func TestBiChannel(t *testing.T) { + + t.Logf("Testing BiChannel screen... ") + child, err := Spawn("cat") + if err != nil { + t.Fatal(err) + } + sender, receiver := child.AsyncInteractChannels() + wait := func(str string) { + for { + msg, open := <-receiver + if !open { + return + } + if strings.Contains(msg, str) { + return + } + } + } + + endlChar := fmt.Sprintln("") + sender <- fmt.Sprintf("echo%v", endlChar) + wait("echo") + sender <- fmt.Sprintf("echo2%v", endlChar) + wait("echo2") + child.Close() + child.Wait() +} + +func TestCommandStart(t *testing.T) { + t.Logf("Testing Command... ") + + // Doing this allows you to modify the cmd struct prior to execution, for example to add environment variables + child, err := Command("echo 'Hello World'") + if err != nil { + t.Fatal(err) + } + child.Start() + child.Expect("Hello World") +} + +var regexMatchTests = []struct { + re string + good string + bad string +}{ + {`a`, `a`, `b`}, + {`.b`, `ab`, `ac`}, + {`a+hello`, `aaaahello`, `bhello`}, + {`(hello|world)`, `hello`, `unknown`}, + {`(hello|world)`, `world`, `unknown`}, + {"\u00a9", "\u00a9", `unknown`}, // 2 bytes long unicode character "copyright sign" +} + +func TestRegexMatch(t *testing.T) { + t.Logf("Testing Regular Expression Matching... ") + for _, tt := range regexMatchTests { + runTest := func(input string) bool { + var match bool + child, err := Spawn("echo \"" + input + "\"") + if err != nil { + t.Fatal(err) + } + match, err = child.ExpectRegex(tt.re) + if err != nil { + t.Fatal(err) + } + return match + } + if !runTest(tt.good) { + t.Errorf("Regex Not matching [%#q] with pattern [%#q]", tt.good, tt.re) + } + if runTest(tt.bad) { + t.Errorf("Regex Matching [%#q] with pattern [%#q]", tt.bad, tt.re) + } + } +} + +var regexFindTests = []struct { + re string + input string + matches []string +}{ + {`he(l)lo wo(r)ld`, `hello world`, []string{"hello world", "l", "r"}}, + {`(a)`, `a`, []string{"a", "a"}}, + {`so.. (hello|world)`, `so.. hello`, []string{"so.. hello", "hello"}}, + {`(a+)hello`, `aaaahello`, []string{"aaaahello", "aaaa"}}, + {`\d+ (\d+) (\d+)`, `123 456 789`, []string{"123 456 789", "456", "789"}}, + {`\d+ (\d+) (\d+)`, "\u00a9 123 456 789 \u00a9", []string{"123 456 789", "456", "789"}}, // check unicode characters +} + +func TestRegexFind(t *testing.T) { + t.Logf("Testing Regular Expression Search... ") + for _, tt := range regexFindTests { + runTest := func(input string) []string { + child, err := Spawn("echo \"" + input + "\"") + if err != nil { + t.Fatal(err) + } + matches, err := child.ExpectRegexFind(tt.re) + if err != nil { + t.Fatal(err) + } + return matches + } + matches := runTest(tt.input) + if len(matches) != len(tt.matches) { + t.Fatalf("Regex not producing the expected number of patterns.. got[%d] ([%s]) expected[%d] ([%s])", + len(matches), strings.Join(matches, ","), + len(tt.matches), strings.Join(tt.matches, ",")) + } + for i, _ := range matches { + if matches[i] != tt.matches[i] { + t.Errorf("Regex Expected group [%s] and got group [%s] with pattern [%#q] and input [%s]", + tt.matches[i], matches[i], tt.re, tt.input) + } + } + } +} + +func TestReadLine(t *testing.T) { + t.Logf("Testing ReadLine...") + + child, err := Spawn("echo \"foo\nbar\"") + + if err != nil { + t.Fatal(err) + } + s, err := child.ReadLine() + + if err != nil { + t.Fatal(err) + } + if s != "foo\r" { + t.Fatalf("expected 'foo\\r', got '%s'", s) + } + s, err = child.ReadLine() + if err != nil { + t.Fatal(err) + } + if s != "bar\r" { + t.Fatalf("expected 'bar\\r', got '%s'", s) + } +} + +func TestRegexWithOutput(t *testing.T) { + t.Logf("Testing Regular Expression search with output...") + + s := "You will not find me" + p, err := Spawn("echo -n " + s) + if err != nil { + t.Fatalf("Cannot exec rkt: %v", err) + } + searchPattern := `I should not find you` + result, out, err := p.ExpectRegexFindWithOutput(searchPattern) + if err == nil { + t.Fatalf("Shouldn't have found `%v` in `%v`", searchPattern, out) + } + if s != out { + t.Fatalf("Child output didn't match: %s", out) + } + + err = p.Wait() + if err != nil { + t.Fatalf("Child didn't terminate correctly: %v", err) + } + + p, err = Spawn("echo You will find me") + if err != nil { + t.Fatalf("Cannot exec rkt: %v", err) + } + searchPattern = `.*(You will).*` + result, out, err = p.ExpectRegexFindWithOutput(searchPattern) + if err != nil || result[1] != "You will" { + t.Fatalf("Did not find pattern `%v` in `%v'\n", searchPattern, out) + } + err = p.Wait() + if err != nil { + t.Fatalf("Child didn't terminate correctly: %v", err) + } +} + +func TestRegexTimeoutWithOutput(t *testing.T) { + t.Logf("Testing Regular Expression search with timeout and output...") + + seconds := 2 + timeout := time.Duration(seconds-1) * time.Second + + p, err := Spawn(fmt.Sprintf("sh -c 'sleep %d && echo You find me'", seconds)) + if err != nil { + t.Fatalf("Cannot exec rkt: %v", err) + } + searchPattern := `find me` + result, out, err := p.ExpectTimeoutRegexFindWithOutput(searchPattern, timeout) + if err == nil { + t.Fatalf("Shouldn't have finished call with result: %v", result) + } + + seconds = 2 + timeout = time.Duration(seconds+1) * time.Second + + p, err = Spawn(fmt.Sprintf("sh -c 'sleep %d && echo You find me'", seconds)) + if err != nil { + t.Fatalf("Cannot exec rkt: %v", err) + } + searchPattern = `find me` + result, out, err = p.ExpectTimeoutRegexFindWithOutput(searchPattern, timeout) + if err != nil { + t.Fatalf("Didn't find %v in output: %v", searchPattern, out) + } +} + +func TestRegexFindNoExcessBytes(t *testing.T) { + t.Logf("Testing Regular Expressions returning output with no excess strings") + repeats := 50 + tests := []struct { + desc string + loopBody string + searchPattern string + expectFullTmpl string + unmatchedData string + }{ + { + desc: `matching lines line by line with $ at the end of the regexp`, + loopBody: `echo "prefix: ${i} line"`, + searchPattern: `(?m)^prefix:\s+(\d+) line\s??$`, + expectFullTmpl: `prefix: %d line`, + unmatchedData: "\n", + // the "$" char at the end of regexp does not + // match the \n, so it is left as an unmatched + // data + }, + { + desc: `matching lines line by line with \n at the end of the regexp`, + loopBody: `echo "prefix: ${i} line"`, + searchPattern: `(?m)^prefix:\s+(\d+) line\s??\n`, + expectFullTmpl: `prefix: %d line`, + unmatchedData: "", + }, + { + desc: `matching chunks in single line chunk by chunk`, + loopBody: `printf "a ${i} b"`, + searchPattern: `a\s+(\d+)\s+b`, + expectFullTmpl: `a %d b`, + unmatchedData: "", + }, + } + seqCmd := fmt.Sprintf("`seq 1 %d`", repeats) + shCmdTmpl := fmt.Sprintf(`sh -c 'for i in %s; do %%s; done'`, seqCmd) + for _, tt := range tests { + t.Logf("Test: %s", tt.desc) + shCmd := fmt.Sprintf(shCmdTmpl, tt.loopBody) + t.Logf("Running command: %s", shCmd) + p, err := Spawn(shCmd) + if err != nil { + t.Fatalf("Cannot exec shell script: %v", err) + } + defer func() { + if err := p.Wait(); err != nil { + t.Fatalf("shell script didn't terminate correctly: %v", err) + } + }() + for i := 1; i <= repeats; i++ { + matches, output, err := p.ExpectRegexFindWithOutput(tt.searchPattern) + if err != nil { + t.Fatalf("Failed to get the match number %d: %v", i, err) + } + if len(matches) != 2 { + t.Fatalf("Expected only 2 matches, got %d", len(matches)) + } + full := strings.TrimSpace(matches[0]) + expFull := fmt.Sprintf(tt.expectFullTmpl, i) + partial := matches[1] + expPartial := fmt.Sprintf("%d", i) + if full != expFull { + t.Fatalf("Did not the expected full match %q, got %q", expFull, full) + } + if partial != expPartial { + t.Fatalf("Did not the expected partial match %q, got %q", expPartial, partial) + } + // The output variable usually contains the + // unmatched data followed by the whole match. + // The first line is special as it has no data + // preceding it. + var expectedOutput string + if i == 1 || tt.unmatchedData == "" { + expectedOutput = matches[0] + } else { + expectedOutput = fmt.Sprintf("%s%s", tt.unmatchedData, matches[0]) + } + if output != expectedOutput { + t.Fatalf("The collected output %q should be the same as the whole match %q", output, expectedOutput) + } + } + } +} + +func TestBufferReadRune(t *testing.T) { + tests := []struct { + bufferContent []byte + fileContent []byte + expectedRune rune + }{ + // unicode "copyright char" is \u00a9 is encoded as two bytes in utf8 0xc2 0xa9 + {[]byte{0xc2, 0xa9}, []byte{}, '\u00a9'}, // whole rune is already in buffer.b + {[]byte{0xc2}, []byte{0xa9}, '\u00a9'}, // half of is in the buffer.b and another half still in buffer.f (file) + {[]byte{}, []byte{0xc2, 0xa9}, '\u00a9'}, // whole rune is the file + // some random noise in the end of file + {[]byte{0xc2, 0xa9}, []byte{0x20, 0x20, 0x20, 0x20}, '\u00a9'}, + {[]byte{0xc2}, []byte{0xa9, 0x20, 0x20, 0x20, 0x20}, '\u00a9'}, + {[]byte{}, []byte{0xc2, 0xa9, 0x20, 0x20, 0x20, 0x20}, '\u00a9'}, + } + + for i, tt := range tests { + + // prepare tmp file with fileContent + f, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + n, err := f.Write(tt.fileContent) + if err != nil { + t.Fatal(err) + } + if n != len(tt.fileContent) { + t.Fatal("expected fileContent written to temp file") + } + _, err = f.Seek(0, 0) + if err != nil { + t.Fatal(err) + } + + // new buffer + buf := buffer{f: f, b: *bytes.NewBuffer(tt.bufferContent)} + + // call ReadRune + r, size, err := buf.ReadRune() + + if r != tt.expectedRune { + t.Fatalf("#%d: expected rune %+q but go is %+q", i, tt.expectedRune, r) + } + + if size != len(string(tt.expectedRune)) { + t.Fatalf("#%d: expected rune %d bytes long but got just %d bytes long", i, len(string(tt.expectedRune)), size) + } + + } + +} diff --git a/vendor/github.com/Xe/eclier/.gitignore b/vendor/github.com/Xe/eclier/.gitignore new file mode 100644 index 0000000..28f7b7d --- /dev/null +++ b/vendor/github.com/Xe/eclier/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +doc \ No newline at end of file diff --git a/vendor/github.com/Xe/eclier/Gopkg.lock b/vendor/github.com/Xe/eclier/Gopkg.lock new file mode 100644 index 0000000..4e650ea --- /dev/null +++ b/vendor/github.com/Xe/eclier/Gopkg.lock @@ -0,0 +1,223 @@ +memo = "b40e77e679fec09015bfda27b7d1fc37f4ba6240cbe95cf9fb88b5c56e40ebdf" + +[[projects]] + branch = "master" + name = "github.com/ThomasRooney/gexpect" + packages = ["."] + revision = "5482f03509440585d13d8f648989e05903001842" + +[[projects]] + branch = "master" + name = "github.com/Xe/x" + packages = ["tools/glue/libs/gluaexpect","tools/glue/libs/gluasimplebox"] + revision = "d0ebe3970f361daa31a135f1e0c7304eb1442f61" + +[[projects]] + branch = "master" + name = "github.com/ailncode/gluaxmlpath" + packages = ["."] + revision = "6ce478ecb4a60c4fc8929838e0b21b7fb7ca7440" + +[[projects]] + branch = "master" + name = "github.com/brandur/simplebox" + packages = ["."] + revision = "84e9865bb03ad38c464043bf5382ce8c68ca5f0c" + +[[projects]] + branch = "master" + name = "github.com/cjoudrey/gluahttp" + packages = ["."] + revision = "b4bfe0c50fea948dcbf3966e120996d6607bbd89" + +[[projects]] + branch = "master" + name = "github.com/cjoudrey/gluaurl" + packages = ["."] + revision = "31cbb9bef199454415879f2e6d609d1136d60cad" + +[[projects]] + branch = "master" + name = "github.com/cyberdelia/heroku-go" + packages = ["v3"] + revision = "bb8b6b1e9656ec0728638961f8e8b4211fee735d" + +[[projects]] + branch = "master" + name = "github.com/dickeyxxx/netrc" + packages = ["."] + revision = "3acf1b3de25d89c7688c63bb45f6b07f566555ec" + +[[projects]] + branch = "master" + name = "github.com/google/go-querystring" + packages = ["query"] + revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a" + +[[projects]] + branch = "master" + name = "github.com/howeyc/gopass" + packages = ["."] + revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8" + +[[projects]] + branch = "master" + name = "github.com/kballard/go-shellquote" + packages = ["."] + revision = "d8ec1a69a250a17bb0e419c386eac1f3711dc142" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluaenv" + packages = ["."] + revision = "2888db6bbe38923d59c42e443895875cc8ce0820" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluafs" + packages = ["."] + revision = "01391ed2d7ab89dc80157605b073403f960aa223" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluaquestion" + packages = ["."] + revision = "311437c29ba54d027ad2af383661725ae2bfdcdc" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluassh" + packages = ["."] + revision = "2a7bd48d7568de8230c87ac1ef4a4c481e45814d" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluatemplate" + packages = ["."] + revision = "d9e2c9d6b00f069a9da377a9ac529c827c1c7d71" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluayaml" + packages = ["."] + revision = "6fe413d49d73d785510ecf1529991ab0573e96c7" + +[[projects]] + branch = "master" + name = "github.com/kr/fs" + packages = ["."] + revision = "2788f0dbd16903de03cb8186e5c7d97b69ad387b" + +[[projects]] + branch = "master" + name = "github.com/kr/pty" + packages = ["."] + revision = "ce7fa45920dc37a92de8377972e52bc55ffa8d57" + +[[projects]] + branch = "master" + name = "github.com/mattn/go-runewidth" + packages = ["."] + revision = "737072b4e32b7a5018b4a7125da8d12de90e8045" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/mapstructure" + packages = ["."] + revision = "cc8532a8e9a55ea36402aa21efdf403a60d34096" + +[[projects]] + branch = "master" + name = "github.com/olekukonko/tablewriter" + packages = ["."] + revision = "44e365d423f4f06769182abfeeae2b91be9d529b" + +[[projects]] + branch = "master" + name = "github.com/otm/gluaflag" + packages = ["."] + revision = "078088de689148194436293886e8e39809167332" + +[[projects]] + branch = "master" + name = "github.com/otm/gluash" + packages = ["."] + revision = "e145c563986f0b91f740a758a84bca46c163aec7" + +[[projects]] + name = "github.com/pborman/uuid" + packages = ["."] + revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53" + version = "v1.1" + +[[projects]] + branch = "master" + name = "github.com/pkg/sftp" + packages = ["."] + revision = "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5" + +[[projects]] + branch = "master" + name = "github.com/yookoala/realpath" + packages = ["."] + revision = "c416d99ab5ed256fa30c1f3bab73152deb59bb69" + +[[projects]] + branch = "master" + name = "github.com/yuin/gluamapper" + packages = ["."] + revision = "d836955830e75240d46ce9f0e6d148d94f2e1d3a" + +[[projects]] + branch = "master" + name = "github.com/yuin/gluare" + packages = ["."] + revision = "8e2742cd1bf2b904720ac66eca3c2091b2ea0720" + +[[projects]] + branch = "master" + name = "github.com/yuin/gopher-lua" + packages = [".","ast","parse","pm"] + revision = "33ebc07735566cd0c3c4b69e2839d522cc389852" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["curve25519","ed25519","ed25519/internal/edwards25519","nacl/secretbox","poly1305","salsa20/salsa","ssh","ssh/agent","ssh/terminal"] + revision = "dd85ac7e6a88fc6ca420478e934de5f1a42dd3c6" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = ["html","html/atom"] + revision = "66aacef3dd8a676686c7ae3716979581e8b03c47" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix"] + revision = "9ccfe848b9db8435a24c424abbc07a921adf1df5" + +[[projects]] + branch = "v2" + name = "gopkg.in/xmlpath.v2" + packages = ["."] + revision = "860cbeca3ebcc600db0b213c0e83ad6ce91f5739" + +[[projects]] + branch = "master" + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b" + +[[projects]] + branch = "master" + name = "layeh.com/gopher-json" + packages = ["."] + revision = "c128cc74278be889c4381681712931976fe0d88b" + +[[projects]] + branch = "master" + name = "layeh.com/gopher-luar" + packages = ["."] + revision = "80196fe2abc5682963fc7a5261f5a5d77509938b" diff --git a/vendor/github.com/Xe/eclier/Gopkg.toml b/vendor/github.com/Xe/eclier/Gopkg.toml new file mode 100644 index 0000000..4219096 --- /dev/null +++ b/vendor/github.com/Xe/eclier/Gopkg.toml @@ -0,0 +1,72 @@ + +[[dependencies]] + branch = "master" + name = "github.com/Xe/x" + +[[dependencies]] + branch = "master" + name = "github.com/ailncode/gluaxmlpath" + +[[dependencies]] + branch = "master" + name = "github.com/cjoudrey/gluahttp" + +[[dependencies]] + branch = "master" + name = "github.com/cjoudrey/gluaurl" + +[[dependencies]] + branch = "master" + name = "github.com/dickeyxxx/netrc" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluaenv" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluafs" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluaquestion" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluassh" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluatemplate" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluayaml" + +[[dependencies]] + branch = "master" + name = "github.com/olekukonko/tablewriter" + +[[dependencies]] + branch = "master" + name = "github.com/otm/gluaflag" + +[[dependencies]] + branch = "master" + name = "github.com/otm/gluash" + +[[dependencies]] + branch = "master" + name = "github.com/yuin/gluare" + +[[dependencies]] + branch = "master" + name = "github.com/yuin/gopher-lua" + +[[dependencies]] + branch = "master" + name = "layeh.com/gopher-json" + +[[dependencies]] + branch = "master" + name = "layeh.com/gopher-luar" diff --git a/vendor/github.com/Xe/eclier/LICENSE b/vendor/github.com/Xe/eclier/LICENSE new file mode 100644 index 0000000..1625c17 --- /dev/null +++ b/vendor/github.com/Xe/eclier/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. \ No newline at end of file diff --git a/vendor/github.com/Xe/eclier/README.md b/vendor/github.com/Xe/eclier/README.md new file mode 100644 index 0000000..7f99158 --- /dev/null +++ b/vendor/github.com/Xe/eclier/README.md @@ -0,0 +1,38 @@ +# eclier + +Pronounced like eclair + +The core of a command line application allowing for trivial user extension. + +Every command and subcommand is its own `.lua` file that is either shipped as +part of the built-in cartridge of commands or a plugin that the user installs. + +The core contains the following: + +- A module loading system for preparing different commands for use +- The core subcommand router + +## How to write plugins + +Create a new file in the script home named after the plugin subcommand, for +example: `scripts/hello.lua`: + +```lua +script.verb = "hello" +script.help = "prints everyone's favorite hello world message" +script.author = "Xe" -- put your github username here +script.version = "1.0" +script.usage = "" + +function(run) + print "Hello, world!" +end +``` + +And then run it using the example shell cli: + +```console +~/go/src/github.com/Xe/eclier:master λ go run ./example/main.go hello +Hello, world! +``` + diff --git a/vendor/github.com/Xe/eclier/builtin.go b/vendor/github.com/Xe/eclier/builtin.go new file mode 100644 index 0000000..3e63ca7 --- /dev/null +++ b/vendor/github.com/Xe/eclier/builtin.go @@ -0,0 +1,113 @@ +package eclier + +import ( + "context" + "flag" + "fmt" +) + +// Constants for built-in commands. +const ( + BuiltinScriptPath = "" + BuiltinAuthor = "" + BuiltinVersion = "" +) + +type pluginCommand struct { + r *Router + fs *flag.FlagSet + + dontShowBuiltin *bool +} + +// Close is a no-op. +func (p *pluginCommand) Close() error { return nil } + +// Init sets up the flags for this command. +func (p *pluginCommand) Init() { + p.fs = flag.NewFlagSet(p.Verb(), flag.ExitOnError) + + p.dontShowBuiltin = p.fs.Bool("no-builtin", false, "if set, don't show built-in commands") +} + +// ScriptPath returns the built-in script path. +func (p *pluginCommand) ScriptPath() string { return BuiltinScriptPath } + +// Verb returns the command verb. +func (p *pluginCommand) Verb() string { return "plugins" } + +// Help returns the command help +func (p *pluginCommand) Help() string { + return `plugin lists all of the loaded commands and their script paths.` +} + +func (p *pluginCommand) Usage() string { + return ` -no-builtin + if set, don't show built-in commands` +} + +func (p *pluginCommand) Author() string { return BuiltinAuthor } + +func (p *pluginCommand) Version() string { return BuiltinVersion } + +// Run executes the command. +func (p *pluginCommand) Run(ctx context.Context, arg []string) error { + p.fs.Parse(arg) + + for _, c := range p.r.cmds { + if c.ScriptPath() == BuiltinScriptPath && *p.dontShowBuiltin { + continue + } + + fmt.Printf("%s\t%s\n", c.Verb(), c.ScriptPath()) + } + + return nil +} + +// NewBuiltinCommand makes it easier to write core commands for eclier. +func NewBuiltinCommand(verb, help, usage string, doer func(context.Context, []string) error) Command { + return &commandFunc{ + verb: verb, + help: help, + usage: usage, + doer: doer, + } +} + +// commandFunc is a simple alias for creating builtin commands. +type commandFunc struct { + verb string + help string + usage string + doer func(context.Context, []string) error +} + +// Close deallocates resources set up by the initialization of the command. +func (c *commandFunc) Close() error { return nil } + +// Init is a no-op. +func (c *commandFunc) Init() {} + +// ScriptPath returns the built-in script path. +func (c *commandFunc) ScriptPath() string { return BuiltinScriptPath } + +// Verb returns the command verb. +func (c *commandFunc) Verb() string { return c.verb } + +// Help returns the command help. +func (c *commandFunc) Help() string { return c.help } + +// Usage returns the command usage. +func (c *commandFunc) Usage() string { return c.usage } + +// Author returns the built-in author. +func (c *commandFunc) Author() string { return BuiltinAuthor } + +// Version returns the built-in version. +func (c *commandFunc) Version() string { return BuiltinVersion } + +// Run runs the command handler. +func (c *commandFunc) Run(ctx context.Context, arg []string) error { + return c.doer(ctx, arg) +} diff --git a/vendor/github.com/Xe/eclier/command.go b/vendor/github.com/Xe/eclier/command.go new file mode 100644 index 0000000..9a20031 --- /dev/null +++ b/vendor/github.com/Xe/eclier/command.go @@ -0,0 +1,18 @@ +package eclier + +import ( + "context" +) + +// Command is an individual subcommand. +type Command interface { + Close() error + Init() + ScriptPath() string + Verb() string + Help() string + Usage() string + Author() string + Version() string + Run(ctx context.Context, arg []string) error +} diff --git a/vendor/github.com/Xe/eclier/config.ld b/vendor/github.com/Xe/eclier/config.ld new file mode 100644 index 0000000..3ccf99c --- /dev/null +++ b/vendor/github.com/Xe/eclier/config.ld @@ -0,0 +1,8 @@ +file = { + "./internal/gluanetrc/netrc.lua", + "./internal/gluaheroku/heroku.lua", +} + +title = "eclier lua libraries" +project = "eclier" +description = "The lua libraries created for eclier demos and common utility." diff --git a/vendor/github.com/Xe/eclier/router.go b/vendor/github.com/Xe/eclier/router.go new file mode 100644 index 0000000..0669dcd --- /dev/null +++ b/vendor/github.com/Xe/eclier/router.go @@ -0,0 +1,140 @@ +package eclier + +import ( + "context" + "fmt" + "log" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/olekukonko/tablewriter" + lua "github.com/yuin/gopher-lua" +) + +// Router is the main subcommand router for eclier. At a high level what this is +// doing is similar to http.ServeMux, but for CLI commands instead of HTTP handlers. +type Router struct { + lock sync.Mutex + cmds map[string]Command + + // configured data + gluaCreationHook func(*lua.LState) + scriptHomes []string + cartridge map[string]string +} + +// NewRouter creates a new instance of Router and sets it up for use. +func NewRouter(opts ...RouterOption) (*Router, error) { + r := &Router{ + cmds: map[string]Command{}, + cartridge: map[string]string{}, + } + + for _, opt := range opts { + opt(r) + } + + // scan r.scriptHome for lua scripts, load them into their own lua states and + // make a wrapper around them for the Command type. + + for _, home := range r.scriptHomes { + err := filepath.Walk(home, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Printf("error in arg: %v", err) + return err + } + + if strings.HasSuffix(info.Name(), ".lua") { + fname := filepath.Join(home, info.Name()) + fin, err := os.Open(fname) + if err != nil { + return err + } + defer fin.Close() + + c := newGluaCommand(r.gluaCreationHook, fname, fin) + + r.cmds[c.Verb()] = c + } + + return nil + }) + + if err != nil { + return nil, err + } + } + + var helpCommand Command = NewBuiltinCommand("help", "shows help for subcommands", "[subcommand]", func(ctx context.Context, arg []string) error { + if len(arg) == 0 { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Verb", "Author", "Version", "Help"}) + + for _, cmd := range r.cmds { + table.Append([]string{cmd.Verb(), cmd.Author(), cmd.Version(), cmd.Help()}) + } + + table.Render() + return nil + } + + cmd, ok := r.cmds[arg[0]] + if !ok { + fmt.Printf("can't find help for %s", arg[0]) + os.Exit(2) + } + + fmt.Printf("Verb: %s\nAuthor: %s\nVersion: %s\nHelp: %s\nUsage: %s %s\n", cmd.Verb(), cmd.Author(), cmd.Version(), cmd.Help(), cmd.Verb(), cmd.Usage()) + return nil + }) + + r.cmds["plugins"] = &pluginCommand{r: r} + r.cmds["help"] = helpCommand + + return r, nil +} + +// Run executes a single command given in slot 0 of the argument array. +func (r *Router) Run(ctx context.Context, arg []string) error { + r.lock.Lock() + defer r.lock.Unlock() + + if len(arg) == 0 { + fmt.Printf("please specify a subcommand, such as `%s help`\n", filepath.Base(os.Args[0])) + os.Exit(2) + } + + cmd := arg[0] + arg = arg[1:] + + ci, ok := r.cmds[cmd] + if !ok { + fmt.Printf("No such command %s could be run.\n", cmd) + os.Exit(2) + } + + ci.Init() + return ci.Run(ctx, arg) +} + +// RouterOption is a functional option for Router. +type RouterOption func(*Router) + +// WithScriptHome sets the router's script home to the given directory. This is +// where lua files will be walked and parsed. +func WithScriptHome(dir string) RouterOption { + return func(r *Router) { + r.scriptHomes = append(r.scriptHomes, dir) + } +} + +// WithGluaCreationHook adds a custom bit of code that runs every time a new +// gopher-lua LState is created. This allows users of this library to register +// custom libraries to the pile of states. +func WithGluaCreationHook(hook func(*lua.LState)) RouterOption { + return func(r *Router) { + r.gluaCreationHook = hook + } +} diff --git a/vendor/github.com/Xe/eclier/script.go b/vendor/github.com/Xe/eclier/script.go new file mode 100644 index 0000000..d4b88b2 --- /dev/null +++ b/vendor/github.com/Xe/eclier/script.go @@ -0,0 +1,92 @@ +package eclier + +import ( + "context" + "errors" + "io" + "io/ioutil" + "sync" + + lua "github.com/yuin/gopher-lua" + luar "layeh.com/gopher-luar" +) + +type Script struct { + Verb string + Help string + Usage string + Author string + Version string +} + +type gluaCommand struct { + sync.Mutex + script *Script + L *lua.LState + + filename string +} + +func newGluaCommand(preload func(*lua.LState), filename string, r io.Reader) Command { + L := lua.NewState() + preload(L) + + script := &Script{} + L.SetGlobal("script", luar.New(L, script)) + + data, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + + err = L.DoString(string(data)) + if err != nil { + panic(err) + } + + return &gluaCommand{script: script, L: L, filename: filename} +} + +func (g *gluaCommand) Close() error { + g.L.Close() + return nil +} + +func (g *gluaCommand) Init() {} + +func (g *gluaCommand) ScriptPath() string { return g.filename } + +func (g *gluaCommand) Verb() string { return g.script.Verb } + +func (g *gluaCommand) Help() string { return g.script.Help } + +func (g *gluaCommand) Usage() string { return g.script.Usage } + +func (g *gluaCommand) Author() string { return g.script.Author } + +func (g *gluaCommand) Version() string { return g.script.Version } + +func (g *gluaCommand) Run(ctx context.Context, arg []string) error { + runf := g.L.GetGlobal("run") + + if runf.Type() == lua.LTNil { + return errors.New("no global function run in this script") + } + + tab := g.L.NewTable() + + for _, a := range arg { + tab.Append(lua.LString(a)) + } + + err := g.L.CallByParam(lua.P{ + Fn: runf, + NRet: 0, + Protect: false, + }, tab) + if err != nil { + panic(err) + } + + return nil +} diff --git a/vendor/github.com/Xe/x/tools/glue/CHEATSHEET.md b/vendor/github.com/Xe/x/tools/glue/CHEATSHEET.md new file mode 100644 index 0000000..0536567 --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/CHEATSHEET.md @@ -0,0 +1,661 @@ +## `json` + +```lua +local json = require "json" +``` + +Json encoder/decoder + +The following functions are exposed by the library: + + decode(string): Decodes a JSON string. Returns nil and an error string if + the string could not be decoded. + encode(value): Encodes a value into a JSON string. Returns nil and an error + string if the value could not be encoded. + +## `xmlpath` + +```lua +local xmlpath = require "xmlpath" +``` + +XMLPath style iteration + + xml ="x1x2x3" + local xmlpath = require("xmlpath") + node,err = xmlpath.loadxml(xml) + path,err = xmlpath.compile("//book") + + it = path:iter(node) + for k,v in pairs(it) do + print(k,v:string()) + end + +## `http` + +```lua +local http = require("http") +``` + +HTTP client library + +### API + +- [`http.delete(url [, options])`](#httpdeleteurl--options) +- [`http.get(url [, options])`](#httpgeturl--options) +- [`http.head(url [, options])`](#httpheadurl--options) +- [`http.patch(url [, options])`](#httppatchurl--options) +- [`http.post(url [, options])`](#httpposturl--options) +- [`http.put(url [, options])`](#httpputurl--options) +- [`http.request(method, url [, options])`](#httprequestmethod-url--options) +- [`http.request_batch(requests)`](#httprequest_batchrequests) +- [`http.response`](#httpresponse) + +#### http.delete(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +#### http.get(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +#### http.head(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +#### http.patch(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +#### http.post(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +#### http.put(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +#### http.request(method, url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| method | String | The HTTP request method | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +#### http.request_batch(requests) + +**Attributes** + +| Name | Type | Description | +| -------- | ----- | ----------- | +| requests | Table | A table of requests to send. Each request item is by itself a table containing [http.request](#httprequestmethod-url--options) parameters for the request | + +**Returns** + +[[http.response](#httpresponse)] or ([[http.response](#httpresponse)], [error message]) + +#### http.response + +The `http.response` table contains information about a completed HTTP request. + +**Attributes** + +| Name | Type | Description | +| ----------- | ------ | ----------- | +| body | String | The HTTP response body | +| body_size | Number | The size of the HTTP reponse body in bytes | +| headers | Table | The HTTP response headers | +| cookies | Table | The cookies sent by the server in the HTTP response | +| status_code | Number | The HTTP response status code | +| url | String | The final URL the request ended pointing to after redirects | + +## `url` + +```lua +local url = require "url" +``` + +URL parsing library + +### API + +- [`url.parse(url)`](#urlparseurl) +- [`url.build(options)`](#urlbuildoptions) +- [`url.build_query_string(query_params)`](#urlbuild_query_stringquery_params) +- [`url.resolve(from, to)`](#urlresolvefrom-to) + +#### url.parse(url) + +Parse URL into a table of key/value components. + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL to parsed | + +**Returns** + +Table with parsed URL or (nil, error message) + +| Name | Type | Description | +| -------- | ------ | ----------- | +| scheme | String | Scheme of the URL | +| username | String | Username | +| password | String | Password | +| host | String | Host and port of the URL | +| path | String | Path | +| query | String | Query string | +| fragment | String | Fragment | + +#### url.build(options) + +Assemble a URL string from a table of URL components. + +**Attributes** + +| Name | Type | Description | +| ------- | ----- | ----------- | +| options | Table | Table with URL components, see [`url.parse`](#urlparseurl) for list of valid components | + +**Returns** + +String + +#### url.build_query_string(query_params) + +Assemble table of query string parameters into a string. + +**Attributes** + +| Name | Type | Description | +| ------------ | ----- | ----------- | +| query_params | Table | Table with query parameters | + +**Returns** + +String + +#### url.resolve(from, to) + +Take a base URL, and a href URL, and resolve them as a browser would for an anchor tag. + +| Name | Type | Description | +| ---- | ------ | ----------- | +| from | String | base URL | +| to | String | href URL | + +**Returns** + +String or (nil, error message) + +## `env` + +```lua +local env = require "env" +``` + +Environment manipulation + +### API + +#### `env.set(key, value)` + +Same `os.setenv` + +#### `env.get(key)` + +Same `os.getenv` + +#### `env.loadfile(file)` + +Loads environment variables from a file. The file is as the following: + +``` +AAA=BBB +CCC=DDD +``` + +If this function fails, it returns `nil`, plus a string describing the error. + +## `fs` + +```lua +local fs = require "fs" +``` + +Filesystem manipulation + +### API + +#### `fs.exists(file)` + +Returns true if the file exists. + +#### `fs.read(file)` + +Reads file content and return it. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.write(file, content, [mode])` + +Writes content to the file. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.mkdir(path, [mode, recursive])` + +Create directory. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.remove(path, [recursive])` + +Remove path. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.symlink(target, link)` + +Create symbolic link. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.dirname(path)` + +Returns all but the last element of path. + +#### `fs.basename(path)` + +Returns the last element of path. + +#### `fs.realpath(path)` + +Returns the real path of a given path in the os. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.getcwd()` + +Returns the current working directory. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.chdir(path)` + +Changes the current working directory. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.file()` + +Returns the script file path. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.dir()` + +Returns the directory path that is parent of the script file. If this function fails, it returns `nil`, plus a string describing the error. + +#### `fs.glob(pattern, function)` + +Run the callback function with the files matching pattern. See below example: + +```lua +local fs = require("fs") +local ret, err = fs.glob("/tmp/*", function(file) + print(file.path) + print(file.realpath) +end) +``` + +## `markdown` + +```lua +local markdown = require "markdown" +``` + +Markdown -> HTML for string and file + +### API + +#### `markdown.dostring(text)` + +Returns HTML string generated from the markdown text. + +#### `markdown.dofile(file)` + +Returns HTML string generated from the markdown text file. If this function fails, it returns `nil`, plus a string describing the error. + +## `question` + +```lua +local question = require "question" +``` + +Prompt library + +### API + +* `question.ask(text)` +* `question.secret(text)` + +## `ssh` + +```lua +local ssh = require "ssh" +``` + +SSH client library + +https://github.com/kohkimakimoto/gluassh/blob/master/gluassh_test.go + +## `template` + +```lua +local template = require "template" +``` + +Go text templates + +### API + +#### `template.dostring(text, table)` + +Returns string generated by text template with the table values. If this function fails, it returns `nil`, plus a string describing the error. + +#### `template.dofile(file, table)` + +Returns string generated by file template with the table values. If this function fails, it returns `nil`, plus a string describing the error. + +## `yaml` + +```lua +local yaml = require "yaml" +``` + +Yaml -> table parser + +### API + +#### `yaml.parse(string)` + +Parses yaml formatted string and returns a table. If this function fails, it returns `nil`, plus a string describing the error. + +## `flag` + +```lua +local flag = require "flag" +``` + +Command line flag parsing. + +See the tests here: https://github.com/otm/gluaflag + +```lua +local flag = require "flag" + +fs = flag.new() + +fs:string("name", "foo", "String help string") +fs:intArg("title", 1, "Title") +fs:numberArg("title", 1, "Title") + +flags = fs:parse(arg) -- arg is the remaining command line arguments +assert(flags.title == 2, "expected title to be 2") +assert(flags.title == 2.32, "expected title to be 2.32") +``` + +## `sh` + +```lua +local sh = require "sh" +``` + +gluash is a interface to call any program as it were a function. Programs are executed asynchronously to enable streaming of data in pipes. + +In all discussions bellow the imported module will be referred to as `sh`. + +Commands are called just like functions, executed on the sh module. + +```lua +sh.ls("/") +``` + +For commands that have exotic names, names that are reserved words, or to execute absolute or relative paths call the sh module directly. + +```lua +sh("/bin/ls", "/") +``` + +#### Multiple Arguments +Commands with multiple arguments have to be invoked with a separate string for each argument. + +```lua +-- this works +sh.ls("-la", "/") + +-- this does not work +sh.ls("-la /") +``` + +#### Piping +Piping in sh is done almost like piping in the shell. Just call next command as a method on the previous command. + +```lua +sh.du("-sb"):sort("-rn"):print() +``` + +If the command has a exotic name, or a reserved word, call the command through `cmd(path, ...args)`. The first argument in `cmd` is the path. + +```lua +sh.du("-sb"):cmd("sort", "-rn"):print() +``` + +### Waiting for Processes +All commands are executed by default in the background, so one have to explicitly wait for a process to finish. There are several ways to wait for the command to finish. + +* `print()` - write stdout and stderr to stdout. +* `ok()` - aborts execution if the command's exit code is not zero +* `success()` - returns true of the commands exit code is zero +* `exitcode()` - returns the exit code of the command + +### Abort by Default +It is possible to set the module to abort on errors without checking. It can be practical in some occasions, however performance will be degraded. When global exit code checks are done the commands are run in series, even in pipes, and output is saved in memory buffers. + +To enable global exit code settings call the sh module with an table with the key `abort` set to true. + +```lua +sh{abort=true} +``` + +To read current settings in the module call the module with an empty table. +```lua +configuration = sh{} +print("abort:", configuration.abort) +``` + +### Analyzing Output +There are several options to analyze the output of a command. + +#### lines() +An iterator is accessible by calling the method `lines()` on the command. + +```lua +for line in sh.cat("/etc/hosts"):lines() do + print(line) +end +``` + +#### stdout([filename]), stderr([filename]), combinedOutput([filename]) +`stdout()`, `stderr()`, and `combinedOutput()` all returns the output of the command as a string. An optional `filename` can be given to the method, in that case the output is also written to the file. The file will be truncated. + +```lua +-- print output of command +output = sh.echo("hello world"):combinedOutput("/tmp/output") +print(output) +``` + +In the example above will print `hello world` and it will write it to `/tmp/output` + +### Glob Expansion +There is no glob expansion done on arguments, however there is a glob functionality in sh. + +```lua +sh.ls(sh.glob("*.go")) +``` + +## `re` + +```lua +local re = require "re" +``` + +Regular Expressions + +### API + +re.find , re.gsub, re.match, re.gmatch are available. These functions have the same API as Lua pattern match. +gluare uses the Go regexp package, so you can use regular expressions that are supported in the Go regexp package. + +In addition, the following functions are defined: +``` +gluare.quote(s string) -> string +Arguments: + +s string: a string value to escape meta characters + +Returns: + +string: escaped string +gluare.quote returns a string that quotes all regular expression metacharacters inside the given text. +``` + +## `simplebox` + +```lua +local simplebox = require "simplebox" +``` + +Simple encryption + +### API + +#### Create a new instance of simplebox with a newly generated key + +```lua +local simplebox = require "simplebox" +local key = simplebox.genkey() +print("key is: " .. key) +local sb = simplebox.new() + + +``` diff --git a/vendor/github.com/Xe/x/tools/glue/Dockerfile b/vendor/github.com/Xe/x/tools/glue/Dockerfile new file mode 100644 index 0000000..23ddabe --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/Dockerfile @@ -0,0 +1,4 @@ +FROM busybox + +ADD glue /glue +CMD /glue diff --git a/vendor/github.com/Xe/x/tools/glue/Gopkg.lock b/vendor/github.com/Xe/x/tools/glue/Gopkg.lock new file mode 100644 index 0000000..966fa45 --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/Gopkg.lock @@ -0,0 +1,181 @@ +memo = "" + +[[projects]] + branch = "master" + name = "github.com/ThomasRooney/gexpect" + packages = ["."] + revision = "5482f03509440585d13d8f648989e05903001842" + +[[projects]] + branch = "master" + name = "github.com/ailncode/gluaxmlpath" + packages = ["."] + revision = "6ce478ecb4a60c4fc8929838e0b21b7fb7ca7440" + +[[projects]] + branch = "master" + name = "github.com/brandur/simplebox" + packages = ["."] + revision = "84e9865bb03ad38c464043bf5382ce8c68ca5f0c" + +[[projects]] + branch = "master" + name = "github.com/cjoudrey/gluahttp" + packages = ["."] + revision = "b4bfe0c50fea948dcbf3966e120996d6607bbd89" + +[[projects]] + branch = "master" + name = "github.com/cjoudrey/gluaurl" + packages = ["."] + revision = "31cbb9bef199454415879f2e6d609d1136d60cad" + +[[projects]] + branch = "master" + name = "github.com/howeyc/gopass" + packages = ["."] + revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8" + +[[projects]] + branch = "master" + name = "github.com/kballard/go-shellquote" + packages = ["."] + revision = "d8ec1a69a250a17bb0e419c386eac1f3711dc142" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluaenv" + packages = ["."] + revision = "2888db6bbe38923d59c42e443895875cc8ce0820" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluafs" + packages = ["."] + revision = "01391ed2d7ab89dc80157605b073403f960aa223" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluaquestion" + packages = ["."] + revision = "311437c29ba54d027ad2af383661725ae2bfdcdc" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluassh" + packages = ["."] + revision = "2a7bd48d7568de8230c87ac1ef4a4c481e45814d" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluatemplate" + packages = ["."] + revision = "d9e2c9d6b00f069a9da377a9ac529c827c1c7d71" + +[[projects]] + branch = "master" + name = "github.com/kohkimakimoto/gluayaml" + packages = ["."] + revision = "6fe413d49d73d785510ecf1529991ab0573e96c7" + +[[projects]] + branch = "master" + name = "github.com/kr/fs" + packages = ["."] + revision = "2788f0dbd16903de03cb8186e5c7d97b69ad387b" + +[[projects]] + branch = "master" + name = "github.com/kr/pty" + packages = ["."] + revision = "ce7fa45920dc37a92de8377972e52bc55ffa8d57" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/mapstructure" + packages = ["."] + revision = "cc8532a8e9a55ea36402aa21efdf403a60d34096" + +[[projects]] + branch = "master" + name = "github.com/otm/gluaflag" + packages = ["."] + revision = "078088de689148194436293886e8e39809167332" + +[[projects]] + branch = "master" + name = "github.com/otm/gluash" + packages = ["."] + revision = "e145c563986f0b91f740a758a84bca46c163aec7" + +[[projects]] + branch = "master" + name = "github.com/pkg/sftp" + packages = ["."] + revision = "e84cc8c755ca39b7b64f510fe1fffc1b51f210a5" + +[[projects]] + branch = "master" + name = "github.com/yookoala/realpath" + packages = ["."] + revision = "c416d99ab5ed256fa30c1f3bab73152deb59bb69" + +[[projects]] + branch = "master" + name = "github.com/yuin/gluamapper" + packages = ["."] + revision = "d836955830e75240d46ce9f0e6d148d94f2e1d3a" + +[[projects]] + branch = "master" + name = "github.com/yuin/gluare" + packages = ["."] + revision = "8e2742cd1bf2b904720ac66eca3c2091b2ea0720" + +[[projects]] + branch = "master" + name = "github.com/yuin/gopher-lua" + packages = [".","parse"] + revision = "33ebc07735566cd0c3c4b69e2839d522cc389852" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["nacl/secretbox","ssh/terminal","ssh","ssh/agent"] + revision = "dd85ac7e6a88fc6ca420478e934de5f1a42dd3c6" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = ["html"] + revision = "66aacef3dd8a676686c7ae3716979581e8b03c47" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix"] + revision = "9ccfe848b9db8435a24c424abbc07a921adf1df5" + +[[projects]] + branch = "v2" + name = "gopkg.in/xmlpath.v2" + packages = ["."] + revision = "860cbeca3ebcc600db0b213c0e83ad6ce91f5739" + +[[projects]] + branch = "master" + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b" + +[[projects]] + branch = "master" + name = "layeh.com/gopher-json" + packages = ["."] + revision = "c128cc74278be889c4381681712931976fe0d88b" + +[[projects]] + branch = "master" + name = "layeh.com/gopher-luar" + packages = ["."] + revision = "80196fe2abc5682963fc7a5261f5a5d77509938b" diff --git a/vendor/github.com/Xe/x/tools/glue/Gopkg.toml b/vendor/github.com/Xe/x/tools/glue/Gopkg.toml new file mode 100644 index 0000000..243127a --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/Gopkg.toml @@ -0,0 +1,68 @@ + +[[dependencies]] + branch = "master" + name = "github.com/ThomasRooney/gexpect" + +[[dependencies]] + branch = "master" + name = "github.com/ailncode/gluaxmlpath" + +[[dependencies]] + branch = "master" + name = "github.com/brandur/simplebox" + +[[dependencies]] + branch = "master" + name = "github.com/cjoudrey/gluahttp" + +[[dependencies]] + branch = "master" + name = "github.com/cjoudrey/gluaurl" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluaenv" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluafs" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluaquestion" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluassh" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluatemplate" + +[[dependencies]] + branch = "master" + name = "github.com/kohkimakimoto/gluayaml" + +[[dependencies]] + branch = "master" + name = "github.com/otm/gluaflag" + +[[dependencies]] + branch = "master" + name = "github.com/otm/gluash" + +[[dependencies]] + branch = "master" + name = "github.com/yuin/gluare" + +[[dependencies]] + branch = "master" + name = "github.com/yuin/gopher-lua" + +[[dependencies]] + branch = "master" + name = "layeh.com/gopher-json" + +[[dependencies]] + branch = "master" + name = "layeh.com/gopher-luar" diff --git a/vendor/github.com/Xe/x/tools/glue/README.md b/vendor/github.com/Xe/x/tools/glue/README.md new file mode 100644 index 0000000..dd33582 --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/README.md @@ -0,0 +1,18 @@ +glue +==== + +Basically gopher-lua's cmd/glua with the following modules imported: +- https://godoc.org/layeh.com/gopher-json +- https://github.com/ailncode/gluaxmlpath +- https://github.com/cjoudrey/gluahttp +- https://github.com/cjoudrey/gluaurl +- https://github.com/kohkimakimoto/gluaenv +- https://github.com/kohkimakimoto/gluafs +- https://github.com/kohkimakimoto/gluamarkdown +- https://github.com/kohkimakimoto/gluaquestion +- https://github.com/kohkimakimoto/gluassh +- https://github.com/kohkimakimoto/gluatemplate +- https://github.com/kohkimakimoto/gluayaml +- https://github.com/otm/gluaflag +- https://github.com/otm/gluash +- https://github.com/yuin/gluare diff --git a/vendor/github.com/Xe/x/tools/glue/box.rb b/vendor/github.com/Xe/x/tools/glue/box.rb new file mode 100644 index 0000000..3b4d18e --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/box.rb @@ -0,0 +1,6 @@ +from "alpine:edge" + +copy "glue", "/glue" +cmd "/glue" +flatten +tag "xena/glue" diff --git a/vendor/github.com/Xe/x/tools/glue/build.lua b/vendor/github.com/Xe/x/tools/glue/build.lua new file mode 100644 index 0000000..31fe844 --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/build.lua @@ -0,0 +1,20 @@ +-- expects glue, $ go get -u github.com/Xe/tools/glue +local sh = require "sh" +sh { abort = true } + +if os.getenv("CGO_ENABLED") ~= "0" then + error("CGO_ENABLED must be set to 1") +end + +print "building glue..." +sh.go("build"):print() +sh.upx("--ultra-brute", "glue"):print() +sh.box("box.rb"):print() + +print "releasing to docker hub" +sh.docker("push", "xena/glue"):print() + +print "moving glue binary to $GOPATH/bin" +sh.mv("glue", (os.getenv("GOPATH") .. "/bin/glue")) + +print "build/release complete" diff --git a/vendor/github.com/Xe/x/tools/glue/glue b/vendor/github.com/Xe/x/tools/glue/glue new file mode 100755 index 0000000..f2112bb Binary files /dev/null and b/vendor/github.com/Xe/x/tools/glue/glue differ diff --git a/vendor/github.com/Xe/x/tools/glue/glue.go b/vendor/github.com/Xe/x/tools/glue/glue.go new file mode 100644 index 0000000..e6e80dd --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/glue.go @@ -0,0 +1,213 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "net/http" + "os" + "runtime/pprof" + + "github.com/Xe/x/tools/glue/libs/gluaexpect" + "github.com/Xe/x/tools/glue/libs/gluasimplebox" + "github.com/ailncode/gluaxmlpath" + "github.com/cjoudrey/gluahttp" + "github.com/cjoudrey/gluaurl" + "github.com/kohkimakimoto/gluaenv" + "github.com/kohkimakimoto/gluafs" + "github.com/kohkimakimoto/gluaquestion" + "github.com/kohkimakimoto/gluassh" + "github.com/kohkimakimoto/gluatemplate" + "github.com/kohkimakimoto/gluayaml" + "github.com/otm/gluaflag" + "github.com/otm/gluash" + "github.com/yuin/gluare" + "github.com/yuin/gopher-lua" + "github.com/yuin/gopher-lua/parse" + json "layeh.com/gopher-json" +) + +func main() { + os.Exit(mainAux()) +} + +func mainAux() int { + var opt_e, opt_l, opt_p string + var opt_i, opt_v, opt_dt, opt_dc bool + var opt_m int + flag.StringVar(&opt_e, "e", "", "") + flag.StringVar(&opt_l, "l", "", "") + flag.StringVar(&opt_p, "p", "", "") + flag.IntVar(&opt_m, "mx", 0, "") + flag.BoolVar(&opt_i, "i", false, "") + flag.BoolVar(&opt_v, "v", false, "") + flag.BoolVar(&opt_dt, "dt", false, "") + flag.BoolVar(&opt_dc, "dc", false, "") + flag.Usage = func() { + fmt.Println(`Usage: glue [options] [script [args]]. +Available options are: + -e stat execute string 'stat' + -l name require library 'name' + -mx MB memory limit(default: unlimited) + -dt dump AST trees + -dc dump VM codes + -i enter interactive mode after executing 'script' + -p file write cpu profiles to the file + -v show version information +`) + } + flag.Parse() + if len(opt_p) != 0 { + f, err := os.Create(opt_p) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + if len(opt_e) == 0 && !opt_i && !opt_v && flag.NArg() == 0 { + opt_i = true + } + + status := 0 + + L := lua.NewState() + defer L.Close() + if opt_m > 0 { + L.SetMx(opt_m) + } + + preload(L) + + if opt_v || opt_i { + fmt.Println(lua.PackageCopyRight) + } + + if len(opt_l) > 0 { + if err := L.DoFile(opt_l); err != nil { + fmt.Println(err.Error()) + } + } + + if nargs := flag.NArg(); nargs > 0 { + script := flag.Arg(0) + argtb := L.NewTable() + for i := 1; i < nargs; i++ { + L.RawSet(argtb, lua.LNumber(i), lua.LString(flag.Arg(i))) + } + L.SetGlobal("arg", argtb) + if opt_dt || opt_dc { + file, err := os.Open(script) + if err != nil { + fmt.Println(err.Error()) + return 1 + } + chunk, err2 := parse.Parse(file, script) + if err2 != nil { + fmt.Println(err2.Error()) + return 1 + } + if opt_dt { + fmt.Println(parse.Dump(chunk)) + } + if opt_dc { + proto, err3 := lua.Compile(chunk, script) + if err3 != nil { + fmt.Println(err3.Error()) + return 1 + } + fmt.Println(proto.String()) + } + } + + if err := L.DoFile(script); err != nil { + fmt.Println(err.Error()) + status = 1 + } + } + + if len(opt_e) > 0 { + if err := L.DoString(opt_e); err != nil { + fmt.Println(err.Error()) + status = 1 + } + } + + if opt_i { + doREPL(L) + } + return status +} + +func preload(L *lua.LState) { + L.PreloadModule("re", gluare.Loader) + L.PreloadModule("sh", gluash.Loader) + L.PreloadModule("fs", gluafs.Loader) + L.PreloadModule("env", gluaenv.Loader) + L.PreloadModule("yaml", gluayaml.Loader) + L.PreloadModule("question", gluaquestion.Loader) + L.PreloadModule("ssh", gluassh.Loader) + L.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader) + L.PreloadModule("flag", gluaflag.Loader) + L.PreloadModule("template", gluatemplate.Loader) + L.PreloadModule("url", gluaurl.Loader) + gluaexpect.Preload(L) + gluasimplebox.Preload(L) + gluaxmlpath.Preload(L) + json.Preload(L) +} + +// do read/eval/print/loop +func doREPL(L *lua.LState) { + reader := bufio.NewReader(os.Stdin) + for { + if str, err := loadline(reader, L); err == nil { + if err := L.DoString(str); err != nil { + fmt.Println(err) + } + } else { // error on loadline + fmt.Println(err) + return + } + } +} + +func incomplete(err error) bool { + if lerr, ok := err.(*lua.ApiError); ok { + if perr, ok := lerr.Cause.(*parse.Error); ok { + return perr.Pos.Line == parse.EOF + } + } + return false +} + +func loadline(reader *bufio.Reader, L *lua.LState) (string, error) { + fmt.Print("> ") + if line, err := reader.ReadString('\n'); err == nil { + if _, err := L.LoadString("return " + line); err == nil { // try add return <...> then compile + return line, nil + } else { + return multiline(line, reader, L) + } + } else { + return "", err + } +} + +func multiline(ml string, reader *bufio.Reader, L *lua.LState) (string, error) { + for { + if _, err := L.LoadString(ml); err == nil { // try compile + return ml, nil + } else if !incomplete(err) { // syntax error , but not EOF + return ml, nil + } else { + fmt.Print(">> ") + if line, err := reader.ReadString('\n'); err == nil { + ml = ml + "\n" + line + } else { + return "", err + } + } + } +} diff --git a/vendor/github.com/Xe/x/tools/glue/libs/gluaexpect/expect.go b/vendor/github.com/Xe/x/tools/glue/libs/gluaexpect/expect.go new file mode 100644 index 0000000..cc33597 --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/libs/gluaexpect/expect.go @@ -0,0 +1,35 @@ +package gluaexpect + +import ( + "github.com/ThomasRooney/gexpect" + lua "github.com/yuin/gopher-lua" + luar "layeh.com/gopher-luar" +) + +func Preload(L *lua.LState) { + L.PreloadModule("expect", Loader) +} + +// Loader is the module loader function. +func Loader(L *lua.LState) int { + mod := L.SetFuncs(L.NewTable(), api) + L.Push(mod) + return 1 +} + +var api = map[string]lua.LGFunction{ + "spawn": spawn, +} + +func spawn(L *lua.LState) int { + cmd := L.CheckString(1) + child, err := gexpect.Spawn(cmd) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + L.Push(luar.New(L, child)) + return 1 +} diff --git a/vendor/github.com/Xe/x/tools/glue/libs/gluasimplebox/sb.go b/vendor/github.com/Xe/x/tools/glue/libs/gluasimplebox/sb.go new file mode 100644 index 0000000..5641782 --- /dev/null +++ b/vendor/github.com/Xe/x/tools/glue/libs/gluasimplebox/sb.go @@ -0,0 +1,100 @@ +package gluasimplebox + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "errors" + + "github.com/brandur/simplebox" + lua "github.com/yuin/gopher-lua" + luar "layeh.com/gopher-luar" +) + +func Preload(L *lua.LState) { + L.PreloadModule("simplebox", Loader) +} + +// Loader is the module loader function. +func Loader(L *lua.LState) int { + mod := L.SetFuncs(L.NewTable(), api) + L.Push(mod) + return 1 +} + +var api = map[string]lua.LGFunction{ + "new": newSecretBox, + "genkey": genKey, +} + +func newSecretBox(L *lua.LState) int { + key := L.CheckString(1) + + k, err := parseKey(key) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + sb := simplebox.NewFromSecretKey(k) + + L.Push(luar.New(L, &box{sb: sb})) + return 1 +} + +func genKey(L *lua.LState) int { + key, err := generateKey() + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + L.Push(lua.LString(base64.URLEncoding.EncodeToString(key[:]))) + return 1 +} + +func generateKey() (*[32]byte, error) { + var k [32]byte + _, err := rand.Read(k[:]) + if err != nil { + return nil, err + } + return &k, nil +} + +func parseKey(s string) (*[32]byte, error) { + k := &[32]byte{} + raw, err := base64.URLEncoding.DecodeString(s) + if err != nil { + return nil, err + } + if n := copy(k[:], raw); n < len(k) { + return nil, errors.New("not valid") + } + return k, nil +} + +type box struct { + sb *simplebox.SimpleBox +} + +func (b *box) Encrypt(data string) string { + result := b.sb.Encrypt([]byte(data)) + return hex.EncodeToString(result) +} + +func (b *box) Decrypt(data string) (string, error) { + d, err := hex.DecodeString(data) + if err != nil { + return "", err + } + + plain, err := b.sb.Decrypt([]byte(d)) + if err != nil { + return "", err + } + + return string(plain), nil +} diff --git a/vendor/github.com/ailncode/gluaxmlpath/README.md b/vendor/github.com/ailncode/gluaxmlpath/README.md new file mode 100644 index 0000000..913cc4f --- /dev/null +++ b/vendor/github.com/ailncode/gluaxmlpath/README.md @@ -0,0 +1,40 @@ +# gluaxmlpath + +gluaxmlpath provides an easy way to use [xmlpath](https://github.com/go-xmlpath/xmlpath) from within [GopherLua](https://github.com/yuin/gopher-lua). + +## Installation + +``` +go get github.com/ailncode/gluaxmlpath +``` + +## Usage + +```go +package main + +import ( + "github.com/ailncode/gluaxmlpath" + "github.com/yuin/gopher-lua" +) + +func main() { + L := lua.NewState() + defer L.Close() + + gluaxmlpath.Preload(L) + + if err := L.DoString(` + xml ="x1x2x3" + local xmlpath = require("xmlpath") + node,err = xmlpath.loadxml(xml) + path,err = xmlpath.compile("//book") + it = path:iter(node) + for k,v in pairs(it) do + print(k,v:string()) + end + `); err != nil { + panic(err) + } +} +``` diff --git a/vendor/github.com/ailncode/gluaxmlpath/api.go b/vendor/github.com/ailncode/gluaxmlpath/api.go new file mode 100644 index 0000000..6163d47 --- /dev/null +++ b/vendor/github.com/ailncode/gluaxmlpath/api.go @@ -0,0 +1,37 @@ +package gluaxmlpath + +import ( + "bytes" + "github.com/yuin/gopher-lua" + xmlpath "gopkg.in/xmlpath.v2" +) + +var api = map[string]lua.LGFunction{ + "loadxml": loadXml, + "compile": compile, +} + +func loadXml(L *lua.LState) int { + xmlStr := L.CheckString(1) + r := bytes.NewReader([]byte(xmlStr)) + node, err := xmlpath.ParseHTML(r) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(newNode(L, node)) + return 1 +} + +func compile(L *lua.LState) int { + xpathStr := L.CheckString(1) + path, err := xmlpath.Compile(xpathStr) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(newPath(L, path)) + return 1 +} diff --git a/vendor/github.com/ailncode/gluaxmlpath/xmlpath.go b/vendor/github.com/ailncode/gluaxmlpath/xmlpath.go new file mode 100644 index 0000000..ab52baa --- /dev/null +++ b/vendor/github.com/ailncode/gluaxmlpath/xmlpath.go @@ -0,0 +1,21 @@ +package gluaxmlpath + +import ( + "github.com/yuin/gopher-lua" +) + +// Preload adds xmlpath to the given Lua state's package.preload table. After it +// has been preloaded, it can be loaded using require: +// +// local xmlpath = require("xmlpath") +func Preload(L *lua.LState) { + L.PreloadModule("xmlpath", Loader) +} + +// Loader is the module loader function. +func Loader(L *lua.LState) int { + mod := L.SetFuncs(L.NewTable(), api) + registerType(L, mod) + L.Push(mod) + return 1 +} diff --git a/vendor/github.com/ailncode/gluaxmlpath/xmlpathtype.go b/vendor/github.com/ailncode/gluaxmlpath/xmlpathtype.go new file mode 100644 index 0000000..e2e7dc8 --- /dev/null +++ b/vendor/github.com/ailncode/gluaxmlpath/xmlpathtype.go @@ -0,0 +1,135 @@ +package gluaxmlpath + +import ( + "github.com/yuin/gopher-lua" + xmlpath "gopkg.in/xmlpath.v2" +) + +type Node struct { + base *xmlpath.Node +} +type Path struct { + base *xmlpath.Path +} + +type Iter struct { + base *xmlpath.Iter +} + +const luaNodeTypeName = "xmlpath.node" +const luaPathTypeName = "xmlpath.path" +const luaIterTypeName = "xmlpath.iter" + +func registerType(L *lua.LState, module *lua.LTable) { + //reg node + nodemt := L.NewTypeMetatable(luaNodeTypeName) + L.SetField(module, "node", nodemt) + L.SetField(nodemt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{ + "string": nodeString, + })) + //reg path + pathmt := L.NewTypeMetatable(luaPathTypeName) + L.SetField(module, "path", pathmt) + L.SetField(pathmt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{ + "iter": iter, + })) + //reg iter + itermt := L.NewTypeMetatable(luaIterTypeName) + L.SetField(module, "iter", itermt) + L.SetField(itermt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{ + //"next": next, + "node": node, + })) +} +func newNode(L *lua.LState, n *xmlpath.Node) *lua.LUserData { + ud := L.NewUserData() + ud.Value = &Node{ + n, + } + L.SetMetatable(ud, L.GetTypeMetatable(luaNodeTypeName)) + return ud +} +func checkNode(L *lua.LState) *Node { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*Node); ok { + return v + } + L.ArgError(1, "node expected") + return nil +} +func newPath(L *lua.LState, p *xmlpath.Path) *lua.LUserData { + ud := L.NewUserData() + ud.Value = &Path{ + p, + } + L.SetMetatable(ud, L.GetTypeMetatable(luaPathTypeName)) + return ud +} +func checkPath(L *lua.LState) *Path { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*Path); ok { + return v + } + L.ArgError(1, "path expected") + return nil +} +func newIter(L *lua.LState, i *xmlpath.Iter) *lua.LUserData { + ud := L.NewUserData() + ud.Value = &Iter{ + i, + } + L.SetMetatable(ud, L.GetTypeMetatable(luaIterTypeName)) + return ud +} +func checkIter(L *lua.LState) *Iter { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*Iter); ok { + return v + } + L.ArgError(1, "iter expected") + return nil +} + +//iter := path.iter(node) +func iter(L *lua.LState) int { + path := checkPath(L) + if L.GetTop() == 2 { + ut := L.CheckUserData(2) + if node, ok := ut.Value.(*Node); ok { + it := path.base.Iter(node.base) + ltab := L.NewTable() + i := 1 + for it.Next() { + L.RawSetInt(ltab, i, newNode(L, it.Node())) + i++ + } + L.Push(ltab) + //L.Push(newIter(L, it)) + return 1 + } + } + L.ArgError(1, "node expected") + return 0 +} + +//support lua standard iterator +//hasNext := iter.next() +// func next(L *lua.LState) int { +// iter := checkIter(L) +// L.Push(lua.LBool(iter.base.Next())) +// return 1 +// } + +//node := iter.node() +func node(L *lua.LState) int { + iter := checkIter(L) + L.Push(newNode(L, iter.base.Node())) + return 1 +} + +//string := node.string() +func nodeString(L *lua.LState) int { + node := checkNode(L) + L.Push(lua.LString(node.base.String())) + return 1 +} diff --git a/vendor/github.com/cjoudrey/gluahttp/.gitignore b/vendor/github.com/cjoudrey/gluahttp/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/cjoudrey/gluahttp/.travis.yml b/vendor/github.com/cjoudrey/gluahttp/.travis.yml new file mode 100644 index 0000000..21346e3 --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/.travis.yml @@ -0,0 +1,15 @@ +language: go + +go: + - 1.4 + - 1.5 + - 1.6 + +install: + - go get github.com/yuin/gopher-lua + +script: + - go test -v + +notifications: + email: false diff --git a/vendor/github.com/cjoudrey/gluahttp/LICENSE b/vendor/github.com/cjoudrey/gluahttp/LICENSE new file mode 100644 index 0000000..56a399a --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Christian Joudrey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/cjoudrey/gluahttp/README.md b/vendor/github.com/cjoudrey/gluahttp/README.md new file mode 100644 index 0000000..9a3dcca --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/README.md @@ -0,0 +1,235 @@ +# gluahttp + +[![](https://travis-ci.org/cjoudrey/gluahttp.svg)](https://travis-ci.org/cjoudrey/gluahttp) + +gluahttp provides an easy way to make HTTP requests from within [GopherLua](https://github.com/yuin/gopher-lua). + +## Installation + +``` +go get github.com/cjoudrey/gluahttp +``` + +## Usage + +```go +import "github.com/yuin/gopher-lua" +import "github.com/cjoudrey/gluahttp" + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("http", NewHttpModule(&http.Client{}).Loader) + + if err := L.DoString(` + + local http = require("http") + + response, error_message = http.request("GET", "http://example.com", { + query="page=1" + headers={ + Accept="*/*" + } + }) + + `); err != nil { + panic(err) + } +} +``` + +## API + +- [`http.delete(url [, options])`](#httpdeleteurl--options) +- [`http.get(url [, options])`](#httpgeturl--options) +- [`http.head(url [, options])`](#httpheadurl--options) +- [`http.patch(url [, options])`](#httppatchurl--options) +- [`http.post(url [, options])`](#httpposturl--options) +- [`http.put(url [, options])`](#httpputurl--options) +- [`http.request(method, url [, options])`](#httprequestmethod-url--options) +- [`http.request_batch(requests)`](#httprequest_batchrequests) +- [`http.response`](#httpresponse) + +### http.delete(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.get(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.head(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.patch(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.post(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.put(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.request(method, url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| method | String | The HTTP request method | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.request_batch(requests) + +**Attributes** + +| Name | Type | Description | +| -------- | ----- | ----------- | +| requests | Table | A table of requests to send. Each request item is by itself a table containing [http.request](#httprequestmethod-url--options) parameters for the request | + +**Returns** + +[[http.response](#httpresponse)] or ([[http.response](#httpresponse)], [error message]) + +### http.response + +The `http.response` table contains information about a completed HTTP request. + +**Attributes** + +| Name | Type | Description | +| ----------- | ------ | ----------- | +| body | String | The HTTP response body | +| body_size | Number | The size of the HTTP reponse body in bytes | +| headers | Table | The HTTP response headers | +| cookies | Table | The cookies sent by the server in the HTTP response | +| status_code | Number | The HTTP response status code | +| url | String | The final URL the request ended pointing to after redirects | diff --git a/vendor/github.com/cjoudrey/gluahttp/gluahttp.go b/vendor/github.com/cjoudrey/gluahttp/gluahttp.go new file mode 100644 index 0000000..567743f --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/gluahttp.go @@ -0,0 +1,212 @@ +package gluahttp + +import "github.com/yuin/gopher-lua" +import "net/http" +import "fmt" +import "errors" +import "io/ioutil" +import "strings" + +type httpModule struct { + client *http.Client +} + +type empty struct{} + +func NewHttpModule(client *http.Client) *httpModule { + return &httpModule{ + client: client, + } +} + +func (h *httpModule) Loader(L *lua.LState) int { + mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{ + "get": h.get, + "delete": h.delete, + "head": h.head, + "patch": h.patch, + "post": h.post, + "put": h.put, + "request": h.request, + "request_batch": h.requestBatch, + }) + registerHttpResponseType(mod, L) + L.Push(mod) + return 1 +} + +func (h *httpModule) get(L *lua.LState) int { + return h.doRequestAndPush(L, "get", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) delete(L *lua.LState) int { + return h.doRequestAndPush(L, "delete", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) head(L *lua.LState) int { + return h.doRequestAndPush(L, "head", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) patch(L *lua.LState) int { + return h.doRequestAndPush(L, "patch", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) post(L *lua.LState) int { + return h.doRequestAndPush(L, "post", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) put(L *lua.LState) int { + return h.doRequestAndPush(L, "put", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) request(L *lua.LState) int { + return h.doRequestAndPush(L, L.ToString(1), L.ToString(2), L.ToTable(3)) +} + +func (h *httpModule) requestBatch(L *lua.LState) int { + requests := L.ToTable(1) + amountRequests := requests.Len() + + errs := make([]error, amountRequests) + responses := make([]*lua.LUserData, amountRequests) + sem := make(chan empty, amountRequests) + + i := 0 + + requests.ForEach(func(_ lua.LValue, value lua.LValue) { + requestTable := toTable(value) + + if requestTable != nil { + method := requestTable.RawGet(lua.LNumber(1)).String() + url := requestTable.RawGet(lua.LNumber(2)).String() + options := toTable(requestTable.RawGet(lua.LNumber(3))) + + go func(i int, L *lua.LState, method string, url string, options *lua.LTable) { + response, err := h.doRequest(L, method, url, options) + + if err == nil { + errs[i] = nil + responses[i] = response + } else { + errs[i] = err + responses[i] = nil + } + + sem <- empty{} + }(i, L, method, url, options) + } else { + errs[i] = errors.New("Request must be a table") + responses[i] = nil + sem <- empty{} + } + + i = i + 1 + }) + + for i = 0; i < amountRequests; i++ { + <-sem + } + + hasErrors := false + errorsTable := L.NewTable() + responsesTable := L.NewTable() + for i = 0; i < amountRequests; i++ { + if errs[i] == nil { + responsesTable.Append(responses[i]) + errorsTable.Append(lua.LNil) + } else { + responsesTable.Append(lua.LNil) + errorsTable.Append(lua.LString(fmt.Sprintf("%s", errs[i]))) + hasErrors = true + } + } + + if hasErrors { + L.Push(responsesTable) + L.Push(errorsTable) + return 2 + } else { + L.Push(responsesTable) + return 1 + } +} + +func (h *httpModule) doRequest(L *lua.LState, method string, url string, options *lua.LTable) (*lua.LUserData, error) { + req, err := http.NewRequest(strings.ToUpper(method), url, nil) + if err != nil { + return nil, err + } + + if options != nil { + if reqCookies, ok := options.RawGet(lua.LString("cookies")).(*lua.LTable); ok { + reqCookies.ForEach(func(key lua.LValue, value lua.LValue) { + req.AddCookie(&http.Cookie{Name: key.String(), Value: value.String()}) + }) + } + + switch reqQuery := options.RawGet(lua.LString("query")).(type) { + case lua.LString: + req.URL.RawQuery = reqQuery.String() + } + + body := options.RawGet(lua.LString("body")) + if _, ok := body.(lua.LString); !ok { + // "form" is deprecated. + body = options.RawGet(lua.LString("form")) + // Only set the Content-Type to application/x-www-form-urlencoded + // when someone uses "form", not for "body". + if _, ok := body.(lua.LString); ok { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + } + + switch reqBody := body.(type) { + case lua.LString: + body := reqBody.String() + req.ContentLength = int64(len(body)) + req.Body = ioutil.NopCloser(strings.NewReader(body)) + } + + // Set these last. That way the code above doesn't overwrite them. + if reqHeaders, ok := options.RawGet(lua.LString("headers")).(*lua.LTable); ok { + reqHeaders.ForEach(func(key lua.LValue, value lua.LValue) { + req.Header.Set(key.String(), value.String()) + }) + } + } + + res, err := h.client.Do(req) + + if err != nil { + return nil, err + } + + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + + if err != nil { + return nil, err + } + + return newHttpResponse(res, &body, len(body), L), nil +} + +func (h *httpModule) doRequestAndPush(L *lua.LState, method string, url string, options *lua.LTable) int { + response, err := h.doRequest(L, method, url, options) + + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(fmt.Sprintf("%s", err))) + return 2 + } + + L.Push(response) + return 1 +} + +func toTable(v lua.LValue) *lua.LTable { + if lv, ok := v.(*lua.LTable); ok { + return lv + } + return nil +} diff --git a/vendor/github.com/cjoudrey/gluahttp/gluahttp_test.go b/vendor/github.com/cjoudrey/gluahttp/gluahttp_test.go new file mode 100644 index 0000000..34c11e7 --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/gluahttp_test.go @@ -0,0 +1,433 @@ +package gluahttp + +import "github.com/yuin/gopher-lua" +import "testing" +import "io/ioutil" +import "net/http" +import "net" +import "fmt" +import "net/http/cookiejar" +import "strings" + +func TestRequestNoMethod(t *testing.T) { + if err := evalLua(t, ` + local http = require("http") + response, error = http.request() + + assert_equal(nil, response) + assert_contains('unsupported protocol scheme ""', error) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestRequestNoUrl(t *testing.T) { + if err := evalLua(t, ` + local http = require("http") + response, error = http.request("get") + + assert_equal(nil, response) + assert_contains('unsupported protocol scheme ""', error) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestRequestBatch(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + responses, errors = http.request_batch({ + {"get", "http://`+listener.Addr().String()+`", {query="page=1"}}, + {"post", "http://`+listener.Addr().String()+`/set_cookie"}, + {"post", ""}, + 1 + }) + + assert_equal(nil, errors[1]) + assert_equal(nil, errors[2]) + assert_contains('unsupported protocol scheme ""', errors[3]) + assert_equal('Request must be a table', errors[4]) + + assert_equal('Requested GET / with query "page=1"', responses[1]["body"]) + assert_equal('Cookie set!', responses[2]["body"]) + assert_equal('12345', responses[2]["cookies"]["session_id"]) + assert_equal(nil, responses[3]) + assert_equal(nil, responses[4]) + + responses, errors = http.request_batch({ + {"get", "http://`+listener.Addr().String()+`/get_cookie"} + }) + + assert_equal(nil, errors) + assert_equal("session_id=12345", responses[1]["body"]) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestRequestGet(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.request("get", "http://`+listener.Addr().String()+`") + + assert_equal('Requested GET / with query ""', response['body']) + assert_equal(200, response['status_code']) + assert_equal('29', response['headers']['Content-Length']) + assert_equal('text/plain; charset=utf-8', response['headers']['Content-Type']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestRequestGetWithRedirect(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.request("get", "http://`+listener.Addr().String()+`/redirect") + + assert_equal('Requested GET / with query ""', response['body']) + assert_equal(200, response['status_code']) + assert_equal('http://`+listener.Addr().String()+`/', response['url']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestRequestPostForm(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.request("post", "http://`+listener.Addr().String()+`", { + form="username=bob&password=secret" + }) + + assert_equal( + 'Requested POST / with query ""' .. + 'Content-Type: application/x-www-form-urlencoded' .. + 'Content-Length: 28' .. + 'Body: username=bob&password=secret', response['body']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestRequestHeaders(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.request("post", "http://`+listener.Addr().String()+`", { + headers={ + ["Content-Type"]="application/json" + } + }) + + assert_equal( + 'Requested POST / with query ""' .. + 'Content-Type: application/json' .. + 'Content-Length: 0' .. + 'Body: ', response['body']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestRequestQuery(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.request("get", "http://`+listener.Addr().String()+`", { + query="page=2" + }) + + assert_equal('Requested GET / with query "page=2"', response['body']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestGet(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.get("http://`+listener.Addr().String()+`", { + query="page=1" + }) + + assert_equal('Requested GET / with query "page=1"', response['body']) + assert_equal(200, response['status_code']) + assert_equal('35', response['headers']['Content-Length']) + assert_equal('text/plain; charset=utf-8', response['headers']['Content-Type']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestDelete(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.delete("http://`+listener.Addr().String()+`", { + query="page=1" + }) + + assert_equal('Requested DELETE / with query "page=1"', response['body']) + assert_equal(200, response['status_code']) + assert_equal('38', response['headers']['Content-Length']) + assert_equal('text/plain; charset=utf-8', response['headers']['Content-Type']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestHead(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.head("http://`+listener.Addr().String()+`/head", { + query="page=1" + }) + + assert_equal(200, response['status_code']) + assert_equal("/head?page=1", response['headers']['X-Request-Uri']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestPost(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.post("http://`+listener.Addr().String()+`", { + body="username=bob&password=secret" + }) + + assert_equal( + 'Requested POST / with query ""' .. + 'Content-Type: ' .. + 'Content-Length: 28' .. + 'Body: username=bob&password=secret', response['body']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestPatch(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.patch("http://`+listener.Addr().String()+`", { + body='{"username":"bob"}', + headers={ + ["Content-Type"]="application/json" + } + }) + + assert_equal( + 'Requested PATCH / with query ""' .. + 'Content-Type: application/json' .. + 'Content-Length: 18' .. + 'Body: {"username":"bob"}', response['body']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestPut(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.put("http://`+listener.Addr().String()+`", { + body="username=bob&password=secret", + headers={ + ["Content-Type"]="application/x-www-form-urlencoded" + } + }) + + assert_equal( + 'Requested PUT / with query ""' .. + 'Content-Type: application/x-www-form-urlencoded' .. + 'Content-Length: 28' .. + 'Body: username=bob&password=secret', response['body']) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestResponseCookies(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.get("http://`+listener.Addr().String()+`/set_cookie") + + assert_equal('Cookie set!', response["body"]) + assert_equal('12345', response["cookies"]["session_id"]) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestRequestCookies(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.get("http://`+listener.Addr().String()+`/get_cookie", { + cookies={ + ["session_id"]="test" + } + }) + + assert_equal('session_id=test', response["body"]) + assert_equal(15, response["body_size"]) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestResponseBodySize(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.get("http://`+listener.Addr().String()+`/") + + assert_equal(29, response["body_size"]) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestResponseBody(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + response, error = http.get("http://`+listener.Addr().String()+`/") + + assert_equal("Requested XXX / with query \"\"", string.gsub(response.body, "GET", "XXX")) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func TestResponseUrl(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + setupServer(listener) + + if err := evalLua(t, ` + local http = require("http") + + response, error = http.get("http://`+listener.Addr().String()+`/redirect") + assert_equal("http://`+listener.Addr().String()+`/", response["url"]) + + response, error = http.get("http://`+listener.Addr().String()+`/get_cookie") + assert_equal("http://`+listener.Addr().String()+`/get_cookie", response["url"]) + `); err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } +} + +func evalLua(t *testing.T, script string) error { + L := lua.NewState() + defer L.Close() + + cookieJar, _ := cookiejar.New(nil) + + L.PreloadModule("http", NewHttpModule(&http.Client{ + Jar: cookieJar, + }, + ).Loader) + + L.SetGlobal("assert_equal", L.NewFunction(func(L *lua.LState) int { + expected := L.Get(1) + actual := L.Get(2) + + if expected.Type() != actual.Type() || expected.String() != actual.String() { + t.Errorf("Expected %s %q, got %s %q", expected.Type(), expected, actual.Type(), actual) + } + + return 0 + })) + + L.SetGlobal("assert_contains", L.NewFunction(func(L *lua.LState) int { + contains := L.Get(1) + actual := L.Get(2) + + if !strings.Contains(actual.String(), contains.String()) { + t.Errorf("Expected %s %q contains %s %q", actual.Type(), actual, contains.Type(), contains) + } + + return 0 + })) + + return L.DoString(script) +} + +func setupServer(listener net.Listener) { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "Requested %s / with query %q", req.Method, req.URL.RawQuery) + + if req.Method == "POST" || req.Method == "PATCH" || req.Method == "PUT" { + body, _ := ioutil.ReadAll(req.Body) + fmt.Fprintf(w, "Content-Type: %s", req.Header.Get("Content-Type")) + fmt.Fprintf(w, "Content-Length: %s", req.Header.Get("Content-Length")) + fmt.Fprintf(w, "Body: %s", body) + } + }) + mux.HandleFunc("/head", func(w http.ResponseWriter, req *http.Request) { + if req.Method == "HEAD" { + w.Header().Set("X-Request-Uri", req.URL.String()) + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(http.StatusNotFound) + } + }) + mux.HandleFunc("/set_cookie", func(w http.ResponseWriter, req *http.Request) { + http.SetCookie(w, &http.Cookie{Name: "session_id", Value: "12345"}) + fmt.Fprint(w, "Cookie set!") + }) + mux.HandleFunc("/get_cookie", func(w http.ResponseWriter, req *http.Request) { + session_id, _ := req.Cookie("session_id") + fmt.Fprint(w, session_id) + }) + mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) { + http.Redirect(w, req, "/", http.StatusFound) + }) + s := &http.Server{ + Handler: mux, + } + go s.Serve(listener) +} diff --git a/vendor/github.com/cjoudrey/gluahttp/httpresponsetype.go b/vendor/github.com/cjoudrey/gluahttp/httpresponsetype.go new file mode 100644 index 0000000..744a587 --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/httpresponsetype.go @@ -0,0 +1,98 @@ +package gluahttp + +import "github.com/yuin/gopher-lua" +import "net/http" + +const luaHttpResponseTypeName = "http.response" + +type luaHttpResponse struct { + res *http.Response + body lua.LString + bodySize int +} + +func registerHttpResponseType(module *lua.LTable, L *lua.LState) { + mt := L.NewTypeMetatable(luaHttpResponseTypeName) + L.SetField(mt, "__index", L.NewFunction(httpResponseIndex)) + + L.SetField(module, "response", mt) +} + +func newHttpResponse(res *http.Response, body *[]byte, bodySize int, L *lua.LState) *lua.LUserData { + ud := L.NewUserData() + ud.Value = &luaHttpResponse{ + res: res, + body: lua.LString(*body), + bodySize: bodySize, + } + L.SetMetatable(ud, L.GetTypeMetatable(luaHttpResponseTypeName)) + return ud +} + +func checkHttpResponse(L *lua.LState) *luaHttpResponse { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*luaHttpResponse); ok { + return v + } + L.ArgError(1, "http.response expected") + return nil +} + +func httpResponseIndex(L *lua.LState) int { + res := checkHttpResponse(L) + + switch L.CheckString(2) { + case "headers": + return httpResponseHeaders(res, L) + case "cookies": + return httpResponseCookies(res, L) + case "status_code": + return httpResponseStatusCode(res, L) + case "url": + return httpResponseUrl(res, L) + case "body": + return httpResponseBody(res, L) + case "body_size": + return httpResponseBodySize(res, L) + } + + return 0 +} + +func httpResponseHeaders(res *luaHttpResponse, L *lua.LState) int { + headers := L.NewTable() + for key, _ := range res.res.Header { + headers.RawSetString(key, lua.LString(res.res.Header.Get(key))) + } + L.Push(headers) + return 1 +} + +func httpResponseCookies(res *luaHttpResponse, L *lua.LState) int { + cookies := L.NewTable() + for _, cookie := range res.res.Cookies() { + cookies.RawSetString(cookie.Name, lua.LString(cookie.Value)) + } + L.Push(cookies) + return 1 +} + +func httpResponseStatusCode(res *luaHttpResponse, L *lua.LState) int { + L.Push(lua.LNumber(res.res.StatusCode)) + return 1 +} + +func httpResponseUrl(res *luaHttpResponse, L *lua.LState) int { + L.Push(lua.LString(res.res.Request.URL.String())) + return 1 +} + +func httpResponseBody(res *luaHttpResponse, L *lua.LState) int { + L.Push(res.body) + return 1 +} + +func httpResponseBodySize(res *luaHttpResponse, L *lua.LState) int { + L.Push(lua.LNumber(res.bodySize)) + return 1 +} diff --git a/vendor/github.com/cjoudrey/gluaurl/.gitignore b/vendor/github.com/cjoudrey/gluaurl/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/cjoudrey/gluaurl/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/cjoudrey/gluaurl/.travis.yml b/vendor/github.com/cjoudrey/gluaurl/.travis.yml new file mode 100644 index 0000000..7a3d33a --- /dev/null +++ b/vendor/github.com/cjoudrey/gluaurl/.travis.yml @@ -0,0 +1,14 @@ +language: go + +go: + - 1.3 + - 1.4 + +install: + - go get github.com/yuin/gopher-lua + +script: + - go test -v + +notifications: + email: false diff --git a/vendor/github.com/cjoudrey/gluaurl/LICENSE b/vendor/github.com/cjoudrey/gluaurl/LICENSE new file mode 100644 index 0000000..56a399a --- /dev/null +++ b/vendor/github.com/cjoudrey/gluaurl/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Christian Joudrey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/cjoudrey/gluaurl/README.md b/vendor/github.com/cjoudrey/gluaurl/README.md new file mode 100644 index 0000000..fc82b5f --- /dev/null +++ b/vendor/github.com/cjoudrey/gluaurl/README.md @@ -0,0 +1,109 @@ +# gluaurl + +[![](https://travis-ci.org/cjoudrey/gluaurl.svg)](https://travis-ci.org/cjoudrey/gluaurl) + +gluahttp provides an easy way to parse and build URLs from within [GopherLua](https://github.com/yuin/gopher-lua). + +## Installation + +``` +go get github.com/cjoudrey/gluaurl +``` + +## Usage + +```go +import "github.com/yuin/gopher-lua" +import "github.com/cjoudrey/gluaurl" + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("url", gluaurl.Loader) + + if err := L.DoString(` + + local url = require("url") + + parsed_url = url.parse("http://example.com/") + + print(parsed_url.host) + + `); err != nil { + panic(err) + } +} +``` + +## API + +- [`url.parse(url)`](#urlparseurl) +- [`url.build(options)`](#urlbuildoptions) +- [`url.build_query_string(query_params)`](#urlbuild_query_stringquery_params) +- [`url.resolve(from, to)`](#urlresolvefrom-to) + +### url.parse(url) + +Parse URL into a table of key/value components. + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL to parsed | + +**Returns** + +Table with parsed URL or (nil, error message) + +| Name | Type | Description | +| -------- | ------ | ----------- | +| scheme | String | Scheme of the URL | +| username | String | Username | +| password | String | Password | +| host | String | Host and port of the URL | +| path | String | Path | +| query | String | Query string | +| fragment | String | Fragment | + +### url.build(options) + +Assemble a URL string from a table of URL components. + +**Attributes** + +| Name | Type | Description | +| ------- | ----- | ----------- | +| options | Table | Table with URL components, see [`url.parse`](#urlparseurl) for list of valid components | + +**Returns** + +String + +### url.build_query_string(query_params) + +Assemble table of query string parameters into a string. + +**Attributes** + +| Name | Type | Description | +| ------------ | ----- | ----------- | +| query_params | Table | Table with query parameters | + +**Returns** + +String + +### url.resolve(from, to) + +Take a base URL, and a href URL, and resolve them as a browser would for an anchor tag. + +| Name | Type | Description | +| ---- | ------ | ----------- | +| from | String | base URL | +| to | String | href URL | + +**Returns** + +String or (nil, error message) diff --git a/vendor/github.com/cjoudrey/gluaurl/gluaurl.go b/vendor/github.com/cjoudrey/gluaurl/gluaurl.go new file mode 100644 index 0000000..287769a --- /dev/null +++ b/vendor/github.com/cjoudrey/gluaurl/gluaurl.go @@ -0,0 +1,178 @@ +package gluaurl + +import "github.com/yuin/gopher-lua" +import "net/url" +import "strings" +import "fmt" +import "sort" +import "regexp" + +var rBracket = regexp.MustCompile("\\[\\]$") + +func Loader(L *lua.LState) int { + mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{ + "parse": parse, + "build": build, + "build_query_string": buildQueryString, + "resolve": resolve, + }) + L.Push(mod) + return 1 +} + +func parse(L *lua.LState) int { + parsed := L.NewTable() + + url, err := url.Parse(L.CheckString(1)) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(fmt.Sprintf("%s", err))) + return 2 + } + + parsed.RawSetString("scheme", lua.LString(url.Scheme)) + + if url.User != nil { + parsed.RawSetString("username", lua.LString(url.User.Username())) + + if password, hasPassword := url.User.Password(); hasPassword { + parsed.RawSetString("password", lua.LString(password)) + } else { + parsed.RawSetString("password", lua.LNil) + } + + } else { + parsed.RawSetString("username", lua.LNil) + parsed.RawSetString("password", lua.LNil) + } + + parsed.RawSetString("host", lua.LString(url.Host)) + parsed.RawSetString("path", lua.LString(url.Path)) + parsed.RawSetString("query", lua.LString(url.RawQuery)) + parsed.RawSetString("fragment", lua.LString(url.Fragment)) + + L.Push(parsed) + + return 1 +} + +func build(L *lua.LState) int { + options := L.CheckTable(1) + + buildUrl := url.URL{} + + if scheme := options.RawGetString("scheme"); scheme != lua.LNil { + buildUrl.Scheme = scheme.String() + } + + if username := options.RawGetString("username"); username != lua.LNil { + if password := options.RawGetString("password"); password != lua.LNil { + buildUrl.User = url.UserPassword(username.String(), password.String()) + } else { + buildUrl.User = url.User(username.String()) + } + } + + if host := options.RawGetString("host"); host != lua.LNil { + buildUrl.Host = host.String() + } + + if path := options.RawGetString("path"); path != lua.LNil { + buildUrl.Path = path.String() + } + + if query := options.RawGetString("query"); query != lua.LNil { + buildUrl.RawQuery = query.String() + } + + if fragment := options.RawGetString("fragment"); fragment != lua.LNil { + buildUrl.Fragment = fragment.String() + } + + L.Push(lua.LString(buildUrl.String())) + + return 1 +} + +func buildQueryString(L *lua.LState) int { + options := L.CheckTable(1) + + ret := make([]string, 0) + + options.ForEach(func(key, value lua.LValue) { + toQueryString(key.String(), value, &ret) + }) + + sort.Strings(ret) + + L.Push(lua.LString(strings.Join(ret, "&"))) + + return 1 +} + +func toQueryString(prefix string, lv lua.LValue, ret *[]string) { + switch v := lv.(type) { + case lua.LBool: + *ret = append(*ret, url.QueryEscape(prefix)+"="+v.String()) + break + + case lua.LNumber: + *ret = append(*ret, url.QueryEscape(prefix)+"="+v.String()) + break + + case lua.LString: + *ret = append(*ret, url.QueryEscape(prefix)+"="+url.QueryEscape(v.String())) + break + + case *lua.LTable: + maxn := v.MaxN() + if maxn == 0 { + ret2 := make([]string, 0) + v.ForEach(func(key lua.LValue, value lua.LValue) { + toQueryString(prefix+"["+key.String()+"]", value, &ret2) + }) + sort.Strings(ret2) + *ret = append(*ret, strings.Join(ret2, "&")) + } else { + ret2 := make([]string, 0) + for i := 1; i <= maxn; i++ { + vi := v.RawGetInt(i) + + if rBracket.MatchString(prefix) { + ret2 = append(ret2, url.QueryEscape(prefix)+"="+vi.String()) + } else { + if vi.Type() == lua.LTTable { + toQueryString(fmt.Sprintf("%s[%d]", prefix, i-1), vi, &ret2) + } else { + toQueryString(prefix+"[]", vi, &ret2) + } + } + } + *ret = append(*ret, strings.Join(ret2, "&")) + } + break + } +} + +func resolve(L *lua.LState) int { + from := L.CheckString(1) + to := L.CheckString(2) + + fromUrl, err := url.Parse(from) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(fmt.Sprintf("%s", err))) + return 2 + } + + toUrl, err := url.Parse(to) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(fmt.Sprintf("%s", err))) + return 2 + } + + resolvedUrl := fromUrl.ResolveReference(toUrl).String() + L.Push(lua.LString(resolvedUrl)) + return 1 +} diff --git a/vendor/github.com/cjoudrey/gluaurl/gluaurl_test.go b/vendor/github.com/cjoudrey/gluaurl/gluaurl_test.go new file mode 100644 index 0000000..a21a675 --- /dev/null +++ b/vendor/github.com/cjoudrey/gluaurl/gluaurl_test.go @@ -0,0 +1,250 @@ +package gluaurl + +import "github.com/yuin/gopher-lua" +import "testing" +import "os" +import "bytes" +import "io" +import "strings" + +func TestParse(t *testing.T) { + output, err := evalScript(` +local url = require("url") + +parsed = url.parse("http://bob:secret@example.com:8080/products?page=2#something") + +print(parsed.scheme) +print(parsed.username) +print(parsed.password) +print(parsed.host) +print(parsed.path) +print(parsed.query) +print(parsed.fragment) +`) + + if err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } else { + if expected := `http +bob +secret +example.com:8080 +/products +page=2 +something +`; expected != output { + t.Errorf("Expected output does not match actual output\nExpected: %s\nActual: %s", expected, output) + } + } +} + +func TestParseOnlyHost(t *testing.T) { + output, err := evalScript(` +local url = require("url") + +parsed = url.parse("https://example.com") + +print(parsed.scheme) +print(parsed.username) +print(parsed.password) +print(parsed.host) +print(parsed.path) +print(parsed.query) +print(parsed.fragment) +`) + + if err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } else { + if expected := `https +nil +nil +example.com + + + +`; expected != output { + t.Errorf("Expected output does not match actual output\nExpected: %s\nActual: %s", expected, output) + } + } +} + +func TestBuild(t *testing.T) { + output, err := evalScript(` +local url = require("url") + +built = url.build({ + scheme="https", + username="bob", + password="secret", + host="example.com:8080", + path="/products", + query="page=2", + fragment="something" +}) + +print(built) +`) + + if err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } else { + if expected := `https://bob:secret@example.com:8080/products?page=2#something +`; expected != output { + t.Errorf("Expected output does not match actual output\nExpected: %s\nActual: %s", expected, output) + } + } +} + +func TestBuildEmpty(t *testing.T) { + output, err := evalScript(` +local url = require("url") + +built = url.build({}) + +print(built) +`) + + if err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } else { + if expected := ` +`; expected != output { + t.Errorf("Expected output does not match actual output\nExpected: %s\nActual: %s", expected, output) + } + } +} + +func TestBuildQueryString(t *testing.T) { + output, err := evalScript(` +local url = require("url") + +function assert_query_string(options, expected, message) + actual = url.build_query_string(options) + + if expected ~= actual then + print("Failed to build '" .. message .. "'") + print("Expected:") + print(expected) + print("Actual:") + print(actual) + end +end + +assert_query_string( + {foo="bar", baz=42, quux="All your base are belong to us"}, + "baz=42&foo=bar&quux=All+your+base+are+belong+to+us", + "simple" +) + +assert_query_string( + {someName={1, 2, 3}, regularThing="blah"}, + "regularThing=blah&someName%5B%5D=1&someName%5B%5D=2&someName%5B%5D=3", + "with array" +) + +assert_query_string( + {foo={"a", "b", "c"}}, + "foo%5B%5D=a&foo%5B%5D=b&foo%5B%5D=c", + "with array of strings" +) + +assert_query_string( + {foo={"baz", 42, "All your base are belong to us"}}, + "foo%5B%5D=baz&foo%5B%5D=42&foo%5B%5D=All+your+base+are+belong+to+us", + "more array" +) + +assert_query_string( + {foo={bar="baz", beep=42, quux="All your base are belong to us"}}, + "foo%5Bbar%5D=baz&foo%5Bbeep%5D=42&foo%5Bquux%5D=All+your+base+are+belong+to+us", + "even more arrays" +) + +assert_query_string( + {a={1,2}, b={c=3, d={4,5}, e={ x={6}, y=7, z={8,9} }, f=true, g=false, h=""}, i={10,11}, j=true, k=false, l={"",0}, m="cowboy hat?" }, + "a%5B%5D=1&a%5B%5D=2&b%5Bc%5D=3&b%5Bd%5D%5B%5D=4&b%5Bd%5D%5B%5D=5&b%5Be%5D%5Bx%5D%5B%5D=6&b%5Be%5D%5By%5D=7&b%5Be%5D%5Bz%5D%5B%5D=8&b%5Be%5D%5Bz%5D%5B%5D=9&b%5Bf%5D=true&b%5Bg%5D=false&b%5Bh%5D=&i%5B%5D=10&i%5B%5D=11&j=true&k=false&l%5B%5D=&l%5B%5D=0&m=cowboy+hat%3F", + "huge structure" +) + +assert_query_string( + { a={0, { 1, 2 }, { 3, { 4, 5 }, { 6 } }, { b= { 7, { 8, 9 }, { { c=10, d=11 } }, { { 12 } }, { { { 13 } } }, { e= { f= { g={ 14, { 15 } } } } }, 16 } }, 17 } }, + "a%5B%5D=0&a%5B1%5D%5B%5D=1&a%5B1%5D%5B%5D=2&a%5B2%5D%5B%5D=3&a%5B2%5D%5B1%5D%5B%5D=4&a%5B2%5D%5B1%5D%5B%5D=5&a%5B2%5D%5B2%5D%5B%5D=6&a%5B3%5D%5Bb%5D%5B%5D=7&a%5B3%5D%5Bb%5D%5B1%5D%5B%5D=8&a%5B3%5D%5Bb%5D%5B1%5D%5B%5D=9&a%5B3%5D%5Bb%5D%5B2%5D%5B0%5D%5Bc%5D=10&a%5B3%5D%5Bb%5D%5B2%5D%5B0%5D%5Bd%5D=11&a%5B3%5D%5Bb%5D%5B3%5D%5B0%5D%5B%5D=12&a%5B3%5D%5Bb%5D%5B4%5D%5B0%5D%5B0%5D%5B%5D=13&a%5B3%5D%5Bb%5D%5B5%5D%5Be%5D%5Bf%5D%5Bg%5D%5B%5D=14&a%5B3%5D%5Bb%5D%5B5%5D%5Be%5D%5Bf%5D%5Bg%5D%5B1%5D%5B%5D=15&a%5B3%5D%5Bb%5D%5B%5D=16&a%5B%5D=17", + "nested arrays" +) + +assert_query_string( + { a= {1,2,3}, ["b[]"]= {4,5,6}, ["c[d]"]= {7,8,9}, e= { f= {10}, g= {11,12}, h= 13 } }, + "a%5B%5D=1&a%5B%5D=2&a%5B%5D=3&b%5B%5D=4&b%5B%5D=5&b%5B%5D=6&c%5Bd%5D%5B%5D=7&c%5Bd%5D%5B%5D=8&c%5Bd%5D%5B%5D=9&e%5Bf%5D%5B%5D=10&e%5Bg%5D%5B%5D=11&e%5Bg%5D%5B%5D=12&e%5Bh%5D=13", + "make sure params are not double-encoded" +) + +`) + + if err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } else { + if expected := ``; expected != output { + t.Error(output) + } + } +} + +func TestResolve(t *testing.T) { + output, err := evalScript(` +local url = require("url") + +print(url.resolve('/one/two/three', 'four')) +print(url.resolve('http://example.com/', '/one')) +print(url.resolve('http://example.com/one', '/two')) +print(url.resolve('https://example.com/one', '//example2.com')) +`) + + if err != nil { + t.Errorf("Failed to evaluate script: %s", err) + } else { + if expected := `/one/two/four +http://example.com/one +http://example.com/two +https://example2.com +`; expected != output { + t.Errorf("Expected output does not match actual output\nExpected: %s\nActual: %s", expected, output) + } + } +} + +func evalScript(script string) (string, error) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("url", Loader) + + var err error + + out := captureStdout(func() { + err = L.DoString(script) + }) + + return out, err +} + +func captureStdout(inner func()) string { + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + outC := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + inner() + + w.Close() + os.Stdout = oldStdout + out := strings.Replace(<-outC, "\r", "", -1) + + return out +} diff --git a/vendor/github.com/howeyc/gopass/.travis.yml b/vendor/github.com/howeyc/gopass/.travis.yml new file mode 100644 index 0000000..cc5d509 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/.travis.yml @@ -0,0 +1,11 @@ +language: go + +os: + - linux + - osx + +go: + - 1.3 + - 1.4 + - 1.5 + - tip diff --git a/vendor/github.com/howeyc/gopass/LICENSE.txt b/vendor/github.com/howeyc/gopass/LICENSE.txt new file mode 100644 index 0000000..14f7470 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/LICENSE.txt @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2012 Chris Howey + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/howeyc/gopass/OPENSOLARIS.LICENSE b/vendor/github.com/howeyc/gopass/OPENSOLARIS.LICENSE new file mode 100644 index 0000000..da23621 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/OPENSOLARIS.LICENSE @@ -0,0 +1,384 @@ +Unless otherwise noted, all files in this distribution are released +under the Common Development and Distribution License (CDDL). +Exceptions are noted within the associated source files. + +-------------------------------------------------------------------- + + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 + +1. Definitions. + + 1.1. "Contributor" means each individual or entity that creates + or contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Software, prior Modifications used by a Contributor (if any), + and the Modifications made by that particular Contributor. + + 1.3. "Covered Software" means (a) the Original Software, or (b) + Modifications, or (c) the combination of files containing + Original Software with files containing Modifications, in + each case including portions thereof. + + 1.4. "Executable" means the Covered Software in any form other + than Source Code. + + 1.5. "Initial Developer" means the individual or entity that first + makes Original Software available under this License. + + 1.6. "Larger Work" means a work which combines Covered Software or + portions thereof with code not governed by the terms of this + License. + + 1.7. "License" means this document. + + 1.8. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed + herein. + + 1.9. "Modifications" means the Source Code and Executable form of + any of the following: + + A. Any file that results from an addition to, deletion from or + modification of the contents of a file containing Original + Software or previous Modifications; + + B. Any new file that contains any part of the Original + Software or previous Modifications; or + + C. Any new file that is contributed or otherwise made + available under the terms of this License. + + 1.10. "Original Software" means the Source Code and Executable + form of computer software code that is originally released + under this License. + + 1.11. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, + process, and apparatus claims, in any patent Licensable by + grantor. + + 1.12. "Source Code" means (a) the common form of computer software + code in which modifications are made and (b) associated + documentation included in or with such code. + + 1.13. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms + of, this License. For legal entities, "You" includes any + entity which controls, is controlled by, or is under common + control with You. For purposes of this definition, + "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty + percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, the Initial + Developer hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer, to use, + reproduce, modify, display, perform, sublicense and + distribute the Original Software (or portions thereof), + with or without Modifications, and/or as part of a Larger + Work; and + + (b) under Patent Claims infringed by the making, using or + selling of Original Software, to make, have made, use, + practice, sell, and offer for sale, and/or otherwise + dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are + effective on the date Initial Developer first distributes + or otherwise makes the Original Software available to a + third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: (1) for code that You delete from the Original + Software, or (2) for infringements caused by: (i) the + modification of the Original Software, or (ii) the + combination of the Original Software with other software + or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor to use, reproduce, + modify, display, perform, sublicense and distribute the + Modifications created by such Contributor (or portions + thereof), either on an unmodified basis, with other + Modifications, as Covered Software and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either + alone and/or in combination with its Contributor Version + (or portions of such combination), to make, use, sell, + offer for sale, have made, and/or otherwise dispose of: + (1) Modifications made by that Contributor (or portions + thereof); and (2) the combination of Modifications made by + that Contributor with its Contributor Version (or portions + of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first distributes or + otherwise makes the Modifications available to a third + party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: (1) for any code that Contributor has deleted + from the Contributor Version; (2) for infringements caused + by: (i) third party modifications of Contributor Version, + or (ii) the combination of Modifications made by that + Contributor with other software (except as part of the + Contributor Version) or other devices; or (3) under Patent + Claims infringed by Covered Software in the absence of + Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + + Any Covered Software that You distribute or otherwise make + available in Executable form must also be made available in Source + Code form and that Source Code form must be distributed only under + the terms of this License. You must include a copy of this + License with every copy of the Source Code form of the Covered + Software You distribute or otherwise make available. You must + inform recipients of any such Covered Software in Executable form + as to how they can obtain such Covered Software in Source Code + form in a reasonable manner on or through a medium customarily + used for software exchange. + + 3.2. Modifications. + + The Modifications that You create or to which You contribute are + governed by the terms of this License. You represent that You + believe Your Modifications are Your original creation(s) and/or + You have sufficient rights to grant the rights conveyed by this + License. + + 3.3. Required Notices. + + You must include a notice in each of Your Modifications that + identifies You as the Contributor of the Modification. You may + not remove or alter any copyright, patent or trademark notices + contained within the Covered Software, or any notices of licensing + or any descriptive text giving attribution to any Contributor or + the Initial Developer. + + 3.4. Application of Additional Terms. + + You may not offer or impose any terms on any Covered Software in + Source Code form that alters or restricts the applicable version + of this License or the recipients' rights hereunder. You may + choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, you may do so only on Your own behalf, + and not on behalf of the Initial Developer or any Contributor. + You must make it absolutely clear that any such warranty, support, + indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every + Contributor for any liability incurred by the Initial Developer or + such Contributor as a result of warranty, support, indemnity or + liability terms You offer. + + 3.5. Distribution of Executable Versions. + + You may distribute the Executable form of the Covered Software + under the terms of this License or under the terms of a license of + Your choice, which may contain terms different from this License, + provided that You are in compliance with the terms of this License + and that the license for the Executable form does not attempt to + limit or alter the recipient's rights in the Source Code form from + the rights set forth in this License. If You distribute the + Covered Software in Executable form under a different license, You + must make it absolutely clear that any terms which differ from + this License are offered by You alone, not by the Initial + Developer or Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred + by the Initial Developer or such Contributor as a result of any + such terms You offer. + + 3.6. Larger Works. + + You may create a Larger Work by combining Covered Software with + other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, + You must make sure the requirements of this License are fulfilled + for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + + Sun Microsystems, Inc. is the initial license steward and may + publish revised and/or new versions of this License from time to + time. Each version will be given a distinguishing version number. + Except as provided in Section 4.3, no one other than the license + steward has the right to modify this License. + + 4.2. Effect of New Versions. + + You may always continue to use, distribute or otherwise make the + Covered Software available under the terms of the version of the + License under which You originally received the Covered Software. + If the Initial Developer includes a notice in the Original + Software prohibiting it from being distributed or otherwise made + available under any subsequent version of the License, You must + distribute and make the Covered Software available under the terms + of the version of the License under which You originally received + the Covered Software. Otherwise, You may also choose to use, + distribute or otherwise make the Covered Software available under + the terms of any subsequent version of the License published by + the license steward. + + 4.3. Modified Versions. + + When You are an Initial Developer and You want to create a new + license for Your Original Software, You may create and use a + modified version of this License if You: (a) rename the license + and remove any references to the name of the license steward + (except to note that the license differs from this License); and + (b) otherwise make it clear that the license contains terms which + differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED + SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR + PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND + PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY + COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE + INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY + NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF + WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. + Provisions which, by their nature, must remain in effect beyond + the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding + declaratory judgment actions) against Initial Developer or a + Contributor (the Initial Developer or Contributor against whom You + assert such claim is referred to as "Participant") alleging that + the Participant Software (meaning the Contributor Version where + the Participant is a Contributor or the Original Software where + the Participant is the Initial Developer) directly or indirectly + infringes any patent, then any and all rights granted directly or + indirectly to You by such Participant, the Initial Developer (if + the Initial Developer is not the Participant) and all Contributors + under Sections 2.1 and/or 2.2 of this License shall, upon 60 days + notice from Participant terminate prospectively and automatically + at the expiration of such 60 day notice period, unless if within + such 60 day period You withdraw Your claim with respect to the + Participant Software against such Participant either unilaterally + or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, + all end user licenses that have been validly granted by You or any + distributor hereunder prior to termination (excluding licenses + granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE + INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF + COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE + LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK + STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT + APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO + NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR + CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT + APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a "commercial item," as that term is + defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial + computer software" (as that term is defined at 48 + C.F.R. 252.227-7014(a)(1)) and "commercial computer software + documentation" as such terms are used in 48 C.F.R. 12.212 + (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 + C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all + U.S. Government End Users acquire Covered Software with only those + rights set forth herein. This U.S. Government Rights clause is in + lieu of, and supersedes, any other FAR, DFAR, or other clause or + provision that addresses Government rights in computer software + under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed + by the law of the jurisdiction specified in a notice contained + within the Original Software (except to the extent applicable law, + if any, provides otherwise), excluding such jurisdiction's + conflict-of-law provisions. Any litigation relating to this + License shall be subject to the jurisdiction of the courts located + in the jurisdiction and venue specified in a notice contained + within the Original Software, with the losing party responsible + for costs, including, without limitation, court costs and + reasonable attorneys' fees and expenses. The application of the + United Nations Convention on Contracts for the International Sale + of Goods is expressly excluded. Any law or regulation which + provides that the language of a contract shall be construed + against the drafter shall not apply to this License. You agree + that You alone are responsible for compliance with the United + States export administration regulations (and the export control + laws and regulation of any other countries) when You use, + distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or + indirectly, out of its utilization of rights under this License + and You agree to work with Initial Developer and Contributors to + distribute such responsibility on an equitable basis. Nothing + herein is intended or shall be deemed to constitute any admission + of liability. + +-------------------------------------------------------------------- + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND +DISTRIBUTION LICENSE (CDDL) + +For Covered Software in this distribution, this License shall +be governed by the laws of the State of California (excluding +conflict-of-law provisions). + +Any litigation relating to this License shall be subject to the +jurisdiction of the Federal Courts of the Northern District of +California and the state courts of the State of California, with +venue lying in Santa Clara County, California. diff --git a/vendor/github.com/howeyc/gopass/README.md b/vendor/github.com/howeyc/gopass/README.md new file mode 100644 index 0000000..2d6a4e7 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/README.md @@ -0,0 +1,27 @@ +# getpasswd in Go [![GoDoc](https://godoc.org/github.com/howeyc/gopass?status.svg)](https://godoc.org/github.com/howeyc/gopass) [![Build Status](https://secure.travis-ci.org/howeyc/gopass.png?branch=master)](http://travis-ci.org/howeyc/gopass) + +Retrieve password from user terminal or piped input without echo. + +Verified on BSD, Linux, and Windows. + +Example: +```go +package main + +import "fmt" +import "github.com/howeyc/gopass" + +func main() { + fmt.Printf("Password: ") + + // Silent. For printing *'s use gopass.GetPasswdMasked() + pass, err := gopass.GetPasswd() + if err != nil { + // Handle gopass.ErrInterrupted or getch() read error + } + + // Do something with pass +} +``` + +Caution: Multi-byte characters not supported! diff --git a/vendor/github.com/howeyc/gopass/pass.go b/vendor/github.com/howeyc/gopass/pass.go new file mode 100644 index 0000000..f5bd5a5 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/pass.go @@ -0,0 +1,110 @@ +package gopass + +import ( + "errors" + "fmt" + "io" + "os" +) + +type FdReader interface { + io.Reader + Fd() uintptr +} + +var defaultGetCh = func(r io.Reader) (byte, error) { + buf := make([]byte, 1) + if n, err := r.Read(buf); n == 0 || err != nil { + if err != nil { + return 0, err + } + return 0, io.EOF + } + return buf[0], nil +} + +var ( + maxLength = 512 + ErrInterrupted = errors.New("interrupted") + ErrMaxLengthExceeded = fmt.Errorf("maximum byte limit (%v) exceeded", maxLength) + + // Provide variable so that tests can provide a mock implementation. + getch = defaultGetCh +) + +// getPasswd returns the input read from terminal. +// If prompt is not empty, it will be output as a prompt to the user +// If masked is true, typing will be matched by asterisks on the screen. +// Otherwise, typing will echo nothing. +func getPasswd(prompt string, masked bool, r FdReader, w io.Writer) ([]byte, error) { + var err error + var pass, bs, mask []byte + if masked { + bs = []byte("\b \b") + mask = []byte("*") + } + + if isTerminal(r.Fd()) { + if oldState, err := makeRaw(r.Fd()); err != nil { + return pass, err + } else { + defer func() { + restore(r.Fd(), oldState) + fmt.Fprintln(w) + }() + } + } + + if prompt != "" { + fmt.Fprint(w, prompt) + } + + // Track total bytes read, not just bytes in the password. This ensures any + // errors that might flood the console with nil or -1 bytes infinitely are + // capped. + var counter int + for counter = 0; counter <= maxLength; counter++ { + if v, e := getch(r); e != nil { + err = e + break + } else if v == 127 || v == 8 { + if l := len(pass); l > 0 { + pass = pass[:l-1] + fmt.Fprint(w, string(bs)) + } + } else if v == 13 || v == 10 { + break + } else if v == 3 { + err = ErrInterrupted + break + } else if v != 0 { + pass = append(pass, v) + fmt.Fprint(w, string(mask)) + } + } + + if counter > maxLength { + err = ErrMaxLengthExceeded + } + + return pass, err +} + +// GetPasswd returns the password read from the terminal without echoing input. +// The returned byte array does not include end-of-line characters. +func GetPasswd() ([]byte, error) { + return getPasswd("", false, os.Stdin, os.Stdout) +} + +// GetPasswdMasked returns the password read from the terminal, echoing asterisks. +// The returned byte array does not include end-of-line characters. +func GetPasswdMasked() ([]byte, error) { + return getPasswd("", true, os.Stdin, os.Stdout) +} + +// GetPasswdPrompt prompts the user and returns the password read from the terminal. +// If mask is true, then asterisks are echoed. +// The returned byte array does not include end-of-line characters. +func GetPasswdPrompt(prompt string, mask bool, r FdReader, w io.Writer) ([]byte, error) { + return getPasswd(prompt, mask, r, w) +} diff --git a/vendor/github.com/howeyc/gopass/pass_test.go b/vendor/github.com/howeyc/gopass/pass_test.go new file mode 100644 index 0000000..7ac3151 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/pass_test.go @@ -0,0 +1,225 @@ +package gopass + +import ( + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "testing" + "time" +) + +// TestGetPasswd tests the password creation and output based on a byte buffer +// as input to mock the underlying getch() methods. +func TestGetPasswd(t *testing.T) { + type testData struct { + input []byte + + // Due to how backspaces are written, it is easier to manually write + // each expected output for the masked cases. + masked string + password string + byesLeft int + reason string + } + + ds := []testData{ + testData{[]byte("abc\n"), "***", "abc", 0, "Password parsing should stop at \\n"}, + testData{[]byte("abc\r"), "***", "abc", 0, "Password parsing should stop at \\r"}, + testData{[]byte("a\nbc\n"), "*", "a", 3, "Password parsing should stop at \\n"}, + testData{[]byte("*!]|\n"), "****", "*!]|", 0, "Special characters shouldn't affect the password."}, + + testData{[]byte("abc\r\n"), "***", "abc", 1, + "Password parsing should stop at \\r; Windows LINE_MODE should be unset so \\r is not converted to \\r\\n."}, + + testData{[]byte{'a', 'b', 'c', 8, '\n'}, "***\b \b", "ab", 0, "Backspace byte should remove the last read byte."}, + testData{[]byte{'a', 'b', 127, 'c', '\n'}, "**\b \b*", "ac", 0, "Delete byte should remove the last read byte."}, + testData{[]byte{'a', 'b', 127, 'c', 8, 127, '\n'}, "**\b \b*\b \b\b \b", "", 0, "Successive deletes continue to delete."}, + testData{[]byte{8, 8, 8, '\n'}, "", "", 0, "Deletes before characters are noops."}, + testData{[]byte{8, 8, 8, 'a', 'b', 'c', '\n'}, "***", "abc", 0, "Deletes before characters are noops."}, + + testData{[]byte{'a', 'b', 0, 'c', '\n'}, "***", "abc", 0, + "Nil byte should be ignored due; may get unintended nil bytes from syscalls on Windows."}, + } + + // Redirecting output for tests as they print to os.Stdout but we want to + // capture and test the output. + for _, masked := range []bool{true, false} { + for _, d := range ds { + pipeBytesToStdin(d.input) + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err.Error()) + } + + result, err := getPasswd("", masked, os.Stdin, w) + if err != nil { + t.Errorf("Error getting password: %s", err.Error()) + } + leftOnBuffer := flushStdin() + + // Test output (masked and unmasked). Delete/backspace actually + // deletes, overwrites and deletes again. As a result, we need to + // remove those from the pipe afterwards to mimic the console's + // interpretation of those bytes. + w.Close() + output, err := ioutil.ReadAll(r) + if err != nil { + t.Fatal(err.Error()) + } + var expectedOutput []byte + if masked { + expectedOutput = []byte(d.masked) + } else { + expectedOutput = []byte("") + } + if bytes.Compare(expectedOutput, output) != 0 { + t.Errorf("Expected output to equal %v (%q) but got %v (%q) instead when masked=%v. %s", expectedOutput, string(expectedOutput), output, string(output), masked, d.reason) + } + + if string(result) != d.password { + t.Errorf("Expected %q but got %q instead when masked=%v. %s", d.password, result, masked, d.reason) + } + + if leftOnBuffer != d.byesLeft { + t.Errorf("Expected %v bytes left on buffer but instead got %v when masked=%v. %s", d.byesLeft, leftOnBuffer, masked, d.reason) + } + } + } +} + +// TestPipe ensures we get our expected pipe behavior. +func TestPipe(t *testing.T) { + type testData struct { + input string + password string + expError error + } + ds := []testData{ + testData{"abc", "abc", io.EOF}, + testData{"abc\n", "abc", nil}, + testData{"abc\r", "abc", nil}, + testData{"abc\r\n", "abc", nil}, + } + + for _, d := range ds { + _, err := pipeToStdin(d.input) + if err != nil { + t.Log("Error writing input to stdin:", err) + t.FailNow() + } + pass, err := GetPasswd() + if string(pass) != d.password { + t.Errorf("Expected %q but got %q instead.", d.password, string(pass)) + } + if err != d.expError { + t.Errorf("Expected %v but got %q instead.", d.expError, err) + } + } +} + +// flushStdin reads from stdin for .5 seconds to ensure no bytes are left on +// the buffer. Returns the number of bytes read. +func flushStdin() int { + ch := make(chan byte) + go func(ch chan byte) { + reader := bufio.NewReader(os.Stdin) + for { + b, err := reader.ReadByte() + if err != nil { // Maybe log non io.EOF errors, if you want + close(ch) + return + } + ch <- b + } + close(ch) + }(ch) + + numBytes := 0 + for { + select { + case _, ok := <-ch: + if !ok { + return numBytes + } + numBytes++ + case <-time.After(500 * time.Millisecond): + return numBytes + } + } + return numBytes +} + +// pipeToStdin pipes the given string onto os.Stdin by replacing it with an +// os.Pipe. The write end of the pipe is closed so that EOF is read after the +// final byte. +func pipeToStdin(s string) (int, error) { + pipeReader, pipeWriter, err := os.Pipe() + if err != nil { + fmt.Println("Error getting os pipes:", err) + os.Exit(1) + } + os.Stdin = pipeReader + w, err := pipeWriter.WriteString(s) + pipeWriter.Close() + return w, err +} + +func pipeBytesToStdin(b []byte) (int, error) { + return pipeToStdin(string(b)) +} + +// TestGetPasswd_Err tests errors are properly handled from getch() +func TestGetPasswd_Err(t *testing.T) { + var inBuffer *bytes.Buffer + getch = func(io.Reader) (byte, error) { + b, err := inBuffer.ReadByte() + if err != nil { + return 13, err + } + if b == 'z' { + return 'z', fmt.Errorf("Forced error; byte returned should not be considered accurate.") + } + return b, nil + } + defer func() { getch = defaultGetCh }() + + for input, expectedPassword := range map[string]string{"abc": "abc", "abzc": "ab"} { + inBuffer = bytes.NewBufferString(input) + p, err := GetPasswdMasked() + if string(p) != expectedPassword { + t.Errorf("Expected %q but got %q instead.", expectedPassword, p) + } + if err == nil { + t.Errorf("Expected error to be returned.") + } + } +} + +func TestMaxPasswordLength(t *testing.T) { + type testData struct { + input []byte + expectedErr error + + // Helper field to output in case of failure; rather than hundreds of + // bytes. + inputDesc string + } + + ds := []testData{ + testData{append(bytes.Repeat([]byte{'a'}, maxLength), '\n'), nil, fmt.Sprintf("%v 'a' bytes followed by a newline", maxLength)}, + testData{append(bytes.Repeat([]byte{'a'}, maxLength+1), '\n'), ErrMaxLengthExceeded, fmt.Sprintf("%v 'a' bytes followed by a newline", maxLength+1)}, + testData{append(bytes.Repeat([]byte{0x00}, maxLength+1), '\n'), ErrMaxLengthExceeded, fmt.Sprintf("%v 0x00 bytes followed by a newline", maxLength+1)}, + } + + for _, d := range ds { + pipeBytesToStdin(d.input) + _, err := GetPasswd() + if err != d.expectedErr { + t.Errorf("Expected error to be %v; isntead got %v from %v", d.expectedErr, err, d.inputDesc) + } + } +} diff --git a/vendor/github.com/howeyc/gopass/terminal.go b/vendor/github.com/howeyc/gopass/terminal.go new file mode 100644 index 0000000..0835641 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/terminal.go @@ -0,0 +1,25 @@ +// +build !solaris + +package gopass + +import "golang.org/x/crypto/ssh/terminal" + +type terminalState struct { + state *terminal.State +} + +func isTerminal(fd uintptr) bool { + return terminal.IsTerminal(int(fd)) +} + +func makeRaw(fd uintptr) (*terminalState, error) { + state, err := terminal.MakeRaw(int(fd)) + + return &terminalState{ + state: state, + }, err +} + +func restore(fd uintptr, oldState *terminalState) error { + return terminal.Restore(int(fd), oldState.state) +} diff --git a/vendor/github.com/howeyc/gopass/terminal_solaris.go b/vendor/github.com/howeyc/gopass/terminal_solaris.go new file mode 100644 index 0000000..257e1b4 --- /dev/null +++ b/vendor/github.com/howeyc/gopass/terminal_solaris.go @@ -0,0 +1,69 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +// Below is derived from Solaris source, so CDDL license is included. + +package gopass + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +type terminalState struct { + state *unix.Termios +} + +// isTerminal returns true if there is a terminal attached to the given +// file descriptor. +// Source: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c +func isTerminal(fd uintptr) bool { + var termio unix.Termio + err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) + return err == nil +} + +// makeRaw puts the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +// Source: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c +func makeRaw(fd uintptr) (*terminalState, error) { + oldTermiosPtr, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) + if err != nil { + return nil, err + } + oldTermios := *oldTermiosPtr + + newTermios := oldTermios + newTermios.Lflag &^= syscall.ECHO | syscall.ECHOE | syscall.ECHOK | syscall.ECHONL + if err := unix.IoctlSetTermios(int(fd), unix.TCSETS, &newTermios); err != nil { + return nil, err + } + + return &terminalState{ + state: oldTermiosPtr, + }, nil +} + +func restore(fd uintptr, oldState *terminalState) error { + return unix.IoctlSetTermios(int(fd), unix.TCSETS, oldState.state) +} diff --git a/vendor/github.com/kballard/go-shellquote/LICENSE b/vendor/github.com/kballard/go-shellquote/LICENSE new file mode 100644 index 0000000..a6d7731 --- /dev/null +++ b/vendor/github.com/kballard/go-shellquote/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2014 Kevin Ballard + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kballard/go-shellquote/README b/vendor/github.com/kballard/go-shellquote/README new file mode 100644 index 0000000..4d34e87 --- /dev/null +++ b/vendor/github.com/kballard/go-shellquote/README @@ -0,0 +1,36 @@ +PACKAGE + +package shellquote + import "github.com/kballard/go-shellquote" + + Shellquote provides utilities for joining/splitting strings using sh's + word-splitting rules. + +VARIABLES + +var ( + UnterminatedSingleQuoteError = errors.New("Unterminated single-quoted string") + UnterminatedDoubleQuoteError = errors.New("Unterminated double-quoted string") + UnterminatedEscapeError = errors.New("Unterminated backslash-escape") +) + + +FUNCTIONS + +func Join(args ...string) string + Join quotes each argument and joins them with a space. If passed to + /bin/sh, the resulting string will be split back into the original + arguments. + +func Split(input string) (words []string, err error) + Split splits a string according to /bin/sh's word-splitting rules. It + supports backslash-escapes, single-quotes, and double-quotes. Notably it + does not support the $'' style of quoting. It also doesn't attempt to + perform any other sort of expansion, including brace expansion, shell + expansion, or pathname expansion. + + If the given input has an unterminated quoted string or ends in a + backslash-escape, one of UnterminatedSingleQuoteError, + UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned. + + diff --git a/vendor/github.com/kballard/go-shellquote/both_test.go b/vendor/github.com/kballard/go-shellquote/both_test.go new file mode 100644 index 0000000..9cba3c8 --- /dev/null +++ b/vendor/github.com/kballard/go-shellquote/both_test.go @@ -0,0 +1,29 @@ +package shellquote + +import ( + "reflect" + "testing" + "testing/quick" +) + +// this is called bothtest because it tests Split and Join together + +func TestJoinSplit(t *testing.T) { + f := func(strs []string) bool { + // Join, then split, the input + combined := Join(strs...) + split, err := Split(combined) + if err != nil { + t.Logf("Error splitting %#v: %v", combined, err) + return false + } + if !reflect.DeepEqual(strs, split) { + t.Logf("Input %q did not match output %q", strs, split) + return false + } + return true + } + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +} diff --git a/vendor/github.com/kballard/go-shellquote/doc.go b/vendor/github.com/kballard/go-shellquote/doc.go new file mode 100644 index 0000000..9445fa4 --- /dev/null +++ b/vendor/github.com/kballard/go-shellquote/doc.go @@ -0,0 +1,3 @@ +// Shellquote provides utilities for joining/splitting strings using sh's +// word-splitting rules. +package shellquote diff --git a/vendor/github.com/kballard/go-shellquote/quote.go b/vendor/github.com/kballard/go-shellquote/quote.go new file mode 100644 index 0000000..72a8cb3 --- /dev/null +++ b/vendor/github.com/kballard/go-shellquote/quote.go @@ -0,0 +1,102 @@ +package shellquote + +import ( + "bytes" + "strings" + "unicode/utf8" +) + +// Join quotes each argument and joins them with a space. +// If passed to /bin/sh, the resulting string will be split back into the +// original arguments. +func Join(args ...string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} + +const ( + specialChars = "\\'\"`${[|&;<>()*?!" + extraSpecialChars = " \t\n" + prefixChars = "~" +) + +func quote(word string, buf *bytes.Buffer) { + // We want to try to produce a "nice" output. As such, we will + // backslash-escape most characters, but if we encounter a space, or if we + // encounter an extra-special char (which doesn't work with + // backslash-escaping) we switch over to quoting the whole word. We do this + // with a space because it's typically easier for people to read multi-word + // arguments when quoted with a space rather than with ugly backslashes + // everywhere. + origLen := buf.Len() + + if len(word) == 0 { + // oops, no content + buf.WriteString("''") + return + } + + cur, prev := word, word + atStart := true + for len(cur) > 0 { + c, l := utf8.DecodeRuneInString(cur) + cur = cur[l:] + if strings.ContainsRune(specialChars, c) || (atStart && strings.ContainsRune(prefixChars, c)) { + // copy the non-special chars up to this point + if len(cur) < len(prev) { + buf.WriteString(prev[0 : len(prev)-len(cur)-l]) + } + buf.WriteByte('\\') + buf.WriteRune(c) + prev = cur + } else if strings.ContainsRune(extraSpecialChars, c) { + // start over in quote mode + buf.Truncate(origLen) + goto quote + } + atStart = false + } + if len(prev) > 0 { + buf.WriteString(prev) + } + return + +quote: + // quote mode + // Use single-quotes, but if we find a single-quote in the word, we need + // to terminate the string, emit an escaped quote, and start the string up + // again + inQuote := false + for len(word) > 0 { + i := strings.IndexRune(word, '\'') + if i == -1 { + break + } + if i > 0 { + if !inQuote { + buf.WriteByte('\'') + inQuote = true + } + buf.WriteString(word[0:i]) + } + word = word[i+1:] + if inQuote { + buf.WriteByte('\'') + inQuote = false + } + buf.WriteString("\\'") + } + if len(word) > 0 { + if !inQuote { + buf.WriteByte('\'') + } + buf.WriteString(word) + buf.WriteByte('\'') + } +} diff --git a/vendor/github.com/kballard/go-shellquote/quote_test.go b/vendor/github.com/kballard/go-shellquote/quote_test.go new file mode 100644 index 0000000..535ed5c --- /dev/null +++ b/vendor/github.com/kballard/go-shellquote/quote_test.go @@ -0,0 +1,31 @@ +package shellquote + +import ( + "testing" +) + +func TestSimpleJoin(t *testing.T) { + for _, elem := range simpleJoinTest { + output := Join(elem.input...) + if output != elem.output { + t.Errorf("Input %q, got %q, expected %q", elem.input, output, elem.output) + } + } +} + +var simpleJoinTest = []struct { + input []string + output string +}{ + {[]string{"test"}, "test"}, + {[]string{"hello goodbye"}, "'hello goodbye'"}, + {[]string{"hello", "goodbye"}, "hello goodbye"}, + {[]string{"don't you know the dewey decimal system?"}, "'don'\\''t you know the dewey decimal system?'"}, + {[]string{"don't", "you", "know", "the", "dewey", "decimal", "system?"}, "don\\'t you know the dewey decimal system\\?"}, + {[]string{"~user", "u~ser", " ~user", "!~user"}, "\\~user u~ser ' ~user' \\!~user"}, + {[]string{"foo*", "M{ovies,usic}", "ab[cd]", "%3"}, "foo\\* M\\{ovies,usic} ab\\[cd] %3"}, + {[]string{"one", "", "three"}, "one '' three"}, + {[]string{"some(parentheses)"}, "some\\(parentheses\\)"}, + {[]string{"$some_ot~her_)spe!cial_*_characters"}, "\\$some_ot~her_\\)spe\\!cial_\\*_characters"}, + {[]string{"' "}, "\\'' '"}, +} diff --git a/vendor/github.com/kballard/go-shellquote/unquote.go b/vendor/github.com/kballard/go-shellquote/unquote.go new file mode 100644 index 0000000..ba3a0f2 --- /dev/null +++ b/vendor/github.com/kballard/go-shellquote/unquote.go @@ -0,0 +1,144 @@ +package shellquote + +import ( + "bytes" + "errors" + "strings" + "unicode/utf8" +) + +var ( + UnterminatedSingleQuoteError = errors.New("Unterminated single-quoted string") + UnterminatedDoubleQuoteError = errors.New("Unterminated double-quoted string") + UnterminatedEscapeError = errors.New("Unterminated backslash-escape") +) + +var ( + splitChars = " \n\t" + singleChar = '\'' + doubleChar = '"' + escapeChar = '\\' + doubleEscapeChars = "$`\"\n\\" +) + +// Split splits a string according to /bin/sh's word-splitting rules. It +// supports backslash-escapes, single-quotes, and double-quotes. Notably it does +// not support the $'' style of quoting. It also doesn't attempt to perform any +// other sort of expansion, including brace expansion, shell expansion, or +// pathname expansion. +// +// If the given input has an unterminated quoted string or ends in a +// backslash-escape, one of UnterminatedSingleQuoteError, +// UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned. +func Split(input string) (words []string, err error) { + var buf bytes.Buffer + words = make([]string, 0) + + for len(input) > 0 { + // skip any splitChars at the start + c, l := utf8.DecodeRuneInString(input) + if strings.ContainsRune(splitChars, c) { + input = input[l:] + continue + } + + var word string + word, input, err = splitWord(input, &buf) + if err != nil { + return + } + words = append(words, word) + } + return +} + +func splitWord(input string, buf *bytes.Buffer) (word string, remainder string, err error) { + buf.Reset() + +raw: + { + cur := input + for len(cur) > 0 { + c, l := utf8.DecodeRuneInString(cur) + cur = cur[l:] + if c == singleChar { + buf.WriteString(input[0 : len(input)-len(cur)-l]) + input = cur + goto single + } else if c == doubleChar { + buf.WriteString(input[0 : len(input)-len(cur)-l]) + input = cur + goto double + } else if c == escapeChar { + buf.WriteString(input[0 : len(input)-len(cur)-l]) + input = cur + goto escape + } else if strings.ContainsRune(splitChars, c) { + buf.WriteString(input[0 : len(input)-len(cur)-l]) + return buf.String(), cur, nil + } + } + if len(input) > 0 { + buf.WriteString(input) + input = "" + } + goto done + } + +escape: + { + if len(input) == 0 { + return "", "", UnterminatedEscapeError + } + c, l := utf8.DecodeRuneInString(input) + if c == '\n' { + // a backslash-escaped newline is elided from the output entirely + } else { + buf.WriteString(input[:l]) + } + input = input[l:] + } + goto raw + +single: + { + i := strings.IndexRune(input, singleChar) + if i == -1 { + return "", "", UnterminatedSingleQuoteError + } + buf.WriteString(input[0:i]) + input = input[i+1:] + goto raw + } + +double: + { + cur := input + for len(cur) > 0 { + c, l := utf8.DecodeRuneInString(cur) + cur = cur[l:] + if c == doubleChar { + buf.WriteString(input[0 : len(input)-len(cur)-l]) + input = cur + goto raw + } else if c == escapeChar { + // bash only supports certain escapes in double-quoted strings + c2, l2 := utf8.DecodeRuneInString(cur) + cur = cur[l2:] + if strings.ContainsRune(doubleEscapeChars, c2) { + buf.WriteString(input[0 : len(input)-len(cur)-l-l2]) + if c2 == '\n' { + // newline is special, skip the backslash entirely + } else { + buf.WriteRune(c2) + } + input = cur + } + } + } + return "", "", UnterminatedDoubleQuoteError + } + +done: + return buf.String(), input, nil +} diff --git a/vendor/github.com/kballard/go-shellquote/unquote_test.go b/vendor/github.com/kballard/go-shellquote/unquote_test.go new file mode 100644 index 0000000..1444a9f --- /dev/null +++ b/vendor/github.com/kballard/go-shellquote/unquote_test.go @@ -0,0 +1,53 @@ +package shellquote + +import ( + "reflect" + "testing" +) + +func TestSimpleSplit(t *testing.T) { + for _, elem := range simpleSplitTest { + output, err := Split(elem.input) + if err != nil { + t.Errorf("Input %q, got error %#v", elem.input, err) + } else if !reflect.DeepEqual(output, elem.output) { + t.Errorf("Input %q, got %q, expected %q", elem.input, output, elem.output) + } + } +} + +func TestErrorSplit(t *testing.T) { + for _, elem := range errorSplitTest { + _, err := Split(elem.input) + if err != elem.error { + t.Errorf("Input %q, got error %#v, expected error %#v", elem.input, err, elem.error) + } + } +} + +var simpleSplitTest = []struct { + input string + output []string +}{ + {"hello", []string{"hello"}}, + {"hello goodbye", []string{"hello", "goodbye"}}, + {"hello goodbye", []string{"hello", "goodbye"}}, + {"glob* test?", []string{"glob*", "test?"}}, + {"don\\'t you know the dewey decimal system\\?", []string{"don't", "you", "know", "the", "dewey", "decimal", "system?"}}, + {"'don'\\''t you know the dewey decimal system?'", []string{"don't you know the dewey decimal system?"}}, + {"one '' two", []string{"one", "", "two"}}, + {"text with\\\na backslash-escaped newline", []string{"text", "witha", "backslash-escaped", "newline"}}, + {"text \"with\na\" quoted newline", []string{"text", "with\na", "quoted", "newline"}}, + {"\"quoted\\d\\\\\\\" text with\\\na backslash-escaped newline\"", []string{"quoted\\d\\\" text witha backslash-escaped newline"}}, + {"foo\"bar\"baz", []string{"foobarbaz"}}, +} + +var errorSplitTest = []struct { + input string + error error +}{ + {"don't worry", UnterminatedSingleQuoteError}, + {"'test'\\''ing", UnterminatedSingleQuoteError}, + {"\"foo'bar", UnterminatedDoubleQuoteError}, + {"foo\\", UnterminatedEscapeError}, +} diff --git a/vendor/github.com/kohkimakimoto/gluaenv/.gitignore b/vendor/github.com/kohkimakimoto/gluaenv/.gitignore new file mode 100644 index 0000000..067ced5 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaenv/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +Thumbs.db +.tags* +._* +.vagrant +*.iml + +!.gitkeep diff --git a/vendor/github.com/kohkimakimoto/gluaenv/LICENSE b/vendor/github.com/kohkimakimoto/gluaenv/LICENSE new file mode 100644 index 0000000..7a3ff6c --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaenv/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Kohki Makimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kohkimakimoto/gluaenv/README.md b/vendor/github.com/kohkimakimoto/gluaenv/README.md new file mode 100644 index 0000000..2600017 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaenv/README.md @@ -0,0 +1,75 @@ +# gluaenv + +Utility package for manipulating environment variables for [gopher-lua](https://github.com/yuin/gopher-lua) + +## Installation + +``` +go get github.com/kohkimakimoto/gluaenv +``` + +## API + +### `env.set(key, value)` + +Same `os.setenv` + +### `env.get(key)` + +Same `os.getenv` + +### `env.loadfile(file)` + +Loads environment variables from a file. The file is as the following: + +``` +AAA=BBB +CCC=DDD +``` + +If this function fails, it returns `nil`, plus a string describing the error. + +## Usage + +```go +package main + +import ( + "github.com/yuin/gopher-lua" + "github.com/kohkimakimoto/gluaenv" +) + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("env", gluaenv.Loader) + if err := L.DoString(` +local env = require("env") + +-- set a environment variable +env.set("HOGE_KEY", "HOGE_VALUE") + +-- get a environment variable +local v = env.get("HOGE_KEY") + +-- load envrironment variables from a file. +env.loadfile("path/to/.env") + +-- file example +-- AAA=BBB +-- CCC=DDD + +`); err != nil { + panic(err) + } +} +``` + +## Author + +Kohki Makimoto + +## License + +MIT license. diff --git a/vendor/github.com/kohkimakimoto/gluaenv/gluaenv.go b/vendor/github.com/kohkimakimoto/gluaenv/gluaenv.go new file mode 100644 index 0000000..94e8bed --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaenv/gluaenv.go @@ -0,0 +1,193 @@ +package gluaenv + +import ( + "github.com/yuin/gopher-lua" + "os" + "bufio" + "strings" + "errors" +) + +func Loader(L *lua.LState) int { + tb := L.NewTable() + L.SetFuncs(tb, map[string]lua.LGFunction{ + "set": envSet, + "get": envGet, + "loadfile": envLoadFile, + }) + L.Push(tb) + + return 1 +} + +func envSet(L *lua.LState) int { + // same github.com/yuin/gopher-lua/oslib.go + err := os.Setenv(L.CheckString(1), L.CheckString(2)) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } else { + L.Push(lua.LTrue) + return 1 + } +} + +func envGet(L *lua.LState) int { + // same github.com/yuin/gopher-lua/oslib.go + v := os.Getenv(L.CheckString(1)) + if len(v) == 0 { + L.Push(lua.LNil) + } else { + L.Push(lua.LString(v)) + } + return 1 +} + +func envLoadFile(L *lua.LState) int { + if err := loadFile(L.CheckString(1)); err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } else { + L.Push(lua.LTrue) + return 1 + } +} + +// loadFile' s code is highly inspired by https://github.com/joho/godotenv/blob/master/godotenv.go +//Copyright (c) 2013 John Barton +// +//MIT License +// +//Permission is hereby granted, free of charge, to any person obtaining +//a copy of this software and associated documentation files (the +//"Software"), to deal in the Software without restriction, including +//without limitation the rights to use, copy, modify, merge, publish, +//distribute, sublicense, and/or sell copies of the Software, and to +//permit persons to whom the Software is furnished to do so, subject to +//the following conditions: +// +//The above copyright notice and this permission notice shall be +//included in all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +//EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +//MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +//NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +//LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +//OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +//WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +func loadFile(filename string) error { + envMap, err := readFile(filename) + if err != nil { + return err + } + + for key, value := range envMap { + os.Setenv(key, value) + } + + return nil +} + +func readFile(filename string) (envMap map[string]string, err error) { + file, err := os.Open(filename) + if err != nil { + return + } + defer file.Close() + + envMap = make(map[string]string) + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + for _, fullLine := range lines { + if !isIgnoredLine(fullLine) { + key, value, err := parseLine(fullLine) + + if err == nil { + envMap[key] = value + } + } + } + return +} + +func parseLine(line string) (key string, value string, err error) { + if len(line) == 0 { + err = errors.New("zero length string") + return + } + + // ditch the comments (but keep quoted hashes) + if strings.Contains(line, "#") { + segmentsBetweenHashes := strings.Split(line, "#") + quotesAreOpen := false + var segmentsToKeep []string + for _, segment := range segmentsBetweenHashes { + if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { + if quotesAreOpen { + quotesAreOpen = false + segmentsToKeep = append(segmentsToKeep, segment) + } else { + quotesAreOpen = true + } + } + + if len(segmentsToKeep) == 0 || quotesAreOpen { + segmentsToKeep = append(segmentsToKeep, segment) + } + } + + line = strings.Join(segmentsToKeep, "#") + } + + // now split key from value + splitString := strings.SplitN(line, "=", 2) + + if len(splitString) != 2 { + // try yaml mode! + splitString = strings.SplitN(line, ":", 2) + } + + if len(splitString) != 2 { + err = errors.New("Can't separate key from value") + return + } + + // Parse the key + key = splitString[0] + if strings.HasPrefix(key, "export") { + key = strings.TrimPrefix(key, "export") + } + key = strings.Trim(key, " ") + + // Parse the value + value = splitString[1] + // trim + value = strings.Trim(value, " ") + + // check if we've got quoted values + if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 { + // pull the quotes off the edges + value = strings.Trim(value, "\"'") + + // expand quotes + value = strings.Replace(value, "\\\"", "\"", -1) + // expand newlines + value = strings.Replace(value, "\\n", "\n", -1) + } + + return +} + +func isIgnoredLine(line string) bool { + trimmedLine := strings.Trim(line, " \n\t") + return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") +} \ No newline at end of file diff --git a/vendor/github.com/kohkimakimoto/gluaenv/gluaenv_test.go b/vendor/github.com/kohkimakimoto/gluaenv/gluaenv_test.go new file mode 100644 index 0000000..47cfc68 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaenv/gluaenv_test.go @@ -0,0 +1,70 @@ +package gluaenv + +import ( + "github.com/yuin/gopher-lua" + "testing" + "io/ioutil" + "os" +) + +func TestSetAndGet(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("env", Loader) + if err := L.DoString(` +local env = require("env") + +env.set("HOGE_KEY", "HOGE_VALUE") + +local v = env.get("HOGE_KEY") + +assert(v == "HOGE_VALUE") + + `); err != nil { + t.Error(err) + } +} + +var sampleFile = ` +AAA=hogehoge +BBB=bbbbbbbb + +# CCC=eeeeee + +DDD=ddddddd +` + +func TestLoadfile(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Errorf("should not raise error: %v", err) + } + if err = ioutil.WriteFile(tmpFile.Name(), []byte(sampleFile), 0644); err != nil { + t.Errorf("should not raise error: %v", err) + } + defer func() { + tmpFile.Close() + os.Remove(tmpFile.Name()) + }() + + L := lua.NewState() + defer L.Close() + L.PreloadModule("env", Loader) + + if err := L.DoString(` +local env = require("env") +env.loadfile("` + tmpFile.Name() + `") + +assert(env.get("AAA") == "hogehoge") +assert(env.get("BBB") == "bbbbbbbb") +assert(env.get("CCC") == nil) +assert(env.get("DDD") == "ddddddd") + +r1, r2 = env.loadfile("` + tmpFile.Name() + `.notfound_file") +assert(r1 == nil) + +`); err != nil { + t.Error(err) + } +} diff --git a/vendor/github.com/kohkimakimoto/gluafs/.gitignore b/vendor/github.com/kohkimakimoto/gluafs/.gitignore new file mode 100644 index 0000000..a5a62ba --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluafs/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +Thumbs.db +.tags* +._* +*.iml diff --git a/vendor/github.com/kohkimakimoto/gluafs/README.md b/vendor/github.com/kohkimakimoto/gluafs/README.md new file mode 100644 index 0000000..e081fb7 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluafs/README.md @@ -0,0 +1,108 @@ +# gluafs + +filesystem utility for [gopher-lua](https://github.com/yuin/gopher-lua). This project is inspired by [layeh/gopher-lfs](https://github.com/layeh/gopher-lfs). + +## Installation + +``` +go get github.com/kohkimakimoto/gluafs +``` + +## API + +### `fs.exists(file)` + +Returns true if the file exists. + +### `fs.read(file)` + +Reads file content and return it. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.write(file, content, [mode])` + +Writes content to the file. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.mkdir(path, [mode, recursive])` + +Create directory. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.remove(path, [recursive])` + +Remove path. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.symlink(target, link)` + +Create symbolic link. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.dirname(path)` + +Returns all but the last element of path. + +### `fs.basename(path)` + +Returns the last element of path. + +### `fs.realpath(path)` + +Returns the real path of a given path in the os. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.getcwd()` + +Returns the current working directory. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.chdir(path)` + +Changes the current working directory. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.file()` + +Returns the script file path. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.dir()` + +Returns the directory path that is parent of the script file. If this function fails, it returns `nil`, plus a string describing the error. + +### `fs.glob(pattern, function)` + +Run the callback function with the files matching pattern. See below example: + +```lua +local fs = require("fs") +local ret, err = fs.glob("/tmp/*", function(file) + print(file.path) + print(file.realpath) +end) +``` + +## Usage + +```go +package main + +import ( + "github.com/yuin/gopher-lua" + "github.com/kohkimakimoto/gluafs" +) + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("fs", gluafs.Loader) + if err := L.DoString(` +local fs = require("fs") +local ret = fs.exists("path/to/file") + +`); err != nil { + panic(err) + } +} +``` + +## Author + +Kohki Makimoto + +## License + +MIT license. diff --git a/vendor/github.com/kohkimakimoto/gluafs/fs.go b/vendor/github.com/kohkimakimoto/gluafs/fs.go new file mode 100644 index 0000000..95198f6 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluafs/fs.go @@ -0,0 +1,327 @@ +package gluafs + +import ( + "fmt" + "github.com/yookoala/realpath" + "github.com/yuin/gopher-lua" + "io/ioutil" + "os" + "path/filepath" + "strconv" +) + +func Loader(L *lua.LState) int { + tb := L.NewTable() + L.SetFuncs(tb, map[string]lua.LGFunction{ + "exists": exists, + "read": read, + "write": write, + "mkdir": mkdir, + "remove": remove, + "symlink": symlink, + "dirname": dirname, + "basename": basename, + "realpath": fnRealpath, + "getcwd": getcwd, + "chdir": chdir, + "file": file, + "dir": dir, + "glob": glob, + }) + L.Push(tb) + + return 1 +} + +func exists(L *lua.LState) int { + var ret bool + + path := L.CheckString(1) + if _, err := os.Stat(path); os.IsNotExist(err) { + ret = false + } else { + ret = true + } + + L.Push(lua.LBool(ret)) + + return 1 +} + +func read(L *lua.LState) int { + path := L.CheckString(1) + if _, err := os.Stat(path); os.IsNotExist(err) { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + content, err := ioutil.ReadFile(path) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + L.Push(lua.LString(string(content))) + return 1 +} + +func write(L *lua.LState) int { + p := L.CheckString(1) + content := []byte(L.CheckString(2)) + var mode os.FileMode = 0666 + + top := L.GetTop() + if top == 3 { + m, err := oct2decimal(L.CheckInt(3)) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + mode = os.FileMode(m) + } + + err := ioutil.WriteFile(p, content, mode) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + L.Push(lua.LTrue) + return 1 +} + +func mkdir(L *lua.LState) int { + dir := L.CheckString(1) + var mode os.FileMode = 0777 + + top := L.GetTop() + if top >= 2 { + m, err := oct2decimal(L.CheckInt(2)) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + mode = os.FileMode(m) + } + + recursive := false + if top >= 3 { + recursive = L.ToBool(3) + } + + var err error + if recursive { + err = os.MkdirAll(dir, mode) + } else { + err = os.Mkdir(dir, mode) + } + + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + L.Push(lua.LTrue) + + return 1 +} + +func remove(L *lua.LState) int { + p := L.CheckString(1) + + recursive := false + if L.GetTop() >= 2 { + recursive = L.ToBool(2) + } + + var err error + if recursive { + err = os.Remove(p) + } else { + err = os.RemoveAll(p) + } + + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(lua.LTrue) + + return 1 +} + +func symlink(L *lua.LState) int { + target := L.CheckString(1) + link := L.CheckString(2) + + err := os.Symlink(target, link) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + L.Push(lua.LTrue) + return 1 +} + +func dirname(L *lua.LState) int { + filep := L.CheckString(1) + dirna := filepath.Dir(filep) + L.Push(lua.LString(dirna)) + + return 1 +} + +func basename(L *lua.LState) int { + filep := L.CheckString(1) + dirna := filepath.Base(filep) + L.Push(lua.LString(dirna)) + + return 1 +} + +func fnRealpath(L *lua.LState) int { + filep := L.CheckString(1) + + real, err := realpath.Realpath(filep) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(lua.LString(real)) + + return 1 +} + +func getcwd(L *lua.LState) int { + dir, err := os.Getwd() + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(lua.LString(dir)) + + return 1 +} + +func chdir(L *lua.LState) int { + dir := L.CheckString(1) + + err := os.Chdir(dir) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(lua.LTrue) + return 1 +} + +func file(L *lua.LState) int { + // same: debug.getinfo(2,'S').source + var dbg *lua.Debug + var err error + var ok bool + + dbg, ok = L.GetStack(1) + if !ok { + L.Push(lua.LNil) + L.Push(lua.LString(fmt.Sprint(dbg))) + return 2 + } + _, err = L.GetInfo("S", dbg, lua.LNil) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + L.Push(lua.LString(dbg.Source)) + return 1 +} + +func dir(L *lua.LState) int { + // same: debug.getinfo(2,'S').source + var dbg *lua.Debug + var err error + var ok bool + + dbg, ok = L.GetStack(1) + if !ok { + L.Push(lua.LNil) + L.Push(lua.LString(fmt.Sprint(dbg))) + return 2 + } + _, err = L.GetInfo("S", dbg, lua.LNil) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + dirname := filepath.Dir(dbg.Source) + L.Push(lua.LString(dirname)) + + return 1 +} + +func glob(L *lua.LState) int { + ptn := L.CheckString(1) + fn := L.CheckFunction(2) + + files, err := filepath.Glob(ptn) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + for _, f := range files { + tb := L.NewTable() + tb.RawSetString("path", lua.LString(f)) + abspath, err := filepath.Abs(f) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + tb.RawSetString("realpath", lua.LString(abspath)) + + err = L.CallByParam(lua.P{ + Fn: fn, + NRet: 0, + Protect: true, + }, tb) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + } + + L.Push(lua.LTrue) + return 1 +} + +func isDir(path string) (ret bool) { + fi, err := os.Stat(path) + if err != nil { + return false + } + + return fi.IsDir() +} + +func oct2decimal(oct int) (uint64, error) { + return strconv.ParseUint(fmt.Sprintf("%d", oct), 8, 32) +} diff --git a/vendor/github.com/kohkimakimoto/gluafs/fs_test.go b/vendor/github.com/kohkimakimoto/gluafs/fs_test.go new file mode 100644 index 0000000..a292db9 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluafs/fs_test.go @@ -0,0 +1,148 @@ +package gluafs + +import ( + "github.com/yuin/gopher-lua" + "io/ioutil" + "os" + "testing" +) + +func TestExists(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("fs", Loader) + if err := L.DoString(` +local fs = require("fs") +assert(fs.exists(".") == true) + + `); err != nil { + t.Error(err) + } +} + +var tmpFileContent = "aaaaaaaabbbbbbbb" + +func TestRead(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Errorf("should not raise error: %v", err) + } + if err = ioutil.WriteFile(tmpFile.Name(), []byte(tmpFileContent), 0644); err != nil { + t.Errorf("should not raise error: %v", err) + } + defer func() { + tmpFile.Close() + os.Remove(tmpFile.Name()) + }() + + L := lua.NewState() + defer L.Close() + + L.PreloadModule("fs", Loader) + if err := L.DoString(` +local fs = require("fs") +local content = fs.read("` + tmpFile.Name() + `") + +assert(content == "aaaaaaaabbbbbbbb") + +local content2, err = fs.read("` + tmpFile.Name() + `.hoge") +assert(content2 == nil) + + + `); err != nil { + t.Error(err) + } +} + +func TestWrite(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "") + if err != nil { + t.Errorf("should not raise error: %v", err) + } + defer func() { + os.RemoveAll(tmpDir) + }() + + L := lua.NewState() + defer L.Close() + + L.PreloadModule("fs", Loader) + if err := L.DoString(` +local fs = require("fs") +local ret, err = fs.write("` + tmpDir + `/hoge", "aaaaaaaabbbbbbbb", 755) +local content = fs.read("` + tmpDir + `/hoge") + +assert(content == "aaaaaaaabbbbbbbb") + +ret, err = fs.write("` + tmpDir + `/hoge/aaaa", "aaaaaaaabbbbbbbb") +assert(ret == nil) + + `); err != nil { + t.Error(err) + } +} + +func TestMkdir(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "") + if err != nil { + t.Errorf("should not raise error: %v", err) + } + defer func() { + os.RemoveAll(tmpDir) + }() + + L := lua.NewState() + defer L.Close() + + L.PreloadModule("fs", Loader) + if err := L.DoString(` +local fs = require("fs") +local ret, err = fs.mkdir("` + tmpDir + `/hoge") +assert(ret == true) + +local ret, err = fs.mkdir("` + tmpDir + `/hoge/aaa/bbb", 0777, true) +assert(ret == true) + +local ret, err = fs.mkdir("` + tmpDir + `/hoge/bbb/eeee", 0777) +assert(ret == nil) +-- print(err) + + `); err != nil { + t.Error(err) + } +} + +func TestGlob(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "") + if err != nil { + t.Errorf("should not raise error: %v", err) + } + defer func() { + os.RemoveAll(tmpDir) + }() + + L := lua.NewState() + defer L.Close() + + L.PreloadModule("fs", Loader) + if err := L.DoString(` +local fs = require("fs") +fs.mkdir("` + tmpDir + `/dir01") +fs.mkdir("` + tmpDir + `/dir02") +fs.mkdir("` + tmpDir + `/dir03") +fs.write("` + tmpDir + `/file01", "aaaaaaa") +fs.write("` + tmpDir + `/file02", "bbbbbbb") + +local ret, err = fs.glob("` + tmpDir + `/*", function(file) + print(file.path) + print(file.realpath) +end) + +assert(ret == true) +assert(err == nil) + + `); err != nil { + t.Error(err) + } +} diff --git a/vendor/github.com/kohkimakimoto/gluaquestion/.gitignore b/vendor/github.com/kohkimakimoto/gluaquestion/.gitignore new file mode 100644 index 0000000..39d2623 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaquestion/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +Thumbs.db +.tags* +._* +*.iml +.vagrant diff --git a/vendor/github.com/kohkimakimoto/gluaquestion/LICENSE b/vendor/github.com/kohkimakimoto/gluaquestion/LICENSE new file mode 100644 index 0000000..7a3ff6c --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaquestion/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Kohki Makimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kohkimakimoto/gluaquestion/README.md b/vendor/github.com/kohkimakimoto/gluaquestion/README.md new file mode 100644 index 0000000..ef52d34 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaquestion/README.md @@ -0,0 +1,44 @@ +# gluaquestion + +A library of [gopher-lua](https://github.com/yuin/gopher-lua) to prompt the user for input. + +## Installation + +``` +go get github.com/kohkimakimoto/gluaquestion +``` + +## API + +* `question.ask(text)` +* `question.secret(text)` + +## Usage + +```go +package main + +import ( + "github.com/yuin/gopher-lua" + "github.com/kohkimakimoto/gluaquestion" +) + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("question", gluaquestion.Loader) + if err := L.DoString(` +local question = require("question") + +local name = question.ask("What's your name: ") +print("hello " .. name) + +-- What's your name: kohki +-- hello kohki + +`); err != nil { + panic(err) + } +} +``` diff --git a/vendor/github.com/kohkimakimoto/gluaquestion/question.go b/vendor/github.com/kohkimakimoto/gluaquestion/question.go new file mode 100644 index 0000000..14f4987 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluaquestion/question.go @@ -0,0 +1,45 @@ +package gluaquestion + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/howeyc/gopass" + "github.com/yuin/gopher-lua" +) + +// Loader is glua module loader. +func Loader(L *lua.LState) int { + tb := L.NewTable() + L.SetFuncs(tb, map[string]lua.LGFunction{ + "ask": ask, + "secret": secret, + }) + L.Push(tb) + + return 1 +} + +func ask(L *lua.LState) int { + msg := L.CheckString(1) + + fmt.Printf(msg) + reader := bufio.NewReader(os.Stdin) + str, _ := reader.ReadString('\n') + str = strings.TrimRight(str, "\r\n") + L.Push(lua.LString(str)) + + return 1 +} + +func secret(L *lua.LState) int { + msg := L.CheckString(1) + + fmt.Printf(msg) + pass, _ := gopass.GetPasswd() + L.Push(lua.LString(pass)) + + return 1 +} diff --git a/vendor/github.com/kohkimakimoto/gluassh/.gitignore b/vendor/github.com/kohkimakimoto/gluassh/.gitignore new file mode 100644 index 0000000..39d2623 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +Thumbs.db +.tags* +._* +*.iml +.vagrant diff --git a/vendor/github.com/kohkimakimoto/gluassh/LICENSE b/vendor/github.com/kohkimakimoto/gluassh/LICENSE new file mode 100644 index 0000000..e4c3a8e --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Kohki Makimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/kohkimakimoto/gluassh/Makefile b/vendor/github.com/kohkimakimoto/gluassh/Makefile new file mode 100644 index 0000000..2b1610e --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/Makefile @@ -0,0 +1,10 @@ +.PHONY: build dep install test + +test_setup: + cd _tests && vagrant up && cd - + +test: + go test ./... -cover + +testverbose: + go test ./... -v -cover diff --git a/vendor/github.com/kohkimakimoto/gluassh/README.md b/vendor/github.com/kohkimakimoto/gluassh/README.md new file mode 100644 index 0000000..f61257f --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/README.md @@ -0,0 +1,4 @@ +# gluassh + +SSH library for [gopher-lua](https://github.com/yuin/gopher-lua) to run commands on remote severs. + diff --git a/vendor/github.com/kohkimakimoto/gluassh/communicator.go b/vendor/github.com/kohkimakimoto/gluassh/communicator.go new file mode 100644 index 0000000..22a66e4 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/communicator.go @@ -0,0 +1,380 @@ +package gluassh + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" + "io" + "io/ioutil" + "net" + "os" + "github.com/yuin/gopher-lua" +) + +// +// Refers to +// +// https://github.com/markpeek/packer/blob/53446bf713ed2866b451aa1d00813a90308ee9e9/communicator/ssh/communicator.go +// https://github.com/rapidloop/rtop +// https://github.com/rapidloop/rtop/blob/ba5b35e964135d50e0babedf0bd69b2fcb5dbcb4/src/sshhelper.go#L185 +// https://gist.github.com/zxvdr/8229523 +// + +// SSH communicator +type Communicator struct { + Config *Config + Agent agent.Agent + Auths []ssh.AuthMethod + ClientConfig *ssh.ClientConfig + UpstreamConfig *Config + client *ssh.Client + clientConns []interface { + Close() error + } + + OriginalConfig *Config +} + +func NewComm(config *Config) (*Communicator, error) { + // Has a proxy? + originalConfig := config + + var upstreamConfig *Config = nil + proxy := config.PopProxyConfig() + if proxy != nil { + upstreamConfig = config + config = proxy + } + + comm := &Communicator{ + Config: config, + UpstreamConfig: upstreamConfig, + clientConns: make([]interface { + Close() error + }, 0), + OriginalConfig: originalConfig, + } + + // construct auths + auths := []ssh.AuthMethod{} + + if config.UseAgent { + // use ssh-agent + if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 { + agconn, err := net.Dial("unix", sock) + if err != nil { + return nil, err + } + ag := agent.NewClient(agconn) + auth := ssh.PublicKeysCallback(ag.Signers) + auths = append(auths, auth) + + comm.Agent = ag + + } else { + return nil, errors.New("Could not get a socket from SSH_AUTH_SOCK") + } + } else { + // use key file + pemBytes, err := ioutil.ReadFile(config.KeyOrDefault()) + if err != nil { + return nil, err + } + + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("no key found in " + config.KeyOrDefault()) + } + + // handle plain and encrypted keyfile + if x509.IsEncryptedPEMBlock(block) { + if config.KeyPassphrase == "" { + return nil, errors.New("You have to set a key_passphrase for the encryped keyfile '" + config.KeyOrDefault() + "'") + } + + block.Bytes, err = x509.DecryptPEMBlock(block, []byte(config.KeyPassphrase)) + if err != nil { + return nil, err + } + key, err := parsePemBlock(block) + if err != nil { + return nil, err + } + signer, err := ssh.NewSignerFromKey(key) + if err != nil { + return nil, err + } + auths = append(auths, ssh.PublicKeys(signer)) + + } else { + signer, err := ssh.ParsePrivateKey(pemBytes) + if err != nil { + return nil, err + } + auths = append(auths, ssh.PublicKeys(signer)) + } + } + + if config.Password != "" { + // Use password + auths = append(auths, ssh.Password(config.Password)) + } + + comm.Auths = auths + + // ssh client config + comm.ClientConfig = &ssh.ClientConfig{ + User: config.UserOrDefault(), + Auth: auths, + } + + return comm, nil +} + +// Exec runs command on remote server. +// It is a low-layer method to be used Run, Script and others method. +func (c *Communicator) Exec(cmd string, opt *Option, L *lua.LState) (*Result, error) { + // get a client + client, err := c.Client() + if err != nil { + return nil, err + } + + // get a session + sess, err := client.NewSession() + if err != nil { + return nil, err + } + defer sess.Close() + + // RequestAgentForwarding + if c.Config.ForwardAgent && c.Agent != nil { + agent.ForwardToAgent(client, c.Agent) + if err = agent.RequestAgentForwarding(sess); err != nil { + return nil, err + } + } + + // Request a PTY + if c.Config.Pty { + // refers to https://github.com/markpeek/packer/blob/53446bf713ed2866b451aa1d00813a90308ee9e9/communicator/ssh/communicator.go + termModes := ssh.TerminalModes{ + ssh.ECHO: 0, // do not echo + ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud + ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud + } + err = sess.RequestPty("xterm", 80, 40, termModes) + if err != nil { + return nil, err + } + } + + // Compose sudo command line. + if opt.Sudo { + if opt.User != "" { + cmd = "sudo -Sku " + opt.User + " /bin/sh -l -c '" + cmd + "'" + } else { + cmd = "sudo -Sk /bin/sh -l -c '" + cmd + "'" + } + } + + // apply io + var outWriter io.Writer + var errWriter io.Writer + var inReader io.Reader + + // buffer + var outBuffer = new(bytes.Buffer) + var errBuffer = new(bytes.Buffer) + var inBuffer = new(bytes.Buffer) + + + // append stdio to buffer + if opt.UseStdout { + if opt.OutputFunc != nil { + outWriter = io.MultiWriter(os.Stdout, outBuffer, NewLFuncWriter(1, opt.OutputFunc, L)) + } else { + outWriter = io.MultiWriter(os.Stdout, outBuffer) + } + } else { + outWriter = io.MultiWriter(outBuffer) + } + + if opt.UseStderr { + if opt.OutputFunc != nil { + errWriter = io.MultiWriter(os.Stderr, errBuffer, NewLFuncWriter(2, opt.OutputFunc, L)) + } else { + errWriter = io.MultiWriter(os.Stderr, errBuffer) + } + } else { + errWriter = io.MultiWriter(errBuffer) + } + + inReader = io.MultiReader(inBuffer, os.Stdin) + + sess.Stdin = inReader + sess.Stdout = outWriter + sess.Stderr = errWriter + + // write sudo password + if opt.Password != "" { + // If it try to use sudo password, write password to input buffer + fmt.Fprintln(inBuffer, opt.Password) + } + + err = sess.Run(cmd) + if err != nil { + if exitErr, ok := err.(*ssh.ExitError); ok { + return NewResult(outBuffer, errBuffer, exitErr.ExitStatus()), err + } else { + return NewResult(outBuffer, errBuffer, 1), err + } + } + + return NewResult(outBuffer, errBuffer, 0), nil +} + +func (c *Communicator) Run(cmd string, opt *Option, L *lua.LState) (*Result, error) { + return c.Exec(cmd, opt, L) +} + +func (c *Communicator) Get(remote string, local string) error { + client, err := c.Client() + if err != nil { + return err + } + + sc, err := sftp.NewClient(client) + if err != nil { + return err + } + defer sc.Close() + + fi, err := sc.Open(remote) + if err != nil { + return err + } + defer fi.Close() + + fo, err := os.Create(local) + if err != nil { + return err + } + defer fo.Close() + + _, err = io.Copy(fo, fi) + if err != nil { + return err + } + + return nil +} + +func (c *Communicator) Put(local string, remote string) error { + client, err := c.Client() + if err != nil { + return err + } + + sc, err := sftp.NewClient(client) + if err != nil { + return err + } + defer sc.Close() + + b, err := ioutil.ReadFile(local) + if err != nil { + return err + } + + fo, err := sc.Create(remote) + if err != nil { + return err + } + defer fo.Close() + + _, err = fo.Write(b) + if err != nil { + return err + } + + return nil +} + +func (c *Communicator) Client() (*ssh.Client, error) { + if c.client != nil { + return c.client, nil + } + + // create ssh client. + client, err := ssh.Dial("tcp", c.Config.HostOrDefault()+":"+c.Config.PortOrDefault(), c.ClientConfig) + if err != nil { + return nil, err + } + c.clientConns = append(c.clientConns, client) + + // If it has a upstream server? + for c.UpstreamConfig != nil { + // It is next server config to connect. + var config *Config = nil + + // Does the upstream server need proxy to connet? + proxy := c.UpstreamConfig.PopProxyConfig() + if proxy != nil { + config = proxy + } else { + config = c.UpstreamConfig + c.UpstreamConfig = nil + } + + // dial to ssh proxy + connection, err := client.Dial("tcp", config.HostOrDefault()+":"+config.PortOrDefault()) + if err != nil { + return nil, err + } + c.clientConns = append(c.clientConns, connection) + + conn, chans, reqs, err := ssh.NewClientConn(connection, config.HostOrDefault()+":"+config.PortOrDefault(), c.ClientConfig) + if err != nil { + return nil, err + } + client = ssh.NewClient(conn, chans, reqs) + c.clientConns = append(c.clientConns, client) + + if err != nil { + return nil, err + } + } + + c.client = client + return c.client, nil +} + +func (c *Communicator) Close() error { + for _, conn := range c.clientConns { + err := conn.Close() + if err != nil { + return err + } + } + return nil +} + +// ref golang.org/x/crypto/ssh/keys.go#ParseRawPrivateKey. +func parsePemBlock(block *pem.Block) (interface{}, error) { + switch block.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(block.Bytes) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(block.Bytes) + case "DSA PRIVATE KEY": + return ssh.ParseDSAPrivateKey(block.Bytes) + default: + return nil, fmt.Errorf("rtop: unsupported key type %q", block.Type) + } +} diff --git a/vendor/github.com/kohkimakimoto/gluassh/config.go b/vendor/github.com/kohkimakimoto/gluassh/config.go new file mode 100644 index 0000000..a9cd426 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/config.go @@ -0,0 +1,106 @@ +package gluassh + +import ( + "encoding/json" + "os" + "os/user" + "path/filepath" +) + +// SSH connection config. +type Config struct { + Host string `json:"host"` + Port string `json:"port"` + User string `json:"user"` + Key string `json:"key"` + KeyPassphrase string `json:"key_passphrase"` + UseAgent bool `json:"use_agent"` + ForwardAgent bool `json:"forward_agent"` + Pty bool `json:"pty"` + Password string `json:"password"` + Proxy *Config `json:"proxy"` +} + +// NewConfig creates new config instance. +func NewConfig() *Config { + c := &Config{} + + return c +} + +// Pop the last proxy server +func (c *Config) PopProxyConfig() *Config { + proxy := c.Proxy + if proxy == nil { + return nil + } + + if proxy.Proxy != nil { + return proxy.PopProxyConfig() + } + + c.Proxy = nil + + return proxy +} + +func (c *Config) UpdateWithJSON(data []byte) error { + err := json.Unmarshal(data, c) + if err != nil { + return err + } + + return nil +} + +func (c *Config) ConfigsChain() []*Config { + var configs []*Config + + configs = append(configs, c) + + if c.Proxy != nil { + proxyConfigs := c.Proxy.ConfigsChain() + configs = append(proxyConfigs, configs...) + } + + return configs +} + +func (c *Config) HostOrDefault() string { + if c.Host == "" { + return "localhost" + } else { + return c.Host + } +} + +func (c *Config) PortOrDefault() string { + if c.Port == "" { + return "22" + } else { + return c.Port + } +} + +func (c *Config) KeyOrDefault() string { + if c.Key == "" { + home := os.Getenv("HOME") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return filepath.Join(home, ".ssh/id_rsa") + } + + return c.Key +} + +func (c *Config) UserOrDefault() string { + if c.User == "" { + u, err := user.Current() + if err == nil { + return u.Username + } + } + + return c.User +} diff --git a/vendor/github.com/kohkimakimoto/gluassh/gluassh.go b/vendor/github.com/kohkimakimoto/gluassh/gluassh.go new file mode 100644 index 0000000..35765f6 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/gluassh.go @@ -0,0 +1,232 @@ +package gluassh + +import ( + "github.com/yuin/gluamapper" + "github.com/yuin/gopher-lua" + "errors" +) + +const LConnectionClass = "SSHConnectionClass*" + +type Connection struct { + +} + +// Loader is glua module loader. +func Loader(L *lua.LState) int { + // register ssh connection class + registerConnectionClass(L) + + // load module + tb := L.NewTable() + L.SetFuncs(tb, map[string]lua.LGFunction{ + "run": run, + "get": get, + "put": put, + }) + + // set up meta table + mt := L.NewTable() + L.SetField(mt, "__call",L.NewFunction(call)) + L.SetMetatable(tb, mt) + + L.Push(tb) + + return 1 +} + +func registerConnectionClass(L *lua.LState) { + mt := L.NewTypeMetatable(LConnectionClass) + // methods + L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{ +// "run": connRun, +// "get": connGet, +// "put": connPut, + })) +} + +func call(L *lua.LState) int { + // TODO: implements + if L.GetTop() != 2 { + L.RaiseError("calling ssh requires 1 argment that is a server config.") + } + +// tb := L.Get(1) + return 0 +} + +func newConnection(L *lua.LState) int { + connection := &Connection{} + ud := L.NewUserData() + ud.Value = connection + L.SetMetatable(ud, L.GetTypeMetatable(LConnectionClass)) + L.Push(ud) + return 1 +} + +// This is a Lua module function. +// run execute command on a remote host via SSH. +func run(L *lua.LState) int { + // communicator + comm, err := commFromLTable(L.CheckTable(1)) + if err != nil { + panic(err) + } + defer comm.Close() + + // option and command + option := NewOption() + cmd := "" + + if L.GetTop() == 4 { + if err := gluamapper.Map(L.CheckTable(2), option); err != nil { + panic(err) + } + cmds, err := toStrings(L.Get(3)) + if err != nil { + panic(err) + } + cmd = concatCommandLines(cmds) + + fn := L.CheckFunction(4) + option.OutputFunc = fn + + } else if L.GetTop() == 3 { + if err := gluamapper.Map(L.CheckTable(2), option); err != nil { + panic(err) + } + cmds, err := toStrings(L.Get(3)) + if err != nil { + panic(err) + } + cmd = concatCommandLines(cmds) + } else if L.GetTop() == 2 { + cmds, err := toStrings(L.Get(2)) + if err != nil { + panic(err) + } + cmd = concatCommandLines(cmds) + } else { + L.RaiseError("run method requires 2 arugments at least.") + } + + // run + ret, err := comm.Run(cmd, option, L) + if err != nil { + L.RaiseError("ssh error: %s", err) + } + + tb := updateLTableByRunResult(L.NewTable(), ret) + L.Push(tb) + + return 1 +} + +// This is a Lua module function. +func get(L *lua.LState) int { + // communicator + comm, err := commFromLTable(L.CheckTable(1)) + if err != nil { + panic(err) + } + defer comm.Close() + + remote := L.ToString(2) + local := L.ToString(3) + + err = comm.Get(remote, local) + if err != nil { + panic(err) + } + + return 0 +} + +// This is a Lua module function. +func put(L *lua.LState) int { + // communicator + comm, err := commFromLTable(L.CheckTable(1)) + if err != nil { + panic(err) + } + defer comm.Close() + + local := L.ToString(2) + remote := L.ToString(3) + + err = comm.Put(local, remote) + if err != nil { + panic(err) + } + + return 0 +} + +func commFromLTable(table *lua.LTable) (*Communicator, error) { + // config + cfg := NewConfig() + // update config by passed table. + if err := gluamapper.Map(table, cfg); err != nil { + return nil, err + } + + // communicator + comm, err := NewComm(cfg) + if err != nil { + return nil, err + } + + return comm, nil +} + +func toStrings(v lua.LValue) ([]string, error) { + var ret []string + + if lv, ok := v.(*lua.LTable); ok { + lv.ForEach(func(tk lua.LValue, tv lua.LValue) { + if ls, ok := tv.(lua.LString); ok { + ret = append(ret, string(ls)) + } + }) + } else { + if ls, ok := v.(lua.LString); ok { + ret = append(ret, string(ls)) + } else { + return ret, errors.New("Could not transfer lua value to string slice") + } + } + + return ret, nil +} + +func concatCommandLines(cmdlines []string) string { + cmdline := "" + + for i, v := range cmdlines { + if i == 0 { + cmdline = v + } else { + cmdline = cmdline + " && " + v + } + } + + return cmdline +} + +func updateLTableByRunResult(tb *lua.LTable, ret *Result) *lua.LTable { + if ret != nil { + tb.RawSetString("out", lua.LString(ret.Out.String())) + tb.RawSetString("err", lua.LString(ret.Err.String())) + tb.RawSetString("status", lua.LNumber(ret.Status)) + tb.RawSetString("successful", lua.LBool(ret.Successful())) + tb.RawSetString("failed", lua.LBool(ret.Failed())) + } else { + tb.RawSetString("out", lua.LNil) + tb.RawSetString("err", lua.LNil) + tb.RawSetString("status", lua.LNil) + tb.RawSetString("successful", lua.LBool(false)) + tb.RawSetString("failed", lua.LBool(true)) + } + + return tb +} diff --git a/vendor/github.com/kohkimakimoto/gluassh/gluassh_test.go b/vendor/github.com/kohkimakimoto/gluassh/gluassh_test.go new file mode 100644 index 0000000..69152e2 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/gluassh_test.go @@ -0,0 +1,120 @@ +package gluassh + +import( + "testing" + "github.com/yuin/gopher-lua" + "os" + "path/filepath" +) + +var keynopass string +var keypass string + +func TestRunWithKeynopass(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("ssh", Loader) + + if err := L.DoString(` +local ssh = require "ssh" + +conn_user_keynopass = { + host = "192.168.56.81", + port = "22", + user = "user_keynopass", + key = "` + keynopass + `", +} + +local ret = ssh.run(conn_user_keynopass, "hostname") + +assert(ret.out == "gluassh-test-server\n") + `); err != nil { + t.Error(err) + } +} + +func TestRunWithKeynopassSudo(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("ssh", Loader) + + if err := L.DoString(` +local ssh = require "ssh" + +conn_user_keynopass = { + host = "192.168.56.81", + port = "22", + user = "user_keynopass", + key = "` + keynopass + `", +} + +ret = ssh.run(conn_user_keynopass, {sudo = true}, "whoami") +assert(ret.out == "root\n") + `); err != nil { + t.Error(err) + } +} + +func TestRunWithKeypass(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("ssh", Loader) + + if err := L.DoString(` +local ssh = require "ssh" + +conn_user_keypass = { + host = "192.168.56.81", + port = "22", + user = "user_keypass", + key = "` + keypass + `", + key_passphrase = "hogehoge", +} + +local ret = ssh.run(conn_user_keypass, "hostname") + +assert(ret.out == "gluassh-test-server\n") + `); err != nil { + t.Error(err) + } +} + +func TestRunWithKeynopassFunc(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("ssh", Loader) + + if err := L.DoString(` +local ssh = require "ssh" + +conn_user_keynopass = { + host = "192.168.56.81", + port = "22", + user = "user_keynopass", + key = "` + keynopass + `", +} + +local ret = ssh.run(conn_user_keynopass, {}, "hostname", function(stdout, stderr) + + print(stdout) + +end) + + `); err != nil { + t.Error(err) + } +} + +func init() { + wd, err := os.Getwd() + if err != nil { + panic(err) + } + + keynopass = filepath.Join(wd, "_tests", "keys", "id_rsa.nopass") + keypass = filepath.Join(wd, "_tests", "keys", "id_rsa.pass") +} \ No newline at end of file diff --git a/vendor/github.com/kohkimakimoto/gluassh/option.go b/vendor/github.com/kohkimakimoto/gluassh/option.go new file mode 100644 index 0000000..a84e36d --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/option.go @@ -0,0 +1,70 @@ +package gluassh + +import ( + "github.com/yuin/gopher-lua" +) + +// SSH run option +type Option struct { + // Use sudo + Sudo bool + // sudo user + User string + // sudo password + Password string + // Use stdout + UseStdout bool + // Use stderr + UseStderr bool + // function receives output. + OutputFunc *lua.LFunction +} + +func NewOption() *Option { + opt := &Option{ + Sudo: false, + UseStdout: true, + UseStderr: true, + } + + return opt +} + +type LFuncWriter struct { + outType int + fn *lua.LFunction + L *lua.LState +} + +func NewLFuncWriter(outType int, fn *lua.LFunction, L *lua.LState) *LFuncWriter { + return &LFuncWriter{ + L: L, + outType: outType, + fn: fn, + } +} + +func (w *LFuncWriter) Write(data []byte) (int, error) { + if w.outType == 1 { + err := w.L.CallByParam(lua.P{ + Fn: w.fn, + NRet: 0, + Protect: true, + }, lua.LString(string(data))) + if err != nil { + return len(data), err + } + + } else { + err := w.L.CallByParam(lua.P{ + Fn: w.fn, + NRet: 0, + Protect: true, + }, lua.LString(string(data))) + if err != nil { + return len(data), err + } + } + + return len(data), nil +} diff --git a/vendor/github.com/kohkimakimoto/gluassh/result.go b/vendor/github.com/kohkimakimoto/gluassh/result.go new file mode 100644 index 0000000..0392cb3 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluassh/result.go @@ -0,0 +1,31 @@ +package gluassh + +import ( + "bytes" +) + +type Result struct { + Out *bytes.Buffer + Err *bytes.Buffer + Status int +} + +func NewResult(outbuf *bytes.Buffer, errbuf *bytes.Buffer, status int) *Result { + return &Result{ + Out: outbuf, + Err: errbuf, + Status: status, + } +} + +func (r *Result) Successful() bool { + if r.Status == 0 { + return true + } else { + return false + } +} + +func (r *Result) Failed() bool { + return !r.Successful() +} diff --git a/vendor/github.com/kohkimakimoto/gluatemplate/.gitignore b/vendor/github.com/kohkimakimoto/gluatemplate/.gitignore new file mode 100644 index 0000000..9ba9fea --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluatemplate/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +Thumbs.db +/.tags* +._* +*.iml +/_vendor diff --git a/vendor/github.com/kohkimakimoto/gluatemplate/LICENSE b/vendor/github.com/kohkimakimoto/gluatemplate/LICENSE new file mode 100644 index 0000000..a9ed130 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluatemplate/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Kohki Makimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kohkimakimoto/gluatemplate/README.md b/vendor/github.com/kohkimakimoto/gluatemplate/README.md new file mode 100644 index 0000000..d33b88f --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluatemplate/README.md @@ -0,0 +1,67 @@ +# gluatemplate + +Text template for [gopher-lua](https://github.com/yuin/gopher-lua) + +This product is based on [Go Text Template](https://golang.org/pkg/text/template/). +If you are not familiar with the syntax, please read the documentation. + +## Installation + +``` +go get github.com/kohkimakimoto/gluatemplate +``` + +## API + +### `template.dostring(text, table)` + +Returns string generated by text template with the table values. If this function fails, it returns `nil`, plus a string describing the error. + +### `template.dofile(file, table)` + +Returns string generated by file template with the table values. If this function fails, it returns `nil`, plus a string describing the error. + +## Usage + +```go +package main + +import ( + "github.com/yuin/gopher-lua" + "github.com/kohkimakimoto/gluatemplate" +) + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("template", gluatemplate.Loader) + if err := L.DoString(` +local template = require("template") + +local text = template.dostring([[ +This is a text template library. +Created by {{.first_name}} {{.last_name}} +]], { + first_name = "kohki", + last_name = "makimoto", +}) + +print(text) +-- +-- This is a text template library. +-- Created by kohki makimoto +-- +`); err != nil { + panic(err) + } +} +``` + +## Author + +Kohki Makimoto + +## License + +MIT license. diff --git a/vendor/github.com/kohkimakimoto/gluatemplate/gluatemplate.go b/vendor/github.com/kohkimakimoto/gluatemplate/gluatemplate.go new file mode 100644 index 0000000..da900c5 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluatemplate/gluatemplate.go @@ -0,0 +1,106 @@ +package gluatemplate + +import ( + "bytes" + "fmt" + "github.com/yuin/gopher-lua" + "text/template" +) + +func Loader(L *lua.LState) int { + tb := L.NewTable() + L.SetFuncs(tb, map[string]lua.LGFunction{ + "dostring": doString, + "dofile": doFile, + }) + L.Push(tb) + + return 1 +} + +// render +func doString(L *lua.LState) int { + tmplcontent := L.CheckString(1) + var dict interface{} + + tmpl, err := template.New("T").Parse(tmplcontent) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + if L.GetTop() >= 2 { + dict = toGoValue(L.CheckTable(2)) + } + + var b bytes.Buffer + if err := tmpl.Execute(&b, dict); err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + s := b.String() + + L.Push(lua.LString(s)) + return 1 +} + +func doFile(L *lua.LState) int { + tmplfile := L.CheckString(1) + var dict interface{} + + tmpl, err := template.ParseFiles(tmplfile) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + if L.GetTop() >= 2 { + dict = toGoValue(L.CheckTable(2)) + } + + var b bytes.Buffer + if err := tmpl.Execute(&b, dict); err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + s := b.String() + + L.Push(lua.LString(s)) + return 1 +} + +// This code refers to https://github.com/yuin/gluamapper/blob/master/gluamapper.go +func toGoValue(lv lua.LValue) interface{} { + switch v := lv.(type) { + case *lua.LNilType: + return nil + case lua.LBool: + return bool(v) + case lua.LString: + return string(v) + case lua.LNumber: + return float64(v) + case *lua.LTable: + maxn := v.MaxN() + if maxn == 0 { // table + ret := make(map[interface{}]interface{}) + v.ForEach(func(key, value lua.LValue) { + keystr := fmt.Sprint(toGoValue(key)) + ret[keystr] = toGoValue(value) + }) + return ret + } else { // array + ret := make([]interface{}, 0, maxn) + for i := 1; i <= maxn; i++ { + ret = append(ret, toGoValue(v.RawGetInt(i))) + } + return ret + } + default: + return v + } +} diff --git a/vendor/github.com/kohkimakimoto/gluatemplate/gluatemplate_test.go b/vendor/github.com/kohkimakimoto/gluatemplate/gluatemplate_test.go new file mode 100644 index 0000000..c195642 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluatemplate/gluatemplate_test.go @@ -0,0 +1,145 @@ +package gluatemplate + +import ( + "github.com/yuin/gopher-lua" + "io/ioutil" + "os" + "testing" +) + +func TestDoString(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("template", Loader) + if err := L.DoString(` +local template = require("template") + +local output = template.dostring([[ +This is a text template library. +Created by {{.first_name}} {{.last_name}} +]], { + first_name = "kohki", + last_name = "makimoto", +}) + +-- print(output) +-- +-- This is a text template library. +-- Created by kohki makimoto +-- + +output = template.dostring("{{.a}}{{.b}}{{.c}}", { + a = "aaa", + b = "bbb", + c = "ccc", + +}) +assert(output == "aaabbbccc") + +output = template.dostring("{{len .b}}{{range $i, $v := .b}}{{$v}}{{end}}", { + a = "aaa", + b = { + "hogehoge", + "foobarfoobar", + }, + +}) + +assert(output == "2hogehogefoobarfoobar") + + `); err != nil { + t.Error(err) + } +} + +func TestDoStringError(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("template", Loader) + if err := L.DoString(` +local template = require("template") + +output, err = template.dostring("{{end}}{{.aaa}}{{.b}}{{.c}}", { + a = "aaa", + b = "bbb", + c = "ccc", + +}) +assert(output == nil) +print(err) + + `); err != nil { + t.Error(err) + } +} + +var sampleFile = "{{.a}}{{.b}}{{.c}}" + +func TestDoFile(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Errorf("should not raise error: %v", err) + } + if err = ioutil.WriteFile(tmpFile.Name(), []byte(sampleFile), 0644); err != nil { + t.Errorf("should not raise error: %v", err) + } + defer func() { + tmpFile.Close() + os.Remove(tmpFile.Name()) + }() + + L := lua.NewState() + defer L.Close() + + L.PreloadModule("template", Loader) + if err := L.DoString(` +local template = require("template") + +local output = template.dofile("` + tmpFile.Name() + `", { + a = "aaa", + b = "bbb", + c = "ccc", +}) + +assert(output == "aaabbbccc") + + + `); err != nil { + t.Error(err) + } +} + +func TestDoFileError(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Errorf("should not raise error: %v", err) + } + if err = ioutil.WriteFile(tmpFile.Name(), []byte(sampleFile), 0644); err != nil { + t.Errorf("should not raise error: %v", err) + } + defer func() { + tmpFile.Close() + os.Remove(tmpFile.Name()) + }() + + L := lua.NewState() + defer L.Close() + + L.PreloadModule("template", Loader) + if err := L.DoString(` +local template = require("template") + +local output = template.dofile("` + tmpFile.Name() + `.hoge", { + a = "aaa", + b = "bbb", + c = "ccc", +}) + +assert(output == nil) + + `); err != nil { + t.Error(err) + } +} diff --git a/vendor/github.com/kohkimakimoto/gluayaml/.gitignore b/vendor/github.com/kohkimakimoto/gluayaml/.gitignore new file mode 100644 index 0000000..94b912c --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluayaml/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +Thumbs.db +/.tags* +._* +*.iml +!.gitkeep \ No newline at end of file diff --git a/vendor/github.com/kohkimakimoto/gluayaml/.travis.yml b/vendor/github.com/kohkimakimoto/gluayaml/.travis.yml new file mode 100644 index 0000000..525b1bf --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluayaml/.travis.yml @@ -0,0 +1,6 @@ +language: go + +go: + - 1.4 + - tip + diff --git a/vendor/github.com/kohkimakimoto/gluayaml/README.md b/vendor/github.com/kohkimakimoto/gluayaml/README.md new file mode 100644 index 0000000..c52fd8c --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluayaml/README.md @@ -0,0 +1,58 @@ +# gluayaml + +Yaml parser for [gopher-lua](https://github.com/yuin/gopher-lua) + +## Installation + +``` +go get github.com/kohkimakimoto/gluayaml +``` + +## API + +### `yaml.parse(string)` + +Parses yaml formatted string and returns a table. If this function fails, it returns `nil`, plus a string describing the error. + +## Usage + +```go +package main + +import ( + "github.com/yuin/gopher-lua" + "github.com/kohkimakimoto/gluayaml" +) + + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("yaml", gluayaml.Loader) + if err := L.DoString(` +local yaml = require("yaml") +local str = [==[ +key1: value1 +key2: + - value2 + - value3 +]==] + +local tb = yaml.parse(str) +print(tb.key1) -- value1 +print(tb.key2[1]) -- value2 +print(tb.key2[2]) -- value3 +`); err != nil { + panic(err) + } +} +``` + +## Author + +Kohki Makimoto + +## License + +MIT license. diff --git a/vendor/github.com/kohkimakimoto/gluayaml/gluatyaml_test.go b/vendor/github.com/kohkimakimoto/gluayaml/gluatyaml_test.go new file mode 100644 index 0000000..5da6a7c --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluayaml/gluatyaml_test.go @@ -0,0 +1,176 @@ +package gluayaml + +import ( + "github.com/yuin/gopher-lua" + "testing" +) + +func TestLoader(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("yaml", Loader) + if err := L.DoString(` +local yaml = require("yaml") + `); err != nil { + t.Error(err) + } +} + +func TestParse(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("yaml", Loader) + if err := L.DoString(` +local yaml = require("yaml") + +local str = [==[ +- aaa +- bbb +- ccc +]==] + +local tb, err = yaml.parse(str) +if tb == nil then + error("parse error" .. err) +end + +assert(tb[1] == "aaa") +assert(tb[2] == "bbb") +assert(tb[3] == "ccc") + + `); err != nil { + t.Error(err) + } +} + +func TestParse2(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("yaml", Loader) + if err := L.DoString(` +local yaml = require("yaml") + +local str = [==[ +aaa: bbb +bbb: ccc +]==] + +local tb, err = yaml.parse(str) +if tb == nil then + error("parse error" .. err) +end + +assert(tb.aaa == "bbb") +assert(tb.bbb == "ccc") + + `); err != nil { + t.Error(err) + } +} + +func TestParse3(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("yaml", Loader) + if err := L.DoString(` +local yaml = require("yaml") + +local str = [==[ +aaa: true +bbb: false +]==] + +local tb, err = yaml.parse(str) +if tb == nil then + error("parse error" .. err) +end + +assert(tb.aaa == true) +assert(tb.bbb == false) + + `); err != nil { + t.Error(err) + } +} + +func TestParse4(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("yaml", Loader) + if err := L.DoString(` +local yaml = require("yaml") + +local str = [==[ +aaa: 1234 +bbb: 1234.567 +ccc: [aa, bb, cc] +ddd: + - aa + - bb +eee: + aa : bb + bb : cc + +]==] + +local tb, err = yaml.parse(str) +if tb == nil then + error("parse error" .. err) +end + +assert(tb.aaa == 1234) +assert(tb.bbb == 1234.567) +assert(tb.ccc[1] == "aa") +assert(tb.ccc[2] == "bb") +assert(tb.ccc[3] == "cc") +assert(tb.ddd[1] == "aa") +assert(tb.ddd[2] == "bb") +assert(tb.eee.aa == "bb") +assert(tb.eee.bb == "cc") + + `); err != nil { + t.Error(err) + } +} + +func TestParseError(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("yaml", Loader) + if err := L.DoString(` +local yaml = require("yaml") + +local str = [==[ +- aaa +aaaaa +- bbb +- ccc +]==] + +local tb, err = yaml.parse(str) +assert(tb == nil) + + + `); err != nil { + t.Error(err) + } +} + +func TestDump(t *testing.T) { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("yaml", Loader) + if err := L.DoString(` +local yaml = require("yaml") + + `); err != nil { + t.Error(err) + } +} diff --git a/vendor/github.com/kohkimakimoto/gluayaml/gluayaml.go b/vendor/github.com/kohkimakimoto/gluayaml/gluayaml.go new file mode 100644 index 0000000..e46ecc0 --- /dev/null +++ b/vendor/github.com/kohkimakimoto/gluayaml/gluayaml.go @@ -0,0 +1,68 @@ +package gluayaml + +import ( + "github.com/yuin/gopher-lua" + "gopkg.in/yaml.v2" +) + +func Loader(L *lua.LState) int { + tb := L.NewTable() + L.SetFuncs(tb, map[string]lua.LGFunction{ + "parse": apiParse, + "dump": apiDump, + }) + L.Push(tb) + + return 1 +} + +// apiParse parses yaml formatted text to the table. +func apiParse(L *lua.LState) int { + str := L.CheckString(1) + + var value interface{} + err := yaml.Unmarshal([]byte(str), &value) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + L.Push(fromYAML(L, value)) + return 1 +} + +// apiDump dumps the yaml formatted text. +func apiDump(L *lua.LState) int { + L.RaiseError("unimplemented function") + return 0 +} + +func fromYAML(L *lua.LState, value interface{}) lua.LValue { + switch converted := value.(type) { + case bool: + return lua.LBool(converted) + case float64: + return lua.LNumber(converted) + case int: + return lua.LNumber(converted) + case string: + return lua.LString(converted) + case []interface{}: + arr := L.CreateTable(len(converted), 0) + for _, item := range converted { + arr.Append(fromYAML(L, item)) + } + return arr + case map[interface{}]interface{}: + tbl := L.CreateTable(0, len(converted)) + for key, item := range converted { + if s, ok := key.(string); ok { + tbl.RawSetH(lua.LString(s), fromYAML(L, item)) + } + } + return tbl + } + + return lua.LNil +} diff --git a/vendor/github.com/kr/fs/LICENSE b/vendor/github.com/kr/fs/LICENSE new file mode 100644 index 0000000..7448756 --- /dev/null +++ b/vendor/github.com/kr/fs/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/kr/fs/Readme b/vendor/github.com/kr/fs/Readme new file mode 100644 index 0000000..c95e13f --- /dev/null +++ b/vendor/github.com/kr/fs/Readme @@ -0,0 +1,3 @@ +Filesystem Package + +http://godoc.org/github.com/kr/fs diff --git a/vendor/github.com/kr/fs/example_test.go b/vendor/github.com/kr/fs/example_test.go new file mode 100644 index 0000000..77e0db9 --- /dev/null +++ b/vendor/github.com/kr/fs/example_test.go @@ -0,0 +1,19 @@ +package fs_test + +import ( + "fmt" + "os" + + "github.com/kr/fs" +) + +func ExampleWalker() { + walker := fs.Walk("/usr/lib") + for walker.Step() { + if err := walker.Err(); err != nil { + fmt.Fprintln(os.Stderr, err) + continue + } + fmt.Println(walker.Path()) + } +} diff --git a/vendor/github.com/kr/fs/filesystem.go b/vendor/github.com/kr/fs/filesystem.go new file mode 100644 index 0000000..f1c4805 --- /dev/null +++ b/vendor/github.com/kr/fs/filesystem.go @@ -0,0 +1,36 @@ +package fs + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// FileSystem defines the methods of an abstract filesystem. +type FileSystem interface { + + // ReadDir reads the directory named by dirname and returns a + // list of directory entries. + ReadDir(dirname string) ([]os.FileInfo, error) + + // Lstat returns a FileInfo describing the named file. If the file is a + // symbolic link, the returned FileInfo describes the symbolic link. Lstat + // makes no attempt to follow the link. + Lstat(name string) (os.FileInfo, error) + + // Join joins any number of path elements into a single path, adding a + // separator if necessary. The result is Cleaned; in particular, all + // empty strings are ignored. + // + // The separator is FileSystem specific. + Join(elem ...string) string +} + +// fs represents a FileSystem provided by the os package. +type fs struct{} + +func (f *fs) ReadDir(dirname string) ([]os.FileInfo, error) { return ioutil.ReadDir(dirname) } + +func (f *fs) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) } + +func (f *fs) Join(elem ...string) string { return filepath.Join(elem...) } diff --git a/vendor/github.com/kr/fs/walk.go b/vendor/github.com/kr/fs/walk.go new file mode 100644 index 0000000..6ffa1e0 --- /dev/null +++ b/vendor/github.com/kr/fs/walk.go @@ -0,0 +1,95 @@ +// Package fs provides filesystem-related functions. +package fs + +import ( + "os" +) + +// Walker provides a convenient interface for iterating over the +// descendants of a filesystem path. +// Successive calls to the Step method will step through each +// file or directory in the tree, including the root. The files +// are walked in lexical order, which makes the output deterministic +// but means that for very large directories Walker can be inefficient. +// Walker does not follow symbolic links. +type Walker struct { + fs FileSystem + cur item + stack []item + descend bool +} + +type item struct { + path string + info os.FileInfo + err error +} + +// Walk returns a new Walker rooted at root. +func Walk(root string) *Walker { + return WalkFS(root, new(fs)) +} + +// WalkFS returns a new Walker rooted at root on the FileSystem fs. +func WalkFS(root string, fs FileSystem) *Walker { + info, err := fs.Lstat(root) + return &Walker{ + fs: fs, + stack: []item{{root, info, err}}, + } +} + +// Step advances the Walker to the next file or directory, +// which will then be available through the Path, Stat, +// and Err methods. +// It returns false when the walk stops at the end of the tree. +func (w *Walker) Step() bool { + if w.descend && w.cur.err == nil && w.cur.info.IsDir() { + list, err := w.fs.ReadDir(w.cur.path) + if err != nil { + w.cur.err = err + w.stack = append(w.stack, w.cur) + } else { + for i := len(list) - 1; i >= 0; i-- { + path := w.fs.Join(w.cur.path, list[i].Name()) + w.stack = append(w.stack, item{path, list[i], nil}) + } + } + } + + if len(w.stack) == 0 { + return false + } + i := len(w.stack) - 1 + w.cur = w.stack[i] + w.stack = w.stack[:i] + w.descend = true + return true +} + +// Path returns the path to the most recent file or directory +// visited by a call to Step. It contains the argument to Walk +// as a prefix; that is, if Walk is called with "dir", which is +// a directory containing the file "a", Path will return "dir/a". +func (w *Walker) Path() string { + return w.cur.path +} + +// Stat returns info for the most recent file or directory +// visited by a call to Step. +func (w *Walker) Stat() os.FileInfo { + return w.cur.info +} + +// Err returns the error, if any, for the most recent attempt +// by Step to visit a file or directory. If a directory has +// an error, w will not descend into that directory. +func (w *Walker) Err() error { + return w.cur.err +} + +// SkipDir causes the currently visited directory to be skipped. +// If w is not on a directory, SkipDir has no effect. +func (w *Walker) SkipDir() { + w.descend = false +} diff --git a/vendor/github.com/kr/fs/walk_test.go b/vendor/github.com/kr/fs/walk_test.go new file mode 100644 index 0000000..6f5ad2a --- /dev/null +++ b/vendor/github.com/kr/fs/walk_test.go @@ -0,0 +1,209 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs_test + +import ( + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/kr/fs" +) + +type PathTest struct { + path, result string +} + +type Node struct { + name string + entries []*Node // nil if the entry is a file + mark int +} + +var tree = &Node{ + "testdata", + []*Node{ + {"a", nil, 0}, + {"b", []*Node{}, 0}, + {"c", nil, 0}, + { + "d", + []*Node{ + {"x", nil, 0}, + {"y", []*Node{}, 0}, + { + "z", + []*Node{ + {"u", nil, 0}, + {"v", nil, 0}, + }, + 0, + }, + }, + 0, + }, + }, + 0, +} + +func walkTree(n *Node, path string, f func(path string, n *Node)) { + f(path, n) + for _, e := range n.entries { + walkTree(e, filepath.Join(path, e.name), f) + } +} + +func makeTree(t *testing.T) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.entries == nil { + fd, err := os.Create(path) + if err != nil { + t.Errorf("makeTree: %v", err) + return + } + fd.Close() + } else { + os.Mkdir(path, 0770) + } + }) +} + +func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } + +func checkMarks(t *testing.T, report bool) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.mark != 1 && report { + t.Errorf("node %s mark = %d; expected 1", path, n.mark) + } + n.mark = 0 + }) +} + +// Assumes that each node name is unique. Good enough for a test. +// If clear is true, any incoming error is cleared before return. The errors +// are always accumulated, though. +func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error { + if err != nil { + *errors = append(*errors, err) + if clear { + return nil + } + return err + } + name := info.Name() + walkTree(tree, tree.name, func(path string, n *Node) { + if n.name == name { + n.mark++ + } + }) + return nil +} + +func TestWalk(t *testing.T) { + makeTree(t) + errors := make([]error, 0, 10) + clear := true + markFn := func(walker *fs.Walker) (err error) { + for walker.Step() { + err = mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear) + if err != nil { + break + } + } + return err + } + // Expect no errors. + err := markFn(fs.Walk(tree.name)) + if err != nil { + t.Fatalf("no error expected, found: %s", err) + } + if len(errors) != 0 { + t.Fatalf("unexpected errors: %s", errors) + } + checkMarks(t, true) + errors = errors[0:0] + + // Test permission errors. Only possible if we're not root + // and only on some file systems (AFS, FAT). To avoid errors during + // all.bash on those file systems, skip during go test -short. + if os.Getuid() > 0 && !testing.Short() { + // introduce 2 errors: chmod top-level directories to 0 + os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) + os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) + + // 3) capture errors, expect two. + // mark respective subtrees manually + markTree(tree.entries[1]) + markTree(tree.entries[3]) + // correct double-marking of directory itself + tree.entries[1].mark-- + tree.entries[3].mark-- + err := markFn(fs.Walk(tree.name)) + if err != nil { + t.Fatalf("expected no error return from Walk, got %s", err) + } + if len(errors) != 2 { + t.Errorf("expected 2 errors, got %d: %s", len(errors), errors) + } + // the inaccessible subtrees were marked manually + checkMarks(t, true) + errors = errors[0:0] + + // 4) capture errors, stop after first error. + // mark respective subtrees manually + markTree(tree.entries[1]) + markTree(tree.entries[3]) + // correct double-marking of directory itself + tree.entries[1].mark-- + tree.entries[3].mark-- + clear = false // error will stop processing + err = markFn(fs.Walk(tree.name)) + if err == nil { + t.Fatalf("expected error return from Walk") + } + if len(errors) != 1 { + t.Errorf("expected 1 error, got %d: %s", len(errors), errors) + } + // the inaccessible subtrees were marked manually + checkMarks(t, false) + errors = errors[0:0] + + // restore permissions + os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770) + os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770) + } + + // cleanup + if err := os.RemoveAll(tree.name); err != nil { + t.Errorf("removeTree: %v", err) + } +} + +func TestBug3486(t *testing.T) { // http://code.google.com/p/go/issues/detail?id=3486 + root, err := filepath.EvalSymlinks(runtime.GOROOT()) + if err != nil { + t.Fatal(err) + } + lib := filepath.Join(root, "lib") + src := filepath.Join(root, "src") + seenSrc := false + walker := fs.Walk(root) + for walker.Step() { + if walker.Err() != nil { + t.Fatal(walker.Err()) + } + + switch walker.Path() { + case lib: + walker.SkipDir() + case src: + seenSrc = true + } + } + if !seenSrc { + t.Fatalf("%q not seen", src) + } +} diff --git a/vendor/github.com/kr/pty/.gitignore b/vendor/github.com/kr/pty/.gitignore new file mode 100644 index 0000000..1f0a99f --- /dev/null +++ b/vendor/github.com/kr/pty/.gitignore @@ -0,0 +1,4 @@ +[568].out +_go* +_test* +_obj diff --git a/vendor/github.com/kr/pty/License b/vendor/github.com/kr/pty/License new file mode 100644 index 0000000..6b7558b --- /dev/null +++ b/vendor/github.com/kr/pty/License @@ -0,0 +1,23 @@ +Copyright (c) 2011 Keith Rarick + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall +be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kr/pty/README.md b/vendor/github.com/kr/pty/README.md new file mode 100644 index 0000000..f9bb002 --- /dev/null +++ b/vendor/github.com/kr/pty/README.md @@ -0,0 +1,100 @@ +# pty + +Pty is a Go package for using unix pseudo-terminals. + +## Install + + go get github.com/kr/pty + +## Example + +### Command + +```go +package main + +import ( + "github.com/kr/pty" + "io" + "os" + "os/exec" +) + +func main() { + c := exec.Command("grep", "--color=auto", "bar") + f, err := pty.Start(c) + if err != nil { + panic(err) + } + + go func() { + f.Write([]byte("foo\n")) + f.Write([]byte("bar\n")) + f.Write([]byte("baz\n")) + f.Write([]byte{4}) // EOT + }() + io.Copy(os.Stdout, f) +} +``` + +### Shell + +```go +package main + +import ( + "io" + "log" + "os" + "os/exec" + "os/signal" + "syscall" + + "github.com/kr/pty" + "golang.org/x/crypto/ssh/terminal" +) + +func test() error { + // Create arbitrary command. + c := exec.Command("bash") + + // Start the command with a pty. + ptmx, err := pty.Start(c) + if err != nil { + return err + } + // Make sure to close the pty at the end. + defer func() { _ = ptmx.Close() }() // Best effort. + + // Handle pty size. + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + go func() { + for range ch { + if err := pty.InheritSize(os.Stdin, ptmx); err != nil { + log.Printf("error resizing pty: %s", err) + } + } + }() + ch <- syscall.SIGWINCH // Initial resize. + + // Set stdin in raw mode. + oldState, err := terminal.MakeRaw(int(os.Stdin.Fd())) + if err != nil { + panic(err) + } + defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. + + // Copy stdin to the pty and the pty to stdout. + go func() { _, _ = io.Copy(ptmx, os.Stdin) }() + _, _ = io.Copy(os.Stdout, ptmx) + + return nil +} + +func main() { + if err := test(); err != nil { + log.Fatal(err) + } +} +``` diff --git a/vendor/github.com/kr/pty/doc.go b/vendor/github.com/kr/pty/doc.go new file mode 100644 index 0000000..190cfbe --- /dev/null +++ b/vendor/github.com/kr/pty/doc.go @@ -0,0 +1,16 @@ +// Package pty provides functions for working with Unix terminals. +package pty + +import ( + "errors" + "os" +) + +// ErrUnsupported is returned if a function is not +// available on the current platform. +var ErrUnsupported = errors.New("unsupported") + +// Opens a pty and its corresponding tty. +func Open() (pty, tty *os.File, err error) { + return open() +} diff --git a/vendor/github.com/kr/pty/ioctl.go b/vendor/github.com/kr/pty/ioctl.go new file mode 100644 index 0000000..c57c19e --- /dev/null +++ b/vendor/github.com/kr/pty/ioctl.go @@ -0,0 +1,13 @@ +// +build !windows + +package pty + +import "syscall" + +func ioctl(fd, cmd, ptr uintptr) error { + _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) + if e != 0 { + return e + } + return nil +} diff --git a/vendor/github.com/kr/pty/ioctl_bsd.go b/vendor/github.com/kr/pty/ioctl_bsd.go new file mode 100644 index 0000000..73b12c5 --- /dev/null +++ b/vendor/github.com/kr/pty/ioctl_bsd.go @@ -0,0 +1,39 @@ +// +build darwin dragonfly freebsd netbsd openbsd + +package pty + +// from +const ( + _IOC_VOID uintptr = 0x20000000 + _IOC_OUT uintptr = 0x40000000 + _IOC_IN uintptr = 0x80000000 + _IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN + _IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN + + _IOC_PARAM_SHIFT = 13 + _IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1 +) + +func _IOC_PARM_LEN(ioctl uintptr) uintptr { + return (ioctl >> 16) & _IOC_PARAM_MASK +} + +func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num +} + +func _IO(group byte, ioctl_num uintptr) uintptr { + return _IOC(_IOC_VOID, group, ioctl_num, 0) +} + +func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_OUT, group, ioctl_num, param_len) +} + +func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_IN, group, ioctl_num, param_len) +} + +func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len) +} diff --git a/vendor/github.com/kr/pty/mktypes.bash b/vendor/github.com/kr/pty/mktypes.bash new file mode 100755 index 0000000..82ee167 --- /dev/null +++ b/vendor/github.com/kr/pty/mktypes.bash @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +GOOSARCH="${GOOS}_${GOARCH}" +case "$GOOSARCH" in +_* | *_ | _) + echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2 + exit 1 + ;; +esac + +GODEFS="go tool cgo -godefs" + +$GODEFS types.go |gofmt > ztypes_$GOARCH.go + +case $GOOS in +freebsd|dragonfly|openbsd) + $GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go + ;; +esac diff --git a/vendor/github.com/kr/pty/pty_darwin.go b/vendor/github.com/kr/pty/pty_darwin.go new file mode 100644 index 0000000..6344b6b --- /dev/null +++ b/vendor/github.com/kr/pty/pty_darwin.go @@ -0,0 +1,65 @@ +package pty + +import ( + "errors" + "os" + "syscall" + "unsafe" +) + +func open() (pty, tty *os.File, err error) { + pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + p := os.NewFile(uintptr(pFD), "/dev/ptmx") + // In case of error after this point, make sure we close the ptmx fd. + defer func() { + if err != nil { + _ = p.Close() // Best effort. + } + }() + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + if err := grantpt(p); err != nil { + return nil, nil, err + } + + if err := unlockpt(p); err != nil { + return nil, nil, err + } + + t, err := os.OpenFile(sname, os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func ptsname(f *os.File) (string, error) { + n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME)) + + err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0]))) + if err != nil { + return "", err + } + + for i, c := range n { + if c == 0 { + return string(n[:i]), nil + } + } + return "", errors.New("TIOCPTYGNAME string not NUL-terminated") +} + +func grantpt(f *os.File) error { + return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0) +} + +func unlockpt(f *os.File) error { + return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0) +} diff --git a/vendor/github.com/kr/pty/pty_dragonfly.go b/vendor/github.com/kr/pty/pty_dragonfly.go new file mode 100644 index 0000000..b7d1f20 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_dragonfly.go @@ -0,0 +1,80 @@ +package pty + +import ( + "errors" + "os" + "strings" + "syscall" + "unsafe" +) + +// same code as pty_darwin.go +func open() (pty, tty *os.File, err error) { + p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + // In case of error after this point, make sure we close the ptmx fd. + defer func() { + if err != nil { + _ = p.Close() // Best effort. + } + }() + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + if err := grantpt(p); err != nil { + return nil, nil, err + } + + if err := unlockpt(p); err != nil { + return nil, nil, err + } + + t, err := os.OpenFile(sname, os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func grantpt(f *os.File) error { + _, err := isptmaster(f.Fd()) + return err +} + +func unlockpt(f *os.File) error { + _, err := isptmaster(f.Fd()) + return err +} + +func isptmaster(fd uintptr) (bool, error) { + err := ioctl(fd, syscall.TIOCISPTMASTER, 0) + return err == nil, err +} + +var ( + emptyFiodgnameArg fiodgnameArg + ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) +) + +func ptsname(f *os.File) (string, error) { + name := make([]byte, _C_SPECNAMELEN) + fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}} + + err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa))) + if err != nil { + return "", err + } + + for i, c := range name { + if c == 0 { + s := "/dev/" + string(name[:i]) + return strings.Replace(s, "ptm", "pts", -1), nil + } + } + return "", errors.New("TIOCPTYGNAME string not NUL-terminated") +} diff --git a/vendor/github.com/kr/pty/pty_freebsd.go b/vendor/github.com/kr/pty/pty_freebsd.go new file mode 100644 index 0000000..63b6d91 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_freebsd.go @@ -0,0 +1,78 @@ +package pty + +import ( + "errors" + "os" + "syscall" + "unsafe" +) + +func posixOpenpt(oflag int) (fd int, err error) { + r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0) + fd = int(r0) + if e1 != 0 { + err = e1 + } + return fd, err +} + +func open() (pty, tty *os.File, err error) { + fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC) + if err != nil { + return nil, nil, err + } + p := os.NewFile(uintptr(fd), "/dev/pts") + // In case of error after this point, make sure we close the pts fd. + defer func() { + if err != nil { + _ = p.Close() // Best effort. + } + }() + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func isptmaster(fd uintptr) (bool, error) { + err := ioctl(fd, syscall.TIOCPTMASTER, 0) + return err == nil, err +} + +var ( + emptyFiodgnameArg fiodgnameArg + ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) +) + +func ptsname(f *os.File) (string, error) { + master, err := isptmaster(f.Fd()) + if err != nil { + return "", err + } + if !master { + return "", syscall.EINVAL + } + + const n = _C_SPECNAMELEN + 1 + var ( + buf = make([]byte, n) + arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))} + ) + if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil { + return "", err + } + + for i, c := range buf { + if c == 0 { + return string(buf[:i]), nil + } + } + return "", errors.New("FIODGNAME string not NUL-terminated") +} diff --git a/vendor/github.com/kr/pty/pty_linux.go b/vendor/github.com/kr/pty/pty_linux.go new file mode 100644 index 0000000..296dd21 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_linux.go @@ -0,0 +1,51 @@ +package pty + +import ( + "os" + "strconv" + "syscall" + "unsafe" +) + +func open() (pty, tty *os.File, err error) { + p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + if err != nil { + return nil, nil, err + } + // In case of error after this point, make sure we close the ptmx fd. + defer func() { + if err != nil { + _ = p.Close() // Best effort. + } + }() + + sname, err := ptsname(p) + if err != nil { + return nil, nil, err + } + + if err := unlockpt(p); err != nil { + return nil, nil, err + } + + t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) + if err != nil { + return nil, nil, err + } + return p, t, nil +} + +func ptsname(f *os.File) (string, error) { + var n _C_uint + err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) + if err != nil { + return "", err + } + return "/dev/pts/" + strconv.Itoa(int(n)), nil +} + +func unlockpt(f *os.File) error { + var u _C_int + // use TIOCSPTLCK with a zero valued arg to clear the slave pty lock + return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) +} diff --git a/vendor/github.com/kr/pty/pty_openbsd.go b/vendor/github.com/kr/pty/pty_openbsd.go new file mode 100644 index 0000000..6e7aeae --- /dev/null +++ b/vendor/github.com/kr/pty/pty_openbsd.go @@ -0,0 +1,33 @@ +package pty + +import ( + "os" + "syscall" + "unsafe" +) + +func open() (pty, tty *os.File, err error) { + /* + * from ptm(4): + * The PTMGET command allocates a free pseudo terminal, changes its + * ownership to the caller, revokes the access privileges for all previous + * users, opens the file descriptors for the master and slave devices and + * returns them to the caller in struct ptmget. + */ + + p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + defer p.Close() + + var ptm ptmget + if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil { + return nil, nil, err + } + + pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm") + tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm") + + return pty, tty, nil +} diff --git a/vendor/github.com/kr/pty/pty_unsupported.go b/vendor/github.com/kr/pty/pty_unsupported.go new file mode 100644 index 0000000..9a3e721 --- /dev/null +++ b/vendor/github.com/kr/pty/pty_unsupported.go @@ -0,0 +1,11 @@ +// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd + +package pty + +import ( + "os" +) + +func open() (pty, tty *os.File, err error) { + return nil, nil, ErrUnsupported +} diff --git a/vendor/github.com/kr/pty/run.go b/vendor/github.com/kr/pty/run.go new file mode 100644 index 0000000..baecca8 --- /dev/null +++ b/vendor/github.com/kr/pty/run.go @@ -0,0 +1,34 @@ +// +build !windows + +package pty + +import ( + "os" + "os/exec" + "syscall" +) + +// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout, +// and c.Stderr, calls c.Start, and returns the File of the tty's +// corresponding pty. +func Start(c *exec.Cmd) (pty *os.File, err error) { + pty, tty, err := Open() + if err != nil { + return nil, err + } + defer tty.Close() + c.Stdout = tty + c.Stdin = tty + c.Stderr = tty + if c.SysProcAttr == nil { + c.SysProcAttr = &syscall.SysProcAttr{} + } + c.SysProcAttr.Setctty = true + c.SysProcAttr.Setsid = true + err = c.Start() + if err != nil { + pty.Close() + return nil, err + } + return pty, err +} diff --git a/vendor/github.com/kr/pty/types.go b/vendor/github.com/kr/pty/types.go new file mode 100644 index 0000000..5aecb6b --- /dev/null +++ b/vendor/github.com/kr/pty/types.go @@ -0,0 +1,10 @@ +// +build ignore + +package pty + +import "C" + +type ( + _C_int C.int + _C_uint C.uint +) diff --git a/vendor/github.com/kr/pty/types_dragonfly.go b/vendor/github.com/kr/pty/types_dragonfly.go new file mode 100644 index 0000000..5c0493b --- /dev/null +++ b/vendor/github.com/kr/pty/types_dragonfly.go @@ -0,0 +1,17 @@ +// +build ignore + +package pty + +/* +#define _KERNEL +#include +#include +#include +*/ +import "C" + +const ( + _C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */ +) + +type fiodgnameArg C.struct_fiodname_args diff --git a/vendor/github.com/kr/pty/types_freebsd.go b/vendor/github.com/kr/pty/types_freebsd.go new file mode 100644 index 0000000..ce3eb95 --- /dev/null +++ b/vendor/github.com/kr/pty/types_freebsd.go @@ -0,0 +1,15 @@ +// +build ignore + +package pty + +/* +#include +#include +*/ +import "C" + +const ( + _C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */ +) + +type fiodgnameArg C.struct_fiodgname_arg diff --git a/vendor/github.com/kr/pty/types_openbsd.go b/vendor/github.com/kr/pty/types_openbsd.go new file mode 100644 index 0000000..47701b5 --- /dev/null +++ b/vendor/github.com/kr/pty/types_openbsd.go @@ -0,0 +1,14 @@ +// +build ignore + +package pty + +/* +#include +#include +#include +*/ +import "C" + +type ptmget C.struct_ptmget + +var ioctl_PTMGET = C.PTMGET diff --git a/vendor/github.com/kr/pty/util.go b/vendor/github.com/kr/pty/util.go new file mode 100644 index 0000000..68a8584 --- /dev/null +++ b/vendor/github.com/kr/pty/util.go @@ -0,0 +1,64 @@ +// +build !windows + +package pty + +import ( + "os" + "syscall" + "unsafe" +) + +// InheritSize applies the terminal size of master to slave. This should be run +// in a signal handler for syscall.SIGWINCH to automatically resize the slave when +// the master receives a window size change notification. +func InheritSize(master, slave *os.File) error { + size, err := GetsizeFull(master) + if err != nil { + return err + } + err = Setsize(slave, size) + if err != nil { + return err + } + return nil +} + +// Setsize resizes t to s. +func Setsize(t *os.File, ws *Winsize) error { + return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ) +} + +// GetsizeFull returns the full terminal size description. +func GetsizeFull(t *os.File) (size *Winsize, err error) { + var ws Winsize + err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ) + return &ws, err +} + +// Getsize returns the number of rows (lines) and cols (positions +// in each line) in terminal t. +func Getsize(t *os.File) (rows, cols int, err error) { + ws, err := GetsizeFull(t) + return int(ws.Rows), int(ws.Cols), err +} + +// Winsize describes the terminal size. +type Winsize struct { + Rows uint16 // ws_row: Number of rows (in cells) + Cols uint16 // ws_col: Number of columns (in cells) + X uint16 // ws_xpixel: Width in pixels + Y uint16 // ws_ypixel: Height in pixels +} + +func windowRectCall(ws *Winsize, fd, a2 uintptr) error { + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, + fd, + a2, + uintptr(unsafe.Pointer(ws)), + ) + if errno != 0 { + return syscall.Errno(errno) + } + return nil +} diff --git a/vendor/github.com/kr/pty/ztypes_386.go b/vendor/github.com/kr/pty/ztypes_386.go new file mode 100644 index 0000000..ff0b8fd --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_386.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_amd64.go b/vendor/github.com/kr/pty/ztypes_amd64.go new file mode 100644 index 0000000..ff0b8fd --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_amd64.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_arm.go b/vendor/github.com/kr/pty/ztypes_arm.go new file mode 100644 index 0000000..ff0b8fd --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_arm.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_arm64.go b/vendor/github.com/kr/pty/ztypes_arm64.go new file mode 100644 index 0000000..6c29a4b --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_arm64.go @@ -0,0 +1,11 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +// +build arm64 + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_dragonfly_amd64.go b/vendor/github.com/kr/pty/ztypes_dragonfly_amd64.go new file mode 100644 index 0000000..6b0ba03 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_dragonfly_amd64.go @@ -0,0 +1,14 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_dragonfly.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Name *byte + Len uint32 + Pad_cgo_0 [4]byte +} diff --git a/vendor/github.com/kr/pty/ztypes_freebsd_386.go b/vendor/github.com/kr/pty/ztypes_freebsd_386.go new file mode 100644 index 0000000..d997537 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_freebsd_386.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Buf *byte +} diff --git a/vendor/github.com/kr/pty/ztypes_freebsd_amd64.go b/vendor/github.com/kr/pty/ztypes_freebsd_amd64.go new file mode 100644 index 0000000..5fa102f --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_freebsd_amd64.go @@ -0,0 +1,14 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Pad_cgo_0 [4]byte + Buf *byte +} diff --git a/vendor/github.com/kr/pty/ztypes_freebsd_arm.go b/vendor/github.com/kr/pty/ztypes_freebsd_arm.go new file mode 100644 index 0000000..d997537 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_freebsd_arm.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Buf *byte +} diff --git a/vendor/github.com/kr/pty/ztypes_mipsx.go b/vendor/github.com/kr/pty/ztypes_mipsx.go new file mode 100644 index 0000000..f0ce740 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_mipsx.go @@ -0,0 +1,12 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +// +build linux +// +build mips mipsle mips64 mips64le + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_openbsd_amd64.go b/vendor/github.com/kr/pty/ztypes_openbsd_amd64.go new file mode 100644 index 0000000..e670516 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_openbsd_amd64.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_openbsd.go + +package pty + +type ptmget struct { + Cfd int32 + Sfd int32 + Cn [16]int8 + Sn [16]int8 +} + +var ioctl_PTMGET = 0x40287401 diff --git a/vendor/github.com/kr/pty/ztypes_ppc64.go b/vendor/github.com/kr/pty/ztypes_ppc64.go new file mode 100644 index 0000000..4e1af84 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_ppc64.go @@ -0,0 +1,11 @@ +// +build ppc64 + +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_ppc64le.go b/vendor/github.com/kr/pty/ztypes_ppc64le.go new file mode 100644 index 0000000..e6780f4 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_ppc64le.go @@ -0,0 +1,11 @@ +// +build ppc64le + +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/kr/pty/ztypes_s390x.go b/vendor/github.com/kr/pty/ztypes_s390x.go new file mode 100644 index 0000000..a7452b6 --- /dev/null +++ b/vendor/github.com/kr/pty/ztypes_s390x.go @@ -0,0 +1,11 @@ +// +build s390x + +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/github.com/otm/gluaflag/LICENSE b/vendor/github.com/otm/gluaflag/LICENSE new file mode 100644 index 0000000..70785cd --- /dev/null +++ b/vendor/github.com/otm/gluaflag/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Nils Lagerkvist + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/otm/gluaflag/README.md b/vendor/github.com/otm/gluaflag/README.md new file mode 100644 index 0000000..5feca40 --- /dev/null +++ b/vendor/github.com/otm/gluaflag/README.md @@ -0,0 +1,3 @@ +# gluaflag + +gluaflag is a flag module for [gopher-lua](https://github.com/yuin/gopher-lua) diff --git a/vendor/github.com/otm/gluaflag/flag.go b/vendor/github.com/otm/gluaflag/flag.go new file mode 100644 index 0000000..275ab22 --- /dev/null +++ b/vendor/github.com/otm/gluaflag/flag.go @@ -0,0 +1,310 @@ +package gluaflag + +import ( + "fmt" + "strconv" + + "github.com/yuin/gopher-lua" +) + +var flagFuncs = map[string]lua.LGFunction{} + +type flg struct { + name string + value interface{} + usage string + required bool + compFn *lua.LFunction +} + +func (f *flg) userdata(L *lua.LState) lua.LValue { + ud := L.NewUserData() + ud.Value = f + L.SetMetatable(ud, L.GetTypeMetatable(luaFlagTypeName)) + return ud +} + +type flgs map[string]*flg + +type argument struct { + name string + times int + glob bool + optional bool + usage string + value lua.LValue + typ string + parser parser + shortUsage shortUsage + compFn *lua.LFunction +} + +func (a *argument) parse(args []string, L *lua.LState) ([]string, error) { + args, value, err := a.parser(args, L) + a.value = value + return args, err +} + +func (a *argument) toLValue(L *lua.LState) lua.LValue { + return a.value +} + +func (a *argument) generateUsage() string { + typ := a.typ + if typ == "" { + typ = "string" + } + + return fmt.Sprintf(" %v %v\n \t%v\n", a.name, typ, a.usage) +} + +type arguments []*argument + +type parser func([]string, *lua.LState) ([]string, lua.LValue, error) + +type shortUsage func(string) string + +// string parsers +func parseString(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, lua.LNil, fmt.Errorf("expected string") + } + return parseOptionalString(args, L) +} + +func parseOptionalString(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, lua.LNil, nil + } + return args[1:len(args)], lua.LString(args[0]), nil +} + +func parseNStrings(n int) parser { + return func(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < n { + return args, lua.LNil, fmt.Errorf("expected %v strings", n) + } + table := L.NewTable() + for i := 0; i < n; i++ { + table.Append(lua.LString(args[i])) + } + return args[n:len(args)], table, nil + } +} + +func parseStrings(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, L.NewTable(), fmt.Errorf("expected at least one string") + } + + return parseOptionalStrings(args, L) +} + +func parseOptionalStrings(args []string, L *lua.LState) ([]string, lua.LValue, error) { + table := L.NewTable() + for i := 0; i < len(args); i++ { + table.Append(lua.LString(args[i])) + } + return make([]string, 0), table, nil +} + +// integer parsers +func parseInt(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, lua.LNil, fmt.Errorf("expected string") + } + return parseOptionalInt(args, L) +} + +func parseOptionalInt(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, lua.LNil, nil + } + i, err := strconv.Atoi(args[0]) + if err != nil { + return args[1:len(args)], lua.LNumber(0), fmt.Errorf("invalid integer value: %v", args[0]) + } + return args[1:len(args)], lua.LNumber(i), nil +} + +func parseNInts(n int) parser { + return func(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < n { + return args, lua.LNil, fmt.Errorf("expected %v integers", n) + } + table := L.NewTable() + for i := 0; i < n; i++ { + v, err := strconv.Atoi(args[i]) + if err != nil { + return args[1:len(args)], lua.LNumber(0), fmt.Errorf("invalid integer value: %v", args[i]) + } + table.Append(lua.LNumber(v)) + } + return args[n:len(args)], table, nil + } +} + +func parseInts(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, L.NewTable(), fmt.Errorf("expected at least one integer") + } + + return parseOptionalInts(args, L) +} + +func parseOptionalInts(args []string, L *lua.LState) ([]string, lua.LValue, error) { + table := L.NewTable() + for i := 0; i < len(args); i++ { + v, err := strconv.Atoi(args[i]) + if err != nil { + return args[1:len(args)], lua.LNumber(0), fmt.Errorf("invalid integer value: %v", args[i]) + } + table.Append(lua.LNumber(v)) + } + return make([]string, 0), table, nil +} + +// number parsers +func parseNumber(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, lua.LNil, fmt.Errorf("expected string") + } + return parseOptionalNumber(args, L) +} + +func parseOptionalNumber(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, lua.LNil, nil + } + i, err := strconv.ParseFloat(args[0], 64) + if err != nil { + return args[1:len(args)], lua.LNumber(0), fmt.Errorf("invalid number value: %v", args[0]) + } + return args[1:len(args)], lua.LNumber(i), nil +} + +func parseNNumbers(n int) parser { + return func(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < n { + return args, lua.LNil, fmt.Errorf("expected %v numbers", n) + } + table := L.NewTable() + for i := 0; i < n; i++ { + v, err := strconv.ParseFloat(args[i], 64) + if err != nil { + return args[1:len(args)], lua.LNumber(0), fmt.Errorf("invalid number value: %v", args[i]) + } + table.Append(lua.LNumber(v)) + } + return args[n:len(args)], table, nil + } +} + +func parseNumbers(args []string, L *lua.LState) ([]string, lua.LValue, error) { + if len(args) < 1 { + return args, L.NewTable(), fmt.Errorf("expected at least one number") + } + + return parseOptionalNumbers(args, L) +} + +func parseOptionalNumbers(args []string, L *lua.LState) ([]string, lua.LValue, error) { + table := L.NewTable() + for i := 0; i < len(args); i++ { + v, err := strconv.ParseFloat(args[i], 64) + if err != nil { + return args[1:len(args)], lua.LNumber(0), fmt.Errorf("invalid number value: %v", args[i]) + } + table.Append(lua.LNumber(v)) + } + return make([]string, 0), table, nil +} + +func getParser(typ string, option lua.LValue) (parser, error) { + parsers := map[string]map[string]parser{ + "string": { + "+": parseStrings, + "*": parseOptionalStrings, + "?": parseOptionalString, + "1": parseString, + }, + "int": { + "+": parseInts, + "*": parseOptionalInts, + "?": parseOptionalInt, + "1": parseInt, + }, + "number": { + "+": parseNumbers, + "*": parseOptionalNumbers, + "?": parseOptionalNumber, + "1": parseNumber, + }, + } + + nParsers := map[string]func(int) parser{ + "string": parseNStrings, + "int": parseNInts, + "number": parseNNumbers, + } + + switch t := option.(type) { + case lua.LString: + if p, ok := parsers[typ][string(t)]; ok { + return p, nil + } + case lua.LNumber: + switch { + case int(t) == 1: + return parsers[typ]["1"], nil + case int(t) > 1: + return nParsers[typ](int(t)), nil + } + } + + return nil, fmt.Errorf("nargs should be an integer or one of '?', '*', or '+'") +} + +func getShortUsageFn(option lua.LValue) (shortUsage, error) { + su := map[string]shortUsage{ + "?": func(name string) string { + return fmt.Sprintf("[%v] ", name) + }, + "1": func(name string) string { + return fmt.Sprintf("%v ", name) + }, + + "*": func(name string) string { + return fmt.Sprintf("[%v...] ", name) + }, + "+": func(name string) string { + return fmt.Sprintf("%v [%v...] ", name, name) + }, + } + + shortNUsage := func(n int) shortUsage { + return func(name string) string { + usage := "" + for i := 0; i < n; i++ { + usage = fmt.Sprintf("%v %v ", usage, name) + } + return usage + } + } + + switch t := option.(type) { + case lua.LString: + if p, ok := su[string(t)]; ok { + return p, nil + } + case lua.LNumber: + switch { + case int(t) == 1: + return su["1"], nil + case int(t) > 1: + return shortNUsage(int(t)), nil + } + } + + return nil, fmt.Errorf("nargs should be an integer or one of '?', '*', or '+'") +} diff --git a/vendor/github.com/otm/gluaflag/flag_test.go b/vendor/github.com/otm/gluaflag/flag_test.go new file mode 100644 index 0000000..bdfa248 --- /dev/null +++ b/vendor/github.com/otm/gluaflag/flag_test.go @@ -0,0 +1,602 @@ +package gluaflag + +import ( + "bytes" + "io" + "os" + "strings" + "testing" + + "github.com/yuin/gopher-lua" +) + +func captureStdout() func() string { + old := os.Stdout // keep backup of the real stdout + r, w, _ := os.Pipe() + os.Stdout = w + + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + return func() string { + w.Close() + os.Stdout = old // restoring the real stdout + return <-outC + } +} + +func captureStderr() func() string { + old := os.Stderr // keep backup of the real Stderr + r, w, _ := os.Pipe() + os.Stderr = w + + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + return func() string { + w.Close() + os.Stderr = old // restoring the real stdout + return <-outC + } +} + +func doString(src string, t *testing.T) (stdout, stderr string) { + L := lua.NewState() + defer L.Close() + L.PreloadModule("flag", Loader) + + restoreStdout := captureStdout() + restoreStderr := captureStderr() + err := L.DoString(src) + stdout = restoreStdout() + stderr = restoreStderr() + if err != nil { + t.Errorf("runtime error: %v", err) + } + + if len(stdout) > 0 { + stdout = stdout[0 : len(stdout)-1] + } + + if len(stderr) > 0 { + stderr = stderr[0 : len(stderr)-1] + } + + return stdout, stderr +} + +func TestUsage(t *testing.T) { + // TODO: Fix output + src := ` + local flag = require('flag') + arg = {"-foo"} + arg[0] = "subcmd" + fs = flag.new("subcommand") + fs:number("times", 1, "Number help string") + function fail() + flags = fs:parse(arg) + end + ok, err = pcall(fail) + + print(err) + ` + + expected := strings.Join([]string{ + "usage: subcommand [options]", + " -times float", + " Number help string (default 1)", + ":8: flag provided but not defined: -foo", + }, "\n") + expectedStderr := strings.Join([]string{ + "flag provided but not defined: -foo", + }, "\n") + got, stderr := doString(src, t) + + if got != expected || stderr != expectedStderr { + t.Errorf("expected stdout: `%v`\ngot: `%v`\nexpected stderr: `%v`\ngot: `%v`\nsrc: `%v`", expected, got, expectedStderr, stderr, src) + } +} + +func TestNumberFlag(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-times", "2"} + arg[0] = "subcmd" + fs = flag.new() + fs:number("times", 1, "Number help string") + flags = fs:parse(arg) + + print(flags.times) + print(type(flags.times)) + ` + + expected := "2\nnumber" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestIntFlag(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-times", "2"} + arg[0] = "subcmd" + fs = flag.new() + fs:int("times", 1, "Number help string") + flags = fs:parse(arg) + + print(flags.times) + print(type(flags.times)) + ` + + expected := "2\nnumber" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestIntSliceFlag(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-times", "2", "-times", "4"} + arg[0] = "subcmd" + fs = flag.new() + fs:ints("times", "Number help string") + flags = fs:parse(arg) + + print(type(flags.times)) + print(table.concat(flags.times, ",")) + ` + + expected := "table\n2,4" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestNumberFlagCompgen(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-times"} + arg[0] = "subcommand" + fs = flag.new() + local function compgen() + return "1 2 3" + end + fs:number("times", 1, "Number help string", compgen) + flags = fs:compgen(2, arg) + + print(table.concat(flags, " ")) + ` + + expected := strings.Join([]string{ + "1", + "2", + "3", + }, " ") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestNumberFlagAndArgs(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-times", "2", "foo"} + arg[0] = "subcmd" + fs = flag.new() + fs:number("times", 1, "Number help string") + flags = fs:parse(arg) + + print(flags.times) + print(type(flags.times)) + for i, v in ipairs(flags) do + print(i .. "=" .. v) + end + print(flags[1]) + ` + + expected := strings.Join([]string{ + "2", + "number", + "1=foo", + "foo", + }, "\n") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestNumberSliceFlag(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-times", "2.4", "-times", "4.3"} + arg[0] = "subcmd" + fs = flag.new() + fs:numbers("times", "Number help string") + flags = fs:parse(arg) + + print(type(flags.times)) + print(table.concat(flags.times, ",")) + ` + + expected := "table\n2.4,4.3" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestStringFlag(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "bar"} + arg[0] = "subcmd" + fs = flag.new() + fs:string("name", "foo", "String help string") + flags = fs:parse(arg) + + print(flags.name) + print(type(flags.name)) + ` + + expected := strings.Join([]string{ + "bar", + "string", + }, "\n") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestStringFlagCompgen(t *testing.T) { + src := ` + local flag = require('flag') + local arg = {"-name"} + arg[0] = "subcommand" + local fs = flag.new() + local function compgen() + return {"fii", "foo", "fum"} + end + fs:string("name", "foo", "String help string", compgen) + flags = fs:compgen(2, arg) + + print(table.concat(flags, "\n")) + ` + + expected := strings.Join([]string{ + "fii", + "foo", + "fum", + }, "\n") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestStringSliceFlag(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-times", "foo", "-times", "bar"} + arg[0] = "subcmd" + fs = flag.new() + fs:strings("times", "Number help string") + flags = fs:parse(arg) + + print(type(flags.times)) + print(table.concat(flags.times, ",")) + ` + + expected := "table\nfoo,bar" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestStringSliceNoValues(t *testing.T) { + src := ` + local flag = require('flag') + arg = {} + arg[0] = "subcmd" + fs = flag.new() + fs:strings("times", "Number help string") + flags = fs:parse(arg) + + print(type(flags.times)) + print(table.concat(flags.times, ",")) + ` + + expected := "table\n" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestBoolFlag(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-q"} + arg[0] = "subcmd" + fs = flag.new() + fs:bool("q", false, "Bool help string") + flags = fs:parse(arg) + + print(flags.q) + print(type(flags.q)) + ` + + expected := strings.Join([]string{ + "true", + "boolean", + }, "\n") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestStringArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"test", "-name", "foo", "mr"} + fs = flag.new() + local function compgen() + return "fii foo fum" + end + fs:string("name", "foo", "String help string", compgen) + fs:stringArg("title", 1, "Title", function() + return "mr miss mrs" + end) + flags = fs:parse(arg) + + function pairsByKeys(t) + local a = {} + for n in pairs(t) do table.insert(a, n) end + table.sort(a) + local i = 0 -- iterator variable + local iter = function () -- iterator function + i = i + 1 + if a[i] == nil then return nil + else return a[i], t[a[i]] + end + end + return iter + end + + for k, v in pairsByKeys(flags) do + print(k .. " " .. v) + end + ` + + expected := strings.Join([]string{ + "name foo", + "title mr", + }, "\n") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestOptionalStringArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "bar"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:stringArg("title", "?", "Title") + flags = fs:parse(arg) + assert(flags.title == "bar", "expected flags.title to be 'bar'") + ` + doString(src, t) +} + +func TestOptionalStringsArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "bar"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:stringArg("title", "*", "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 1, "expected flags.title to have length == 1") + assert(flags.title[1] == "bar", "expected flags.title[1] to be 'bar'") + ` + doString(src, t) +} + +func TestStringsArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "bar"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:stringArg("title", "+", "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 1, "expected flags.title to have length == 1") + assert(flags.title[1] == "bar", "expected flags.title[1] to be 'bar'") + ` + doString(src, t) +} + +func TestStringsArgToFew(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:stringArg("title", "+", "Title") + ok, err = pcall(function() fs:parse(arg) end) + print(err) + ` + expected := ":8: argument title: expected at least one string" + stdout, _ := doString(src, t) + if stdout != expected { + t.Errorf("expected: `%v`\ngot: `%v`\nsrc: `%v`", expected, stdout, src) + } +} + +func TestNStringsArgument(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "bar", "baz"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:stringArg("title", 2, "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 2, "expected flags.title to have length == 1") + assert(flags.title[1] == "bar", "expected flags.title[1] to be 'bar'") + assert(flags.title[2] == "baz", "expected flags.title[1] to be 'baz'") + ` + doString(src, t) +} + +func TestNStringsArgToFew(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "bar"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:stringArg("title", 2, "Title") + ok, err = pcall(function() fs:parse(arg) end) + print(err) + ` + expected := ":8: argument title: expected 2 strings" + stdout, _ := doString(src, t) + if stdout != expected { + t.Errorf("expected: `%v`\ngot: `%v`\nsrc: `%v`", expected, stdout, src) + } +} + +func TestStringArgumentCompgen(t *testing.T) { + src := ` + local flag = require('flag') + + fs = flag.new("subcommand") + local function compgen() + return "fii", "foo", "fum" + end + + res = {} + fs:string("name", "foo", "String help string", compgen) + fs:stringArg("mr", 1, "Title", function(arg, flags, raw) + res.arg = arg + res.flags = flags + res.raw = raw + return "mr", "miss", "mrs" + end) + + local arg = {"-name", "foo", "m"} + arg[0] = "subcommand" + flags = fs:compgen(3, arg) + assert(res.arg == "m", "expected arg to be 'm', got " .. res.arg) + assert(res.flags.name == "foo", "expected flag.names to be 'foo', got " .. res.flags.name) + assert(res.raw[0] == "subcommand", "expected raw[0] to be 'subcommand', got " .. res.raw[0]) + assert(res.raw[1] == "-name", "expected raw[1] to be '-name', got " .. res.raw[1]) + + print(table.concat(flags, " ")) + + ` + + expected := strings.Join([]string{ + "mrs", + "miss", + "mr", + }, " ") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TeststringArgumentCompgen2(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "m"} + arg[0] = "subcommand" + fs = flag.new() + local function compgen() + return "fii foo fum" + end + fs:string("name", "foo", "String help string", compgen) + fs:stringArgument("mr", 1, "Title", function() + return "mr miss mrs" + end) + flags = fs:compgen(3, arg) + + print(flags) + ` + + expected := strings.Join([]string{ + "mr", + "miss", + "mrs", + }, " ") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestStringArgumentUsage(t *testing.T) { + src := ` + local flag = require('flag') + fs = flag.new("subcommand") + fs:stringArg("title", 1, "Your title") + + print(fs:usage()) + ` + + expected := strings.Join([]string{ + "usage: subcommand title ", + " title string", + " \tYour title\n", + }, "\n") + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected stdout: `%v`\ngot: `%v`\nsrc: `%v`", expected, got, src) + } +} diff --git a/vendor/github.com/otm/gluaflag/flagset.go b/vendor/github.com/otm/gluaflag/flagset.go new file mode 100644 index 0000000..466a027 --- /dev/null +++ b/vendor/github.com/otm/gluaflag/flagset.go @@ -0,0 +1,769 @@ +package gluaflag + +import ( + "bytes" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/yuin/gopher-lua" +) + +var flagSetFuncs = map[string]lua.LGFunction{ + "number": number, + "numbers": numbers, + "int": integer, + "ints": integers, + "string": str, + "strings": strs, + "bool": boolean, + "stringArg": stringArgument, + "intArg": intArgument, + "numberArg": numberArgument, + "parse": parse, + "compgen": compgen, + "usage": usage, +} + +// FlagSet is the background userdata component +type FlagSet struct { + name string + fs *flag.FlagSet + flags flgs + arguments arguments + output io.Writer +} + +// New returns a new flagset userdata +func New(L *lua.LState, name string) *lua.LUserData { + f := flag.NewFlagSet(name, flag.ContinueOnError) + + flags := &FlagSet{ + name: name, + fs: f, + flags: make(flgs), + arguments: make(arguments, 0), + output: os.Stderr, + } + + flags.fs.Usage = func() { + fmt.Fprint(flags.output, flags.Usage()) + } + + ud := L.NewUserData() + ud.Value = flags + L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName)) + return ud +} + +func new(L *lua.LState) int { + var d lua.LValue = lua.LString("") + larg := L.GetGlobal("arg") + if targ, ok := larg.(*lua.LTable); ok { + d = targ.RawGetInt(0) + } + name := L.OptString(1, d.String()) + + L.Push(New(L, name)) + + return 1 +} + +// Usage returns the usage message for the flag set +func (fs *FlagSet) Usage() string { + buff := &bytes.Buffer{} + + buff.WriteString(fmt.Sprintf("usage: %v\n", fs.ShortUsage())) + + fs.fs.SetOutput(buff) + defer fs.fs.SetOutput(os.Stderr) + fs.fs.PrintDefaults() + + for _, arg := range fs.arguments { + buff.WriteString(arg.generateUsage()) + } + + return buff.String() +} + +// ShortUsage returns the usage string for a flagset +func (fs *FlagSet) ShortUsage() string { + buff := &bytes.Buffer{} + buff.WriteString(fs.name) + if len(fs.flags) > 0 { + buff.WriteString(fmt.Sprintf(" [options]")) + } + for _, arg := range fs.arguments { + buff.WriteString(" ") + buff.WriteString(arg.shortUsage(arg.name)) + } + + return buff.String() +} + +// FlagDefaults returns the flagsets help string for the flags +func (fs *FlagSet) FlagDefaults() string { + buff := &bytes.Buffer{} + fs.fs.SetOutput(buff) + defer fs.fs.SetOutput(os.Stderr) + fs.fs.PrintDefaults() + + return buff.String() +} + +// ArgDefaults returns the flagsets help string for the possitional arguments +func (fs *FlagSet) ArgDefaults() string { + buff := &bytes.Buffer{} + for _, arg := range fs.arguments { + buff.WriteString(arg.generateUsage()) + } + + return buff.String() +} + +func (fs *FlagSet) printFlags() string { + var s []string + fs.fs.VisitAll(func(fl *flag.Flag) { + s = append(s, "-"+fl.Name) + }) + return strings.Join(s, "\n") +} + +func (fs *FlagSet) getFlags() []string { + var s []string + fs.fs.VisitAll(func(fl *flag.Flag) { + s = append(s, "-"+fl.Name) + }) + return s +} + +// Compgen returns a string with possible options for the flag +func (fs *FlagSet) Compgen(L *lua.LState, compCWords int, compWords []string) []string { + if compCWords == 1 && len(compWords) == 1 { + return fs.getArguments(compCWords, compWords, L) + } + + if compCWords <= len(compWords) { + prev := compWords[compCWords-1] + if string(prev[0]) == "-" { + fl := fs.fs.Lookup(prev[1:len(prev)]) + v, ok := fs.flags[fl.Name] + if !ok { + return []string{} + } + switch value := v.value.(type) { + case *bool: + if string(compWords[len(compWords)-1][0]) == "-" { + return fs.getFlags() + } + return []string{} + case *string, *float64, *int: + word := compWords[len(compWords)-1] + if compCWords == len(compWords) { + word = "" + } + + table := L.NewTable() + fs.fs.Visit(func(f *flag.Flag) { + table.RawSetString(f.Name, lua.LString(f.Value.String())) + }) + + raw := L.NewTable() + for i, word := range compWords { + if i == 0 { + raw.RawSet(lua.LNumber(0), lua.LString(word)) + continue + } + raw.Append(lua.LString(word)) + } + + stack := L.GetTop() + if err := L.CallByParam(lua.P{ + Fn: v.compFn, + NRet: -1, + Protect: true, + }, lua.LString(word), table, raw); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + stack = L.GetTop() - stack + + if stack == 1 { + res := L.Get(-1) + L.Pop(1) + switch r := res.(type) { + case *lua.LTable: + return toStringSlice(r) + case lua.LString: + s := string(r) + if strings.Index(s, "\n") > 0 { + return strings.Split(s, "\n") + } + return []string{s} + case *lua.LFunction: + return forEachStrings(L, r) + default: + L.RaiseError("unknown type: %T", r) + } + } + + res := []string{} + for i := 1; i <= stack; i++ { + res = append(res, L.Get(-i).String()) + } + L.Pop(stack) + return res + + default: + L.RaiseError("not implemented type: %T", value) + return []string{} + } + } else if string(compWords[len(compWords)-1][0]) == "-" { + // current argument starts with "-" + return fs.getFlags() + } else { // argument + return fs.getArguments(compCWords, compWords, L) + } + } + return []string{} +} + +func (fs *FlagSet) getArguments(compCWords int, compWords []string, L *lua.LState) []string { + err := fs.fs.Parse(compWords[1:len(compWords)]) + if err != nil { + return []string{} + } + nargs := fs.fs.NArg() + if nargs == len(fs.arguments) { + nargs-- + } + word := compWords[len(compWords)-1] + if compCWords == len(compWords) { + word = "" + } + + table := L.NewTable() + fs.fs.Visit(func(f *flag.Flag) { + table.RawSetString(f.Name, lua.LString(f.Value.String())) + }) + + raw := L.NewTable() + for i, word := range compWords { + if i == 0 { + raw.RawSet(lua.LNumber(0), lua.LString(word)) + continue + } + raw.Append(lua.LString(word)) + } + + if nargs >= len(fs.arguments) || nargs < 0 { + return []string{} + } + + // stack is needed to know how the stack grows + stack := L.GetTop() + if err := L.CallByParam(lua.P{ + Fn: fs.arguments[nargs].compFn, + NRet: -1, + Protect: true, + }, lua.LString(word), table, raw); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + stack = L.GetTop() - stack + + if stack == 1 { + res := L.Get(-1) + L.Pop(1) + switch r := res.(type) { + case *lua.LTable: + return toStringSlice(r) + case *lua.LString: + s := string(*r) + if strings.Index(s, "\n") > 0 { + return strings.Split(s, "\n") + } + return []string{s} + case *lua.LFunction: + return forEachStrings(L, r) + default: + L.RaiseError("unknown type: %T", r) + } + } + + res := []string{} + for i := 1; i <= stack; i++ { + res = append(res, L.Get(-i).String()) + } + L.Pop(stack) + return res + +} + +func usage(L *lua.LState) int { + gf := checkFlagSet(L, 1) + L.Push(lua.LString(gf.Usage())) + return 1 +} + +func compgen(L *lua.LState) int { + ud := L.CheckUserData(1) + compCWords := L.CheckInt(2) + compWords := L.CheckTable(3) + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + comp := gf.Compgen(L, compCWords, toStringSlice(compWords)) + + L.Push(toTable(L, comp)) + return 1 +} + +func number(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + value := L.CheckNumber(3) + usage := L.CheckString(4) + cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + f := gf.fs.Float64(name, float64(value), usage) + gf.flags[name] = &flg{ + name: name, + value: f, + usage: usage, + compFn: cf, + } + + L.Push(gf.flags[name].userdata(L)) + return 1 +} + +func numbers(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + usage := L.CheckString(3) + cf := L.OptFunction(4, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + var numbers numberslice + gf.fs.Var(&numbers, name, usage) + gf.flags[name] = &flg{ + name: name, + value: &numbers, + usage: usage, + compFn: cf, + } + + return 0 +} + +func integer(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + value := L.CheckInt(3) + usage := L.CheckString(4) + cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + f := gf.fs.Int(name, int(value), usage) + gf.flags[name] = &flg{ + name: name, + value: f, + usage: usage, + compFn: cf, + } + + return 0 +} + +func integers(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + usage := L.CheckString(3) + cf := L.OptFunction(4, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + var ints intslice + gf.fs.Var(&ints, name, usage) + gf.flags[name] = &flg{ + name: name, + value: &ints, + usage: usage, + compFn: cf, + } + + return 0 +} + +func str(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + value := L.CheckString(3) + usage := L.CheckString(4) + cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + f := gf.fs.String(name, value, usage) + gf.flags[name] = &flg{ + name: name, + value: f, + usage: usage, + compFn: cf, + } + + return 0 +} + +func strs(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + usage := L.CheckString(3) + cf := L.OptFunction(4, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + var strs stringslice + gf.fs.Var(&strs, name, usage) + gf.flags[name] = &flg{ + name: name, + value: &strs, + usage: usage, + compFn: cf, + } + + return 0 +} + +func boolean(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + value := L.CheckBool(3) + usage := L.CheckString(4) + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + f := gf.fs.Bool(name, value, usage) + gf.flags[name] = &flg{ + name: name, + value: f, + usage: usage, + compFn: nil, + } + + return 0 +} + +func stringArgument(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + times := L.CheckAny(3) + usage := L.CheckString(4) + cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + a := &argument{ + name: name, + usage: usage, + compFn: cf, + typ: "string", + } + + parser, err := getParser("string", times) + if err != nil { + L.RaiseError(err.Error()) + } + a.parser = parser + + su, err := getShortUsageFn(times) + if err != nil { + L.RaiseError(err.Error()) + } + a.shortUsage = su + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + udPossitionalArgument := L.NewUserData() + udPossitionalArgument.Value = a + L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName)) + + gf.arguments = append(gf.arguments, a) + + L.Push(udPossitionalArgument) + return 1 +} + +func intArgument(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + times := L.CheckAny(3) + usage := L.CheckString(4) + cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + a := &argument{ + name: name, + usage: usage, + compFn: cf, + typ: "int", + } + + parser, err := getParser("int", times) + if err != nil { + L.RaiseError(err.Error()) + } + a.parser = parser + + su, err := getShortUsageFn(times) + if err != nil { + L.RaiseError(err.Error()) + } + a.shortUsage = su + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + udPossitionalArgument := L.NewUserData() + udPossitionalArgument.Value = a + L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName)) + + gf.arguments = append(gf.arguments, a) + + L.Push(udPossitionalArgument) + return 1 +} + +func numberArgument(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + times := L.CheckAny(3) + usage := L.CheckString(4) + cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + a := &argument{ + name: name, + usage: usage, + compFn: cf, + typ: "number", + } + + parser, err := getParser("number", times) + if err != nil { + L.RaiseError(err.Error()) + } + a.parser = parser + + su, err := getShortUsageFn(times) + if err != nil { + L.RaiseError(err.Error()) + } + a.shortUsage = su + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + udPossitionalArgument := L.NewUserData() + udPossitionalArgument.Value = a + L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName)) + + gf.arguments = append(gf.arguments, a) + + L.Push(udPossitionalArgument) + return 1 +} + +func possitionalInt(L *lua.LState) int { + ud := L.CheckUserData(1) + name := L.CheckString(2) + times := L.CheckAny(3) + usage := L.CheckString(4) + cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int { + L.Push(lua.LString("")) + return 1 + })) + + a := &argument{ + name: name, + usage: usage, + typ: "int", + compFn: cf, + } + + switch t := times.(type) { + case lua.LString: + switch t { + case "+": + a.glob = true + case "*": + a.glob = true + a.optional = true + case "?": + a.times = 1 + a.optional = true + default: + L.RaiseError("nargs should be an integer or one of '?', '*', or '+'") + } + case lua.LNumber: + if int(t) < 1 { + L.RaiseError("nargs should be an integer or one of '?', '*', or '+'") + } + a.times = int(t) + default: + L.RaiseError("nargs should be an integer or one of '?', '*', or '+'") + } + + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + } + + udPossitionalArgument := L.NewUserData() + udPossitionalArgument.Value = a + L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName)) + + gf.arguments = append(gf.arguments, a) + + L.Push(udPossitionalArgument) + return 1 +} + +// Parse the command line parameters +func Parse(L *lua.LState, ud *lua.LUserData, args []string) (*lua.LTable, error) { + gf, ok := ud.Value.(*FlagSet) + if !ok { + L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value) + return nil, ErrUserDataType + } + + gf.fs.SetOutput(ioutil.Discard) + gf.output = ioutil.Discard + err := gf.fs.Parse(args) + if err != nil { + return nil, err + } + + t := L.NewTable() + for f, v := range gf.flags { + switch value := v.value.(type) { + case *float64: + t.RawSetString(f, lua.LNumber(*value)) + case *string: + t.RawSetString(f, lua.LString(*value)) + case *bool: + t.RawSetString(f, lua.LBool(*value)) + case *int: + t.RawSetString(f, lua.LNumber(*value)) + case *intslice: + t.RawSetString(f, value.Table(L)) + case *numberslice: + t.RawSetString(f, value.Table(L)) + case *stringslice: + t.RawSetString(f, value.Table(L)) + default: + L.RaiseError("unknown type: `%T`", v) + } + } + + // nothing defined for possitional arguments, just copy them + if len(gf.arguments) == 0 { + for _, v := range gf.fs.Args() { + t.Append(lua.LString(v)) + } + return t, nil + } + + // TODO: refactor to a function in arguments + args = gf.fs.Args() + for _, arg := range gf.arguments { + args, err = arg.parse(args, L) + if err != nil { + return nil, fmt.Errorf("argument %v: %v", arg.name, err.Error()) + } + t.RawSetString(arg.name, arg.toLValue(L)) + } + + if len(args) > 0 { + L.RaiseError("unknown argument: %v", args) + } + + return t, nil +} + +func parse(L *lua.LState) int { + ud := L.CheckUserData(1) + args := L.CheckTable(2) + + a := toStringSlice(args) + + t, err := Parse(L, ud, a[1:len(a)]) + if err != nil { + L.RaiseError("%v", err) + } + + L.Push(t) + return 1 +} diff --git a/vendor/github.com/otm/gluaflag/helpers.go b/vendor/github.com/otm/gluaflag/helpers.go new file mode 100644 index 0000000..0f9b680 --- /dev/null +++ b/vendor/github.com/otm/gluaflag/helpers.go @@ -0,0 +1,62 @@ +package gluaflag + +import "github.com/yuin/gopher-lua" + +func toStringSlice(t *lua.LTable) []string { + args := make([]string, 0, t.Len()) + if zv := t.RawGet(lua.LNumber(0)); zv.Type() != lua.LTNil { + args = append(args, zv.String()) + } + + t.ForEach(func(k, v lua.LValue) { + if key, ok := k.(lua.LNumber); !ok || int(key) < 1 { + return + } + args = append(args, v.String()) + }) + return args +} + +func toTable(L *lua.LState, s []string) *lua.LTable { + table := L.NewTable() + for _, str := range s { + table.Append(lua.LString(str)) + } + return table +} + +func forEachStrings(L *lua.LState, fn *lua.LFunction) []string { + p := lua.P{ + Fn: fn, + NRet: -1, + Protect: true, + } + res := []string{} + + for L.CallByParam(p) != nil { + switch L.GetTop() { + case 0: + break + case 1: + res = append(res, L.Get(-1).String()) + L.Pop(1) + case 2: + res = append(res, L.Get(-2).String()) + L.Pop(2) + default: + L.RaiseError("Iterator should return 1 or 2 arguments: got %v", L.GetTop()) + } + } + + return res +} + +func checkFlagSet(L *lua.LState, i int) *FlagSet { + ud := L.CheckUserData(i) + if gf, ok := ud.Value.(*FlagSet); ok { + return gf + } + + L.RaiseError("expected flagset userdata, got: `%T`", ud.Value) + return nil +} diff --git a/vendor/github.com/otm/gluaflag/intarg_test.go b/vendor/github.com/otm/gluaflag/intarg_test.go new file mode 100644 index 0000000..33230d2 --- /dev/null +++ b/vendor/github.com/otm/gluaflag/intarg_test.go @@ -0,0 +1,135 @@ +package gluaflag + +import "testing" + +func TestIntArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"test", "-name", "foo", "2"} + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:intArg("title", 1, "Title") + flags = fs:parse(arg) + assert(flags.title == 2, "expected title to be 2") + ` + + doString(src, t) +} + +func TestOptionalIntArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", 2} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:intArg("title", "?", "Title") + flags = fs:parse(arg) + assert(flags.title == 2, "expected flags.title to be '2'") + ` + doString(src, t) +} + +func TestOptionalIntsArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", 2} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:intArg("title", "*", "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 1, "expected flags.title to have length == 1") + assert(flags.title[1] == 2, "expected flags.title[1] to be '2'") + ` + doString(src, t) +} + +func TestIntsArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", 3, 4} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:intArg("title", "+", "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 2, "expected flags.title to have length == 1") + assert(flags.title[1] == 3, "expected flags.title[1] to be '3'") + assert(flags.title[2] == 4, "expected flags.title[2] to be '4', got " .. flags.title[2]) + ` + doString(src, t) +} + +func TestIntsArgToFew(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:intArg("title", "+", "Title") + ok, err = pcall(function() fs:parse(arg) end) + print(err) + ` + expected := ":8: argument title: expected at least one integer" + stdout, _ := doString(src, t) + if stdout != expected { + t.Errorf("expected: `%v`\ngot: `%v`\nsrc: `%v`", expected, stdout, src) + } +} + +func TestNIntsArgument(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "1", "2"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:intArg("title", 2, "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 2, "expected flags.title to have length == 1") + assert(flags.title[1] == 1, "expected flags.title[1] to be '1'") + assert(flags.title[2] == 2, "expected flags.title[2] to be '2', got " .. flags.title[2]) + ` + doString(src, t) +} + +func TestNIntsArgToFew(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "2"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:intArg("title", 2, "Title") + ok, err = pcall(function() fs:parse(arg) end) + print(err) + ` + expected := ":8: argument title: expected 2 integers" + stdout, _ := doString(src, t) + if stdout != expected { + t.Errorf("expected: `%v`\ngot: `%v`\nsrc: `%v`", expected, stdout, src) + } +} + +func TestIntArgWithString(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "bar"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:intArg("title", 1, "Title") + ok, err = pcall(function() fs:parse(arg) end) + print(err) + ` + expected := ":8: argument title: invalid integer value: bar" + stdout, _ := doString(src, t) + if stdout != expected { + t.Errorf("expected: `%v`\ngot: `%v`\nsrc: `%v`", expected, stdout, src) + } +} diff --git a/vendor/github.com/otm/gluaflag/module.go b/vendor/github.com/otm/gluaflag/module.go new file mode 100644 index 0000000..46de30b --- /dev/null +++ b/vendor/github.com/otm/gluaflag/module.go @@ -0,0 +1,36 @@ +package gluaflag + +import ( + "fmt" + + "github.com/yuin/gopher-lua" +) + +const ( + luaFlagSetTypeName = "flagset" + luaFlagTypeName = "flag" +) + +// UserDataTypeError is returned when it is not a flag userdata received +var ErrUserDataType = fmt.Errorf("Expected gluaflag userdata") + +var exports = map[string]lua.LGFunction{ + "new": new, +} + +// Loader is used for preloading the module +func Loader(L *lua.LState) int { + + // register functions to the table + mod := L.SetFuncs(L.NewTable(), exports) + + flagSetMetaTable := L.NewTypeMetatable(luaFlagSetTypeName) + L.SetField(flagSetMetaTable, "__index", L.SetFuncs(L.NewTable(), flagSetFuncs)) + + flagMetaTable := L.NewTypeMetatable(luaFlagTypeName) + L.SetField(flagMetaTable, "__index", L.SetFuncs(L.NewTable(), flagFuncs)) + + // returns the module + L.Push(mod) + return 1 +} diff --git a/vendor/github.com/otm/gluaflag/numberarg_test.go b/vendor/github.com/otm/gluaflag/numberarg_test.go new file mode 100644 index 0000000..158858a --- /dev/null +++ b/vendor/github.com/otm/gluaflag/numberarg_test.go @@ -0,0 +1,136 @@ +package gluaflag + +import "testing" + +func TestNumberArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "2.32"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:numberArg("title", 1, "Title") + flags = fs:parse(arg) + assert(flags.title == 2.32, "expected title to be 2.32") + ` + + doString(src, t) +} + +func TestOptionalNumberArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", 2.45} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:numberArg("title", "?", "Title") + flags = fs:parse(arg) + assert(flags.title == 2.45, "expected flags.title to be '2.45'") + ` + doString(src, t) +} + +func TestOptionalNubersArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", 2.55} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:numberArg("title", "*", "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 1, "expected flags.title to have length == 1") + assert(flags.title[1] == 2.55, "expected flags.title[1] to be '2.55'") + ` + doString(src, t) +} + +func TestNumbersArg(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", 3.33, 4.44} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:numberArg("title", "+", "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 2, "expected flags.title to have length == 1") + assert(flags.title[1] == 3.33, "expected flags.title[1] to be '3.33'") + assert(flags.title[2] == 4.44, "expected flags.title[2] to be '4.44', got " .. flags.title[2]) + ` + doString(src, t) +} + +func TestNumbersArgToFew(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:numberArg("title", "+", "Title") + ok, err = pcall(function() fs:parse(arg) end) + print(err) + ` + expected := ":8: argument title: expected at least one number" + stdout, _ := doString(src, t) + if stdout != expected { + t.Errorf("expected: `%v`\ngot: `%v`\nsrc: `%v`", expected, stdout, src) + } +} + +func TestNNumbersArgument(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "1.11", "2.22"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:numberArg("title", 2, "Title") + flags = fs:parse(arg) + assert(type(flags.title) == "table", "expected flags.title to be a 'table'") + assert(#flags.title == 2, "expected flags.title to have length == 1") + assert(flags.title[1] == 1.11, "expected flags.title[1] to be '1.11'") + assert(flags.title[2] == 2.22, "expected flags.title[2] to be '2.22', got " .. flags.title[2]) + ` + doString(src, t) +} + +func TestNNumbersArgToFew(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "2.22"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:numberArg("title", 2, "Title") + ok, err = pcall(function() fs:parse(arg) end) + print(err) + ` + expected := ":8: argument title: expected 2 numbers" + stdout, _ := doString(src, t) + if stdout != expected { + t.Errorf("expected: `%v`\ngot: `%v`\nsrc: `%v`", expected, stdout, src) + } +} + +func TestNumberArgWithString(t *testing.T) { + src := ` + local flag = require('flag') + arg = {"-name", "foo", "bar"} + arg[0] = "subcommand" + fs = flag.new() + fs:string("name", "foo", "String help string") + fs:numberArg("title", 1, "Title") + ok, err = pcall(function() fs:parse(arg) end) + print(err) + ` + expected := ":8: argument title: invalid number value: bar" + stdout, _ := doString(src, t) + if stdout != expected { + t.Errorf("expected: `%v`\ngot: `%v`\nsrc: `%v`", expected, stdout, src) + } +} diff --git a/vendor/github.com/otm/gluaflag/types.go b/vendor/github.com/otm/gluaflag/types.go new file mode 100644 index 0000000..3eccd82 --- /dev/null +++ b/vendor/github.com/otm/gluaflag/types.go @@ -0,0 +1,80 @@ +package gluaflag + +import ( + "fmt" + "strconv" + + "github.com/yuin/gopher-lua" +) + +type numberslice []float64 + +// String implements the stringer interface +func (i *numberslice) String() string { + return fmt.Sprintf("%v", *i) +} + +// Set implements the flag interface +func (i *numberslice) Set(value string) error { + tmp, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + *i = append(*i, tmp) + return nil +} + +// Table converts the slice to a lua.LTable +func (i *numberslice) Table(L *lua.LState) *lua.LTable { + t := L.NewTable() + for _, v := range *i { + t.Append(lua.LNumber(v)) + } + return t +} + +type intslice []int + +// String implements the stringer interface +func (i *intslice) String() string { + return fmt.Sprintf("%d", *i) +} + +// Set implements the flag interface +func (i *intslice) Set(value string) error { + tmp, err := strconv.Atoi(value) + if err != nil { + return err + } + *i = append(*i, tmp) + return nil +} + +func (i *intslice) Table(L *lua.LState) *lua.LTable { + t := L.NewTable() + for _, v := range *i { + t.Append(lua.LNumber(v)) + } + return t +} + +type stringslice []string + +// String implements the stringer interface +func (s *stringslice) String() string { + return fmt.Sprintf("%v", *s) +} + +// Set implements the flag interface +func (s *stringslice) Set(value string) error { + *s = append(*s, value) + return nil +} + +func (s *stringslice) Table(L *lua.LState) *lua.LTable { + t := L.NewTable() + for _, v := range *s { + t.Append(lua.LString(v)) + } + return t +} diff --git a/vendor/github.com/otm/gluash/LICENSE b/vendor/github.com/otm/gluash/LICENSE new file mode 100644 index 0000000..70785cd --- /dev/null +++ b/vendor/github.com/otm/gluash/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Nils Lagerkvist + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/otm/gluash/README.md b/vendor/github.com/otm/gluash/README.md new file mode 100644 index 0000000..30171fc --- /dev/null +++ b/vendor/github.com/otm/gluash/README.md @@ -0,0 +1,124 @@ +# gluash +glush is a interface to call any program as it were a function. Programs are executed asynchronously to enable streaming of data in pipes. + +# Installation +``` +go get github.com/otm/gluash +``` + +# Usage +``` lua +import "github.com/yuin/gopher-lua" +import "github.com/otm/gluash" + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("sh", gluash.Loader) + + if err := L.DoString(` + + local sh = require("sh") + + sh.echo("hello", "world"):print() + + `); err != nil { + panic(err) + } +} +``` + +# API +In all discussions bellow the imported module will be referred to as `sh`. + +Commands are called just like functions, executed on the sh module. + +```lua +sh.ls("/") +``` + +For commands that have exotic names, names that are reserved words, or to execute absolute or relative paths call the sh module directly. + +```lua +sh("/bin/ls", "/") +``` + +### Multiple Arguments +Commands with multiple arguments have to be invoked with a separate string for each argument. + +```lua +-- this works +sh.ls("-la", "/") + +-- this does not work +sh.ls("-la /") +``` + +### Piping +Piping in sh is done almost like piping in the shell. Just call next command as a method on the previous command. + +``` lua +sh.du("-sb"):sort("-rn"):print() +``` + +If the command has a exotic name, or a reserved word, call the command through `cmd(path, ...args)`. The first argument in `cmd` is the path. + +```lua +sh.du("-sb"):cmd("sort", "-rn"):print() +``` + + +### Waiting for Processes +All commands are executed by default in the background, so one have to explicitly wait for a process to finish. There are several ways to wait for the command to finish. + +* `print()` - write stdout and stderr to stdout. +* `ok()` - aborts execution if the command's exit code is not zero +* `success()` - returns true of the commands exit code is zero +* `exitcode()` - returns the exit code of the command + +### Abort by Default +It is possible to set the module to abort on errors without checking. It can be practical in some occasions, however performance will be degraded. When global exit code checks are done the commands are run in series, even in pipes, and output is saved in memory buffers. + +To enable global exit code settings call the sh module with an table with the key `abort` set to true. + +```lua +sh{abort=true} +``` + +To read current settings in the module call the module with an empty table. +```lua +configuration = sh{} +print("abort:", configuration.abort) +``` + +### Analyzing Output +There are several options to analyze the output of a command. + +#### lines() +An iterator is accessible by calling the method `lines()` on the command. + +```lua +for line in sh.cat("/etc/hosts"):lines() do + print(line) +end +``` + +#### stdout([filename]), stderr([filename]), combinedOutput([filename]) +`stdout()`, `stderr()`, and `combinedOutput()` all returns the output of the command as a string. An optional `filename` can be given to the method, in that case the output is also written to the file. The file will be truncated. + +``` lua +-- print output of command +output = sh.echo("hello world"):combinedOutput("/tmp/output") +print(output) +``` + +In the example above will print `hello world` and it will write it to `/tmp/output` + + +### Glob Expansion +There is no glob expansion done on arguments, however there is a glob functionality in sh. + +```lua +sh.ls(sh.glob("*.go")) +``` diff --git a/vendor/github.com/otm/gluash/glob.go b/vendor/github.com/otm/gluash/glob.go new file mode 100644 index 0000000..e8d7323 --- /dev/null +++ b/vendor/github.com/otm/gluash/glob.go @@ -0,0 +1,81 @@ +package gluash + +import ( + "os" + "path/filepath" + "sort" + "strings" +) + +// Glob files and directories +func Glob(pattern string) (matches []string, err error) { + if !hasMeta(pattern) { + if _, err = os.Lstat(pattern); err != nil { + return nil, nil + } + return []string{pattern}, nil + } + dir, file := filepath.Split(pattern) + switch dir { + case "": + dir = "." + case string(filepath.Separator): + // nothing + default: + dir = dir[0 : len(dir)-1] // chop off trailing separator + } + if !hasMeta(dir) { + return glober(dir, file, nil) + } + var m []string + m, err = Glob(dir) + if err != nil { + return + } + for _, d := range m { + matches, err = glober(d, file, matches) + if err != nil { + return + } + } + return +} + +// glob searches for files matching pattern in the directory dir +// and appends them to matches. If the directory cannot be +// opened, it returns the existing matches. New matches are +// added in lexicographical order. +func glober(dir, pattern string, matches []string) (m []string, e error) { + m = matches + // fi, err := os.Stat(dir) + // if err != nil { + // return + // } + // if !fi.IsDir() { + // return + // } + d, err := os.Open(dir) + if err != nil { + return + } + defer d.Close() + names, _ := d.Readdirnames(-1) + sort.Strings(names) + for _, n := range names { + matched, err := filepath.Match(pattern, n) + if err != nil { + return m, err + } + if matched { + m = append(m, filepath.Join(dir, n)) + } + } + return +} + +// hasMeta reports whether path contains any of the magic characters +// recognized by Match. +func hasMeta(path string) bool { + // TODO(niemeyer): Should other magic characters be added here? + return strings.IndexAny(path, "*?[") >= 0 +} diff --git a/vendor/github.com/otm/gluash/sh.go b/vendor/github.com/otm/gluash/sh.go new file mode 100644 index 0000000..828d71c --- /dev/null +++ b/vendor/github.com/otm/gluash/sh.go @@ -0,0 +1,120 @@ +package gluash + +import "github.com/yuin/gopher-lua" + +var exports = map[string]lua.LGFunction{} +var abort = false +var shouldWait = false +var debug = false + +// Loader is used for preloading a module +func Loader(L *lua.LState) int { + + // register functions to the table + mod := L.SetFuncs(L.NewTable(), exports) + + // set up meta table + mt := L.NewTable() + L.SetField(mt, "__index", L.NewClosure(moduleIndex)) + L.SetField(mt, "__call", L.NewClosure(moduleCall)) + L.SetMetatable(mod, mt) + + shMetaTable := L.NewTypeMetatable(luaShTypeName) + L.SetField(shMetaTable, "__call", L.NewFunction(shCall)) + L.SetField(shMetaTable, "__index", L.NewFunction(shIndex)) + + // returns the module + L.Push(mod) + return 1 +} + +// moduleIndex creates and returns userdata shell command (sh) defined by the +// index. +func moduleIndex(L *lua.LState) int { + index := L.CheckString(2) + + if index == "glob" { + L.Push(L.NewFunction(glob)) + return 1 + } + + cmd := &shellCommand{ + path: index, + } + + L.Push(cmd.UserData(L)) + return 1 +} + +func moduleCall(L *lua.LState) int { + if L.GetTop() == 2 && L.Get(2).Type() == lua.LTTable { + return configure(L) + } + + path := L.CheckString(2) + args := checkStrings(L, 3) + + cmd, err := newShellCommand(path, args...) + checkError(L, err) + + err = cmd.Start(shouldWait) + checkError(L, err) + + L.Push(cmd.UserData(L)) + return 1 +} + +func glob(L *lua.LState) int { + pattern := L.CheckString(1) + + args, err := Glob(pattern) + if err != nil { + L.RaiseError("%v", err) + } + + for _, arg := range args { + L.Push(lua.LString(arg)) + } + + return len(args) +} + +func configure(L *lua.LState) int { + conf := L.CheckTable(2) + + emptyTable := true + conf.ForEach(func(key, value lua.LValue) { + emptyTable = false + switch key.String() { + case "abort": + if v, ok := value.(lua.LBool); ok { + abort = bool(v) + shouldWait = bool(v) + return + } + L.RaiseError("abort: type error: expected `%v`, got `%v`", lua.LTBool, value.Type()) + case "wait": + if v, ok := value.(lua.LBool); ok { + shouldWait = bool(v) + return + } + L.RaiseError("wait: type error: expected `%v`, got `%v`", lua.LTBool, value.Type()) + case "debug": + if v, ok := value.(lua.LBool); ok { + debug = bool(v) + return + } + L.RaiseError("debug: type error: expected `%v`, got `%v`", lua.LTBool, value.Type()) + } + }) + + if emptyTable { + conf.RawSetString("abort", lua.LBool(abort)) + conf.RawSetString("wait", lua.LBool(shouldWait)) + conf.RawSetString("debug", lua.LBool(shouldWait)) + L.Push(conf) + return 1 + } + + return 0 +} diff --git a/vendor/github.com/otm/gluash/sh_test.go b/vendor/github.com/otm/gluash/sh_test.go new file mode 100644 index 0000000..b8b1c1e --- /dev/null +++ b/vendor/github.com/otm/gluash/sh_test.go @@ -0,0 +1,553 @@ +package gluash + +import ( + "bytes" + "io" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/yuin/gopher-lua" +) + +func captureStdout() func() string { + old := os.Stdout // keep backup of the real stdout + r, w, _ := os.Pipe() + os.Stdout = w + + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + return func() string { + w.Close() + os.Stdout = old // restoring the real stdout + return <-outC + } +} + +func captureStderr() func() string { + old := os.Stderr // keep backup of the real Stderr + r, w, _ := os.Pipe() + os.Stderr = w + + outC := make(chan string) + // copy the output in a separate goroutine so printing can't block indefinitely + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + outC <- buf.String() + }() + + return func() string { + w.Close() + os.Stderr = old // restoring the real stdout + return <-outC + } +} + +func doString(src string, t *testing.T) (stdout, stderr string) { + L := lua.NewState() + defer L.Close() + L.PreloadModule("sh", Loader) + + restoreStdout := captureStdout() + restoreStderr := captureStderr() + err := L.DoString(src) + stdout = restoreStdout() + stderr = restoreStderr() + if err != nil { + t.Errorf("unable to run source: %v", err) + } + + if len(stdout) > 0 { + stdout = stdout[0 : len(stdout)-1] + } + + if len(stderr) > 0 { + stderr = stderr[0 : len(stderr)-1] + } + + return stdout, stderr +} + +func TestModuleCall(t *testing.T) { + src := ` + local sh = require('sh') + sh("echo", "foo", "bar"):print() + ` + expected := "foo bar" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestIndexCall(t *testing.T) { + src := ` + local sh = require('sh') + sh.echo("foo", "bar"):print() + ` + expected := "foo bar" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestOkPrintCall(t *testing.T) { + src := ` + local sh = require('sh') + sh.echo("foo", "bar"):ok():print() + ` + expected := "foo bar" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestOkFailPrintCall(t *testing.T) { + src := ` + local sh = require('sh') + function fail() + sh.sh("testdata/quickabort_test.sh"):ok():print() + end + ok, err = pcall(fail) + print(ok) + print(err) + ` + expected := `false +:4: exit status 1 +STDOUT: + +STDERR: +stderr text +` + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestPipe(t *testing.T) { + src := ` + local sh = require('sh') + sh.echo("foo", "bar\n", "biz", "buz"):grep("foo"):print() + ` + expected := "foo bar" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestPipeWithCmd(t *testing.T) { + src := ` + local sh = require('sh') + sh.echo("foo", "bar\n", "biz", "buz"):cmd("grep", "foo"):print() + ` + expected := "foo bar" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestLines(t *testing.T) { + src := ` + local sh = require('sh') + for line in sh.echo("foo bar\nbiz", "buz"):lines() do + print(line) + end + ` + expected := "foo bar\nbiz buz" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestOK(t *testing.T) { + src := ` + local sh = require('sh') + sh.echo("foo"):ok() + print("ok") + ` + expected := "ok" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestNotOK(t *testing.T) { + src := ` + local sh = require('sh') + function fail() + sh.grep("-d"):ok() + end + + ok, err = pcall(fail) + print(ok) + print(err) + ` + expected := `false +:4: exit status 2 +STDOUT: + +STDERR: +grep: option requires an argument -- 'd' +Usage: grep [OPTION]... PATTERN [FILE]... +Try 'grep --help' for more information. +` + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestSuccess(t *testing.T) { + src := ` + local sh = require('sh') + ok = sh.echo("foo"):success() + print(ok) + ` + expected := "true" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: `%v`", expected, got, src) + } +} + +func TestNotSuccess(t *testing.T) { + src := ` + local sh = require('sh') + ok = sh.grep("-d"):success() + + print(ok) + ` + expected := "false" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestExitcode(t *testing.T) { + src := ` + local sh = require('sh') + exitcode = sh.echo("foo"):exitcode() + print(exitcode) + ` + expected := "0" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestNotExitcode(t *testing.T) { + src := ` + local sh = require('sh') + exitcode = sh.grep("-d"):exitcode() + + print(exitcode) + ` + expected := "2" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestStdout(t *testing.T) { + src := ` + local sh = require('sh') + out = sh.echo("foo"):stdout() + print(out) + ` + expected := "foo" + "\n" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestStderr(t *testing.T) { + src := ` + local sh = require('sh') + out = sh("./testdata/stderr.test.sh"):stderr() + print(out) + ` + expected := "foo" + "\n" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestWriteStdoutToFile(t *testing.T) { + src := ` + local sh = require('sh') + tmp = "./remove.me" + out = sh.echo("foo"):stdout(tmp) + print(out) + ` + expected := "foo" + "\n" + file := "./remove.me" + defer os.Remove(file) + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected stdout: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } + dat, err := ioutil.ReadFile(file) + if err != nil { + t.Errorf("unable to read file: `%v`", file) + } + if string(dat) != expected { + t.Errorf("expected file: `%v`, got: `%v`\nsrc: %v", expected, string(dat), src) + } +} + +func TestWriteStderrToFile(t *testing.T) { + src := ` + local sh = require('sh') + tmp = "./remove.me" + out = sh("./testdata/stderr.test.sh"):stderr(tmp) + print(out) + ` + expected := "foo" + "\n" + file := "./remove.me" + defer os.Remove(file) + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected stdout: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } + dat, err := ioutil.ReadFile(file) + if err != nil { + t.Errorf("unable to read file: `%v`", file) + } + if string(dat) != expected { + t.Errorf("expected file: `%v`, got: `%v`\nsrc: %v", expected, string(dat), src) + } +} + +func TestCombindedOutput(t *testing.T) { + src := ` + local sh = require('sh') + out = sh("./testdata/stderr.test.sh"):combinedOutput() + print(out) + ` + expected := "foo" + "\n" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected stdout: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestWriteCombindedOutputToFile(t *testing.T) { + src := ` + local sh = require('sh') + tmp = "./remove.me" + out = sh("./testdata/stderr.test.sh"):combinedOutput(tmp) + print(out) + ` + expected := "foo" + "\n" + file := "./remove.me" + defer os.Remove(file) + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected stdout: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } + dat, err := ioutil.ReadFile(file) + if err != nil { + t.Errorf("unable to read file: `%v`", file) + } + if string(dat) != expected { + t.Errorf("expected file: `%v`, got: `%v`\nsrc: %v", expected, string(dat), src) + } +} + +func TestSetGlobalAbort(t *testing.T) { + src := ` + local sh = require('sh') + + conf = sh{} + print(conf.abort) + + sh{abort=true} + + conf = sh{} + print(conf.abort) + ` + expected := "false\ntrue" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected stdout: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestGlobalAbort(t *testing.T) { + src := ` + local sh = require('sh') + sh{abort=true} + + function fail() + print("this should print") + sh("false"):print() + print("but not this") + end + + function fail2() + print("this should print") + sh("false"):ok() + print("but not this") + end + + function fail3() + print("this should print") + sh("false"):success() + print("but not this") + end + + function fail4() + print("this should print") + sh("false") + print("but not this") + end + + ok, err = pcall(fail) + ok, err = pcall(fail2) + ok, err = pcall(fail3) + ok, err = pcall(fail4) + sh{abort=false} + ` + expected := strings.TrimSpace(strings.Repeat("this should print\n", 4)) + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected stdout: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestSetGlobalWait(t *testing.T) { + src := ` + local sh = require('sh') + + conf = sh{} + print(conf.wait) + + sh{wait=true} + + conf = sh{} + print(conf.wait) + sh{wait=false} + ` + expected := "false\ntrue" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected stdout: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestGlobalWaitWithAbort(t *testing.T) { + src := ` + local sh = require('sh') + sh{wait=true, abort=true} + sh("/bin/sh", "./testdata/abort.test.sh") + print("bar") + ` + expected := `:4: exit status 1 +STDOUT: +stdout text + +STDERR: +stderr text +} +stack traceback: + [G]: in sh + :4: in function 'main chunk' + [G]: ?` + L := lua.NewState() + defer L.Close() + L.PreloadModule("sh", Loader) + + err := L.DoString(src) + + if err.Error() != expected { + t.Errorf( + "expected: `%v`\ngot: `%v`\nsrc: %v", + expected, + err, + src, + ) + } +} + +func TestModulePipeWithAbort(t *testing.T) { + src := ` + local sh = require('sh') + sh{abort=true} + sh("echo", "foo", "bar\n", "biz", "buz\n", "fii foo"):grep("foo"):print() + ` + expected := "foo bar\n fii foo" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestPipeWithAbort(t *testing.T) { + src := ` + local sh = require('sh') + sh{abort=true} + sh.echo("foo", "bar\n", "biz", "buz\n", "fii foo"):grep("foo"):print() + ` + expected := "foo bar\n fii foo" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} + +func TestPipeGlob(t *testing.T) { + src := ` + local sh = require('sh') + sh.echo(sh.glob('*.md')):print() + ` + expected := "README.md" + got, _ := doString(src, t) + + if got != expected { + t.Errorf("expected: `%v`, got: `%v`\nsrc: %v", expected, got, src) + } +} diff --git a/vendor/github.com/otm/gluash/shellcommand.go b/vendor/github.com/otm/gluash/shellcommand.go new file mode 100644 index 0000000..7a0adac --- /dev/null +++ b/vendor/github.com/otm/gluash/shellcommand.go @@ -0,0 +1,474 @@ +package gluash + +import ( + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "sync" + "syscall" + + "github.com/yuin/gopher-lua" +) + +const luaShTypeName = "sh" + +type shellCommand struct { + path string + args []string + command *exec.Cmd + stdout io.ReadCloser + stderr io.ReadCloser + stdin io.ReadCloser + + waitCalled bool + stdoutClosed bool + stderrClosed bool +} + +func newShellCommand(path string, args ...string) (*shellCommand, error) { + cmd := &shellCommand{ + path: path, + } + + err := cmd.Command(path, args...) + if err != nil { + return nil, err + } + return cmd, nil +} + +func (s *shellCommand) Command(path string, args ...string) error { + s.command = exec.Command(path, args...) + + stdout, err := s.command.StdoutPipe() + if err != nil { + return err + } + + stderr, err := s.command.StderrPipe() + if err != nil { + return err + } + + s.stdout = stdout + s.stderr = stderr + + return nil +} + +func (s *shellCommand) Start(wait bool) error { + if !wait { + return s.command.Start() + } + stderr := &bytes.Buffer{} + stdout := &bytes.Buffer{} + + wg := sync.WaitGroup{} + wg.Add(2) + + go func() { + io.Copy(stdout, s.stdout) + wg.Done() + }() + + go func() { + io.Copy(stderr, s.stderr) + wg.Done() + }() + + err := s.command.Start() + if err != nil { + return err + } + if debug { + fmt.Println("Waiting for process") + } + if err = s.command.Wait(); err != nil && !isExitError(err) { + return err + } + + wg.Wait() + + if abort { + status, ok := s.command.ProcessState.Sys().(syscall.WaitStatus) + if ok && status.ExitStatus() != 0 { + return fmt.Errorf( + "exit -- status %v\nSTDOUT:\n%v\nSTDERR:\n%v}", + status.ExitStatus(), + stdout, + stderr, + ) + } + } + + s.stdout = ioutil.NopCloser(stdout) + s.stderr = ioutil.NopCloser(stderr) + return nil +} + +func (s *shellCommand) UserData(L *lua.LState) *lua.LUserData { + ud := L.NewUserData() + ud.Value = s + L.SetMetatable(ud, L.GetTypeMetatable(luaShTypeName)) + return ud +} + +func (s *shellCommand) CloseStdout() { + //s.stdout.Close() + s.stdoutClosed = true +} + +func (s *shellCommand) CloseStderr() { + //s.stderr.Close() + s.stderrClosed = true +} + +func (s *shellCommand) Close(std string) { + switch std { + case "stderr": + s.CloseStderr() + case "stdout": + s.CloseStdout() + default: + log.Fatalf("unable to close stream: %v", std) + } +} + +func (s *shellCommand) IsClosed(std string) bool { + switch std { + case "stderr": + return s.stderrClosed + case "stdout": + return s.stdoutClosed + default: + log.Fatalf("Unknown option: %v", std) + } + return false +} + +// shIndex checks if it is a predefined method or if it should be interprited as +// shell command. +func shIndex(L *lua.LState) int { + index := L.CheckString(2) + + switch index { + case "cmd": + L.Push(L.NewFunction(shPathCmd(L))) + return 1 + case "print": + L.Push(L.NewFunction(shPrint)) + return 1 + case "ok": + L.Push(L.NewFunction(shOk)) + return 1 + case "lines": + L.Push(L.NewFunction(shLines)) + return 1 + case "success": + L.Push(L.NewFunction(shSuccess)) + return 1 + case "exitcode": + L.Push(L.NewFunction(shExitCode)) + return 1 + case "stdout", "stderr", "combinedOutput": + L.Push(L.NewFunction(shOutput(index))) + return 1 + default: + return shCmd(L) + } + +} + +func shCmd(L *lua.LState) int { + shellCmd := checkShellCmd(L) + index := L.CheckString(2) + + cmd := &shellCommand{ + path: index, + stdin: shellCmd.stdout, + } + + L.Push(cmd.UserData(L)) + return 1 +} + +func shPathCmd(L *lua.LState) lua.LGFunction { + shellCmd := checkShellCmd(L) + + return func(L *lua.LState) int { + path := L.CheckString(2) + args := checkStrings(L, 3) + + cmd := &shellCommand{ + path: path, + } + + err := cmd.Command(path, args...) + checkError(L, err) + + cmd.command.Stdin = shellCmd.stdout + + err = cmd.Start(true) + checkError(L, err) + + L.Push(cmd.UserData(L)) + return 1 + } +} + +// shCall executes the shell command and returns it self +func shCall(L *lua.LState) int { + ud := L.CheckUserData(1) + args := checkStrings(L, 2) + shellCmd := checkShellCmd(L) + err := shellCmd.Command(shellCmd.path, args...) + checkError(L, err) + + if shellCmd.stdin != nil { + shellCmd.command.Stdin = shellCmd.stdin + } + + err = shellCmd.Start(shouldWait) + checkError(L, err) + + L.Push(ud) + return 1 +} + +func shOutput(std string) lua.LGFunction { + return func(L *lua.LState) int { + shellCmd := checkShellCmd(L) + file := L.OptString(2, "") + + if shellCmd.waitCalled { + L.RaiseError("Do not call `ok` or `success` before print") + } + + stream := io.MultiReader(shellCmd.stdout, shellCmd.stderr) + if std == "stderr" { + stream = shellCmd.stderr + } + if std == "stdout" { + stream = shellCmd.stdout + } + + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(stream) + if err != nil { + L.RaiseError("Unable to read from `%v` several times", std) + } + + if std == "stderr" || std == "stdout" { + shellCmd.Close(std) + } else { + shellCmd.CloseStderr() + shellCmd.CloseStdout() + } + + if file != "" { + err := ioutil.WriteFile(file, buf.Bytes(), 0644) + checkError(L, err) + } + + out := buf.String() + L.Push(lua.LString(out)) + return 1 + } +} + +func shOk(L *lua.LState) int { + ud := L.CheckUserData(1) + shellCmd := checkShellCmd(L) + + exitcode, err := wait(L, shellCmd) + checkError(L, err) + + if exitcode != 0 { + buf := new(bytes.Buffer) + buf.ReadFrom(shellCmd.stdout) + so := buf.String() + buf.Reset() + buf.ReadFrom(shellCmd.stderr) + se := buf.String() + L.RaiseError( + "exit status %v\nSTDOUT:\n%v\nSTDERR:\n%v", + exitcode, + so, + se, + ) + //L.RaiseError("exit status %v", exitcode) + } + + L.Push(ud) + return 1 +} + +func shSuccess(L *lua.LState) int { + shellCmd := checkShellCmd(L) + + errorCode, err := wait(L, shellCmd) + checkError(L, err) + + L.Push(lua.LBool(errorCode == 0)) + return 1 +} + +func shExitCode(L *lua.LState) int { + shellCmd := checkShellCmd(L) + exitcode, err := wait(L, shellCmd) + checkError(L, err) + L.Push(lua.LNumber(exitcode)) + return 1 +} + +func shLines(L *lua.LState) int { + shellCmd := checkShellCmd(L) + std := L.OptString(2, "stdout") + + if !(std == "stdout" || std == "stderr") { + L.RaiseError("lines: illigal file handle `%v`", std) + } + if shellCmd.IsClosed(std) { + L.RaiseError("Unable to read from `%v` several times", std) + } + + stream := shellCmd.stdout + if std == "stderr" { + stream = shellCmd.stderr + } + + scanner := bufio.NewScanner(stream) + scanner.Split(bufio.ScanLines) + iterator := func(L *lua.LState) int { + if scanner.Scan() { + L.Push(lua.LString(scanner.Text())) + return 1 + } + + shellCmd.Close(std) + checkError(L, scanner.Err()) + return 0 + } + + L.Push(L.NewFunction(iterator)) + return 1 +} + +func shPrint(L *lua.LState) int { + ud := L.CheckUserData(1) + shellCmd := checkShellCmd(L) + if shellCmd.stderrClosed || shellCmd.stdoutClosed { + L.RaiseError("Unable to read from `%v` several times", "stdout/stderr") + } + + if shellCmd.waitCalled { + L.RaiseError("Do not call `ok` or `success` before print") + } + + combo := io.MultiReader(shellCmd.stdout, shellCmd.stderr) + + _, err := io.Copy(os.Stdout, combo) + //fmt.Println("A") + checkError(L, err) + //fmt.Println("B") + _, err = wait(L, shellCmd) + if err != nil && !isExitError(err) { + L.RaiseError("Error while waiting for command to finish: %v", err) + } + + shellCmd.CloseStderr() + shellCmd.CloseStdout() + L.Push(ud) + return 1 +} + +// check if it is a shellCmd userdata as the first parmeter +func checkShellCmd(L *lua.LState) *shellCommand { + ud := L.CheckUserData(1) + shellCmd, ok := ud.Value.(*shellCommand) + if !ok { + L.Error(lua.LString("Expected the user data should be a shell command"), 0) + return nil + } + return shellCmd +} + +// converts all input parameters to strings from the n:th element +func checkStrings(L *lua.LState, n int) []string { + params := L.GetTop() + if n > params { + return []string{} + } + args := make([]string, 0, (params - n + 1)) + for i := n; i <= params; i++ { + p := L.Get(i) + if p.Type() == lua.LTUserData { + continue + } + L.CheckTypes(i, lua.LTString, lua.LTNumber) + args = append(args, p.String()) + } + return args +} + +func checkError(L *lua.LState, err error) { + if err != nil { + L.RaiseError("%v", err) + } +} + +func isExitError(err error) bool { + _, ok := err.(*exec.ExitError) + return ok +} + +func wait(L *lua.LState, shellCmd *shellCommand) (exitcode int, err error) { + + defer func() { + if abort && (err != nil || exitcode != 0) { + L.RaiseError("exit status %v, err: %v", exitcode, err) + } + }() + + if shellCmd.command.ProcessState == nil { + stderr := &bytes.Buffer{} + stdout := &bytes.Buffer{} + + wg := sync.WaitGroup{} + wg.Add(2) + + go func() { + io.Copy(stdout, shellCmd.stdout) + wg.Done() + }() + + go func() { + io.Copy(stderr, shellCmd.stderr) + wg.Done() + }() + + err := shellCmd.command.Wait() + shellCmd.stdout = ioutil.NopCloser(stdout) + shellCmd.stderr = ioutil.NopCloser(stderr) + + if err != nil && !isExitError(err) { + return 0, err + } + } + + if shellCmd.command.ProcessState.Success() { + return 0, nil + } + + if status, ok := shellCmd.command.ProcessState.Sys().(syscall.WaitStatus); ok { + return status.ExitStatus(), nil + } + + err = fmt.Errorf("`%v`: error retreiving exit code", shellCmd.command.Args) + return 0, err +} diff --git a/vendor/github.com/pkg/sftp/.gitignore b/vendor/github.com/pkg/sftp/.gitignore new file mode 100644 index 0000000..e1ec837 --- /dev/null +++ b/vendor/github.com/pkg/sftp/.gitignore @@ -0,0 +1,7 @@ +.*.swo +.*.swp + +server_standalone/server_standalone + +examples/*/id_rsa +examples/*/id_rsa.pub diff --git a/vendor/github.com/pkg/sftp/.travis.yml b/vendor/github.com/pkg/sftp/.travis.yml new file mode 100644 index 0000000..e51cb9d --- /dev/null +++ b/vendor/github.com/pkg/sftp/.travis.yml @@ -0,0 +1,38 @@ +language: go +go_import_path: github.com/pkg/sftp + +# current and previous stable releases, plus tip +# remember to exclude previous and tip for macs below +go: + - 1.8.x + - 1.9.x + - tip + +os: + - linux + - osx + +matrix: + exclude: + - os: osx + go: 1.8.x + - os: osx + go: tip + +sudo: false + +addons: + ssh_known_hosts: + - bitbucket.org + +install: + - go get -t -v ./... + - ssh-keygen -t rsa -q -P "" -f $HOME/.ssh/id_rsa + +script: + - go test -integration -v ./... + - go test -testserver -v ./... + - go test -integration -testserver -v ./... + - go test -race -integration -v ./... + - go test -race -testserver -v ./... + - go test -race -integration -testserver -v ./... diff --git a/vendor/github.com/pkg/sftp/CONTRIBUTORS b/vendor/github.com/pkg/sftp/CONTRIBUTORS new file mode 100644 index 0000000..5c7196a --- /dev/null +++ b/vendor/github.com/pkg/sftp/CONTRIBUTORS @@ -0,0 +1,3 @@ +Dave Cheney +Saulius Gurklys +John Eikenberry diff --git a/vendor/github.com/pkg/sftp/LICENSE b/vendor/github.com/pkg/sftp/LICENSE new file mode 100644 index 0000000..b7b5392 --- /dev/null +++ b/vendor/github.com/pkg/sftp/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) 2013, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/sftp/README.md b/vendor/github.com/pkg/sftp/README.md new file mode 100644 index 0000000..1fb700c --- /dev/null +++ b/vendor/github.com/pkg/sftp/README.md @@ -0,0 +1,44 @@ +sftp +---- + +The `sftp` package provides support for file system operations on remote ssh +servers using the SFTP subsystem. It also implements an SFTP server for serving +files from the filesystem. + +[![UNIX Build Status](https://travis-ci.org/pkg/sftp.svg?branch=master)](https://travis-ci.org/pkg/sftp) [![GoDoc](http://godoc.org/github.com/pkg/sftp?status.svg)](http://godoc.org/github.com/pkg/sftp) + +usage and examples +------------------ + +See [godoc.org/github.com/pkg/sftp](http://godoc.org/github.com/pkg/sftp) for +examples and usage. + +The basic operation of the package mirrors the facilities of the +[os](http://golang.org/pkg/os) package. + +The Walker interface for directory traversal is heavily inspired by Keith +Rarick's [fs](http://godoc.org/github.com/kr/fs) package. + +roadmap +------- + + * There is way too much duplication in the Client methods. If there was an + unmarshal(interface{}) method this would reduce a heap of the duplication. + +contributing +------------ + +We welcome pull requests, bug fixes and issue reports. + +Before proposing a large change, first please discuss your change by raising an +issue. + +For API/code bugs, please include a small, self contained code example to +reproduce the issue. For pull requests, remember test coverage. + +We try to handle issues and pull requests with a 0 open philosophy. That means +we will try to address the submission as soon as possible and will work toward +a resolution. If progress can no longer be made (eg. unreproducible bug) or +stops (eg. unresponsive submitter), we will close the bug. + +Thanks. diff --git a/vendor/github.com/pkg/sftp/attrs.go b/vendor/github.com/pkg/sftp/attrs.go new file mode 100644 index 0000000..2ed8608 --- /dev/null +++ b/vendor/github.com/pkg/sftp/attrs.go @@ -0,0 +1,241 @@ +package sftp + +// ssh_FXP_ATTRS support +// see http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 + +import ( + "os" + "syscall" + "time" +) + +const ( + ssh_FILEXFER_ATTR_SIZE = 0x00000001 + ssh_FILEXFER_ATTR_UIDGID = 0x00000002 + ssh_FILEXFER_ATTR_PERMISSIONS = 0x00000004 + ssh_FILEXFER_ATTR_ACMODTIME = 0x00000008 + ssh_FILEXFER_ATTR_EXTENDED = 0x80000000 +) + +// fileInfo is an artificial type designed to satisfy os.FileInfo. +type fileInfo struct { + name string + size int64 + mode os.FileMode + mtime time.Time + sys interface{} +} + +// Name returns the base name of the file. +func (fi *fileInfo) Name() string { return fi.name } + +// Size returns the length in bytes for regular files; system-dependent for others. +func (fi *fileInfo) Size() int64 { return fi.size } + +// Mode returns file mode bits. +func (fi *fileInfo) Mode() os.FileMode { return fi.mode } + +// ModTime returns the last modification time of the file. +func (fi *fileInfo) ModTime() time.Time { return fi.mtime } + +// IsDir returns true if the file is a directory. +func (fi *fileInfo) IsDir() bool { return fi.Mode().IsDir() } + +func (fi *fileInfo) Sys() interface{} { return fi.sys } + +// FileStat holds the original unmarshalled values from a call to READDIR or *STAT. +// It is exported for the purposes of accessing the raw values via os.FileInfo.Sys() +type FileStat struct { + Size uint64 + Mode uint32 + Mtime uint32 + Atime uint32 + UID uint32 + GID uint32 + Extended []StatExtended +} + +// StatExtended contains additional, extended information for a FileStat. +type StatExtended struct { + ExtType string + ExtData string +} + +func fileInfoFromStat(st *FileStat, name string) os.FileInfo { + fs := &fileInfo{ + name: name, + size: int64(st.Size), + mode: toFileMode(st.Mode), + mtime: time.Unix(int64(st.Mtime), 0), + sys: st, + } + return fs +} + +func fileStatFromInfo(fi os.FileInfo) (uint32, FileStat) { + mtime := fi.ModTime().Unix() + atime := mtime + var flags uint32 = ssh_FILEXFER_ATTR_SIZE | + ssh_FILEXFER_ATTR_PERMISSIONS | + ssh_FILEXFER_ATTR_ACMODTIME + + fileStat := FileStat{ + Size: uint64(fi.Size()), + Mode: fromFileMode(fi.Mode()), + Mtime: uint32(mtime), + Atime: uint32(atime), + } + + // os specific file stat decoding + fileStatFromInfoOs(fi, &flags, &fileStat) + + return flags, fileStat +} + +func unmarshalAttrs(b []byte) (*FileStat, []byte) { + flags, b := unmarshalUint32(b) + return getFileStat(flags, b) +} + +func getFileStat(flags uint32, b []byte) (*FileStat, []byte) { + var fs FileStat + if flags&ssh_FILEXFER_ATTR_SIZE == ssh_FILEXFER_ATTR_SIZE { + fs.Size, b = unmarshalUint64(b) + } + if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID { + fs.UID, b = unmarshalUint32(b) + } + if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID { + fs.GID, b = unmarshalUint32(b) + } + if flags&ssh_FILEXFER_ATTR_PERMISSIONS == ssh_FILEXFER_ATTR_PERMISSIONS { + fs.Mode, b = unmarshalUint32(b) + } + if flags&ssh_FILEXFER_ATTR_ACMODTIME == ssh_FILEXFER_ATTR_ACMODTIME { + fs.Atime, b = unmarshalUint32(b) + fs.Mtime, b = unmarshalUint32(b) + } + if flags&ssh_FILEXFER_ATTR_EXTENDED == ssh_FILEXFER_ATTR_EXTENDED { + var count uint32 + count, b = unmarshalUint32(b) + ext := make([]StatExtended, count, count) + for i := uint32(0); i < count; i++ { + var typ string + var data string + typ, b = unmarshalString(b) + data, b = unmarshalString(b) + ext[i] = StatExtended{typ, data} + } + fs.Extended = ext + } + return &fs, b +} + +func marshalFileInfo(b []byte, fi os.FileInfo) []byte { + // attributes variable struct, and also variable per protocol version + // spec version 3 attributes: + // uint32 flags + // uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + // uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID + // uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID + // uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + // uint32 atime present only if flag SSH_FILEXFER_ACMODTIME + // uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME + // uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + // string extended_type + // string extended_data + // ... more extended data (extended_type - extended_data pairs), + // so that number of pairs equals extended_count + + flags, fileStat := fileStatFromInfo(fi) + + b = marshalUint32(b, flags) + if flags&ssh_FILEXFER_ATTR_SIZE != 0 { + b = marshalUint64(b, fileStat.Size) + } + if flags&ssh_FILEXFER_ATTR_UIDGID != 0 { + b = marshalUint32(b, fileStat.UID) + b = marshalUint32(b, fileStat.GID) + } + if flags&ssh_FILEXFER_ATTR_PERMISSIONS != 0 { + b = marshalUint32(b, fileStat.Mode) + } + if flags&ssh_FILEXFER_ATTR_ACMODTIME != 0 { + b = marshalUint32(b, fileStat.Atime) + b = marshalUint32(b, fileStat.Mtime) + } + + return b +} + +// toFileMode converts sftp filemode bits to the os.FileMode specification +func toFileMode(mode uint32) os.FileMode { + var fm = os.FileMode(mode & 0777) + switch mode & syscall.S_IFMT { + case syscall.S_IFBLK: + fm |= os.ModeDevice + case syscall.S_IFCHR: + fm |= os.ModeDevice | os.ModeCharDevice + case syscall.S_IFDIR: + fm |= os.ModeDir + case syscall.S_IFIFO: + fm |= os.ModeNamedPipe + case syscall.S_IFLNK: + fm |= os.ModeSymlink + case syscall.S_IFREG: + // nothing to do + case syscall.S_IFSOCK: + fm |= os.ModeSocket + } + if mode&syscall.S_ISGID != 0 { + fm |= os.ModeSetgid + } + if mode&syscall.S_ISUID != 0 { + fm |= os.ModeSetuid + } + if mode&syscall.S_ISVTX != 0 { + fm |= os.ModeSticky + } + return fm +} + +// fromFileMode converts from the os.FileMode specification to sftp filemode bits +func fromFileMode(mode os.FileMode) uint32 { + ret := uint32(0) + + if mode&os.ModeDevice != 0 { + if mode&os.ModeCharDevice != 0 { + ret |= syscall.S_IFCHR + } else { + ret |= syscall.S_IFBLK + } + } + if mode&os.ModeDir != 0 { + ret |= syscall.S_IFDIR + } + if mode&os.ModeSymlink != 0 { + ret |= syscall.S_IFLNK + } + if mode&os.ModeNamedPipe != 0 { + ret |= syscall.S_IFIFO + } + if mode&os.ModeSetgid != 0 { + ret |= syscall.S_ISGID + } + if mode&os.ModeSetuid != 0 { + ret |= syscall.S_ISUID + } + if mode&os.ModeSticky != 0 { + ret |= syscall.S_ISVTX + } + if mode&os.ModeSocket != 0 { + ret |= syscall.S_IFSOCK + } + + if mode&os.ModeType == 0 { + ret |= syscall.S_IFREG + } + ret |= uint32(mode & os.ModePerm) + + return ret +} diff --git a/vendor/github.com/pkg/sftp/attrs_stubs.go b/vendor/github.com/pkg/sftp/attrs_stubs.go new file mode 100644 index 0000000..81cf3ea --- /dev/null +++ b/vendor/github.com/pkg/sftp/attrs_stubs.go @@ -0,0 +1,11 @@ +// +build !cgo,!plan9 windows android + +package sftp + +import ( + "os" +) + +func fileStatFromInfoOs(fi os.FileInfo, flags *uint32, fileStat *FileStat) { + // todo +} diff --git a/vendor/github.com/pkg/sftp/attrs_test.go b/vendor/github.com/pkg/sftp/attrs_test.go new file mode 100644 index 0000000..e234649 --- /dev/null +++ b/vendor/github.com/pkg/sftp/attrs_test.go @@ -0,0 +1,45 @@ +package sftp + +import ( + "bytes" + "os" + "reflect" + "testing" + "time" +) + +// ensure that attrs implemenst os.FileInfo +var _ os.FileInfo = new(fileInfo) + +var unmarshalAttrsTests = []struct { + b []byte + want *fileInfo + rest []byte +}{ + {marshal(nil, struct{ Flags uint32 }{}), &fileInfo{mtime: time.Unix(int64(0), 0)}, nil}, + {marshal(nil, struct { + Flags uint32 + Size uint64 + }{ssh_FILEXFER_ATTR_SIZE, 20}), &fileInfo{size: 20, mtime: time.Unix(int64(0), 0)}, nil}, + {marshal(nil, struct { + Flags uint32 + Size uint64 + Permissions uint32 + }{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil}, + {marshal(nil, struct { + Flags uint32 + Size uint64 + UID, GID, Permissions uint32 + }{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 1000, 1000, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil}, +} + +func TestUnmarshalAttrs(t *testing.T) { + for _, tt := range unmarshalAttrsTests { + stat, rest := unmarshalAttrs(tt.b) + got := fileInfoFromStat(stat, "") + tt.want.sys = got.Sys() + if !reflect.DeepEqual(got, tt.want) || !bytes.Equal(tt.rest, rest) { + t.Errorf("unmarshalAttrs(%#v): want %#v, %#v, got: %#v, %#v", tt.b, tt.want, tt.rest, got, rest) + } + } +} diff --git a/vendor/github.com/pkg/sftp/attrs_unix.go b/vendor/github.com/pkg/sftp/attrs_unix.go new file mode 100644 index 0000000..ab6ecde --- /dev/null +++ b/vendor/github.com/pkg/sftp/attrs_unix.go @@ -0,0 +1,17 @@ +// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris +// +build cgo + +package sftp + +import ( + "os" + "syscall" +) + +func fileStatFromInfoOs(fi os.FileInfo, flags *uint32, fileStat *FileStat) { + if statt, ok := fi.Sys().(*syscall.Stat_t); ok { + *flags |= ssh_FILEXFER_ATTR_UIDGID + fileStat.UID = statt.Uid + fileStat.GID = statt.Gid + } +} diff --git a/vendor/github.com/pkg/sftp/client.go b/vendor/github.com/pkg/sftp/client.go new file mode 100644 index 0000000..ea0fc6d --- /dev/null +++ b/vendor/github.com/pkg/sftp/client.go @@ -0,0 +1,1173 @@ +package sftp + +import ( + "bytes" + "encoding/binary" + "io" + "os" + "path" + "sync/atomic" + "time" + + "github.com/kr/fs" + "github.com/pkg/errors" + "golang.org/x/crypto/ssh" +) + +// InternalInconsistency indicates the packets sent and the data queued to be +// written to the file don't match up. It is an unusual error and usually is +// caused by bad behavior server side or connection issues. The error is +// limited in scope to the call where it happened, the client object is still +// OK to use as long as the connection is still open. +var InternalInconsistency = errors.New("internal inconsistency") + +// A ClientOption is a function which applies configuration to a Client. +type ClientOption func(*Client) error + +// This is based on Openssh's max accepted size of 1<<18 - overhead +const maxMaxPacket = (1 << 18) - 1024 + +// MaxPacket sets the maximum size of the payload. The size param must be +// between 32768 (1<<15) and 261120 ((1 << 18) - 1024). The minimum size is +// given by the RFC, while the maximum size is a de-facto standard based on +// Openssh's SFTP server which won't accept packets much larger than that. +// +// Note if you aren't using Openssh's sftp server and get the error "failed to +// send packet header: EOF" when copying a large file try lowering this number. +func MaxPacket(size int) ClientOption { + return func(c *Client) error { + if size < 1<<15 { + return errors.Errorf("size must be greater or equal to 32k") + } + if size > maxMaxPacket { + return errors.Errorf("max packet size is too large (see docs)") + } + c.maxPacket = size + return nil + } +} + +// NewClient creates a new SFTP client on conn, using zero or more option +// functions. +func NewClient(conn *ssh.Client, opts ...ClientOption) (*Client, error) { + s, err := conn.NewSession() + if err != nil { + return nil, err + } + if err := s.RequestSubsystem("sftp"); err != nil { + return nil, err + } + pw, err := s.StdinPipe() + if err != nil { + return nil, err + } + pr, err := s.StdoutPipe() + if err != nil { + return nil, err + } + + return NewClientPipe(pr, pw, opts...) +} + +// NewClientPipe creates a new SFTP client given a Reader and a WriteCloser. +// This can be used for connecting to an SFTP server over TCP/TLS or by using +// the system's ssh client program (e.g. via exec.Command). +func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Client, error) { + sftp := &Client{ + clientConn: clientConn{ + conn: conn{ + Reader: rd, + WriteCloser: wr, + }, + inflight: make(map[uint32]chan<- result), + }, + maxPacket: 1 << 15, + } + if err := sftp.applyOptions(opts...); err != nil { + wr.Close() + return nil, err + } + if err := sftp.sendInit(); err != nil { + wr.Close() + return nil, err + } + if err := sftp.recvVersion(); err != nil { + wr.Close() + return nil, err + } + sftp.clientConn.wg.Add(1) + go sftp.loop() + return sftp, nil +} + +// Client represents an SFTP session on a *ssh.ClientConn SSH connection. +// Multiple Clients can be active on a single SSH connection, and a Client +// may be called concurrently from multiple Goroutines. +// +// Client implements the github.com/kr/fs.FileSystem interface. +type Client struct { + clientConn + + maxPacket int // max packet size read or written. + nextid uint32 +} + +// Create creates the named file mode 0666 (before umask), truncating it if +// it already exists. If successful, methods on the returned File can be +// used for I/O; the associated file descriptor has mode O_RDWR. +func (c *Client) Create(path string) (*File, error) { + return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC)) +} + +const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 + +func (c *Client) sendInit() error { + return c.clientConn.conn.sendPacket(sshFxInitPacket{ + Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 + }) +} + +// returns the next value of c.nextid +func (c *Client) nextID() uint32 { + return atomic.AddUint32(&c.nextid, 1) +} + +func (c *Client) recvVersion() error { + typ, data, err := c.recvPacket() + if err != nil { + return err + } + if typ != ssh_FXP_VERSION { + return &unexpectedPacketErr{ssh_FXP_VERSION, typ} + } + + version, _ := unmarshalUint32(data) + if version != sftpProtocolVersion { + return &unexpectedVersionErr{sftpProtocolVersion, version} + } + + return nil +} + +// Walk returns a new Walker rooted at root. +func (c *Client) Walk(root string) *fs.Walker { + return fs.WalkFS(root, c) +} + +// ReadDir reads the directory named by dirname and returns a list of +// directory entries. +func (c *Client) ReadDir(p string) ([]os.FileInfo, error) { + handle, err := c.opendir(p) + if err != nil { + return nil, err + } + defer c.close(handle) // this has to defer earlier than the lock below + var attrs []os.FileInfo + var done = false + for !done { + id := c.nextID() + typ, data, err1 := c.sendPacket(sshFxpReaddirPacket{ + ID: id, + Handle: handle, + }) + if err1 != nil { + err = err1 + done = true + break + } + switch typ { + case ssh_FXP_NAME: + sid, data := unmarshalUint32(data) + if sid != id { + return nil, &unexpectedIDErr{id, sid} + } + count, data := unmarshalUint32(data) + for i := uint32(0); i < count; i++ { + var filename string + filename, data = unmarshalString(data) + _, data = unmarshalString(data) // discard longname + var attr *FileStat + attr, data = unmarshalAttrs(data) + if filename == "." || filename == ".." { + continue + } + attrs = append(attrs, fileInfoFromStat(attr, path.Base(filename))) + } + case ssh_FXP_STATUS: + // TODO(dfc) scope warning! + err = normaliseError(unmarshalStatus(id, data)) + done = true + default: + return nil, unimplementedPacketErr(typ) + } + } + if err == io.EOF { + err = nil + } + return attrs, err +} + +func (c *Client) opendir(path string) (string, error) { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpOpendirPacket{ + ID: id, + Path: path, + }) + if err != nil { + return "", err + } + switch typ { + case ssh_FXP_HANDLE: + sid, data := unmarshalUint32(data) + if sid != id { + return "", &unexpectedIDErr{id, sid} + } + handle, _ := unmarshalString(data) + return handle, nil + case ssh_FXP_STATUS: + return "", normaliseError(unmarshalStatus(id, data)) + default: + return "", unimplementedPacketErr(typ) + } +} + +// Stat returns a FileInfo structure describing the file specified by path 'p'. +// If 'p' is a symbolic link, the returned FileInfo structure describes the referent file. +func (c *Client) Stat(p string) (os.FileInfo, error) { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpStatPacket{ + ID: id, + Path: p, + }) + if err != nil { + return nil, err + } + switch typ { + case ssh_FXP_ATTRS: + sid, data := unmarshalUint32(data) + if sid != id { + return nil, &unexpectedIDErr{id, sid} + } + attr, _ := unmarshalAttrs(data) + return fileInfoFromStat(attr, path.Base(p)), nil + case ssh_FXP_STATUS: + return nil, normaliseError(unmarshalStatus(id, data)) + default: + return nil, unimplementedPacketErr(typ) + } +} + +// Lstat returns a FileInfo structure describing the file specified by path 'p'. +// If 'p' is a symbolic link, the returned FileInfo structure describes the symbolic link. +func (c *Client) Lstat(p string) (os.FileInfo, error) { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpLstatPacket{ + ID: id, + Path: p, + }) + if err != nil { + return nil, err + } + switch typ { + case ssh_FXP_ATTRS: + sid, data := unmarshalUint32(data) + if sid != id { + return nil, &unexpectedIDErr{id, sid} + } + attr, _ := unmarshalAttrs(data) + return fileInfoFromStat(attr, path.Base(p)), nil + case ssh_FXP_STATUS: + return nil, normaliseError(unmarshalStatus(id, data)) + default: + return nil, unimplementedPacketErr(typ) + } +} + +// ReadLink reads the target of a symbolic link. +func (c *Client) ReadLink(p string) (string, error) { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpReadlinkPacket{ + ID: id, + Path: p, + }) + if err != nil { + return "", err + } + switch typ { + case ssh_FXP_NAME: + sid, data := unmarshalUint32(data) + if sid != id { + return "", &unexpectedIDErr{id, sid} + } + count, data := unmarshalUint32(data) + if count != 1 { + return "", unexpectedCount(1, count) + } + filename, _ := unmarshalString(data) // ignore dummy attributes + return filename, nil + case ssh_FXP_STATUS: + return "", normaliseError(unmarshalStatus(id, data)) + default: + return "", unimplementedPacketErr(typ) + } +} + +// Symlink creates a symbolic link at 'newname', pointing at target 'oldname' +func (c *Client) Symlink(oldname, newname string) error { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpSymlinkPacket{ + ID: id, + Linkpath: newname, + Targetpath: oldname, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return normaliseError(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + +// setstat is a convience wrapper to allow for changing of various parts of the file descriptor. +func (c *Client) setstat(path string, flags uint32, attrs interface{}) error { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpSetstatPacket{ + ID: id, + Path: path, + Flags: flags, + Attrs: attrs, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return normaliseError(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + +// Chtimes changes the access and modification times of the named file. +func (c *Client) Chtimes(path string, atime time.Time, mtime time.Time) error { + type times struct { + Atime uint32 + Mtime uint32 + } + attrs := times{uint32(atime.Unix()), uint32(mtime.Unix())} + return c.setstat(path, ssh_FILEXFER_ATTR_ACMODTIME, attrs) +} + +// Chown changes the user and group owners of the named file. +func (c *Client) Chown(path string, uid, gid int) error { + type owner struct { + UID uint32 + GID uint32 + } + attrs := owner{uint32(uid), uint32(gid)} + return c.setstat(path, ssh_FILEXFER_ATTR_UIDGID, attrs) +} + +// Chmod changes the permissions of the named file. +func (c *Client) Chmod(path string, mode os.FileMode) error { + return c.setstat(path, ssh_FILEXFER_ATTR_PERMISSIONS, uint32(mode)) +} + +// Truncate sets the size of the named file. Although it may be safely assumed +// that if the size is less than its current size it will be truncated to fit, +// the SFTP protocol does not specify what behavior the server should do when setting +// size greater than the current size. +func (c *Client) Truncate(path string, size int64) error { + return c.setstat(path, ssh_FILEXFER_ATTR_SIZE, uint64(size)) +} + +// Open opens the named file for reading. If successful, methods on the +// returned file can be used for reading; the associated file descriptor +// has mode O_RDONLY. +func (c *Client) Open(path string) (*File, error) { + return c.open(path, flags(os.O_RDONLY)) +} + +// OpenFile is the generalized open call; most users will use Open or +// Create instead. It opens the named file with specified flag (O_RDONLY +// etc.). If successful, methods on the returned File can be used for I/O. +func (c *Client) OpenFile(path string, f int) (*File, error) { + return c.open(path, flags(f)) +} + +func (c *Client) open(path string, pflags uint32) (*File, error) { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpOpenPacket{ + ID: id, + Path: path, + Pflags: pflags, + }) + if err != nil { + return nil, err + } + switch typ { + case ssh_FXP_HANDLE: + sid, data := unmarshalUint32(data) + if sid != id { + return nil, &unexpectedIDErr{id, sid} + } + handle, _ := unmarshalString(data) + return &File{c: c, path: path, handle: handle}, nil + case ssh_FXP_STATUS: + return nil, normaliseError(unmarshalStatus(id, data)) + default: + return nil, unimplementedPacketErr(typ) + } +} + +// close closes a handle handle previously returned in the response +// to SSH_FXP_OPEN or SSH_FXP_OPENDIR. The handle becomes invalid +// immediately after this request has been sent. +func (c *Client) close(handle string) error { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpClosePacket{ + ID: id, + Handle: handle, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return normaliseError(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + +func (c *Client) fstat(handle string) (*FileStat, error) { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpFstatPacket{ + ID: id, + Handle: handle, + }) + if err != nil { + return nil, err + } + switch typ { + case ssh_FXP_ATTRS: + sid, data := unmarshalUint32(data) + if sid != id { + return nil, &unexpectedIDErr{id, sid} + } + attr, _ := unmarshalAttrs(data) + return attr, nil + case ssh_FXP_STATUS: + return nil, normaliseError(unmarshalStatus(id, data)) + default: + return nil, unimplementedPacketErr(typ) + } +} + +// StatVFS retrieves VFS statistics from a remote host. +// +// It implements the statvfs@openssh.com SSH_FXP_EXTENDED feature +// from http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/PROTOCOL?txt. +func (c *Client) StatVFS(path string) (*StatVFS, error) { + // send the StatVFS packet to the server + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpStatvfsPacket{ + ID: id, + Path: path, + }) + if err != nil { + return nil, err + } + + switch typ { + // server responded with valid data + case ssh_FXP_EXTENDED_REPLY: + var response StatVFS + err = binary.Read(bytes.NewReader(data), binary.BigEndian, &response) + if err != nil { + return nil, errors.New("can not parse reply") + } + + return &response, nil + + // the resquest failed + case ssh_FXP_STATUS: + return nil, errors.New(fxp(ssh_FXP_STATUS).String()) + + default: + return nil, unimplementedPacketErr(typ) + } +} + +// Join joins any number of path elements into a single path, adding a +// separating slash if necessary. The result is Cleaned; in particular, all +// empty strings are ignored. +func (c *Client) Join(elem ...string) string { return path.Join(elem...) } + +// Remove removes the specified file or directory. An error will be returned if no +// file or directory with the specified path exists, or if the specified directory +// is not empty. +func (c *Client) Remove(path string) error { + err := c.removeFile(path) + if err, ok := err.(*StatusError); ok { + switch err.Code { + // some servers, *cough* osx *cough*, return EPERM, not ENODIR. + // serv-u returns ssh_FX_FILE_IS_A_DIRECTORY + case ssh_FX_PERMISSION_DENIED, ssh_FX_FAILURE, ssh_FX_FILE_IS_A_DIRECTORY: + return c.RemoveDirectory(path) + } + } + return err +} + +func (c *Client) removeFile(path string) error { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpRemovePacket{ + ID: id, + Filename: path, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return normaliseError(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + +// RemoveDirectory removes a directory path. +func (c *Client) RemoveDirectory(path string) error { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpRmdirPacket{ + ID: id, + Path: path, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return normaliseError(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + +// Rename renames a file. +func (c *Client) Rename(oldname, newname string) error { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpRenamePacket{ + ID: id, + Oldpath: oldname, + Newpath: newname, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return normaliseError(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + +// PosixRename renames a file using the posix-rename@openssh.com extension +// which will replace newname if it already exists. +func (c *Client) PosixRename(oldname, newname string) error { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpPosixRenamePacket{ + ID: id, + Oldpath: oldname, + Newpath: newname, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return normaliseError(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + +func (c *Client) realpath(path string) (string, error) { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpRealpathPacket{ + ID: id, + Path: path, + }) + if err != nil { + return "", err + } + switch typ { + case ssh_FXP_NAME: + sid, data := unmarshalUint32(data) + if sid != id { + return "", &unexpectedIDErr{id, sid} + } + count, data := unmarshalUint32(data) + if count != 1 { + return "", unexpectedCount(1, count) + } + filename, _ := unmarshalString(data) // ignore attributes + return filename, nil + case ssh_FXP_STATUS: + return "", normaliseError(unmarshalStatus(id, data)) + default: + return "", unimplementedPacketErr(typ) + } +} + +// Getwd returns the current working directory of the server. Operations +// involving relative paths will be based at this location. +func (c *Client) Getwd() (string, error) { + return c.realpath(".") +} + +// Mkdir creates the specified directory. An error will be returned if a file or +// directory with the specified path already exists, or if the directory's +// parent folder does not exist (the method cannot create complete paths). +func (c *Client) Mkdir(path string) error { + id := c.nextID() + typ, data, err := c.sendPacket(sshFxpMkdirPacket{ + ID: id, + Path: path, + }) + if err != nil { + return err + } + switch typ { + case ssh_FXP_STATUS: + return normaliseError(unmarshalStatus(id, data)) + default: + return unimplementedPacketErr(typ) + } +} + +// applyOptions applies options functions to the Client. +// If an error is encountered, option processing ceases. +func (c *Client) applyOptions(opts ...ClientOption) error { + for _, f := range opts { + if err := f(c); err != nil { + return err + } + } + return nil +} + +// File represents a remote file. +type File struct { + c *Client + path string + handle string + offset uint64 // current offset within remote file +} + +// Close closes the File, rendering it unusable for I/O. It returns an +// error, if any. +func (f *File) Close() error { + return f.c.close(f.handle) +} + +// Name returns the name of the file as presented to Open or Create. +func (f *File) Name() string { + return f.path +} + +const maxConcurrentRequests = 64 + +// Read reads up to len(b) bytes from the File. It returns the number of bytes +// read and an error, if any. Read follows io.Reader semantics, so when Read +// encounters an error or EOF condition after successfully reading n > 0 bytes, +// it returns the number of bytes read. +func (f *File) Read(b []byte) (int, error) { + // Split the read into multiple maxPacket sized concurrent reads + // bounded by maxConcurrentRequests. This allows reads with a suitably + // large buffer to transfer data at a much faster rate due to + // overlapping round trip times. + inFlight := 0 + desiredInFlight := 1 + offset := f.offset + // maxConcurrentRequests buffer to deal with broadcastErr() floods + // also must have a buffer of max value of (desiredInFlight - inFlight) + ch := make(chan result, maxConcurrentRequests) + type inflightRead struct { + b []byte + offset uint64 + } + reqs := map[uint32]inflightRead{} + type offsetErr struct { + offset uint64 + err error + } + var firstErr offsetErr + + sendReq := func(b []byte, offset uint64) { + reqID := f.c.nextID() + f.c.dispatchRequest(ch, sshFxpReadPacket{ + ID: reqID, + Handle: f.handle, + Offset: offset, + Len: uint32(len(b)), + }) + inFlight++ + reqs[reqID] = inflightRead{b: b, offset: offset} + } + + var read int + for len(b) > 0 || inFlight > 0 { + for inFlight < desiredInFlight && len(b) > 0 && firstErr.err == nil { + l := min(len(b), f.c.maxPacket) + rb := b[:l] + sendReq(rb, offset) + offset += uint64(l) + b = b[l:] + } + + if inFlight == 0 { + break + } + res := <-ch + inFlight-- + if res.err != nil { + firstErr = offsetErr{offset: 0, err: res.err} + continue + } + reqID, data := unmarshalUint32(res.data) + req, ok := reqs[reqID] + if !ok { + firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)} + continue + } + delete(reqs, reqID) + switch res.typ { + case ssh_FXP_STATUS: + if firstErr.err == nil || req.offset < firstErr.offset { + firstErr = offsetErr{ + offset: req.offset, + err: normaliseError(unmarshalStatus(reqID, res.data)), + } + } + case ssh_FXP_DATA: + l, data := unmarshalUint32(data) + n := copy(req.b, data[:l]) + read += n + if n < len(req.b) { + sendReq(req.b[l:], req.offset+uint64(l)) + } + if desiredInFlight < maxConcurrentRequests { + desiredInFlight++ + } + default: + firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)} + } + } + // If the error is anything other than EOF, then there + // may be gaps in the data copied to the buffer so it's + // best to return 0 so the caller can't make any + // incorrect assumptions about the state of the buffer. + if firstErr.err != nil && firstErr.err != io.EOF { + read = 0 + } + f.offset += uint64(read) + return read, firstErr.err +} + +// WriteTo writes the file to w. The return value is the number of bytes +// written. Any error encountered during the write is also returned. +func (f *File) WriteTo(w io.Writer) (int64, error) { + fi, err := f.Stat() + if err != nil { + return 0, err + } + inFlight := 0 + desiredInFlight := 1 + offset := f.offset + writeOffset := offset + fileSize := uint64(fi.Size()) + // see comment on same line in Read() above + ch := make(chan result, maxConcurrentRequests) + type inflightRead struct { + b []byte + offset uint64 + } + reqs := map[uint32]inflightRead{} + pendingWrites := map[uint64][]byte{} + type offsetErr struct { + offset uint64 + err error + } + var firstErr offsetErr + + sendReq := func(b []byte, offset uint64) { + reqID := f.c.nextID() + f.c.dispatchRequest(ch, sshFxpReadPacket{ + ID: reqID, + Handle: f.handle, + Offset: offset, + Len: uint32(len(b)), + }) + inFlight++ + reqs[reqID] = inflightRead{b: b, offset: offset} + } + + var copied int64 + for firstErr.err == nil || inFlight > 0 { + if firstErr.err == nil { + for inFlight+len(pendingWrites) < desiredInFlight { + b := make([]byte, f.c.maxPacket) + sendReq(b, offset) + offset += uint64(f.c.maxPacket) + if offset > fileSize { + desiredInFlight = 1 + } + } + } + + if inFlight == 0 { + if firstErr.err == nil && len(pendingWrites) > 0 { + return copied, InternalInconsistency + } + break + } + res := <-ch + inFlight-- + if res.err != nil { + firstErr = offsetErr{offset: 0, err: res.err} + continue + } + reqID, data := unmarshalUint32(res.data) + req, ok := reqs[reqID] + if !ok { + firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)} + continue + } + delete(reqs, reqID) + switch res.typ { + case ssh_FXP_STATUS: + if firstErr.err == nil || req.offset < firstErr.offset { + firstErr = offsetErr{offset: req.offset, err: normaliseError(unmarshalStatus(reqID, res.data))} + } + case ssh_FXP_DATA: + l, data := unmarshalUint32(data) + if req.offset == writeOffset { + nbytes, err := w.Write(data) + copied += int64(nbytes) + if err != nil { + // We will never receive another DATA with offset==writeOffset, so + // the loop will drain inFlight and then exit. + firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: err} + break + } + if nbytes < int(l) { + firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: io.ErrShortWrite} + break + } + switch { + case offset > fileSize: + desiredInFlight = 1 + case desiredInFlight < maxConcurrentRequests: + desiredInFlight++ + } + writeOffset += uint64(nbytes) + for { + pendingData, ok := pendingWrites[writeOffset] + if !ok { + break + } + // Give go a chance to free the memory. + delete(pendingWrites, writeOffset) + nbytes, err := w.Write(pendingData) + // Do not move writeOffset on error so subsequent iterations won't trigger + // any writes. + if err != nil { + firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: err} + break + } + if nbytes < len(pendingData) { + firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: io.ErrShortWrite} + break + } + writeOffset += uint64(nbytes) + } + } else { + // Don't write the data yet because + // this response came in out of order + // and we need to wait for responses + // for earlier segments of the file. + pendingWrites[req.offset] = data + } + default: + firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)} + } + } + if firstErr.err != io.EOF { + return copied, firstErr.err + } + return copied, nil +} + +// Stat returns the FileInfo structure describing file. If there is an +// error. +func (f *File) Stat() (os.FileInfo, error) { + fs, err := f.c.fstat(f.handle) + if err != nil { + return nil, err + } + return fileInfoFromStat(fs, path.Base(f.path)), nil +} + +// Write writes len(b) bytes to the File. It returns the number of bytes +// written and an error, if any. Write returns a non-nil error when n != +// len(b). +func (f *File) Write(b []byte) (int, error) { + // Split the write into multiple maxPacket sized concurrent writes + // bounded by maxConcurrentRequests. This allows writes with a suitably + // large buffer to transfer data at a much faster rate due to + // overlapping round trip times. + inFlight := 0 + desiredInFlight := 1 + offset := f.offset + // see comment on same line in Read() above + ch := make(chan result, maxConcurrentRequests) + var firstErr error + written := len(b) + for len(b) > 0 || inFlight > 0 { + for inFlight < desiredInFlight && len(b) > 0 && firstErr == nil { + l := min(len(b), f.c.maxPacket) + rb := b[:l] + f.c.dispatchRequest(ch, sshFxpWritePacket{ + ID: f.c.nextID(), + Handle: f.handle, + Offset: offset, + Length: uint32(len(rb)), + Data: rb, + }) + inFlight++ + offset += uint64(l) + b = b[l:] + } + + if inFlight == 0 { + break + } + res := <-ch + inFlight-- + if res.err != nil { + firstErr = res.err + continue + } + switch res.typ { + case ssh_FXP_STATUS: + id, _ := unmarshalUint32(res.data) + err := normaliseError(unmarshalStatus(id, res.data)) + if err != nil && firstErr == nil { + firstErr = err + break + } + if desiredInFlight < maxConcurrentRequests { + desiredInFlight++ + } + default: + firstErr = unimplementedPacketErr(res.typ) + } + } + // If error is non-nil, then there may be gaps in the data written to + // the file so it's best to return 0 so the caller can't make any + // incorrect assumptions about the state of the file. + if firstErr != nil { + written = 0 + } + f.offset += uint64(written) + return written, firstErr +} + +// ReadFrom reads data from r until EOF and writes it to the file. The return +// value is the number of bytes read. Any error except io.EOF encountered +// during the read is also returned. +func (f *File) ReadFrom(r io.Reader) (int64, error) { + inFlight := 0 + desiredInFlight := 1 + offset := f.offset + // see comment on same line in Read() above + ch := make(chan result, maxConcurrentRequests) + var firstErr error + read := int64(0) + b := make([]byte, f.c.maxPacket) + for inFlight > 0 || firstErr == nil { + for inFlight < desiredInFlight && firstErr == nil { + n, err := r.Read(b) + if err != nil { + firstErr = err + } + f.c.dispatchRequest(ch, sshFxpWritePacket{ + ID: f.c.nextID(), + Handle: f.handle, + Offset: offset, + Length: uint32(n), + Data: b[:n], + }) + inFlight++ + offset += uint64(n) + read += int64(n) + } + + if inFlight == 0 { + break + } + res := <-ch + inFlight-- + if res.err != nil { + firstErr = res.err + continue + } + switch res.typ { + case ssh_FXP_STATUS: + id, _ := unmarshalUint32(res.data) + err := normaliseError(unmarshalStatus(id, res.data)) + if err != nil && firstErr == nil { + firstErr = err + break + } + if desiredInFlight < maxConcurrentRequests { + desiredInFlight++ + } + default: + firstErr = unimplementedPacketErr(res.typ) + } + } + if firstErr == io.EOF { + firstErr = nil + } + // If error is non-nil, then there may be gaps in the data written to + // the file so it's best to return 0 so the caller can't make any + // incorrect assumptions about the state of the file. + if firstErr != nil { + read = 0 + } + f.offset += uint64(read) + return read, firstErr +} + +// Seek implements io.Seeker by setting the client offset for the next Read or +// Write. It returns the next offset read. Seeking before or after the end of +// the file is undefined. Seeking relative to the end calls Stat. +func (f *File) Seek(offset int64, whence int) (int64, error) { + switch whence { + case os.SEEK_SET: + f.offset = uint64(offset) + case os.SEEK_CUR: + f.offset = uint64(int64(f.offset) + offset) + case os.SEEK_END: + fi, err := f.Stat() + if err != nil { + return int64(f.offset), err + } + f.offset = uint64(fi.Size() + offset) + default: + return int64(f.offset), unimplementedSeekWhence(whence) + } + return int64(f.offset), nil +} + +// Chown changes the uid/gid of the current file. +func (f *File) Chown(uid, gid int) error { + return f.c.Chown(f.path, uid, gid) +} + +// Chmod changes the permissions of the current file. +func (f *File) Chmod(mode os.FileMode) error { + return f.c.Chmod(f.path, mode) +} + +// Truncate sets the size of the current file. Although it may be safely assumed +// that if the size is less than its current size it will be truncated to fit, +// the SFTP protocol does not specify what behavior the server should do when setting +// size greater than the current size. +func (f *File) Truncate(size int64) error { + return f.c.Truncate(f.path, size) +} + +func min(a, b int) int { + if a > b { + return b + } + return a +} + +// normaliseError normalises an error into a more standard form that can be +// checked against stdlib errors like io.EOF or os.ErrNotExist. +func normaliseError(err error) error { + switch err := err.(type) { + case *StatusError: + switch err.Code { + case ssh_FX_EOF: + return io.EOF + case ssh_FX_NO_SUCH_FILE: + return os.ErrNotExist + case ssh_FX_OK: + return nil + default: + return err + } + default: + return err + } +} + +func unmarshalStatus(id uint32, data []byte) error { + sid, data := unmarshalUint32(data) + if sid != id { + return &unexpectedIDErr{id, sid} + } + code, data := unmarshalUint32(data) + msg, data, _ := unmarshalStringSafe(data) + lang, _, _ := unmarshalStringSafe(data) + return &StatusError{ + Code: code, + msg: msg, + lang: lang, + } +} + +func marshalStatus(b []byte, err StatusError) []byte { + b = marshalUint32(b, err.Code) + b = marshalString(b, err.msg) + b = marshalString(b, err.lang) + return b +} + +// flags converts the flags passed to OpenFile into ssh flags. +// Unsupported flags are ignored. +func flags(f int) uint32 { + var out uint32 + switch f & os.O_WRONLY { + case os.O_WRONLY: + out |= ssh_FXF_WRITE + case os.O_RDONLY: + out |= ssh_FXF_READ + } + if f&os.O_RDWR == os.O_RDWR { + out |= ssh_FXF_READ | ssh_FXF_WRITE + } + if f&os.O_APPEND == os.O_APPEND { + out |= ssh_FXF_APPEND + } + if f&os.O_CREATE == os.O_CREATE { + out |= ssh_FXF_CREAT + } + if f&os.O_TRUNC == os.O_TRUNC { + out |= ssh_FXF_TRUNC + } + if f&os.O_EXCL == os.O_EXCL { + out |= ssh_FXF_EXCL + } + return out +} diff --git a/vendor/github.com/pkg/sftp/client_integration_darwin_test.go b/vendor/github.com/pkg/sftp/client_integration_darwin_test.go new file mode 100644 index 0000000..6c72536 --- /dev/null +++ b/vendor/github.com/pkg/sftp/client_integration_darwin_test.go @@ -0,0 +1,42 @@ +package sftp + +import ( + "syscall" + "testing" +) + +const sftpServer = "/usr/libexec/sftp-server" + +func TestClientStatVFS(t *testing.T) { + if *testServerImpl { + t.Skipf("go server does not support FXP_EXTENDED") + } + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + vfs, err := sftp.StatVFS("/") + if err != nil { + t.Fatal(err) + } + + // get system stats + s := syscall.Statfs_t{} + err = syscall.Statfs("/", &s) + if err != nil { + t.Fatal(err) + } + + // check some stats + if vfs.Files != uint64(s.Files) { + t.Fatal("fr_size does not match") + } + + if vfs.Bfree != uint64(s.Bfree) { + t.Fatal("f_bsize does not match") + } + + if vfs.Favail != uint64(s.Ffree) { + t.Fatal("f_namemax does not match") + } +} diff --git a/vendor/github.com/pkg/sftp/client_integration_linux_test.go b/vendor/github.com/pkg/sftp/client_integration_linux_test.go new file mode 100644 index 0000000..1517998 --- /dev/null +++ b/vendor/github.com/pkg/sftp/client_integration_linux_test.go @@ -0,0 +1,42 @@ +package sftp + +import ( + "syscall" + "testing" +) + +const sftpServer = "/usr/lib/openssh/sftp-server" + +func TestClientStatVFS(t *testing.T) { + if *testServerImpl { + t.Skipf("go server does not support FXP_EXTENDED") + } + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + vfs, err := sftp.StatVFS("/") + if err != nil { + t.Fatal(err) + } + + // get system stats + s := syscall.Statfs_t{} + err = syscall.Statfs("/", &s) + if err != nil { + t.Fatal(err) + } + + // check some stats + if vfs.Frsize != uint64(s.Frsize) { + t.Fatalf("fr_size does not match, expected: %v, got: %v", s.Frsize, vfs.Frsize) + } + + if vfs.Bsize != uint64(s.Bsize) { + t.Fatalf("f_bsize does not match, expected: %v, got: %v", s.Bsize, vfs.Bsize) + } + + if vfs.Namemax != uint64(s.Namelen) { + t.Fatalf("f_namemax does not match, expected: %v, got: %v", s.Namelen, vfs.Namemax) + } +} diff --git a/vendor/github.com/pkg/sftp/client_integration_test.go b/vendor/github.com/pkg/sftp/client_integration_test.go new file mode 100644 index 0000000..1411dca --- /dev/null +++ b/vendor/github.com/pkg/sftp/client_integration_test.go @@ -0,0 +1,2188 @@ +package sftp + +// sftp integration tests +// enable with -integration + +import ( + "bytes" + "crypto/sha1" + "encoding" + "errors" + "flag" + "io" + "io/ioutil" + "math/rand" + "net" + "os" + "os/exec" + "os/user" + "path" + "path/filepath" + "reflect" + "regexp" + "strconv" + "testing" + "testing/quick" + "time" + + "sort" + + "github.com/kr/fs" +) + +const ( + READONLY = true + READWRITE = false + NO_DELAY time.Duration = 0 + + debuglevel = "ERROR" // set to "DEBUG" for debugging +) + +var testServerImpl = flag.Bool("testserver", false, "perform integration tests against sftp package server instance") +var testIntegration = flag.Bool("integration", false, "perform integration tests against sftp server process") +var testSftp = flag.String("sftp", sftpServer, "location of the sftp server binary") + +type delayedWrite struct { + t time.Time + b []byte +} + +// delayedWriter wraps a writer and artificially delays the write. This is +// meant to mimic connections with various latencies. Error's returned from the +// underlying writer will panic so this should only be used over reliable +// connections. +type delayedWriter struct { + w io.WriteCloser + ch chan delayedWrite + closed chan struct{} +} + +func newDelayedWriter(w io.WriteCloser, delay time.Duration) io.WriteCloser { + ch := make(chan delayedWrite, 128) + closed := make(chan struct{}) + go func() { + for writeMsg := range ch { + time.Sleep(writeMsg.t.Add(delay).Sub(time.Now())) + n, err := w.Write(writeMsg.b) + if err != nil { + panic("write error") + } + if n < len(writeMsg.b) { + panic("showrt write") + } + } + w.Close() + close(closed) + }() + return delayedWriter{w: w, ch: ch, closed: closed} +} + +func (w delayedWriter) Write(b []byte) (int, error) { + bcopy := make([]byte, len(b)) + copy(bcopy, b) + w.ch <- delayedWrite{t: time.Now(), b: bcopy} + return len(b), nil +} + +func (w delayedWriter) Close() error { + close(w.ch) + <-w.closed + return nil +} + +// netPipe provides a pair of io.ReadWriteClosers connected to each other. +// The functions is identical to os.Pipe with the exception that netPipe +// provides the Read/Close guarantees that os.File derrived pipes do not. +func netPipe(t testing.TB) (io.ReadWriteCloser, io.ReadWriteCloser) { + type result struct { + net.Conn + error + } + + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + + ch := make(chan result, 1) + go func() { + conn, err := l.Accept() + ch <- result{conn, err} + err = l.Close() + if err != nil { + t.Error(err) + } + }() + c1, err := net.Dial("tcp", l.Addr().String()) + if err != nil { + l.Close() // might cause another in the listening goroutine, but too bad + t.Fatal(err) + } + r := <-ch + if r.error != nil { + t.Fatal(err) + } + return c1, r.Conn +} + +func testClientGoSvr(t testing.TB, readonly bool, delay time.Duration) (*Client, *exec.Cmd) { + c1, c2 := netPipe(t) + + options := []ServerOption{WithDebug(os.Stderr)} + if readonly { + options = append(options, ReadOnly()) + } + + server, err := NewServer(c1, options...) + if err != nil { + t.Fatal(err) + } + go server.Serve() + + var ctx io.WriteCloser = c2 + if delay > NO_DELAY { + ctx = newDelayedWriter(ctx, delay) + } + + client, err := NewClientPipe(c2, ctx) + if err != nil { + t.Fatal(err) + } + + // dummy command... + return client, exec.Command("true") +} + +// testClient returns a *Client connected to a localy running sftp-server +// the *exec.Cmd returned must be defer Wait'd. +func testClient(t testing.TB, readonly bool, delay time.Duration) (*Client, *exec.Cmd) { + if !*testIntegration { + t.Skip("skipping intergration test") + } + + if *testServerImpl { + return testClientGoSvr(t, readonly, delay) + } + + cmd := exec.Command(*testSftp, "-e", "-R", "-l", debuglevel) // log to stderr, read only + if !readonly { + cmd = exec.Command(*testSftp, "-e", "-l", debuglevel) // log to stderr + } + cmd.Stderr = os.Stdout + pw, err := cmd.StdinPipe() + if err != nil { + t.Fatal(err) + } + if delay > NO_DELAY { + pw = newDelayedWriter(pw, delay) + } + pr, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + if err := cmd.Start(); err != nil { + t.Skipf("could not start sftp-server process: %v", err) + } + + sftp, err := NewClientPipe(pr, pw) + if err != nil { + t.Fatal(err) + } + + return sftp, cmd +} + +func TestNewClient(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + + if err := sftp.Close(); err != nil { + t.Fatal(err) + } +} + +func TestClientLstat(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + + want, err := os.Lstat(f.Name()) + if err != nil { + t.Fatal(err) + } + + got, err := sftp.Lstat(f.Name()) + if err != nil { + t.Fatal(err) + } + + if !sameFile(want, got) { + t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got) + } +} + +func TestClientLstatIsNotExist(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + os.Remove(f.Name()) + + if _, err := sftp.Lstat(f.Name()); !os.IsNotExist(err) { + t.Errorf("os.IsNotExist(%v) = false, want true", err) + } +} + +func TestClientMkdir(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + dir, err := ioutil.TempDir("", "sftptest") + if err != nil { + t.Fatal(err) + } + sub := path.Join(dir, "mkdir1") + if err := sftp.Mkdir(sub); err != nil { + t.Fatal(err) + } + if _, err := os.Lstat(sub); err != nil { + t.Fatal(err) + } +} + +func TestClientOpen(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + + got, err := sftp.Open(f.Name()) + if err != nil { + t.Fatal(err) + } + if err := got.Close(); err != nil { + t.Fatal(err) + } +} + +func TestClientOpenIsNotExist(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + if _, err := sftp.Open("/doesnt/exist/"); !os.IsNotExist(err) { + t.Errorf("os.IsNotExist(%v) = false, want true", err) + } +} + +func TestClientStatIsNotExist(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + if _, err := sftp.Stat("/doesnt/exist/"); !os.IsNotExist(err) { + t.Errorf("os.IsNotExist(%v) = false, want true", err) + } +} + +const seekBytes = 128 * 1024 + +type seek struct { + offset int64 +} + +func (s seek) Generate(r *rand.Rand, _ int) reflect.Value { + s.offset = int64(r.Int31n(seekBytes)) + return reflect.ValueOf(s) +} + +func (s seek) set(t *testing.T, r io.ReadSeeker) { + if _, err := r.Seek(s.offset, os.SEEK_SET); err != nil { + t.Fatalf("error while seeking with %+v: %v", s, err) + } +} + +func (s seek) current(t *testing.T, r io.ReadSeeker) { + const mid = seekBytes / 2 + + skip := s.offset / 2 + if s.offset > mid { + skip = -skip + } + + if _, err := r.Seek(mid, os.SEEK_SET); err != nil { + t.Fatalf("error seeking to midpoint with %+v: %v", s, err) + } + if _, err := r.Seek(skip, os.SEEK_CUR); err != nil { + t.Fatalf("error seeking from %d with %+v: %v", mid, s, err) + } +} + +func (s seek) end(t *testing.T, r io.ReadSeeker) { + if _, err := r.Seek(-s.offset, os.SEEK_END); err != nil { + t.Fatalf("error seeking from end with %+v: %v", s, err) + } +} + +func TestClientSeek(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + fOS, err := ioutil.TempFile("", "seek-test") + if err != nil { + t.Fatal(err) + } + defer fOS.Close() + + fSFTP, err := sftp.Open(fOS.Name()) + if err != nil { + t.Fatal(err) + } + defer fSFTP.Close() + + writeN(t, fOS, seekBytes) + + if err := quick.CheckEqual( + func(s seek) (string, int64) { s.set(t, fOS); return readHash(t, fOS) }, + func(s seek) (string, int64) { s.set(t, fSFTP); return readHash(t, fSFTP) }, + nil, + ); err != nil { + t.Errorf("Seek: expected equal absolute seeks: %v", err) + } + + if err := quick.CheckEqual( + func(s seek) (string, int64) { s.current(t, fOS); return readHash(t, fOS) }, + func(s seek) (string, int64) { s.current(t, fSFTP); return readHash(t, fSFTP) }, + nil, + ); err != nil { + t.Errorf("Seek: expected equal seeks from middle: %v", err) + } + + if err := quick.CheckEqual( + func(s seek) (string, int64) { s.end(t, fOS); return readHash(t, fOS) }, + func(s seek) (string, int64) { s.end(t, fSFTP); return readHash(t, fSFTP) }, + nil, + ); err != nil { + t.Errorf("Seek: expected equal seeks from end: %v", err) + } +} + +func TestClientCreate(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer f.Close() + defer os.Remove(f.Name()) + + f2, err := sftp.Create(f.Name()) + if err != nil { + t.Fatal(err) + } + defer f2.Close() +} + +func TestClientAppend(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer f.Close() + defer os.Remove(f.Name()) + + f2, err := sftp.OpenFile(f.Name(), os.O_RDWR|os.O_APPEND) + if err != nil { + t.Fatal(err) + } + defer f2.Close() +} + +func TestClientCreateFailed(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer f.Close() + defer os.Remove(f.Name()) + + f2, err := sftp.Create(f.Name()) + if err1, ok := err.(*StatusError); !ok || err1.Code != ssh_FX_PERMISSION_DENIED { + t.Fatalf("Create: want: %v, got %#v", ssh_FX_PERMISSION_DENIED, err) + } + if err == nil { + f2.Close() + } +} + +func TestClientFileName(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + + f2, err := sftp.Open(f.Name()) + if err != nil { + t.Fatal(err) + } + + if got, want := f2.Name(), f.Name(); got != want { + t.Fatalf("Name: got %q want %q", want, got) + } +} + +func TestClientFileStat(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + + want, err := os.Lstat(f.Name()) + if err != nil { + t.Fatal(err) + } + + f2, err := sftp.Open(f.Name()) + if err != nil { + t.Fatal(err) + } + + got, err := f2.Stat() + if err != nil { + t.Fatal(err) + } + + if !sameFile(want, got) { + t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got) + } +} + +func TestClientStatLink(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + + realName := f.Name() + linkName := f.Name() + ".softlink" + + // create a symlink that points at sftptest + if err := os.Symlink(realName, linkName); err != nil { + t.Fatal(err) + } + defer os.Remove(linkName) + + // compare Lstat of links + wantLstat, err := os.Lstat(linkName) + if err != nil { + t.Fatal(err) + } + wantStat, err := os.Stat(linkName) + if err != nil { + t.Fatal(err) + } + + gotLstat, err := sftp.Lstat(linkName) + if err != nil { + t.Fatal(err) + } + gotStat, err := sftp.Stat(linkName) + if err != nil { + t.Fatal(err) + } + + // check that stat is not lstat from os package + if sameFile(wantLstat, wantStat) { + t.Fatalf("Lstat / Stat(%q): both %#v %#v", f.Name(), wantLstat, wantStat) + } + + // compare Lstat of links + if !sameFile(wantLstat, gotLstat) { + t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), wantLstat, gotLstat) + } + + // compare Stat of links + if !sameFile(wantStat, gotStat) { + t.Fatalf("Stat(%q): want %#v, got %#v", f.Name(), wantStat, gotStat) + } + + // check that stat is not lstat + if sameFile(gotLstat, gotStat) { + t.Fatalf("Lstat / Stat(%q): both %#v %#v", f.Name(), gotLstat, gotStat) + } +} + +func TestClientRemove(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + if err := sftp.Remove(f.Name()); err != nil { + t.Fatal(err) + } + if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) { + t.Fatal(err) + } +} + +func TestClientRemoveDir(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + dir, err := ioutil.TempDir("", "sftptest") + if err != nil { + t.Fatal(err) + } + if err := sftp.Remove(dir); err != nil { + t.Fatal(err) + } + if _, err := os.Lstat(dir); !os.IsNotExist(err) { + t.Fatal(err) + } +} + +func TestClientRemoveFailed(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + if err := sftp.Remove(f.Name()); err == nil { + t.Fatalf("Remove(%v): want: permission denied, got %v", f.Name(), err) + } + if _, err := os.Lstat(f.Name()); err != nil { + t.Fatal(err) + } +} + +func TestClientRename(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + f2 := f.Name() + ".new" + if err := sftp.Rename(f.Name(), f2); err != nil { + t.Fatal(err) + } + if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) { + t.Fatal(err) + } + if _, err := os.Lstat(f2); err != nil { + t.Fatal(err) + } +} + +func TestClientPosixRename(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + f2 := f.Name() + ".new" + if err := sftp.PosixRename(f.Name(), f2); err != nil { + t.Fatal(err) + } + if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) { + t.Fatal(err) + } + if _, err := os.Lstat(f2); err != nil { + t.Fatal(err) + } +} + +func TestClientGetwd(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + lwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + rwd, err := sftp.Getwd() + if err != nil { + t.Fatal(err) + } + if !path.IsAbs(rwd) { + t.Fatalf("Getwd: wanted absolute path, got %q", rwd) + } + if lwd != rwd { + t.Fatalf("Getwd: want %q, got %q", lwd, rwd) + } +} + +func TestClientReadLink(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + f2 := f.Name() + ".sym" + if err := os.Symlink(f.Name(), f2); err != nil { + t.Fatal(err) + } + if rl, err := sftp.ReadLink(f2); err != nil { + t.Fatal(err) + } else if rl != f.Name() { + t.Fatalf("unexpected link target: %v, not %v", rl, f.Name()) + } +} + +func TestClientSymlink(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + f2 := f.Name() + ".sym" + if err := sftp.Symlink(f.Name(), f2); err != nil { + t.Fatal(err) + } + if rl, err := sftp.ReadLink(f2); err != nil { + t.Fatal(err) + } else if rl != f.Name() { + t.Fatalf("unexpected link target: %v, not %v", rl, f.Name()) + } +} + +func TestClientChmod(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + if err := sftp.Chmod(f.Name(), 0531); err != nil { + t.Fatal(err) + } + if stat, err := os.Stat(f.Name()); err != nil { + t.Fatal(err) + } else if stat.Mode()&os.ModePerm != 0531 { + t.Fatalf("invalid perm %o\n", stat.Mode()) + } +} + +func TestClientChmodReadonly(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + if err := sftp.Chmod(f.Name(), 0531); err == nil { + t.Fatal("expected error") + } +} + +func TestClientChown(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + usr, err := user.Current() + if err != nil { + t.Fatal(err) + } + chownto, err := user.Lookup("daemon") // seems common-ish... + if err != nil { + t.Fatal(err) + } + + if usr.Uid != "0" { + t.Log("must be root to run chown tests") + t.Skip() + } + toUID, err := strconv.Atoi(chownto.Uid) + if err != nil { + t.Fatal(err) + } + toGID, err := strconv.Atoi(chownto.Gid) + if err != nil { + t.Fatal(err) + } + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + before, err := exec.Command("ls", "-nl", f.Name()).Output() + if err != nil { + t.Fatal(err) + } + if err := sftp.Chown(f.Name(), toUID, toGID); err != nil { + t.Fatal(err) + } + after, err := exec.Command("ls", "-nl", f.Name()).Output() + if err != nil { + t.Fatal(err) + } + + spaceRegex := regexp.MustCompile(`\s+`) + + beforeWords := spaceRegex.Split(string(before), -1) + if beforeWords[2] != "0" { + t.Fatalf("bad previous user? should be root") + } + afterWords := spaceRegex.Split(string(after), -1) + if afterWords[2] != chownto.Uid || afterWords[3] != chownto.Gid { + t.Fatalf("bad chown: %#v", afterWords) + } + t.Logf("before: %v", string(before)) + t.Logf(" after: %v", string(after)) +} + +func TestClientChownReadonly(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + usr, err := user.Current() + if err != nil { + t.Fatal(err) + } + chownto, err := user.Lookup("daemon") // seems common-ish... + if err != nil { + t.Fatal(err) + } + + if usr.Uid != "0" { + t.Log("must be root to run chown tests") + t.Skip() + } + toUID, err := strconv.Atoi(chownto.Uid) + if err != nil { + t.Fatal(err) + } + toGID, err := strconv.Atoi(chownto.Gid) + if err != nil { + t.Fatal(err) + } + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + if err := sftp.Chown(f.Name(), toUID, toGID); err == nil { + t.Fatal("expected error") + } +} + +func TestClientChtimes(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + + atime := time.Date(2013, 2, 23, 13, 24, 35, 0, time.UTC) + mtime := time.Date(1985, 6, 12, 6, 6, 6, 0, time.UTC) + if err := sftp.Chtimes(f.Name(), atime, mtime); err != nil { + t.Fatal(err) + } + if stat, err := os.Stat(f.Name()); err != nil { + t.Fatal(err) + } else if stat.ModTime().Sub(mtime) != 0 { + t.Fatalf("incorrect mtime: %v vs %v", stat.ModTime(), mtime) + } +} + +func TestClientChtimesReadonly(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + + atime := time.Date(2013, 2, 23, 13, 24, 35, 0, time.UTC) + mtime := time.Date(1985, 6, 12, 6, 6, 6, 0, time.UTC) + if err := sftp.Chtimes(f.Name(), atime, mtime); err == nil { + t.Fatal("expected error") + } +} + +func TestClientTruncate(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + fname := f.Name() + + if n, err := f.Write([]byte("hello world")); n != 11 || err != nil { + t.Fatal(err) + } + f.Close() + + if err := sftp.Truncate(fname, 5); err != nil { + t.Fatal(err) + } + if stat, err := os.Stat(fname); err != nil { + t.Fatal(err) + } else if stat.Size() != 5 { + t.Fatalf("unexpected size: %d", stat.Size()) + } +} + +func TestClientTruncateReadonly(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + t.Fatal(err) + } + fname := f.Name() + + if n, err := f.Write([]byte("hello world")); n != 11 || err != nil { + t.Fatal(err) + } + f.Close() + + if err := sftp.Truncate(fname, 5); err == nil { + t.Fatal("expected error") + } + if stat, err := os.Stat(fname); err != nil { + t.Fatal(err) + } else if stat.Size() != 11 { + t.Fatalf("unexpected size: %d", stat.Size()) + } +} + +func sameFile(want, got os.FileInfo) bool { + return want.Name() == got.Name() && + want.Size() == got.Size() +} + +func TestClientReadSimple(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + d, err := ioutil.TempDir("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(d) + + f, err := ioutil.TempFile(d, "read-test") + if err != nil { + t.Fatal(err) + } + fname := f.Name() + f.Write([]byte("hello")) + f.Close() + + f2, err := sftp.Open(fname) + if err != nil { + t.Fatal(err) + } + defer f2.Close() + stuff := make([]byte, 32) + n, err := f2.Read(stuff) + if err != nil && err != io.EOF { + t.Fatalf("err: %v", err) + } + if n != 5 { + t.Fatalf("n should be 5, is %v", n) + } + if string(stuff[0:5]) != "hello" { + t.Fatalf("invalid contents") + } +} + +func TestClientReadDir(t *testing.T) { + sftp1, cmd1 := testClient(t, READONLY, NO_DELAY) + sftp2, cmd2 := testClientGoSvr(t, READONLY, NO_DELAY) + defer cmd1.Wait() + defer cmd2.Wait() + defer sftp1.Close() + defer sftp2.Close() + + dir := "/dev/" + + d, err := os.Open(dir) + if err != nil { + t.Fatal(err) + } + defer d.Close() + osfiles, err := d.Readdir(4096) + if err != nil { + t.Fatal(err) + } + + sftp1Files, err := sftp1.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + sftp2Files, err := sftp2.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + osFilesByName := map[string]os.FileInfo{} + for _, f := range osfiles { + osFilesByName[f.Name()] = f + } + sftp1FilesByName := map[string]os.FileInfo{} + for _, f := range sftp1Files { + sftp1FilesByName[f.Name()] = f + } + sftp2FilesByName := map[string]os.FileInfo{} + for _, f := range sftp2Files { + sftp2FilesByName[f.Name()] = f + } + + if len(osFilesByName) != len(sftp1FilesByName) || len(sftp1FilesByName) != len(sftp2FilesByName) { + t.Fatalf("os gives %v, sftp1 gives %v, sftp2 gives %v", len(osFilesByName), len(sftp1FilesByName), len(sftp2FilesByName)) + } + + for name, osF := range osFilesByName { + sftp1F, ok := sftp1FilesByName[name] + if !ok { + t.Fatalf("%v present in os but not sftp1", name) + } + sftp2F, ok := sftp2FilesByName[name] + if !ok { + t.Fatalf("%v present in os but not sftp2", name) + } + + //t.Logf("%v: %v %v %v", name, osF, sftp1F, sftp2F) + if osF.Size() != sftp1F.Size() || sftp1F.Size() != sftp2F.Size() { + t.Fatalf("size %v %v %v", osF.Size(), sftp1F.Size(), sftp2F.Size()) + } + if osF.IsDir() != sftp1F.IsDir() || sftp1F.IsDir() != sftp2F.IsDir() { + t.Fatalf("isdir %v %v %v", osF.IsDir(), sftp1F.IsDir(), sftp2F.IsDir()) + } + if osF.ModTime().Sub(sftp1F.ModTime()) > time.Second || sftp1F.ModTime() != sftp2F.ModTime() { + t.Fatalf("modtime %v %v %v", osF.ModTime(), sftp1F.ModTime(), sftp2F.ModTime()) + } + if osF.Mode() != sftp1F.Mode() || sftp1F.Mode() != sftp2F.Mode() { + t.Fatalf("mode %x %x %x", osF.Mode(), sftp1F.Mode(), sftp2F.Mode()) + } + } +} + +var clientReadTests = []struct { + n int64 +}{ + {0}, + {1}, + {1000}, + {1024}, + {1025}, + {2048}, + {4096}, + {1 << 12}, + {1 << 13}, + {1 << 14}, + {1 << 15}, + {1 << 16}, + {1 << 17}, + {1 << 18}, + {1 << 19}, + {1 << 20}, +} + +func TestClientRead(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + d, err := ioutil.TempDir("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(d) + + for _, tt := range clientReadTests { + f, err := ioutil.TempFile(d, "read-test") + if err != nil { + t.Fatal(err) + } + defer f.Close() + hash := writeN(t, f, tt.n) + f2, err := sftp.Open(f.Name()) + if err != nil { + t.Fatal(err) + } + defer f2.Close() + hash2, n := readHash(t, f2) + if hash != hash2 || tt.n != n { + t.Errorf("Read: hash: want: %q, got %q, read: want: %v, got %v", hash, hash2, tt.n, n) + } + } +} + +// readHash reads r until EOF returning the number of bytes read +// and the hash of the contents. +func readHash(t *testing.T, r io.Reader) (string, int64) { + h := sha1.New() + tr := io.TeeReader(r, h) + read, err := io.Copy(ioutil.Discard, tr) + if err != nil { + t.Fatal(err) + } + return string(h.Sum(nil)), read +} + +// writeN writes n bytes of random data to w and returns the +// hash of that data. +func writeN(t *testing.T, w io.Writer, n int64) string { + rand, err := os.Open("/dev/urandom") + if err != nil { + t.Fatal(err) + } + defer rand.Close() + + h := sha1.New() + + mw := io.MultiWriter(w, h) + + written, err := io.CopyN(mw, rand, n) + if err != nil { + t.Fatal(err) + } + if written != n { + t.Fatalf("CopyN(%v): wrote: %v", n, written) + } + return string(h.Sum(nil)) +} + +var clientWriteTests = []struct { + n int + total int64 // cumulative file size +}{ + {0, 0}, + {1, 1}, + {0, 1}, + {999, 1000}, + {24, 1024}, + {1023, 2047}, + {2048, 4095}, + {1 << 12, 8191}, + {1 << 13, 16383}, + {1 << 14, 32767}, + {1 << 15, 65535}, + {1 << 16, 131071}, + {1 << 17, 262143}, + {1 << 18, 524287}, + {1 << 19, 1048575}, + {1 << 20, 2097151}, + {1 << 21, 4194303}, +} + +func TestClientWrite(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + d, err := ioutil.TempDir("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(d) + + f := path.Join(d, "writeTest") + w, err := sftp.Create(f) + if err != nil { + t.Fatal(err) + } + defer w.Close() + + for _, tt := range clientWriteTests { + got, err := w.Write(make([]byte, tt.n)) + if err != nil { + t.Fatal(err) + } + if got != tt.n { + t.Errorf("Write(%v): wrote: want: %v, got %v", tt.n, tt.n, got) + } + fi, err := os.Stat(f) + if err != nil { + t.Fatal(err) + } + if total := fi.Size(); total != tt.total { + t.Errorf("Write(%v): size: want: %v, got %v", tt.n, tt.total, total) + } + } +} + +// ReadFrom is basically Write with io.Reader as the arg +func TestClientReadFrom(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + d, err := ioutil.TempDir("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(d) + + f := path.Join(d, "writeTest") + w, err := sftp.Create(f) + if err != nil { + t.Fatal(err) + } + defer w.Close() + + for _, tt := range clientWriteTests { + got, err := w.ReadFrom(bytes.NewReader(make([]byte, tt.n))) + if err != nil { + t.Fatal(err) + } + if got != int64(tt.n) { + t.Errorf("Write(%v): wrote: want: %v, got %v", tt.n, tt.n, got) + } + fi, err := os.Stat(f) + if err != nil { + t.Fatal(err) + } + if total := fi.Size(); total != tt.total { + t.Errorf("Write(%v): size: want: %v, got %v", tt.n, tt.total, total) + } + } +} + +// Issue #145 in github +// Deadlock in ReadFrom when network drops after 1 good packet. +// Deadlock would occur anytime desiredInFlight-inFlight==2 and 2 errors +// occured in a row. The channel to report the errors only had a buffer +// of 1 and 2 would be sent. +var fakeNetErr = errors.New("Fake network issue") + +func TestClientReadFromDeadlock(t *testing.T) { + clientWriteDeadlock(t, 1, func(f *File) { + b := make([]byte, 32768*4) + content := bytes.NewReader(b) + n, err := f.ReadFrom(content) + if n != 0 { + t.Fatal("Write should return 0", n) + } + if err != fakeNetErr { + t.Fatal("Didn't recieve correct error", err) + } + }) +} + +// Write has exact same problem +func TestClientWriteDeadlock(t *testing.T) { + clientWriteDeadlock(t, 1, func(f *File) { + b := make([]byte, 32768*4) + n, err := f.Write(b) + if n != 0 { + t.Fatal("Write should return 0", n) + } + if err != fakeNetErr { + t.Fatal("Didn't recieve correct error", err) + } + }) +} + +// shared body for both previous tests +func clientWriteDeadlock(t *testing.T, N int, badfunc func(*File)) { + if !*testServerImpl { + t.Skipf("skipping without -testserver") + } + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + d, err := ioutil.TempDir("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(d) + + f := path.Join(d, "writeTest") + w, err := sftp.Create(f) + if err != nil { + t.Fatal(err) + } + defer w.Close() + + // Override sendPacket with failing version + // Replicates network error/drop part way through (after 1 good packet) + count := 0 + sendPacketTest := func(w io.Writer, m encoding.BinaryMarshaler) error { + count++ + if count > N { + return fakeNetErr + } + return sendPacket(w, m) + } + sftp.clientConn.conn.sendPacketTest = sendPacketTest + defer func() { + sftp.clientConn.conn.sendPacketTest = nil + }() + + // this locked (before the fix) + badfunc(w) +} + +// Read/WriteTo has this issue as well +func TestClientReadDeadlock(t *testing.T) { + clientReadDeadlock(t, 1, func(f *File) { + b := make([]byte, 32768*4) + n, err := f.Read(b) + if n != 0 { + t.Fatal("Write should return 0", n) + } + if err != fakeNetErr { + t.Fatal("Didn't recieve correct error", err) + } + }) +} + +func TestClientWriteToDeadlock(t *testing.T) { + clientReadDeadlock(t, 2, func(f *File) { + b := make([]byte, 32768*4) + buf := bytes.NewBuffer(b) + n, err := f.WriteTo(buf) + if n != 32768 { + t.Fatal("Write should return 0", n) + } + if err != fakeNetErr { + t.Fatal("Didn't recieve correct error", err) + } + }) +} + +func clientReadDeadlock(t *testing.T, N int, badfunc func(*File)) { + if !*testServerImpl { + t.Skipf("skipping without -testserver") + } + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + d, err := ioutil.TempDir("", "sftptest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(d) + + f := path.Join(d, "writeTest") + w, err := sftp.Create(f) + if err != nil { + t.Fatal(err) + } + // write the data for the read tests + b := make([]byte, 32768*4) + w.Write(b) + defer w.Close() + + // open new copy of file for read tests + r, err := sftp.Open(f) + if err != nil { + t.Fatal(err) + } + defer r.Close() + + // Override sendPacket with failing version + // Replicates network error/drop part way through (after 1 good packet) + count := 0 + sendPacketTest := func(w io.Writer, m encoding.BinaryMarshaler) error { + count++ + if count > N { + return fakeNetErr + } + return sendPacket(w, m) + } + sftp.clientConn.conn.sendPacketTest = sendPacketTest + defer func() { + sftp.clientConn.conn.sendPacketTest = nil + }() + + // this locked (before the fix) + badfunc(r) +} + +// taken from github.com/kr/fs/walk_test.go + +type Node struct { + name string + entries []*Node // nil if the entry is a file + mark int +} + +var tree = &Node{ + "testdata", + []*Node{ + {"a", nil, 0}, + {"b", []*Node{}, 0}, + {"c", nil, 0}, + { + "d", + []*Node{ + {"x", nil, 0}, + {"y", []*Node{}, 0}, + { + "z", + []*Node{ + {"u", nil, 0}, + {"v", nil, 0}, + }, + 0, + }, + }, + 0, + }, + }, + 0, +} + +func walkTree(n *Node, path string, f func(path string, n *Node)) { + f(path, n) + for _, e := range n.entries { + walkTree(e, filepath.Join(path, e.name), f) + } +} + +func makeTree(t *testing.T) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.entries == nil { + fd, err := os.Create(path) + if err != nil { + t.Errorf("makeTree: %v", err) + return + } + fd.Close() + } else { + os.Mkdir(path, 0770) + } + }) +} + +func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } + +func checkMarks(t *testing.T, report bool) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.mark != 1 && report { + t.Errorf("node %s mark = %d; expected 1", path, n.mark) + } + n.mark = 0 + }) +} + +// Assumes that each node name is unique. Good enough for a test. +// If clear is true, any incoming error is cleared before return. The errors +// are always accumulated, though. +func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error { + if err != nil { + *errors = append(*errors, err) + if clear { + return nil + } + return err + } + name := info.Name() + walkTree(tree, tree.name, func(path string, n *Node) { + if n.name == name { + n.mark++ + } + }) + return nil +} + +func TestClientWalk(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + makeTree(t) + errors := make([]error, 0, 10) + clear := true + markFn := func(walker *fs.Walker) error { + for walker.Step() { + err := mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear) + if err != nil { + return err + } + } + return nil + } + // Expect no errors. + err := markFn(sftp.Walk(tree.name)) + if err != nil { + t.Fatalf("no error expected, found: %s", err) + } + if len(errors) != 0 { + t.Fatalf("unexpected errors: %s", errors) + } + checkMarks(t, true) + errors = errors[0:0] + + // Test permission errors. Only possible if we're not root + // and only on some file systems (AFS, FAT). To avoid errors during + // all.bash on those file systems, skip during go test -short. + if os.Getuid() > 0 && !testing.Short() { + // introduce 2 errors: chmod top-level directories to 0 + os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) + os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) + + // 3) capture errors, expect two. + // mark respective subtrees manually + markTree(tree.entries[1]) + markTree(tree.entries[3]) + // correct double-marking of directory itself + tree.entries[1].mark-- + tree.entries[3].mark-- + err := markFn(sftp.Walk(tree.name)) + if err != nil { + t.Fatalf("expected no error return from Walk, got %s", err) + } + if len(errors) != 2 { + t.Errorf("expected 2 errors, got %d: %s", len(errors), errors) + } + // the inaccessible subtrees were marked manually + checkMarks(t, true) + errors = errors[0:0] + + // 4) capture errors, stop after first error. + // mark respective subtrees manually + markTree(tree.entries[1]) + markTree(tree.entries[3]) + // correct double-marking of directory itself + tree.entries[1].mark-- + tree.entries[3].mark-- + clear = false // error will stop processing + err = markFn(sftp.Walk(tree.name)) + if err == nil { + t.Fatalf("expected error return from Walk") + } + if len(errors) != 1 { + t.Errorf("expected 1 error, got %d: %s", len(errors), errors) + } + // the inaccessible subtrees were marked manually + checkMarks(t, false) + errors = errors[0:0] + + // restore permissions + os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770) + os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770) + } + + // cleanup + if err := os.RemoveAll(tree.name); err != nil { + t.Errorf("removeTree: %v", err) + } +} + +type MatchTest struct { + pattern, s string + match bool + err error +} + +var matchTests = []MatchTest{ + {"abc", "abc", true, nil}, + {"*", "abc", true, nil}, + {"*c", "abc", true, nil}, + {"a*", "a", true, nil}, + {"a*", "abc", true, nil}, + {"a*", "ab/c", false, nil}, + {"a*/b", "abc/b", true, nil}, + {"a*/b", "a/c/b", false, nil}, + {"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil}, + {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil}, + {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil}, + {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil}, + {"a*b?c*x", "abxbbxdbxebxczzx", true, nil}, + {"a*b?c*x", "abxbbxdbxebxczzy", false, nil}, + {"ab[c]", "abc", true, nil}, + {"ab[b-d]", "abc", true, nil}, + {"ab[e-g]", "abc", false, nil}, + {"ab[^c]", "abc", false, nil}, + {"ab[^b-d]", "abc", false, nil}, + {"ab[^e-g]", "abc", true, nil}, + {"a\\*b", "a*b", true, nil}, + {"a\\*b", "ab", false, nil}, + {"a?b", "a☺b", true, nil}, + {"a[^a]b", "a☺b", true, nil}, + {"a???b", "a☺b", false, nil}, + {"a[^a][^a][^a]b", "a☺b", false, nil}, + {"[a-ζ]*", "α", true, nil}, + {"*[a-ζ]", "A", false, nil}, + {"a?b", "a/b", false, nil}, + {"a*b", "a/b", false, nil}, + {"[\\]a]", "]", true, nil}, + {"[\\-]", "-", true, nil}, + {"[x\\-]", "x", true, nil}, + {"[x\\-]", "-", true, nil}, + {"[x\\-]", "z", false, nil}, + {"[\\-x]", "x", true, nil}, + {"[\\-x]", "-", true, nil}, + {"[\\-x]", "a", false, nil}, + {"[]a]", "]", false, ErrBadPattern}, + {"[-]", "-", false, ErrBadPattern}, + {"[x-]", "x", false, ErrBadPattern}, + {"[x-]", "-", false, ErrBadPattern}, + {"[x-]", "z", false, ErrBadPattern}, + {"[-x]", "x", false, ErrBadPattern}, + {"[-x]", "-", false, ErrBadPattern}, + {"[-x]", "a", false, ErrBadPattern}, + {"\\", "a", false, ErrBadPattern}, + {"[a-b-c]", "a", false, ErrBadPattern}, + {"[", "a", false, ErrBadPattern}, + {"[^", "a", false, ErrBadPattern}, + {"[^bc", "a", false, ErrBadPattern}, + {"a[", "a", false, nil}, + {"a[", "ab", false, ErrBadPattern}, + {"*x", "xxx", true, nil}, +} + +func errp(e error) string { + if e == nil { + return "" + } + return e.Error() +} + +// contains returns true if vector contains the string s. +func contains(vector []string, s string) bool { + for _, elem := range vector { + if elem == s { + return true + } + } + return false +} + +var globTests = []struct { + pattern, result string +}{ + {"match.go", "match.go"}, + {"mat?h.go", "match.go"}, + {"ma*ch.go", "match.go"}, + {"../*/match.go", "../sftp/match.go"}, +} + +type globTest struct { + pattern string + matches []string +} + +func (test *globTest) buildWant(root string) []string { + var want []string + for _, m := range test.matches { + want = append(want, root+filepath.FromSlash(m)) + } + sort.Strings(want) + return want +} + +func TestMatch(t *testing.T) { + for _, tt := range matchTests { + pattern := tt.pattern + s := tt.s + ok, err := Match(pattern, s) + if ok != tt.match || err != tt.err { + t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err)) + } + } +} + +func TestGlob(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + for _, tt := range globTests { + pattern := tt.pattern + result := tt.result + matches, err := sftp.Glob(pattern) + if err != nil { + t.Errorf("Glob error for %q: %s", pattern, err) + continue + } + if !contains(matches, result) { + t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result) + } + } + for _, pattern := range []string{"no_match", "../*/no_match"} { + matches, err := sftp.Glob(pattern) + if err != nil { + t.Errorf("Glob error for %q: %s", pattern, err) + continue + } + if len(matches) != 0 { + t.Errorf("Glob(%#q) = %#v want []", pattern, matches) + } + } +} + +func TestGlobError(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + _, err := sftp.Glob("[7]") + if err != nil { + t.Error("expected error for bad pattern; got none") + } +} + +func TestGlobUNC(t *testing.T) { + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + // Just make sure this runs without crashing for now. + // See issue 15879. + sftp.Glob(`\\?\C:\*`) +} + +// sftp/issue/42, abrupt server hangup would result in client hangs. +func TestServerRoughDisconnect(t *testing.T) { + if *testServerImpl { + t.Skipf("skipping with -testserver") + } + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := sftp.Open("/dev/zero") + if err != nil { + t.Fatal(err) + } + defer f.Close() + go func() { + time.Sleep(100 * time.Millisecond) + cmd.Process.Kill() + }() + + io.Copy(ioutil.Discard, f) +} + +// sftp/issue/181, abrupt server hangup would result in client hangs. +// due to broadcastErr filling up the request channel +// this reproduces it about 50% of the time +func TestServerRoughDisconnect2(t *testing.T) { + if *testServerImpl { + t.Skipf("skipping with -testserver") + } + sftp, cmd := testClient(t, READONLY, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := sftp.Open("/dev/zero") + if err != nil { + t.Fatal(err) + } + defer f.Close() + b := make([]byte, 32768*100) + go func() { + time.Sleep(1 * time.Millisecond) + cmd.Process.Kill() + }() + for { + _, err = f.Read(b) + if err != nil { + break + } + } +} + +// sftp/issue/26 writing to a read only file caused client to loop. +func TestClientWriteToROFile(t *testing.T) { + sftp, cmd := testClient(t, READWRITE, NO_DELAY) + defer cmd.Wait() + defer sftp.Close() + + f, err := sftp.Open("/dev/zero") + if err != nil { + t.Fatal(err) + } + defer f.Close() + _, err = f.Write([]byte("hello")) + if err == nil { + t.Fatal("expected error, got", err) + } +} + +func benchmarkRead(b *testing.B, bufsize int, delay time.Duration) { + size := 10*1024*1024 + 123 // ~10MiB + + // open sftp client + sftp, cmd := testClient(b, READONLY, delay) + defer cmd.Wait() + // defer sftp.Close() + + buf := make([]byte, bufsize) + + b.ResetTimer() + b.SetBytes(int64(size)) + + for i := 0; i < b.N; i++ { + offset := 0 + + f2, err := sftp.Open("/dev/zero") + if err != nil { + b.Fatal(err) + } + defer f2.Close() + + for offset < size { + n, err := io.ReadFull(f2, buf) + offset += n + if err == io.ErrUnexpectedEOF && offset != size { + b.Fatalf("read too few bytes! want: %d, got: %d", size, n) + } + + if err != nil { + b.Fatal(err) + } + + offset += n + } + } +} + +func BenchmarkRead1k(b *testing.B) { + benchmarkRead(b, 1*1024, NO_DELAY) +} + +func BenchmarkRead16k(b *testing.B) { + benchmarkRead(b, 16*1024, NO_DELAY) +} + +func BenchmarkRead32k(b *testing.B) { + benchmarkRead(b, 32*1024, NO_DELAY) +} + +func BenchmarkRead128k(b *testing.B) { + benchmarkRead(b, 128*1024, NO_DELAY) +} + +func BenchmarkRead512k(b *testing.B) { + benchmarkRead(b, 512*1024, NO_DELAY) +} + +func BenchmarkRead1MiB(b *testing.B) { + benchmarkRead(b, 1024*1024, NO_DELAY) +} + +func BenchmarkRead4MiB(b *testing.B) { + benchmarkRead(b, 4*1024*1024, NO_DELAY) +} + +func BenchmarkRead4MiBDelay10Msec(b *testing.B) { + benchmarkRead(b, 4*1024*1024, 10*time.Millisecond) +} + +func BenchmarkRead4MiBDelay50Msec(b *testing.B) { + benchmarkRead(b, 4*1024*1024, 50*time.Millisecond) +} + +func BenchmarkRead4MiBDelay150Msec(b *testing.B) { + benchmarkRead(b, 4*1024*1024, 150*time.Millisecond) +} + +func benchmarkWrite(b *testing.B, bufsize int, delay time.Duration) { + size := 10*1024*1024 + 123 // ~10MiB + + // open sftp client + sftp, cmd := testClient(b, false, delay) + defer cmd.Wait() + // defer sftp.Close() + + data := make([]byte, size) + + b.ResetTimer() + b.SetBytes(int64(size)) + + for i := 0; i < b.N; i++ { + offset := 0 + + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + b.Fatal(err) + } + defer os.Remove(f.Name()) + + f2, err := sftp.Create(f.Name()) + if err != nil { + b.Fatal(err) + } + defer f2.Close() + + for offset < size { + n, err := f2.Write(data[offset:min(len(data), offset+bufsize)]) + if err != nil { + b.Fatal(err) + } + + if offset+n < size && n != bufsize { + b.Fatalf("wrote too few bytes! want: %d, got: %d", size, n) + } + + offset += n + } + + f2.Close() + + fi, err := os.Stat(f.Name()) + if err != nil { + b.Fatal(err) + } + + if fi.Size() != int64(size) { + b.Fatalf("wrong file size: want %d, got %d", size, fi.Size()) + } + + os.Remove(f.Name()) + } +} + +func BenchmarkWrite1k(b *testing.B) { + benchmarkWrite(b, 1*1024, NO_DELAY) +} + +func BenchmarkWrite16k(b *testing.B) { + benchmarkWrite(b, 16*1024, NO_DELAY) +} + +func BenchmarkWrite32k(b *testing.B) { + benchmarkWrite(b, 32*1024, NO_DELAY) +} + +func BenchmarkWrite128k(b *testing.B) { + benchmarkWrite(b, 128*1024, NO_DELAY) +} + +func BenchmarkWrite512k(b *testing.B) { + benchmarkWrite(b, 512*1024, NO_DELAY) +} + +func BenchmarkWrite1MiB(b *testing.B) { + benchmarkWrite(b, 1024*1024, NO_DELAY) +} + +func BenchmarkWrite4MiB(b *testing.B) { + benchmarkWrite(b, 4*1024*1024, NO_DELAY) +} + +func BenchmarkWrite4MiBDelay10Msec(b *testing.B) { + benchmarkWrite(b, 4*1024*1024, 10*time.Millisecond) +} + +func BenchmarkWrite4MiBDelay50Msec(b *testing.B) { + benchmarkWrite(b, 4*1024*1024, 50*time.Millisecond) +} + +func BenchmarkWrite4MiBDelay150Msec(b *testing.B) { + benchmarkWrite(b, 4*1024*1024, 150*time.Millisecond) +} + +func benchmarkReadFrom(b *testing.B, bufsize int, delay time.Duration) { + size := 10*1024*1024 + 123 // ~10MiB + + // open sftp client + sftp, cmd := testClient(b, false, delay) + defer cmd.Wait() + // defer sftp.Close() + + data := make([]byte, size) + + b.ResetTimer() + b.SetBytes(int64(size)) + + for i := 0; i < b.N; i++ { + f, err := ioutil.TempFile("", "sftptest") + if err != nil { + b.Fatal(err) + } + defer os.Remove(f.Name()) + + f2, err := sftp.Create(f.Name()) + if err != nil { + b.Fatal(err) + } + defer f2.Close() + + f2.ReadFrom(bytes.NewReader(data)) + f2.Close() + + fi, err := os.Stat(f.Name()) + if err != nil { + b.Fatal(err) + } + + if fi.Size() != int64(size) { + b.Fatalf("wrong file size: want %d, got %d", size, fi.Size()) + } + + os.Remove(f.Name()) + } +} + +func BenchmarkReadFrom1k(b *testing.B) { + benchmarkReadFrom(b, 1*1024, NO_DELAY) +} + +func BenchmarkReadFrom16k(b *testing.B) { + benchmarkReadFrom(b, 16*1024, NO_DELAY) +} + +func BenchmarkReadFrom32k(b *testing.B) { + benchmarkReadFrom(b, 32*1024, NO_DELAY) +} + +func BenchmarkReadFrom128k(b *testing.B) { + benchmarkReadFrom(b, 128*1024, NO_DELAY) +} + +func BenchmarkReadFrom512k(b *testing.B) { + benchmarkReadFrom(b, 512*1024, NO_DELAY) +} + +func BenchmarkReadFrom1MiB(b *testing.B) { + benchmarkReadFrom(b, 1024*1024, NO_DELAY) +} + +func BenchmarkReadFrom4MiB(b *testing.B) { + benchmarkReadFrom(b, 4*1024*1024, NO_DELAY) +} + +func BenchmarkReadFrom4MiBDelay10Msec(b *testing.B) { + benchmarkReadFrom(b, 4*1024*1024, 10*time.Millisecond) +} + +func BenchmarkReadFrom4MiBDelay50Msec(b *testing.B) { + benchmarkReadFrom(b, 4*1024*1024, 50*time.Millisecond) +} + +func BenchmarkReadFrom4MiBDelay150Msec(b *testing.B) { + benchmarkReadFrom(b, 4*1024*1024, 150*time.Millisecond) +} + +func benchmarkCopyDown(b *testing.B, fileSize int64, delay time.Duration) { + // Create a temp file and fill it with zero's. + src, err := ioutil.TempFile("", "sftptest") + if err != nil { + b.Fatal(err) + } + defer src.Close() + srcFilename := src.Name() + defer os.Remove(srcFilename) + zero, err := os.Open("/dev/zero") + if err != nil { + b.Fatal(err) + } + n, err := io.Copy(src, io.LimitReader(zero, fileSize)) + if err != nil { + b.Fatal(err) + } + if n < fileSize { + b.Fatal("short copy") + } + zero.Close() + src.Close() + + sftp, cmd := testClient(b, READONLY, delay) + defer cmd.Wait() + // defer sftp.Close() + b.ResetTimer() + b.SetBytes(fileSize) + + for i := 0; i < b.N; i++ { + dst, err := ioutil.TempFile("", "sftptest") + if err != nil { + b.Fatal(err) + } + defer os.Remove(dst.Name()) + + src, err := sftp.Open(srcFilename) + if err != nil { + b.Fatal(err) + } + defer src.Close() + n, err := io.Copy(dst, src) + if err != nil { + b.Fatalf("copy error: %v", err) + } + if n < fileSize { + b.Fatal("unable to copy all bytes") + } + dst.Close() + fi, err := os.Stat(dst.Name()) + if err != nil { + b.Fatal(err) + } + + if fi.Size() != fileSize { + b.Fatalf("wrong file size: want %d, got %d", fileSize, fi.Size()) + } + os.Remove(dst.Name()) + } +} + +func BenchmarkCopyDown10MiBDelay10Msec(b *testing.B) { + benchmarkCopyDown(b, 10*1024*1024, 10*time.Millisecond) +} + +func BenchmarkCopyDown10MiBDelay50Msec(b *testing.B) { + benchmarkCopyDown(b, 10*1024*1024, 50*time.Millisecond) +} + +func BenchmarkCopyDown10MiBDelay150Msec(b *testing.B) { + benchmarkCopyDown(b, 10*1024*1024, 150*time.Millisecond) +} + +func benchmarkCopyUp(b *testing.B, fileSize int64, delay time.Duration) { + // Create a temp file and fill it with zero's. + src, err := ioutil.TempFile("", "sftptest") + if err != nil { + b.Fatal(err) + } + defer src.Close() + srcFilename := src.Name() + defer os.Remove(srcFilename) + zero, err := os.Open("/dev/zero") + if err != nil { + b.Fatal(err) + } + n, err := io.Copy(src, io.LimitReader(zero, fileSize)) + if err != nil { + b.Fatal(err) + } + if n < fileSize { + b.Fatal("short copy") + } + zero.Close() + src.Close() + + sftp, cmd := testClient(b, false, delay) + defer cmd.Wait() + // defer sftp.Close() + + b.ResetTimer() + b.SetBytes(fileSize) + + for i := 0; i < b.N; i++ { + tmp, err := ioutil.TempFile("", "sftptest") + if err != nil { + b.Fatal(err) + } + tmp.Close() + defer os.Remove(tmp.Name()) + + dst, err := sftp.Create(tmp.Name()) + if err != nil { + b.Fatal(err) + } + defer dst.Close() + src, err := os.Open(srcFilename) + if err != nil { + b.Fatal(err) + } + defer src.Close() + n, err := io.Copy(dst, src) + if err != nil { + b.Fatalf("copy error: %v", err) + } + if n < fileSize { + b.Fatal("unable to copy all bytes") + } + + fi, err := os.Stat(tmp.Name()) + if err != nil { + b.Fatal(err) + } + + if fi.Size() != fileSize { + b.Fatalf("wrong file size: want %d, got %d", fileSize, fi.Size()) + } + os.Remove(tmp.Name()) + } +} + +func BenchmarkCopyUp10MiBDelay10Msec(b *testing.B) { + benchmarkCopyUp(b, 10*1024*1024, 10*time.Millisecond) +} + +func BenchmarkCopyUp10MiBDelay50Msec(b *testing.B) { + benchmarkCopyUp(b, 10*1024*1024, 50*time.Millisecond) +} + +func BenchmarkCopyUp10MiBDelay150Msec(b *testing.B) { + benchmarkCopyUp(b, 10*1024*1024, 150*time.Millisecond) +} diff --git a/vendor/github.com/pkg/sftp/client_test.go b/vendor/github.com/pkg/sftp/client_test.go new file mode 100644 index 0000000..0c9994c --- /dev/null +++ b/vendor/github.com/pkg/sftp/client_test.go @@ -0,0 +1,147 @@ +package sftp + +import ( + "errors" + "io" + "os" + "reflect" + "testing" + + "github.com/kr/fs" +) + +// assert that *Client implements fs.FileSystem +var _ fs.FileSystem = new(Client) + +// assert that *File implements io.ReadWriteCloser +var _ io.ReadWriteCloser = new(File) + +func TestNormaliseError(t *testing.T) { + var ( + ok = &StatusError{Code: ssh_FX_OK} + eof = &StatusError{Code: ssh_FX_EOF} + fail = &StatusError{Code: ssh_FX_FAILURE} + noSuchFile = &StatusError{Code: ssh_FX_NO_SUCH_FILE} + foo = errors.New("foo") + ) + + var tests = []struct { + desc string + err error + want error + }{ + { + desc: "nil error", + }, + { + desc: "not *StatusError", + err: foo, + want: foo, + }, + { + desc: "*StatusError with ssh_FX_EOF", + err: eof, + want: io.EOF, + }, + { + desc: "*StatusError with ssh_FX_NO_SUCH_FILE", + err: noSuchFile, + want: os.ErrNotExist, + }, + { + desc: "*StatusError with ssh_FX_OK", + err: ok, + }, + { + desc: "*StatusError with ssh_FX_FAILURE", + err: fail, + want: fail, + }, + } + + for _, tt := range tests { + got := normaliseError(tt.err) + if got != tt.want { + t.Errorf("normaliseError(%#v), test %q\n- want: %#v\n- got: %#v", + tt.err, tt.desc, tt.want, got) + } + } +} + +var flagsTests = []struct { + flags int + want uint32 +}{ + {os.O_RDONLY, ssh_FXF_READ}, + {os.O_WRONLY, ssh_FXF_WRITE}, + {os.O_RDWR, ssh_FXF_READ | ssh_FXF_WRITE}, + {os.O_RDWR | os.O_CREATE | os.O_TRUNC, ssh_FXF_READ | ssh_FXF_WRITE | ssh_FXF_CREAT | ssh_FXF_TRUNC}, + {os.O_WRONLY | os.O_APPEND, ssh_FXF_WRITE | ssh_FXF_APPEND}, +} + +func TestFlags(t *testing.T) { + for i, tt := range flagsTests { + got := flags(tt.flags) + if got != tt.want { + t.Errorf("test %v: flags(%x): want: %x, got: %x", i, tt.flags, tt.want, got) + } + } +} + +func TestUnmarshalStatus(t *testing.T) { + requestID := uint32(1) + + id := marshalUint32([]byte{}, requestID) + idCode := marshalUint32(id, ssh_FX_FAILURE) + idCodeMsg := marshalString(idCode, "err msg") + idCodeMsgLang := marshalString(idCodeMsg, "lang tag") + + var tests = []struct { + desc string + reqID uint32 + status []byte + want error + }{ + { + desc: "well-formed status", + reqID: 1, + status: idCodeMsgLang, + want: &StatusError{ + Code: ssh_FX_FAILURE, + msg: "err msg", + lang: "lang tag", + }, + }, + { + desc: "missing error message and language tag", + reqID: 1, + status: idCode, + want: &StatusError{ + Code: ssh_FX_FAILURE, + }, + }, + { + desc: "missing language tag", + reqID: 1, + status: idCodeMsg, + want: &StatusError{ + Code: ssh_FX_FAILURE, + msg: "err msg", + }, + }, + { + desc: "request identifier mismatch", + reqID: 2, + status: idCodeMsgLang, + want: &unexpectedIDErr{2, requestID}, + }, + } + + for _, tt := range tests { + got := unmarshalStatus(tt.reqID, tt.status) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("unmarshalStatus(%v, %v), test %q\n- want: %#v\n- got: %#v", + requestID, tt.status, tt.desc, tt.want, got) + } + } +} diff --git a/vendor/github.com/pkg/sftp/conn.go b/vendor/github.com/pkg/sftp/conn.go new file mode 100644 index 0000000..d9e1095 --- /dev/null +++ b/vendor/github.com/pkg/sftp/conn.go @@ -0,0 +1,133 @@ +package sftp + +import ( + "encoding" + "io" + "sync" + + "github.com/pkg/errors" +) + +// conn implements a bidirectional channel on which client and server +// connections are multiplexed. +type conn struct { + io.Reader + io.WriteCloser + sync.Mutex // used to serialise writes to sendPacket + // sendPacketTest is needed to replicate packet issues in testing + sendPacketTest func(w io.Writer, m encoding.BinaryMarshaler) error +} + +func (c *conn) recvPacket() (uint8, []byte, error) { + return recvPacket(c) +} + +func (c *conn) sendPacket(m encoding.BinaryMarshaler) error { + c.Lock() + defer c.Unlock() + if c.sendPacketTest != nil { + return c.sendPacketTest(c, m) + } + return sendPacket(c, m) +} + +type clientConn struct { + conn + wg sync.WaitGroup + sync.Mutex // protects inflight + inflight map[uint32]chan<- result // outstanding requests +} + +// Close closes the SFTP session. +func (c *clientConn) Close() error { + defer c.wg.Wait() + return c.conn.Close() +} + +func (c *clientConn) loop() { + defer c.wg.Done() + err := c.recv() + if err != nil { + c.broadcastErr(err) + } +} + +// recv continuously reads from the server and forwards responses to the +// appropriate channel. +func (c *clientConn) recv() error { + defer func() { + c.conn.Lock() + c.conn.Close() + c.conn.Unlock() + }() + for { + typ, data, err := c.recvPacket() + if err != nil { + return err + } + sid, _ := unmarshalUint32(data) + c.Lock() + ch, ok := c.inflight[sid] + delete(c.inflight, sid) + c.Unlock() + if !ok { + // This is an unexpected occurrence. Send the error + // back to all listeners so that they terminate + // gracefully. + return errors.Errorf("sid: %v not fond", sid) + } + ch <- result{typ: typ, data: data} + } +} + +// result captures the result of receiving the a packet from the server +type result struct { + typ byte + data []byte + err error +} + +type idmarshaler interface { + id() uint32 + encoding.BinaryMarshaler +} + +func (c *clientConn) sendPacket(p idmarshaler) (byte, []byte, error) { + ch := make(chan result, 1) + c.dispatchRequest(ch, p) + s := <-ch + return s.typ, s.data, s.err +} + +func (c *clientConn) dispatchRequest(ch chan<- result, p idmarshaler) { + c.Lock() + c.inflight[p.id()] = ch + c.Unlock() + if err := c.conn.sendPacket(p); err != nil { + c.Lock() + delete(c.inflight, p.id()) + c.Unlock() + ch <- result{err: err} + } +} + +// broadcastErr sends an error to all goroutines waiting for a response. +func (c *clientConn) broadcastErr(err error) { + c.Lock() + listeners := make([]chan<- result, 0, len(c.inflight)) + for _, ch := range c.inflight { + listeners = append(listeners, ch) + } + c.Unlock() + for _, ch := range listeners { + ch <- result{err: err} + } +} + +type serverConn struct { + conn +} + +func (s *serverConn) sendError(p ider, err error) error { + return s.sendPacket(statusFromError(p, err)) +} diff --git a/vendor/github.com/pkg/sftp/debug.go b/vendor/github.com/pkg/sftp/debug.go new file mode 100644 index 0000000..3e264ab --- /dev/null +++ b/vendor/github.com/pkg/sftp/debug.go @@ -0,0 +1,9 @@ +// +build debug + +package sftp + +import "log" + +func debug(fmt string, args ...interface{}) { + log.Printf(fmt, args...) +} diff --git a/vendor/github.com/pkg/sftp/example_test.go b/vendor/github.com/pkg/sftp/example_test.go new file mode 100644 index 0000000..483f68f --- /dev/null +++ b/vendor/github.com/pkg/sftp/example_test.go @@ -0,0 +1,147 @@ +package sftp_test + +import ( + "fmt" + "log" + "os" + "os/exec" + "path" + "strings" + + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" +) + +func Example() { + var conn *ssh.Client + + // open an SFTP session over an existing ssh connection. + sftp, err := sftp.NewClient(conn) + if err != nil { + log.Fatal(err) + } + defer sftp.Close() + + // walk a directory + w := sftp.Walk("/home/user") + for w.Step() { + if w.Err() != nil { + continue + } + log.Println(w.Path()) + } + + // leave your mark + f, err := sftp.Create("hello.txt") + if err != nil { + log.Fatal(err) + } + if _, err := f.Write([]byte("Hello world!")); err != nil { + log.Fatal(err) + } + + // check it's there + fi, err := sftp.Lstat("hello.txt") + if err != nil { + log.Fatal(err) + } + log.Println(fi) +} + +func ExampleNewClientPipe() { + // Connect to a remote host and request the sftp subsystem via the 'ssh' + // command. This assumes that passwordless login is correctly configured. + cmd := exec.Command("ssh", "example.com", "-s", "sftp") + + // send errors from ssh to stderr + cmd.Stderr = os.Stderr + + // get stdin and stdout + wr, err := cmd.StdinPipe() + if err != nil { + log.Fatal(err) + } + rd, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + + // start the process + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + defer cmd.Wait() + + // open the SFTP session + client, err := sftp.NewClientPipe(rd, wr) + if err != nil { + log.Fatal(err) + } + + // read a directory + list, err := client.ReadDir("/") + if err != nil { + log.Fatal(err) + } + + // print contents + for _, item := range list { + fmt.Println(item.Name()) + } + + // close the connection + client.Close() +} + +func ExampleClient_Mkdir_parents() { + // Example of mimicing 'mkdir --parents'; I.E. recursively create + // directoryies and don't error if any directories already exists. + var conn *ssh.Client + + client, err := sftp.NewClient(conn) + if err != nil { + log.Fatal(err) + } + defer client.Close() + + sshFxFailure := uint32(4) + mkdirParents := func(client *sftp.Client, dir string) (err error) { + var parents string + + if path.IsAbs(dir) { + // Otherwise, an absolute path given below would be turned in to a relative one + // by splitting on "/" + parents = "/" + } + + for _, name := range strings.Split(dir, "/") { + if name == "" { + // Paths with double-/ in them should just move along + // this will also catch the case of the first character being a "/", i.e. an absolute path + continue + } + parents = path.Join(parents, name) + err = client.Mkdir(parents) + if status, ok := err.(*sftp.StatusError); ok { + if status.Code == sshFxFailure { + var fi os.FileInfo + fi, err = client.Stat(parents) + if err == nil { + if !fi.IsDir() { + return fmt.Errorf("File exists: %s", parents) + } + } + } + } + if err != nil { + break + } + } + return err + } + + err = mkdirParents(client, "/tmp/foo/bar") + if err != nil { + log.Fatal(err) + } +} diff --git a/vendor/github.com/pkg/sftp/match.go b/vendor/github.com/pkg/sftp/match.go new file mode 100644 index 0000000..e2f2ba4 --- /dev/null +++ b/vendor/github.com/pkg/sftp/match.go @@ -0,0 +1,295 @@ +package sftp + +import ( + "path" + "strings" + "unicode/utf8" +) + +// ErrBadPattern indicates a globbing pattern was malformed. +var ErrBadPattern = path.ErrBadPattern + +// Unix separator +const separator = "/" + +// Match reports whether name matches the shell file name pattern. +// The pattern syntax is: +// +// pattern: +// { term } +// term: +// '*' matches any sequence of non-Separator characters +// '?' matches any single non-Separator character +// '[' [ '^' ] { character-range } ']' +// character class (must be non-empty) +// c matches character c (c != '*', '?', '\\', '[') +// '\\' c matches character c +// +// character-range: +// c matches character c (c != '\\', '-', ']') +// '\\' c matches character c +// lo '-' hi matches character c for lo <= c <= hi +// +// Match requires pattern to match all of name, not just a substring. +// The only possible returned error is ErrBadPattern, when pattern +// is malformed. +// +// +func Match(pattern, name string) (matched bool, err error) { + return path.Match(pattern, name) +} + +// detect if byte(char) is path separator +func isPathSeparator(c byte) bool { + return string(c) == "/" +} + +// scanChunk gets the next segment of pattern, which is a non-star string +// possibly preceded by a star. +func scanChunk(pattern string) (star bool, chunk, rest string) { + for len(pattern) > 0 && pattern[0] == '*' { + pattern = pattern[1:] + star = true + } + inrange := false + var i int +Scan: + for i = 0; i < len(pattern); i++ { + switch pattern[i] { + case '\\': + + // error check handled in matchChunk: bad pattern. + if i+1 < len(pattern) { + i++ + } + case '[': + inrange = true + case ']': + inrange = false + case '*': + if !inrange { + break Scan + } + } + } + return star, pattern[0:i], pattern[i:] +} + +// matchChunk checks whether chunk matches the beginning of s. +// If so, it returns the remainder of s (after the match). +// Chunk is all single-character operators: literals, char classes, and ?. +func matchChunk(chunk, s string) (rest string, ok bool, err error) { + for len(chunk) > 0 { + if len(s) == 0 { + return + } + switch chunk[0] { + case '[': + // character class + r, n := utf8.DecodeRuneInString(s) + s = s[n:] + chunk = chunk[1:] + // We can't end right after '[', we're expecting at least + // a closing bracket and possibly a caret. + if len(chunk) == 0 { + err = ErrBadPattern + return + } + // possibly negated + negated := chunk[0] == '^' + if negated { + chunk = chunk[1:] + } + // parse all ranges + match := false + nrange := 0 + for { + if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { + chunk = chunk[1:] + break + } + var lo, hi rune + if lo, chunk, err = getEsc(chunk); err != nil { + return + } + hi = lo + if chunk[0] == '-' { + if hi, chunk, err = getEsc(chunk[1:]); err != nil { + return + } + } + if lo <= r && r <= hi { + match = true + } + nrange++ + } + if match == negated { + return + } + + case '?': + if isPathSeparator(s[0]) { + return + } + _, n := utf8.DecodeRuneInString(s) + s = s[n:] + chunk = chunk[1:] + + case '\\': + chunk = chunk[1:] + if len(chunk) == 0 { + err = ErrBadPattern + return + } + fallthrough + + default: + if chunk[0] != s[0] { + return + } + s = s[1:] + chunk = chunk[1:] + } + } + return s, true, nil +} + +// getEsc gets a possibly-escaped character from chunk, for a character class. +func getEsc(chunk string) (r rune, nchunk string, err error) { + if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { + err = ErrBadPattern + return + } + if chunk[0] == '\\' { + chunk = chunk[1:] + if len(chunk) == 0 { + err = ErrBadPattern + return + } + } + r, n := utf8.DecodeRuneInString(chunk) + if r == utf8.RuneError && n == 1 { + err = ErrBadPattern + } + nchunk = chunk[n:] + if len(nchunk) == 0 { + err = ErrBadPattern + } + return +} + +// Split splits path immediately following the final Separator, +// separating it into a directory and file name component. +// If there is no Separator in path, Split returns an empty dir +// and file set to path. +// The returned values have the property that path = dir+file. +func Split(path string) (dir, file string) { + i := len(path) - 1 + for i >= 0 && !isPathSeparator(path[i]) { + i-- + } + return path[:i+1], path[i+1:] +} + +// Glob returns the names of all files matching pattern or nil +// if there is no matching file. The syntax of patterns is the same +// as in Match. The pattern may describe hierarchical names such as +// /usr/*/bin/ed (assuming the Separator is '/'). +// +// Glob ignores file system errors such as I/O errors reading directories. +// The only possible returned error is ErrBadPattern, when pattern +// is malformed. +func (c *Client) Glob(pattern string) (matches []string, err error) { + if !hasMeta(pattern) { + file, err := c.Lstat(pattern) + if err != nil { + return nil, nil + } + dir, _ := Split(pattern) + dir = cleanGlobPath(dir) + return []string{Join(dir, file.Name())}, nil + } + + dir, file := Split(pattern) + dir = cleanGlobPath(dir) + + if !hasMeta(dir) { + return c.glob(dir, file, nil) + } + + // Prevent infinite recursion. See issue 15879. + if dir == pattern { + return nil, ErrBadPattern + } + + var m []string + m, err = c.Glob(dir) + if err != nil { + return + } + for _, d := range m { + matches, err = c.glob(d, file, matches) + if err != nil { + return + } + } + return +} + +// cleanGlobPath prepares path for glob matching. +func cleanGlobPath(path string) string { + switch path { + case "": + return "." + case string(separator): + // do nothing to the path + return path + default: + return path[0 : len(path)-1] // chop off trailing separator + } +} + +// glob searches for files matching pattern in the directory dir +// and appends them to matches. If the directory cannot be +// opened, it returns the existing matches. New matches are +// added in lexicographical order. +func (c *Client) glob(dir, pattern string, matches []string) (m []string, e error) { + m = matches + fi, err := c.Stat(dir) + if err != nil { + return + } + if !fi.IsDir() { + return + } + names, err := c.ReadDir(dir) + if err != nil { + return + } + //sort.Strings(names) + + for _, n := range names { + matched, err := Match(pattern, n.Name()) + if err != nil { + return m, err + } + if matched { + m = append(m, Join(dir, n.Name())) + } + } + return +} + +// Join joins any number of path elements into a single path, adding +// a Separator if necessary. +// all empty strings are ignored. +func Join(elem ...string) string { + return path.Join(elem...) +} + +// hasMeta reports whether path contains any of the magic characters +// recognized by Match. +func hasMeta(path string) bool { + // TODO(niemeyer): Should other magic characters be added here? + return strings.ContainsAny(path, "*?[") +} diff --git a/vendor/github.com/pkg/sftp/other_test.go b/vendor/github.com/pkg/sftp/other_test.go new file mode 100644 index 0000000..1b84ccf --- /dev/null +++ b/vendor/github.com/pkg/sftp/other_test.go @@ -0,0 +1,5 @@ +// +build !linux,!darwin + +package sftp + +const sftpServer = "/usr/bin/false" // unsupported diff --git a/vendor/github.com/pkg/sftp/packet-manager.go b/vendor/github.com/pkg/sftp/packet-manager.go new file mode 100644 index 0000000..8c6691c --- /dev/null +++ b/vendor/github.com/pkg/sftp/packet-manager.go @@ -0,0 +1,156 @@ +package sftp + +import ( + "encoding" + "sync" +) + +// The goal of the packetManager is to keep the outgoing packets in the same +// order as the incoming. This is due to some sftp clients requiring this +// behavior (eg. winscp). + +type packetSender interface { + sendPacket(encoding.BinaryMarshaler) error +} + +type packetManager struct { + requests chan requestPacket + responses chan responsePacket + fini chan struct{} + incoming requestPacketIDs + outgoing responsePackets + sender packetSender // connection object + working *sync.WaitGroup +} + +func newPktMgr(sender packetSender) *packetManager { + s := &packetManager{ + requests: make(chan requestPacket, SftpServerWorkerCount), + responses: make(chan responsePacket, SftpServerWorkerCount), + fini: make(chan struct{}), + incoming: make([]uint32, 0, SftpServerWorkerCount), + outgoing: make([]responsePacket, 0, SftpServerWorkerCount), + sender: sender, + working: &sync.WaitGroup{}, + } + go s.controller() + return s +} + +// register incoming packets to be handled +// send id of 0 for packets without id +func (s *packetManager) incomingPacket(pkt requestPacket) { + s.working.Add(1) + s.requests <- pkt // buffer == SftpServerWorkerCount +} + +// register outgoing packets as being ready +func (s *packetManager) readyPacket(pkt responsePacket) { + s.responses <- pkt + s.working.Done() +} + +// shut down packetManager controller +func (s *packetManager) close() { + // pause until current packets are processed + s.working.Wait() + close(s.fini) +} + +// Passed a worker function, returns a channel for incoming packets. +// The goal is to process packets in the order they are received as is +// requires by section 7 of the RFC, while maximizing throughput of file +// transfers. +func (s *packetManager) workerChan(runWorker func(requestChan)) requestChan { + + rwChan := make(chan requestPacket, SftpServerWorkerCount) + for i := 0; i < SftpServerWorkerCount; i++ { + runWorker(rwChan) + } + + cmdChan := make(chan requestPacket) + runWorker(cmdChan) + + pktChan := make(chan requestPacket, SftpServerWorkerCount) + go func() { + // start with cmdChan + curChan := cmdChan + for pkt := range pktChan { + // on file open packet, switch to rwChan + switch pkt.(type) { + case *sshFxpOpenPacket: + curChan = rwChan + // on file close packet, switch back to cmdChan + // after waiting for any reads/writes to finish + case *sshFxpClosePacket: + // wait for rwChan to finish + s.working.Wait() + // stop using rwChan + curChan = cmdChan + } + s.incomingPacket(pkt) + curChan <- pkt + } + close(rwChan) + close(cmdChan) + s.close() + }() + + return pktChan +} + +// process packets +func (s *packetManager) controller() { + for { + select { + case pkt := <-s.requests: + debug("incoming id: %v", pkt.id()) + s.incoming = append(s.incoming, pkt.id()) + if len(s.incoming) > 1 { + s.incoming.Sort() + } + case pkt := <-s.responses: + debug("outgoing pkt: %v", pkt.id()) + s.outgoing = append(s.outgoing, pkt) + if len(s.outgoing) > 1 { + s.outgoing.Sort() + } + case <-s.fini: + return + } + s.maybeSendPackets() + } +} + +// send as many packets as are ready +func (s *packetManager) maybeSendPackets() { + for { + if len(s.outgoing) == 0 || len(s.incoming) == 0 { + debug("break! -- outgoing: %v; incoming: %v", + len(s.outgoing), len(s.incoming)) + break + } + out := s.outgoing[0] + in := s.incoming[0] + // debug("incoming: %v", s.incoming) + // debug("outgoing: %v", outfilter(s.outgoing)) + if in == out.id() { + s.sender.sendPacket(out) + // pop off heads + copy(s.incoming, s.incoming[1:]) // shift left + s.incoming = s.incoming[:len(s.incoming)-1] // remove last + copy(s.outgoing, s.outgoing[1:]) // shift left + s.outgoing = s.outgoing[:len(s.outgoing)-1] // remove last + } else { + break + } + } +} + +//func outfilter(o []responsePacket) []uint32 { +// res := make([]uint32, 0, len(o)) +// for _, v := range o { +// res = append(res, v.id()) +// } +// return res +//} diff --git a/vendor/github.com/pkg/sftp/packet-manager_go1.8.go b/vendor/github.com/pkg/sftp/packet-manager_go1.8.go new file mode 100644 index 0000000..ccae8c1 --- /dev/null +++ b/vendor/github.com/pkg/sftp/packet-manager_go1.8.go @@ -0,0 +1,21 @@ +// +build go1.8 + +package sftp + +import "sort" + +type responsePackets []responsePacket + +func (r responsePackets) Sort() { + sort.Slice(r, func(i, j int) bool { + return r[i].id() < r[j].id() + }) +} + +type requestPacketIDs []uint32 + +func (r requestPacketIDs) Sort() { + sort.Slice(r, func(i, j int) bool { + return r[i] < r[j] + }) +} diff --git a/vendor/github.com/pkg/sftp/packet-manager_legacy.go b/vendor/github.com/pkg/sftp/packet-manager_legacy.go new file mode 100644 index 0000000..97f0ff0 --- /dev/null +++ b/vendor/github.com/pkg/sftp/packet-manager_legacy.go @@ -0,0 +1,21 @@ +// +build !go1.8 + +package sftp + +import "sort" + +// for sorting/ordering outgoing +type responsePackets []responsePacket + +func (r responsePackets) Len() int { return len(r) } +func (r responsePackets) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r responsePackets) Less(i, j int) bool { return r[i].id() < r[j].id() } +func (r responsePackets) Sort() { sort.Sort(r) } + +// for sorting/ordering incoming +type requestPacketIDs []uint32 + +func (r requestPacketIDs) Len() int { return len(r) } +func (r requestPacketIDs) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r requestPacketIDs) Less(i, j int) bool { return r[i] < r[j] } +func (r requestPacketIDs) Sort() { sort.Sort(r) } diff --git a/vendor/github.com/pkg/sftp/packet-manager_test.go b/vendor/github.com/pkg/sftp/packet-manager_test.go new file mode 100644 index 0000000..7e187d9 --- /dev/null +++ b/vendor/github.com/pkg/sftp/packet-manager_test.go @@ -0,0 +1,154 @@ +package sftp + +import ( + "encoding" + "fmt" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +type _testSender struct { + sent chan encoding.BinaryMarshaler +} + +func newTestSender() *_testSender { + return &_testSender{make(chan encoding.BinaryMarshaler)} +} + +func (s _testSender) sendPacket(p encoding.BinaryMarshaler) error { + s.sent <- p + return nil +} + +type fakepacket uint32 + +func (fakepacket) MarshalBinary() ([]byte, error) { + return []byte{}, nil +} + +func (fakepacket) UnmarshalBinary([]byte) error { + return nil +} + +func (f fakepacket) id() uint32 { + return uint32(f) +} + +type pair struct { + in fakepacket + out fakepacket +} + +// basic test +var ttable1 = []pair{ + pair{fakepacket(0), fakepacket(0)}, + pair{fakepacket(1), fakepacket(1)}, + pair{fakepacket(2), fakepacket(2)}, + pair{fakepacket(3), fakepacket(3)}, +} + +// outgoing packets out of order +var ttable2 = []pair{ + pair{fakepacket(0), fakepacket(0)}, + pair{fakepacket(1), fakepacket(4)}, + pair{fakepacket(2), fakepacket(1)}, + pair{fakepacket(3), fakepacket(3)}, + pair{fakepacket(4), fakepacket(2)}, +} + +// incoming packets out of order +var ttable3 = []pair{ + pair{fakepacket(2), fakepacket(0)}, + pair{fakepacket(1), fakepacket(1)}, + pair{fakepacket(3), fakepacket(2)}, + pair{fakepacket(0), fakepacket(3)}, +} + +var tables = [][]pair{ttable1, ttable2, ttable3} + +func TestPacketManager(t *testing.T) { + sender := newTestSender() + s := newPktMgr(sender) + + for i := range tables { + table := tables[i] + for _, p := range table { + s.incomingPacket(p.in) + } + for _, p := range table { + s.readyPacket(p.out) + } + for i := 0; i < len(table); i++ { + pkt := <-sender.sent + id := pkt.(fakepacket).id() + assert.Equal(t, id, uint32(i)) + } + } + s.close() +} + +func (p sshFxpRemovePacket) String() string { + return fmt.Sprintf("RmPct:%d", p.ID) +} +func (p sshFxpOpenPacket) String() string { + return fmt.Sprintf("OpPct:%d", p.ID) +} +func (p sshFxpWritePacket) String() string { + return fmt.Sprintf("WrPct:%d", p.ID) +} +func (p sshFxpClosePacket) String() string { + return fmt.Sprintf("ClPct:%d", p.ID) +} + +// Test what happens when the pool processes a close packet on a file that it +// is still reading from. +func TestCloseOutOfOrder(t *testing.T) { + packets := []requestPacket{ + &sshFxpRemovePacket{ID: 0, Filename: "foo"}, + &sshFxpOpenPacket{ID: 1}, + &sshFxpWritePacket{ID: 2, Handle: "foo"}, + &sshFxpWritePacket{ID: 3, Handle: "foo"}, + &sshFxpWritePacket{ID: 4, Handle: "foo"}, + &sshFxpWritePacket{ID: 5, Handle: "foo"}, + &sshFxpClosePacket{ID: 6, Handle: "foo"}, + &sshFxpRemovePacket{ID: 7, Filename: "foo"}, + } + + recvChan := make(chan requestPacket, len(packets)+1) + sender := newTestSender() + pktMgr := newPktMgr(sender) + wg := sync.WaitGroup{} + wg.Add(len(packets)) + runWorker := func(ch requestChan) { + go func() { + for pkt := range ch { + if _, ok := pkt.(*sshFxpWritePacket); ok { + // sleep to cause writes to come after close/remove + time.Sleep(time.Millisecond) + } + pktMgr.working.Done() + recvChan <- pkt + wg.Done() + } + }() + } + pktChan := pktMgr.workerChan(runWorker) + for _, p := range packets { + pktChan <- p + } + wg.Wait() + close(recvChan) + received := []requestPacket{} + for p := range recvChan { + received = append(received, p) + } + if received[len(received)-2].id() != packets[len(packets)-2].id() { + t.Fatal("Packets processed out of order1:", received, packets) + } + if received[len(received)-1].id() != packets[len(packets)-1].id() { + t.Fatal("Packets processed out of order2:", received, packets) + } +} diff --git a/vendor/github.com/pkg/sftp/packet-typing.go b/vendor/github.com/pkg/sftp/packet-typing.go new file mode 100644 index 0000000..a0dc56a --- /dev/null +++ b/vendor/github.com/pkg/sftp/packet-typing.go @@ -0,0 +1,135 @@ +package sftp + +import ( + "encoding" + + "github.com/pkg/errors" +) + +// all incoming packets +type requestPacket interface { + encoding.BinaryUnmarshaler + id() uint32 +} + +type requestChan chan requestPacket + +type responsePacket interface { + encoding.BinaryMarshaler + id() uint32 +} + +// interfaces to group types +type hasPath interface { + requestPacket + getPath() string +} + +type hasHandle interface { + requestPacket + getHandle() string +} + +type notReadOnly interface { + notReadOnly() +} + +//// define types by adding methods +// hasPath +func (p sshFxpLstatPacket) getPath() string { return p.Path } +func (p sshFxpStatPacket) getPath() string { return p.Path } +func (p sshFxpRmdirPacket) getPath() string { return p.Path } +func (p sshFxpReadlinkPacket) getPath() string { return p.Path } +func (p sshFxpRealpathPacket) getPath() string { return p.Path } +func (p sshFxpMkdirPacket) getPath() string { return p.Path } +func (p sshFxpSetstatPacket) getPath() string { return p.Path } +func (p sshFxpStatvfsPacket) getPath() string { return p.Path } +func (p sshFxpRemovePacket) getPath() string { return p.Filename } +func (p sshFxpRenamePacket) getPath() string { return p.Oldpath } +func (p sshFxpSymlinkPacket) getPath() string { return p.Targetpath } +func (p sshFxpOpendirPacket) getPath() string { return p.Path } +func (p sshFxpOpenPacket) getPath() string { return p.Path } + +func (p sshFxpExtendedPacketPosixRename) getPath() string { return p.Oldpath } + +// hasHandle +func (p sshFxpFstatPacket) getHandle() string { return p.Handle } +func (p sshFxpFsetstatPacket) getHandle() string { return p.Handle } +func (p sshFxpReadPacket) getHandle() string { return p.Handle } +func (p sshFxpWritePacket) getHandle() string { return p.Handle } +func (p sshFxpReaddirPacket) getHandle() string { return p.Handle } + +// notReadOnly +func (p sshFxpWritePacket) notReadOnly() {} +func (p sshFxpSetstatPacket) notReadOnly() {} +func (p sshFxpFsetstatPacket) notReadOnly() {} +func (p sshFxpRemovePacket) notReadOnly() {} +func (p sshFxpMkdirPacket) notReadOnly() {} +func (p sshFxpRmdirPacket) notReadOnly() {} +func (p sshFxpRenamePacket) notReadOnly() {} +func (p sshFxpSymlinkPacket) notReadOnly() {} +func (p sshFxpExtendedPacketPosixRename) notReadOnly() {} + +// this has a handle, but is only used for close +func (p sshFxpClosePacket) getHandle() string { return p.Handle } + +// some packets with ID are missing id() +func (p sshFxpDataPacket) id() uint32 { return p.ID } +func (p sshFxpStatusPacket) id() uint32 { return p.ID } +func (p sshFxpStatResponse) id() uint32 { return p.ID } +func (p sshFxpNamePacket) id() uint32 { return p.ID } +func (p sshFxpHandlePacket) id() uint32 { return p.ID } +func (p sshFxVersionPacket) id() uint32 { return 0 } + +// take raw incoming packet data and build packet objects +func makePacket(p rxPacket) (requestPacket, error) { + var pkt requestPacket + switch p.pktType { + case ssh_FXP_INIT: + pkt = &sshFxInitPacket{} + case ssh_FXP_LSTAT: + pkt = &sshFxpLstatPacket{} + case ssh_FXP_OPEN: + pkt = &sshFxpOpenPacket{} + case ssh_FXP_CLOSE: + pkt = &sshFxpClosePacket{} + case ssh_FXP_READ: + pkt = &sshFxpReadPacket{} + case ssh_FXP_WRITE: + pkt = &sshFxpWritePacket{} + case ssh_FXP_FSTAT: + pkt = &sshFxpFstatPacket{} + case ssh_FXP_SETSTAT: + pkt = &sshFxpSetstatPacket{} + case ssh_FXP_FSETSTAT: + pkt = &sshFxpFsetstatPacket{} + case ssh_FXP_OPENDIR: + pkt = &sshFxpOpendirPacket{} + case ssh_FXP_READDIR: + pkt = &sshFxpReaddirPacket{} + case ssh_FXP_REMOVE: + pkt = &sshFxpRemovePacket{} + case ssh_FXP_MKDIR: + pkt = &sshFxpMkdirPacket{} + case ssh_FXP_RMDIR: + pkt = &sshFxpRmdirPacket{} + case ssh_FXP_REALPATH: + pkt = &sshFxpRealpathPacket{} + case ssh_FXP_STAT: + pkt = &sshFxpStatPacket{} + case ssh_FXP_RENAME: + pkt = &sshFxpRenamePacket{} + case ssh_FXP_READLINK: + pkt = &sshFxpReadlinkPacket{} + case ssh_FXP_SYMLINK: + pkt = &sshFxpSymlinkPacket{} + case ssh_FXP_EXTENDED: + pkt = &sshFxpExtendedPacket{} + default: + return nil, errors.Errorf("unhandled packet type: %s", p.pktType) + } + if err := pkt.UnmarshalBinary(p.pktBytes); err != nil { + return nil, err + } + return pkt, nil +} diff --git a/vendor/github.com/pkg/sftp/packet.go b/vendor/github.com/pkg/sftp/packet.go new file mode 100644 index 0000000..9b68138 --- /dev/null +++ b/vendor/github.com/pkg/sftp/packet.go @@ -0,0 +1,952 @@ +package sftp + +import ( + "bytes" + "encoding" + "encoding/binary" + "fmt" + "io" + "os" + "reflect" + + "github.com/pkg/errors" +) + +var ( + errShortPacket = errors.New("packet too short") + errUnknownExtendedPacket = errors.New("unknown extended packet") +) + +const ( + debugDumpTxPacket = false + debugDumpRxPacket = false + debugDumpTxPacketBytes = false + debugDumpRxPacketBytes = false +) + +func marshalUint32(b []byte, v uint32) []byte { + return append(b, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) +} + +func marshalUint64(b []byte, v uint64) []byte { + return marshalUint32(marshalUint32(b, uint32(v>>32)), uint32(v)) +} + +func marshalString(b []byte, v string) []byte { + return append(marshalUint32(b, uint32(len(v))), v...) +} + +func marshal(b []byte, v interface{}) []byte { + if v == nil { + return b + } + switch v := v.(type) { + case uint8: + return append(b, v) + case uint32: + return marshalUint32(b, v) + case uint64: + return marshalUint64(b, v) + case string: + return marshalString(b, v) + case os.FileInfo: + return marshalFileInfo(b, v) + default: + switch d := reflect.ValueOf(v); d.Kind() { + case reflect.Struct: + for i, n := 0, d.NumField(); i < n; i++ { + b = append(marshal(b, d.Field(i).Interface())) + } + return b + case reflect.Slice: + for i, n := 0, d.Len(); i < n; i++ { + b = append(marshal(b, d.Index(i).Interface())) + } + return b + default: + panic(fmt.Sprintf("marshal(%#v): cannot handle type %T", v, v)) + } + } +} + +func unmarshalUint32(b []byte) (uint32, []byte) { + v := uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 + return v, b[4:] +} + +func unmarshalUint32Safe(b []byte) (uint32, []byte, error) { + var v uint32 + if len(b) < 4 { + return 0, nil, errShortPacket + } + v, b = unmarshalUint32(b) + return v, b, nil +} + +func unmarshalUint64(b []byte) (uint64, []byte) { + h, b := unmarshalUint32(b) + l, b := unmarshalUint32(b) + return uint64(h)<<32 | uint64(l), b +} + +func unmarshalUint64Safe(b []byte) (uint64, []byte, error) { + var v uint64 + if len(b) < 8 { + return 0, nil, errShortPacket + } + v, b = unmarshalUint64(b) + return v, b, nil +} + +func unmarshalString(b []byte) (string, []byte) { + n, b := unmarshalUint32(b) + return string(b[:n]), b[n:] +} + +func unmarshalStringSafe(b []byte) (string, []byte, error) { + n, b, err := unmarshalUint32Safe(b) + if err != nil { + return "", nil, err + } + if int64(n) > int64(len(b)) { + return "", nil, errShortPacket + } + return string(b[:n]), b[n:], nil +} + +// sendPacket marshals p according to RFC 4234. +func sendPacket(w io.Writer, m encoding.BinaryMarshaler) error { + bb, err := m.MarshalBinary() + if err != nil { + return errors.Errorf("binary marshaller failed: %v", err) + } + if debugDumpTxPacketBytes { + debug("send packet: %s %d bytes %x", fxp(bb[0]), len(bb), bb[1:]) + } else if debugDumpTxPacket { + debug("send packet: %s %d bytes", fxp(bb[0]), len(bb)) + } + l := uint32(len(bb)) + hdr := []byte{byte(l >> 24), byte(l >> 16), byte(l >> 8), byte(l)} + _, err = w.Write(hdr) + if err != nil { + return errors.Errorf("failed to send packet header: %v", err) + } + _, err = w.Write(bb) + if err != nil { + return errors.Errorf("failed to send packet body: %v", err) + } + return nil +} + +func recvPacket(r io.Reader) (uint8, []byte, error) { + var b = []byte{0, 0, 0, 0} + if _, err := io.ReadFull(r, b); err != nil { + return 0, nil, err + } + l, _ := unmarshalUint32(b) + b = make([]byte, l) + if _, err := io.ReadFull(r, b); err != nil { + debug("recv packet %d bytes: err %v", l, err) + return 0, nil, err + } + if debugDumpRxPacketBytes { + debug("recv packet: %s %d bytes %x", fxp(b[0]), l, b[1:]) + } else if debugDumpRxPacket { + debug("recv packet: %s %d bytes", fxp(b[0]), l) + } + return b[0], b[1:], nil +} + +type extensionPair struct { + Name string + Data string +} + +func unmarshalExtensionPair(b []byte) (extensionPair, []byte, error) { + var ep extensionPair + var err error + ep.Name, b, err = unmarshalStringSafe(b) + if err != nil { + return ep, b, err + } + ep.Data, b, err = unmarshalStringSafe(b) + return ep, b, err +} + +// Here starts the definition of packets along with their MarshalBinary +// implementations. +// Manually writing the marshalling logic wins us a lot of time and +// allocation. + +type sshFxInitPacket struct { + Version uint32 + Extensions []extensionPair +} + +func (p sshFxInitPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 // byte + uint32 + for _, e := range p.Extensions { + l += 4 + len(e.Name) + 4 + len(e.Data) + } + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_INIT) + b = marshalUint32(b, p.Version) + for _, e := range p.Extensions { + b = marshalString(b, e.Name) + b = marshalString(b, e.Data) + } + return b, nil +} + +func (p *sshFxInitPacket) UnmarshalBinary(b []byte) error { + var err error + if p.Version, b, err = unmarshalUint32Safe(b); err != nil { + return err + } + for len(b) > 0 { + var ep extensionPair + ep, b, err = unmarshalExtensionPair(b) + if err != nil { + return err + } + p.Extensions = append(p.Extensions, ep) + } + return nil +} + +type sshFxVersionPacket struct { + Version uint32 + Extensions []struct { + Name, Data string + } +} + +func (p sshFxVersionPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 // byte + uint32 + for _, e := range p.Extensions { + l += 4 + len(e.Name) + 4 + len(e.Data) + } + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_VERSION) + b = marshalUint32(b, p.Version) + for _, e := range p.Extensions { + b = marshalString(b, e.Name) + b = marshalString(b, e.Data) + } + return b, nil +} + +func marshalIDString(packetType byte, id uint32, str string) ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + 4 + len(str) + + b := make([]byte, 0, l) + b = append(b, packetType) + b = marshalUint32(b, id) + b = marshalString(b, str) + return b, nil +} + +func unmarshalIDString(b []byte, id *uint32, str *string) error { + var err error + *id, b, err = unmarshalUint32Safe(b) + if err != nil { + return err + } + *str, b, err = unmarshalStringSafe(b) + return err +} + +type sshFxpReaddirPacket struct { + ID uint32 + Handle string +} + +func (p sshFxpReaddirPacket) id() uint32 { return p.ID } + +func (p sshFxpReaddirPacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_READDIR, p.ID, p.Handle) +} + +func (p *sshFxpReaddirPacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Handle) +} + +type sshFxpOpendirPacket struct { + ID uint32 + Path string +} + +func (p sshFxpOpendirPacket) id() uint32 { return p.ID } + +func (p sshFxpOpendirPacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_OPENDIR, p.ID, p.Path) +} + +func (p *sshFxpOpendirPacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Path) +} + +type sshFxpLstatPacket struct { + ID uint32 + Path string +} + +func (p sshFxpLstatPacket) id() uint32 { return p.ID } + +func (p sshFxpLstatPacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_LSTAT, p.ID, p.Path) +} + +func (p *sshFxpLstatPacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Path) +} + +type sshFxpStatPacket struct { + ID uint32 + Path string +} + +func (p sshFxpStatPacket) id() uint32 { return p.ID } + +func (p sshFxpStatPacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_STAT, p.ID, p.Path) +} + +func (p *sshFxpStatPacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Path) +} + +type sshFxpFstatPacket struct { + ID uint32 + Handle string +} + +func (p sshFxpFstatPacket) id() uint32 { return p.ID } + +func (p sshFxpFstatPacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_FSTAT, p.ID, p.Handle) +} + +func (p *sshFxpFstatPacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Handle) +} + +type sshFxpClosePacket struct { + ID uint32 + Handle string +} + +func (p sshFxpClosePacket) id() uint32 { return p.ID } + +func (p sshFxpClosePacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_CLOSE, p.ID, p.Handle) +} + +func (p *sshFxpClosePacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Handle) +} + +type sshFxpRemovePacket struct { + ID uint32 + Filename string +} + +func (p sshFxpRemovePacket) id() uint32 { return p.ID } + +func (p sshFxpRemovePacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_REMOVE, p.ID, p.Filename) +} + +func (p *sshFxpRemovePacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Filename) +} + +type sshFxpRmdirPacket struct { + ID uint32 + Path string +} + +func (p sshFxpRmdirPacket) id() uint32 { return p.ID } + +func (p sshFxpRmdirPacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_RMDIR, p.ID, p.Path) +} + +func (p *sshFxpRmdirPacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Path) +} + +type sshFxpSymlinkPacket struct { + ID uint32 + Targetpath string + Linkpath string +} + +func (p sshFxpSymlinkPacket) id() uint32 { return p.ID } + +func (p sshFxpSymlinkPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + 4 + len(p.Targetpath) + + 4 + len(p.Linkpath) + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_SYMLINK) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Targetpath) + b = marshalString(b, p.Linkpath) + return b, nil +} + +func (p *sshFxpSymlinkPacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Targetpath, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Linkpath, b, err = unmarshalStringSafe(b); err != nil { + return err + } + return nil +} + +type sshFxpReadlinkPacket struct { + ID uint32 + Path string +} + +func (p sshFxpReadlinkPacket) id() uint32 { return p.ID } + +func (p sshFxpReadlinkPacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_READLINK, p.ID, p.Path) +} + +func (p *sshFxpReadlinkPacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Path) +} + +type sshFxpRealpathPacket struct { + ID uint32 + Path string +} + +func (p sshFxpRealpathPacket) id() uint32 { return p.ID } + +func (p sshFxpRealpathPacket) MarshalBinary() ([]byte, error) { + return marshalIDString(ssh_FXP_REALPATH, p.ID, p.Path) +} + +func (p *sshFxpRealpathPacket) UnmarshalBinary(b []byte) error { + return unmarshalIDString(b, &p.ID, &p.Path) +} + +type sshFxpNameAttr struct { + Name string + LongName string + Attrs []interface{} +} + +func (p sshFxpNameAttr) MarshalBinary() ([]byte, error) { + b := []byte{} + b = marshalString(b, p.Name) + b = marshalString(b, p.LongName) + for _, attr := range p.Attrs { + b = marshal(b, attr) + } + return b, nil +} + +type sshFxpNamePacket struct { + ID uint32 + NameAttrs []sshFxpNameAttr +} + +func (p sshFxpNamePacket) MarshalBinary() ([]byte, error) { + b := []byte{} + b = append(b, ssh_FXP_NAME) + b = marshalUint32(b, p.ID) + b = marshalUint32(b, uint32(len(p.NameAttrs))) + for _, na := range p.NameAttrs { + ab, err := na.MarshalBinary() + if err != nil { + return nil, err + } + + b = append(b, ab...) + } + return b, nil +} + +type sshFxpOpenPacket struct { + ID uint32 + Path string + Pflags uint32 + Flags uint32 // ignored +} + +func (p sshFxpOpenPacket) id() uint32 { return p.ID } + +func (p sshFxpOpenPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + + 4 + len(p.Path) + + 4 + 4 + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_OPEN) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Path) + b = marshalUint32(b, p.Pflags) + b = marshalUint32(b, p.Flags) + return b, nil +} + +func (p *sshFxpOpenPacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Path, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Pflags, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Flags, b, err = unmarshalUint32Safe(b); err != nil { + return err + } + return nil +} + +type sshFxpReadPacket struct { + ID uint32 + Handle string + Offset uint64 + Len uint32 +} + +func (p sshFxpReadPacket) id() uint32 { return p.ID } + +func (p sshFxpReadPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + 4 + len(p.Handle) + + 8 + 4 // uint64 + uint32 + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_READ) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Handle) + b = marshalUint64(b, p.Offset) + b = marshalUint32(b, p.Len) + return b, nil +} + +func (p *sshFxpReadPacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Handle, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Offset, b, err = unmarshalUint64Safe(b); err != nil { + return err + } else if p.Len, b, err = unmarshalUint32Safe(b); err != nil { + return err + } + return nil +} + +type sshFxpRenamePacket struct { + ID uint32 + Oldpath string + Newpath string +} + +func (p sshFxpRenamePacket) id() uint32 { return p.ID } + +func (p sshFxpRenamePacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + 4 + len(p.Oldpath) + + 4 + len(p.Newpath) + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_RENAME) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Oldpath) + b = marshalString(b, p.Newpath) + return b, nil +} + +func (p *sshFxpRenamePacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Oldpath, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Newpath, b, err = unmarshalStringSafe(b); err != nil { + return err + } + return nil +} + +type sshFxpPosixRenamePacket struct { + ID uint32 + Oldpath string + Newpath string +} + +func (p sshFxpPosixRenamePacket) id() uint32 { return p.ID } + +func (p sshFxpPosixRenamePacket) MarshalBinary() ([]byte, error) { + const ext = "posix-rename@openssh.com" + l := 1 + 4 + // type(byte) + uint32 + 4 + len(ext) + + 4 + len(p.Oldpath) + + 4 + len(p.Newpath) + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_EXTENDED) + b = marshalUint32(b, p.ID) + b = marshalString(b, ext) + b = marshalString(b, p.Oldpath) + b = marshalString(b, p.Newpath) + return b, nil +} + +type sshFxpWritePacket struct { + ID uint32 + Handle string + Offset uint64 + Length uint32 + Data []byte +} + +func (p sshFxpWritePacket) id() uint32 { return p.ID } + +func (p sshFxpWritePacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + 4 + len(p.Handle) + + 8 + 4 + // uint64 + uint32 + len(p.Data) + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_WRITE) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Handle) + b = marshalUint64(b, p.Offset) + b = marshalUint32(b, p.Length) + b = append(b, p.Data...) + return b, nil +} + +func (p *sshFxpWritePacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Handle, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Offset, b, err = unmarshalUint64Safe(b); err != nil { + return err + } else if p.Length, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if uint32(len(b)) < p.Length { + return errShortPacket + } + + p.Data = append([]byte{}, b[:p.Length]...) + return nil +} + +type sshFxpMkdirPacket struct { + ID uint32 + Path string + Flags uint32 // ignored +} + +func (p sshFxpMkdirPacket) id() uint32 { return p.ID } + +func (p sshFxpMkdirPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + 4 + len(p.Path) + + 4 // uint32 + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_MKDIR) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Path) + b = marshalUint32(b, p.Flags) + return b, nil +} + +func (p *sshFxpMkdirPacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Path, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Flags, b, err = unmarshalUint32Safe(b); err != nil { + return err + } + return nil +} + +type sshFxpSetstatPacket struct { + ID uint32 + Path string + Flags uint32 + Attrs interface{} +} + +type sshFxpFsetstatPacket struct { + ID uint32 + Handle string + Flags uint32 + Attrs interface{} +} + +func (p sshFxpSetstatPacket) id() uint32 { return p.ID } +func (p sshFxpFsetstatPacket) id() uint32 { return p.ID } + +func (p sshFxpSetstatPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + 4 + len(p.Path) + + 4 // uint32 + uint64 + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_SETSTAT) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Path) + b = marshalUint32(b, p.Flags) + b = marshal(b, p.Attrs) + return b, nil +} + +func (p sshFxpFsetstatPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + 4 + len(p.Handle) + + 4 // uint32 + uint64 + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_FSETSTAT) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Handle) + b = marshalUint32(b, p.Flags) + b = marshal(b, p.Attrs) + return b, nil +} + +func (p *sshFxpSetstatPacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Path, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Flags, b, err = unmarshalUint32Safe(b); err != nil { + return err + } + p.Attrs = b + return nil +} + +func (p *sshFxpFsetstatPacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Handle, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Flags, b, err = unmarshalUint32Safe(b); err != nil { + return err + } + p.Attrs = b + return nil +} + +type sshFxpHandlePacket struct { + ID uint32 + Handle string +} + +func (p sshFxpHandlePacket) MarshalBinary() ([]byte, error) { + b := []byte{ssh_FXP_HANDLE} + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Handle) + return b, nil +} + +type sshFxpStatusPacket struct { + ID uint32 + StatusError +} + +func (p sshFxpStatusPacket) MarshalBinary() ([]byte, error) { + b := []byte{ssh_FXP_STATUS} + b = marshalUint32(b, p.ID) + b = marshalStatus(b, p.StatusError) + return b, nil +} + +type sshFxpDataPacket struct { + ID uint32 + Length uint32 + Data []byte +} + +func (p sshFxpDataPacket) MarshalBinary() ([]byte, error) { + b := []byte{ssh_FXP_DATA} + b = marshalUint32(b, p.ID) + b = marshalUint32(b, p.Length) + b = append(b, p.Data[:p.Length]...) + return b, nil +} + +func (p *sshFxpDataPacket) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.Length, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if uint32(len(b)) < p.Length { + return errors.New("truncated packet") + } + + p.Data = make([]byte, p.Length) + copy(p.Data, b) + return nil +} + +type sshFxpStatvfsPacket struct { + ID uint32 + Path string +} + +func (p sshFxpStatvfsPacket) id() uint32 { return p.ID } + +func (p sshFxpStatvfsPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + // type(byte) + uint32 + len(p.Path) + + len("statvfs@openssh.com") + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_EXTENDED) + b = marshalUint32(b, p.ID) + b = marshalString(b, "statvfs@openssh.com") + b = marshalString(b, p.Path) + return b, nil +} + +// A StatVFS contains statistics about a filesystem. +type StatVFS struct { + ID uint32 + Bsize uint64 /* file system block size */ + Frsize uint64 /* fundamental fs block size */ + Blocks uint64 /* number of blocks (unit f_frsize) */ + Bfree uint64 /* free blocks in file system */ + Bavail uint64 /* free blocks for non-root */ + Files uint64 /* total file inodes */ + Ffree uint64 /* free file inodes */ + Favail uint64 /* free file inodes for to non-root */ + Fsid uint64 /* file system id */ + Flag uint64 /* bit mask of f_flag values */ + Namemax uint64 /* maximum filename length */ +} + +// TotalSpace calculates the amount of total space in a filesystem. +func (p *StatVFS) TotalSpace() uint64 { + return p.Frsize * p.Blocks +} + +// FreeSpace calculates the amount of free space in a filesystem. +func (p *StatVFS) FreeSpace() uint64 { + return p.Frsize * p.Bfree +} + +// Convert to ssh_FXP_EXTENDED_REPLY packet binary format +func (p *StatVFS) MarshalBinary() ([]byte, error) { + var buf bytes.Buffer + buf.Write([]byte{ssh_FXP_EXTENDED_REPLY}) + err := binary.Write(&buf, binary.BigEndian, p) + return buf.Bytes(), err +} + +type sshFxpExtendedPacket struct { + ID uint32 + ExtendedRequest string + SpecificPacket interface { + serverRespondablePacket + readonly() bool + } +} + +func (p sshFxpExtendedPacket) id() uint32 { return p.ID } +func (p sshFxpExtendedPacket) readonly() bool { return p.SpecificPacket.readonly() } + +func (p sshFxpExtendedPacket) respond(svr *Server) error { + return p.SpecificPacket.respond(svr) +} + +func (p *sshFxpExtendedPacket) UnmarshalBinary(b []byte) error { + var err error + bOrig := b + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil { + return err + } + + // specific unmarshalling + switch p.ExtendedRequest { + case "statvfs@openssh.com": + p.SpecificPacket = &sshFxpExtendedPacketStatVFS{} + case "posix-rename@openssh.com": + p.SpecificPacket = &sshFxpExtendedPacketPosixRename{} + default: + return errUnknownExtendedPacket + } + + return p.SpecificPacket.UnmarshalBinary(bOrig) +} + +type sshFxpExtendedPacketStatVFS struct { + ID uint32 + ExtendedRequest string + Path string +} + +func (p sshFxpExtendedPacketStatVFS) id() uint32 { return p.ID } +func (p sshFxpExtendedPacketStatVFS) readonly() bool { return true } +func (p *sshFxpExtendedPacketStatVFS) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Path, b, err = unmarshalStringSafe(b); err != nil { + return err + } + return nil +} + +type sshFxpExtendedPacketPosixRename struct { + ID uint32 + ExtendedRequest string + Oldpath string + Newpath string +} + +func (p sshFxpExtendedPacketPosixRename) id() uint32 { return p.ID } +func (p sshFxpExtendedPacketPosixRename) readonly() bool { return false } +func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error { + var err error + if p.ID, b, err = unmarshalUint32Safe(b); err != nil { + return err + } else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Oldpath, b, err = unmarshalStringSafe(b); err != nil { + return err + } else if p.Newpath, b, err = unmarshalStringSafe(b); err != nil { + return err + } + return nil +} + +func (p sshFxpExtendedPacketPosixRename) respond(s *Server) error { + err := os.Rename(p.Oldpath, p.Newpath) + return s.sendError(p, err) +} diff --git a/vendor/github.com/pkg/sftp/packet_test.go b/vendor/github.com/pkg/sftp/packet_test.go new file mode 100644 index 0000000..2a948db --- /dev/null +++ b/vendor/github.com/pkg/sftp/packet_test.go @@ -0,0 +1,345 @@ +package sftp + +import ( + "bytes" + "encoding" + "os" + "testing" +) + +var marshalUint32Tests = []struct { + v uint32 + want []byte +}{ + {1, []byte{0, 0, 0, 1}}, + {256, []byte{0, 0, 1, 0}}, + {^uint32(0), []byte{255, 255, 255, 255}}, +} + +func TestMarshalUint32(t *testing.T) { + for _, tt := range marshalUint32Tests { + got := marshalUint32(nil, tt.v) + if !bytes.Equal(tt.want, got) { + t.Errorf("marshalUint32(%d): want %v, got %v", tt.v, tt.want, got) + } + } +} + +var marshalUint64Tests = []struct { + v uint64 + want []byte +}{ + {1, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}}, + {256, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0}}, + {^uint64(0), []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {1 << 32, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}}, +} + +func TestMarshalUint64(t *testing.T) { + for _, tt := range marshalUint64Tests { + got := marshalUint64(nil, tt.v) + if !bytes.Equal(tt.want, got) { + t.Errorf("marshalUint64(%d): want %#v, got %#v", tt.v, tt.want, got) + } + } +} + +var marshalStringTests = []struct { + v string + want []byte +}{ + {"", []byte{0, 0, 0, 0}}, + {"/foo", []byte{0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f}}, +} + +func TestMarshalString(t *testing.T) { + for _, tt := range marshalStringTests { + got := marshalString(nil, tt.v) + if !bytes.Equal(tt.want, got) { + t.Errorf("marshalString(%q): want %#v, got %#v", tt.v, tt.want, got) + } + } +} + +var marshalTests = []struct { + v interface{} + want []byte +}{ + {uint8(1), []byte{1}}, + {byte(1), []byte{1}}, + {uint32(1), []byte{0, 0, 0, 1}}, + {uint64(1), []byte{0, 0, 0, 0, 0, 0, 0, 1}}, + {"foo", []byte{0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f}}, + {[]uint32{1, 2, 3, 4}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x4}}, +} + +func TestMarshal(t *testing.T) { + for _, tt := range marshalTests { + got := marshal(nil, tt.v) + if !bytes.Equal(tt.want, got) { + t.Errorf("marshal(%v): want %#v, got %#v", tt.v, tt.want, got) + } + } +} + +var unmarshalUint32Tests = []struct { + b []byte + want uint32 + rest []byte +}{ + {[]byte{0, 0, 0, 0}, 0, nil}, + {[]byte{0, 0, 1, 0}, 256, nil}, + {[]byte{255, 0, 0, 255}, 4278190335, nil}, +} + +func TestUnmarshalUint32(t *testing.T) { + for _, tt := range unmarshalUint32Tests { + got, rest := unmarshalUint32(tt.b) + if got != tt.want || !bytes.Equal(rest, tt.rest) { + t.Errorf("unmarshalUint32(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest) + } + } +} + +var unmarshalUint64Tests = []struct { + b []byte + want uint64 + rest []byte +}{ + {[]byte{0, 0, 0, 0, 0, 0, 0, 0}, 0, nil}, + {[]byte{0, 0, 0, 0, 0, 0, 1, 0}, 256, nil}, + {[]byte{255, 0, 0, 0, 0, 0, 0, 255}, 18374686479671623935, nil}, +} + +func TestUnmarshalUint64(t *testing.T) { + for _, tt := range unmarshalUint64Tests { + got, rest := unmarshalUint64(tt.b) + if got != tt.want || !bytes.Equal(rest, tt.rest) { + t.Errorf("unmarshalUint64(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest) + } + } +} + +var unmarshalStringTests = []struct { + b []byte + want string + rest []byte +}{ + {marshalString(nil, ""), "", nil}, + {marshalString(nil, "blah"), "blah", nil}, +} + +func TestUnmarshalString(t *testing.T) { + for _, tt := range unmarshalStringTests { + got, rest := unmarshalString(tt.b) + if got != tt.want || !bytes.Equal(rest, tt.rest) { + t.Errorf("unmarshalUint64(%v): want %q, %#v, got %q, %#v", tt.b, tt.want, tt.rest, got, rest) + } + } +} + +var sendPacketTests = []struct { + p encoding.BinaryMarshaler + want []byte +}{ + {sshFxInitPacket{ + Version: 3, + Extensions: []extensionPair{ + {"posix-rename@openssh.com", "1"}, + }, + }, []byte{0x0, 0x0, 0x0, 0x26, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}}, + + {sshFxpOpenPacket{ + ID: 1, + Path: "/foo", + Pflags: flags(os.O_RDONLY), + }, []byte{0x0, 0x0, 0x0, 0x15, 0x3, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}}, + + {sshFxpWritePacket{ + ID: 124, + Handle: "foo", + Offset: 13, + Length: uint32(len([]byte("bar"))), + Data: []byte("bar"), + }, []byte{0x0, 0x0, 0x0, 0x1b, 0x6, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x3, 0x62, 0x61, 0x72}}, + + {sshFxpSetstatPacket{ + ID: 31, + Path: "/bar", + Flags: flags(os.O_WRONLY), + Attrs: struct { + UID uint32 + GID uint32 + }{1000, 100}, + }, []byte{0x0, 0x0, 0x0, 0x19, 0x9, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x3, 0xe8, 0x0, 0x0, 0x0, 0x64}}, +} + +func TestSendPacket(t *testing.T) { + for _, tt := range sendPacketTests { + var w bytes.Buffer + sendPacket(&w, tt.p) + if got := w.Bytes(); !bytes.Equal(tt.want, got) { + t.Errorf("sendPacket(%v): want %#v, got %#v", tt.p, tt.want, got) + } + } +} + +func sp(p encoding.BinaryMarshaler) []byte { + var w bytes.Buffer + sendPacket(&w, p) + return w.Bytes() +} + +var recvPacketTests = []struct { + b []byte + want uint8 + rest []byte +}{ + {sp(sshFxInitPacket{ + Version: 3, + Extensions: []extensionPair{ + {"posix-rename@openssh.com", "1"}, + }, + }), ssh_FXP_INIT, []byte{0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}}, +} + +func TestRecvPacket(t *testing.T) { + for _, tt := range recvPacketTests { + r := bytes.NewReader(tt.b) + got, rest, _ := recvPacket(r) + if got != tt.want || !bytes.Equal(rest, tt.rest) { + t.Errorf("recvPacket(%#v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest) + } + } +} + +func TestSSHFxpOpenPacketreadonly(t *testing.T) { + var tests = []struct { + pflags uint32 + ok bool + }{ + { + pflags: ssh_FXF_READ, + ok: true, + }, + { + pflags: ssh_FXF_WRITE, + ok: false, + }, + { + pflags: ssh_FXF_READ | ssh_FXF_WRITE, + ok: false, + }, + } + + for _, tt := range tests { + p := &sshFxpOpenPacket{ + Pflags: tt.pflags, + } + + if want, got := tt.ok, p.readonly(); want != got { + t.Errorf("unexpected value for p.readonly(): want: %v, got: %v", + want, got) + } + } +} + +func TestSSHFxpOpenPackethasPflags(t *testing.T) { + var tests = []struct { + desc string + haveFlags uint32 + testFlags []uint32 + ok bool + }{ + { + desc: "have read, test against write", + haveFlags: ssh_FXF_READ, + testFlags: []uint32{ssh_FXF_WRITE}, + ok: false, + }, + { + desc: "have write, test against read", + haveFlags: ssh_FXF_WRITE, + testFlags: []uint32{ssh_FXF_READ}, + ok: false, + }, + { + desc: "have read+write, test against read", + haveFlags: ssh_FXF_READ | ssh_FXF_WRITE, + testFlags: []uint32{ssh_FXF_READ}, + ok: true, + }, + { + desc: "have read+write, test against write", + haveFlags: ssh_FXF_READ | ssh_FXF_WRITE, + testFlags: []uint32{ssh_FXF_WRITE}, + ok: true, + }, + { + desc: "have read+write, test against read+write", + haveFlags: ssh_FXF_READ | ssh_FXF_WRITE, + testFlags: []uint32{ssh_FXF_READ, ssh_FXF_WRITE}, + ok: true, + }, + } + + for _, tt := range tests { + t.Log(tt.desc) + + p := &sshFxpOpenPacket{ + Pflags: tt.haveFlags, + } + + if want, got := tt.ok, p.hasPflags(tt.testFlags...); want != got { + t.Errorf("unexpected value for p.hasPflags(%#v): want: %v, got: %v", + tt.testFlags, want, got) + } + } +} + +func BenchmarkMarshalInit(b *testing.B) { + for i := 0; i < b.N; i++ { + sp(sshFxInitPacket{ + Version: 3, + Extensions: []extensionPair{ + {"posix-rename@openssh.com", "1"}, + }, + }) + } +} + +func BenchmarkMarshalOpen(b *testing.B) { + for i := 0; i < b.N; i++ { + sp(sshFxpOpenPacket{ + ID: 1, + Path: "/home/test/some/random/path", + Pflags: flags(os.O_RDONLY), + }) + } +} + +func BenchmarkMarshalWriteWorstCase(b *testing.B) { + data := make([]byte, 32*1024) + for i := 0; i < b.N; i++ { + sp(sshFxpWritePacket{ + ID: 1, + Handle: "someopaquehandle", + Offset: 0, + Length: uint32(len(data)), + Data: data, + }) + } +} + +func BenchmarkMarshalWrite1k(b *testing.B) { + data := make([]byte, 1024) + for i := 0; i < b.N; i++ { + sp(sshFxpWritePacket{ + ID: 1, + Handle: "someopaquehandle", + Offset: 0, + Length: uint32(len(data)), + Data: data, + }) + } +} diff --git a/vendor/github.com/pkg/sftp/release.go b/vendor/github.com/pkg/sftp/release.go new file mode 100644 index 0000000..b695528 --- /dev/null +++ b/vendor/github.com/pkg/sftp/release.go @@ -0,0 +1,5 @@ +// +build !debug + +package sftp + +func debug(fmt string, args ...interface{}) {} diff --git a/vendor/github.com/pkg/sftp/request-attrs.go b/vendor/github.com/pkg/sftp/request-attrs.go new file mode 100644 index 0000000..0149d93 --- /dev/null +++ b/vendor/github.com/pkg/sftp/request-attrs.go @@ -0,0 +1,63 @@ +package sftp + +// Methods on the Request object to make working with the Flags bitmasks and +// Attr(ibutes) byte blob easier. Use Pflags() when working with an Open/Write +// request and AttrFlags() and Attributes() when working with SetStat requests. + +import "os" + +// Open packet pflags +type pflags struct { + Read, Write, Append, Creat, Trunc, Excl bool +} + +// testable constructor +func newPflags(flags uint32) pflags { + return pflags{ + Read: flags&ssh_FXF_READ != 0, + Write: flags&ssh_FXF_WRITE != 0, + Append: flags&ssh_FXF_APPEND != 0, + Creat: flags&ssh_FXF_CREAT != 0, + Trunc: flags&ssh_FXF_TRUNC != 0, + Excl: flags&ssh_FXF_EXCL != 0, + } +} + +// Check bitmap/uint32 for Open packet pflag values +func (r *Request) Pflags() pflags { + return newPflags(r.Flags) +} + +// File attribute flags +type aflags struct { + Size, UidGid, Permissions, Acmodtime bool +} + +// testable constructor +func newAflags(flags uint32) aflags { + return aflags{ + Size: (flags & ssh_FILEXFER_ATTR_SIZE) != 0, + UidGid: (flags & ssh_FILEXFER_ATTR_UIDGID) != 0, + Permissions: (flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0, + Acmodtime: (flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0, + } +} + +// Check bitmap/uint32 for file attribute flags +func (r *Request) AttrFlags(flags uint32) aflags { + return newAflags(r.Flags) +} + +// File attributes +type fileattrs FileStat + +// Return Mode wrapped in os.FileMode +func (a fileattrs) FileMode() os.FileMode { + return os.FileMode(a.Mode) +} + +// Parse file attributes byte blob and return them in object +func (r *Request) Attributes() fileattrs { + fa, _ := getFileStat(r.Flags, r.Attrs) + return fileattrs(*fa) +} diff --git a/vendor/github.com/pkg/sftp/request-attrs_test.go b/vendor/github.com/pkg/sftp/request-attrs_test.go new file mode 100644 index 0000000..bcbb06a --- /dev/null +++ b/vendor/github.com/pkg/sftp/request-attrs_test.go @@ -0,0 +1,51 @@ +package sftp + +import ( + "os" + + "github.com/stretchr/testify/assert" + + "testing" +) + +func TestRequestPflags(t *testing.T) { + pflags := newPflags(ssh_FXF_READ | ssh_FXF_WRITE | ssh_FXF_APPEND) + assert.True(t, pflags.Read) + assert.True(t, pflags.Write) + assert.True(t, pflags.Append) + assert.False(t, pflags.Creat) + assert.False(t, pflags.Trunc) + assert.False(t, pflags.Excl) +} + +func TestRequestAflags(t *testing.T) { + aflags := newAflags(ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_UIDGID) + assert.True(t, aflags.Size) + assert.True(t, aflags.UidGid) + assert.False(t, aflags.Acmodtime) + assert.False(t, aflags.Permissions) +} + +func TestRequestAttributes(t *testing.T) { + // UID/GID + fa := fileattrs{UID: 1, GID: 2} + fl := uint32(ssh_FILEXFER_ATTR_UIDGID) + at := []byte{} + at = marshalUint32(at, 1) + at = marshalUint32(at, 2) + test_fs, _ := getFileStat(fl, at) + assert.Equal(t, fa, fileattrs(*test_fs)) + // Size and Mode + fa = fileattrs{Mode: 700, Size: 99} + fl = uint32(ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_PERMISSIONS) + at = []byte{} + at = marshalUint64(at, 99) + at = marshalUint32(at, 700) + test_fs, _ = getFileStat(fl, at) + test_fa := fileattrs(*test_fs) + assert.Equal(t, fa, test_fa) + // FileMode + assert.True(t, test_fa.FileMode().IsRegular()) + assert.False(t, test_fa.FileMode().IsDir()) + assert.Equal(t, test_fa.FileMode().Perm(), os.FileMode(700).Perm()) +} diff --git a/vendor/github.com/pkg/sftp/request-example.go b/vendor/github.com/pkg/sftp/request-example.go new file mode 100644 index 0000000..fe73174 --- /dev/null +++ b/vendor/github.com/pkg/sftp/request-example.go @@ -0,0 +1,262 @@ +package sftp + +// This serves as an example of how to implement the request server handler as +// well as a dummy backend for testing. It implements an in-memory backend that +// works as a very simple filesystem with simple flat key-value lookup system. + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "sync" + "time" +) + +// InMemHandler returns a Hanlders object with the test handlers +func InMemHandler() Handlers { + root := &root{ + files: make(map[string]*memFile), + } + root.memFile = newMemFile("/", true) + return Handlers{root, root, root, root} +} + +// So I can test Handlers returning errors +var ( + readErr error = nil + writeErr error = nil + cmdErr error = nil + listErr error = nil +) + +// Handlers +func (fs *root) Fileread(r *Request) (io.ReaderAt, error) { + if readErr != nil { + return nil, readErr + } + fs.filesLock.Lock() + defer fs.filesLock.Unlock() + file, err := fs.fetch(r.Filepath) + if err != nil { + return nil, err + } + if file.symlink != "" { + file, err = fs.fetch(file.symlink) + if err != nil { + return nil, err + } + } + return file.ReaderAt() +} + +func (fs *root) Filewrite(r *Request) (io.WriterAt, error) { + if writeErr != nil { + return nil, writeErr + } + fs.filesLock.Lock() + defer fs.filesLock.Unlock() + file, err := fs.fetch(r.Filepath) + if err == os.ErrNotExist { + dir, err := fs.fetch(filepath.Dir(r.Filepath)) + if err != nil { + return nil, err + } + if !dir.isdir { + return nil, os.ErrInvalid + } + file = newMemFile(r.Filepath, false) + fs.files[r.Filepath] = file + } + return file.WriterAt() +} + +func (fs *root) Filecmd(r *Request) error { + if cmdErr != nil { + return cmdErr + } + fs.filesLock.Lock() + defer fs.filesLock.Unlock() + switch r.Method { + case "Setstat": + return nil + case "Rename": + file, err := fs.fetch(r.Filepath) + if err != nil { + return err + } + if _, ok := fs.files[r.Target]; ok { + return &os.LinkError{Op: "rename", Old: r.Filepath, New: r.Target, + Err: fmt.Errorf("dest file exists")} + } + fs.files[r.Target] = file + delete(fs.files, r.Filepath) + case "Rmdir", "Remove": + _, err := fs.fetch(filepath.Dir(r.Filepath)) + if err != nil { + return err + } + delete(fs.files, r.Filepath) + case "Mkdir": + _, err := fs.fetch(filepath.Dir(r.Filepath)) + if err != nil { + return err + } + fs.files[r.Filepath] = newMemFile(r.Filepath, true) + case "Symlink": + _, err := fs.fetch(r.Filepath) + if err != nil { + return err + } + link := newMemFile(r.Target, false) + link.symlink = r.Filepath + fs.files[r.Target] = link + } + return nil +} + +type listerat []os.FileInfo + +// Modeled after strings.Reader's ReadAt() implementation +func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) { + var n int + if offset >= int64(len(f)) { + return 0, io.EOF + } + n = copy(ls, f[offset:]) + if n < len(ls) { + return n, io.EOF + } + return n, nil +} + +func (fs *root) Filelist(r *Request) (ListerAt, error) { + if listErr != nil { + return nil, listErr + } + fs.filesLock.Lock() + defer fs.filesLock.Unlock() + + switch r.Method { + case "List": + ordered_names := []string{} + for fn, _ := range fs.files { + if filepath.Dir(fn) == r.Filepath { + ordered_names = append(ordered_names, fn) + } + } + sort.Sort(sort.StringSlice(ordered_names)) + list := make([]os.FileInfo, len(ordered_names)) + for i, fn := range ordered_names { + list[i] = fs.files[fn] + } + return listerat(list), nil + case "Stat": + file, err := fs.fetch(r.Filepath) + if err != nil { + return nil, err + } + return listerat([]os.FileInfo{file}), nil + case "Readlink": + file, err := fs.fetch(r.Filepath) + if err != nil { + return nil, err + } + if file.symlink != "" { + file, err = fs.fetch(file.symlink) + if err != nil { + return nil, err + } + } + return listerat([]os.FileInfo{file}), nil + } + return nil, nil +} + +// In memory file-system-y thing that the Hanlders live on +type root struct { + *memFile + files map[string]*memFile + filesLock sync.Mutex +} + +func (fs *root) fetch(path string) (*memFile, error) { + if path == "/" { + return fs.memFile, nil + } + if file, ok := fs.files[path]; ok { + return file, nil + } + return nil, os.ErrNotExist +} + +// Implements os.FileInfo, Reader and Writer interfaces. +// These are the 3 interfaces necessary for the Handlers. +type memFile struct { + name string + modtime time.Time + symlink string + isdir bool + content []byte + contentLock sync.RWMutex +} + +// factory to make sure modtime is set +func newMemFile(name string, isdir bool) *memFile { + return &memFile{ + name: name, + modtime: time.Now(), + isdir: isdir, + } +} + +// Have memFile fulfill os.FileInfo interface +func (f *memFile) Name() string { return filepath.Base(f.name) } +func (f *memFile) Size() int64 { return int64(len(f.content)) } +func (f *memFile) Mode() os.FileMode { + ret := os.FileMode(0644) + if f.isdir { + ret = os.FileMode(0755) | os.ModeDir + } + if f.symlink != "" { + ret = os.FileMode(0777) | os.ModeSymlink + } + return ret +} +func (f *memFile) ModTime() time.Time { return f.modtime } +func (f *memFile) IsDir() bool { return f.isdir } +func (f *memFile) Sys() interface{} { + return fakeFileInfoSys() +} + +// Read/Write +func (f *memFile) ReaderAt() (io.ReaderAt, error) { + if f.isdir { + return nil, os.ErrInvalid + } + return bytes.NewReader(f.content), nil +} + +func (f *memFile) WriterAt() (io.WriterAt, error) { + if f.isdir { + return nil, os.ErrInvalid + } + return f, nil +} +func (f *memFile) WriteAt(p []byte, off int64) (int, error) { + // fmt.Println(string(p), off) + // mimic write delays, should be optional + time.Sleep(time.Microsecond * time.Duration(len(p))) + f.contentLock.Lock() + defer f.contentLock.Unlock() + plen := len(p) + int(off) + if plen >= len(f.content) { + nc := make([]byte, plen) + copy(nc, f.content) + f.content = nc + } + copy(f.content[off:], p) + return len(p), nil +} diff --git a/vendor/github.com/pkg/sftp/request-interfaces.go b/vendor/github.com/pkg/sftp/request-interfaces.go new file mode 100644 index 0000000..0f2b0aa --- /dev/null +++ b/vendor/github.com/pkg/sftp/request-interfaces.go @@ -0,0 +1,38 @@ +package sftp + +import ( + "io" + "os" +) + +// Interfaces are differentiated based on required returned values. +// All input arguments are to be pulled from Request (the only arg). + +// FileReader should return an io.Reader for the filepath +type FileReader interface { + Fileread(*Request) (io.ReaderAt, error) +} + +// FileWriter should return an io.Writer for the filepath +type FileWriter interface { + Filewrite(*Request) (io.WriterAt, error) +} + +// FileCmder should return an error (rename, remove, setstate, etc.) +type FileCmder interface { + Filecmd(*Request) error +} + +// FileLister should return file info interface and errors (readdir, stat) +type FileLister interface { + Filelist(*Request) (ListerAt, error) +} + +// ListerAt does for file lists what io.ReaderAt does for files. +// ListAt should return the number of entries copied and an io.EOF +// error if at end of list. This is testable by comparing how many you +// copied to how many could be copied (eg. n < len(ls) below). +// The copy() builtin is best for the copying. +type ListerAt interface { + ListAt([]os.FileInfo, int64) (int, error) +} diff --git a/vendor/github.com/pkg/sftp/request-readme.md b/vendor/github.com/pkg/sftp/request-readme.md new file mode 100644 index 0000000..f887274 --- /dev/null +++ b/vendor/github.com/pkg/sftp/request-readme.md @@ -0,0 +1,53 @@ +# Request Based SFTP API + +The request based API allows for custom backends in a way similar to the http +package. In order to create a backend you need to implement 4 handler +interfaces; one for reading, one for writing, one for misc commands and one for +listing files. Each has 1 required method and in each case those methods take +the Request as the only parameter and they each return something different. +These 4 interfaces are enough to handle all the SFTP traffic in a simplified +manner. + +The Request structure has 5 public fields which you will deal with. + +- Method (string) - string name of incoming call +- Filepath (string) - POSIX path of file to act on +- Flags (uint32) - 32bit bitmask value of file open/create flags +- Attrs ([]byte) - byte string of file attribute data +- Target (string) - target path for renames and sym-links + +Below are the methods and a brief description of what they need to do. + +### Fileread(*Request) (io.Reader, error) + +Handler for "Get" method and returns an io.Reader for the file which the server +then sends to the client. + +### Filewrite(*Request) (io.Writer, error) + +Handler for "Put" method and returns an io.Writer for the file which the server +then writes the uploaded file to. The file opening "pflags" are currently +preserved in the Request.Flags field as a 32bit bitmask value. See the [SFTP +spec](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.3) for +details. + +### Filecmd(*Request) error + +Handles "SetStat", "Rename", "Rmdir", "Mkdir" and "Symlink" methods. Makes the +appropriate changes and returns nil for success or an filesystem like error +(eg. os.ErrNotExist). The attributes are currently propagated in their raw form +([]byte) and will need to be unmarshalled to be useful. See the respond method +on sshFxpSetstatPacket for example of you might want to do this. + +### Fileinfo(*Request) ([]os.FileInfo, error) + +Handles "List", "Stat", "Readlink" methods. Gathers/creates FileInfo structs +with the data on the files and returns in a list (list of 1 for Stat and +Readlink). + + +## TODO + +- Add support for API users to see trace/debugging info of what is going on +inside SFTP server. +- Unmarshal the file attributes into a structure on the Request object. diff --git a/vendor/github.com/pkg/sftp/request-server.go b/vendor/github.com/pkg/sftp/request-server.go new file mode 100644 index 0000000..cb0b70f --- /dev/null +++ b/vendor/github.com/pkg/sftp/request-server.go @@ -0,0 +1,230 @@ +package sftp + +import ( + "encoding" + "io" + "path" + "path/filepath" + "strconv" + "sync" + "syscall" + + "github.com/pkg/errors" +) + +var maxTxPacket uint32 = 1 << 15 + +type handleHandler func(string) string + +// Handlers contains the 4 SFTP server request handlers. +type Handlers struct { + FileGet FileReader + FilePut FileWriter + FileCmd FileCmder + FileList FileLister +} + +// RequestServer abstracts the sftp protocol with an http request-like protocol +type RequestServer struct { + *serverConn + Handlers Handlers + pktMgr *packetManager + openRequests map[string]*Request + openRequestLock sync.RWMutex + handleCount int +} + +// NewRequestServer creates/allocates/returns new RequestServer. +// Normally there there will be one server per user-session. +func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer { + svrConn := &serverConn{ + conn: conn{ + Reader: rwc, + WriteCloser: rwc, + }, + } + return &RequestServer{ + serverConn: svrConn, + Handlers: h, + pktMgr: newPktMgr(svrConn), + openRequests: make(map[string]*Request), + } +} + +// New Open packet/Request +func (rs *RequestServer) nextRequest(r *Request) string { + rs.openRequestLock.Lock() + defer rs.openRequestLock.Unlock() + rs.handleCount++ + handle := strconv.Itoa(rs.handleCount) + rs.openRequests[handle] = r + return handle +} + +// Returns Request from openRequests, bool is false if it is missing +// If the method is different, save/return a new Request w/ that Method. +// +// The Requests in openRequests work essentially as open file descriptors that +// you can do different things with. What you are doing with it are denoted by +// the first packet of that type (read/write/etc). We create a new Request when +// it changes to set the request.Method attribute in a thread safe way. +func (rs *RequestServer) getRequest(handle, method string) (*Request, bool) { + rs.openRequestLock.RLock() + r, ok := rs.openRequests[handle] + rs.openRequestLock.RUnlock() + if !ok || r.Method == method { + return r, ok + } + // if we make it here we need to replace the request + rs.openRequestLock.Lock() + defer rs.openRequestLock.Unlock() + r, ok = rs.openRequests[handle] + if !ok || r.Method == method { // re-check needed b/c lock race + return r, ok + } + r = &Request{Method: method, Filepath: r.Filepath, state: r.state} + rs.openRequests[handle] = r + return r, ok +} + +func (rs *RequestServer) closeRequest(handle string) error { + rs.openRequestLock.Lock() + defer rs.openRequestLock.Unlock() + if r, ok := rs.openRequests[handle]; ok { + delete(rs.openRequests, handle) + return r.close() + } + return syscall.EBADF +} + +// Close the read/write/closer to trigger exiting the main server loop +func (rs *RequestServer) Close() error { return rs.conn.Close() } + +// Serve requests for user session +func (rs *RequestServer) Serve() error { + var wg sync.WaitGroup + runWorker := func(ch requestChan) { + wg.Add(1) + go func() { + defer wg.Done() + if err := rs.packetWorker(ch); err != nil { + rs.conn.Close() // shuts down recvPacket + } + }() + } + pktChan := rs.pktMgr.workerChan(runWorker) + + var err error + var pkt requestPacket + var pktType uint8 + var pktBytes []byte + for { + pktType, pktBytes, err = rs.recvPacket() + if err != nil { + break + } + + pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes}) + if err != nil { + debug("makePacket err: %v", err) + rs.conn.Close() // shuts down recvPacket + break + } + + pktChan <- pkt + } + + close(pktChan) // shuts down sftpServerWorkers + wg.Wait() // wait for all workers to exit + + return err +} + +func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error { + for pkt := range pktChan { + var rpkt responsePacket + switch pkt := pkt.(type) { + case *sshFxInitPacket: + rpkt = sshFxVersionPacket{sftpProtocolVersion, nil} + case *sshFxpClosePacket: + handle := pkt.getHandle() + rpkt = statusFromError(pkt, rs.closeRequest(handle)) + case *sshFxpRealpathPacket: + rpkt = cleanPacketPath(pkt) + case *sshFxpOpendirPacket: + request := requestFromPacket(pkt) + handle := rs.nextRequest(request) + rpkt = sshFxpHandlePacket{pkt.id(), handle} + case *sshFxpOpenPacket: + request := requestFromPacket(pkt) + handle := rs.nextRequest(request) + rpkt = sshFxpHandlePacket{pkt.id(), handle} + if pkt.hasPflags(ssh_FXF_CREAT) { + if p := request.call(rs.Handlers, pkt); !isOk(p) { + rpkt = p // if error in write, return it + } + } + case hasHandle: + handle := pkt.getHandle() + request, ok := rs.getRequest(handle, requestMethod(pkt)) + if !ok { + rpkt = statusFromError(pkt, syscall.EBADF) + } else { + rpkt = request.call(rs.Handlers, pkt) + } + case hasPath: + request := requestFromPacket(pkt) + rpkt = request.call(rs.Handlers, pkt) + default: + return errors.Errorf("unexpected packet type %T", pkt) + } + + err := rs.sendPacket(rpkt) + if err != nil { + return err + } + } + return nil +} + +// True is responsePacket is an OK status packet +func isOk(rpkt responsePacket) bool { + p, ok := rpkt.(sshFxpStatusPacket) + return ok && p.StatusError.Code == ssh_FX_OK +} + +// clean and return name packet for file +func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket { + path := cleanPath(pkt.getPath()) + return &sshFxpNamePacket{ + ID: pkt.id(), + NameAttrs: []sshFxpNameAttr{{ + Name: path, + LongName: path, + Attrs: emptyFileStat, + }}, + } +} + +// Makes sure we have a clean POSIX (/) absolute path to work with +func cleanPath(p string) string { + p = filepath.ToSlash(p) + if !filepath.IsAbs(p) { + p = "/" + p + } + return path.Clean(p) +} + +// Wrap underlying connection methods to use packetManager +func (rs *RequestServer) sendPacket(m encoding.BinaryMarshaler) error { + if pkt, ok := m.(responsePacket); ok { + rs.pktMgr.readyPacket(pkt) + } else { + return errors.Errorf("unexpected packet type %T", m) + } + return nil +} + +func (rs *RequestServer) sendError(p ider, err error) error { + return rs.sendPacket(statusFromError(p, err)) +} diff --git a/vendor/github.com/pkg/sftp/request-server_test.go b/vendor/github.com/pkg/sftp/request-server_test.go new file mode 100644 index 0000000..e82a7ca --- /dev/null +++ b/vendor/github.com/pkg/sftp/request-server_test.go @@ -0,0 +1,376 @@ +package sftp + +import ( + "fmt" + "io" + "net" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +var _ = fmt.Print + +type csPair struct { + cli *Client + svr *RequestServer +} + +// these must be closed in order, else client.Close will hang +func (cs csPair) Close() { + cs.svr.Close() + cs.cli.Close() + os.Remove(sock) +} + +func (cs csPair) testHandler() *root { + return cs.svr.Handlers.FileGet.(*root) +} + +const sock = "/tmp/rstest.sock" + +func clientRequestServerPair(t *testing.T) *csPair { + ready := make(chan bool) + os.Remove(sock) // either this or signal handling + var server *RequestServer + go func() { + l, err := net.Listen("unix", sock) + if err != nil { + // neither assert nor t.Fatal reliably exit before Accept errors + panic(err) + } + ready <- true + fd, err := l.Accept() + assert.Nil(t, err) + handlers := InMemHandler() + server = NewRequestServer(fd, handlers) + server.Serve() + }() + <-ready + defer os.Remove(sock) + c, err := net.Dial("unix", sock) + assert.Nil(t, err) + client, err := NewClientPipe(c, c) + if err != nil { + t.Fatalf("%+v\n", err) + } + return &csPair{client, server} +} + +// after adding logging, maybe check log to make sure packet handling +// was split over more than one worker +func TestRequestSplitWrite(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + w, err := p.cli.Create("/foo") + assert.Nil(t, err) + p.cli.maxPacket = 3 // force it to send in small chunks + contents := "one two three four five six seven eight nine ten" + w.Write([]byte(contents)) + w.Close() + r := p.testHandler() + f, _ := r.fetch("/foo") + assert.Equal(t, contents, string(f.content)) +} + +func TestRequestCache(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + foo := NewRequest("", "foo") + bar := NewRequest("", "bar") + fh := p.svr.nextRequest(foo) + bh := p.svr.nextRequest(bar) + assert.Len(t, p.svr.openRequests, 2) + _foo, ok := p.svr.getRequest(fh, "") + assert.Equal(t, foo, _foo) + assert.True(t, ok) + _, ok = p.svr.getRequest("zed", "") + assert.False(t, ok) + p.svr.closeRequest(fh) + p.svr.closeRequest(bh) + assert.Len(t, p.svr.openRequests, 0) +} + +func TestRequestCacheState(t *testing.T) { + // test operation that uses open/close + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + assert.Len(t, p.svr.openRequests, 0) + // test operation that doesn't open/close + err = p.cli.Remove("/foo") + assert.Nil(t, err) + assert.Len(t, p.svr.openRequests, 0) +} + +func putTestFile(cli *Client, path, content string) (int, error) { + w, err := cli.Create(path) + if err == nil { + defer w.Close() + return w.Write([]byte(content)) + } + return 0, err +} + +func TestRequestWrite(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + n, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + assert.Equal(t, 5, n) + r := p.testHandler() + f, err := r.fetch("/foo") + assert.Nil(t, err) + assert.False(t, f.isdir) + assert.Equal(t, f.content, []byte("hello")) +} + +func TestRequestWriteEmpty(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + n, err := putTestFile(p.cli, "/foo", "") + assert.NoError(t, err) + assert.Equal(t, 0, n) + r := p.testHandler() + f, err := r.fetch("/foo") + if assert.Nil(t, err) { + assert.False(t, f.isdir) + assert.Equal(t, f.content, []byte("")) + } + // lets test with an error + writeErr = os.ErrInvalid + n, err = putTestFile(p.cli, "/bar", "") + assert.Error(t, err) + writeErr = nil +} + +func TestRequestFilename(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.NoError(t, err) + r := p.testHandler() + f, err := r.fetch("/foo") + assert.NoError(t, err) + assert.Equal(t, f.Name(), "foo") + f, err = r.fetch("/bar") + assert.Error(t, err) +} + +func TestRequestRead(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + rf, err := p.cli.Open("/foo") + assert.Nil(t, err) + defer rf.Close() + contents := make([]byte, 5) + n, err := rf.Read(contents) + if err != nil && err != io.EOF { + t.Fatalf("err: %v", err) + } + assert.Equal(t, 5, n) + assert.Equal(t, "hello", string(contents[0:5])) +} + +func TestRequestReadFail(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + rf, err := p.cli.Open("/foo") + assert.Nil(t, err) + contents := make([]byte, 5) + n, err := rf.Read(contents) + assert.Equal(t, n, 0) + assert.Exactly(t, os.ErrNotExist, err) +} + +func TestRequestOpen(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + fh, err := p.cli.Open("foo") + assert.Nil(t, err) + err = fh.Close() + assert.Nil(t, err) +} + +func TestRequestMkdir(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + err := p.cli.Mkdir("/foo") + assert.Nil(t, err) + r := p.testHandler() + f, err := r.fetch("/foo") + assert.Nil(t, err) + assert.True(t, f.isdir) +} + +func TestRequestRemove(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + r := p.testHandler() + _, err = r.fetch("/foo") + assert.Nil(t, err) + err = p.cli.Remove("/foo") + assert.Nil(t, err) + _, err = r.fetch("/foo") + assert.Equal(t, err, os.ErrNotExist) +} + +func TestRequestRename(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + r := p.testHandler() + _, err = r.fetch("/foo") + assert.Nil(t, err) + err = p.cli.Rename("/foo", "/bar") + assert.Nil(t, err) + _, err = r.fetch("/bar") + assert.Nil(t, err) + _, err = r.fetch("/foo") + assert.Equal(t, err, os.ErrNotExist) +} + +func TestRequestRenameFail(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + _, err = putTestFile(p.cli, "/bar", "goodbye") + assert.Nil(t, err) + err = p.cli.Rename("/foo", "/bar") + assert.IsType(t, &StatusError{}, err) +} + +func TestRequestStat(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + fi, err := p.cli.Stat("/foo") + assert.Equal(t, fi.Name(), "foo") + assert.Equal(t, fi.Size(), int64(5)) + assert.Equal(t, fi.Mode(), os.FileMode(0644)) + assert.NoError(t, testOsSys(fi.Sys())) +} + +// NOTE: Setstat is a noop in the request server tests, but we want to test +// that is does nothing without crapping out. +func TestRequestSetstat(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + mode := os.FileMode(0644) + err = p.cli.Chmod("/foo", mode) + assert.Nil(t, err) + fi, err := p.cli.Stat("/foo") + assert.Nil(t, err) + assert.Equal(t, fi.Name(), "foo") + assert.Equal(t, fi.Size(), int64(5)) + assert.Equal(t, fi.Mode(), os.FileMode(0644)) + assert.NoError(t, testOsSys(fi.Sys())) +} + +func TestRequestFstat(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + fp, err := p.cli.Open("/foo") + assert.Nil(t, err) + fi, err := fp.Stat() + if assert.NoError(t, err) { + assert.Equal(t, fi.Name(), "foo") + assert.Equal(t, fi.Size(), int64(5)) + assert.Equal(t, fi.Mode(), os.FileMode(0644)) + assert.NoError(t, testOsSys(fi.Sys())) + } +} + +func TestRequestStatFail(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + fi, err := p.cli.Stat("/foo") + assert.Nil(t, fi) + assert.True(t, os.IsNotExist(err)) +} + +func TestRequestSymlink(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + err = p.cli.Symlink("/foo", "/bar") + assert.Nil(t, err) + r := p.testHandler() + fi, err := r.fetch("/bar") + assert.Nil(t, err) + assert.True(t, fi.Mode()&os.ModeSymlink == os.ModeSymlink) +} + +func TestRequestSymlinkFail(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + err := p.cli.Symlink("/foo", "/bar") + assert.True(t, os.IsNotExist(err)) +} + +func TestRequestReadlink(t *testing.T) { + p := clientRequestServerPair(t) + defer p.Close() + _, err := putTestFile(p.cli, "/foo", "hello") + assert.Nil(t, err) + err = p.cli.Symlink("/foo", "/bar") + assert.Nil(t, err) + rl, err := p.cli.ReadLink("/bar") + assert.Nil(t, err) + assert.Equal(t, "foo", rl) +} + +func TestRequestReaddir(t *testing.T) { + p := clientRequestServerPair(t) + MaxFilelist = 22 // make not divisible by our test amount (100) + defer p.Close() + for i := 0; i < 100; i++ { + fname := fmt.Sprintf("/foo_%02d", i) + _, err := putTestFile(p.cli, fname, fname) + assert.Nil(t, err) + } + di, err := p.cli.ReadDir("/") + assert.Nil(t, err) + assert.Len(t, di, 100) + names := []string{di[18].Name(), di[81].Name()} + assert.Equal(t, []string{"foo_18", "foo_81"}, names) +} + +func TestCleanPath(t *testing.T) { + assert.Equal(t, "/", cleanPath("/")) + assert.Equal(t, "/", cleanPath(".")) + assert.Equal(t, "/", cleanPath("/.")) + assert.Equal(t, "/", cleanPath("/a/..")) + assert.Equal(t, "/a/c", cleanPath("/a/b/../c")) + assert.Equal(t, "/a/c", cleanPath("/a/b/../c/")) + assert.Equal(t, "/a", cleanPath("/a/b/..")) + assert.Equal(t, "/a/b/c", cleanPath("/a/b/c")) + assert.Equal(t, "/", cleanPath("//")) + assert.Equal(t, "/a", cleanPath("/a/")) + assert.Equal(t, "/a", cleanPath("a/")) + assert.Equal(t, "/a/b/c", cleanPath("/a//b//c/")) + + // filepath.ToSlash does not touch \ as char on unix systems + // so os.PathSeparator is used for windows compatible tests + bslash := string(os.PathSeparator) + assert.Equal(t, "/", cleanPath(bslash)) + assert.Equal(t, "/", cleanPath(bslash+bslash)) + assert.Equal(t, "/a", cleanPath(bslash+"a"+bslash)) + assert.Equal(t, "/a", cleanPath("a"+bslash)) + assert.Equal(t, "/a/b/c", + cleanPath(bslash+"a"+bslash+bslash+"b"+bslash+bslash+"c"+bslash)) +} diff --git a/vendor/github.com/pkg/sftp/request-unix.go b/vendor/github.com/pkg/sftp/request-unix.go new file mode 100644 index 0000000..a71a898 --- /dev/null +++ b/vendor/github.com/pkg/sftp/request-unix.go @@ -0,0 +1,23 @@ +// +build !windows + +package sftp + +import ( + "errors" + "syscall" +) + +func fakeFileInfoSys() interface{} { + return &syscall.Stat_t{Uid: 65534, Gid: 65534} +} + +func testOsSys(sys interface{}) error { + fstat := sys.(*FileStat) + if fstat.UID != uint32(65534) { + return errors.New("Uid failed to match.") + } + if fstat.GID != uint32(65534) { + return errors.New("Gid failed to match:") + } + return nil +} diff --git a/vendor/github.com/pkg/sftp/request.go b/vendor/github.com/pkg/sftp/request.go new file mode 100644 index 0000000..bcbf7bb --- /dev/null +++ b/vendor/github.com/pkg/sftp/request.go @@ -0,0 +1,307 @@ +package sftp + +import ( + "io" + "os" + "path" + "path/filepath" + "sync" + "syscall" + + "github.com/pkg/errors" +) + +// MaxFilelist is the max number of files to return in a readdir batch. +var MaxFilelist int64 = 100 + +// Request contains the data and state for the incoming service request. +type Request struct { + // Get, Put, Setstat, Stat, Rename, Remove + // Rmdir, Mkdir, List, Readlink, Symlink + Method string + Filepath string + Flags uint32 + Attrs []byte // convert to sub-struct + Target string // for renames and sym-links + // reader/writer/readdir from handlers + stateLock sync.RWMutex + state state +} + +type state struct { + writerAt io.WriterAt + readerAt io.ReaderAt + listerAt ListerAt + lsoffset int64 +} + +// New Request initialized based on packet data +func requestFromPacket(pkt hasPath) *Request { + method := requestMethod(pkt) + request := NewRequest(method, pkt.getPath()) + switch p := pkt.(type) { + case *sshFxpOpenPacket: + request.Flags = p.Pflags + case *sshFxpSetstatPacket: + request.Flags = p.Flags + request.Attrs = p.Attrs.([]byte) + case *sshFxpRenamePacket: + request.Target = cleanPath(p.Newpath) + case *sshFxpSymlinkPacket: + request.Target = cleanPath(p.Linkpath) + } + return request +} + +// NewRequest creates a new Request object. +func NewRequest(method, path string) *Request { + return &Request{Method: method, Filepath: cleanPath(path)} +} + +// Returns current offset for file list +func (r *Request) lsNext() int64 { + r.stateLock.RLock() + defer r.stateLock.RUnlock() + return r.state.lsoffset +} + +// Increases next offset +func (r *Request) lsInc(offset int64) { + r.stateLock.Lock() + defer r.stateLock.Unlock() + r.state.lsoffset = r.state.lsoffset + offset +} + +// manage file read/write state +func (r *Request) setWriterState(wa io.WriterAt) { + r.stateLock.Lock() + defer r.stateLock.Unlock() + r.state.writerAt = wa +} +func (r *Request) setReaderState(ra io.ReaderAt) { + r.stateLock.Lock() + defer r.stateLock.Unlock() + r.state.readerAt = ra +} +func (r *Request) setListerState(la ListerAt) { + r.stateLock.Lock() + defer r.stateLock.Unlock() + r.state.listerAt = la +} + +func (r *Request) getWriter() io.WriterAt { + r.stateLock.RLock() + defer r.stateLock.RUnlock() + return r.state.writerAt +} + +func (r *Request) getReader() io.ReaderAt { + r.stateLock.RLock() + defer r.stateLock.RUnlock() + return r.state.readerAt +} + +func (r *Request) getLister() ListerAt { + r.stateLock.RLock() + defer r.stateLock.RUnlock() + return r.state.listerAt +} + +// Close reader/writer if possible +func (r *Request) close() error { + rd := r.getReader() + if c, ok := rd.(io.Closer); ok { + return c.Close() + } + wt := r.getWriter() + if c, ok := wt.(io.Closer); ok { + return c.Close() + } + return nil +} + +// called from worker to handle packet/request +func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket { + switch r.Method { + case "Get": + return fileget(handlers.FileGet, r, pkt) + case "Put", "Open": + return fileput(handlers.FilePut, r, pkt) + case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove": + return filecmd(handlers.FileCmd, r, pkt) + case "List", "Stat", "Readlink": + return filelist(handlers.FileList, r, pkt) + default: + return statusFromError(pkt, + errors.Errorf("unexpected method: %s", r.Method)) + } +} + +// file data for additional read/write packets +func packetData(p requestPacket) (data []byte, offset int64, length uint32) { + switch p := p.(type) { + case *sshFxpReadPacket: + length = p.Len + offset = int64(p.Offset) + case *sshFxpWritePacket: + data = p.Data + length = p.Length + offset = int64(p.Offset) + } + return +} + +// wrap FileReader handler +func fileget(h FileReader, r *Request, pkt requestPacket) responsePacket { + var err error + reader := r.getReader() + if reader == nil { + reader, err = h.Fileread(r) + if err != nil { + return statusFromError(pkt, err) + } + r.setReaderState(reader) + } + + _, offset, length := packetData(pkt) + data := make([]byte, clamp(length, maxTxPacket)) + n, err := reader.ReadAt(data, offset) + // only return EOF erro if no data left to read + if err != nil && (err != io.EOF || n == 0) { + return statusFromError(pkt, err) + } + return &sshFxpDataPacket{ + ID: pkt.id(), + Length: uint32(n), + Data: data[:n], + } +} + +// wrap FileWriter handler +func fileput(h FileWriter, r *Request, pkt requestPacket) responsePacket { + var err error + writer := r.getWriter() + if writer == nil { + writer, err = h.Filewrite(r) + if err != nil { + return statusFromError(pkt, err) + } + r.setWriterState(writer) + } + + data, offset, _ := packetData(pkt) + _, err = writer.WriteAt(data, offset) + return statusFromError(pkt, err) +} + +// wrap FileCmder handler +func filecmd(h FileCmder, r *Request, pkt requestPacket) responsePacket { + err := h.Filecmd(r) + return statusFromError(pkt, err) +} + +// wrap FileLister handler +func filelist(h FileLister, r *Request, pkt requestPacket) responsePacket { + var err error + lister := r.getLister() + if lister == nil { + lister, err = h.Filelist(r) + if err != nil { + return statusFromError(pkt, err) + } + r.setListerState(lister) + } + + offset := r.lsNext() + finfo := make([]os.FileInfo, MaxFilelist) + n, err := lister.ListAt(finfo, offset) + r.lsInc(int64(n)) + // ignore EOF as we only return it when there are no results + finfo = finfo[:n] // avoid need for nil tests below + + switch r.Method { + case "List": + if err != nil && err != io.EOF { + return statusFromError(pkt, err) + } + if n == 0 { + return statusFromError(pkt, io.EOF) + } + dirname := filepath.ToSlash(path.Base(r.Filepath)) + ret := &sshFxpNamePacket{ID: pkt.id()} + + for _, fi := range finfo { + ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{ + Name: fi.Name(), + LongName: runLs(dirname, fi), + Attrs: []interface{}{fi}, + }) + } + return ret + case "Stat": + if err != nil && err != io.EOF { + return statusFromError(pkt, err) + } + if n == 0 { + err = &os.PathError{Op: "stat", Path: r.Filepath, + Err: syscall.ENOENT} + return statusFromError(pkt, err) + } + return &sshFxpStatResponse{ + ID: pkt.id(), + info: finfo[0], + } + case "Readlink": + if err != nil && err != io.EOF { + return statusFromError(pkt, err) + } + if n == 0 { + err = &os.PathError{Op: "readlink", Path: r.Filepath, + Err: syscall.ENOENT} + return statusFromError(pkt, err) + } + filename := finfo[0].Name() + return &sshFxpNamePacket{ + ID: pkt.id(), + NameAttrs: []sshFxpNameAttr{{ + Name: filename, + LongName: filename, + Attrs: emptyFileStat, + }}, + } + default: + err = errors.Errorf("unexpected method: %s", r.Method) + return statusFromError(pkt, err) + } +} + +// init attributes of request object from packet data +func requestMethod(p requestPacket) (method string) { + switch p.(type) { + case *sshFxpReadPacket: + method = "Get" + case *sshFxpWritePacket: + method = "Put" + case *sshFxpReaddirPacket: + method = "List" + case *sshFxpOpenPacket, *sshFxpOpendirPacket: + method = "Open" + case *sshFxpSetstatPacket, *sshFxpFsetstatPacket: + method = "Setstat" + case *sshFxpRenamePacket: + method = "Rename" + case *sshFxpSymlinkPacket: + method = "Symlink" + case *sshFxpRemovePacket: + method = "Remove" + case *sshFxpStatPacket, *sshFxpLstatPacket, *sshFxpFstatPacket: + method = "Stat" + case *sshFxpRmdirPacket: + method = "Rmdir" + case *sshFxpReadlinkPacket: + method = "Readlink" + case *sshFxpMkdirPacket: + method = "Mkdir" + } + return method +} diff --git a/vendor/github.com/pkg/sftp/request_test.go b/vendor/github.com/pkg/sftp/request_test.go new file mode 100644 index 0000000..f39eeaf --- /dev/null +++ b/vendor/github.com/pkg/sftp/request_test.go @@ -0,0 +1,183 @@ +package sftp + +import ( + "github.com/stretchr/testify/assert" + + "bytes" + "errors" + "io" + "os" + "testing" +) + +type testHandler struct { + filecontents []byte // dummy contents + output io.WriterAt // dummy file out + err error // dummy error, should be file related +} + +func (t *testHandler) Fileread(r *Request) (io.ReaderAt, error) { + if t.err != nil { + return nil, t.err + } + return bytes.NewReader(t.filecontents), nil +} + +func (t *testHandler) Filewrite(r *Request) (io.WriterAt, error) { + if t.err != nil { + return nil, t.err + } + return io.WriterAt(t.output), nil +} + +func (t *testHandler) Filecmd(r *Request) error { + return t.err +} + +func (t *testHandler) Filelist(r *Request) (ListerAt, error) { + if t.err != nil { + return nil, t.err + } + f, err := os.Open(r.Filepath) + if err != nil { + return nil, err + } + fi, err := f.Stat() + if err != nil { + return nil, err + } + return listerat([]os.FileInfo{fi}), nil +} + +// make sure len(fakefile) == len(filecontents) +type fakefile [10]byte + +var filecontents = []byte("file-data.") + +func testRequest(method string) *Request { + request := &Request{ + Filepath: "./request_test.go", + Method: method, + Attrs: []byte("foo"), + Target: "foo", + } + return request +} + +func (ff *fakefile) WriteAt(p []byte, off int64) (int, error) { + n := copy(ff[off:], p) + return n, nil +} + +func (ff fakefile) string() string { + b := make([]byte, len(ff)) + copy(b, ff[:]) + return string(b) +} + +func newTestHandlers() Handlers { + handler := &testHandler{ + filecontents: filecontents, + output: &fakefile{}, + err: nil, + } + return Handlers{ + FileGet: handler, + FilePut: handler, + FileCmd: handler, + FileList: handler, + } +} + +func (h Handlers) getOutString() string { + handler := h.FilePut.(*testHandler) + return handler.output.(*fakefile).string() +} + +var errTest = errors.New("test error") + +func (h *Handlers) returnError() { + handler := h.FilePut.(*testHandler) + handler.err = errTest +} + +func statusOk(t *testing.T, p interface{}) { + if pkt, ok := p.(*sshFxpStatusPacket); ok { + assert.Equal(t, pkt.StatusError.Code, uint32(ssh_FX_OK)) + } +} + +// fake/test packet +type fakePacket struct { + myid uint32 + handle string +} + +func (f fakePacket) id() uint32 { + return f.myid +} + +func (f fakePacket) getHandle() string { + return f.handle +} +func (fakePacket) UnmarshalBinary(d []byte) error { return nil } + +func TestRequestGet(t *testing.T) { + handlers := newTestHandlers() + request := testRequest("Get") + // req.length is 5, so we test reads in 5 byte chunks + for i, txt := range []string{"file-", "data."} { + pkt := &sshFxpReadPacket{uint32(i), "a", uint64(i * 5), 5} + rpkt := request.call(handlers, pkt) + dpkt := rpkt.(*sshFxpDataPacket) + assert.Equal(t, dpkt.id(), uint32(i)) + assert.Equal(t, string(dpkt.Data), txt) + } +} + +func TestRequestPut(t *testing.T) { + handlers := newTestHandlers() + request := testRequest("Put") + pkt := &sshFxpWritePacket{0, "a", 0, 5, []byte("file-")} + rpkt := request.call(handlers, pkt) + statusOk(t, rpkt) + pkt = &sshFxpWritePacket{1, "a", 5, 5, []byte("data.")} + rpkt = request.call(handlers, pkt) + statusOk(t, rpkt) + assert.Equal(t, "file-data.", handlers.getOutString()) +} + +func TestRequestCmdr(t *testing.T) { + handlers := newTestHandlers() + request := testRequest("Mkdir") + pkt := fakePacket{myid: 1} + rpkt := request.call(handlers, pkt) + statusOk(t, rpkt) + + handlers.returnError() + rpkt = request.call(handlers, pkt) + assert.Equal(t, rpkt, statusFromError(rpkt, errTest)) +} + +func TestRequestInfoList(t *testing.T) { testInfoMethod(t, "List") } +func TestRequestInfoReadlink(t *testing.T) { testInfoMethod(t, "Readlink") } +func TestRequestInfoStat(t *testing.T) { + handlers := newTestHandlers() + request := testRequest("Stat") + pkt := fakePacket{myid: 1} + rpkt := request.call(handlers, pkt) + spkt, ok := rpkt.(*sshFxpStatResponse) + assert.True(t, ok) + assert.Equal(t, spkt.info.Name(), "request_test.go") +} + +func testInfoMethod(t *testing.T, method string) { + handlers := newTestHandlers() + request := testRequest(method) + pkt := fakePacket{myid: 1} + rpkt := request.call(handlers, pkt) + npkt, ok := rpkt.(*sshFxpNamePacket) + assert.True(t, ok) + assert.IsType(t, sshFxpNameAttr{}, npkt.NameAttrs[0]) + assert.Equal(t, npkt.NameAttrs[0].Name, "request_test.go") +} diff --git a/vendor/github.com/pkg/sftp/request_windows.go b/vendor/github.com/pkg/sftp/request_windows.go new file mode 100644 index 0000000..94d306b --- /dev/null +++ b/vendor/github.com/pkg/sftp/request_windows.go @@ -0,0 +1,11 @@ +package sftp + +import "syscall" + +func fakeFileInfoSys() interface{} { + return syscall.Win32FileAttributeData{} +} + +func testOsSys(sys interface{}) error { + return nil +} diff --git a/vendor/github.com/pkg/sftp/server.go b/vendor/github.com/pkg/sftp/server.go new file mode 100644 index 0000000..be14f62 --- /dev/null +++ b/vendor/github.com/pkg/sftp/server.go @@ -0,0 +1,665 @@ +package sftp + +// sftp server counterpart + +import ( + "encoding" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "sync" + "syscall" + "time" + + "github.com/pkg/errors" +) + +const ( + SftpServerWorkerCount = 8 +) + +// Server is an SSH File Transfer Protocol (sftp) server. +// This is intended to provide the sftp subsystem to an ssh server daemon. +// This implementation currently supports most of sftp server protocol version 3, +// as specified at http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 +type Server struct { + *serverConn + debugStream io.Writer + readOnly bool + pktMgr *packetManager + openFiles map[string]*os.File + openFilesLock sync.RWMutex + handleCount int + maxTxPacket uint32 +} + +func (svr *Server) nextHandle(f *os.File) string { + svr.openFilesLock.Lock() + defer svr.openFilesLock.Unlock() + svr.handleCount++ + handle := strconv.Itoa(svr.handleCount) + svr.openFiles[handle] = f + return handle +} + +func (svr *Server) closeHandle(handle string) error { + svr.openFilesLock.Lock() + defer svr.openFilesLock.Unlock() + if f, ok := svr.openFiles[handle]; ok { + delete(svr.openFiles, handle) + return f.Close() + } + + return syscall.EBADF +} + +func (svr *Server) getHandle(handle string) (*os.File, bool) { + svr.openFilesLock.RLock() + defer svr.openFilesLock.RUnlock() + f, ok := svr.openFiles[handle] + return f, ok +} + +type serverRespondablePacket interface { + encoding.BinaryUnmarshaler + id() uint32 + respond(svr *Server) error +} + +// NewServer creates a new Server instance around the provided streams, serving +// content from the root of the filesystem. Optionally, ServerOption +// functions may be specified to further configure the Server. +// +// A subsequent call to Serve() is required to begin serving files over SFTP. +func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) { + svrConn := &serverConn{ + conn: conn{ + Reader: rwc, + WriteCloser: rwc, + }, + } + s := &Server{ + serverConn: svrConn, + debugStream: ioutil.Discard, + pktMgr: newPktMgr(svrConn), + openFiles: make(map[string]*os.File), + maxTxPacket: 1 << 15, + } + + for _, o := range options { + if err := o(s); err != nil { + return nil, err + } + } + + return s, nil +} + +// A ServerOption is a function which applies configuration to a Server. +type ServerOption func(*Server) error + +// WithDebug enables Server debugging output to the supplied io.Writer. +func WithDebug(w io.Writer) ServerOption { + return func(s *Server) error { + s.debugStream = w + return nil + } +} + +// ReadOnly configures a Server to serve files in read-only mode. +func ReadOnly() ServerOption { + return func(s *Server) error { + s.readOnly = true + return nil + } +} + +type rxPacket struct { + pktType fxp + pktBytes []byte +} + +// Up to N parallel servers +func (svr *Server) sftpServerWorker(pktChan chan requestPacket) error { + for pkt := range pktChan { + + // readonly checks + readonly := true + switch pkt := pkt.(type) { + case notReadOnly: + readonly = false + case *sshFxpOpenPacket: + readonly = pkt.readonly() + case *sshFxpExtendedPacket: + readonly = pkt.SpecificPacket.readonly() + } + + // If server is operating read-only and a write operation is requested, + // return permission denied + if !readonly && svr.readOnly { + if err := svr.sendError(pkt, syscall.EPERM); err != nil { + return errors.Wrap(err, "failed to send read only packet response") + } + continue + } + + if err := handlePacket(svr, pkt); err != nil { + return err + } + } + return nil +} + +func handlePacket(s *Server, p interface{}) error { + switch p := p.(type) { + case *sshFxInitPacket: + return s.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil}) + case *sshFxpStatPacket: + // stat the requested file + info, err := os.Stat(p.Path) + if err != nil { + return s.sendError(p, err) + } + return s.sendPacket(sshFxpStatResponse{ + ID: p.ID, + info: info, + }) + case *sshFxpLstatPacket: + // stat the requested file + info, err := os.Lstat(p.Path) + if err != nil { + return s.sendError(p, err) + } + return s.sendPacket(sshFxpStatResponse{ + ID: p.ID, + info: info, + }) + case *sshFxpFstatPacket: + f, ok := s.getHandle(p.Handle) + if !ok { + return s.sendError(p, syscall.EBADF) + } + + info, err := f.Stat() + if err != nil { + return s.sendError(p, err) + } + + return s.sendPacket(sshFxpStatResponse{ + ID: p.ID, + info: info, + }) + case *sshFxpMkdirPacket: + // TODO FIXME: ignore flags field + err := os.Mkdir(p.Path, 0755) + return s.sendError(p, err) + case *sshFxpRmdirPacket: + err := os.Remove(p.Path) + return s.sendError(p, err) + case *sshFxpRemovePacket: + err := os.Remove(p.Filename) + return s.sendError(p, err) + case *sshFxpRenamePacket: + err := os.Rename(p.Oldpath, p.Newpath) + return s.sendError(p, err) + case *sshFxpSymlinkPacket: + err := os.Symlink(p.Targetpath, p.Linkpath) + return s.sendError(p, err) + case *sshFxpClosePacket: + return s.sendError(p, s.closeHandle(p.Handle)) + case *sshFxpReadlinkPacket: + f, err := os.Readlink(p.Path) + if err != nil { + return s.sendError(p, err) + } + + return s.sendPacket(sshFxpNamePacket{ + ID: p.ID, + NameAttrs: []sshFxpNameAttr{{ + Name: f, + LongName: f, + Attrs: emptyFileStat, + }}, + }) + + case *sshFxpRealpathPacket: + f, err := filepath.Abs(p.Path) + if err != nil { + return s.sendError(p, err) + } + f = cleanPath(f) + return s.sendPacket(sshFxpNamePacket{ + ID: p.ID, + NameAttrs: []sshFxpNameAttr{{ + Name: f, + LongName: f, + Attrs: emptyFileStat, + }}, + }) + case *sshFxpOpendirPacket: + return sshFxpOpenPacket{ + ID: p.ID, + Path: p.Path, + Pflags: ssh_FXF_READ, + }.respond(s) + case *sshFxpReadPacket: + f, ok := s.getHandle(p.Handle) + if !ok { + return s.sendError(p, syscall.EBADF) + } + + data := make([]byte, clamp(p.Len, s.maxTxPacket)) + n, err := f.ReadAt(data, int64(p.Offset)) + if err != nil && (err != io.EOF || n == 0) { + return s.sendError(p, err) + } + return s.sendPacket(sshFxpDataPacket{ + ID: p.ID, + Length: uint32(n), + Data: data[:n], + }) + case *sshFxpWritePacket: + f, ok := s.getHandle(p.Handle) + if !ok { + return s.sendError(p, syscall.EBADF) + } + + _, err := f.WriteAt(p.Data, int64(p.Offset)) + return s.sendError(p, err) + case serverRespondablePacket: + err := p.respond(s) + return errors.Wrap(err, "pkt.respond failed") + default: + return errors.Errorf("unexpected packet type %T", p) + } +} + +// Serve serves SFTP connections until the streams stop or the SFTP subsystem +// is stopped. +func (svr *Server) Serve() error { + var wg sync.WaitGroup + runWorker := func(ch requestChan) { + wg.Add(1) + go func() { + defer wg.Done() + if err := svr.sftpServerWorker(ch); err != nil { + svr.conn.Close() // shuts down recvPacket + } + }() + } + pktChan := svr.pktMgr.workerChan(runWorker) + + var err error + var pkt requestPacket + var pktType uint8 + var pktBytes []byte + for { + pktType, pktBytes, err = svr.recvPacket() + if err != nil { + break + } + + pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes}) + if err != nil { + debug("makePacket err: %v", err) + svr.conn.Close() // shuts down recvPacket + break + } + + pktChan <- pkt + } + + close(pktChan) // shuts down sftpServerWorkers + wg.Wait() // wait for all workers to exit + + // close any still-open files + for handle, file := range svr.openFiles { + fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name()) + file.Close() + } + return err // error from recvPacket +} + +// Wrap underlying connection methods to use packetManager +func (svr *Server) sendPacket(m encoding.BinaryMarshaler) error { + if pkt, ok := m.(responsePacket); ok { + svr.pktMgr.readyPacket(pkt) + } else { + return errors.Errorf("unexpected packet type %T", m) + } + return nil +} + +func (svr *Server) sendError(p ider, err error) error { + return svr.sendPacket(statusFromError(p, err)) +} + +type ider interface { + id() uint32 +} + +// The init packet has no ID, so we just return a zero-value ID +func (p sshFxInitPacket) id() uint32 { return 0 } + +type sshFxpStatResponse struct { + ID uint32 + info os.FileInfo +} + +func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) { + b := []byte{ssh_FXP_ATTRS} + b = marshalUint32(b, p.ID) + b = marshalFileInfo(b, p.info) + return b, nil +} + +var emptyFileStat = []interface{}{uint32(0)} + +func (p sshFxpOpenPacket) readonly() bool { + return !p.hasPflags(ssh_FXF_WRITE) +} + +func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool { + for _, f := range flags { + if p.Pflags&f == 0 { + return false + } + } + return true +} + +func (p sshFxpOpenPacket) respond(svr *Server) error { + var osFlags int + if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) { + osFlags |= os.O_RDWR + } else if p.hasPflags(ssh_FXF_WRITE) { + osFlags |= os.O_WRONLY + } else if p.hasPflags(ssh_FXF_READ) { + osFlags |= os.O_RDONLY + } else { + // how are they opening? + return svr.sendError(p, syscall.EINVAL) + } + + if p.hasPflags(ssh_FXF_APPEND) { + osFlags |= os.O_APPEND + } + if p.hasPflags(ssh_FXF_CREAT) { + osFlags |= os.O_CREATE + } + if p.hasPflags(ssh_FXF_TRUNC) { + osFlags |= os.O_TRUNC + } + if p.hasPflags(ssh_FXF_EXCL) { + osFlags |= os.O_EXCL + } + + f, err := os.OpenFile(p.Path, osFlags, 0644) + if err != nil { + return svr.sendError(p, err) + } + + handle := svr.nextHandle(f) + return svr.sendPacket(sshFxpHandlePacket{p.ID, handle}) +} + +func (p sshFxpReaddirPacket) respond(svr *Server) error { + f, ok := svr.getHandle(p.Handle) + if !ok { + return svr.sendError(p, syscall.EBADF) + } + + dirname := f.Name() + dirents, err := f.Readdir(128) + if err != nil { + return svr.sendError(p, err) + } + + ret := sshFxpNamePacket{ID: p.ID} + for _, dirent := range dirents { + ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{ + Name: dirent.Name(), + LongName: runLs(dirname, dirent), + Attrs: []interface{}{dirent}, + }) + } + return svr.sendPacket(ret) +} + +func (p sshFxpSetstatPacket) respond(svr *Server) error { + // additional unmarshalling is required for each possibility here + b := p.Attrs.([]byte) + var err error + + debug("setstat name \"%s\"", p.Path) + if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 { + var size uint64 + if size, b, err = unmarshalUint64Safe(b); err == nil { + err = os.Truncate(p.Path, int64(size)) + } + } + if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 { + var mode uint32 + if mode, b, err = unmarshalUint32Safe(b); err == nil { + err = os.Chmod(p.Path, os.FileMode(mode)) + } + } + if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 { + var atime uint32 + var mtime uint32 + if atime, b, err = unmarshalUint32Safe(b); err != nil { + } else if mtime, b, err = unmarshalUint32Safe(b); err != nil { + } else { + atimeT := time.Unix(int64(atime), 0) + mtimeT := time.Unix(int64(mtime), 0) + err = os.Chtimes(p.Path, atimeT, mtimeT) + } + } + if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 { + var uid uint32 + var gid uint32 + if uid, b, err = unmarshalUint32Safe(b); err != nil { + } else if gid, b, err = unmarshalUint32Safe(b); err != nil { + } else { + err = os.Chown(p.Path, int(uid), int(gid)) + } + } + + return svr.sendError(p, err) +} + +func (p sshFxpFsetstatPacket) respond(svr *Server) error { + f, ok := svr.getHandle(p.Handle) + if !ok { + return svr.sendError(p, syscall.EBADF) + } + + // additional unmarshalling is required for each possibility here + b := p.Attrs.([]byte) + var err error + + debug("fsetstat name \"%s\"", f.Name()) + if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 { + var size uint64 + if size, b, err = unmarshalUint64Safe(b); err == nil { + err = f.Truncate(int64(size)) + } + } + if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 { + var mode uint32 + if mode, b, err = unmarshalUint32Safe(b); err == nil { + err = f.Chmod(os.FileMode(mode)) + } + } + if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 { + var atime uint32 + var mtime uint32 + if atime, b, err = unmarshalUint32Safe(b); err != nil { + } else if mtime, b, err = unmarshalUint32Safe(b); err != nil { + } else { + atimeT := time.Unix(int64(atime), 0) + mtimeT := time.Unix(int64(mtime), 0) + err = os.Chtimes(f.Name(), atimeT, mtimeT) + } + } + if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 { + var uid uint32 + var gid uint32 + if uid, b, err = unmarshalUint32Safe(b); err != nil { + } else if gid, b, err = unmarshalUint32Safe(b); err != nil { + } else { + err = f.Chown(int(uid), int(gid)) + } + } + + return svr.sendError(p, err) +} + +// translateErrno translates a syscall error number to a SFTP error code. +func translateErrno(errno syscall.Errno) uint32 { + switch errno { + case 0: + return ssh_FX_OK + case syscall.ENOENT: + return ssh_FX_NO_SUCH_FILE + case syscall.EPERM: + return ssh_FX_PERMISSION_DENIED + } + + return ssh_FX_FAILURE +} + +func statusFromError(p ider, err error) sshFxpStatusPacket { + ret := sshFxpStatusPacket{ + ID: p.id(), + StatusError: StatusError{ + // ssh_FX_OK = 0 + // ssh_FX_EOF = 1 + // ssh_FX_NO_SUCH_FILE = 2 ENOENT + // ssh_FX_PERMISSION_DENIED = 3 + // ssh_FX_FAILURE = 4 + // ssh_FX_BAD_MESSAGE = 5 + // ssh_FX_NO_CONNECTION = 6 + // ssh_FX_CONNECTION_LOST = 7 + // ssh_FX_OP_UNSUPPORTED = 8 + Code: ssh_FX_OK, + }, + } + if err != nil { + debug("statusFromError: error is %T %#v", err, err) + ret.StatusError.Code = ssh_FX_FAILURE + ret.StatusError.msg = err.Error() + if err == io.EOF { + ret.StatusError.Code = ssh_FX_EOF + } else if err == os.ErrNotExist { + ret.StatusError.Code = ssh_FX_NO_SUCH_FILE + } else if errno, ok := err.(syscall.Errno); ok { + ret.StatusError.Code = translateErrno(errno) + } else if pathError, ok := err.(*os.PathError); ok { + debug("statusFromError: error is %T %#v", pathError.Err, pathError.Err) + if errno, ok := pathError.Err.(syscall.Errno); ok { + ret.StatusError.Code = translateErrno(errno) + } + } + } + return ret +} + +func clamp(v, max uint32) uint32 { + if v > max { + return max + } + return v +} + +func runLsTypeWord(dirent os.FileInfo) string { + // find first character, the type char + // b Block special file. + // c Character special file. + // d Directory. + // l Symbolic link. + // s Socket link. + // p FIFO. + // - Regular file. + tc := '-' + mode := dirent.Mode() + if (mode & os.ModeDir) != 0 { + tc = 'd' + } else if (mode & os.ModeDevice) != 0 { + tc = 'b' + if (mode & os.ModeCharDevice) != 0 { + tc = 'c' + } + } else if (mode & os.ModeSymlink) != 0 { + tc = 'l' + } else if (mode & os.ModeSocket) != 0 { + tc = 's' + } else if (mode & os.ModeNamedPipe) != 0 { + tc = 'p' + } + + // owner + orc := '-' + if (mode & 0400) != 0 { + orc = 'r' + } + owc := '-' + if (mode & 0200) != 0 { + owc = 'w' + } + oxc := '-' + ox := (mode & 0100) != 0 + setuid := (mode & os.ModeSetuid) != 0 + if ox && setuid { + oxc = 's' + } else if setuid { + oxc = 'S' + } else if ox { + oxc = 'x' + } + + // group + grc := '-' + if (mode & 040) != 0 { + grc = 'r' + } + gwc := '-' + if (mode & 020) != 0 { + gwc = 'w' + } + gxc := '-' + gx := (mode & 010) != 0 + setgid := (mode & os.ModeSetgid) != 0 + if gx && setgid { + gxc = 's' + } else if setgid { + gxc = 'S' + } else if gx { + gxc = 'x' + } + + // all / others + arc := '-' + if (mode & 04) != 0 { + arc = 'r' + } + awc := '-' + if (mode & 02) != 0 { + awc = 'w' + } + axc := '-' + ax := (mode & 01) != 0 + sticky := (mode & os.ModeSticky) != 0 + if ax && sticky { + axc = 't' + } else if sticky { + axc = 'T' + } else if ax { + axc = 'x' + } + + return fmt.Sprintf("%c%c%c%c%c%c%c%c%c%c", tc, orc, owc, oxc, grc, gwc, gxc, arc, awc, axc) +} diff --git a/vendor/github.com/pkg/sftp/server_integration_test.go b/vendor/github.com/pkg/sftp/server_integration_test.go new file mode 100644 index 0000000..25b89ef --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_integration_test.go @@ -0,0 +1,718 @@ +package sftp + +// sftp server integration tests +// enable with -integration +// example invokation (darwin): gofmt -w `find . -name \*.go` && (cd server_standalone/ ; go build -tags debug) && go test -tags debug github.com/pkg/sftp -integration -v -sftp /usr/libexec/sftp-server -run ServerCompareSubsystems + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + crand "crypto/rand" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "flag" + "fmt" + "io/ioutil" + "math/rand" + "net" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strconv" + "strings" + "testing" + "time" + + "github.com/kr/fs" + "golang.org/x/crypto/ssh" +) + +var testSftpClientBin = flag.String("sftp_client", "/usr/bin/sftp", "location of the sftp client binary") +var sshServerDebugStream = ioutil.Discard +var sftpServerDebugStream = ioutil.Discard +var sftpClientDebugStream = ioutil.Discard + +const ( + GOLANG_SFTP = true + OPENSSH_SFTP = false +) + +var ( + hostPrivateKeySigner ssh.Signer + privKey = []byte(` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEArhp7SqFnXVZAgWREL9Ogs+miy4IU/m0vmdkoK6M97G9NX/Pj +wf8I/3/ynxmcArbt8Rc4JgkjT2uxx/NqR0yN42N1PjO5Czu0dms1PSqcKIJdeUBV +7gdrKSm9Co4d2vwfQp5mg47eG4w63pz7Drk9+VIyi9YiYH4bve7WnGDswn4ycvYZ +slV5kKnjlfCdPig+g5P7yQYud0cDWVwyA0+kxvL6H3Ip+Fu8rLDZn4/P1WlFAIuc +PAf4uEKDGGmC2URowi5eesYR7f6GN/HnBs2776laNlAVXZUmYTUfOGagwLsEkx8x +XdNqntfbs2MOOoK+myJrNtcB9pCrM0H6um19uQIDAQABAoIBABkWr9WdVKvalgkP +TdQmhu3mKRNyd1wCl+1voZ5IM9Ayac/98UAvZDiNU4Uhx52MhtVLJ0gz4Oa8+i16 +IkKMAZZW6ro/8dZwkBzQbieWUFJ2Fso2PyvB3etcnGU8/Yhk9IxBDzy+BbuqhYE2 +1ebVQtz+v1HvVZzaD11bYYm/Xd7Y28QREVfFen30Q/v3dv7dOteDE/RgDS8Czz7w +jMW32Q8JL5grz7zPkMK39BLXsTcSYcaasT2ParROhGJZDmbgd3l33zKCVc1zcj9B +SA47QljGd09Tys958WWHgtj2o7bp9v1Ufs4LnyKgzrB80WX1ovaSQKvd5THTLchO +kLIhUAECgYEA2doGXy9wMBmTn/hjiVvggR1aKiBwUpnB87Hn5xCMgoECVhFZlT6l +WmZe7R2klbtG1aYlw+y+uzHhoVDAJW9AUSV8qoDUwbRXvBVlp+In5wIqJ+VjfivK +zgIfzomL5NvDz37cvPmzqIeySTowEfbQyq7CUQSoDtE9H97E2wWZhDkCgYEAzJdJ +k+NSFoTkHhfD3L0xCDHpRV3gvaOeew8524fVtVUq53X8m91ng4AX1r74dCUYwwiF +gqTtSSJfx2iH1xKnNq28M9uKg7wOrCKrRqNPnYUO3LehZEC7rwUr26z4iJDHjjoB +uBcS7nw0LJ+0Zeg1IF+aIdZGV3MrAKnrzWPixYECgYBsffX6ZWebrMEmQ89eUtFF +u9ZxcGI/4K8ErC7vlgBD5ffB4TYZ627xzFWuBLs4jmHCeNIJ9tct5rOVYN+wRO1k +/CRPzYUnSqb+1jEgILL6istvvv+DkE+ZtNkeRMXUndWwel94BWsBnUKe0UmrSJ3G +sq23J3iCmJW2T3z+DpXbkQKBgQCK+LUVDNPE0i42NsRnm+fDfkvLP7Kafpr3Umdl +tMY474o+QYn+wg0/aPJIf9463rwMNyyhirBX/k57IIktUdFdtfPicd2MEGETElWv +nN1GzYxD50Rs2f/jKisZhEwqT9YNyV9DkgDdGGdEbJNYqbv0qpwDIg8T9foe8E1p +bdErgQKBgAt290I3L316cdxIQTkJh1DlScN/unFffITwu127WMr28Jt3mq3cZpuM +Aecey/eEKCj+Rlas5NDYKsB18QIuAw+qqWyq0LAKLiAvP1965Rkc4PLScl3MgJtO +QYa37FK0p8NcDeUuF86zXBVutwS5nJLchHhKfd590ks57OROtm29 +-----END RSA PRIVATE KEY----- +`) +) + +func init() { + var err error + hostPrivateKeySigner, err = ssh.ParsePrivateKey(privKey) + if err != nil { + panic(err) + } +} + +func keyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + permissions := &ssh.Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + } + return permissions, nil +} + +func pwAuth(conn ssh.ConnMetadata, pw []byte) (*ssh.Permissions, error) { + permissions := &ssh.Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + } + return permissions, nil +} + +func basicServerConfig() *ssh.ServerConfig { + config := ssh.ServerConfig{ + Config: ssh.Config{ + MACs: []string{"hmac-sha1"}, + }, + PasswordCallback: pwAuth, + PublicKeyCallback: keyAuth, + } + config.AddHostKey(hostPrivateKeySigner) + return &config +} + +type sshServer struct { + useSubsystem bool + conn net.Conn + config *ssh.ServerConfig + sshConn *ssh.ServerConn + newChans <-chan ssh.NewChannel + newReqs <-chan *ssh.Request +} + +func sshServerFromConn(conn net.Conn, useSubsystem bool, config *ssh.ServerConfig) (*sshServer, error) { + // From a standard TCP connection to an encrypted SSH connection + sshConn, newChans, newReqs, err := ssh.NewServerConn(conn, config) + if err != nil { + return nil, err + } + + svr := &sshServer{useSubsystem, conn, config, sshConn, newChans, newReqs} + svr.listenChannels() + return svr, nil +} + +func (svr *sshServer) Wait() error { + return svr.sshConn.Wait() +} + +func (svr *sshServer) Close() error { + return svr.sshConn.Close() +} + +func (svr *sshServer) listenChannels() { + go func() { + for chanReq := range svr.newChans { + go svr.handleChanReq(chanReq) + } + }() + go func() { + for req := range svr.newReqs { + go svr.handleReq(req) + } + }() +} + +func (svr *sshServer) handleReq(req *ssh.Request) { + switch req.Type { + default: + rejectRequest(req) + } +} + +type sshChannelServer struct { + svr *sshServer + chanReq ssh.NewChannel + ch ssh.Channel + newReqs <-chan *ssh.Request +} + +type sshSessionChannelServer struct { + *sshChannelServer + env []string +} + +func (svr *sshServer) handleChanReq(chanReq ssh.NewChannel) { + fmt.Fprintf(sshServerDebugStream, "channel request: %v, extra: '%v'\n", chanReq.ChannelType(), hex.EncodeToString(chanReq.ExtraData())) + switch chanReq.ChannelType() { + case "session": + if ch, reqs, err := chanReq.Accept(); err != nil { + fmt.Fprintf(sshServerDebugStream, "fail to accept channel request: %v\n", err) + chanReq.Reject(ssh.ResourceShortage, "channel accept failure") + } else { + chsvr := &sshSessionChannelServer{ + sshChannelServer: &sshChannelServer{svr, chanReq, ch, reqs}, + env: append([]string{}, os.Environ()...), + } + chsvr.handle() + } + default: + chanReq.Reject(ssh.UnknownChannelType, "channel type is not a session") + } +} + +func (chsvr *sshSessionChannelServer) handle() { + // should maybe do something here... + go chsvr.handleReqs() +} + +func (chsvr *sshSessionChannelServer) handleReqs() { + for req := range chsvr.newReqs { + chsvr.handleReq(req) + } + fmt.Fprintf(sshServerDebugStream, "ssh server session channel complete\n") +} + +func (chsvr *sshSessionChannelServer) handleReq(req *ssh.Request) { + switch req.Type { + case "env": + chsvr.handleEnv(req) + case "subsystem": + chsvr.handleSubsystem(req) + default: + rejectRequest(req) + } +} + +func rejectRequest(req *ssh.Request) error { + fmt.Fprintf(sshServerDebugStream, "ssh rejecting request, type: %s\n", req.Type) + err := req.Reply(false, []byte{}) + if err != nil { + fmt.Fprintf(sshServerDebugStream, "ssh request reply had error: %v\n", err) + } + return err +} + +func rejectRequestUnmarshalError(req *ssh.Request, s interface{}, err error) error { + fmt.Fprintf(sshServerDebugStream, "ssh request unmarshaling error, type '%T': %v\n", s, err) + rejectRequest(req) + return err +} + +// env request form: +type sshEnvRequest struct { + Envvar string + Value string +} + +func (chsvr *sshSessionChannelServer) handleEnv(req *ssh.Request) error { + envReq := &sshEnvRequest{} + if err := ssh.Unmarshal(req.Payload, envReq); err != nil { + return rejectRequestUnmarshalError(req, envReq, err) + } + req.Reply(true, nil) + + found := false + for i, envstr := range chsvr.env { + if strings.HasPrefix(envstr, envReq.Envvar+"=") { + found = true + chsvr.env[i] = envReq.Envvar + "=" + envReq.Value + } + } + if !found { + chsvr.env = append(chsvr.env, envReq.Envvar+"="+envReq.Value) + } + + return nil +} + +// Payload: int: command size, string: command +type sshSubsystemRequest struct { + Name string +} + +type sshSubsystemExitStatus struct { + Status uint32 +} + +func (chsvr *sshSessionChannelServer) handleSubsystem(req *ssh.Request) error { + defer func() { + err1 := chsvr.ch.CloseWrite() + err2 := chsvr.ch.Close() + fmt.Fprintf(sshServerDebugStream, "ssh server subsystem request complete, err: %v %v\n", err1, err2) + }() + + subsystemReq := &sshSubsystemRequest{} + if err := ssh.Unmarshal(req.Payload, subsystemReq); err != nil { + return rejectRequestUnmarshalError(req, subsystemReq, err) + } + + // reply to the ssh client + + // no idea if this is actually correct spec-wise. + // just enough for an sftp server to start. + if subsystemReq.Name != "sftp" { + return req.Reply(false, nil) + } + + req.Reply(true, nil) + + if !chsvr.svr.useSubsystem { + // use the openssh sftp server backend; this is to test the ssh code, not the sftp code, + // or is used for comparison between our sftp subsystem and the openssh sftp subsystem + cmd := exec.Command(*testSftp, "-e", "-l", "DEBUG") // log to stderr + cmd.Stdin = chsvr.ch + cmd.Stdout = chsvr.ch + cmd.Stderr = sftpServerDebugStream + if err := cmd.Start(); err != nil { + return err + } + return cmd.Wait() + } + + sftpServer, err := NewServer( + chsvr.ch, + WithDebug(sftpServerDebugStream), + ) + if err != nil { + return err + } + + // wait for the session to close + runErr := sftpServer.Serve() + exitStatus := uint32(1) + if runErr == nil { + exitStatus = uint32(0) + } + + _, exitStatusErr := chsvr.ch.SendRequest("exit-status", false, ssh.Marshal(sshSubsystemExitStatus{exitStatus})) + return exitStatusErr +} + +// starts an ssh server to test. returns: host string and port +func testServer(t *testing.T, useSubsystem bool, readonly bool) (net.Listener, string, int) { + if !*testIntegration { + t.Skip("skipping integration test") + } + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + + host, portStr, err := net.SplitHostPort(listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + port, err := strconv.Atoi(portStr) + if err != nil { + t.Fatal(err) + } + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + fmt.Fprintf(sshServerDebugStream, "ssh server socket closed: %v\n", err) + break + } + + go func() { + defer conn.Close() + sshSvr, err := sshServerFromConn(conn, useSubsystem, basicServerConfig()) + if err != nil { + t.Error(err) + return + } + err = sshSvr.Wait() + fmt.Fprintf(sshServerDebugStream, "ssh server finished, err: %v\n", err) + }() + } + }() + + return listener, host, port +} + +func makeDummyKey() (string, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), crand.Reader) + if err != nil { + return "", fmt.Errorf("cannot generate key: %v", err) + } + der, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return "", fmt.Errorf("cannot marshal key: %v", err) + } + block := &pem.Block{Type: "EC PRIVATE KEY", Bytes: der} + f, err := ioutil.TempFile("", "sftp-test-key-") + if err != nil { + return "", fmt.Errorf("cannot create temp file: %v", err) + } + defer func() { + if f != nil { + _ = f.Close() + _ = os.Remove(f.Name()) + } + }() + if err := pem.Encode(f, block); err != nil { + return "", fmt.Errorf("cannot write key: %v", err) + } + if err := f.Close(); err != nil { + return "", fmt.Errorf("error closing key file: %v", err) + } + path := f.Name() + f = nil + return path, nil +} + +func runSftpClient(t *testing.T, script string, path string, host string, port int) (string, error) { + // if sftp client binary is unavailable, skip test + if _, err := os.Stat(*testSftpClientBin); err != nil { + t.Skip("sftp client binary unavailable") + } + + // make a dummy key so we don't rely on ssh-agent + dummyKey, err := makeDummyKey() + if err != nil { + return "", err + } + defer os.Remove(dummyKey) + + args := []string{ + // "-vvvv", + "-b", "-", + "-o", "StrictHostKeyChecking=no", + "-o", "LogLevel=ERROR", + "-o", "UserKnownHostsFile /dev/null", + // do not trigger ssh-agent prompting + "-o", "IdentityFile=" + dummyKey, + "-o", "IdentitiesOnly=yes", + "-P", fmt.Sprintf("%d", port), fmt.Sprintf("%s:%s", host, path), + } + cmd := exec.Command(*testSftpClientBin, args...) + var stdout bytes.Buffer + cmd.Stdin = bytes.NewBufferString(script) + cmd.Stdout = &stdout + cmd.Stderr = sftpClientDebugStream + if err := cmd.Start(); err != nil { + return "", err + } + err = cmd.Wait() + return string(stdout.Bytes()), err +} + +func TestServerCompareSubsystems(t *testing.T) { + listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY) + listenerOp, hostOp, portOp := testServer(t, OPENSSH_SFTP, READONLY) + defer listenerGo.Close() + defer listenerOp.Close() + + script := ` +ls / +ls -l / +ls /dev/ +ls -l /dev/ +ls -l /etc/ +ls -l /bin/ +ls -l /usr/bin/ +` + outputGo, err := runSftpClient(t, script, "/", hostGo, portGo) + if err != nil { + t.Fatal(err) + } + + outputOp, err := runSftpClient(t, script, "/", hostOp, portOp) + if err != nil { + t.Fatal(err) + } + + newlineRegex := regexp.MustCompile(`\r*\n`) + spaceRegex := regexp.MustCompile(`\s+`) + outputGoLines := newlineRegex.Split(outputGo, -1) + outputOpLines := newlineRegex.Split(outputOp, -1) + + for i, goLine := range outputGoLines { + if i > len(outputOpLines) { + t.Fatalf("output line count differs") + } + opLine := outputOpLines[i] + bad := false + if goLine != opLine { + goWords := spaceRegex.Split(goLine, -1) + opWords := spaceRegex.Split(opLine, -1) + // allow words[2] and [3] to be different as these are users & groups + // also allow words[1] to differ as the link count for directories like + // proc is unstable during testing as processes are created/destroyed. + for j, goWord := range goWords { + if j > len(opWords) { + bad = true + } + opWord := opWords[j] + if goWord != opWord && j != 1 && j != 2 && j != 3 { + bad = true + } + } + } + + if bad { + t.Errorf("outputs differ, go:\n%v\nopenssh:\n%v\n", goLine, opLine) + } + } +} + +var rng = rand.New(rand.NewSource(time.Now().Unix())) + +func randData(length int) []byte { + data := make([]byte, length) + for i := 0; i < length; i++ { + data[i] = byte(rng.Uint32()) + } + return data +} + +func randName() string { + return "sftp." + hex.EncodeToString(randData(16)) +} + +func TestServerMkdirRmdir(t *testing.T) { + listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY) + defer listenerGo.Close() + + tmpDir := "/tmp/" + randName() + defer os.RemoveAll(tmpDir) + + // mkdir remote + if _, err := runSftpClient(t, "mkdir "+tmpDir, "/", hostGo, portGo); err != nil { + t.Fatal(err) + } + + // directory should now exist + if _, err := os.Stat(tmpDir); err != nil { + t.Fatal(err) + } + + // now remove the directory + if _, err := runSftpClient(t, "rmdir "+tmpDir, "/", hostGo, portGo); err != nil { + t.Fatal(err) + } + + if _, err := os.Stat(tmpDir); err == nil { + t.Fatal("should have error after deleting the directory") + } +} + +func TestServerSymlink(t *testing.T) { + listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY) + defer listenerGo.Close() + + link := "/tmp/" + randName() + defer os.RemoveAll(link) + + // now create a symbolic link within the new directory + if output, err := runSftpClient(t, "symlink /bin/sh "+link, "/", hostGo, portGo); err != nil { + t.Fatalf("failed: %v %v", err, string(output)) + } + + // symlink should now exist + if stat, err := os.Lstat(link); err != nil { + t.Fatal(err) + } else if (stat.Mode() & os.ModeSymlink) != os.ModeSymlink { + t.Fatalf("is not a symlink: %v", stat.Mode()) + } +} + +func TestServerPut(t *testing.T) { + listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY) + defer listenerGo.Close() + + tmpFileLocal := "/tmp/" + randName() + tmpFileRemote := "/tmp/" + randName() + defer os.RemoveAll(tmpFileLocal) + defer os.RemoveAll(tmpFileRemote) + + t.Logf("put: local %v remote %v", tmpFileLocal, tmpFileRemote) + + // create a file with random contents. This will be the local file pushed to the server + tmpFileLocalData := randData(10 * 1024 * 1024) + if err := ioutil.WriteFile(tmpFileLocal, tmpFileLocalData, 0644); err != nil { + t.Fatal(err) + } + + // sftp the file to the server + if output, err := runSftpClient(t, "put "+tmpFileLocal+" "+tmpFileRemote, "/", hostGo, portGo); err != nil { + t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output) + } + + // tmpFile2 should now exist, with the same contents + if tmpFileRemoteData, err := ioutil.ReadFile(tmpFileRemote); err != nil { + t.Fatal(err) + } else if string(tmpFileLocalData) != string(tmpFileRemoteData) { + t.Fatal("contents of file incorrect after put") + } +} + +func TestServerGet(t *testing.T) { + listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY) + defer listenerGo.Close() + + tmpFileLocal := "/tmp/" + randName() + tmpFileRemote := "/tmp/" + randName() + defer os.RemoveAll(tmpFileLocal) + defer os.RemoveAll(tmpFileRemote) + + t.Logf("get: local %v remote %v", tmpFileLocal, tmpFileRemote) + + // create a file with random contents. This will be the remote file pulled from the server + tmpFileRemoteData := randData(10 * 1024 * 1024) + if err := ioutil.WriteFile(tmpFileRemote, tmpFileRemoteData, 0644); err != nil { + t.Fatal(err) + } + + // sftp the file to the server + if output, err := runSftpClient(t, "get "+tmpFileRemote+" "+tmpFileLocal, "/", hostGo, portGo); err != nil { + t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output) + } + + // tmpFile2 should now exist, with the same contents + if tmpFileLocalData, err := ioutil.ReadFile(tmpFileLocal); err != nil { + t.Fatal(err) + } else if string(tmpFileLocalData) != string(tmpFileRemoteData) { + t.Fatal("contents of file incorrect after put") + } +} + +func compareDirectoriesRecursive(t *testing.T, aroot, broot string) { + walker := fs.Walk(aroot) + for walker.Step() { + if err := walker.Err(); err != nil { + t.Fatal(err) + } + // find paths + aPath := walker.Path() + aRel, err := filepath.Rel(aroot, aPath) + if err != nil { + t.Fatalf("could not find relative path for %v: %v", aPath, err) + } + bPath := path.Join(broot, aRel) + + if aRel == "." { + continue + } + + //t.Logf("comparing: %v a: %v b %v", aRel, aPath, bPath) + + // if a is a link, the sftp recursive copy won't have copied it. ignore + aLink, err := os.Lstat(aPath) + if err != nil { + t.Fatalf("could not lstat %v: %v", aPath, err) + } + if aLink.Mode()&os.ModeSymlink != 0 { + continue + } + + // stat the files + aFile, err := os.Stat(aPath) + if err != nil { + t.Fatalf("could not stat %v: %v", aPath, err) + } + bFile, err := os.Stat(bPath) + if err != nil { + t.Fatalf("could not stat %v: %v", bPath, err) + } + + // compare stats, with some leniency for the timestamp + if aFile.Mode() != bFile.Mode() { + t.Fatalf("modes different for %v: %v vs %v", aRel, aFile.Mode(), bFile.Mode()) + } + if !aFile.IsDir() { + if aFile.Size() != bFile.Size() { + t.Fatalf("sizes different for %v: %v vs %v", aRel, aFile.Size(), bFile.Size()) + } + } + timeDiff := aFile.ModTime().Sub(bFile.ModTime()) + if timeDiff > time.Second || timeDiff < -time.Second { + t.Fatalf("mtimes different for %v: %v vs %v", aRel, aFile.ModTime(), bFile.ModTime()) + } + + // compare contents + if !aFile.IsDir() { + if aContents, err := ioutil.ReadFile(aPath); err != nil { + t.Fatal(err) + } else if bContents, err := ioutil.ReadFile(bPath); err != nil { + t.Fatal(err) + } else if string(aContents) != string(bContents) { + t.Fatalf("contents different for %v", aRel) + } + } + } +} + +func TestServerPutRecursive(t *testing.T) { + listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY) + defer listenerGo.Close() + + dirLocal, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + tmpDirRemote := "/tmp/" + randName() + defer os.RemoveAll(tmpDirRemote) + + t.Logf("put recursive: local %v remote %v", dirLocal, tmpDirRemote) + + // push this directory (source code etc) recursively to the server + if output, err := runSftpClient(t, "mkdir "+tmpDirRemote+"\r\nput -r -P "+dirLocal+"/ "+tmpDirRemote+"/", "/", hostGo, portGo); err != nil { + t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output) + } + + compareDirectoriesRecursive(t, dirLocal, path.Join(tmpDirRemote, path.Base(dirLocal))) +} + +func TestServerGetRecursive(t *testing.T) { + listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY) + defer listenerGo.Close() + + dirRemote, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + tmpDirLocal := "/tmp/" + randName() + defer os.RemoveAll(tmpDirLocal) + + t.Logf("get recursive: local %v remote %v", tmpDirLocal, dirRemote) + + // pull this directory (source code etc) recursively from the server + if output, err := runSftpClient(t, "lmkdir "+tmpDirLocal+"\r\nget -r -P "+dirRemote+"/ "+tmpDirLocal+"/", "/", hostGo, portGo); err != nil { + t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output) + } + + compareDirectoriesRecursive(t, dirRemote, path.Join(tmpDirLocal, path.Base(dirRemote))) +} diff --git a/vendor/github.com/pkg/sftp/server_statvfs_darwin.go b/vendor/github.com/pkg/sftp/server_statvfs_darwin.go new file mode 100644 index 0000000..8c01dac --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_statvfs_darwin.go @@ -0,0 +1,21 @@ +package sftp + +import ( + "syscall" +) + +func statvfsFromStatfst(stat *syscall.Statfs_t) (*StatVFS, error) { + return &StatVFS{ + Bsize: uint64(stat.Bsize), + Frsize: uint64(stat.Bsize), // fragment size is a linux thing; use block size here + Blocks: stat.Blocks, + Bfree: stat.Bfree, + Bavail: stat.Bavail, + Files: stat.Files, + Ffree: stat.Ffree, + Favail: stat.Ffree, // not sure how to calculate Favail + Fsid: uint64(uint64(stat.Fsid.Val[1])<<32 | uint64(stat.Fsid.Val[0])), // endianness? + Flag: uint64(stat.Flags), // assuming POSIX? + Namemax: 1024, // man 2 statfs shows: #define MAXPATHLEN 1024 + }, nil +} diff --git a/vendor/github.com/pkg/sftp/server_statvfs_impl.go b/vendor/github.com/pkg/sftp/server_statvfs_impl.go new file mode 100644 index 0000000..c26870e --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_statvfs_impl.go @@ -0,0 +1,25 @@ +// +build darwin linux,!gccgo + +// fill in statvfs structure with OS specific values +// Statfs_t is different per-kernel, and only exists on some unixes (not Solaris for instance) + +package sftp + +import ( + "syscall" +) + +func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error { + stat := &syscall.Statfs_t{} + if err := syscall.Statfs(p.Path, stat); err != nil { + return svr.sendPacket(statusFromError(p, err)) + } + + retPkt, err := statvfsFromStatfst(stat) + if err != nil { + return svr.sendPacket(statusFromError(p, err)) + } + retPkt.ID = p.ID + + return svr.sendPacket(retPkt) +} diff --git a/vendor/github.com/pkg/sftp/server_statvfs_linux.go b/vendor/github.com/pkg/sftp/server_statvfs_linux.go new file mode 100644 index 0000000..43478e8 --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_statvfs_linux.go @@ -0,0 +1,22 @@ +// +build !gccgo,linux + +package sftp + +import ( + "syscall" +) + +func statvfsFromStatfst(stat *syscall.Statfs_t) (*StatVFS, error) { + return &StatVFS{ + Bsize: uint64(stat.Bsize), + Frsize: uint64(stat.Frsize), + Blocks: stat.Blocks, + Bfree: stat.Bfree, + Bavail: stat.Bavail, + Files: stat.Files, + Ffree: stat.Ffree, + Favail: stat.Ffree, // not sure how to calculate Favail + Flag: uint64(stat.Flags), // assuming POSIX? + Namemax: uint64(stat.Namelen), + }, nil +} diff --git a/vendor/github.com/pkg/sftp/server_statvfs_stubs.go b/vendor/github.com/pkg/sftp/server_statvfs_stubs.go new file mode 100644 index 0000000..1512a13 --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_statvfs_stubs.go @@ -0,0 +1,11 @@ +// +build !darwin,!linux gccgo + +package sftp + +import ( + "syscall" +) + +func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error { + return syscall.ENOTSUP +} diff --git a/vendor/github.com/pkg/sftp/server_stubs.go b/vendor/github.com/pkg/sftp/server_stubs.go new file mode 100644 index 0000000..a14c734 --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_stubs.go @@ -0,0 +1,32 @@ +// +build !cgo,!plan9 windows android + +package sftp + +import ( + "os" + "time" + "fmt" +) + +func runLs(dirname string, dirent os.FileInfo) string { + typeword := runLsTypeWord(dirent) + numLinks := 1 + if dirent.IsDir() { + numLinks = 0 + } + username := "root" + groupname := "root" + mtime := dirent.ModTime() + monthStr := mtime.Month().String()[0:3] + day := mtime.Day() + year := mtime.Year() + now := time.Now() + isOld := mtime.Before(now.Add(-time.Hour * 24 * 365 / 2)) + + yearOrTime := fmt.Sprintf("%02d:%02d", mtime.Hour(), mtime.Minute()) + if isOld { + yearOrTime = fmt.Sprintf("%d", year) + } + + return fmt.Sprintf("%s %4d %-8s %-8s %8d %s %2d %5s %s", typeword, numLinks, username, groupname, dirent.Size(), monthStr, day, yearOrTime, dirent.Name()) +} diff --git a/vendor/github.com/pkg/sftp/server_test.go b/vendor/github.com/pkg/sftp/server_test.go new file mode 100644 index 0000000..f2f034a --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_test.go @@ -0,0 +1,243 @@ +package sftp + +import ( + "testing" + "os" + "regexp" + "time" + "io" + "sync" +) + +const ( + typeDirectory = "d" + typeFile = "[^d]" +) + +func TestRunLsWithExamplesDirectory(t *testing.T) { + path := "examples" + item, _ := os.Stat(path) + result := runLs(path, item) + runLsTestHelper(t, result, typeDirectory, path) +} + +func TestRunLsWithLicensesFile(t *testing.T) { + path := "LICENSE" + item, _ := os.Stat(path) + result := runLs(path, item) + runLsTestHelper(t, result, typeFile, path) +} + +/* + The format of the `longname' field is unspecified by this protocol. + It MUST be suitable for use in the output of a directory listing + command (in fact, the recommended operation for a directory listing + command is to simply display this data). However, clients SHOULD NOT + attempt to parse the longname field for file attributes; they SHOULD + use the attrs field instead. + + The recommended format for the longname field is as follows: + + -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer + 1234567890 123 12345678 12345678 12345678 123456789012 + + Here, the first line is sample output, and the second field indicates + widths of the various fields. Fields are separated by spaces. The + first field lists file permissions for user, group, and others; the + second field is link count; the third field is the name of the user + who owns the file; the fourth field is the name of the group that + owns the file; the fifth field is the size of the file in bytes; the + sixth field (which actually may contain spaces, but is fixed to 12 + characters) is the file modification time, and the seventh field is + the file name. Each field is specified to be a minimum of certain + number of character positions (indicated by the second line above), + but may also be longer if the data does not fit in the specified + length. + + The SSH_FXP_ATTRS response has the following format: + + uint32 id + ATTRS attrs + + where `id' is the request identifier, and `attrs' is the returned + file attributes as described in Section ``File Attributes''. + */ +func runLsTestHelper(t *testing.T, result, expectedType, path string) { + // using regular expressions to make tests work on all systems + // a virtual file system (like afero) would be needed to mock valid filesystem checks + // expected layout is: + // drwxr-xr-x 8 501 20 272 Aug 9 19:46 examples + + // permissions (len 10, "drwxr-xr-x") + got := result[0:10] + if ok, err := regexp.MatchString("^"+expectedType+"[rwx-]{9}$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): permission field mismatch, expected dir, got: %#v, err: %#v", path, got, err) + } + + // space + got = result[10:11] + if ok, err := regexp.MatchString("^\\s$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): spacer 1 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) + } + + // link count (len 3, number) + got = result[12:15] + if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): link count field mismatch, got: %#v, err: %#v", path, got, err) + } + + // spacer + got = result[15:16] + if ok, err := regexp.MatchString("^\\s$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): spacer 2 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) + } + + // username / uid (len 8, number or string) + got = result[16:24] + if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): username / uid mismatch, expected user, got: %#v, err: %#v", path, got, err) + } + + // spacer + got = result[24:25] + if ok, err := regexp.MatchString("^\\s$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): spacer 3 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) + } + + // groupname / gid (len 8, number or string) + got = result[25:33] + if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): groupname / gid mismatch, expected group, got: %#v, err: %#v", path, got, err) + } + + // spacer + got = result[33:34] + if ok, err := regexp.MatchString("^\\s$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): spacer 4 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) + } + + // filesize (len 8) + got = result[34:42] + if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): filesize field mismatch, expected size in bytes, got: %#v, err: %#v", path, got, err) + } + + // spacer + got = result[42:43] + if ok, err := regexp.MatchString("^\\s$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): spacer 5 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) + } + + // mod time (len 12, e.g. Aug 9 19:46) + got = result[43:55] + layout := "Jan 2 15:04" + _, err := time.Parse(layout, got) + + if err != nil { + layout = "Jan 2 2006" + _, err = time.Parse(layout, got) + } + if err != nil { + t.Errorf("runLs(%#v, *FileInfo): mod time field mismatch, expected date layout %s, got: %#v, err: %#v", path, layout, got, err) + } + + // spacer + got = result[55:56] + if ok, err := regexp.MatchString("^\\s$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): spacer 6 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) + } + + // filename + got = result[56:] + if ok, err := regexp.MatchString("^"+path+"$", got); !ok { + t.Errorf("runLs(%#v, *FileInfo): name field mismatch, expected examples, got: %#v, err: %#v", path, got, err) + } +} + +func clientServerPair(t *testing.T) (*Client, *Server) { + cr, sw := io.Pipe() + sr, cw := io.Pipe() + server, err := NewServer(struct { + io.Reader + io.WriteCloser + }{sr, sw}) + if err != nil { + t.Fatal(err) + } + go server.Serve() + client, err := NewClientPipe(cr, cw) + if err != nil { + t.Fatalf("%+v\n", err) + } + return client, server +} + +type sshFxpTestBadExtendedPacket struct { + ID uint32 + Extension string + Data string +} + +func (p sshFxpTestBadExtendedPacket) id() uint32 { return p.ID } + +func (p sshFxpTestBadExtendedPacket) MarshalBinary() ([]byte, error) { + l := 1 + 4 + 4 + // type(byte) + uint32 + uint32 + len(p.Extension) + + len(p.Data) + + b := make([]byte, 0, l) + b = append(b, ssh_FXP_EXTENDED) + b = marshalUint32(b, p.ID) + b = marshalString(b, p.Extension) + b = marshalString(b, p.Data) + return b, nil +} + +// test that errors are sent back when we request an invalid extended packet operation +func TestInvalidExtendedPacket(t *testing.T) { + client, server := clientServerPair(t) + defer client.Close() + defer server.Close() + + badPacket := sshFxpTestBadExtendedPacket{client.nextID(), "thisDoesn'tExist", "foobar"} + _, _, err := client.clientConn.sendPacket(badPacket) + if err == nil { + t.Fatal("expected error from bad packet") + } + + // try to stat a file; the client should have shut down. + filePath := "/etc/passwd" + _, err = client.Stat(filePath) + if err == nil { + t.Fatal("expected error from closed connection") + } + +} + +// test that server handles concurrent requests correctly +func TestConcurrentRequests(t *testing.T) { + client, server := clientServerPair(t) + defer client.Close() + defer server.Close() + + concurrency := 2 + var wg sync.WaitGroup + wg.Add(concurrency) + + for i := 0; i < concurrency; i++ { + go func() { + defer wg.Done() + + for j := 0; j < 1024; j++ { + f, err := client.Open("/etc/passwd") + if err != nil { + t.Errorf("failed to open file: %v", err) + } + if err := f.Close(); err != nil { + t.Errorf("failed t close file: %v", err) + } + } + }() + } + wg.Wait() +} diff --git a/vendor/github.com/pkg/sftp/server_unix.go b/vendor/github.com/pkg/sftp/server_unix.go new file mode 100644 index 0000000..8d0c138 --- /dev/null +++ b/vendor/github.com/pkg/sftp/server_unix.go @@ -0,0 +1,54 @@ +// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris +// +build cgo + +package sftp + +import ( + "fmt" + "os" + "path" + "syscall" + "time" +) + +func runLsStatt(dirent os.FileInfo, statt *syscall.Stat_t) string { + // example from openssh sftp server: + // crw-rw-rw- 1 root wheel 0 Jul 31 20:52 ttyvd + // format: + // {directory / char device / etc}{rwxrwxrwx} {number of links} owner group size month day [time (this year) | year (otherwise)] name + + typeword := runLsTypeWord(dirent) + numLinks := statt.Nlink + uid := statt.Uid + gid := statt.Gid + username := fmt.Sprintf("%d", uid) + groupname := fmt.Sprintf("%d", gid) + // TODO FIXME: uid -> username, gid -> groupname lookup for ls -l format output + + mtime := dirent.ModTime() + monthStr := mtime.Month().String()[0:3] + day := mtime.Day() + year := mtime.Year() + now := time.Now() + isOld := mtime.Before(now.Add(-time.Hour * 24 * 365 / 2)) + + yearOrTime := fmt.Sprintf("%02d:%02d", mtime.Hour(), mtime.Minute()) + if isOld { + yearOrTime = fmt.Sprintf("%d", year) + } + + return fmt.Sprintf("%s %4d %-8s %-8s %8d %s %2d %5s %s", typeword, numLinks, username, groupname, dirent.Size(), monthStr, day, yearOrTime, dirent.Name()) +} + +// ls -l style output for a file, which is in the 'long output' section of a readdir response packet +// this is a very simple (lazy) implementation, just enough to look almost like openssh in a few basic cases +func runLs(dirname string, dirent os.FileInfo) string { + dsys := dirent.Sys() + if dsys == nil { + } else if statt, ok := dsys.(*syscall.Stat_t); !ok { + } else { + return runLsStatt(dirent, statt) + } + + return path.Join(dirname, dirent.Name()) +} diff --git a/vendor/github.com/pkg/sftp/sftp.go b/vendor/github.com/pkg/sftp/sftp.go new file mode 100644 index 0000000..3cdb14d --- /dev/null +++ b/vendor/github.com/pkg/sftp/sftp.go @@ -0,0 +1,217 @@ +// Package sftp implements the SSH File Transfer Protocol as described in +// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02 +package sftp + +import ( + "fmt" + + "github.com/pkg/errors" +) + +const ( + ssh_FXP_INIT = 1 + ssh_FXP_VERSION = 2 + ssh_FXP_OPEN = 3 + ssh_FXP_CLOSE = 4 + ssh_FXP_READ = 5 + ssh_FXP_WRITE = 6 + ssh_FXP_LSTAT = 7 + ssh_FXP_FSTAT = 8 + ssh_FXP_SETSTAT = 9 + ssh_FXP_FSETSTAT = 10 + ssh_FXP_OPENDIR = 11 + ssh_FXP_READDIR = 12 + ssh_FXP_REMOVE = 13 + ssh_FXP_MKDIR = 14 + ssh_FXP_RMDIR = 15 + ssh_FXP_REALPATH = 16 + ssh_FXP_STAT = 17 + ssh_FXP_RENAME = 18 + ssh_FXP_READLINK = 19 + ssh_FXP_SYMLINK = 20 + ssh_FXP_STATUS = 101 + ssh_FXP_HANDLE = 102 + ssh_FXP_DATA = 103 + ssh_FXP_NAME = 104 + ssh_FXP_ATTRS = 105 + ssh_FXP_EXTENDED = 200 + ssh_FXP_EXTENDED_REPLY = 201 +) + +const ( + ssh_FX_OK = 0 + ssh_FX_EOF = 1 + ssh_FX_NO_SUCH_FILE = 2 + ssh_FX_PERMISSION_DENIED = 3 + ssh_FX_FAILURE = 4 + ssh_FX_BAD_MESSAGE = 5 + ssh_FX_NO_CONNECTION = 6 + ssh_FX_CONNECTION_LOST = 7 + ssh_FX_OP_UNSUPPORTED = 8 + + // see draft-ietf-secsh-filexfer-13 + // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1 + ssh_FX_INVALID_HANDLE = 9 + ssh_FX_NO_SUCH_PATH = 10 + ssh_FX_FILE_ALREADY_EXISTS = 11 + ssh_FX_WRITE_PROTECT = 12 + ssh_FX_NO_MEDIA = 13 + ssh_FX_NO_SPACE_ON_FILESYSTEM = 14 + ssh_FX_QUOTA_EXCEEDED = 15 + ssh_FX_UNKNOWN_PRINCIPAL = 16 + ssh_FX_LOCK_CONFLICT = 17 + ssh_FX_DIR_NOT_EMPTY = 18 + ssh_FX_NOT_A_DIRECTORY = 19 + ssh_FX_INVALID_FILENAME = 20 + ssh_FX_LINK_LOOP = 21 + ssh_FX_CANNOT_DELETE = 22 + ssh_FX_INVALID_PARAMETER = 23 + ssh_FX_FILE_IS_A_DIRECTORY = 24 + ssh_FX_BYTE_RANGE_LOCK_CONFLICT = 25 + ssh_FX_BYTE_RANGE_LOCK_REFUSED = 26 + ssh_FX_DELETE_PENDING = 27 + ssh_FX_FILE_CORRUPT = 28 + ssh_FX_OWNER_INVALID = 29 + ssh_FX_GROUP_INVALID = 30 + ssh_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31 +) + +const ( + ssh_FXF_READ = 0x00000001 + ssh_FXF_WRITE = 0x00000002 + ssh_FXF_APPEND = 0x00000004 + ssh_FXF_CREAT = 0x00000008 + ssh_FXF_TRUNC = 0x00000010 + ssh_FXF_EXCL = 0x00000020 +) + +type fxp uint8 + +func (f fxp) String() string { + switch f { + case ssh_FXP_INIT: + return "SSH_FXP_INIT" + case ssh_FXP_VERSION: + return "SSH_FXP_VERSION" + case ssh_FXP_OPEN: + return "SSH_FXP_OPEN" + case ssh_FXP_CLOSE: + return "SSH_FXP_CLOSE" + case ssh_FXP_READ: + return "SSH_FXP_READ" + case ssh_FXP_WRITE: + return "SSH_FXP_WRITE" + case ssh_FXP_LSTAT: + return "SSH_FXP_LSTAT" + case ssh_FXP_FSTAT: + return "SSH_FXP_FSTAT" + case ssh_FXP_SETSTAT: + return "SSH_FXP_SETSTAT" + case ssh_FXP_FSETSTAT: + return "SSH_FXP_FSETSTAT" + case ssh_FXP_OPENDIR: + return "SSH_FXP_OPENDIR" + case ssh_FXP_READDIR: + return "SSH_FXP_READDIR" + case ssh_FXP_REMOVE: + return "SSH_FXP_REMOVE" + case ssh_FXP_MKDIR: + return "SSH_FXP_MKDIR" + case ssh_FXP_RMDIR: + return "SSH_FXP_RMDIR" + case ssh_FXP_REALPATH: + return "SSH_FXP_REALPATH" + case ssh_FXP_STAT: + return "SSH_FXP_STAT" + case ssh_FXP_RENAME: + return "SSH_FXP_RENAME" + case ssh_FXP_READLINK: + return "SSH_FXP_READLINK" + case ssh_FXP_SYMLINK: + return "SSH_FXP_SYMLINK" + case ssh_FXP_STATUS: + return "SSH_FXP_STATUS" + case ssh_FXP_HANDLE: + return "SSH_FXP_HANDLE" + case ssh_FXP_DATA: + return "SSH_FXP_DATA" + case ssh_FXP_NAME: + return "SSH_FXP_NAME" + case ssh_FXP_ATTRS: + return "SSH_FXP_ATTRS" + case ssh_FXP_EXTENDED: + return "SSH_FXP_EXTENDED" + case ssh_FXP_EXTENDED_REPLY: + return "SSH_FXP_EXTENDED_REPLY" + default: + return "unknown" + } +} + +type fx uint8 + +func (f fx) String() string { + switch f { + case ssh_FX_OK: + return "SSH_FX_OK" + case ssh_FX_EOF: + return "SSH_FX_EOF" + case ssh_FX_NO_SUCH_FILE: + return "SSH_FX_NO_SUCH_FILE" + case ssh_FX_PERMISSION_DENIED: + return "SSH_FX_PERMISSION_DENIED" + case ssh_FX_FAILURE: + return "SSH_FX_FAILURE" + case ssh_FX_BAD_MESSAGE: + return "SSH_FX_BAD_MESSAGE" + case ssh_FX_NO_CONNECTION: + return "SSH_FX_NO_CONNECTION" + case ssh_FX_CONNECTION_LOST: + return "SSH_FX_CONNECTION_LOST" + case ssh_FX_OP_UNSUPPORTED: + return "SSH_FX_OP_UNSUPPORTED" + default: + return "unknown" + } +} + +type unexpectedPacketErr struct { + want, got uint8 +} + +func (u *unexpectedPacketErr) Error() string { + return fmt.Sprintf("sftp: unexpected packet: want %v, got %v", fxp(u.want), fxp(u.got)) +} + +func unimplementedPacketErr(u uint8) error { + return errors.Errorf("sftp: unimplemented packet type: got %v", fxp(u)) +} + +type unexpectedIDErr struct{ want, got uint32 } + +func (u *unexpectedIDErr) Error() string { + return fmt.Sprintf("sftp: unexpected id: want %v, got %v", u.want, u.got) +} + +func unimplementedSeekWhence(whence int) error { + return errors.Errorf("sftp: unimplemented seek whence %v", whence) +} + +func unexpectedCount(want, got uint32) error { + return errors.Errorf("sftp: unexpected count: want %v, got %v", want, got) +} + +type unexpectedVersionErr struct{ want, got uint32 } + +func (u *unexpectedVersionErr) Error() string { + return fmt.Sprintf("sftp: unexpected server version: want %v, got %v", u.want, u.got) +} + +// A StatusError is returned when an SFTP operation fails, and provides +// additional information about the failure. +type StatusError struct { + Code uint32 + msg, lang string +} + +func (s *StatusError) Error() string { return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code)) } diff --git a/vendor/github.com/yookoala/realpath/.travis.yml b/vendor/github.com/yookoala/realpath/.travis.yml new file mode 100644 index 0000000..354b7f8 --- /dev/null +++ b/vendor/github.com/yookoala/realpath/.travis.yml @@ -0,0 +1,8 @@ +language: go + +go: + - 1.1 + - 1.2 + - 1.3 + - 1.4 + - tip diff --git a/vendor/github.com/yookoala/realpath/LICENSE b/vendor/github.com/yookoala/realpath/LICENSE new file mode 100644 index 0000000..a9d6ab8 --- /dev/null +++ b/vendor/github.com/yookoala/realpath/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2012-2017 Taru Karttunen and Shu Hung Yeung (Koala Yeung) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/yookoala/realpath/Makefile b/vendor/github.com/yookoala/realpath/Makefile new file mode 100644 index 0000000..0caa317 --- /dev/null +++ b/vendor/github.com/yookoala/realpath/Makefile @@ -0,0 +1,11 @@ +# +# This Makefile is for testing code only +# To compile or install, please use `go get` +# Read README.md for details +# + +test: + go test -v + cd example && go build -v + +.PHONY: test \ No newline at end of file diff --git a/vendor/github.com/yookoala/realpath/README.md b/vendor/github.com/yookoala/realpath/README.md new file mode 100644 index 0000000..f6fc79e --- /dev/null +++ b/vendor/github.com/yookoala/realpath/README.md @@ -0,0 +1,60 @@ +realpath +======== + +[![Travis last test result on master][travis-shield]][travis-link] + +This is a implementation of realpath() function in Go (golang). + +If you provide it with a valid relative path / alias path, it will return you +with a string of its real absolute path in the system. + +The original version is created by Taru Karttunen in golang-nuts group. You +may read [the original post about it](https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/htns6YWMp7s). + +[travis-shield]: https://api.travis-ci.org/yookoala/realpath.svg?branch=master +[travis-link]: https://travis-ci.org/yookoala/realpath?branch=master + +Installation +------------ + +``` +go get github.com/yookoala/realpath +``` + + +Usage +----- + +```go +import "github.com/yookoala/realpath" + +func main() { + myRealpath, err := realpath.Realpath("/some/path") +} +``` + + +License +------- +The MIT License + +Copyright (c) 2012-2017 [Taru Karttunen](https://github.com/taruti/) and +[Koala Yeung](https://github.com/yookoala/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/yookoala/realpath/realpath.go b/vendor/github.com/yookoala/realpath/realpath.go new file mode 100644 index 0000000..c83bdb6 --- /dev/null +++ b/vendor/github.com/yookoala/realpath/realpath.go @@ -0,0 +1,116 @@ +// This software is distributed under the MIT License. +// +// You should have received a copy of the MIT License along with this program. +// If not, see + +package realpath + +import ( + "bytes" + "os" + "path/filepath" +) + +// Realpath returns the real path of a given file in the os +func Realpath(fpath string) (string, error) { + + if len(fpath) == 0 { + return "", os.ErrInvalid + } + + if !filepath.IsAbs(fpath) { + pwd, err := os.Getwd() + if err != nil { + return "", err + } + fpath = filepath.Join(pwd, fpath) + } + + path := []byte(fpath) + nlinks := 0 + start := 1 + prev := 1 + for start < len(path) { + c := nextComponent(path, start) + cur := c[start:] + + switch { + + case len(cur) == 0: + copy(path[start:], path[start+1:]) + path = path[0 : len(path)-1] + + case len(cur) == 1 && cur[0] == '.': + if start+2 < len(path) { + copy(path[start:], path[start+2:]) + } + path = path[0 : len(path)-2] + + case len(cur) == 2 && cur[0] == '.' && cur[1] == '.': + copy(path[prev:], path[start+2:]) + path = path[0 : len(path)+prev-(start+2)] + prev = 1 + start = 1 + + default: + + fi, err := os.Lstat(string(c)) + if err != nil { + return "", err + } + if isSymlink(fi) { + + nlinks++ + if nlinks > 16 { + return "", os.ErrInvalid + } + + var link string + link, err = os.Readlink(string(c)) + after := string(path[len(c):]) + + // switch symlink component with its real path + path = switchSymlinkCom(path, start, link, after) + + prev = 1 + start = 1 + } else { + // Directories + prev = start + start = len(c) + 1 + } + } + } + + for len(path) > 1 && path[len(path)-1] == os.PathSeparator { + path = path[0 : len(path)-1] + } + return string(path), nil + +} + +// test if a link is symbolic link +func isSymlink(fi os.FileInfo) bool { + return fi.Mode()&os.ModeSymlink == os.ModeSymlink +} + +// switch a symbolic link component to its real path +func switchSymlinkCom(path []byte, start int, link, after string) []byte { + + if link[0] == os.PathSeparator { + // Absolute links + return []byte(filepath.Join(link, after)) + } + + // Relative links + return []byte(filepath.Join(string(path[0:start]), link, after)) +} + +// get the next component +func nextComponent(path []byte, start int) []byte { + v := bytes.IndexByte(path[start:], os.PathSeparator) + if v < 0 { + return path + } + return path[0 : start+v] +} diff --git a/vendor/github.com/yookoala/realpath/realpath_test.go b/vendor/github.com/yookoala/realpath/realpath_test.go new file mode 100644 index 0000000..bcbca21 --- /dev/null +++ b/vendor/github.com/yookoala/realpath/realpath_test.go @@ -0,0 +1,54 @@ +package realpath + +import ( + "testing" +) + +var symlinkTests []symlinkTest + +type symlinkTest struct { + path []byte + start int + link string + after string + expect []byte +} + +func (t symlinkTest) expecting(result []byte) bool { + return string(result) == string(t.expect) +} + +func init() { + symlinkTests = []symlinkTest{ + symlinkTest{ + path: []byte("/absolute/path/link/test"), + start: 14, + link: "/another/absolute/path", + after: "/test", + expect: []byte("/another/absolute/path/test"), + }, + symlinkTest{ + path: []byte("/absolute/path/link/test"), + start: 14, + link: "./relative/path2", + after: "/test", + expect: []byte("/absolute/path/relative/path2/test"), + }, + symlinkTest{ + path: []byte("/absolute/path/link/test"), + start: 14, + link: "../relative/path2", + after: "/test", + expect: []byte("/absolute/relative/path2/test"), + }, + } +} + +func TestSwitchSymlinkCom(t *testing.T) { + for i, tt := range symlinkTests { + if r := switchSymlinkCom(tt.path, tt.start, tt.link, tt.after); !tt.expecting(r) { + t.Errorf("Failed test %d.\nExpected: \"%s\"\nActually: \"%s\"", + i, tt.expect, r) + } + } +} diff --git a/vendor/github.com/yuin/gluamapper/.travis.yml b/vendor/github.com/yuin/gluamapper/.travis.yml new file mode 100644 index 0000000..a760e01 --- /dev/null +++ b/vendor/github.com/yuin/gluamapper/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - 1.3 + - 1.4 + +install: + - go get github.com/mitchellh/mapstructure + - go get github.com/yuin/gopher-lua + +script: + - go test -v diff --git a/vendor/github.com/yuin/gluamapper/LICENSE b/vendor/github.com/yuin/gluamapper/LICENSE new file mode 100644 index 0000000..4daf480 --- /dev/null +++ b/vendor/github.com/yuin/gluamapper/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Yusuke Inuzuka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/yuin/gluamapper/README.rst b/vendor/github.com/yuin/gluamapper/README.rst new file mode 100644 index 0000000..8ddbb24 --- /dev/null +++ b/vendor/github.com/yuin/gluamapper/README.rst @@ -0,0 +1,79 @@ +=============================================================================== +gluamapper: maps a GopherLua table to a Go struct +=============================================================================== + +.. image:: https://godoc.org/github.com/yuin/gluamapper?status.svg + :target: http://godoc.org/github.com/yuin/gluamapper + +.. image:: https://travis-ci.org/yuin/gluamapper.svg + :target: https://travis-ci.org/yuin/gluamapper + +| + +gluamapper provides an easy way to map GopherLua tables to Go structs. + +gluamapper converts a GopherLua table to ``map[string]interface{}`` , and then converts it to a Go struct using `mapstructure `_ . + +---------------------------------------------------------------- +Installation +---------------------------------------------------------------- + +.. code-block:: bash + + go get github.com/yuin/gluamapper + +---------------------------------------------------------------- +API +---------------------------------------------------------------- +See `Go doc `_ . + +---------------------------------------------------------------- +Usage +---------------------------------------------------------------- + +.. code-block:: go + + type Role struct { + Name string + } + + type Person struct { + Name string + Age int + WorkPlace string + Role []*Role + } + + L := lua.NewState() + if err := L.DoString(` + person = { + name = "Michel", + age = "31", -- weakly input + work_place = "San Jose", + role = { + { + name = "Administrator" + }, + { + name = "Operator" + } + } + } + `); err != nil { + panic(err) + } + var person Person + if err := gluamapper.Map(L.GetGlobal("person").(*lua.LTable), &person); err != nil { + panic(err) + } + fmt.Printf("%s %d", person.Name, person.Age) + +---------------------------------------------------------------- +License +---------------------------------------------------------------- +MIT + +---------------------------------------------------------------- +Author +---------------------------------------------------------------- +Yusuke Inuzuka diff --git a/vendor/github.com/yuin/gluamapper/gluamapper.go b/vendor/github.com/yuin/gluamapper/gluamapper.go new file mode 100644 index 0000000..f23cc66 --- /dev/null +++ b/vendor/github.com/yuin/gluamapper/gluamapper.go @@ -0,0 +1,108 @@ +// gluamapper provides an easy way to map GopherLua tables to Go structs. +package gluamapper + +import ( + "errors" + "fmt" + "github.com/mitchellh/mapstructure" + "github.com/yuin/gopher-lua" + "regexp" + "strings" +) + +// Option is a configuration that is used to create a new mapper. +type Option struct { + // Function to convert a lua table key to Go's one. This defaults to "ToUpperCamelCase". + NameFunc func(string) string + + // Returns error if unused keys exist. + ErrorUnused bool + + // A struct tag name for lua table keys . This defaults to "gluamapper" + TagName string +} + +// Mapper maps a lua table to a Go struct pointer. +type Mapper struct { + Option Option +} + +// NewMapper returns a new mapper. +func NewMapper(opt Option) *Mapper { + if opt.NameFunc == nil { + opt.NameFunc = ToUpperCamelCase + } + if opt.TagName == "" { + opt.TagName = "gluamapper" + } + return &Mapper{opt} +} + +// Map maps the lua table to the given struct pointer. +func (mapper *Mapper) Map(tbl *lua.LTable, st interface{}) error { + opt := mapper.Option + mp, ok := ToGoValue(tbl, opt).(map[interface{}]interface{}) + if !ok { + return errors.New("arguments #1 must be a table, but got an array") + } + config := &mapstructure.DecoderConfig{ + WeaklyTypedInput: true, + Result: st, + TagName: opt.TagName, + ErrorUnused: opt.ErrorUnused, + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + return decoder.Decode(mp) +} + +// Map maps the lua table to the given struct pointer with default options. +func Map(tbl *lua.LTable, st interface{}) error { + return NewMapper(Option{}).Map(tbl, st) +} + + +// Id is an Option.NameFunc that returns given string as-is. +func Id(s string) string { + return s +} + +var camelre = regexp.MustCompile(`_([a-z])`) +// ToUpperCamelCase is an Option.NameFunc that converts strings from snake case to upper camel case. +func ToUpperCamelCase(s string) string { + return strings.ToUpper(string(s[0])) + camelre.ReplaceAllStringFunc(s[1:len(s)], func(s string) string { return strings.ToUpper(s[1:len(s)]) }) +} + +// ToGoValue converts the given LValue to a Go object. +func ToGoValue(lv lua.LValue, opt Option) interface{} { + switch v := lv.(type) { + case *lua.LNilType: + return nil + case lua.LBool: + return bool(v) + case lua.LString: + return string(v) + case lua.LNumber: + return float64(v) + case *lua.LTable: + maxn := v.MaxN() + if maxn == 0 { // table + ret := make(map[interface{}]interface{}) + v.ForEach(func(key, value lua.LValue) { + keystr := fmt.Sprint(ToGoValue(key, opt)) + ret[opt.NameFunc(keystr)] = ToGoValue(value, opt) + }) + return ret + } else { // array + ret := make([]interface{}, 0, maxn) + for i := 1; i <= maxn; i++ { + ret = append(ret, ToGoValue(v.RawGetInt(i), opt)) + } + return ret + } + default: + return v + } +} diff --git a/vendor/github.com/yuin/gluamapper/gluamapper_example_test.go b/vendor/github.com/yuin/gluamapper/gluamapper_example_test.go new file mode 100644 index 0000000..0caff72 --- /dev/null +++ b/vendor/github.com/yuin/gluamapper/gluamapper_example_test.go @@ -0,0 +1,45 @@ +package gluamapper + +import ( + "fmt" + "github.com/yuin/gopher-lua" +) + +func ExampleMap() { + type Role struct { + Name string + } + + type Person struct { + Name string + Age int + WorkPlace string + Role []*Role + } + + L := lua.NewState() + if err := L.DoString(` + person = { + name = "Michel", + age = "31", -- weakly input + work_place = "San Jose", + role = { + { + name = "Administrator" + }, + { + name = "Operator" + } + } + } + `); err != nil { + panic(err) + } + var person Person + if err := Map(L.GetGlobal("person").(*lua.LTable), &person); err != nil { + panic(err) + } + fmt.Printf("%s %d", person.Name, person.Age) + // Output: + // Michel 31 +} diff --git a/vendor/github.com/yuin/gluamapper/gluamapper_test.go b/vendor/github.com/yuin/gluamapper/gluamapper_test.go new file mode 100644 index 0000000..265f5e5 --- /dev/null +++ b/vendor/github.com/yuin/gluamapper/gluamapper_test.go @@ -0,0 +1,139 @@ +package gluamapper + +import ( + "github.com/yuin/gopher-lua" + "path/filepath" + "runtime" + "testing" +) + +func errorIfNotEqual(t *testing.T, v1, v2 interface{}) { + if v1 != v2 { + _, file, line, _ := runtime.Caller(1) + t.Errorf("%v line %v: '%v' expected, but got '%v'", filepath.Base(file), line, v1, v2) + } +} + +type testRole struct { + Name string +} + +type testPerson struct { + Name string + Age int + WorkPlace string + Role []*testRole +} + +type testStruct struct { + Nil interface{} + Bool bool + String string + Number int `gluamapper:"number_value"` + Func interface{} +} + +func TestMap(t *testing.T) { + L := lua.NewState() + if err := L.DoString(` + person = { + name = "Michel", + age = "31", -- weakly input + work_place = "San Jose", + role = { + { + name = "Administrator" + }, + { + name = "Operator" + } + } + } + `); err != nil { + t.Error(err) + } + var person testPerson + if err := Map(L.GetGlobal("person").(*lua.LTable), &person); err != nil { + t.Error(err) + } + errorIfNotEqual(t, "Michel", person.Name) + errorIfNotEqual(t, 31, person.Age) + errorIfNotEqual(t, "San Jose", person.WorkPlace) + errorIfNotEqual(t, 2, len(person.Role)) + errorIfNotEqual(t, "Administrator", person.Role[0].Name) + errorIfNotEqual(t, "Operator", person.Role[1].Name) +} + +func TestTypes(t *testing.T) { + L := lua.NewState() + if err := L.DoString(` + tbl = { + ["Nil"] = nil, + ["Bool"] = true, + ["String"] = "string", + ["Number_value"] = 10, + ["Func"] = function() end + } + `); err != nil { + t.Error(err) + } + var stct testStruct + + if err := NewMapper(Option{NameFunc: Id}).Map(L.GetGlobal("tbl").(*lua.LTable), &stct); err != nil { + t.Error(err) + } + errorIfNotEqual(t, nil, stct.Nil) + errorIfNotEqual(t, true, stct.Bool) + errorIfNotEqual(t, "string", stct.String) + errorIfNotEqual(t, 10, stct.Number) +} + +func TestNameFunc(t *testing.T) { + L := lua.NewState() + if err := L.DoString(` + person = { + Name = "Michel", + Age = "31", -- weekly input + WorkPlace = "San Jose", + Role = { + { + Name = "Administrator" + }, + { + Name = "Operator" + } + } + } + `); err != nil { + t.Error(err) + } + var person testPerson + mapper := NewMapper(Option{NameFunc: Id}) + if err := mapper.Map(L.GetGlobal("person").(*lua.LTable), &person); err != nil { + t.Error(err) + } + errorIfNotEqual(t, "Michel", person.Name) + errorIfNotEqual(t, 31, person.Age) + errorIfNotEqual(t, "San Jose", person.WorkPlace) + errorIfNotEqual(t, 2, len(person.Role)) + errorIfNotEqual(t, "Administrator", person.Role[0].Name) + errorIfNotEqual(t, "Operator", person.Role[1].Name) +} + +func TestError(t *testing.T) { + L := lua.NewState() + tbl := L.NewTable() + L.SetField(tbl, "key", lua.LString("value")) + err := Map(tbl, 1) + if err.Error() != "result must be a pointer" { + t.Error("invalid error message") + } + + tbl = L.NewTable() + tbl.Append(lua.LNumber(1)) + var person testPerson + err = Map(tbl, &person) + if err.Error() != "arguments #1 must be a table, but got an array" { + t.Error("invalid error message") + } +} diff --git a/vendor/github.com/yuin/gluare/.travis.yml b/vendor/github.com/yuin/gluare/.travis.yml new file mode 100644 index 0000000..4c7d302 --- /dev/null +++ b/vendor/github.com/yuin/gluare/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - 1.4 + - 1.5 + - tip + +install: + - go get github.com/yuin/gopher-lua + +script: + - go test -v diff --git a/vendor/github.com/yuin/gluare/LICENSE b/vendor/github.com/yuin/gluare/LICENSE new file mode 100644 index 0000000..edd9a41 --- /dev/null +++ b/vendor/github.com/yuin/gluare/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Yusuke Inuzuka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/yuin/gluare/README.rst b/vendor/github.com/yuin/gluare/README.rst new file mode 100644 index 0000000..c1da3dd --- /dev/null +++ b/vendor/github.com/yuin/gluare/README.rst @@ -0,0 +1,62 @@ +=============================================================================== +gluare: Regular expressions for the GopherLua +=============================================================================== + +.. image:: https://travis-ci.org/yuin/gluare.svg + :target: https://travis-ci.org/yuin/gluare + +| + +gluare is a regular expression library for the `GopherLua `_ . +gluare has almost the same API as Lua pattern match. + +.. contents:: + :depth: 1 + +---------------------------------------------------------------- +Installation +---------------------------------------------------------------- + +.. code-block:: bash + + go get github.com/yuin/gluare + +---------------------------------------------------------------- +Usage +---------------------------------------------------------------- + +.. code-block:: go + + import ( + "github.com/yuin/gopher-lua" + "github.com/yuin/gluare" + ) + + L := lua.NewState() + defer L.Close() + L.PreloadModule("re", gluare.Loader) + +---------------------------------------------------------------- +Lua functions +---------------------------------------------------------------- + +`re.find` , `re.gsub`, `re.match`, `re.gmatch` are available. These functions have the same API as Lua pattern match. gluare uses the Go regexp package, so you can use regular expressions that are supported in the Go regexp package. + +In addition, the following functions are defined: + + +**gluare.quote(s string) -> string** + Arguments: + + =========================== ============================================== + s string a string value to escape meta characters + =========================== ============================================== + + + Returns: + + =========================== ============================================== + string escaped string + =========================== ============================================== + + gluare.quote returns a string that quotes all regular expression metacharacters inside the given text. diff --git a/vendor/github.com/yuin/gluare/gluare.go b/vendor/github.com/yuin/gluare/gluare.go new file mode 100644 index 0000000..cff8c70 --- /dev/null +++ b/vendor/github.com/yuin/gluare/gluare.go @@ -0,0 +1,284 @@ +package gluare + +import ( + "github.com/yuin/gopher-lua" + "regexp" + "strings" +) + +func Loader(L *lua.LState) int { + mod := L.SetFuncs(L.NewTable(), exports) + mod.RawSetString("gmatch", L.NewClosure(reGmatch, L.NewFunction(reGmatchIter))) + L.Push(mod) + return 1 +} + +var exports = map[string]lua.LGFunction{ + "find": reFind, + "gsub": reGsub, + "match": reMatch, + "quote": reQuote, +} + +func reQuote(L *lua.LState) int { + str := L.CheckString(1) + L.Push(lua.LString(regexp.QuoteMeta(str))) + return 1 +} + +func reFind(L *lua.LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + if len(str) == 0 && len(pattern) == 0 { + L.Push(lua.LNumber(1)) + L.Push(lua.LNumber(0)) + return 2 + } + init := luaIndex2StringIndex(str, L.OptInt(3, 1), true) + plain := false + if L.GetTop() == 4 { + plain = lua.LVAsBool(L.Get(4)) + } + + if plain { + pos := strings.Index(str[init:], pattern) + if pos < 0 { + L.Push(lua.LNil) + return 1 + } + L.Push(lua.LNumber(init+pos) + 1) + L.Push(lua.LNumber(init + pos + len(pattern))) + return 2 + } + + re, err := regexp.Compile(pattern) + if err != nil { + L.RaiseError(err.Error()) + } + stroffset := str[init:] + positions := re.FindStringSubmatchIndex(stroffset) + if positions == nil || (len(positions) > 1 && positions[2] < 0) { + L.Push(lua.LNil) + return 1 + } + npos := len(positions) + L.Push(lua.LNumber(init+positions[0]) + 1) + L.Push(lua.LNumber(init + positions[npos-1])) + for i := 2; i < npos; i += 2 { + L.Push(lua.LString(stroffset[positions[i]:positions[i+1]])) + } + return npos/2 + 1 +} + +func reGsub(L *lua.LState) int { + str := L.CheckString(1) + pat := L.CheckString(2) + L.CheckTypes(3, lua.LTString, lua.LTTable, lua.LTFunction) + repl := L.CheckAny(3) + limit := L.OptInt(4, -1) + + re, err := regexp.Compile(pat) + if err != nil { + L.RaiseError(err.Error()) + } + matches := re.FindAllStringSubmatchIndex(str, limit) + if matches == nil || len(matches) == 0 { + L.SetTop(1) + L.Push(lua.LNumber(0)) + return 2 + } + switch lv := repl.(type) { + case lua.LString: + L.Push(lua.LString(reGsubStr(str, re, string(lv), matches))) + case *lua.LTable: + L.Push(lua.LString(reGsubTable(L, str, lv, matches))) + case *lua.LFunction: + L.Push(lua.LString(reGsubFunc(L, str, lv, matches))) + } + L.Push(lua.LNumber(len(matches))) + return 2 +} + +type replaceInfo struct { + Indicies []int + String string +} + +func reGsubDoReplace(str string, info []replaceInfo) string { + offset := 0 + buf := []byte(str) + for _, replace := range info { + oldlen := len(buf) + b1 := append([]byte(""), buf[0:offset+replace.Indicies[0]]...) + b2 := []byte("") + index2 := offset + replace.Indicies[1] + if index2 <= len(buf) { + b2 = append(b2, buf[index2:len(buf)]...) + } + buf = append(b1, replace.String...) + buf = append(buf, b2...) + offset += len(buf) - oldlen + } + return string(buf) +} + +func reGsubStr(str string, re *regexp.Regexp, repl string, matches [][]int) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + start, end := match[0], match[1] + if end < 0 { + continue + } + buf := make([]byte, 0, end-start) + buf = re.ExpandString(buf, repl, str, match) + infoList = append(infoList, replaceInfo{[]int{start, end}, string(buf)}) + } + + return reGsubDoReplace(str, infoList) +} + +func reGsubTable(L *lua.LState, str string, repl *lua.LTable, matches [][]int) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + var key string + start, end := match[0], match[1] + if end < 0 { + continue + } + if len(match) > 2 { // has captures + key = str[match[2]:match[3]] + } else { + key = str[match[0]:match[1]] + } + value := L.GetField(repl, key) + if !lua.LVIsFalse(value) { + infoList = append(infoList, replaceInfo{[]int{start, end}, lua.LVAsString(value)}) + } + } + return reGsubDoReplace(str, infoList) +} + +func reGsubFunc(L *lua.LState, str string, repl *lua.LFunction, matches [][]int) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + start, end := match[0], match[1] + if end < 0 { + continue + } + L.Push(repl) + nargs := 0 + if len(match) > 2 { // has captures + for i := 2; i < len(match); i += 2 { + L.Push(lua.LString(str[match[i]:match[i+1]])) + nargs++ + } + } else { + L.Push(lua.LString(str[start:end])) + nargs++ + } + L.Call(nargs, 1) + ret := L.Get(-1) + L.Pop(1) + if !lua.LVIsFalse(ret) { + infoList = append(infoList, replaceInfo{[]int{start, end}, lua.LVAsString(ret)}) + } + } + return reGsubDoReplace(str, infoList) +} + +type reMatchData struct { + str string + pos int + matches [][]int +} + +func reGmatchIter(L *lua.LState) int { + md := L.CheckUserData(1).Value.(*reMatchData) + str := md.str + matches := md.matches + idx := md.pos + md.pos += 1 + if idx == len(matches) { + return 0 + } + L.Push(L.Get(1)) + match := matches[idx] + if len(match) == 2 { + L.Push(lua.LString(str[match[0]:match[1]])) + return 1 + } + + for i := 2; i < len(match); i += 2 { + L.Push(lua.LString(str[match[i]:match[i+1]])) + } + return len(match)/2 - 1 +} + +func reGmatch(L *lua.LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + re, err := regexp.Compile(pattern) + if err != nil { + L.RaiseError(err.Error()) + } + L.Push(L.Get(lua.UpvalueIndex(1))) + ud := L.NewUserData() + ud.Value = &reMatchData{str, 0, re.FindAllStringSubmatchIndex(str, -1)} + L.Push(ud) + return 2 +} + +func reMatch(L *lua.LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + offset := L.OptInt(3, 1) + l := len(str) + if offset < 0 { + offset = l + offset + 1 + } + offset-- + if offset < 0 { + offset = 0 + } + + re, err := regexp.Compile(pattern) + if err != nil { + L.RaiseError(err.Error()) + } + str = str[offset:] + subs := re.FindStringSubmatchIndex(str) + nsubs := len(subs) / 2 + switch nsubs { + case 0: + L.Push(lua.LNil) + return 1 + case 1: + L.Push(lua.LString(str[subs[0]:subs[1]])) + return 1 + default: + for i := 2; i < len(subs); i += 2 { + L.Push(lua.LString(str[subs[i]:subs[i+1]])) + } + return nsubs - 1 + } + +} + +func luaIndex2StringIndex(str string, i int, start bool) int { + if start && i != 0 { + i -= 1 + } + l := len(str) + if i < 0 { + i = l + i + 1 + } + if 0 > i { + i = 0 + } + if !start && i > l { + i = l + } + return i +} + +// diff --git a/vendor/github.com/yuin/gluare/gluare_test.go b/vendor/github.com/yuin/gluare/gluare_test.go new file mode 100644 index 0000000..d493eb9 --- /dev/null +++ b/vendor/github.com/yuin/gluare/gluare_test.go @@ -0,0 +1,32 @@ +package gluare + +import ( + "fmt" + "github.com/yuin/gopher-lua" + "os" + "testing" +) + +func testScriptDir(t *testing.T, tests []string, directory string) { + if err := os.Chdir(directory); err != nil { + t.Error(err) + } + defer os.Chdir("..") + for _, script := range tests { + fmt.Printf("testing %s/%s\n", directory, script) + L := lua.NewState(lua.Options{ + RegistrySize: 1024 * 20, + CallStackSize: 1024, + IncludeGoStackTrace: true, + }) + L.PreloadModule("re", Loader) + if err := L.DoFile(script); err != nil { + t.Error(err) + } + L.Close() + } +} + +func TestGluaRe(t *testing.T) { + testScriptDir(t, []string{"test.lua"}, "_tests") +} diff --git a/vendor/github.com/yuin/gopher-lua/.travis.yml b/vendor/github.com/yuin/gopher-lua/.travis.yml new file mode 100644 index 0000000..fdaf536 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/.travis.yml @@ -0,0 +1,15 @@ +language: go + +go: + - 1.7 + - 1.8 + - 1.9 + +before_install: + - go get github.com/axw/gocov/gocov + - go get github.com/mattn/goveralls + - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi +install: + - go get -u -v $(go list -f '{{join .Imports "\n"}}{{"\n"}}{{join .TestImports "\n"}}' ./... | sort | uniq | grep -v gopher-lua) +script: + - $HOME/gopath/bin/goveralls -service=travis-ci diff --git a/vendor/github.com/yuin/gopher-lua/LICENSE b/vendor/github.com/yuin/gopher-lua/LICENSE new file mode 100644 index 0000000..4daf480 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Yusuke Inuzuka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/yuin/gopher-lua/Makefile b/vendor/github.com/yuin/gopher-lua/Makefile new file mode 100644 index 0000000..6d9e55c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/Makefile @@ -0,0 +1,10 @@ +.PHONY: build test glua + +build: + ./_tools/go-inline *.go && go fmt . && go build + +glua: *.go pm/*.go cmd/glua/glua.go + ./_tools/go-inline *.go && go fmt . && go build cmd/glua/glua.go + +test: + ./_tools/go-inline *.go && go fmt . && go test diff --git a/vendor/github.com/yuin/gopher-lua/README.rst b/vendor/github.com/yuin/gopher-lua/README.rst new file mode 100644 index 0000000..fc5f6df --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/README.rst @@ -0,0 +1,802 @@ +=============================================================================== +GopherLua: VM and compiler for Lua in Go. +=============================================================================== + +.. image:: https://godoc.org/github.com/yuin/gopher-lua?status.svg + :target: http://godoc.org/github.com/yuin/gopher-lua + +.. image:: https://travis-ci.org/yuin/gopher-lua.svg + :target: https://travis-ci.org/yuin/gopher-lua + +.. image:: https://coveralls.io/repos/yuin/gopher-lua/badge.svg + :target: https://coveralls.io/r/yuin/gopher-lua + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :alt: Join the chat at https://gitter.im/yuin/gopher-lua + :target: https://gitter.im/yuin/gopher-lua?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge + +| + + +GopherLua is a Lua5.1 VM and compiler written in Go. GopherLua has a same goal +with Lua: **Be a scripting language with extensible semantics** . It provides +Go APIs that allow you to easily embed a scripting language to your Go host +programs. + +.. contents:: + :depth: 1 + +---------------------------------------------------------------- +Design principle +---------------------------------------------------------------- + +- Be a scripting language with extensible semantics. +- User-friendly Go API + - The stack based API like the one used in the original Lua + implementation will cause a performance improvements in GopherLua + (It will reduce memory allocations and concrete type <-> interface conversions). + GopherLua API is **not** the stack based API. + GopherLua give preference to the user-friendliness over the performance. + +---------------------------------------------------------------- +How about performance? +---------------------------------------------------------------- +GopherLua is not fast but not too slow, I think. + +GopherLua has almost equivalent ( or little bit better ) performance as Python3 on micro benchmarks. + +There are some benchmarks on the `wiki page `_ . + +---------------------------------------------------------------- +Installation +---------------------------------------------------------------- + +.. code-block:: bash + + go get github.com/yuin/gopher-lua + +GopherLua supports >= Go1.7. + +---------------------------------------------------------------- +Usage +---------------------------------------------------------------- +GopherLua APIs perform in much the same way as Lua, **but the stack is used only +for passing arguments and receiving returned values.** + +GopherLua supports channel operations. See **"Goroutines"** section. + +Import a package. + +.. code-block:: go + + import ( + "github.com/yuin/gopher-lua" + ) + +Run scripts in the VM. + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoString(`print("hello")`); err != nil { + panic(err) + } + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoFile("hello.lua"); err != nil { + panic(err) + } + +Refer to `Lua Reference Manual `_ and `Go doc `_ for further information. + +Note that elements that are not commented in `Go doc `_ equivalent to `Lua Reference Manual `_ , except GopherLua uses objects instead of Lua stack indices. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Data model +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +All data in a GopherLua program is an ``LValue`` . ``LValue`` is an interface +type that has following methods. + +- ``String() string`` +- ``Type() LValueType`` + + +Objects implement an LValue interface are + +================ ========================= ================== ======================= + Type name Go type Type() value Constants +================ ========================= ================== ======================= + ``LNilType`` (constants) ``LTNil`` ``LNil`` + ``LBool`` (constants) ``LTBool`` ``LTrue``, ``LFalse`` + ``LNumber`` float64 ``LTNumber`` ``-`` + ``LString`` string ``LTString`` ``-`` + ``LFunction`` struct pointer ``LTFunction`` ``-`` + ``LUserData`` struct pointer ``LTUserData`` ``-`` + ``LState`` struct pointer ``LTThread`` ``-`` + ``LTable`` struct pointer ``LTTable`` ``-`` + ``LChannel`` chan LValue ``LTChannel`` ``-`` +================ ========================= ================== ======================= + +You can test an object type in Go way(type assertion) or using a ``Type()`` value. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if str, ok := lv.(lua.LString); ok { + // lv is LString + fmt.Println(string(str)) + } + if lv.Type() != lua.LTString { + panic("string required.") + } + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if tbl, ok := lv.(*lua.LTable); ok { + // lv is LTable + fmt.Println(L.ObjLen(tbl)) + } + +Note that ``LBool`` , ``LNumber`` , ``LString`` is not a pointer. + +To test ``LNilType`` and ``LBool``, You **must** use pre-defined constants. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + + if lv == lua.LTrue { // correct + } + + if bl, ok := lv.(lua.LBool); ok && bool(bl) { // wrong + } + +In Lua, both ``nil`` and ``false`` make a condition false. ``LVIsFalse`` and ``LVAsBool`` implement this specification. + +.. code-block:: go + + lv := L.Get(-1) // get the value at the top of the stack + if lua.LVIsFalse(lv) { // lv is nil or false + } + + if lua.LVAsBool(lv) { // lv is neither nil nor false + } + +Objects that based on go structs(``LFunction``. ``LUserData``, ``LTable``) +have some public methods and fields. You can use these methods and fields for +performance and debugging, but there are some limitations. + +- Metatable does not work. +- No error handlings. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Callstack & Registry size +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Size of the callstack & registry is **fixed** for mainly performance. +You can change the default size of the callstack & registry. + +.. code-block:: go + + lua.RegistrySize = 1024 * 20 + lua.CallStackSize = 1024 + L := lua.NewState() + defer L.Close() + +You can also create an LState object that has the callstack & registry size specified by ``Options`` . + +.. code-block:: go + + L := lua.NewState(lua.Options{ + CallStackSize: 120, + RegistrySize: 120*20, + }) + +An LState object that has been created by ``*LState#NewThread()`` inherits the callstack & registry size from the parent LState object. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Miscellaneous lua.NewState options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- **Options.SkipOpenLibs bool(default false)** + - By default, GopherLua opens all built-in libraries when new LState is created. + - You can skip this behaviour by setting this to ``true`` . + - Using the various `OpenXXX(L *LState) int` functions you can open only those libraries that you require, for an example see below. +- **Options.IncludeGoStackTrace bool(default false)** + - By default, GopherLua does not show Go stack traces when panics occur. + - You can get Go stack traces by setting this to ``true`` . + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Refer to `Lua Reference Manual `_ and `Go doc(LState methods) `_ for further information. + ++++++++++++++++++++++++++++++++++++++++++ +Calling Go from Lua ++++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: go + + func Double(L *lua.LState) int { + lv := L.ToInt(1) /* get argument */ + L.Push(lua.LNumber(lv * 2)) /* push result */ + return 1 /* number of results */ + } + + func main() { + L := lua.NewState() + defer L.Close() + L.SetGlobal("double", L.NewFunction(Double)) /* Original lua_setglobal uses stack... */ + } + +.. code-block:: lua + + print(double(20)) -- > "40" + +Any function registered with GopherLua is a ``lua.LGFunction``, defined in ``value.go`` + +.. code-block:: go + + type LGFunction func(*LState) int + +Working with coroutines. + +.. code-block:: go + + co, _ := L.NewThread() /* create a new thread */ + fn := L.GetGlobal("coro").(*lua.LFunction) /* get function from lua */ + for { + st, err, values := L.Resume(co, fn) + if st == lua.ResumeError { + fmt.Println("yield break(error)") + fmt.Println(err.Error()) + break + } + + for i, lv := range values { + fmt.Printf("%v : %v\n", i, lv) + } + + if st == lua.ResumeOK { + fmt.Println("yield break(ok)") + break + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Opening a subset of builtin modules ++++++++++++++++++++++++++++++++++++++++++ + +The following demonstrates how to open a subset of the built-in modules in Lua, say for example to avoid enabling modules with access to local files or system calls. + +main.go + +.. code-block:: go + + func main() { + L := lua.NewState(lua.Options{SkipOpenLibs: true}) + defer L.Close() + for _, pair := range []struct { + n string + f lua.LGFunction + }{ + {lua.LoadLibName, lua.OpenPackage}, // Must be first + {lua.BaseLibName, lua.OpenBase}, + {lua.TabLibName, lua.OpenTable}, + } { + if err := L.CallByParam(lua.P{ + Fn: L.NewFunction(pair.f), + NRet: 0, + Protect: true, + }, lua.LString(pair.n)); err != nil { + panic(err) + } + } + if err := L.DoFile("main.lua"); err != nil { + panic(err) + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Creating a module by Go ++++++++++++++++++++++++++++++++++++++++++ + +mymodule.go + +.. code-block:: go + + package mymodule + + import ( + "github.com/yuin/gopher-lua" + ) + + func Loader(L *lua.LState) int { + // register functions to the table + mod := L.SetFuncs(L.NewTable(), exports) + // register other stuff + L.SetField(mod, "name", lua.LString("value")) + + // returns the module + L.Push(mod) + return 1 + } + + var exports = map[string]lua.LGFunction{ + "myfunc": myfunc, + } + + func myfunc(L *lua.LState) int { + return 0 + } + +mymain.go + +.. code-block:: go + + package main + + import ( + "./mymodule" + "github.com/yuin/gopher-lua" + ) + + func main() { + L := lua.NewState() + defer L.Close() + L.PreloadModule("mymodule", mymodule.Loader) + if err := L.DoFile("main.lua"); err != nil { + panic(err) + } + } + +main.lua + +.. code-block:: lua + + local m = require("mymodule") + m.myfunc() + print(m.name) + + ++++++++++++++++++++++++++++++++++++++++++ +Calling Lua from Go ++++++++++++++++++++++++++++++++++++++++++ + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + if err := L.DoFile("double.lua"); err != nil { + panic(err) + } + if err := L.CallByParam(lua.P{ + Fn: L.GetGlobal("double"), + NRet: 1, + Protect: true, + }, lua.LNumber(10)); err != nil { + panic(err) + } + ret := L.Get(-1) // returned value + L.Pop(1) // remove received value + +If ``Protect`` is false, GopherLua will panic instead of returning an ``error`` value. + ++++++++++++++++++++++++++++++++++++++++++ +User-Defined types ++++++++++++++++++++++++++++++++++++++++++ +You can extend GopherLua with new types written in Go. +``LUserData`` is provided for this purpose. + +.. code-block:: go + + type Person struct { + Name string + } + + const luaPersonTypeName = "person" + + // Registers my person type to given L. + func registerPersonType(L *lua.LState) { + mt := L.NewTypeMetatable(luaPersonTypeName) + L.SetGlobal("person", mt) + // static attributes + L.SetField(mt, "new", L.NewFunction(newPerson)) + // methods + L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods)) + } + + // Constructor + func newPerson(L *lua.LState) int { + person := &Person{L.CheckString(1)} + ud := L.NewUserData() + ud.Value = person + L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName)) + L.Push(ud) + return 1 + } + + // Checks whether the first lua argument is a *LUserData with *Person and returns this *Person. + func checkPerson(L *lua.LState) *Person { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*Person); ok { + return v + } + L.ArgError(1, "person expected") + return nil + } + + var personMethods = map[string]lua.LGFunction{ + "name": personGetSetName, + } + + // Getter and setter for the Person#Name + func personGetSetName(L *lua.LState) int { + p := checkPerson(L) + if L.GetTop() == 2 { + p.Name = L.CheckString(2) + return 0 + } + L.Push(lua.LString(p.Name)) + return 1 + } + + func main() { + L := lua.NewState() + defer L.Close() + registerPersonType(L) + if err := L.DoString(` + p = person.new("Steeve") + print(p:name()) -- "Steeve" + p:name("Alice") + print(p:name()) -- "Alice" + `); err != nil { + panic(err) + } + } + ++++++++++++++++++++++++++++++++++++++++++ +Terminating a running LState ++++++++++++++++++++++++++++++++++++++++++ +GopherLua supports the `Go Concurrency Patterns: Context `_ . + + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + // set the context to our LState + L.SetContext(ctx) + err := L.DoString(` + local clock = os.clock + function sleep(n) -- seconds + local t0 = clock() + while clock() - t0 <= n do end + end + sleep(3) + `) + // err.Error() contains "context deadline exceeded" + +With coroutines + +.. code-block:: go + + L := lua.NewState() + defer L.Close() + ctx, cancel := context.WithCancel(context.Background()) + L.SetContext(ctx) + defer cancel() + L.DoString(` + function coro() + local i = 0 + while true do + coroutine.yield(i) + i = i+1 + end + return i + end + `) + co, cocancel := L.NewThread() + defer cocancel() + fn := L.GetGlobal("coro").(*LFunction) + + _, err, values := L.Resume(co, fn) // err is nil + + cancel() // cancel the parent context + + _, err, values = L.Resume(co, fn) // err is NOT nil : child context was canceled + +**Note that using a context causes performance degradation.** + +.. code-block:: + + time ./glua-with-context.exe fib.lua + 9227465 + 0.01s user 0.11s system 1% cpu 7.505 total + + time ./glua-without-context.exe fib.lua + 9227465 + 0.01s user 0.01s system 0% cpu 5.306 total + + ++++++++++++++++++++++++++++++++++++++++++ +Goroutines ++++++++++++++++++++++++++++++++++++++++++ +The ``LState`` is not goroutine-safe. It is recommended to use one LState per goroutine and communicate between goroutines by using channels. + +Channels are represented by ``channel`` objects in GopherLua. And a ``channel`` table provides functions for performing channel operations. + +Some objects can not be sent over channels due to having non-goroutine-safe objects inside itself. + +- a thread(state) +- a function +- an userdata +- a table with a metatable + +You **must not** send these objects from Go APIs to channels. + + + +.. code-block:: go + + func receiver(ch, quit chan lua.LValue) { + L := lua.NewState() + defer L.Close() + L.SetGlobal("ch", lua.LChannel(ch)) + L.SetGlobal("quit", lua.LChannel(quit)) + if err := L.DoString(` + local exit = false + while not exit do + channel.select( + {"|<-", ch, function(ok, v) + if not ok then + print("channel closed") + exit = true + else + print("received:", v) + end + end}, + {"|<-", quit, function(ok, v) + print("quit") + exit = true + end} + ) + end + `); err != nil { + panic(err) + } + } + + func sender(ch, quit chan lua.LValue) { + L := lua.NewState() + defer L.Close() + L.SetGlobal("ch", lua.LChannel(ch)) + L.SetGlobal("quit", lua.LChannel(quit)) + if err := L.DoString(` + ch:send("1") + ch:send("2") + `); err != nil { + panic(err) + } + ch <- lua.LString("3") + quit <- lua.LTrue + } + + func main() { + ch := make(chan lua.LValue) + quit := make(chan lua.LValue) + go receiver(ch, quit) + go sender(ch, quit) + time.Sleep(3 * time.Second) + } + +''''''''''''''' +Go API +''''''''''''''' + +``ToChannel``, ``CheckChannel``, ``OptChannel`` are available. + +Refer to `Go doc(LState methods) `_ for further information. + +''''''''''''''' +Lua API +''''''''''''''' + +- **channel.make([buf:int]) -> ch:channel** + - Create new channel that has a buffer size of ``buf``. By default, ``buf`` is 0. + +- **channel.select(case:table [, case:table, case:table ...]) -> {index:int, recv:any, ok}** + - Same as the ``select`` statement in Go. It returns the index of the chosen case and, if that + case was a receive operation, the value received and a boolean indicating whether the channel has been closed. + - ``case`` is a table that outlined below. + - receiving: `{"|<-", ch:channel [, handler:func(ok, data:any)]}` + - sending: `{"<-|", ch:channel, data:any [, handler:func(data:any)]}` + - default: `{"default" [, handler:func()]}` + +``channel.select`` examples: + +.. code-block:: lua + + local idx, recv, ok = channel.select( + {"|<-", ch1}, + {"|<-", ch2} + ) + if not ok then + print("closed") + elseif idx == 1 then -- received from ch1 + print(recv) + elseif idx == 2 then -- received from ch2 + print(recv) + end + +.. code-block:: lua + + channel.select( + {"|<-", ch1, function(ok, data) + print(ok, data) + end}, + {"<-|", ch2, "value", function(data) + print(data) + end}, + {"default", function() + print("default action") + end} + ) + +- **channel:send(data:any)** + - Send ``data`` over the channel. +- **channel:receive() -> ok:bool, data:any** + - Receive some data over the channel. +- **channel:close()** + - Close the channel. + +'''''''''''''''''''''''''''''' +The LState pool pattern +'''''''''''''''''''''''''''''' +To create per-thread LState instances, You can use the ``sync.Pool`` like mechanism. + +.. code-block:: go + + type lStatePool struct { + m sync.Mutex + saved []*lua.LState + } + + func (pl *lStatePool) Get() *lua.LState { + pl.m.Lock() + defer pl.m.Unlock() + n := len(pl.saved) + if n == 0 { + return pl.New() + } + x := pl.saved[n-1] + pl.saved = pl.saved[0 : n-1] + return x + } + + func (pl *lStatePool) New() *lua.LState { + L := lua.NewState() + // setting the L up here. + // load scripts, set global variables, share channels, etc... + return L + } + + func (pl *lStatePool) Put(L *lua.LState) { + pl.m.Lock() + defer pl.m.Unlock() + pl.saved = append(pl.saved, L) + } + + func (pl *lStatePool) Shutdown() { + for _, L := range pl.saved { + L.Close() + } + } + + // Global LState pool + var luaPool = &lStatePool{ + saved: make([]*lua.LState, 0, 4), + } + +Now, you can get per-thread LState objects from the ``luaPool`` . + +.. code-block:: go + + func MyWorker() { + L := luaPool.Get() + defer luaPool.Put(L) + /* your code here */ + } + + func main() { + defer luaPool.Shutdown() + go MyWorker() + go MyWorker() + /* etc... */ + } + + +---------------------------------------------------------------- +Differences between Lua and GopherLua +---------------------------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Goroutines +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- GopherLua supports channel operations. + - GopherLua has a type named ``channel``. + - The ``channel`` table provides functions for performing channel operations. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Unsupported functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``string.dump`` +- ``os.setlocale`` +- ``lua_Debug.namewhat`` +- ``package.loadlib`` +- debug hooks + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Miscellaneous notes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``collectgarbage`` does not take any arguments and runs the garbage collector for the entire Go program. +- ``file:setvbuf`` does not support a line buffering. +- Daylight saving time is not supported. +- GopherLua has a function to set an environment variable : ``os.setenv(name, value)`` + +---------------------------------------------------------------- +Standalone interpreter +---------------------------------------------------------------- +Lua has an interpreter called ``lua`` . GopherLua has an interpreter called ``glua`` . + +.. code-block:: bash + + go get github.com/yuin/gopher-lua/cmd/glua + +``glua`` has same options as ``lua`` . + +---------------------------------------------------------------- +How to Contribute +---------------------------------------------------------------- +See `Guidlines for contributors `_ . + +---------------------------------------------------------------- +Libraries for GopherLua +---------------------------------------------------------------- + +- `gopher-luar `_ : Custom type reflection for gopher-lua +- `gluamapper `_ : Mapping a Lua table to a Go struct +- `gluare `_ : Regular expressions for gopher-lua +- `gluahttp `_ : HTTP request module for gopher-lua +- `gopher-json `_ : A simple JSON encoder/decoder for gopher-lua +- `gluayaml `_ : Yaml parser for gopher-lua +- `glua-lfs `_ : Partially implements the luafilesystem module for gopher-lua +- `gluaurl `_ : A url parser/builder module for gopher-lua +- `gluahttpscrape `_ : A simple HTML scraper module for gopher-lua +- `gluaxmlpath `_ : An xmlpath module for gopher-lua +- `gluasocket `_ : A LuaSocket library for the GopherLua VM +- `gluabit32 `_ : A native Go implementation of bit32 for the GopherLua VM. + +---------------------------------------------------------------- +Donation +---------------------------------------------------------------- + +BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB + +---------------------------------------------------------------- +License +---------------------------------------------------------------- +MIT + +---------------------------------------------------------------- +Author +---------------------------------------------------------------- +Yusuke Inuzuka diff --git a/vendor/github.com/yuin/gopher-lua/_state.go b/vendor/github.com/yuin/gopher-lua/_state.go new file mode 100644 index 0000000..aba9f4e --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/_state.go @@ -0,0 +1,1787 @@ +package lua + +import ( + "context" + "fmt" + "github.com/yuin/gopher-lua/parse" + "io" + "math" + "os" + "runtime" + "strings" + "sync/atomic" + "time" +) + +const MultRet = -1 +const RegistryIndex = -10000 +const EnvironIndex = -10001 +const GlobalsIndex = -10002 + +/* ApiError {{{ */ + +type ApiError struct { + Type ApiErrorType + Object LValue + StackTrace string + // Underlying error. This attribute is set only if the Type is ApiErrorFile or ApiErrorSyntax + Cause error +} + +func newApiError(code ApiErrorType, object LValue) *ApiError { + return &ApiError{code, object, "", nil} +} + +func newApiErrorS(code ApiErrorType, message string) *ApiError { + return newApiError(code, LString(message)) +} + +func newApiErrorE(code ApiErrorType, err error) *ApiError { + return &ApiError{code, LString(err.Error()), "", err} +} + +func (e *ApiError) Error() string { + if len(e.StackTrace) > 0 { + return fmt.Sprintf("%s\n%s", e.Object.String(), e.StackTrace) + } + return e.Object.String() +} + +type ApiErrorType int + +const ( + ApiErrorSyntax ApiErrorType = iota + ApiErrorFile + ApiErrorRun + ApiErrorError + ApiErrorPanic +) + +/* }}} */ + +/* ResumeState {{{ */ + +type ResumeState int + +const ( + ResumeOK ResumeState = iota + ResumeYield + ResumeError +) + +/* }}} */ + +/* P {{{ */ + +type P struct { + Fn LValue + NRet int + Protect bool + Handler *LFunction +} + +/* }}} */ + +/* Options {{{ */ + +// Options is a configuration that is used to create a new LState. +type Options struct { + // Call stack size. This defaults to `lua.CallStackSize`. + CallStackSize int + // Data stack size. This defaults to `lua.RegistrySize`. + RegistrySize int + // Controls whether or not libraries are opened by default + SkipOpenLibs bool + // Tells whether a Go stacktrace should be included in a Lua stacktrace when panics occur. + IncludeGoStackTrace bool +} + +/* }}} */ + +/* Debug {{{ */ + +type Debug struct { + frame *callFrame + Name string + What string + Source string + CurrentLine int + NUpvalues int + LineDefined int + LastLineDefined int +} + +/* }}} */ + +/* callFrame {{{ */ + +type callFrame struct { + Idx int + Fn *LFunction + Parent *callFrame + Pc int + Base int + LocalBase int + ReturnBase int + NArgs int + NRet int + TailCall int +} + +type callFrameStack struct { + array []callFrame + sp int +} + +func newCallFrameStack(size int) *callFrameStack { + return &callFrameStack{ + array: make([]callFrame, size), + sp: 0, + } +} + +func (cs *callFrameStack) IsEmpty() bool { return cs.sp == 0 } + +func (cs *callFrameStack) Clear() { + cs.sp = 0 +} + +func (cs *callFrameStack) Push(v callFrame) { // +inline-start + cs.array[cs.sp] = v + cs.array[cs.sp].Idx = cs.sp + cs.sp++ +} // +inline-end + +func (cs *callFrameStack) Remove(sp int) { + psp := sp - 1 + nsp := sp + 1 + var pre *callFrame + var next *callFrame + if psp > 0 { + pre = &cs.array[psp] + } + if nsp < cs.sp { + next = &cs.array[nsp] + } + if next != nil { + next.Parent = pre + } + for i := sp; i+1 < cs.sp; i++ { + cs.array[i] = cs.array[i+1] + cs.array[i].Idx = i + cs.sp = i + } + cs.sp++ +} + +func (cs *callFrameStack) Sp() int { + return cs.sp +} + +func (cs *callFrameStack) SetSp(sp int) { + cs.sp = sp +} + +func (cs *callFrameStack) Last() *callFrame { + if cs.sp == 0 { + return nil + } + return &cs.array[cs.sp-1] +} + +func (cs *callFrameStack) At(sp int) *callFrame { + return &cs.array[sp] +} + +func (cs *callFrameStack) Pop() *callFrame { + cs.sp-- + return &cs.array[cs.sp] +} + +/* }}} */ + +/* registry {{{ */ + +type registry struct { + array []LValue + top int + alloc *allocator +} + +func newRegistry(size int, alloc *allocator) *registry { + return ®istry{make([]LValue, size), 0, alloc} +} + +func (rg *registry) SetTop(top int) { + oldtop := rg.top + rg.top = top + for i := oldtop; i < rg.top; i++ { + rg.array[i] = LNil + } + for i := rg.top; i < oldtop; i++ { + rg.array[i] = LNil + } +} + +func (rg *registry) Top() int { + return rg.top +} + +func (rg *registry) Push(v LValue) { + rg.array[rg.top] = v + rg.top++ +} + +func (rg *registry) Pop() LValue { + v := rg.array[rg.top-1] + rg.array[rg.top-1] = LNil + rg.top-- + return v +} + +func (rg *registry) Get(reg int) LValue { + return rg.array[reg] +} + +func (rg *registry) CopyRange(regv, start, limit, n int) { // +inline-start + for i := 0; i < n; i++ { + if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[tidx] + } + } + rg.top = regv + n +} // +inline-end + +func (rg *registry) FillNil(regm, n int) { // +inline-start + for i := 0; i < n; i++ { + rg.array[regm+i] = LNil + } + rg.top = regm + n +} // +inline-end + +func (rg *registry) Insert(value LValue, reg int) { + top := rg.Top() + if reg >= top { + rg.Set(reg, value) + return + } + top-- + for ; top >= reg; top-- { + rg.Set(top+1, rg.Get(top)) + } + rg.Set(reg, value) +} + +func (rg *registry) Set(reg int, val LValue) { + rg.array[reg] = val + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) SetNumber(reg int, val LNumber) { + rg.array[reg] = rg.alloc.LNumber2I(val) + if reg >= rg.top { + rg.top = reg + 1 + } +} /* }}} */ + +/* Global {{{ */ + +func newGlobal() *Global { + return &Global{ + MainThread: nil, + Registry: newLTable(0, 32), + Global: newLTable(0, 64), + builtinMts: make(map[int]LValue), + tempFiles: make([]*os.File, 0, 10), + } +} + +/* }}} */ + +/* package local methods {{{ */ + +func panicWithTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + err.StackTrace = L.stackTrace(0) + panic(err) +} + +func panicWithoutTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + panic(err) +} + +func newLState(options Options) *LState { + al := newAllocator(32) + ls := &LState{ + G: newGlobal(), + Parent: nil, + Panic: panicWithTraceback, + Dead: false, + Options: options, + + stop: 0, + reg: newRegistry(options.RegistrySize, al), + stack: newCallFrameStack(options.CallStackSize), + alloc: al, + currentFrame: nil, + wrapped: false, + uvcache: nil, + hasErrorFunc: false, + mainLoop: mainLoop, + ctx: nil, + } + ls.Env = ls.G.Global + return ls +} + +func (ls *LState) printReg() { + println("-------------------------") + println("thread:", ls) + println("top:", ls.reg.Top()) + if ls.currentFrame != nil { + println("function base:", ls.currentFrame.Base) + println("return base:", ls.currentFrame.ReturnBase) + } else { + println("(vm not started)") + } + println("local base:", ls.currentLocalBase()) + for i := 0; i < ls.reg.Top(); i++ { + println(i, ls.reg.Get(i).String()) + } + println("-------------------------") +} + +func (ls *LState) printCallStack() { + println("-------------------------") + for i := 0; i < ls.stack.Sp(); i++ { + print(i) + print(" ") + frame := ls.stack.At(i) + if frame == nil { + break + } + if frame.Fn.IsG { + println("IsG:", true, "Frame:", frame, "Fn:", frame.Fn) + } else { + println("IsG:", false, "Frame:", frame, "Fn:", frame.Fn, "pc:", frame.Pc) + } + } + println("-------------------------") +} + +func (ls *LState) closeAllUpvalues() { // +inline-start + for cf := ls.currentFrame; cf != nil; cf = cf.Parent { + if !cf.Fn.IsG { + ls.closeUpvalues(cf.LocalBase) + } + } +} // +inline-end + +func (ls *LState) raiseError(level int, format string, args ...interface{}) { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + message := format + if len(args) > 0 { + message = fmt.Sprintf(format, args...) + } + if level > 0 { + message = fmt.Sprintf("%v %v", ls.where(level-1, true), message) + } + ls.reg.Push(LString(message)) + ls.Panic(ls) +} + +func (ls *LState) findLocal(frame *callFrame, no int) string { + fn := frame.Fn + if !fn.IsG { + if name, ok := fn.LocalName(no, frame.Pc-1); ok { + return name + } + } + var top int + if ls.currentFrame == frame { + top = ls.reg.Top() + } else if frame.Idx+1 < ls.stack.Sp() { + top = ls.stack.At(frame.Idx + 1).Base + } else { + return "" + } + if top-frame.LocalBase >= no { + return "(*temporary)" + } + return "" +} + +func (ls *LState) where(level int, skipg bool) string { + dbg, ok := ls.GetStack(level) + if !ok { + return "" + } + cf := dbg.frame + proto := cf.Fn.Proto + sourcename := "[G]" + if proto != nil { + sourcename = proto.SourceName + } else if skipg { + return ls.where(level+1, skipg) + } + line := "" + if proto != nil { + line = fmt.Sprintf("%v:", proto.DbgSourcePositions[cf.Pc-1]) + } + return fmt.Sprintf("%v:%v", sourcename, line) +} + +func (ls *LState) stackTrace(level int) string { + buf := []string{} + header := "stack traceback:" + if ls.currentFrame != nil { + i := 0 + for dbg, ok := ls.GetStack(i); ok; dbg, ok = ls.GetStack(i) { + cf := dbg.frame + buf = append(buf, fmt.Sprintf("\t%v in %v", ls.Where(i), ls.formattedFrameFuncName(cf))) + if !cf.Fn.IsG && cf.TailCall > 0 { + for tc := cf.TailCall; tc > 0; tc-- { + buf = append(buf, "\t(tailcall): ?") + i++ + } + } + i++ + } + } + buf = append(buf, fmt.Sprintf("\t%v: %v", "[G]", "?")) + buf = buf[intMax(0, intMin(level, len(buf))):len(buf)] + if len(buf) > 20 { + newbuf := make([]string, 0, 20) + newbuf = append(newbuf, buf[0:7]...) + newbuf = append(newbuf, "\t...") + newbuf = append(newbuf, buf[len(buf)-7:len(buf)]...) + buf = newbuf + } + return fmt.Sprintf("%s\n%s", header, strings.Join(buf, "\n")) +} + +func (ls *LState) formattedFrameFuncName(fr *callFrame) string { + name, ischunk := ls.frameFuncName(fr) + if ischunk { + return name + } + if name[0] != '(' && name[0] != '<' { + return fmt.Sprintf("function '%s'", name) + } + return fmt.Sprintf("function %s", name) +} + +func (ls *LState) rawFrameFuncName(fr *callFrame) string { + name, _ := ls.frameFuncName(fr) + return name +} + +func (ls *LState) frameFuncName(fr *callFrame) (string, bool) { + frame := fr.Parent + if frame == nil { + if ls.Parent == nil { + return "main chunk", true + } else { + return "corountine", true + } + } + if !frame.Fn.IsG { + pc := frame.Pc - 1 + for _, call := range frame.Fn.Proto.DbgCalls { + if call.Pc == pc { + name := call.Name + if (name == "?" || fr.TailCall > 0) && !fr.Fn.IsG { + name = fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined) + } + return name, false + } + } + } + if !fr.Fn.IsG { + return fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined), false + } + return "(anonymous)", false +} + +func (ls *LState) isStarted() bool { + return ls.currentFrame != nil +} + +func (ls *LState) kill() { + ls.Dead = true +} + +func (ls *LState) indexToReg(idx int) int { + base := ls.currentLocalBase() + if idx > 0 { + return base + idx - 1 + } else if idx == 0 { + return -1 + } else { + tidx := ls.reg.Top() + idx + if tidx < base { + return -1 + } + return tidx + } +} + +func (ls *LState) currentLocalBase() int { + base := 0 + if ls.currentFrame != nil { + base = ls.currentFrame.LocalBase + } + return base +} + +func (ls *LState) currentEnv() *LTable { + return ls.Env + /* + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + */ +} + +func (ls *LState) rkValue(idx int) LValue { + /* + if OpIsK(idx) { + return ls.currentFrame.Fn.Proto.Constants[opIndexK(idx)] + } + return ls.reg.Get(ls.currentFrame.LocalBase + idx) + */ + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.Constants[idx & ^opBitRk] + } + return ls.reg.array[ls.currentFrame.LocalBase+idx] +} + +func (ls *LState) rkString(idx int) string { + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.stringConstants[idx & ^opBitRk] + } + return string(ls.reg.array[ls.currentFrame.LocalBase+idx].(LString)) +} + +func (ls *LState) closeUpvalues(idx int) { // +inline-start + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } +} // +inline-end + +func (ls *LState) findUpvalue(idx int) *Upvalue { + var prev *Upvalue + var next *Upvalue + if ls.uvcache != nil { + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index == idx { + return uv + } + if uv.index > idx { + next = uv + break + } + prev = uv + } + } + uv := &Upvalue{reg: ls.reg, index: idx, closed: false} + if prev != nil { + prev.next = uv + } else { + ls.uvcache = uv + } + if next != nil { + uv.next = next + } + return uv +} + +func (ls *LState) metatable(lvalue LValue, rawget bool) LValue { + var metatable LValue = LNil + switch obj := lvalue.(type) { + case *LTable: + metatable = obj.Metatable + case *LUserData: + metatable = obj.Metatable + default: + if table, ok := ls.G.builtinMts[int(obj.Type())]; ok { + metatable = table + } + } + + if !rawget && metatable != LNil { + oldmt := metatable + if tb, ok := metatable.(*LTable); ok { + metatable = tb.RawGetString("__metatable") + if metatable == LNil { + metatable = oldmt + } + } + } + + return metatable +} + +func (ls *LState) metaOp1(lvalue LValue, event string) LValue { + if mt := ls.metatable(lvalue, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaOp2(value1, value2 LValue, event string) LValue { + if mt := ls.metatable(value1, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + if ret := tb.RawGetString(event); ret != LNil { + return ret + } + } + } + if mt := ls.metatable(value2, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaCall(lvalue LValue) (*LFunction, bool) { + if fn, ok := lvalue.(*LFunction); ok { + return fn, false + } + if fn, ok := ls.metaOp1(lvalue, "__call").(*LFunction); ok { + return fn, true + } + return nil, false +} + +func (ls *LState) initCallFrame(cf *callFrame) { // +inline-start + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } +} // +inline-end + +func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) { // +inline-start + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.sp == ls.Options.CallStackSize { + ls.RaiseError("stack overflow") + } + // +inline-call ls.stack.Push cf + newcf := ls.stack.Last() + // +inline-call ls.initCallFrame newcf + ls.currentFrame = newcf +} // +inline-end + +func (ls *LState) callR(nargs, nret, rbase int) { + base := ls.reg.Top() - nargs - 1 + if rbase < 0 { + rbase = base + } + lv := ls.reg.Get(base) + fn, meta := ls.metaCall(lv) + ls.pushCallFrame(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: rbase, + NArgs: nargs, + NRet: nret, + Parent: ls.currentFrame, + TailCall: 0, + }, lv, meta) + if ls.G.MainThread == nil { + ls.G.MainThread = ls + ls.G.CurrentThread = ls + ls.mainLoop(ls, nil) + } else { + ls.mainLoop(ls, ls.currentFrame) + } + if nret != MultRet { + ls.reg.SetTop(rbase + nret) + } +} + +func (ls *LState) getField(obj LValue, key LValue) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGet(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String()) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) getFieldString(obj LValue, key string) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGetString(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String()) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) setField(obj LValue, key LValue, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGet(key) != LNil { + ls.RawSet(tb, key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String()) + } + ls.RawSet(tb, key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +func (ls *LState) setFieldString(obj LValue, key string, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGetString(key) != LNil { + tb.RawSetString(key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String()) + } + tb.RawSetString(key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +/* }}} */ + +/* api methods {{{ */ + +func NewState(opts ...Options) *LState { + var ls *LState + if len(opts) == 0 { + ls = newLState(Options{ + CallStackSize: CallStackSize, + RegistrySize: RegistrySize, + }) + ls.OpenLibs() + } else { + if opts[0].CallStackSize < 1 { + opts[0].CallStackSize = CallStackSize + } + if opts[0].RegistrySize < 128 { + opts[0].RegistrySize = RegistrySize + } + ls = newLState(opts[0]) + if !opts[0].SkipOpenLibs { + ls.OpenLibs() + } + } + return ls +} + +func (ls *LState) Close() { + atomic.AddInt32(&ls.stop, 1) + for _, file := range ls.G.tempFiles { + // ignore errors in these operations + file.Close() + os.Remove(file.Name()) + } +} + +/* registry operations {{{ */ + +func (ls *LState) GetTop() int { + return ls.reg.Top() - ls.currentLocalBase() +} + +func (ls *LState) SetTop(idx int) { + base := ls.currentLocalBase() + newtop := ls.indexToReg(idx) + 1 + if newtop < base { + ls.reg.SetTop(base) + } else { + ls.reg.SetTop(newtop) + } +} + +func (ls *LState) Replace(idx int, value LValue) { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + ls.reg.Set(reg, value) + } + } else if idx == 0 { + } else if idx > RegistryIndex { + if tidx := ls.reg.Top() + idx; tidx >= base { + ls.reg.Set(tidx, value) + } + } else { + switch idx { + case RegistryIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Registry = tb + } else { + ls.RaiseError("registry must be a table(%v)", value.Type().String()) + } + case EnvironIndex: + if ls.currentFrame == nil { + ls.RaiseError("no calling environment") + } + if tb, ok := value.(*LTable); ok { + ls.currentFrame.Fn.Env = tb + } else { + ls.RaiseError("environment must be a table(%v)", value.Type().String()) + } + case GlobalsIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Global = tb + } else { + ls.RaiseError("_G must be a table(%v)", value.Type().String()) + } + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + fn.Upvalues[index].SetValue(value) + } + } + } +} + +func (ls *LState) Get(idx int) LValue { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + return ls.reg.Get(reg) + } + return LNil + } else if idx == 0 { + return LNil + } else if idx > RegistryIndex { + tidx := ls.reg.Top() + idx + if tidx < base { + return LNil + } + return ls.reg.Get(tidx) + } else { + switch idx { + case RegistryIndex: + return ls.G.Registry + case EnvironIndex: + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + case GlobalsIndex: + return ls.G.Global + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + return fn.Upvalues[index].Value() + } + return LNil + } + } + return LNil +} + +func (ls *LState) Push(value LValue) { + ls.reg.Push(value) +} + +func (ls *LState) Pop(n int) { + for i := 0; i < n; i++ { + if ls.GetTop() == 0 { + ls.RaiseError("register underflow") + } + ls.reg.Pop() + } +} + +func (ls *LState) Insert(value LValue, index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + if reg >= top { + ls.reg.Set(reg, value) + return + } + if reg <= ls.currentLocalBase() { + reg = ls.currentLocalBase() + } + top-- + for ; top >= reg; top-- { + ls.reg.Set(top+1, ls.reg.Get(top)) + } + ls.reg.Set(reg, value) +} + +func (ls *LState) Remove(index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + switch { + case reg >= top: + return + case reg < ls.currentLocalBase(): + return + case reg == top-1: + ls.Pop(1) + return + } + for i := reg; i < top-1; i++ { + ls.reg.Set(i, ls.reg.Get(i+1)) + } + ls.reg.SetTop(top - 1) +} + +/* }}} */ + +/* object allocation {{{ */ + +func (ls *LState) NewTable() *LTable { + return newLTable(defaultArrayCap, defaultHashCap) +} + +func (ls *LState) CreateTable(acap, hcap int) *LTable { + return newLTable(acap, hcap) +} + +// NewThread returns a new LState that shares with the original state all global objects. +// If the original state has context.Context, the new state has a new child context of the original state and this function returns its cancel function. +func (ls *LState) NewThread() (*LState, context.CancelFunc) { + thread := newLState(ls.Options) + thread.G = ls.G + thread.Env = ls.Env + var f context.CancelFunc = nil + if ls.ctx != nil { + thread.mainLoop = mainLoopWithContext + thread.ctx, f = context.WithCancel(ls.ctx) + } + return thread, f +} + +func (ls *LState) NewUserData() *LUserData { + return &LUserData{ + Env: ls.currentEnv(), + Metatable: LNil, + } +} + +func (ls *LState) NewFunction(fn LGFunction) *LFunction { + return newLFunctionG(fn, ls.currentEnv(), 0) +} + +func (ls *LState) NewClosure(fn LGFunction, upvalues ...LValue) *LFunction { + cl := newLFunctionG(fn, ls.currentEnv(), len(upvalues)) + for i, lv := range upvalues { + cl.Upvalues[i] = &Upvalue{} + cl.Upvalues[i].Close() + cl.Upvalues[i].SetValue(lv) + } + return cl +} + +/* }}} */ + +/* toType {{{ */ + +func (ls *LState) ToBool(n int) bool { + return LVAsBool(ls.Get(n)) +} + +func (ls *LState) ToInt(n int) int { + if lv, ok := ls.Get(n).(LNumber); ok { + return int(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int(num) + } + } + return 0 +} + +func (ls *LState) ToInt64(n int) int64 { + if lv, ok := ls.Get(n).(LNumber); ok { + return int64(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int64(num) + } + } + return 0 +} + +func (ls *LState) ToNumber(n int) LNumber { + return LVAsNumber(ls.Get(n)) +} + +func (ls *LState) ToString(n int) string { + return LVAsString(ls.Get(n)) +} + +func (ls *LState) ToTable(n int) *LTable { + if lv, ok := ls.Get(n).(*LTable); ok { + return lv + } + return nil +} + +func (ls *LState) ToFunction(n int) *LFunction { + if lv, ok := ls.Get(n).(*LFunction); ok { + return lv + } + return nil +} + +func (ls *LState) ToUserData(n int) *LUserData { + if lv, ok := ls.Get(n).(*LUserData); ok { + return lv + } + return nil +} + +func (ls *LState) ToThread(n int) *LState { + if lv, ok := ls.Get(n).(*LState); ok { + return lv + } + return nil +} + +/* }}} */ + +/* error & debug operations {{{ */ + +// This function is equivalent to luaL_error( http://www.lua.org/manual/5.1/manual.html#luaL_error ). +func (ls *LState) RaiseError(format string, args ...interface{}) { + ls.raiseError(1, format, args...) +} + +// This function is equivalent to lua_error( http://www.lua.org/manual/5.1/manual.html#lua_error ). +func (ls *LState) Error(lv LValue, level int) { + if str, ok := lv.(LString); ok { + ls.raiseError(level, string(str)) + } else { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + ls.Push(lv) + ls.Panic(ls) + } +} + +func (ls *LState) GetInfo(what string, dbg *Debug, fn LValue) (LValue, error) { + if !strings.HasPrefix(what, ">") { + fn = dbg.frame.Fn + } else { + what = what[1:] + } + f, ok := fn.(*LFunction) + if !ok { + return LNil, newApiErrorS(ApiErrorRun, "can not get debug info(an object in not a function)") + } + + retfn := false + for _, c := range what { + switch c { + case 'f': + retfn = true + case 'S': + if dbg.frame != nil && dbg.frame.Parent == nil { + dbg.What = "main" + } else if f.IsG { + dbg.What = "G" + } else if dbg.frame != nil && dbg.frame.TailCall > 0 { + dbg.What = "tail" + } else { + dbg.What = "Lua" + } + if !f.IsG { + dbg.Source = f.Proto.SourceName + dbg.LineDefined = f.Proto.LineDefined + dbg.LastLineDefined = f.Proto.LastLineDefined + } + case 'l': + if !f.IsG && dbg.frame != nil { + if dbg.frame.Pc > 0 { + dbg.CurrentLine = f.Proto.DbgSourcePositions[dbg.frame.Pc-1] + } + } else { + dbg.CurrentLine = -1 + } + case 'u': + dbg.NUpvalues = len(f.Upvalues) + case 'n': + if dbg.frame != nil { + dbg.Name = ls.rawFrameFuncName(dbg.frame) + } + default: + return LNil, newApiErrorS(ApiErrorRun, "invalid what: "+string(c)) + } + } + + if retfn { + return f, nil + } + return LNil, nil + +} + +func (ls *LState) GetStack(level int) (*Debug, bool) { + frame := ls.currentFrame + for ; level > 0 && frame != nil; frame = frame.Parent { + level-- + if !frame.Fn.IsG { + level -= frame.TailCall + } + } + + if level == 0 && frame != nil { + return &Debug{frame: frame}, true + } else if level < 0 && ls.stack.Sp() > 0 { + return &Debug{frame: ls.stack.At(0)}, true + } + return &Debug{}, false +} + +func (ls *LState) GetLocal(dbg *Debug, no int) (string, LValue) { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + return name, ls.reg.Get(frame.LocalBase + no - 1) + } + return "", LNil +} + +func (ls *LState) SetLocal(dbg *Debug, no int, lv LValue) string { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + ls.reg.Set(frame.LocalBase+no-1, lv) + return name + } + return "" +} + +func (ls *LState) GetUpvalue(fn *LFunction, no int) (string, LValue) { + if fn.IsG { + return "", LNil + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + return fn.Proto.DbgUpvalues[no], fn.Upvalues[no].Value() + } + return "", LNil +} + +func (ls *LState) SetUpvalue(fn *LFunction, no int, lv LValue) string { + if fn.IsG { + return "" + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + fn.Upvalues[no].SetValue(lv) + return fn.Proto.DbgUpvalues[no] + } + return "" +} + +/* }}} */ + +/* env operations {{{ */ + +func (ls *LState) GetFEnv(obj LValue) LValue { + switch lv := obj.(type) { + case *LFunction: + return lv.Env + case *LUserData: + return lv.Env + case *LState: + return lv.Env + } + return LNil +} + +func (ls *LState) SetFEnv(obj LValue, env LValue) { + tb, ok := env.(*LTable) + if !ok { + ls.RaiseError("cannot use %v as an environment", env.Type().String()) + } + + switch lv := obj.(type) { + case *LFunction: + lv.Env = tb + case *LUserData: + lv.Env = tb + case *LState: + lv.Env = tb + } + /* do nothing */ +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) RawGet(tb *LTable, key LValue) LValue { + return tb.RawGet(key) +} + +func (ls *LState) RawGetInt(tb *LTable, key int) LValue { + return tb.RawGetInt(key) +} + +func (ls *LState) GetField(obj LValue, skey string) LValue { + return ls.getFieldString(obj, skey) +} + +func (ls *LState) GetTable(obj LValue, key LValue) LValue { + return ls.getField(obj, key) +} + +func (ls *LState) RawSet(tb *LTable, key LValue, value LValue) { + if n, ok := key.(LNumber); ok && math.IsNaN(float64(n)) { + ls.RaiseError("table index is NaN") + } else if key == LNil { + ls.RaiseError("table index is nil") + } + tb.RawSet(key, value) +} + +func (ls *LState) RawSetInt(tb *LTable, key int, value LValue) { + tb.RawSetInt(key, value) +} + +func (ls *LState) SetField(obj LValue, key string, value LValue) { + ls.setFieldString(obj, key, value) +} + +func (ls *LState) SetTable(obj LValue, key LValue, value LValue) { + ls.setField(obj, key, value) +} + +func (ls *LState) ForEach(tb *LTable, cb func(LValue, LValue)) { + tb.ForEach(cb) +} + +func (ls *LState) GetGlobal(name string) LValue { + return ls.GetField(ls.Get(GlobalsIndex), name) +} + +func (ls *LState) SetGlobal(name string, value LValue) { + ls.SetField(ls.Get(GlobalsIndex), name, value) +} + +func (ls *LState) Next(tb *LTable, key LValue) (LValue, LValue) { + return tb.Next(key) +} + +/* }}} */ + +/* unary operations {{{ */ + +func (ls *LState) ObjLen(v1 LValue) int { + if v1.Type() == LTString { + return len(string(v1.(LString))) + } + op := ls.metaOp1(v1, "__len") + if op.Type() == LTFunction { + ls.Push(op) + ls.Push(v1) + ls.Call(1, 1) + ret := ls.reg.Pop() + if ret.Type() == LTNumber { + return int(ret.(LNumber)) + } + } else if v1.Type() == LTTable { + return v1.(*LTable).Len() + } + return 0 +} + +/* }}} */ + +/* binary operations {{{ */ + +func (ls *LState) Concat(values ...LValue) string { + top := ls.reg.Top() + for _, value := range values { + ls.reg.Push(value) + } + ret := stringConcat(ls, len(values), ls.reg.Top()-1) + ls.reg.SetTop(top) + return LVAsString(ret) +} + +func (ls *LState) LessThan(lhs, rhs LValue) bool { + return lessThan(ls, lhs, rhs) +} + +func (ls *LState) Equal(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, false) +} + +func (ls *LState) RawEqual(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, true) +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) Register(name string, fn LGFunction) { + ls.SetGlobal(name, ls.NewFunction(fn)) +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) { + chunk, err := parse.Parse(reader, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + proto, err := Compile(chunk, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + return newLFunctionL(proto, ls.currentEnv(), 0), nil +} + +func (ls *LState) Call(nargs, nret int) { + ls.callR(nargs, nret, -1) +} + +func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { + err = nil + sp := ls.stack.Sp() + base := ls.reg.Top() - nargs - 1 + oldpanic := ls.Panic + ls.Panic = panicWithoutTraceback + if errfunc != nil { + ls.hasErrorFunc = true + } + defer func() { + ls.Panic = oldpanic + ls.hasErrorFunc = false + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + "\n" + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + } + if errfunc != nil { + ls.Push(errfunc) + ls.Push(err.(*ApiError).Object) + ls.Panic = panicWithoutTraceback + defer func() { + ls.Panic = oldpanic + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + } + }() + ls.Call(1, 1) + err = newApiError(ApiErrorError, ls.Get(-1)) + } else if len(err.(*ApiError).StackTrace) == 0 { + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) + } + ls.stack.SetSp(sp) + if sp == 0 { + ls.currentFrame = nil + } + }() + + ls.Call(nargs, nret) + + return +} + +func (ls *LState) GPCall(fn LGFunction, data LValue) error { + ls.Push(newLFunctionG(fn, ls.currentEnv(), 0)) + ls.Push(data) + return ls.PCall(1, MultRet, nil) +} + +func (ls *LState) CallByParam(cp P, args ...LValue) error { + ls.Push(cp.Fn) + for _, arg := range args { + ls.Push(arg) + } + + if cp.Protect { + return ls.PCall(len(args), cp.NRet, cp.Handler) + } + ls.Call(len(args), cp.NRet) + return nil +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) GetMetatable(obj LValue) LValue { + return ls.metatable(obj, false) +} + +func (ls *LState) SetMetatable(obj LValue, mt LValue) { + switch mt.(type) { + case *LNilType, *LTable: + default: + ls.RaiseError("metatable must be a table or nil, but got %v", mt.Type().String()) + } + + switch v := obj.(type) { + case *LTable: + v.Metatable = mt + case *LUserData: + v.Metatable = mt + default: + ls.G.builtinMts[int(obj.Type())] = mt + } +} + +/* }}} */ + +/* coroutine operations {{{ */ + +func (ls *LState) Status(th *LState) string { + status := "suspended" + if th.Dead { + status = "dead" + } else if ls.G.CurrentThread == th { + status = "running" + } else if ls.Parent == th { + status = "normal" + } + return status +} + +func (ls *LState) Resume(th *LState, fn *LFunction, args ...LValue) (ResumeState, error, []LValue) { + isstarted := th.isStarted() + if !isstarted { + base := 0 + th.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + } + + if ls.G.CurrentThread == th { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a running thread"), nil + } + if th.Dead { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a dead thread"), nil + } + th.Parent = ls + ls.G.CurrentThread = th + if !isstarted { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + for _, arg := range args { + th.Push(arg) + } + cf.NArgs = len(args) + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + for _, arg := range args { + th.Push(arg) + } + } + top := ls.GetTop() + threadRun(th) + haserror := LVIsFalse(ls.Get(top + 1)) + ret := make([]LValue, 0, ls.GetTop()) + for idx := top + 2; idx <= ls.GetTop(); idx++ { + ret = append(ret, ls.Get(idx)) + } + if len(ret) == 0 { + ret = append(ret, LNil) + } + ls.SetTop(top) + + if haserror { + return ResumeError, newApiError(ApiErrorRun, ret[0]), nil + } else if th.stack.IsEmpty() { + return ResumeOK, nil, ret + } + return ResumeYield, nil, ret +} + +func (ls *LState) Yield(values ...LValue) int { + ls.SetTop(0) + for _, lv := range values { + ls.Push(lv) + } + return -1 +} + +func (ls *LState) XMoveTo(other *LState, n int) { + if ls == other { + return + } + top := ls.GetTop() + n = intMin(n, top) + for i := n; i > 0; i-- { + other.Push(ls.Get(top - i + 1)) + } + ls.SetTop(top - n) +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// Set maximum memory size. This function can only be called from the main thread. +func (ls *LState) SetMx(mx int) { + if ls.Parent != nil { + ls.RaiseError("sub threads are not allowed to set a memory limit") + } + go func() { + limit := uint64(mx * 1024 * 1024) //MB + var s runtime.MemStats + for ls.stop == 0 { + runtime.ReadMemStats(&s) + if s.Alloc >= limit { + fmt.Println("out of memory") + os.Exit(3) + } + time.Sleep(100 * time.Millisecond) + } + }() +} + +// SetContext set a context ctx to this LState. The provided ctx must be non-nil. +func (ls *LState) SetContext(ctx context.Context) { + ls.mainLoop = mainLoopWithContext + ls.ctx = ctx +} + +// Context returns the LState's context. To change the context, use WithContext. +func (ls *LState) Context() context.Context { + return ls.ctx +} + +// RemoveContext removes the context associated with this LState and returns this context. +func (ls *LState) RemoveContext() context.Context { + oldctx := ls.ctx + ls.mainLoop = mainLoop + ls.ctx = nil + return oldctx +} + +// Converts the Lua value at the given acceptable index to the chan LValue. +func (ls *LState) ToChannel(n int) chan LValue { + if lv, ok := ls.Get(n).(LChannel); ok { + return (chan LValue)(lv) + } + return nil +} + +/* }}} */ + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/_vm.go b/vendor/github.com/yuin/gopher-lua/_vm.go new file mode 100644 index 0000000..cef4380 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/_vm.go @@ -0,0 +1,1024 @@ +package lua + +import ( + "fmt" + "math" + "strings" +) + +func mainLoop(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } +} + +func mainLoopWithContext(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + select { + case <-L.ctx.Done(): + L.RaiseError(L.ctx.Err().Error()) + return + default: + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } + } +} + +func copyReturnValues(L *LState, regv, start, n, b int) { // +inline-start + if b == 1 { + // +inline-call L.reg.FillNil regv n + } else { + // +inline-call L.reg.CopyRange regv start -1 n + } +} // +inline-end + +func switchToParentThread(L *LState, nargs int, haserror bool, kill bool) { + parent := L.Parent + if parent == nil { + L.RaiseError("can not yield from outside of a coroutine") + } + L.G.CurrentThread = parent + L.Parent = nil + if !L.wrapped { + if haserror { + parent.Push(LFalse) + } else { + parent.Push(LTrue) + } + } + L.XMoveTo(parent, nargs) + L.stack.Pop() + offset := L.currentFrame.LocalBase - L.currentFrame.ReturnBase + L.currentFrame = L.stack.Last() + L.reg.SetTop(L.reg.Top() - offset) // remove 'yield' function(including tailcalled functions) + if kill { + L.kill() + } +} + +func callGFunction(L *LState, tailcall bool) bool { + frame := L.currentFrame + gfnret := frame.Fn.GFunction(L) + if tailcall { + L.stack.Remove(L.stack.Sp() - 2) // remove caller lua function frame + L.currentFrame = L.stack.Last() + } + + if gfnret < 0 { + switchToParentThread(L, L.GetTop(), false, false) + return true + } + + wantret := frame.NRet + if wantret == MultRet { + wantret = gfnret + } + + if tailcall && L.Parent != nil && L.stack.Sp() == 1 { + switchToParentThread(L, wantret, false, true) + return true + } + + // +inline-call L.reg.CopyRange frame.ReturnBase L.reg.Top()-gfnret -1 wantret + L.stack.Pop() + L.currentFrame = L.stack.Last() + return false +} + +func threadRun(L *LState) { + if L.stack.IsEmpty() { + return + } + + defer func() { + if rcv := recover(); rcv != nil { + var lv LValue + if v, ok := rcv.(*ApiError); ok { + lv = v.Object + } else { + lv = LString(fmt.Sprint(rcv)) + } + if parent := L.Parent; parent != nil { + if L.wrapped { + L.Push(lv) + parent.Panic(L) + } else { + L.SetTop(0) + L.Push(lv) + switchToParentThread(L, 1, true, true) + } + } else { + panic(rcv) + } + } + }() + L.mainLoop(L, nil) +} + +type instFunc func(*LState, uint32, *callFrame) int + +var jumpTable [opCodeMax + 1]instFunc + +func init() { + jumpTable = [opCodeMax + 1]instFunc{ + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, reg.Get(lbase+B)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(lbase+A, reg.Get(lbase+B)) + code := cf.Fn.Proto.Code + pc := cf.Pc + for i := 0; i < C; i++ { + inst = code[pc] + pc++ + A = int(inst>>18) & 0xff //GETA + B = int(inst & 0x1ff) //GETB + reg.Set(lbase+A, reg.Get(lbase+B)) + } + cf.Pc = pc + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADK + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if B != 0 { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + if C != 0 { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADNIL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + for i := RA; i <= lbase+B; i++ { + reg.Set(i, LNil) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, cf.Fn.Upvalues[B].Value()) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + //reg.Set(RA, L.getField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx])) + reg.Set(RA, L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx])) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, L.getField(reg.Get(lbase+B), L.rkValue(C))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, L.getFieldString(reg.Get(lbase+B), L.rkString(C))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + //L.setField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx], reg.Get(RA)) + L.setFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx], reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + cf.Fn.Upvalues[B].SetValue(reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setField(reg.Get(RA), L.rkValue(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setFieldString(reg.Get(RA), L.rkString(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NEWTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, newLTable(B, C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + selfobj := reg.Get(lbase + B) + reg.Set(RA, L.getFieldString(selfobj, L.rkString(C))) + reg.Set(RA+1, selfobj) + return 0 + }, + opArith, // OP_ADD + opArith, // OP_SUB + opArith, // OP_MUL + opArith, // OP_DIV + opArith, // OP_MOD + opArith, // OP_POW + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_UNM + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + unaryv := L.rkValue(B) + if nm, ok := unaryv.(LNumber); ok { + reg.SetNumber(RA, -nm) + } else { + op := L.metaOp1(unaryv, "__unm") + if op.Type() == LTFunction { + reg.Push(op) + reg.Push(unaryv) + L.Call(1, 1) + reg.Set(RA, reg.Pop()) + } else if str, ok1 := unaryv.(LString); ok1 { + if num, err := parseNumber(string(str)); err == nil { + reg.Set(RA, -num) + } else { + L.RaiseError("__unm undefined") + } + } else { + L.RaiseError("__unm undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + if LVIsFalse(reg.Get(lbase + B)) { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + switch lv := L.rkValue(B).(type) { + case LString: + reg.SetNumber(RA, LNumber(len(lv))) + default: + op := L.metaOp1(lv, "__len") + if op.Type() == LTFunction { + reg.Push(op) + reg.Push(lv) + L.Call(1, 1) + ret := reg.Pop() + if ret.Type() == LTNumber { + reg.SetNumber(RA, ret.(LNumber)) + } else { + reg.SetNumber(RA, LNumber(0)) + } + } else if lv.Type() == LTTable { + reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + } else { + L.RaiseError("__len undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CONCAT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + RC := lbase + C + RB := lbase + B + reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP + cf := L.currentFrame + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_EQ + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := equals(L, L.rkValue(B), L.rkValue(C), false) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LT + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := lessThan(L, L.rkValue(B), L.rkValue(C)) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LE + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + ret := false + + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + ret = v1 <= v2 + } else { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + } else { + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) <= 0 + default: + switch objectRational(L, lhs, rhs, "__le") { + case 1: + ret = true + case 0: + ret = false + default: + ret = !objectRationalWithError(L, rhs, lhs, "__lt") + } + } + } + + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TEST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + C := int(inst>>9) & 0x1ff //GETC + if LVAsBool(reg.Get(RA)) == (C == 0) { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TESTSET + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if value := reg.Get(lbase + B); LVAsBool(value) != (C == 0) { + reg.Set(RA, value) + } else { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + nret := C - 1 + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + // +inline-call L.pushCallFrame callFrame{Fn:callable,Pc:0,Base:RA,LocalBase:RA+1,ReturnBase:RA,NArgs:nargs,NRet:nret,Parent:cf,TailCall:0} lv meta + if callable.IsG && callGFunction(L, false) { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TAILCALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + if callable == nil { + L.RaiseError("attempt to call a non-function object") + } + // +inline-call L.closeUpvalues lbase + if callable.IsG { + luaframe := cf + L.pushCallFrame(callFrame{ + Fn: callable, + Pc: 0, + Base: RA, + LocalBase: RA + 1, + ReturnBase: cf.ReturnBase, + NArgs: nargs, + NRet: cf.NRet, + Parent: cf, + TailCall: 0, + }, lv, meta) + if callGFunction(L, true) { + return 1 + } + if L.currentFrame == nil || L.currentFrame.Fn.IsG || luaframe == baseframe { + return 1 + } + } else { + base := cf.Base + cf.Fn = callable + cf.Pc = 0 + cf.Base = RA + cf.LocalBase = RA + 1 + cf.ReturnBase = cf.ReturnBase + cf.NArgs = nargs + cf.NRet = cf.NRet + cf.TailCall++ + lbase := cf.LocalBase + if meta { + cf.NArgs++ + L.reg.Insert(lv, cf.LocalBase) + } + // +inline-call L.initCallFrame cf + // +inline-call L.reg.CopyRange base RA -1 reg.Top()-RA-1 + cf.Base = base + cf.LocalBase = base + (cf.LocalBase - lbase + 1) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_RETURN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + // +inline-call L.closeUpvalues lbase + nret := B - 1 + if B == 0 { + nret = reg.Top() - RA + } + n := cf.NRet + if cf.NRet == MultRet { + n = nret + } + + if L.Parent != nil && L.stack.Sp() == 1 { + // +inline-call copyReturnValues L reg.Top() RA n B + switchToParentThread(L, n, false, true) + return 1 + } + islast := baseframe == L.stack.Pop() || L.stack.IsEmpty() + // +inline-call copyReturnValues L cf.ReturnBase RA n B + L.currentFrame = L.stack.Last() + if islast || L.currentFrame == nil || L.currentFrame.Fn.IsG { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORLOOP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { + if limit, ok2 := reg.Get(RA + 1).assertFloat64(); ok2 { + if step, ok3 := reg.Get(RA + 2).assertFloat64(); ok3 { + init += step + reg.SetNumber(RA, LNumber(init)) + if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + reg.SetNumber(RA+3, LNumber(init)) + } else { + reg.SetTop(RA + 1) + } + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement limit must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORPREP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { + if step, ok2 := reg.Get(RA + 2).assertFloat64(); ok2 { + reg.SetNumber(RA, LNumber(init-step)) + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TFORLOOP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + C := int(inst>>9) & 0x1ff //GETC + nret := C + reg.SetTop(RA + 3 + 2) + reg.Set(RA+3+2, reg.Get(RA+2)) + reg.Set(RA+3+1, reg.Get(RA+1)) + reg.Set(RA+3, reg.Get(RA)) + L.callR(2, nret, RA+3) + if value := reg.Get(RA + 3); value != LNil { + reg.Set(RA+2, value) + pc := cf.Fn.Proto.Code[cf.Pc] + cf.Pc += int(pc&0x3ffff) - opMaxArgSbx + } + cf.Pc++ + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETLIST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if C == 0 { + C = int(cf.Fn.Proto.Code[cf.Pc]) + cf.Pc++ + } + offset := (C - 1) * FieldsPerFlush + table := reg.Get(RA).(*LTable) + nelem := B + if B == 0 { + nelem = reg.Top() - RA - 1 + } + for i := 1; i <= nelem; i++ { + table.RawSetInt(offset+i, reg.Get(RA+i)) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSE + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + // +inline-call L.closeUpvalues RA + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSURE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + proto := cf.Fn.Proto.FunctionPrototypes[Bx] + closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) + reg.Set(RA, closure) + for i := 0; i < int(proto.NumUpvalues); i++ { + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + B := opGetArgB(inst) + switch opGetOpCode(inst) { + case OP_MOVE: + closure.Upvalues[i] = L.findUpvalue(lbase + B) + case OP_GETUPVAL: + closure.Upvalues[i] = cf.Fn.Upvalues[B] + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_VARARG + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nparams := int(cf.Fn.Proto.NumParameters) + nvarargs := cf.NArgs - nparams + if nvarargs < 0 { + nvarargs = 0 + } + nwant := B - 1 + if B == 0 { + nwant = nvarargs + } + // +inline-call reg.CopyRange RA cf.Base+nparams+1 cf.LocalBase nwant + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOP + return 0 + }, + } +} + +func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + opcode := int(inst >> 26) //GETOPCODE + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + v1, ok1 := lhs.assertFloat64() + v2, ok2 := rhs.assertFloat64() + if ok1 && ok2 { + reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + } else { + reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + } + return 0 +} + +func luaModulo(lhs, rhs LNumber) LNumber { + flhs := float64(lhs) + frhs := float64(rhs) + v := math.Mod(flhs, frhs) + if flhs < 0 || frhs < 0 && !(flhs < 0 && frhs < 0) { + v += frhs + } + return LNumber(v) +} + +func numberArith(L *LState, opcode int, lhs, rhs LNumber) LNumber { + switch opcode { + case OP_ADD: + return lhs + rhs + case OP_SUB: + return lhs - rhs + case OP_MUL: + return lhs * rhs + case OP_DIV: + return lhs / rhs + case OP_MOD: + return luaModulo(lhs, rhs) + case OP_POW: + flhs := float64(lhs) + frhs := float64(rhs) + return LNumber(math.Pow(flhs, frhs)) + } + panic("should not reach here") + return LNumber(0) +} + +func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { + event := "" + switch opcode { + case OP_ADD: + event = "__add" + case OP_SUB: + event = "__sub" + case OP_MUL: + event = "__mul" + case OP_DIV: + event = "__div" + case OP_MOD: + event = "__mod" + case OP_POW: + event = "__pow" + } + op := L.metaOp2(lhs, rhs, event) + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + return L.reg.Pop() + } + if str, ok := lhs.(LString); ok { + if lnum, err := parseNumber(string(str)); err == nil { + lhs = lnum + } + } + if str, ok := rhs.(LString); ok { + if rnum, err := parseNumber(string(str)); err == nil { + rhs = rnum + } + } + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return numberArith(L, opcode, LNumber(v1), LNumber(v2)) + } + } + L.RaiseError(fmt.Sprintf("cannot perform %v operation between %v and %v", + strings.TrimLeft(event, "_"), lhs.Type().String(), rhs.Type().String())) + + return LNil +} + +func stringConcat(L *LState, total, last int) LValue { + rhs := L.reg.Get(last) + total-- + for i := last - 1; total > 0; { + lhs := L.reg.Get(i) + if !(LVCanConvToString(lhs) && LVCanConvToString(rhs)) { + op := L.metaOp2(lhs, rhs, "__concat") + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + rhs = L.reg.Pop() + total-- + i-- + } else { + L.RaiseError("cannot perform concat operation between %v and %v", lhs.Type().String(), rhs.Type().String()) + return LNil + } + } else { + buf := make([]string, total+1) + buf[total] = LVAsString(rhs) + for total > 0 { + lhs = L.reg.Get(i) + if !LVCanConvToString(lhs) { + break + } + buf[total-1] = LVAsString(lhs) + i-- + total-- + } + rhs = LString(strings.Join(buf, "")) + } + } + return rhs +} + +func lessThan(L *LState, lhs, rhs LValue) bool { + // optimization for numbers + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return v1 < v2 + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false + } + ret := false + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) < 0 + default: + ret = objectRationalWithError(L, lhs, rhs, "__lt") + } + return ret +} + +func equals(L *LState, lhs, rhs LValue, raw bool) bool { + if lhs.Type() != rhs.Type() { + return false + } + + ret := false + switch lhs.Type() { + case LTNil: + ret = true + case LTNumber: + v1, _ := lhs.assertFloat64() + v2, _ := rhs.assertFloat64() + ret = v1 == v2 + case LTBool: + ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) + case LTString: + ret = string(lhs.(LString)) == string(rhs.(LString)) + case LTUserData, LTTable: + if lhs == rhs { + ret = true + } else if !raw { + switch objectRational(L, lhs, rhs, "__eq") { + case 1: + ret = true + default: + ret = false + } + } + default: + ret = lhs == rhs + } + return ret +} + +func objectRationalWithError(L *LState, lhs, rhs LValue, event string) bool { + switch objectRational(L, lhs, rhs, event) { + case 1: + return true + case 0: + return false + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false +} + +func objectRational(L *LState, lhs, rhs LValue, event string) int { + m1 := L.metaOp1(lhs, event) + m2 := L.metaOp1(rhs, event) + if m1.Type() == LTFunction && m1 == m2 { + L.reg.Push(m1) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + if LVAsBool(L.reg.Pop()) { + return 1 + } + return 0 + } + return -1 +} diff --git a/vendor/github.com/yuin/gopher-lua/alloc.go b/vendor/github.com/yuin/gopher-lua/alloc.go new file mode 100644 index 0000000..4db4660 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/alloc.go @@ -0,0 +1,73 @@ +package lua + +import ( + "reflect" + "unsafe" +) + +// iface is an internal representation of the go-interface. +type iface struct { + itab unsafe.Pointer + word unsafe.Pointer +} + +const preloadLimit LNumber = 128 + +var _fv float64 +var _uv uintptr + +// allocator is a fast bulk memory allocator for the LValue. +type allocator struct { + top int + size int + nptrs []LValue + nheader *reflect.SliceHeader + fptrs []float64 + fheader *reflect.SliceHeader + itabLNumber unsafe.Pointer + preloads [int(preloadLimit)]LValue +} + +func newAllocator(size int) *allocator { + al := &allocator{ + top: 0, + size: size, + nptrs: make([]LValue, size), + nheader: nil, + fptrs: make([]float64, size), + fheader: nil, + itabLNumber: unsafe.Pointer(nil), + } + al.nheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.nptrs)) + al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs)) + + var v LValue = LNumber(0) + vp := (*iface)(unsafe.Pointer(&v)) + al.itabLNumber = vp.itab + for i := 0; i < int(preloadLimit); i++ { + al.preloads[i] = LNumber(i) + } + return al +} + +func (al *allocator) LNumber2I(v LNumber) LValue { + if v >= 0 && v < preloadLimit && float64(v) == float64(int64(v)) { + return al.preloads[int(v)] + } + if al.top == len(al.nptrs)-1 { + al.top = 0 + al.nptrs = make([]LValue, al.size) + al.nheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.nptrs)) + al.fptrs = make([]float64, al.size) + al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs)) + } + fptr := (*float64)(unsafe.Pointer(al.fheader.Data + uintptr(al.top)*unsafe.Sizeof(_fv))) + e := *(*LValue)(unsafe.Pointer(al.nheader.Data + uintptr(al.top)*unsafe.Sizeof(_uv))) + al.top++ + + ep := (*iface)(unsafe.Pointer(&e)) + ep.itab = al.itabLNumber + *fptr = float64(v) + ep.word = unsafe.Pointer(fptr) + return e +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/ast.go b/vendor/github.com/yuin/gopher-lua/ast/ast.go new file mode 100644 index 0000000..f337a29 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/ast.go @@ -0,0 +1,29 @@ +package ast + +type PositionHolder interface { + Line() int + SetLine(int) + LastLine() int + SetLastLine(int) +} + +type Node struct { + line int + lastline int +} + +func (self *Node) Line() int { + return self.line +} + +func (self *Node) SetLine(line int) { + self.line = line +} + +func (self *Node) LastLine() int { + return self.lastline +} + +func (self *Node) SetLastLine(line int) { + self.lastline = line +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/expr.go b/vendor/github.com/yuin/gopher-lua/ast/expr.go new file mode 100644 index 0000000..ccda327 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/expr.go @@ -0,0 +1,137 @@ +package ast + +type Expr interface { + PositionHolder + exprMarker() +} + +type ExprBase struct { + Node +} + +func (expr *ExprBase) exprMarker() {} + +/* ConstExprs {{{ */ + +type ConstExpr interface { + Expr + constExprMarker() +} + +type ConstExprBase struct { + ExprBase +} + +func (expr *ConstExprBase) constExprMarker() {} + +type TrueExpr struct { + ConstExprBase +} + +type FalseExpr struct { + ConstExprBase +} + +type NilExpr struct { + ConstExprBase +} + +type NumberExpr struct { + ConstExprBase + + Value string +} + +type StringExpr struct { + ConstExprBase + + Value string +} + +/* ConstExprs }}} */ + +type Comma3Expr struct { + ExprBase +} + +type IdentExpr struct { + ExprBase + + Value string +} + +type AttrGetExpr struct { + ExprBase + + Object Expr + Key Expr +} + +type TableExpr struct { + ExprBase + + Fields []*Field +} + +type FuncCallExpr struct { + ExprBase + + Func Expr + Receiver Expr + Method string + Args []Expr + AdjustRet bool +} + +type LogicalOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type RelationalOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type StringConcatOpExpr struct { + ExprBase + + Lhs Expr + Rhs Expr +} + +type ArithmeticOpExpr struct { + ExprBase + + Operator string + Lhs Expr + Rhs Expr +} + +type UnaryMinusOpExpr struct { + ExprBase + Expr Expr +} + +type UnaryNotOpExpr struct { + ExprBase + Expr Expr +} + +type UnaryLenOpExpr struct { + ExprBase + Expr Expr +} + +type FunctionExpr struct { + ExprBase + + ParList *ParList + Stmts []Stmt +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/misc.go b/vendor/github.com/yuin/gopher-lua/ast/misc.go new file mode 100644 index 0000000..d811c04 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/misc.go @@ -0,0 +1,17 @@ +package ast + +type Field struct { + Key Expr + Value Expr +} + +type ParList struct { + HasVargs bool + Names []string +} + +type FuncName struct { + Func Expr + Receiver Expr + Method string +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/stmt.go b/vendor/github.com/yuin/gopher-lua/ast/stmt.go new file mode 100644 index 0000000..56ea6d1 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/stmt.go @@ -0,0 +1,95 @@ +package ast + +type Stmt interface { + PositionHolder + stmtMarker() +} + +type StmtBase struct { + Node +} + +func (stmt *StmtBase) stmtMarker() {} + +type AssignStmt struct { + StmtBase + + Lhs []Expr + Rhs []Expr +} + +type LocalAssignStmt struct { + StmtBase + + Names []string + Exprs []Expr +} + +type FuncCallStmt struct { + StmtBase + + Expr Expr +} + +type DoBlockStmt struct { + StmtBase + + Stmts []Stmt +} + +type WhileStmt struct { + StmtBase + + Condition Expr + Stmts []Stmt +} + +type RepeatStmt struct { + StmtBase + + Condition Expr + Stmts []Stmt +} + +type IfStmt struct { + StmtBase + + Condition Expr + Then []Stmt + Else []Stmt +} + +type NumberForStmt struct { + StmtBase + + Name string + Init Expr + Limit Expr + Step Expr + Stmts []Stmt +} + +type GenericForStmt struct { + StmtBase + + Names []string + Exprs []Expr + Stmts []Stmt +} + +type FuncDefStmt struct { + StmtBase + + Name *FuncName + Func *FunctionExpr +} + +type ReturnStmt struct { + StmtBase + + Exprs []Expr +} + +type BreakStmt struct { + StmtBase +} diff --git a/vendor/github.com/yuin/gopher-lua/ast/token.go b/vendor/github.com/yuin/gopher-lua/ast/token.go new file mode 100644 index 0000000..820467c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/ast/token.go @@ -0,0 +1,22 @@ +package ast + +import ( + "fmt" +) + +type Position struct { + Source string + Line int + Column int +} + +type Token struct { + Type int + Name string + Str string + Pos Position +} + +func (self *Token) String() string { + return fmt.Sprintf("", self.Name, self.Str) +} diff --git a/vendor/github.com/yuin/gopher-lua/auxlib.go b/vendor/github.com/yuin/gopher-lua/auxlib.go new file mode 100644 index 0000000..1519939 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/auxlib.go @@ -0,0 +1,458 @@ +package lua + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" +) + +/* checkType {{{ */ + +func (ls *LState) CheckAny(n int) LValue { + if n > ls.GetTop() { + ls.ArgError(n, "value expected") + } + return ls.Get(n) +} + +func (ls *LState) CheckInt(n int) int { + v := ls.Get(n) + if intv, ok := v.(LNumber); ok { + return int(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckInt64(n int) int64 { + v := ls.Get(n) + if intv, ok := v.(LNumber); ok { + return int64(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckNumber(n int) LNumber { + v := ls.Get(n) + if lv, ok := v.(LNumber); ok { + return lv + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) CheckString(n int) string { + v := ls.Get(n) + if lv, ok := v.(LString); ok { + return string(lv) + } + ls.TypeError(n, LTString) + return "" +} + +func (ls *LState) CheckBool(n int) bool { + v := ls.Get(n) + if lv, ok := v.(LBool); ok { + return bool(lv) + } + ls.TypeError(n, LTBool) + return false +} + +func (ls *LState) CheckTable(n int) *LTable { + v := ls.Get(n) + if lv, ok := v.(*LTable); ok { + return lv + } + ls.TypeError(n, LTTable) + return nil +} + +func (ls *LState) CheckFunction(n int) *LFunction { + v := ls.Get(n) + if lv, ok := v.(*LFunction); ok { + return lv + } + ls.TypeError(n, LTFunction) + return nil +} + +func (ls *LState) CheckUserData(n int) *LUserData { + v := ls.Get(n) + if lv, ok := v.(*LUserData); ok { + return lv + } + ls.TypeError(n, LTUserData) + return nil +} + +func (ls *LState) CheckThread(n int) *LState { + v := ls.Get(n) + if lv, ok := v.(*LState); ok { + return lv + } + ls.TypeError(n, LTThread) + return nil +} + +func (ls *LState) CheckType(n int, typ LValueType) { + v := ls.Get(n) + if v.Type() != typ { + ls.TypeError(n, typ) + } +} + +func (ls *LState) CheckTypes(n int, typs ...LValueType) { + vt := ls.Get(n).Type() + for _, typ := range typs { + if vt == typ { + return + } + } + buf := []string{} + for _, typ := range typs { + buf = append(buf, typ.String()) + } + ls.ArgError(n, strings.Join(buf, " or ")+" expected, got "+ls.Get(n).Type().String()) +} + +func (ls *LState) CheckOption(n int, options []string) int { + str := ls.CheckString(n) + for i, v := range options { + if v == str { + return i + } + } + ls.ArgError(n, fmt.Sprintf("invalid option: %s (must be one of %s)", str, strings.Join(options, ","))) + return 0 +} + +/* }}} */ + +/* optType {{{ */ + +func (ls *LState) OptInt(n int, d int) int { + v := ls.Get(n) + if v == LNil { + return d + } + if intv, ok := v.(LNumber); ok { + return int(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptInt64(n int, d int64) int64 { + v := ls.Get(n) + if v == LNil { + return d + } + if intv, ok := v.(LNumber); ok { + return int64(intv) + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptNumber(n int, d LNumber) LNumber { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LNumber); ok { + return lv + } + ls.TypeError(n, LTNumber) + return 0 +} + +func (ls *LState) OptString(n int, d string) string { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LString); ok { + return string(lv) + } + ls.TypeError(n, LTString) + return "" +} + +func (ls *LState) OptBool(n int, d bool) bool { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(LBool); ok { + return bool(lv) + } + ls.TypeError(n, LTBool) + return false +} + +func (ls *LState) OptTable(n int, d *LTable) *LTable { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LTable); ok { + return lv + } + ls.TypeError(n, LTTable) + return nil +} + +func (ls *LState) OptFunction(n int, d *LFunction) *LFunction { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LFunction); ok { + return lv + } + ls.TypeError(n, LTFunction) + return nil +} + +func (ls *LState) OptUserData(n int, d *LUserData) *LUserData { + v := ls.Get(n) + if v == LNil { + return d + } + if lv, ok := v.(*LUserData); ok { + return lv + } + ls.TypeError(n, LTUserData) + return nil +} + +/* }}} */ + +/* error operations {{{ */ + +func (ls *LState) ArgError(n int, message string) { + ls.RaiseError("bad argument #%v to %v (%v)", n, ls.rawFrameFuncName(ls.currentFrame), message) +} + +func (ls *LState) TypeError(n int, typ LValueType) { + ls.RaiseError("bad argument #%v to %v (%v expected, got %v)", n, ls.rawFrameFuncName(ls.currentFrame), typ.String(), ls.Get(n).Type().String()) +} + +/* }}} */ + +/* debug operations {{{ */ + +func (ls *LState) Where(level int) string { + return ls.where(level, false) +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) FindTable(obj *LTable, n string, size int) LValue { + names := strings.Split(n, ".") + curobj := obj + for _, name := range names { + if curobj.Type() != LTTable { + return LNil + } + nextobj := ls.RawGet(curobj, LString(name)) + if nextobj == LNil { + tb := ls.CreateTable(0, size) + ls.RawSet(curobj, LString(name), tb) + curobj = tb + } else if nextobj.Type() != LTTable { + return LNil + } else { + curobj = nextobj.(*LTable) + } + } + return curobj +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) RegisterModule(name string, funcs map[string]LGFunction) LValue { + tb := ls.FindTable(ls.Get(RegistryIndex).(*LTable), "_LOADED", 1) + mod := ls.GetField(tb, name) + if mod.Type() != LTTable { + newmod := ls.FindTable(ls.Get(GlobalsIndex).(*LTable), name, len(funcs)) + if newmodtb, ok := newmod.(*LTable); !ok { + ls.RaiseError("name conflict for module(%v)", name) + } else { + for fname, fn := range funcs { + newmodtb.RawSetString(fname, ls.NewFunction(fn)) + } + ls.SetField(tb, name, newmodtb) + return newmodtb + } + } + return mod +} + +func (ls *LState) SetFuncs(tb *LTable, funcs map[string]LGFunction, upvalues ...LValue) *LTable { + for fname, fn := range funcs { + tb.RawSetString(fname, ls.NewClosure(fn, upvalues...)) + } + return tb +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) NewTypeMetatable(typ string) *LTable { + regtable := ls.Get(RegistryIndex) + mt := ls.GetField(regtable, typ) + if tb, ok := mt.(*LTable); ok { + return tb + } + mtnew := ls.NewTable() + ls.SetField(regtable, typ, mtnew) + return mtnew +} + +func (ls *LState) GetMetaField(obj LValue, event string) LValue { + return ls.metaOp1(obj, event) +} + +func (ls *LState) GetTypeMetatable(typ string) LValue { + return ls.GetField(ls.Get(RegistryIndex), typ) +} + +func (ls *LState) CallMeta(obj LValue, event string) LValue { + op := ls.metaOp1(obj, event) + if op.Type() == LTFunction { + ls.reg.Push(op) + ls.reg.Push(obj) + ls.Call(1, 1) + return ls.reg.Pop() + } + return LNil +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) LoadFile(path string) (*LFunction, error) { + var file *os.File + var err error + if len(path) == 0 { + file = os.Stdin + } else { + file, err = os.Open(path) + defer file.Close() + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + reader := bufio.NewReader(file) + // get the first character. + c, err := reader.ReadByte() + if err != nil && err != io.EOF { + return nil, newApiErrorE(ApiErrorFile, err) + } + if c == byte('#') { + // Unix exec. file? + // skip first line + _, err, _ = readBufioLine(reader) + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + if err != io.EOF { + // if the file is not empty, + // unread the first character of the file or newline character(readBufioLine's last byte). + err = reader.UnreadByte() + if err != nil { + return nil, newApiErrorE(ApiErrorFile, err) + } + } + + return ls.Load(reader, path) +} + +func (ls *LState) LoadString(source string) (*LFunction, error) { + return ls.Load(strings.NewReader(source), "") +} + +func (ls *LState) DoFile(path string) error { + if fn, err := ls.LoadFile(path); err != nil { + return err + } else { + ls.Push(fn) + return ls.PCall(0, MultRet, nil) + } +} + +func (ls *LState) DoString(source string) error { + if fn, err := ls.LoadString(source); err != nil { + return err + } else { + ls.Push(fn) + return ls.PCall(0, MultRet, nil) + } +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// ToStringMeta returns string representation of given LValue. +// This method calls the `__tostring` meta method if defined. +func (ls *LState) ToStringMeta(lv LValue) LValue { + if fn, ok := ls.metaOp1(lv, "__tostring").assertFunction(); ok { + ls.Push(fn) + ls.Push(lv) + ls.Call(1, 1) + return ls.reg.Pop() + } else { + return LString(lv.String()) + } +} + +// Set a module loader to the package.preload table. +func (ls *LState) PreloadModule(name string, loader LGFunction) { + preload := ls.GetField(ls.GetField(ls.Get(EnvironIndex), "package"), "preload") + if _, ok := preload.(*LTable); !ok { + ls.RaiseError("package.preload must be a table") + } + ls.SetField(preload, name, ls.NewFunction(loader)) +} + +// Checks whether the given index is an LChannel and returns this channel. +func (ls *LState) CheckChannel(n int) chan LValue { + v := ls.Get(n) + if ch, ok := v.(LChannel); ok { + return (chan LValue)(ch) + } + ls.TypeError(n, LTChannel) + return nil +} + +// If the given index is a LChannel, returns this channel. If this argument is absent or is nil, returns ch. Otherwise, raises an error. +func (ls *LState) OptChannel(n int, ch chan LValue) chan LValue { + v := ls.Get(n) + if v == LNil { + return ch + } + if ch, ok := v.(LChannel); ok { + return (chan LValue)(ch) + } + ls.TypeError(n, LTChannel) + return nil +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/auxlib_test.go b/vendor/github.com/yuin/gopher-lua/auxlib_test.go new file mode 100644 index 0000000..ac8356d --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/auxlib_test.go @@ -0,0 +1,333 @@ +package lua + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestCheckInt(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Push(LNumber(10)) + errorIfNotEqual(t, 10, L.CheckInt(2)) + L.Push(LString("aaa")) + L.CheckInt(3) + return 0 + }, "number expected, got string") +} + +func TestCheckInt64(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Push(LNumber(10)) + errorIfNotEqual(t, int64(10), L.CheckInt64(2)) + L.Push(LString("aaa")) + L.CheckInt64(3) + return 0 + }, "number expected, got string") +} + +func TestCheckNumber(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Push(LNumber(10)) + errorIfNotEqual(t, LNumber(10), L.CheckNumber(2)) + L.Push(LString("aaa")) + L.CheckNumber(3) + return 0 + }, "number expected, got string") +} + +func TestCheckString(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Push(LString("aaa")) + errorIfNotEqual(t, "aaa", L.CheckString(2)) + L.Push(LNumber(10)) + L.CheckString(3) + return 0 + }, "string expected, got number") +} + +func TestCheckBool(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Push(LTrue) + errorIfNotEqual(t, true, L.CheckBool(2)) + L.Push(LNumber(10)) + L.CheckBool(3) + return 0 + }, "boolean expected, got number") +} + +func TestCheckTable(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + tbl := L.NewTable() + L.Push(tbl) + errorIfNotEqual(t, tbl, L.CheckTable(2)) + L.Push(LNumber(10)) + L.CheckTable(3) + return 0 + }, "table expected, got number") +} + +func TestCheckFunction(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + fn := L.NewFunction(func(l *LState) int { return 0 }) + L.Push(fn) + errorIfNotEqual(t, fn, L.CheckFunction(2)) + L.Push(LNumber(10)) + L.CheckFunction(3) + return 0 + }, "function expected, got number") +} + +func TestCheckUserData(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + ud := L.NewUserData() + L.Push(ud) + errorIfNotEqual(t, ud, L.CheckUserData(2)) + L.Push(LNumber(10)) + L.CheckUserData(3) + return 0 + }, "userdata expected, got number") +} + +func TestCheckThread(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + th, _ := L.NewThread() + L.Push(th) + errorIfNotEqual(t, th, L.CheckThread(2)) + L.Push(LNumber(10)) + L.CheckThread(3) + return 0 + }, "thread expected, got number") +} + +func TestCheckChannel(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + ch := make(chan LValue) + L.Push(LChannel(ch)) + errorIfNotEqual(t, ch, L.CheckChannel(2)) + L.Push(LString("aaa")) + L.CheckChannel(3) + return 0 + }, "channel expected, got string") +} + +func TestCheckType(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Push(LNumber(10)) + L.CheckType(2, LTNumber) + L.CheckType(2, LTString) + return 0 + }, "string expected, got number") +} + +func TestCheckTypes(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Push(LNumber(10)) + L.CheckTypes(2, LTString, LTBool, LTNumber) + L.CheckTypes(2, LTString, LTBool) + return 0 + }, "string or boolean expected, got number") +} + +func TestCheckOption(t *testing.T) { + opts := []string{ + "opt1", + "opt2", + "opt3", + } + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Push(LString("opt1")) + errorIfNotEqual(t, 0, L.CheckOption(2, opts)) + L.Push(LString("opt5")) + L.CheckOption(3, opts) + return 0 + }, "invalid option: opt5 \\(must be one of opt1,opt2,opt3\\)") +} + +func TestOptInt(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + errorIfNotEqual(t, 99, L.OptInt(1, 99)) + L.Push(LNumber(10)) + errorIfNotEqual(t, 10, L.OptInt(2, 99)) + L.Push(LString("aaa")) + L.OptInt(3, 99) + return 0 + }, "number expected, got string") +} + +func TestOptInt64(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + errorIfNotEqual(t, int64(99), L.OptInt64(1, int64(99))) + L.Push(LNumber(10)) + errorIfNotEqual(t, int64(10), L.OptInt64(2, int64(99))) + L.Push(LString("aaa")) + L.OptInt64(3, int64(99)) + return 0 + }, "number expected, got string") +} + +func TestOptNumber(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + errorIfNotEqual(t, LNumber(99), L.OptNumber(1, LNumber(99))) + L.Push(LNumber(10)) + errorIfNotEqual(t, LNumber(10), L.OptNumber(2, LNumber(99))) + L.Push(LString("aaa")) + L.OptNumber(3, LNumber(99)) + return 0 + }, "number expected, got string") +} + +func TestOptString(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + errorIfNotEqual(t, "bbb", L.OptString(1, "bbb")) + L.Push(LString("aaa")) + errorIfNotEqual(t, "aaa", L.OptString(2, "bbb")) + L.Push(LNumber(10)) + L.OptString(3, "bbb") + return 0 + }, "string expected, got number") +} + +func TestOptBool(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + errorIfNotEqual(t, true, L.OptBool(1, true)) + L.Push(LTrue) + errorIfNotEqual(t, true, L.OptBool(2, false)) + L.Push(LNumber(10)) + L.OptBool(3, false) + return 0 + }, "boolean expected, got number") +} + +func TestOptTable(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + deftbl := L.NewTable() + errorIfNotEqual(t, deftbl, L.OptTable(1, deftbl)) + tbl := L.NewTable() + L.Push(tbl) + errorIfNotEqual(t, tbl, L.OptTable(2, deftbl)) + L.Push(LNumber(10)) + L.OptTable(3, deftbl) + return 0 + }, "table expected, got number") +} + +func TestOptFunction(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + deffn := L.NewFunction(func(l *LState) int { return 0 }) + errorIfNotEqual(t, deffn, L.OptFunction(1, deffn)) + fn := L.NewFunction(func(l *LState) int { return 0 }) + L.Push(fn) + errorIfNotEqual(t, fn, L.OptFunction(2, deffn)) + L.Push(LNumber(10)) + L.OptFunction(3, deffn) + return 0 + }, "function expected, got number") +} + +func TestOptUserData(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + defud := L.NewUserData() + errorIfNotEqual(t, defud, L.OptUserData(1, defud)) + ud := L.NewUserData() + L.Push(ud) + errorIfNotEqual(t, ud, L.OptUserData(2, defud)) + L.Push(LNumber(10)) + L.OptUserData(3, defud) + return 0 + }, "userdata expected, got number") +} + +func TestOptChannel(t *testing.T) { + L := NewState() + defer L.Close() + errorIfGFuncNotFail(t, L, func(L *LState) int { + defch := make(chan LValue) + errorIfNotEqual(t, defch, L.OptChannel(1, defch)) + ch := make(chan LValue) + L.Push(LChannel(ch)) + errorIfNotEqual(t, ch, L.OptChannel(2, defch)) + L.Push(LString("aaa")) + L.OptChannel(3, defch) + return 0 + }, "channel expected, got string") +} + +func TestLoadFileForShebang(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + errorIfNotNil(t, err) + + err = ioutil.WriteFile(tmpFile.Name(), []byte(`#!/path/to/lua +print("hello") +`), 0644) + errorIfNotNil(t, err) + + defer func() { + tmpFile.Close() + os.Remove(tmpFile.Name()) + }() + + L := NewState() + defer L.Close() + + _, err = L.LoadFile(tmpFile.Name()) + errorIfNotNil(t, err) +} + +func TestLoadFileForEmptyFile(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + errorIfNotNil(t, err) + + defer func() { + tmpFile.Close() + os.Remove(tmpFile.Name()) + }() + + L := NewState() + defer L.Close() + + _, err = L.LoadFile(tmpFile.Name()) + errorIfNotNil(t, err) +} diff --git a/vendor/github.com/yuin/gopher-lua/baselib.go b/vendor/github.com/yuin/gopher-lua/baselib.go new file mode 100644 index 0000000..2868d41 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/baselib.go @@ -0,0 +1,586 @@ +package lua + +import ( + "fmt" + "io" + "os" + "runtime" + "strconv" + "strings" +) + +/* basic functions {{{ */ + +func OpenBase(L *LState) int { + global := L.Get(GlobalsIndex).(*LTable) + L.SetGlobal("_G", global) + L.SetGlobal("_VERSION", LString(LuaVersion)) + L.SetGlobal("_GOPHER_LUA_VERSION", LString(PackageName+" "+PackageVersion)) + basemod := L.RegisterModule("_G", baseFuncs) + global.RawSetString("ipairs", L.NewClosure(baseIpairs, L.NewFunction(ipairsaux))) + global.RawSetString("pairs", L.NewClosure(basePairs, L.NewFunction(pairsaux))) + L.Push(basemod) + return 1 +} + +var baseFuncs = map[string]LGFunction{ + "assert": baseAssert, + "collectgarbage": baseCollectGarbage, + "dofile": baseDoFile, + "error": baseError, + "getfenv": baseGetFEnv, + "getmetatable": baseGetMetatable, + "load": baseLoad, + "loadfile": baseLoadFile, + "loadstring": baseLoadString, + "next": baseNext, + "pcall": basePCall, + "print": basePrint, + "rawequal": baseRawEqual, + "rawget": baseRawGet, + "rawset": baseRawSet, + "select": baseSelect, + "_printregs": base_PrintRegs, + "setfenv": baseSetFEnv, + "setmetatable": baseSetMetatable, + "tonumber": baseToNumber, + "tostring": baseToString, + "type": baseType, + "unpack": baseUnpack, + "xpcall": baseXPCall, + // loadlib + "module": loModule, + "require": loRequire, + // hidden features + "newproxy": baseNewProxy, +} + +func baseAssert(L *LState) int { + if !L.ToBool(1) { + L.RaiseError(L.OptString(2, "assertion failed!")) + return 0 + } + return L.GetTop() +} + +func baseCollectGarbage(L *LState) int { + runtime.GC() + return 0 +} + +func baseDoFile(L *LState) int { + src := L.ToString(1) + top := L.GetTop() + fn, err := L.LoadFile(src) + if err != nil { + L.Push(LString(err.Error())) + L.Panic(L) + } + L.Push(fn) + L.Call(0, MultRet) + return L.GetTop() - top +} + +func baseError(L *LState) int { + obj := L.CheckAny(1) + level := L.OptInt(2, 1) + L.Error(obj, level) + return 0 +} + +func baseGetFEnv(L *LState) int { + var value LValue + if L.GetTop() == 0 { + value = LNumber(1) + } else { + value = L.Get(1) + } + + if fn, ok := value.(*LFunction); ok { + if !fn.IsG { + L.Push(fn.Env) + } else { + L.Push(L.G.Global) + } + return 1 + } + + if number, ok := value.(LNumber); ok { + level := int(float64(number)) + if level <= 0 { + L.Push(L.Env) + } else { + cf := L.currentFrame + for i := 0; i < level && cf != nil; i++ { + cf = cf.Parent + } + if cf == nil || cf.Fn.IsG { + L.Push(L.G.Global) + } else { + L.Push(cf.Fn.Env) + } + } + return 1 + } + + L.Push(L.G.Global) + return 1 +} + +func baseGetMetatable(L *LState) int { + L.Push(L.GetMetatable(L.CheckAny(1))) + return 1 +} + +func ipairsaux(L *LState) int { + tb := L.CheckTable(1) + i := L.CheckInt(2) + i++ + v := tb.RawGetInt(i) + if v == LNil { + return 0 + } else { + L.Pop(1) + L.Push(LNumber(i)) + L.Push(LNumber(i)) + L.Push(v) + return 2 + } +} + +func baseIpairs(L *LState) int { + tb := L.CheckTable(1) + L.Push(L.Get(UpvalueIndex(1))) + L.Push(tb) + L.Push(LNumber(0)) + return 3 +} + +func loadaux(L *LState, reader io.Reader, chunkname string) int { + if fn, err := L.Load(reader, chunkname); err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(fn) + return 1 + } +} + +func baseLoad(L *LState) int { + fn := L.CheckFunction(1) + chunkname := L.OptString(2, "?") + top := L.GetTop() + buf := []string{} + for { + L.SetTop(top) + L.Push(fn) + L.Call(0, 1) + ret := L.reg.Pop() + if ret == LNil { + break + } else if LVCanConvToString(ret) { + str := ret.String() + if len(str) > 0 { + buf = append(buf, string(str)) + } else { + break + } + } else { + L.Push(LNil) + L.Push(LString("reader function must return a string")) + return 2 + } + } + return loadaux(L, strings.NewReader(strings.Join(buf, "")), chunkname) +} + +func baseLoadFile(L *LState) int { + var reader io.Reader + var chunkname string + var err error + if L.GetTop() < 1 { + reader = os.Stdin + chunkname = "" + } else { + chunkname = L.CheckString(1) + reader, err = os.Open(chunkname) + if err != nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("can not open file: %v", chunkname))) + return 2 + } + defer reader.(*os.File).Close() + } + return loadaux(L, reader, chunkname) +} + +func baseLoadString(L *LState) int { + return loadaux(L, strings.NewReader(L.CheckString(1)), L.OptString(2, "")) +} + +func baseNext(L *LState) int { + tb := L.CheckTable(1) + index := LNil + if L.GetTop() >= 2 { + index = L.Get(2) + } + key, value := tb.Next(index) + if key == LNil { + L.Push(LNil) + return 1 + } + L.Push(key) + L.Push(value) + return 2 +} + +func pairsaux(L *LState) int { + tb := L.CheckTable(1) + key, value := tb.Next(L.Get(2)) + if key == LNil { + return 0 + } else { + L.Pop(1) + L.Push(key) + L.Push(key) + L.Push(value) + return 2 + } +} + +func basePairs(L *LState) int { + tb := L.CheckTable(1) + L.Push(L.Get(UpvalueIndex(1))) + L.Push(tb) + L.Push(LNil) + return 3 +} + +func basePCall(L *LState) int { + L.CheckFunction(1) + nargs := L.GetTop() - 1 + if err := L.PCall(nargs, MultRet, nil); err != nil { + L.Push(LFalse) + if aerr, ok := err.(*ApiError); ok { + L.Push(aerr.Object) + } else { + L.Push(LString(err.Error())) + } + return 2 + } else { + L.Insert(LTrue, 1) + return L.GetTop() + } +} + +func basePrint(L *LState) int { + top := L.GetTop() + for i := 1; i <= top; i++ { + fmt.Print(L.ToStringMeta(L.Get(i)).String()) + if i != top { + fmt.Print("\t") + } + } + fmt.Println("") + return 0 +} + +func base_PrintRegs(L *LState) int { + L.printReg() + return 0 +} + +func baseRawEqual(L *LState) int { + if L.CheckAny(1) == L.CheckAny(2) { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + return 1 +} + +func baseRawGet(L *LState) int { + L.Push(L.RawGet(L.CheckTable(1), L.CheckAny(2))) + return 1 +} + +func baseRawSet(L *LState) int { + L.RawSet(L.CheckTable(1), L.CheckAny(2), L.CheckAny(3)) + return 0 +} + +func baseSelect(L *LState) int { + L.CheckTypes(1, LTNumber, LTString) + switch lv := L.Get(1).(type) { + case LNumber: + idx := int(lv) + num := L.reg.Top() - L.indexToReg(int(lv)) - 1 + if idx < 0 { + num++ + } + return num + case LString: + if string(lv) != "#" { + L.ArgError(1, "invalid string '"+string(lv)+"'") + } + L.Push(LNumber(L.GetTop() - 1)) + return 1 + } + return 0 +} + +func baseSetFEnv(L *LState) int { + var value LValue + if L.GetTop() == 0 { + value = LNumber(1) + } else { + value = L.Get(1) + } + env := L.CheckTable(2) + + if fn, ok := value.(*LFunction); ok { + if fn.IsG { + L.RaiseError("cannot change the environment of given object") + } else { + fn.Env = env + L.Push(fn) + return 1 + } + } + + if number, ok := value.(LNumber); ok { + level := int(float64(number)) + if level <= 0 { + L.Env = env + return 0 + } + + cf := L.currentFrame + for i := 0; i < level && cf != nil; i++ { + cf = cf.Parent + } + if cf == nil || cf.Fn.IsG { + L.RaiseError("cannot change the environment of given object") + } else { + cf.Fn.Env = env + L.Push(cf.Fn) + return 1 + } + } + + L.RaiseError("cannot change the environment of given object") + return 0 +} + +func baseSetMetatable(L *LState) int { + L.CheckTypes(2, LTNil, LTTable) + obj := L.Get(1) + if obj == LNil { + L.RaiseError("cannot set metatable to a nil object.") + } + mt := L.Get(2) + if m := L.metatable(obj, true); m != LNil { + if tb, ok := m.(*LTable); ok && tb.RawGetString("__metatable") != LNil { + L.RaiseError("cannot change a protected metatable") + } + } + L.SetMetatable(obj, mt) + L.SetTop(1) + return 1 +} + +func baseToNumber(L *LState) int { + base := L.OptInt(2, 10) + noBase := L.Get(2) == LNil + + switch lv := L.CheckAny(1).(type) { + case LNumber: + L.Push(lv) + case LString: + str := strings.Trim(string(lv), " \n\t") + if strings.Index(str, ".") > -1 { + if v, err := strconv.ParseFloat(str, LNumberBit); err != nil { + L.Push(LNil) + } else { + L.Push(LNumber(v)) + } + } else { + if noBase && strings.HasPrefix(strings.ToLower(str), "0x") { + base, str = 16, str[2:] // Hex number + } + if v, err := strconv.ParseInt(str, base, LNumberBit); err != nil { + L.Push(LNil) + } else { + L.Push(LNumber(v)) + } + } + default: + L.Push(LNil) + } + return 1 +} + +func baseToString(L *LState) int { + v1 := L.CheckAny(1) + L.Push(L.ToStringMeta(v1)) + return 1 +} + +func baseType(L *LState) int { + L.Push(LString(L.CheckAny(1).Type().String())) + return 1 +} + +func baseUnpack(L *LState) int { + tb := L.CheckTable(1) + start := L.OptInt(2, 1) + end := L.OptInt(3, tb.Len()) + for i := start; i <= end; i++ { + L.Push(tb.RawGetInt(i)) + } + ret := end - start + 1 + if ret < 0 { + return 0 + } + return ret +} + +func baseXPCall(L *LState) int { + fn := L.CheckFunction(1) + errfunc := L.CheckFunction(2) + + top := L.GetTop() + L.Push(fn) + if err := L.PCall(0, MultRet, errfunc); err != nil { + L.Push(LFalse) + if aerr, ok := err.(*ApiError); ok { + L.Push(aerr.Object) + } else { + L.Push(LString(err.Error())) + } + return 2 + } else { + L.Insert(LTrue, top+1) + return L.GetTop() - top + } +} + +/* }}} */ + +/* load lib {{{ */ + +func loModule(L *LState) int { + name := L.CheckString(1) + loaded := L.GetField(L.Get(RegistryIndex), "_LOADED") + tb := L.GetField(loaded, name) + if _, ok := tb.(*LTable); !ok { + tb = L.FindTable(L.Get(GlobalsIndex).(*LTable), name, 1) + if tb == LNil { + L.RaiseError("name conflict for module: %v", name) + } + L.SetField(loaded, name, tb) + } + if L.GetField(tb, "_NAME") == LNil { + L.SetField(tb, "_M", tb) + L.SetField(tb, "_NAME", LString(name)) + names := strings.Split(name, ".") + pname := "" + if len(names) > 1 { + pname = strings.Join(names[:len(names)-1], ".") + "." + } + L.SetField(tb, "_PACKAGE", LString(pname)) + } + + caller := L.currentFrame.Parent + if caller == nil { + L.RaiseError("no calling stack.") + } else if caller.Fn.IsG { + L.RaiseError("module() can not be called from GFunctions.") + } + L.SetFEnv(caller.Fn, tb) + + top := L.GetTop() + for i := 2; i <= top; i++ { + L.Push(L.Get(i)) + L.Push(tb) + L.Call(1, 0) + } + L.Push(tb) + return 1 +} + +var loopdetection = &LUserData{} + +func loRequire(L *LState) int { + name := L.CheckString(1) + loaded := L.GetField(L.Get(RegistryIndex), "_LOADED") + lv := L.GetField(loaded, name) + if LVAsBool(lv) { + if lv == loopdetection { + L.RaiseError("loop or previous error loading module: %s", name) + } + L.Push(lv) + return 1 + } + loaders, ok := L.GetField(L.Get(RegistryIndex), "_LOADERS").(*LTable) + if !ok { + L.RaiseError("package.loaders must be a table") + } + messages := []string{} + var modasfunc LValue + for i := 1; ; i++ { + loader := L.RawGetInt(loaders, i) + if loader == LNil { + L.RaiseError("module %s not found:\n\t%s, ", name, strings.Join(messages, "\n\t")) + } + L.Push(loader) + L.Push(LString(name)) + L.Call(1, 1) + ret := L.reg.Pop() + switch retv := ret.(type) { + case *LFunction: + modasfunc = retv + goto loopbreak + case LString: + messages = append(messages, string(retv)) + } + } +loopbreak: + L.SetField(loaded, name, loopdetection) + L.Push(modasfunc) + L.Push(LString(name)) + L.Call(1, 1) + ret := L.reg.Pop() + modv := L.GetField(loaded, name) + if ret != LNil && modv == loopdetection { + L.SetField(loaded, name, ret) + L.Push(ret) + } else if modv == loopdetection { + L.SetField(loaded, name, LTrue) + L.Push(LTrue) + } else { + L.Push(modv) + } + return 1 +} + +/* }}} */ + +/* hidden features {{{ */ + +func baseNewProxy(L *LState) int { + ud := L.NewUserData() + L.SetTop(1) + if L.Get(1) == LTrue { + L.SetMetatable(ud, L.NewTable()) + } else if d, ok := L.Get(1).(*LUserData); ok { + L.SetMetatable(ud, L.GetMetatable(d)) + } + L.Push(ud) + return 1 +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/channellib.go b/vendor/github.com/yuin/gopher-lua/channellib.go new file mode 100644 index 0000000..6df3e88 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/channellib.go @@ -0,0 +1,152 @@ +package lua + +import ( + "reflect" +) + +func checkChannel(L *LState, idx int) reflect.Value { + ch := L.CheckChannel(idx) + return reflect.ValueOf(ch) +} + +func checkGoroutineSafe(L *LState, idx int) LValue { + v := L.CheckAny(2) + if !isGoroutineSafe(v) { + L.ArgError(2, "can not send a function, userdata, thread or table that has a metatable") + } + return v +} + +func OpenChannel(L *LState) int { + var mod LValue + //_, ok := L.G.builtinMts[int(LTChannel)] + // if !ok { + mod = L.RegisterModule(ChannelLibName, channelFuncs) + mt := L.SetFuncs(L.NewTable(), channelMethods) + mt.RawSetString("__index", mt) + L.G.builtinMts[int(LTChannel)] = mt + // } + L.Push(mod) + return 1 +} + +var channelFuncs = map[string]LGFunction{ + "make": channelMake, + "select": channelSelect, +} + +func channelMake(L *LState) int { + buffer := L.OptInt(1, 0) + L.Push(LChannel(make(chan LValue, buffer))) + return 1 +} + +func channelSelect(L *LState) int { + //TODO check case table size + cases := make([]reflect.SelectCase, L.GetTop()) + top := L.GetTop() + for i := 0; i < top; i++ { + cas := reflect.SelectCase{reflect.SelectSend, reflect.ValueOf(nil), reflect.ValueOf(nil)} + tbl := L.CheckTable(i + 1) + dir, ok1 := tbl.RawGetInt(1).(LString) + if !ok1 { + L.ArgError(i+1, "invalid select case") + } + switch string(dir) { + case "<-|": + ch, ok := tbl.RawGetInt(2).(LChannel) + if !ok { + L.ArgError(i+1, "invalid select case") + } + cas.Chan = reflect.ValueOf((chan LValue)(ch)) + v := tbl.RawGetInt(3) + if !isGoroutineSafe(v) { + L.ArgError(i+1, "can not send a function, userdata, thread or table that has a metatable") + } + cas.Send = reflect.ValueOf(v) + case "|<-": + ch, ok := tbl.RawGetInt(2).(LChannel) + if !ok { + L.ArgError(i+1, "invalid select case") + } + cas.Chan = reflect.ValueOf((chan LValue)(ch)) + cas.Dir = reflect.SelectRecv + case "default": + cas.Dir = reflect.SelectDefault + default: + L.ArgError(i+1, "invalid channel direction:"+string(dir)) + } + cases[i] = cas + } + + pos, recv, rok := reflect.Select(cases) + lv := LNil + if recv.Kind() != 0 { + lv, _ = recv.Interface().(LValue) + if lv == nil { + lv = LNil + } + } + tbl := L.Get(pos + 1).(*LTable) + last := tbl.RawGetInt(tbl.Len()) + if last.Type() == LTFunction { + L.Push(last) + switch cases[pos].Dir { + case reflect.SelectRecv: + if rok { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + L.Push(lv) + L.Call(2, 0) + case reflect.SelectSend: + L.Push(tbl.RawGetInt(3)) + L.Call(1, 0) + case reflect.SelectDefault: + L.Call(0, 0) + } + } + L.Push(LNumber(pos + 1)) + L.Push(lv) + if rok { + L.Push(LTrue) + } else { + L.Push(LFalse) + } + return 3 +} + +var channelMethods = map[string]LGFunction{ + "receive": channelReceive, + "send": channelSend, + "close": channelClose, +} + +func channelReceive(L *LState) int { + rch := checkChannel(L, 1) + v, ok := rch.Recv() + if ok { + L.Push(LTrue) + L.Push(v.Interface().(LValue)) + } else { + L.Push(LFalse) + L.Push(LNil) + } + return 2 +} + +func channelSend(L *LState) int { + rch := checkChannel(L, 1) + v := checkGoroutineSafe(L, 2) + rch.Send(reflect.ValueOf(v)) + return 0 +} + +func channelClose(L *LState) int { + rch := checkChannel(L, 1) + rch.Close() + return 0 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/channellib_test.go b/vendor/github.com/yuin/gopher-lua/channellib_test.go new file mode 100644 index 0000000..a7fbffc --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/channellib_test.go @@ -0,0 +1,261 @@ +package lua + +import ( + "reflect" + "sync" + "testing" + "time" +) + +func TestChannelMake(t *testing.T) { + L := NewState() + defer L.Close() + errorIfScriptFail(t, L, ` + ch = channel.make() + `) + obj := L.GetGlobal("ch") + ch, ok := obj.(LChannel) + errorIfFalse(t, ok, "channel expected") + errorIfNotEqual(t, 0, reflect.ValueOf(ch).Cap()) + close(ch) + + errorIfScriptFail(t, L, ` + ch = channel.make(10) + `) + obj = L.GetGlobal("ch") + ch, _ = obj.(LChannel) + errorIfNotEqual(t, 10, reflect.ValueOf(ch).Cap()) + close(ch) +} + +func TestChannelSelectError(t *testing.T) { + L := NewState() + defer L.Close() + errorIfScriptFail(t, L, `ch = channel.make()`) + errorIfScriptNotFail(t, L, `channel.select({1,2,3})`, "invalid select case") + errorIfScriptNotFail(t, L, `channel.select({"<-|", 1, 3})`, "invalid select case") + errorIfScriptNotFail(t, L, `channel.select({"<-|", ch, function() end})`, "can not send a function") + errorIfScriptNotFail(t, L, `channel.select({"|<-", 1, 3})`, "invalid select case") + errorIfScriptNotFail(t, L, `channel.select({"<-->", 1, 3})`, "invalid channel direction") + errorIfScriptFail(t, L, `ch:close()`) +} + +func TestChannelSelect1(t *testing.T) { + var result LValue + var wg sync.WaitGroup + receiver := func(ch, quit chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + L.SetGlobal("quit", LChannel(quit)) + if err := L.DoString(` + buf = "" + local exit = false + while not exit do + channel.select( + {"|<-", ch, function(ok, v) + if not ok then + buf = buf .. "channel closed" + exit = true + else + buf = buf .. "received:" .. v + end + end}, + {"|<-", quit, function(ok, v) + buf = buf .. "quit" + end} + ) + end + `); err != nil { + panic(err) + } + result = L.GetGlobal("buf") + } + + sender := func(ch, quit chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + L.SetGlobal("quit", LChannel(quit)) + if err := L.DoString(` + ch:send("1") + ch:send("2") + `); err != nil { + panic(err) + } + ch <- LString("3") + quit <- LTrue + time.Sleep(1 * time.Second) + close(ch) + } + + ch := make(chan LValue) + quit := make(chan LValue) + wg.Add(2) + go receiver(ch, quit) + go sender(ch, quit) + wg.Wait() + lstr, ok := result.(LString) + errorIfFalse(t, ok, "must be string") + str := string(lstr) + errorIfNotEqual(t, "received:1received:2received:3quitchannel closed", str) + +} + +func TestChannelSelect2(t *testing.T) { + var wg sync.WaitGroup + receiver := func(ch, quit chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + L.SetGlobal("quit", LChannel(quit)) + errorIfScriptFail(t, L, ` + idx, rcv, ok = channel.select( + {"|<-", ch}, + {"|<-", quit} + ) + assert(idx == 1) + assert(rcv == "1") + assert(ok) + idx, rcv, ok = channel.select( + {"|<-", ch}, + {"|<-", quit} + ) + assert(idx == 1) + assert(rcv == nil) + assert(not ok) + `) + } + + sender := func(ch, quit chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + L.SetGlobal("quit", LChannel(quit)) + errorIfScriptFail(t, L, `ch:send("1")`) + errorIfScriptFail(t, L, `ch:close()`) + } + + ch := make(chan LValue) + quit := make(chan LValue) + wg.Add(2) + go receiver(ch, quit) + go sender(ch, quit) + wg.Wait() +} + +func TestChannelSelect3(t *testing.T) { + var wg sync.WaitGroup + receiver := func(ch chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + errorIfScriptFail(t, L, ` + ok = true + while ok do + idx, rcv, ok = channel.select( + {"|<-", ch} + ) + end + `) + } + + sender := func(ch chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + errorIfScriptFail(t, L, ` + ok = false + channel.select( + {"<-|", ch, "1", function(v) + ok = true + end} + ) + assert(ok) + idx, rcv, ok = channel.select( + {"<-|", ch, "1"} + ) + assert(idx == 1) + ch:close() + `) + } + + ch := make(chan LValue) + wg.Add(2) + go receiver(ch) + time.Sleep(1) + go sender(ch) + wg.Wait() +} + +func TestChannelSelect4(t *testing.T) { + var wg sync.WaitGroup + receiver := func(ch chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + errorIfScriptFail(t, L, ` + idx, rcv, ok = channel.select( + {"|<-", ch}, + {"default"} + ) + assert(idx == 2) + called = false + idx, rcv, ok = channel.select( + {"|<-", ch}, + {"default", function() + called = true + end} + ) + assert(called) + ch:close() + `) + } + + ch := make(chan LValue) + wg.Add(1) + go receiver(ch) + wg.Wait() +} + +func TestChannelSendReceive1(t *testing.T) { + var wg sync.WaitGroup + receiver := func(ch chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + errorIfScriptFail(t, L, ` + local ok, v = ch:receive() + assert(ok) + assert(v == "1") + `) + time.Sleep(1 * time.Second) + errorIfScriptFail(t, L, ` + local ok, v = ch:receive() + assert(not ok) + assert(v == nil) + `) + } + sender := func(ch chan LValue) { + defer wg.Done() + L := NewState() + defer L.Close() + L.SetGlobal("ch", LChannel(ch)) + errorIfScriptFail(t, L, `ch:send("1")`) + errorIfScriptNotFail(t, L, `ch:send(function() end)`, "can not send a function") + errorIfScriptFail(t, L, `ch:close()`) + } + ch := make(chan LValue) + wg.Add(2) + go receiver(ch) + go sender(ch) + wg.Wait() +} diff --git a/vendor/github.com/yuin/gopher-lua/compile.go b/vendor/github.com/yuin/gopher-lua/compile.go new file mode 100644 index 0000000..dd51c0d --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/compile.go @@ -0,0 +1,1656 @@ +package lua + +import ( + "fmt" + "github.com/yuin/gopher-lua/ast" + "math" + "reflect" +) + +/* internal constants & structs {{{ */ + +const maxRegisters = 200 + +type expContextType int + +const ( + ecGlobal expContextType = iota + ecUpvalue + ecLocal + ecTable + ecVararg + ecMethod + ecNone +) + +const regNotDefined = opMaxArgsA + 1 +const labelNoJump = 0 + +type expcontext struct { + ctype expContextType + reg int + // varargopt >= 0: wants varargopt+1 results, i.e a = func() + // varargopt = -1: ignore results i.e func() + // varargopt = -2: receive all results i.e a = {func()} + varargopt int +} + +type assigncontext struct { + ec *expcontext + keyrk int + valuerk int + keyks bool + needmove bool +} + +type lblabels struct { + t int + f int + e int + b bool +} + +type constLValueExpr struct { + ast.ExprBase + + Value LValue +} + +// }}} + +/* utilities {{{ */ +var _ecnone0 = &expcontext{ecNone, regNotDefined, 0} +var _ecnonem1 = &expcontext{ecNone, regNotDefined, -1} +var _ecnonem2 = &expcontext{ecNone, regNotDefined, -2} +var ecfuncdef = &expcontext{ecMethod, regNotDefined, 0} + +func ecupdate(ec *expcontext, ctype expContextType, reg, varargopt int) { + ec.ctype = ctype + ec.reg = reg + ec.varargopt = varargopt +} + +func ecnone(varargopt int) *expcontext { + switch varargopt { + case 0: + return _ecnone0 + case -1: + return _ecnonem1 + case -2: + return _ecnonem2 + } + return &expcontext{ecNone, regNotDefined, varargopt} +} + +func sline(pos ast.PositionHolder) int { + return pos.Line() +} + +func eline(pos ast.PositionHolder) int { + return pos.LastLine() +} + +func savereg(ec *expcontext, reg int) int { + if ec.ctype != ecLocal || ec.reg == regNotDefined { + return reg + } + return ec.reg +} + +func raiseCompileError(context *funcContext, line int, format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + panic(&CompileError{context: context, Line: line, Message: msg}) +} + +func isVarArgReturnExpr(expr ast.Expr) bool { + switch ex := expr.(type) { + case *ast.FuncCallExpr: + return !ex.AdjustRet + case *ast.Comma3Expr: + return true + } + return false +} + +func lnumberValue(expr ast.Expr) (LNumber, bool) { + if ex, ok := expr.(*ast.NumberExpr); ok { + lv, err := parseNumber(ex.Value) + if err != nil { + lv = LNumber(math.NaN()) + } + return lv, true + } else if ex, ok := expr.(*constLValueExpr); ok { + return ex.Value.(LNumber), true + } + return 0, false +} + +/* utilities }}} */ + +type CompileError struct { // {{{ + context *funcContext + Line int + Message string +} + +func (e *CompileError) Error() string { + return fmt.Sprintf("compile error near line(%v) %v: %v", e.Line, e.context.Proto.SourceName, e.Message) +} // }}} + +type codeStore struct { // {{{ + codes []uint32 + lines []int + pc int +} + +func (cd *codeStore) Add(inst uint32, line int) { + if l := len(cd.codes); l <= 0 || cd.pc == l { + cd.codes = append(cd.codes, inst) + cd.lines = append(cd.lines, line) + } else { + cd.codes[cd.pc] = inst + cd.lines[cd.pc] = line + } + cd.pc++ +} + +func (cd *codeStore) AddABC(op int, a int, b int, c int, line int) { + cd.Add(opCreateABC(op, a, b, c), line) +} + +func (cd *codeStore) AddABx(op int, a int, bx int, line int) { + cd.Add(opCreateABx(op, a, bx), line) +} + +func (cd *codeStore) AddASbx(op int, a int, sbx int, line int) { + cd.Add(opCreateASbx(op, a, sbx), line) +} + +func (cd *codeStore) PropagateKMV(top int, save *int, reg *int, inc int) { + lastinst := cd.Last() + if opGetArgA(lastinst) >= top { + switch opGetOpCode(lastinst) { + case OP_LOADK: + cindex := opGetArgBx(lastinst) + if cindex <= opMaxIndexRk { + cd.Pop() + *save = opRkAsk(cindex) + return + } + case OP_MOVE: + cd.Pop() + *save = opGetArgB(lastinst) + return + } + } + *save = *reg + *reg = *reg + inc +} + +func (cd *codeStore) PropagateMV(top int, save *int, reg *int, inc int) { + lastinst := cd.Last() + if opGetArgA(lastinst) >= top { + switch opGetOpCode(lastinst) { + case OP_MOVE: + cd.Pop() + *save = opGetArgB(lastinst) + return + } + } + *save = *reg + *reg = *reg + inc +} + +func (cd *codeStore) SetOpCode(pc int, v int) { + opSetOpCode(&cd.codes[pc], v) +} + +func (cd *codeStore) SetA(pc int, v int) { + opSetArgA(&cd.codes[pc], v) +} + +func (cd *codeStore) SetB(pc int, v int) { + opSetArgB(&cd.codes[pc], v) +} + +func (cd *codeStore) SetC(pc int, v int) { + opSetArgC(&cd.codes[pc], v) +} + +func (cd *codeStore) SetBx(pc int, v int) { + opSetArgBx(&cd.codes[pc], v) +} + +func (cd *codeStore) SetSbx(pc int, v int) { + opSetArgSbx(&cd.codes[pc], v) +} + +func (cd *codeStore) At(pc int) uint32 { + return cd.codes[pc] +} + +func (cd *codeStore) List() []uint32 { + return cd.codes[:cd.pc] +} + +func (cd *codeStore) PosList() []int { + return cd.lines[:cd.pc] +} + +func (cd *codeStore) LastPC() int { + return cd.pc - 1 +} + +func (cd *codeStore) Last() uint32 { + if cd.pc == 0 { + return opInvalidInstruction + } + return cd.codes[cd.pc-1] +} + +func (cd *codeStore) Pop() { + cd.pc-- +} /* }}} Code */ + +/* {{{ VarNamePool */ + +type varNamePoolValue struct { + Index int + Name string +} + +type varNamePool struct { + names []string + offset int +} + +func newVarNamePool(offset int) *varNamePool { + return &varNamePool{make([]string, 0, 16), offset} +} + +func (vp *varNamePool) Names() []string { + return vp.names +} + +func (vp *varNamePool) List() []varNamePoolValue { + result := make([]varNamePoolValue, len(vp.names), len(vp.names)) + for i, name := range vp.names { + result[i].Index = i + vp.offset + result[i].Name = name + } + return result +} + +func (vp *varNamePool) LastIndex() int { + return vp.offset + len(vp.names) +} + +func (vp *varNamePool) Find(name string) int { + for i := len(vp.names) - 1; i >= 0; i-- { + if vp.names[i] == name { + return i + vp.offset + } + } + return -1 +} + +func (vp *varNamePool) RegisterUnique(name string) int { + index := vp.Find(name) + if index < 0 { + return vp.Register(name) + } + return index +} + +func (vp *varNamePool) Register(name string) int { + vp.names = append(vp.names, name) + return len(vp.names) - 1 + vp.offset +} + +/* }}} VarNamePool */ + +/* FuncContext {{{ */ + +type codeBlock struct { + LocalVars *varNamePool + BreakLabel int + Parent *codeBlock + RefUpvalue bool + LineStart int + LastLine int +} + +func newCodeBlock(localvars *varNamePool, blabel int, parent *codeBlock, pos ast.PositionHolder) *codeBlock { + bl := &codeBlock{localvars, blabel, parent, false, 0, 0} + if pos != nil { + bl.LineStart = pos.Line() + bl.LastLine = pos.LastLine() + } + return bl +} + +type funcContext struct { + Proto *FunctionProto + Code *codeStore + Parent *funcContext + Upvalues *varNamePool + Block *codeBlock + Blocks []*codeBlock + regTop int + labelId int + labelPc map[int]int +} + +func newFuncContext(sourcename string, parent *funcContext) *funcContext { + fc := &funcContext{ + Proto: newFunctionProto(sourcename), + Code: &codeStore{make([]uint32, 0, 1024), make([]int, 0, 1024), 0}, + Parent: parent, + Upvalues: newVarNamePool(0), + Block: newCodeBlock(newVarNamePool(0), labelNoJump, nil, nil), + regTop: 0, + labelId: 1, + labelPc: map[int]int{}, + } + fc.Blocks = []*codeBlock{fc.Block} + return fc +} + +func (fc *funcContext) NewLabel() int { + ret := fc.labelId + fc.labelId++ + return ret +} + +func (fc *funcContext) SetLabelPc(label int, pc int) { + fc.labelPc[label] = pc +} + +func (fc *funcContext) GetLabelPc(label int) int { + return fc.labelPc[label] +} + +func (fc *funcContext) ConstIndex(value LValue) int { + ctype := value.Type() + for i, lv := range fc.Proto.Constants { + if lv.Type() == ctype && lv == value { + return i + } + } + fc.Proto.Constants = append(fc.Proto.Constants, value) + v := len(fc.Proto.Constants) - 1 + if v > opMaxArgBx { + raiseCompileError(fc, fc.Proto.LineDefined, "too many constants") + } + return v +} + +func (fc *funcContext) RegisterLocalVar(name string) int { + ret := fc.Block.LocalVars.Register(name) + fc.Proto.DbgLocals = append(fc.Proto.DbgLocals, &DbgLocalInfo{Name: name, StartPc: fc.Code.LastPC() + 1}) + fc.SetRegTop(fc.RegTop() + 1) + return ret +} + +func (fc *funcContext) FindLocalVarAndBlock(name string) (int, *codeBlock) { + for block := fc.Block; block != nil; block = block.Parent { + if index := block.LocalVars.Find(name); index > -1 { + return index, block + } + } + return -1, nil +} + +func (fc *funcContext) FindLocalVar(name string) int { + idx, _ := fc.FindLocalVarAndBlock(name) + return idx +} + +func (fc *funcContext) LocalVars() []varNamePoolValue { + result := make([]varNamePoolValue, 0, 32) + for _, block := range fc.Blocks { + result = append(result, block.LocalVars.List()...) + } + return result +} + +func (fc *funcContext) EnterBlock(blabel int, pos ast.PositionHolder) { + fc.Block = newCodeBlock(newVarNamePool(fc.RegTop()), blabel, fc.Block, pos) + fc.Blocks = append(fc.Blocks, fc.Block) +} + +func (fc *funcContext) CloseUpvalues() int { + n := -1 + if fc.Block.RefUpvalue { + n = fc.Block.Parent.LocalVars.LastIndex() + fc.Code.AddABC(OP_CLOSE, n, 0, 0, fc.Block.LastLine) + } + return n +} + +func (fc *funcContext) LeaveBlock() int { + closed := fc.CloseUpvalues() + fc.EndScope() + fc.Block = fc.Block.Parent + fc.SetRegTop(fc.Block.LocalVars.LastIndex()) + return closed +} + +func (fc *funcContext) EndScope() { + for _, vr := range fc.Block.LocalVars.List() { + fc.Proto.DbgLocals[vr.Index].EndPc = fc.Code.LastPC() + } +} + +func (fc *funcContext) SetRegTop(top int) { + if top > maxRegisters { + raiseCompileError(fc, fc.Proto.LineDefined, "too many local variables") + } + fc.regTop = top +} + +func (fc *funcContext) RegTop() int { + return fc.regTop +} + +/* FuncContext }}} */ + +func compileChunk(context *funcContext, chunk []ast.Stmt) { // {{{ + for _, stmt := range chunk { + compileStmt(context, stmt) + } +} // }}} + +func compileBlock(context *funcContext, chunk []ast.Stmt) { // {{{ + if len(chunk) == 0 { + return + } + ph := &ast.Node{} + ph.SetLine(sline(chunk[0])) + ph.SetLastLine(eline(chunk[len(chunk)-1])) + context.EnterBlock(labelNoJump, ph) + for _, stmt := range chunk { + compileStmt(context, stmt) + } + context.LeaveBlock() +} // }}} + +func compileStmt(context *funcContext, stmt ast.Stmt) { // {{{ + switch st := stmt.(type) { + case *ast.AssignStmt: + compileAssignStmt(context, st) + case *ast.LocalAssignStmt: + compileLocalAssignStmt(context, st) + case *ast.FuncCallStmt: + compileFuncCallExpr(context, context.RegTop(), st.Expr.(*ast.FuncCallExpr), ecnone(-1)) + case *ast.DoBlockStmt: + context.EnterBlock(labelNoJump, st) + compileChunk(context, st.Stmts) + context.LeaveBlock() + case *ast.WhileStmt: + compileWhileStmt(context, st) + case *ast.RepeatStmt: + compileRepeatStmt(context, st) + case *ast.FuncDefStmt: + compileFuncDefStmt(context, st) + case *ast.ReturnStmt: + compileReturnStmt(context, st) + case *ast.IfStmt: + compileIfStmt(context, st) + case *ast.BreakStmt: + compileBreakStmt(context, st) + case *ast.NumberForStmt: + compileNumberForStmt(context, st) + case *ast.GenericForStmt: + compileGenericForStmt(context, st) + } +} // }}} + +func compileAssignStmtLeft(context *funcContext, stmt *ast.AssignStmt) (int, []*assigncontext) { // {{{ + reg := context.RegTop() + acs := make([]*assigncontext, 0, len(stmt.Lhs)) + for i, lhs := range stmt.Lhs { + islast := i == len(stmt.Lhs)-1 + switch st := lhs.(type) { + case *ast.IdentExpr: + identtype := getIdentRefType(context, context, st) + ec := &expcontext{identtype, regNotDefined, 0} + switch identtype { + case ecGlobal: + context.ConstIndex(LString(st.Value)) + case ecUpvalue: + context.Upvalues.RegisterUnique(st.Value) + case ecLocal: + if islast { + ec.reg = context.FindLocalVar(st.Value) + } + } + acs = append(acs, &assigncontext{ec, 0, 0, false, false}) + case *ast.AttrGetExpr: + ac := &assigncontext{&expcontext{ecTable, regNotDefined, 0}, 0, 0, false, false} + compileExprWithKMVPropagation(context, st.Object, ®, &ac.ec.reg) + ac.keyrk = reg + reg += compileExpr(context, reg, st.Key, ecnone(0)) + if _, ok := st.Key.(*ast.StringExpr); ok { + ac.keyks = true + } + acs = append(acs, ac) + + default: + panic("invalid left expression.") + } + } + return reg, acs +} // }}} + +func compileAssignStmtRight(context *funcContext, stmt *ast.AssignStmt, reg int, acs []*assigncontext) (int, []*assigncontext) { // {{{ + lennames := len(stmt.Lhs) + lenexprs := len(stmt.Rhs) + namesassigned := 0 + + for namesassigned < lennames { + ac := acs[namesassigned] + ec := ac.ec + var expr ast.Expr = nil + if namesassigned >= lenexprs { + expr = &ast.NilExpr{} + expr.SetLine(sline(stmt.Lhs[namesassigned])) + expr.SetLastLine(eline(stmt.Lhs[namesassigned])) + } else if isVarArgReturnExpr(stmt.Rhs[namesassigned]) && (lenexprs-namesassigned-1) <= 0 { + varargopt := lennames - namesassigned - 1 + regstart := reg + reginc := compileExpr(context, reg, stmt.Rhs[namesassigned], ecnone(varargopt)) + reg += reginc + for i := namesassigned; i < namesassigned+int(reginc); i++ { + acs[i].needmove = true + if acs[i].ec.ctype == ecTable { + acs[i].valuerk = regstart + (i - namesassigned) + } + } + namesassigned = lennames + continue + } + + if expr == nil { + expr = stmt.Rhs[namesassigned] + } + idx := reg + reginc := compileExpr(context, reg, expr, ec) + if ec.ctype == ecTable { + if _, ok := expr.(*ast.LogicalOpExpr); !ok { + context.Code.PropagateKMV(context.RegTop(), &ac.valuerk, ®, reginc) + } else { + ac.valuerk = idx + reg += reginc + } + } else { + ac.needmove = reginc != 0 + reg += reginc + } + namesassigned += 1 + } + + rightreg := reg - 1 + + // extra right exprs + for i := namesassigned; i < lenexprs; i++ { + varargopt := -1 + if i != lenexprs-1 { + varargopt = 0 + } + reg += compileExpr(context, reg, stmt.Rhs[i], ecnone(varargopt)) + } + return rightreg, acs +} // }}} + +func compileAssignStmt(context *funcContext, stmt *ast.AssignStmt) { // {{{ + code := context.Code + lennames := len(stmt.Lhs) + reg, acs := compileAssignStmtLeft(context, stmt) + reg, acs = compileAssignStmtRight(context, stmt, reg, acs) + + for i := lennames - 1; i >= 0; i-- { + ex := stmt.Lhs[i] + switch acs[i].ec.ctype { + case ecLocal: + if acs[i].needmove { + code.AddABC(OP_MOVE, context.FindLocalVar(ex.(*ast.IdentExpr).Value), reg, 0, sline(ex)) + reg -= 1 + } + case ecGlobal: + code.AddABx(OP_SETGLOBAL, reg, context.ConstIndex(LString(ex.(*ast.IdentExpr).Value)), sline(ex)) + reg -= 1 + case ecUpvalue: + code.AddABC(OP_SETUPVAL, reg, context.Upvalues.RegisterUnique(ex.(*ast.IdentExpr).Value), 0, sline(ex)) + reg -= 1 + case ecTable: + opcode := OP_SETTABLE + if acs[i].keyks { + opcode = OP_SETTABLEKS + } + code.AddABC(opcode, acs[i].ec.reg, acs[i].keyrk, acs[i].valuerk, sline(ex)) + if !opIsK(acs[i].valuerk) { + reg -= 1 + } + } + } +} // }}} + +func compileRegAssignment(context *funcContext, names []string, exprs []ast.Expr, reg int, nvars int, line int) { // {{{ + lennames := len(names) + lenexprs := len(exprs) + namesassigned := 0 + ec := &expcontext{} + + for namesassigned < lennames && namesassigned < lenexprs { + if isVarArgReturnExpr(exprs[namesassigned]) && (lenexprs-namesassigned-1) <= 0 { + + varargopt := nvars - namesassigned + ecupdate(ec, ecVararg, reg, varargopt-1) + compileExpr(context, reg, exprs[namesassigned], ec) + reg += varargopt + namesassigned = lennames + } else { + ecupdate(ec, ecLocal, reg, 0) + compileExpr(context, reg, exprs[namesassigned], ec) + reg += 1 + namesassigned += 1 + } + } + + // extra left names + if lennames > namesassigned { + restleft := lennames - namesassigned - 1 + context.Code.AddABC(OP_LOADNIL, reg, reg+restleft, 0, line) + reg += restleft + } + + // extra right exprs + for i := namesassigned; i < lenexprs; i++ { + varargopt := -1 + if i != lenexprs-1 { + varargopt = 0 + } + ecupdate(ec, ecNone, reg, varargopt) + reg += compileExpr(context, reg, exprs[i], ec) + } +} // }}} + +func compileLocalAssignStmt(context *funcContext, stmt *ast.LocalAssignStmt) { // {{{ + reg := context.RegTop() + if len(stmt.Names) == 1 && len(stmt.Exprs) == 1 { + if _, ok := stmt.Exprs[0].(*ast.FunctionExpr); ok { + context.RegisterLocalVar(stmt.Names[0]) + compileRegAssignment(context, stmt.Names, stmt.Exprs, reg, len(stmt.Names), sline(stmt)) + return + } + } + + compileRegAssignment(context, stmt.Names, stmt.Exprs, reg, len(stmt.Names), sline(stmt)) + for _, name := range stmt.Names { + context.RegisterLocalVar(name) + } +} // }}} + +func compileReturnStmt(context *funcContext, stmt *ast.ReturnStmt) { // {{{ + lenexprs := len(stmt.Exprs) + code := context.Code + reg := context.RegTop() + a := reg + lastisvaarg := false + + if lenexprs == 1 { + switch ex := stmt.Exprs[0].(type) { + case *ast.IdentExpr: + if idx := context.FindLocalVar(ex.Value); idx > -1 { + code.AddABC(OP_RETURN, idx, 2, 0, sline(stmt)) + return + } + case *ast.FuncCallExpr: + reg += compileExpr(context, reg, ex, ecnone(-2)) + code.SetOpCode(code.LastPC(), OP_TAILCALL) + code.AddABC(OP_RETURN, a, 0, 0, sline(stmt)) + return + } + } + + for i, expr := range stmt.Exprs { + if i == lenexprs-1 && isVarArgReturnExpr(expr) { + compileExpr(context, reg, expr, ecnone(-2)) + lastisvaarg = true + } else { + reg += compileExpr(context, reg, expr, ecnone(0)) + } + } + count := reg - a + 1 + if lastisvaarg { + count = 0 + } + context.Code.AddABC(OP_RETURN, a, count, 0, sline(stmt)) +} // }}} + +func compileIfStmt(context *funcContext, stmt *ast.IfStmt) { // {{{ + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + endlabel := context.NewLabel() + + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + context.SetLabelPc(thenlabel, context.Code.LastPC()) + compileBlock(context, stmt.Then) + if len(stmt.Else) > 0 { + context.Code.AddASbx(OP_JMP, 0, endlabel, sline(stmt)) + } + context.SetLabelPc(elselabel, context.Code.LastPC()) + if len(stmt.Else) > 0 { + compileBlock(context, stmt.Else) + context.SetLabelPc(endlabel, context.Code.LastPC()) + } + +} // }}} + +func compileBranchCondition(context *funcContext, reg int, expr ast.Expr, thenlabel, elselabel int, hasnextcond bool) { // {{{ + // TODO folding constants? + code := context.Code + flip := 0 + jumplabel := elselabel + if hasnextcond { + flip = 1 + jumplabel = thenlabel + } + + switch ex := expr.(type) { + case *ast.FalseExpr, *ast.NilExpr: + if !hasnextcond { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + return + } + case *ast.TrueExpr, *ast.NumberExpr, *ast.StringExpr: + if !hasnextcond { + return + } + case *ast.UnaryNotOpExpr: + compileBranchCondition(context, reg, ex.Expr, elselabel, thenlabel, !hasnextcond) + return + case *ast.LogicalOpExpr: + switch ex.Operator { + case "and": + nextcondlabel := context.NewLabel() + compileBranchCondition(context, reg, ex.Lhs, nextcondlabel, elselabel, false) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileBranchCondition(context, reg, ex.Rhs, thenlabel, elselabel, hasnextcond) + case "or": + nextcondlabel := context.NewLabel() + compileBranchCondition(context, reg, ex.Lhs, thenlabel, nextcondlabel, true) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileBranchCondition(context, reg, ex.Rhs, thenlabel, elselabel, hasnextcond) + } + return + case *ast.RelationalOpExpr: + compileRelationalOpExprAux(context, reg, ex, flip, jumplabel) + return + } + + a := reg + compileExprWithMVPropagation(context, expr, ®, &a) + code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) + code.AddASbx(OP_JMP, 0, jumplabel, sline(expr)) +} // }}} + +func compileWhileStmt(context *funcContext, stmt *ast.WhileStmt) { // {{{ + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + condlabel := context.NewLabel() + + context.SetLabelPc(condlabel, context.Code.LastPC()) + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + context.SetLabelPc(thenlabel, context.Code.LastPC()) + context.EnterBlock(elselabel, stmt) + compileChunk(context, stmt.Stmts) + context.CloseUpvalues() + context.Code.AddASbx(OP_JMP, 0, condlabel, eline(stmt)) + context.LeaveBlock() + context.SetLabelPc(elselabel, context.Code.LastPC()) +} // }}} + +func compileRepeatStmt(context *funcContext, stmt *ast.RepeatStmt) { // {{{ + initlabel := context.NewLabel() + thenlabel := context.NewLabel() + elselabel := context.NewLabel() + + context.SetLabelPc(initlabel, context.Code.LastPC()) + context.SetLabelPc(elselabel, context.Code.LastPC()) + context.EnterBlock(thenlabel, stmt) + compileChunk(context, stmt.Stmts) + compileBranchCondition(context, context.RegTop(), stmt.Condition, thenlabel, elselabel, false) + + context.SetLabelPc(thenlabel, context.Code.LastPC()) + n := context.LeaveBlock() + + if n > -1 { + label := context.NewLabel() + context.Code.AddASbx(OP_JMP, 0, label, eline(stmt)) + context.SetLabelPc(elselabel, context.Code.LastPC()) + context.Code.AddABC(OP_CLOSE, n, 0, 0, eline(stmt)) + context.Code.AddASbx(OP_JMP, 0, initlabel, eline(stmt)) + context.SetLabelPc(label, context.Code.LastPC()) + } + +} // }}} + +func compileBreakStmt(context *funcContext, stmt *ast.BreakStmt) { // {{{ + for block := context.Block; block != nil; block = block.Parent { + if label := block.BreakLabel; label != labelNoJump { + if block.RefUpvalue { + context.Code.AddABC(OP_CLOSE, block.Parent.LocalVars.LastIndex(), 0, 0, sline(stmt)) + } + context.Code.AddASbx(OP_JMP, 0, label, sline(stmt)) + return + } + } + raiseCompileError(context, sline(stmt), "no loop to break") +} // }}} + +func compileFuncDefStmt(context *funcContext, stmt *ast.FuncDefStmt) { // {{{ + if stmt.Name.Func == nil { + reg := context.RegTop() + var treg, kreg int + compileExprWithKMVPropagation(context, stmt.Name.Receiver, ®, &treg) + kreg = loadRk(context, ®, stmt.Func, LString(stmt.Name.Method)) + compileExpr(context, reg, stmt.Func, ecfuncdef) + context.Code.AddABC(OP_SETTABLE, treg, kreg, reg, sline(stmt.Name.Receiver)) + } else { + astmt := &ast.AssignStmt{Lhs: []ast.Expr{stmt.Name.Func}, Rhs: []ast.Expr{stmt.Func}} + astmt.SetLine(sline(stmt.Func)) + astmt.SetLastLine(eline(stmt.Func)) + compileAssignStmt(context, astmt) + } +} // }}} + +func compileNumberForStmt(context *funcContext, stmt *ast.NumberForStmt) { // {{{ + code := context.Code + endlabel := context.NewLabel() + ec := &expcontext{} + + context.EnterBlock(endlabel, stmt) + reg := context.RegTop() + rindex := context.RegisterLocalVar("(for index)") + ecupdate(ec, ecLocal, rindex, 0) + compileExpr(context, reg, stmt.Init, ec) + + reg = context.RegTop() + rlimit := context.RegisterLocalVar("(for limit)") + ecupdate(ec, ecLocal, rlimit, 0) + compileExpr(context, reg, stmt.Limit, ec) + + reg = context.RegTop() + rstep := context.RegisterLocalVar("(for step)") + if stmt.Step == nil { + stmt.Step = &ast.NumberExpr{Value: "1"} + stmt.Step.SetLine(sline(stmt.Init)) + } + ecupdate(ec, ecLocal, rstep, 0) + compileExpr(context, reg, stmt.Step, ec) + + code.AddASbx(OP_FORPREP, rindex, 0, sline(stmt)) + + context.RegisterLocalVar(stmt.Name) + + bodypc := code.LastPC() + compileChunk(context, stmt.Stmts) + + context.LeaveBlock() + + flpc := code.LastPC() + code.AddASbx(OP_FORLOOP, rindex, bodypc-(flpc+1), sline(stmt)) + + context.SetLabelPc(endlabel, code.LastPC()) + code.SetSbx(bodypc, flpc-bodypc) + +} // }}} + +func compileGenericForStmt(context *funcContext, stmt *ast.GenericForStmt) { // {{{ + code := context.Code + endlabel := context.NewLabel() + bodylabel := context.NewLabel() + fllabel := context.NewLabel() + nnames := len(stmt.Names) + + context.EnterBlock(endlabel, stmt) + rgen := context.RegisterLocalVar("(for generator)") + context.RegisterLocalVar("(for state)") + context.RegisterLocalVar("(for control)") + + compileRegAssignment(context, stmt.Names, stmt.Exprs, context.RegTop()-3, 3, sline(stmt)) + + code.AddASbx(OP_JMP, 0, fllabel, sline(stmt)) + + for _, name := range stmt.Names { + context.RegisterLocalVar(name) + } + + context.SetLabelPc(bodylabel, code.LastPC()) + compileChunk(context, stmt.Stmts) + + context.LeaveBlock() + + context.SetLabelPc(fllabel, code.LastPC()) + code.AddABC(OP_TFORLOOP, rgen, 0, nnames, sline(stmt)) + code.AddASbx(OP_JMP, 0, bodylabel, sline(stmt)) + + context.SetLabelPc(endlabel, code.LastPC()) +} // }}} + +func compileExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) int { // {{{ + code := context.Code + sreg := savereg(ec, reg) + sused := 1 + if sreg < reg { + sused = 0 + } + + switch ex := expr.(type) { + case *ast.StringExpr: + code.AddABx(OP_LOADK, sreg, context.ConstIndex(LString(ex.Value)), sline(ex)) + return sused + case *ast.NumberExpr: + num, err := parseNumber(ex.Value) + if err != nil { + num = LNumber(math.NaN()) + } + code.AddABx(OP_LOADK, sreg, context.ConstIndex(num), sline(ex)) + return sused + case *constLValueExpr: + code.AddABx(OP_LOADK, sreg, context.ConstIndex(ex.Value), sline(ex)) + return sused + case *ast.NilExpr: + code.AddABC(OP_LOADNIL, sreg, sreg, 0, sline(ex)) + return sused + case *ast.FalseExpr: + code.AddABC(OP_LOADBOOL, sreg, 0, 0, sline(ex)) + return sused + case *ast.TrueExpr: + code.AddABC(OP_LOADBOOL, sreg, 1, 0, sline(ex)) + return sused + case *ast.IdentExpr: + switch getIdentRefType(context, context, ex) { + case ecGlobal: + code.AddABx(OP_GETGLOBAL, sreg, context.ConstIndex(LString(ex.Value)), sline(ex)) + case ecUpvalue: + code.AddABC(OP_GETUPVAL, sreg, context.Upvalues.RegisterUnique(ex.Value), 0, sline(ex)) + case ecLocal: + b := context.FindLocalVar(ex.Value) + code.AddABC(OP_MOVE, sreg, b, 0, sline(ex)) + } + return sused + case *ast.Comma3Expr: + if context.Proto.IsVarArg == 0 { + raiseCompileError(context, sline(ex), "cannot use '...' outside a vararg function") + } + context.Proto.IsVarArg &= ^VarArgNeedsArg + code.AddABC(OP_VARARG, sreg, 2+ec.varargopt, 0, sline(ex)) + if context.RegTop() > (sreg+2+ec.varargopt) || ec.varargopt < -1 { + return 0 + } + return (sreg + 1 + ec.varargopt) - reg + case *ast.AttrGetExpr: + a := sreg + b := reg + compileExprWithMVPropagation(context, ex.Object, ®, &b) + c := reg + compileExprWithKMVPropagation(context, ex.Key, ®, &c) + opcode := OP_GETTABLE + if _, ok := ex.Key.(*ast.StringExpr); ok { + opcode = OP_GETTABLEKS + } + code.AddABC(opcode, a, b, c, sline(ex)) + return sused + case *ast.TableExpr: + compileTableExpr(context, reg, ex, ec) + return 1 + case *ast.ArithmeticOpExpr: + compileArithmeticOpExpr(context, reg, ex, ec) + return sused + case *ast.StringConcatOpExpr: + compileStringConcatOpExpr(context, reg, ex, ec) + return sused + case *ast.UnaryMinusOpExpr, *ast.UnaryNotOpExpr, *ast.UnaryLenOpExpr: + compileUnaryOpExpr(context, reg, ex, ec) + return sused + case *ast.RelationalOpExpr: + compileRelationalOpExpr(context, reg, ex, ec) + return sused + case *ast.LogicalOpExpr: + compileLogicalOpExpr(context, reg, ex, ec) + return sused + case *ast.FuncCallExpr: + return compileFuncCallExpr(context, reg, ex, ec) + case *ast.FunctionExpr: + childcontext := newFuncContext(context.Proto.SourceName, context) + compileFunctionExpr(childcontext, ex, ec) + protono := len(context.Proto.FunctionPrototypes) + context.Proto.FunctionPrototypes = append(context.Proto.FunctionPrototypes, childcontext.Proto) + code.AddABx(OP_CLOSURE, sreg, protono, sline(ex)) + for _, upvalue := range childcontext.Upvalues.List() { + localidx, block := context.FindLocalVarAndBlock(upvalue.Name) + if localidx > -1 { + code.AddABC(OP_MOVE, 0, localidx, 0, sline(ex)) + block.RefUpvalue = true + } else { + upvalueidx := context.Upvalues.Find(upvalue.Name) + if upvalueidx < 0 { + upvalueidx = context.Upvalues.RegisterUnique(upvalue.Name) + } + code.AddABC(OP_GETUPVAL, 0, upvalueidx, 0, sline(ex)) + } + } + return sused + default: + panic(fmt.Sprintf("expr %v not implemented.", reflect.TypeOf(ex).Elem().Name())) + } + + panic("should not reach here") + return sused +} // }}} + +func compileExprWithPropagation(context *funcContext, expr ast.Expr, reg *int, save *int, propergator func(int, *int, *int, int)) { // {{{ + reginc := compileExpr(context, *reg, expr, ecnone(0)) + if _, ok := expr.(*ast.LogicalOpExpr); ok { + *save = *reg + *reg = *reg + reginc + } else { + propergator(context.RegTop(), save, reg, reginc) + } +} // }}} + +func compileExprWithKMVPropagation(context *funcContext, expr ast.Expr, reg *int, save *int) { // {{{ + compileExprWithPropagation(context, expr, reg, save, context.Code.PropagateKMV) +} // }}} + +func compileExprWithMVPropagation(context *funcContext, expr ast.Expr, reg *int, save *int) { // {{{ + compileExprWithPropagation(context, expr, reg, save, context.Code.PropagateMV) +} // }}} + +func constFold(exp ast.Expr) ast.Expr { // {{{ + switch expr := exp.(type) { + case *ast.ArithmeticOpExpr: + lvalue, lisconst := lnumberValue(expr.Lhs) + rvalue, risconst := lnumberValue(expr.Rhs) + if lisconst && risconst { + switch expr.Operator { + case "+": + return &constLValueExpr{Value: lvalue + rvalue} + case "-": + return &constLValueExpr{Value: lvalue - rvalue} + case "*": + return &constLValueExpr{Value: lvalue * rvalue} + case "/": + return &constLValueExpr{Value: lvalue / rvalue} + case "%": + return &constLValueExpr{Value: luaModulo(lvalue, rvalue)} + case "^": + return &constLValueExpr{Value: LNumber(math.Pow(float64(lvalue), float64(rvalue)))} + default: + panic(fmt.Sprintf("unknown binop: %v", expr.Operator)) + } + } else { + retexpr := *expr + retexpr.Lhs = constFold(expr.Lhs) + retexpr.Rhs = constFold(expr.Rhs) + return &retexpr + } + case *ast.UnaryMinusOpExpr: + expr.Expr = constFold(expr.Expr) + if value, ok := lnumberValue(expr.Expr); ok { + return &constLValueExpr{Value: LNumber(-value)} + } + return expr + default: + + return exp + } + return exp +} // }}} + +func compileFunctionExpr(context *funcContext, funcexpr *ast.FunctionExpr, ec *expcontext) { // {{{ + context.Proto.LineDefined = sline(funcexpr) + context.Proto.LastLineDefined = eline(funcexpr) + if len(funcexpr.ParList.Names) > maxRegisters { + raiseCompileError(context, context.Proto.LineDefined, "register overflow") + } + context.Proto.NumParameters = uint8(len(funcexpr.ParList.Names)) + if ec.ctype == ecMethod { + context.Proto.NumParameters += 1 + context.RegisterLocalVar("self") + } + for _, name := range funcexpr.ParList.Names { + context.RegisterLocalVar(name) + } + if funcexpr.ParList.HasVargs { + if CompatVarArg { + context.Proto.IsVarArg = VarArgHasArg | VarArgNeedsArg + if context.Parent != nil { + context.RegisterLocalVar("arg") + } + } + context.Proto.IsVarArg |= VarArgIsVarArg + } + + compileChunk(context, funcexpr.Stmts) + + context.Code.AddABC(OP_RETURN, 0, 1, 0, eline(funcexpr)) + context.EndScope() + context.Proto.Code = context.Code.List() + context.Proto.DbgSourcePositions = context.Code.PosList() + context.Proto.DbgUpvalues = context.Upvalues.Names() + context.Proto.NumUpvalues = uint8(len(context.Proto.DbgUpvalues)) + for _, clv := range context.Proto.Constants { + sv := "" + if slv, ok := clv.(LString); ok { + sv = string(slv) + } + context.Proto.stringConstants = append(context.Proto.stringConstants, sv) + } + patchCode(context) +} // }}} + +func compileTableExpr(context *funcContext, reg int, ex *ast.TableExpr, ec *expcontext) { // {{{ + code := context.Code + /* + tablereg := savereg(ec, reg) + if tablereg == reg { + reg += 1 + } + */ + tablereg := reg + reg++ + code.AddABC(OP_NEWTABLE, tablereg, 0, 0, sline(ex)) + tablepc := code.LastPC() + regbase := reg + + arraycount := 0 + lastvararg := false + for i, field := range ex.Fields { + islast := i == len(ex.Fields)-1 + if field.Key == nil { + if islast && isVarArgReturnExpr(field.Value) { + reg += compileExpr(context, reg, field.Value, ecnone(-2)) + lastvararg = true + } else { + reg += compileExpr(context, reg, field.Value, ecnone(0)) + arraycount += 1 + } + } else { + regorg := reg + b := reg + compileExprWithKMVPropagation(context, field.Key, ®, &b) + c := reg + compileExprWithKMVPropagation(context, field.Value, ®, &c) + opcode := OP_SETTABLE + if _, ok := field.Key.(*ast.StringExpr); ok { + opcode = OP_SETTABLEKS + } + code.AddABC(opcode, tablereg, b, c, sline(ex)) + reg = regorg + } + flush := arraycount % FieldsPerFlush + if (arraycount != 0 && (flush == 0 || islast)) || lastvararg { + reg = regbase + num := flush + if num == 0 { + num = FieldsPerFlush + } + c := (arraycount-1)/FieldsPerFlush + 1 + b := num + if islast && isVarArgReturnExpr(field.Value) { + b = 0 + } + line := field.Value + if field.Key != nil { + line = field.Key + } + if c > 511 { + c = 0 + } + code.AddABC(OP_SETLIST, tablereg, b, c, sline(line)) + if c == 0 { + code.Add(uint32(c), sline(line)) + } + } + } + code.SetB(tablepc, int2Fb(arraycount)) + code.SetC(tablepc, int2Fb(len(ex.Fields)-arraycount)) + if ec.ctype == ecLocal && ec.reg != tablereg { + code.AddABC(OP_MOVE, ec.reg, tablereg, 0, sline(ex)) + } +} // }}} + +func compileArithmeticOpExpr(context *funcContext, reg int, expr *ast.ArithmeticOpExpr, ec *expcontext) { // {{{ + exp := constFold(expr) + if ex, ok := exp.(*constLValueExpr); ok { + exp.SetLine(sline(expr)) + compileExpr(context, reg, ex, ec) + return + } + expr, _ = exp.(*ast.ArithmeticOpExpr) + a := savereg(ec, reg) + b := reg + compileExprWithKMVPropagation(context, expr.Lhs, ®, &b) + c := reg + compileExprWithKMVPropagation(context, expr.Rhs, ®, &c) + + op := 0 + switch expr.Operator { + case "+": + op = OP_ADD + case "-": + op = OP_SUB + case "*": + op = OP_MUL + case "/": + op = OP_DIV + case "%": + op = OP_MOD + case "^": + op = OP_POW + } + context.Code.AddABC(op, a, b, c, sline(expr)) +} // }}} + +func compileStringConcatOpExpr(context *funcContext, reg int, expr *ast.StringConcatOpExpr, ec *expcontext) { // {{{ + code := context.Code + crange := 1 + for current := expr.Rhs; current != nil; { + if ex, ok := current.(*ast.StringConcatOpExpr); ok { + crange += 1 + current = ex.Rhs + } else { + current = nil + } + } + a := savereg(ec, reg) + basereg := reg + reg += compileExpr(context, reg, expr.Lhs, ecnone(0)) + reg += compileExpr(context, reg, expr.Rhs, ecnone(0)) + for pc := code.LastPC(); pc != 0 && opGetOpCode(code.At(pc)) == OP_CONCAT; pc-- { + code.Pop() + } + code.AddABC(OP_CONCAT, a, basereg, basereg+crange, sline(expr)) +} // }}} + +func compileUnaryOpExpr(context *funcContext, reg int, expr ast.Expr, ec *expcontext) { // {{{ + opcode := 0 + code := context.Code + var operandexpr ast.Expr + switch ex := expr.(type) { + case *ast.UnaryMinusOpExpr: + exp := constFold(ex) + if lvexpr, ok := exp.(*constLValueExpr); ok { + exp.SetLine(sline(expr)) + compileExpr(context, reg, lvexpr, ec) + return + } + ex, _ = exp.(*ast.UnaryMinusOpExpr) + operandexpr = ex.Expr + opcode = OP_UNM + case *ast.UnaryNotOpExpr: + switch ex.Expr.(type) { + case *ast.TrueExpr: + code.AddABC(OP_LOADBOOL, savereg(ec, reg), 0, 0, sline(expr)) + return + case *ast.FalseExpr, *ast.NilExpr: + code.AddABC(OP_LOADBOOL, savereg(ec, reg), 1, 0, sline(expr)) + return + default: + opcode = OP_NOT + operandexpr = ex.Expr + } + case *ast.UnaryLenOpExpr: + opcode = OP_LEN + operandexpr = ex.Expr + } + + a := savereg(ec, reg) + b := reg + compileExprWithMVPropagation(context, operandexpr, ®, &b) + code.AddABC(opcode, a, b, 0, sline(expr)) +} // }}} + +func compileRelationalOpExprAux(context *funcContext, reg int, expr *ast.RelationalOpExpr, flip int, label int) { // {{{ + code := context.Code + b := reg + compileExprWithKMVPropagation(context, expr.Lhs, ®, &b) + c := reg + compileExprWithKMVPropagation(context, expr.Rhs, ®, &c) + switch expr.Operator { + case "<": + code.AddABC(OP_LT, 0^flip, b, c, sline(expr)) + case ">": + code.AddABC(OP_LT, 0^flip, c, b, sline(expr)) + case "<=": + code.AddABC(OP_LE, 0^flip, b, c, sline(expr)) + case ">=": + code.AddABC(OP_LE, 0^flip, c, b, sline(expr)) + case "==": + code.AddABC(OP_EQ, 0^flip, b, c, sline(expr)) + case "~=": + code.AddABC(OP_EQ, 1^flip, b, c, sline(expr)) + } + code.AddASbx(OP_JMP, 0, label, sline(expr)) +} // }}} + +func compileRelationalOpExpr(context *funcContext, reg int, expr *ast.RelationalOpExpr, ec *expcontext) { // {{{ + a := savereg(ec, reg) + code := context.Code + jumplabel := context.NewLabel() + compileRelationalOpExprAux(context, reg, expr, 1, jumplabel) + code.AddABC(OP_LOADBOOL, a, 0, 1, sline(expr)) + context.SetLabelPc(jumplabel, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 1, 0, sline(expr)) +} // }}} + +func compileLogicalOpExpr(context *funcContext, reg int, expr *ast.LogicalOpExpr, ec *expcontext) { // {{{ + a := savereg(ec, reg) + code := context.Code + endlabel := context.NewLabel() + lb := &lblabels{context.NewLabel(), context.NewLabel(), endlabel, false} + nextcondlabel := context.NewLabel() + if expr.Operator == "and" { + compileLogicalOpExprAux(context, reg, expr.Lhs, ec, nextcondlabel, endlabel, false, lb) + context.SetLabelPc(nextcondlabel, code.LastPC()) + compileLogicalOpExprAux(context, reg, expr.Rhs, ec, endlabel, endlabel, false, lb) + } else { + compileLogicalOpExprAux(context, reg, expr.Lhs, ec, endlabel, nextcondlabel, true, lb) + context.SetLabelPc(nextcondlabel, code.LastPC()) + compileLogicalOpExprAux(context, reg, expr.Rhs, ec, endlabel, endlabel, false, lb) + } + + if lb.b { + context.SetLabelPc(lb.f, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 0, 1, sline(expr)) + context.SetLabelPc(lb.t, code.LastPC()) + code.AddABC(OP_LOADBOOL, a, 1, 0, sline(expr)) + } + + lastinst := code.Last() + if opGetOpCode(lastinst) == OP_JMP && opGetArgSbx(lastinst) == endlabel { + code.Pop() + } + + context.SetLabelPc(endlabel, code.LastPC()) +} // }}} + +func compileLogicalOpExprAux(context *funcContext, reg int, expr ast.Expr, ec *expcontext, thenlabel, elselabel int, hasnextcond bool, lb *lblabels) { // {{{ + // TODO folding constants? + code := context.Code + flip := 0 + jumplabel := elselabel + if hasnextcond { + flip = 1 + jumplabel = thenlabel + } + + switch ex := expr.(type) { + case *ast.FalseExpr: + if elselabel == lb.e { + code.AddASbx(OP_JMP, 0, lb.f, sline(expr)) + lb.b = true + } else { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + } + return + case *ast.NilExpr: + if elselabel == lb.e { + compileExpr(context, reg, expr, ec) + code.AddASbx(OP_JMP, 0, lb.e, sline(expr)) + } else { + code.AddASbx(OP_JMP, 0, elselabel, sline(expr)) + } + return + case *ast.TrueExpr: + if thenlabel == lb.e { + code.AddASbx(OP_JMP, 0, lb.t, sline(expr)) + lb.b = true + } else { + code.AddASbx(OP_JMP, 0, thenlabel, sline(expr)) + } + return + case *ast.NumberExpr, *ast.StringExpr: + if thenlabel == lb.e { + compileExpr(context, reg, expr, ec) + code.AddASbx(OP_JMP, 0, lb.e, sline(expr)) + } else { + code.AddASbx(OP_JMP, 0, thenlabel, sline(expr)) + } + return + case *ast.LogicalOpExpr: + switch ex.Operator { + case "and": + nextcondlabel := context.NewLabel() + compileLogicalOpExprAux(context, reg, ex.Lhs, ec, nextcondlabel, elselabel, false, lb) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileLogicalOpExprAux(context, reg, ex.Rhs, ec, thenlabel, elselabel, hasnextcond, lb) + case "or": + nextcondlabel := context.NewLabel() + compileLogicalOpExprAux(context, reg, ex.Lhs, ec, thenlabel, nextcondlabel, true, lb) + context.SetLabelPc(nextcondlabel, context.Code.LastPC()) + compileLogicalOpExprAux(context, reg, ex.Rhs, ec, thenlabel, elselabel, hasnextcond, lb) + } + return + case *ast.RelationalOpExpr: + if thenlabel == elselabel { + flip ^= 1 + jumplabel = lb.t + lb.b = true + } else if thenlabel == lb.e { + jumplabel = lb.t + lb.b = true + } else if elselabel == lb.e { + jumplabel = lb.f + lb.b = true + } + compileRelationalOpExprAux(context, reg, ex, flip, jumplabel) + return + } + + if !hasnextcond && thenlabel == elselabel { + reg += compileExpr(context, reg, expr, ec) + } else { + a := reg + sreg := savereg(ec, a) + reg += compileExpr(context, reg, expr, ecnone(0)) + if sreg == a { + code.AddABC(OP_TEST, a, 0, 0^flip, sline(expr)) + } else { + code.AddABC(OP_TESTSET, sreg, a, 0^flip, sline(expr)) + } + } + code.AddASbx(OP_JMP, 0, jumplabel, sline(expr)) +} // }}} + +func compileFuncCallExpr(context *funcContext, reg int, expr *ast.FuncCallExpr, ec *expcontext) int { // {{{ + funcreg := reg + if ec.ctype == ecLocal && ec.reg == (int(context.Proto.NumParameters)-1) { + funcreg = ec.reg + reg = ec.reg + } + argc := len(expr.Args) + islastvararg := false + name := "(anonymous)" + + if expr.Func != nil { // hoge.func() + reg += compileExpr(context, reg, expr.Func, ecnone(0)) + name = getExprName(context, expr.Func) + } else { // hoge:method() + b := reg + compileExprWithMVPropagation(context, expr.Receiver, ®, &b) + c := loadRk(context, ®, expr, LString(expr.Method)) + context.Code.AddABC(OP_SELF, funcreg, b, c, sline(expr)) + // increments a register for an implicit "self" + reg = b + 1 + reg2 := funcreg + 2 + if reg2 > reg { + reg = reg2 + } + argc += 1 + name = string(expr.Method) + } + + for i, ar := range expr.Args { + islastvararg = (i == len(expr.Args)-1) && isVarArgReturnExpr(ar) + if islastvararg { + compileExpr(context, reg, ar, ecnone(-2)) + } else { + reg += compileExpr(context, reg, ar, ecnone(0)) + } + } + b := argc + 1 + if islastvararg { + b = 0 + } + context.Code.AddABC(OP_CALL, funcreg, b, ec.varargopt+2, sline(expr)) + context.Proto.DbgCalls = append(context.Proto.DbgCalls, DbgCall{Pc: context.Code.LastPC(), Name: name}) + + if ec.varargopt == 0 && ec.ctype == ecLocal && funcreg != ec.reg { + context.Code.AddABC(OP_MOVE, ec.reg, funcreg, 0, sline(expr)) + return 1 + } + if context.RegTop() > (funcreg+2+ec.varargopt) || ec.varargopt < -1 { + return 0 + } + return ec.varargopt + 1 +} // }}} + +func loadRk(context *funcContext, reg *int, expr ast.Expr, cnst LValue) int { // {{{ + cindex := context.ConstIndex(cnst) + if cindex <= opMaxIndexRk { + return opRkAsk(cindex) + } else { + ret := *reg + *reg++ + context.Code.AddABx(OP_LOADK, ret, cindex, sline(expr)) + return ret + } +} // }}} + +func getIdentRefType(context *funcContext, current *funcContext, expr *ast.IdentExpr) expContextType { // {{{ + if current == nil { + return ecGlobal + } else if current.FindLocalVar(expr.Value) > -1 { + if current == context { + return ecLocal + } + return ecUpvalue + } + return getIdentRefType(context, current.Parent, expr) +} // }}} + +func getExprName(context *funcContext, expr ast.Expr) string { // {{{ + switch ex := expr.(type) { + case *ast.IdentExpr: + return ex.Value + case *ast.AttrGetExpr: + switch kex := ex.Key.(type) { + case *ast.StringExpr: + return kex.Value + } + return "?" + } + return "?" +} // }}} + +func patchCode(context *funcContext) { // {{{ + maxreg := 1 + if np := int(context.Proto.NumParameters); np > 1 { + maxreg = np + } + moven := 0 + code := context.Code.List() + for pc := 0; pc < len(code); pc++ { + inst := code[pc] + curop := opGetOpCode(inst) + switch curop { + case OP_CLOSURE: + pc += int(context.Proto.FunctionPrototypes[opGetArgBx(inst)].NumUpvalues) + moven = 0 + continue + case OP_SETGLOBAL, OP_SETUPVAL, OP_EQ, OP_LT, OP_LE, OP_TEST, + OP_TAILCALL, OP_RETURN, OP_FORPREP, OP_FORLOOP, OP_TFORLOOP, + OP_SETLIST, OP_CLOSE: + /* nothing to do */ + case OP_CALL: + if reg := opGetArgA(inst) + opGetArgC(inst) - 2; reg > maxreg { + maxreg = reg + } + case OP_VARARG: + if reg := opGetArgA(inst) + opGetArgB(inst) - 1; reg > maxreg { + maxreg = reg + } + case OP_SELF: + if reg := opGetArgA(inst) + 1; reg > maxreg { + maxreg = reg + } + case OP_LOADNIL: + if reg := opGetArgB(inst); reg > maxreg { + maxreg = reg + } + case OP_JMP: // jump to jump optimization + distance := 0 + count := 0 // avoiding infinite loops + for jmp := inst; opGetOpCode(jmp) == OP_JMP && count < 5; jmp = context.Code.At(pc + distance + 1) { + d := context.GetLabelPc(opGetArgSbx(jmp)) - pc + if d > opMaxArgSbx { + if distance == 0 { + raiseCompileError(context, context.Proto.LineDefined, "too long to jump.") + } + break + } + distance = d + count++ + } + if distance == 0 { + context.Code.SetOpCode(pc, OP_NOP) + } else { + context.Code.SetSbx(pc, distance) + } + default: + if reg := opGetArgA(inst); reg > maxreg { + maxreg = reg + } + } + + // bulk move optimization(reducing op dipatch costs) + if curop == OP_MOVE { + moven++ + } else { + if moven > 1 { + context.Code.SetOpCode(pc-moven, OP_MOVEN) + context.Code.SetC(pc-moven, intMin(moven-1, opMaxArgsC)) + } + moven = 0 + } + } + maxreg++ + if maxreg > maxRegisters { + raiseCompileError(context, context.Proto.LineDefined, "register overflow(too many local variables)") + } + context.Proto.NumUsedRegisters = uint8(maxreg) +} // }}} + +func Compile(chunk []ast.Stmt, name string) (proto *FunctionProto, err error) { // {{{ + defer func() { + if rcv := recover(); rcv != nil { + if _, ok := rcv.(*CompileError); ok { + err = rcv.(error) + } else { + panic(rcv) + } + } + }() + err = nil + parlist := &ast.ParList{HasVargs: true, Names: []string{}} + funcexpr := &ast.FunctionExpr{ParList: parlist, Stmts: chunk} + context := newFuncContext(name, nil) + compileFunctionExpr(context, funcexpr, ecnone(0)) + proto = context.Proto + return +} // }}} diff --git a/vendor/github.com/yuin/gopher-lua/config.go b/vendor/github.com/yuin/gopher-lua/config.go new file mode 100644 index 0000000..96c2ad3 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/config.go @@ -0,0 +1,35 @@ +package lua + +import ( + "os" +) + +var CompatVarArg = true +var FieldsPerFlush = 50 +var RegistrySize = 256 * 20 +var CallStackSize = 256 +var MaxTableGetLoop = 100 +var MaxArrayIndex = 67108864 + +type LNumber float64 + +const LNumberBit = 64 +const LNumberScanFormat = "%f" +const LuaVersion = "Lua 5.1" + +var LuaPath = "LUA_PATH" +var LuaLDir string +var LuaPathDefault string +var LuaOS string + +func init() { + if os.PathSeparator == '/' { // unix-like + LuaOS = "unix" + LuaLDir = "/usr/local/share/lua/5.1" + LuaPathDefault = "./?.lua;" + LuaLDir + "/?.lua;" + LuaLDir + "/?/init.lua" + } else { // windows + LuaOS = "windows" + LuaLDir = "!\\lua" + LuaPathDefault = ".\\?.lua;" + LuaLDir + "\\?.lua;" + LuaLDir + "\\?\\init.lua" + } +} diff --git a/vendor/github.com/yuin/gopher-lua/coroutinelib.go b/vendor/github.com/yuin/gopher-lua/coroutinelib.go new file mode 100644 index 0000000..d42c41a --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/coroutinelib.go @@ -0,0 +1,112 @@ +package lua + +func OpenCoroutine(L *LState) int { + // TODO: Tie module name to contents of linit.go? + mod := L.RegisterModule(CoroutineLibName, coFuncs) + L.Push(mod) + return 1 +} + +var coFuncs = map[string]LGFunction{ + "create": coCreate, + "yield": coYield, + "resume": coResume, + "running": coRunning, + "status": coStatus, + "wrap": coWrap, +} + +func coCreate(L *LState) int { + fn := L.CheckFunction(1) + newthread, _ := L.NewThread() + base := 0 + newthread.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + L.Push(newthread) + return 1 +} + +func coYield(L *LState) int { + return -1 +} + +func coResume(L *LState) int { + th := L.CheckThread(1) + if L.G.CurrentThread == th { + msg := "can not resume a running thread" + if th.wrapped { + L.RaiseError(msg) + return 0 + } + L.Push(LFalse) + L.Push(LString(msg)) + return 2 + } + if th.Dead { + msg := "can not resume a dead thread" + if th.wrapped { + L.RaiseError(msg) + return 0 + } + L.Push(LFalse) + L.Push(LString(msg)) + return 2 + } + th.Parent = L + L.G.CurrentThread = th + if !th.isStarted() { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + nargs := L.GetTop() - 1 + L.XMoveTo(th, nargs) + cf.NArgs = nargs + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + nargs := L.GetTop() - 1 + L.XMoveTo(th, nargs) + } + top := L.GetTop() + threadRun(th) + return L.GetTop() - top +} + +func coRunning(L *LState) int { + if L.G.MainThread == L { + L.Push(LNil) + return 1 + } + L.Push(L.G.CurrentThread) + return 1 +} + +func coStatus(L *LState) int { + L.Push(LString(L.Status(L.CheckThread(1)))) + return 1 +} + +func wrapaux(L *LState) int { + L.Insert(L.ToThread(UpvalueIndex(1)), 1) + return coResume(L) +} + +func coWrap(L *LState) int { + coCreate(L) + L.CheckThread(L.GetTop()).wrapped = true + v := L.Get(L.GetTop()) + L.Pop(1) + L.Push(L.NewClosure(wrapaux, v)) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/debuglib.go b/vendor/github.com/yuin/gopher-lua/debuglib.go new file mode 100644 index 0000000..41f883f --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/debuglib.go @@ -0,0 +1,173 @@ +package lua + +import ( + "fmt" + "strings" +) + +func OpenDebug(L *LState) int { + dbgmod := L.RegisterModule(DebugLibName, debugFuncs) + L.Push(dbgmod) + return 1 +} + +var debugFuncs = map[string]LGFunction{ + "getfenv": debugGetFEnv, + "getinfo": debugGetInfo, + "getlocal": debugGetLocal, + "getmetatable": debugGetMetatable, + "getupvalue": debugGetUpvalue, + "setfenv": debugSetFEnv, + "setlocal": debugSetLocal, + "setmetatable": debugSetMetatable, + "setupvalue": debugSetUpvalue, + "traceback": debugTraceback, +} + +func debugGetFEnv(L *LState) int { + L.Push(L.GetFEnv(L.CheckAny(1))) + return 1 +} + +func debugGetInfo(L *LState) int { + L.CheckTypes(1, LTFunction, LTNumber) + arg1 := L.Get(1) + what := L.OptString(2, "Slunf") + var dbg *Debug + var fn LValue + var err error + var ok bool + switch lv := arg1.(type) { + case *LFunction: + dbg = &Debug{} + fn, err = L.GetInfo(">"+what, dbg, lv) + case LNumber: + dbg, ok = L.GetStack(int(lv)) + if !ok { + L.Push(LNil) + return 1 + } + fn, err = L.GetInfo(what, dbg, LNil) + } + + if err != nil { + L.Push(LNil) + return 1 + } + tbl := L.NewTable() + if len(dbg.Name) > 0 { + tbl.RawSetString("name", LString(dbg.Name)) + } else { + tbl.RawSetString("name", LNil) + } + tbl.RawSetString("what", LString(dbg.What)) + tbl.RawSetString("source", LString(dbg.Source)) + tbl.RawSetString("currentline", LNumber(dbg.CurrentLine)) + tbl.RawSetString("nups", LNumber(dbg.NUpvalues)) + tbl.RawSetString("linedefined", LNumber(dbg.LineDefined)) + tbl.RawSetString("lastlinedefined", LNumber(dbg.LastLineDefined)) + tbl.RawSetString("func", fn) + L.Push(tbl) + return 1 +} + +func debugGetLocal(L *LState) int { + level := L.CheckInt(1) + idx := L.CheckInt(2) + dbg, ok := L.GetStack(level) + if !ok { + L.ArgError(1, "level out of range") + } + name, value := L.GetLocal(dbg, idx) + if len(name) > 0 { + L.Push(LString(name)) + L.Push(value) + return 2 + } + L.Push(LNil) + return 1 +} + +func debugGetMetatable(L *LState) int { + L.Push(L.GetMetatable(L.CheckAny(1))) + return 1 +} + +func debugGetUpvalue(L *LState) int { + fn := L.CheckFunction(1) + idx := L.CheckInt(2) + name, value := L.GetUpvalue(fn, idx) + if len(name) > 0 { + L.Push(LString(name)) + L.Push(value) + return 2 + } + L.Push(LNil) + return 1 +} + +func debugSetFEnv(L *LState) int { + L.SetFEnv(L.CheckAny(1), L.CheckAny(2)) + return 0 +} + +func debugSetLocal(L *LState) int { + level := L.CheckInt(1) + idx := L.CheckInt(2) + value := L.CheckAny(3) + dbg, ok := L.GetStack(level) + if !ok { + L.ArgError(1, "level out of range") + } + name := L.SetLocal(dbg, idx, value) + if len(name) > 0 { + L.Push(LString(name)) + } else { + L.Push(LNil) + } + return 1 +} + +func debugSetMetatable(L *LState) int { + L.CheckTypes(2, LTNil, LTTable) + obj := L.Get(1) + mt := L.Get(2) + L.SetMetatable(obj, mt) + L.SetTop(1) + return 1 +} + +func debugSetUpvalue(L *LState) int { + fn := L.CheckFunction(1) + idx := L.CheckInt(2) + value := L.CheckAny(3) + name := L.SetUpvalue(fn, idx, value) + if len(name) > 0 { + L.Push(LString(name)) + } else { + L.Push(LNil) + } + return 1 +} + +func debugTraceback(L *LState) int { + msg := "" + level := L.OptInt(2, 1) + ls := L + if L.GetTop() > 0 { + if s, ok := L.Get(1).assertString(); ok { + msg = s + } + if l, ok := L.Get(1).(*LState); ok { + ls = l + msg = "" + } + } + + traceback := strings.TrimSpace(ls.stackTrace(level)) + if len(msg) > 0 { + traceback = fmt.Sprintf("%s\n%s", msg, traceback) + } + L.Push(LString(traceback)) + return 1 +} diff --git a/vendor/github.com/yuin/gopher-lua/function.go b/vendor/github.com/yuin/gopher-lua/function.go new file mode 100644 index 0000000..169e540 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/function.go @@ -0,0 +1,193 @@ +package lua + +import ( + "fmt" + "strings" +) + +const ( + VarArgHasArg uint8 = 1 + VarArgIsVarArg uint8 = 2 + VarArgNeedsArg uint8 = 4 +) + +type DbgLocalInfo struct { + Name string + StartPc int + EndPc int +} + +type DbgCall struct { + Name string + Pc int +} + +type FunctionProto struct { + SourceName string + LineDefined int + LastLineDefined int + NumUpvalues uint8 + NumParameters uint8 + IsVarArg uint8 + NumUsedRegisters uint8 + Code []uint32 + Constants []LValue + FunctionPrototypes []*FunctionProto + + DbgSourcePositions []int + DbgLocals []*DbgLocalInfo + DbgCalls []DbgCall + DbgUpvalues []string + + stringConstants []string +} + +/* Upvalue {{{ */ + +type Upvalue struct { + next *Upvalue + reg *registry + index int + value LValue + closed bool +} + +func (uv *Upvalue) Value() LValue { + //if uv.IsClosed() { + if uv.closed || uv.reg == nil { + return uv.value + } + //return uv.reg.Get(uv.index) + return uv.reg.array[uv.index] +} + +func (uv *Upvalue) SetValue(value LValue) { + if uv.IsClosed() { + uv.value = value + } else { + uv.reg.Set(uv.index, value) + } +} + +func (uv *Upvalue) Close() { + value := uv.Value() + uv.closed = true + uv.value = value +} + +func (uv *Upvalue) IsClosed() bool { + return uv.closed || uv.reg == nil +} + +func UpvalueIndex(i int) int { + return GlobalsIndex - i +} + +/* }}} */ + +/* FunctionProto {{{ */ + +func newFunctionProto(name string) *FunctionProto { + return &FunctionProto{ + SourceName: name, + LineDefined: 0, + LastLineDefined: 0, + NumUpvalues: 0, + NumParameters: 0, + IsVarArg: 0, + NumUsedRegisters: 2, + Code: make([]uint32, 0, 128), + Constants: make([]LValue, 0, 32), + FunctionPrototypes: make([]*FunctionProto, 0, 16), + + DbgSourcePositions: make([]int, 0, 128), + DbgLocals: make([]*DbgLocalInfo, 0, 16), + DbgCalls: make([]DbgCall, 0, 128), + DbgUpvalues: make([]string, 0, 16), + + stringConstants: make([]string, 0, 32), + } +} + +func (fp *FunctionProto) String() string { + return fp.str(1, 0) +} + +func (fp *FunctionProto) str(level int, count int) string { + indent := strings.Repeat(" ", level-1) + buf := []string{} + buf = append(buf, fmt.Sprintf("%v; function [%v] definition (level %v)\n", + indent, count, level)) + buf = append(buf, fmt.Sprintf("%v; %v upvalues, %v params, %v stacks\n", + indent, fp.NumUpvalues, fp.NumParameters, fp.NumUsedRegisters)) + for reg, linfo := range fp.DbgLocals { + buf = append(buf, fmt.Sprintf("%v.local %v ; %v\n", indent, linfo.Name, reg)) + } + for reg, upvalue := range fp.DbgUpvalues { + buf = append(buf, fmt.Sprintf("%v.upvalue %v ; %v\n", indent, upvalue, reg)) + } + for reg, conzt := range fp.Constants { + buf = append(buf, fmt.Sprintf("%v.const %v ; %v\n", indent, conzt.String(), reg)) + } + buf = append(buf, "\n") + + protono := 0 + for no, code := range fp.Code { + inst := opGetOpCode(code) + if inst == OP_CLOSURE { + buf = append(buf, "\n") + buf = append(buf, fp.FunctionPrototypes[protono].str(level+1, protono)) + buf = append(buf, "\n") + protono++ + } + buf = append(buf, fmt.Sprintf("%v[%03d] %v (line:%v)\n", + indent, no+1, opToString(code), fp.DbgSourcePositions[no])) + + } + buf = append(buf, fmt.Sprintf("%v; end of function\n", indent)) + return strings.Join(buf, "") +} + +/* }}} */ + +/* LFunction {{{ */ + +func newLFunctionL(proto *FunctionProto, env *LTable, nupvalue int) *LFunction { + return &LFunction{ + IsG: false, + Env: env, + + Proto: proto, + GFunction: nil, + Upvalues: make([]*Upvalue, nupvalue), + } +} + +func newLFunctionG(gfunc LGFunction, env *LTable, nupvalue int) *LFunction { + return &LFunction{ + IsG: true, + Env: env, + + Proto: nil, + GFunction: gfunc, + Upvalues: make([]*Upvalue, nupvalue), + } +} + +func (fn *LFunction) LocalName(regno, pc int) (string, bool) { + if fn.IsG { + return "", false + } + p := fn.Proto + for i := 0; i < len(p.DbgLocals) && p.DbgLocals[i].StartPc < pc; i++ { + if pc < p.DbgLocals[i].EndPc { + regno-- + if regno == 0 { + return p.DbgLocals[i].Name, true + } + } + } + return "", false +} + +/* }}} */ diff --git a/vendor/github.com/yuin/gopher-lua/iolib.go b/vendor/github.com/yuin/gopher-lua/iolib.go new file mode 100644 index 0000000..b0c2f56 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/iolib.go @@ -0,0 +1,743 @@ +package lua + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "syscall" +) + +var ioFuncs = map[string]LGFunction{ + "close": ioClose, + "flush": ioFlush, + "lines": ioLines, + "input": ioInput, + "output": ioOutput, + "open": ioOpenFile, + "popen": ioPopen, + "read": ioRead, + "type": ioType, + "tmpfile": ioTmpFile, + "write": ioWrite, +} + +const lFileClass = "FILE*" + +type lFile struct { + fp *os.File + pp *exec.Cmd + writer io.Writer + reader *bufio.Reader + closed bool +} + +type lFileType int + +const ( + lFileFile lFileType = iota + lFileProcess +) + +const fileDefOutIndex = 1 +const fileDefInIndex = 2 +const fileDefaultWriteBuffer = 4096 +const fileDefaultReadBuffer = 4096 + +func checkFile(L *LState) *lFile { + ud := L.CheckUserData(1) + if file, ok := ud.Value.(*lFile); ok { + return file + } + L.ArgError(1, "file expected") + return nil +} + +func errorIfFileIsClosed(L *LState, file *lFile) { + if file.closed { + L.ArgError(1, "file is closed") + } +} + +func newFile(L *LState, file *os.File, path string, flag int, perm os.FileMode, writable, readable bool) (*LUserData, error) { + ud := L.NewUserData() + var err error + if file == nil { + file, err = os.OpenFile(path, flag, perm) + if err != nil { + return nil, err + } + } + lfile := &lFile{fp: file, pp: nil, writer: nil, reader: nil, closed: false} + ud.Value = lfile + if writable { + lfile.writer = file + } + if readable { + lfile.reader = bufio.NewReaderSize(file, fileDefaultReadBuffer) + } + L.SetMetatable(ud, L.GetTypeMetatable(lFileClass)) + return ud, nil +} + +func newProcess(L *LState, cmd string, writable, readable bool) (*LUserData, error) { + ud := L.NewUserData() + c, args := popenArgs(cmd) + pp := exec.Command(c, args...) + lfile := &lFile{fp: nil, pp: pp, writer: nil, reader: nil, closed: false} + ud.Value = lfile + + var err error + if writable { + lfile.writer, err = pp.StdinPipe() + } + if readable { + var reader io.Reader + reader, err = pp.StdoutPipe() + lfile.reader = bufio.NewReaderSize(reader, fileDefaultReadBuffer) + } + if err != nil { + return nil, err + } + err = pp.Start() + if err != nil { + return nil, err + } + + L.SetMetatable(ud, L.GetTypeMetatable(lFileClass)) + return ud, nil +} + +func (file *lFile) Type() lFileType { + if file.fp == nil { + return lFileProcess + } + return lFileFile +} + +func (file *lFile) Name() string { + switch file.Type() { + case lFileFile: + return fmt.Sprintf("file %s", file.fp.Name()) + case lFileProcess: + return fmt.Sprintf("process %s", file.pp.Path) + } + return "" +} + +func (file *lFile) AbandonReadBuffer() error { + if file.Type() == lFileFile && file.reader != nil { + _, err := file.fp.Seek(-int64(file.reader.Buffered()), 1) + if err != nil { + return err + } + file.reader = bufio.NewReaderSize(file.fp, fileDefaultReadBuffer) + } + return nil +} + +func fileDefOut(L *LState) *LUserData { + return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefOutIndex).(*LUserData) +} + +func fileDefIn(L *LState) *LUserData { + return L.Get(UpvalueIndex(1)).(*LTable).RawGetInt(fileDefInIndex).(*LUserData) +} + +func fileIsWritable(L *LState, file *lFile) int { + if file.writer == nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("%s is opened for only reading.", file.Name()))) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + return 0 +} + +func fileIsReadable(L *LState, file *lFile) int { + if file.reader == nil { + L.Push(LNil) + L.Push(LString(fmt.Sprintf("%s is opened for only writing.", file.Name()))) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + return 0 +} + +var stdFiles = []struct { + name string + file *os.File + writable bool + readable bool +}{ + {"stdout", os.Stdout, true, false}, + {"stdin", os.Stdin, false, true}, + {"stderr", os.Stderr, true, false}, +} + +func OpenIo(L *LState) int { + mod := L.RegisterModule(IoLibName, map[string]LGFunction{}).(*LTable) + mt := L.NewTypeMetatable(lFileClass) + mt.RawSetString("__index", mt) + L.SetFuncs(mt, fileMethods) + mt.RawSetString("lines", L.NewClosure(fileLines, L.NewFunction(fileLinesIter))) + + for _, finfo := range stdFiles { + file, _ := newFile(L, finfo.file, "", 0, os.FileMode(0), finfo.writable, finfo.readable) + mod.RawSetString(finfo.name, file) + } + uv := L.CreateTable(2, 0) + uv.RawSetInt(fileDefOutIndex, mod.RawGetString("stdout")) + uv.RawSetInt(fileDefInIndex, mod.RawGetString("stdin")) + for name, fn := range ioFuncs { + mod.RawSetString(name, L.NewClosure(fn, uv)) + } + mod.RawSetString("lines", L.NewClosure(ioLines, uv, L.NewClosure(ioLinesIter, uv))) + // Modifications are being made in-place rather than returned? + L.Push(mod) + return 1 +} + +var fileMethods = map[string]LGFunction{ + "__tostring": fileToString, + "write": fileWrite, + "close": fileClose, + "flush": fileFlush, + "lines": fileLines, + "read": fileRead, + "seek": fileSeek, + "setvbuf": fileSetVBuf, +} + +func fileToString(L *LState) int { + file := checkFile(L) + if file.Type() == lFileFile { + if file.closed { + L.Push(LString("file (closed)")) + } else { + L.Push(LString("file")) + } + } else { + if file.closed { + L.Push(LString("process (closed)")) + } else { + L.Push(LString("process")) + } + } + return 1 +} + +func fileWriteAux(L *LState, file *lFile, idx int) int { + if n := fileIsWritable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + top := L.GetTop() + out := file.writer + var err error + for i := idx; i <= top; i++ { + L.CheckTypes(i, LTNumber, LTString) + s := LVAsString(L.Get(i)) + if _, err = out.Write(unsafeFastStringToReadOnlyBytes(s)); err != nil { + goto errreturn + } + } + + file.AbandonReadBuffer() + L.Push(LTrue) + return 1 +errreturn: + + file.AbandonReadBuffer() + L.Push(LNil) + L.Push(LString(err.Error())) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 +} + +func fileCloseAux(L *LState, file *lFile) int { + file.closed = true + var err error + if file.writer != nil { + if bwriter, ok := file.writer.(*bufio.Writer); ok { + if err = bwriter.Flush(); err != nil { + goto errreturn + } + } + } + file.AbandonReadBuffer() + + switch file.Type() { + case lFileFile: + if err = file.fp.Close(); err != nil { + goto errreturn + } + L.Push(LTrue) + return 1 + case lFileProcess: + err = file.pp.Wait() + var exitStatus int // Initialised to zero value = 0 + if err != nil { + if e2, ok := err.(*exec.ExitError); ok { + if s, ok := e2.Sys().(syscall.WaitStatus); ok { + exitStatus = s.ExitStatus() + } else { + err = errors.New("Unimplemented for system where exec.ExitError.Sys() is not syscall.WaitStatus.") + } + } + } else { + exitStatus = 0 + } + L.Push(LNumber(exitStatus)) + return 1 + } + +errreturn: + L.RaiseError(err.Error()) + return 0 +} + +func fileFlushAux(L *LState, file *lFile) int { + if n := fileIsWritable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + + if bwriter, ok := file.writer.(*bufio.Writer); ok { + if err := bwriter.Flush(); err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + } + L.Push(LTrue) + return 1 +} + +func fileReadAux(L *LState, file *lFile, idx int) int { + if n := fileIsReadable(L, file); n != 0 { + return n + } + errorIfFileIsClosed(L, file) + if L.GetTop() == idx-1 { + L.Push(LString("*l")) + } + var err error + top := L.GetTop() + for i := idx; i <= top; i++ { + switch lv := L.Get(i).(type) { + case LNumber: + size := int64(lv) + if size == 0 { + _, err = file.reader.ReadByte() + if err == io.EOF { + L.Push(LNil) + goto normalreturn + } + file.reader.UnreadByte() + } + var buf []byte + var iseof bool + buf, err, iseof = readBufioSize(file.reader, size) + if iseof { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + case LString: + options := L.CheckString(i) + if len(options) > 0 && options[0] != '*' { + L.ArgError(2, "invalid options:"+options) + } + for _, opt := range options[1:] { + switch opt { + case 'n': + var v LNumber + _, err = fmt.Fscanf(file.reader, LNumberScanFormat, &v) + if err == io.EOF { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(v) + case 'a': + var buf []byte + buf, err = ioutil.ReadAll(file.reader) + if err == io.EOF { + L.Push(emptyLString) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + case 'l': + var buf []byte + var iseof bool + buf, err, iseof = readBufioLine(file.reader) + if iseof { + L.Push(LNil) + goto normalreturn + } + if err != nil { + goto errreturn + } + L.Push(LString(string(buf))) + default: + L.ArgError(2, "invalid options:"+string(opt)) + } + } + } + } +normalreturn: + return L.GetTop() - top + +errreturn: + L.RaiseError(err.Error()) + //L.Push(LNil) + //L.Push(LString(err.Error())) + return 2 +} + +var fileSeekOptions = []string{"set", "cur", "end"} + +func fileSeek(L *LState) int { + file := checkFile(L) + if file.Type() != lFileFile { + L.Push(LNil) + L.Push(LString("can not seek a process.")) + return 2 + } + + top := L.GetTop() + if top == 1 { + L.Push(LString("cur")) + L.Push(LNumber(0)) + } else if top == 2 { + L.Push(LNumber(0)) + } + + var pos int64 + var err error + + err = file.AbandonReadBuffer() + if err != nil { + goto errreturn + } + + pos, err = file.fp.Seek(L.CheckInt64(3), L.CheckOption(2, fileSeekOptions)) + if err != nil { + goto errreturn + } + + L.Push(LNumber(pos)) + return 1 + +errreturn: + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 +} + +func fileWrite(L *LState) int { + return fileWriteAux(L, checkFile(L), 2) +} + +func fileClose(L *LState) int { + return fileCloseAux(L, checkFile(L)) +} + +func fileFlush(L *LState) int { + return fileFlushAux(L, checkFile(L)) +} + +func fileLinesIter(L *LState) int { + var file *lFile + if ud, ok := L.Get(1).(*LUserData); ok { + file = ud.Value.(*lFile) + } else { + file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile) + } + buf, _, err := file.reader.ReadLine() + if err != nil { + if err == io.EOF { + L.Push(LNil) + return 1 + } + L.RaiseError(err.Error()) + } + L.Push(LString(string(buf))) + return 1 +} + +func fileLines(L *LState) int { + file := checkFile(L) + ud := L.CheckUserData(1) + if n := fileIsReadable(L, file); n != 0 { + return 0 + } + L.Push(L.NewClosure(fileLinesIter, L.Get(UpvalueIndex(1)), ud)) + return 1 +} + +func fileRead(L *LState) int { + return fileReadAux(L, checkFile(L), 2) +} + +var filebufOptions = []string{"no", "full"} + +func fileSetVBuf(L *LState) int { + var err error + var writer io.Writer + file := checkFile(L) + if n := fileIsWritable(L, file); n != 0 { + return n + } + switch filebufOptions[L.CheckOption(2, filebufOptions)] { + case "no": + switch file.Type() { + case lFileFile: + file.writer = file.fp + case lFileProcess: + file.writer, err = file.pp.StdinPipe() + if err != nil { + goto errreturn + } + } + case "full", "line": // TODO line buffer not supported + bufsize := L.OptInt(3, fileDefaultWriteBuffer) + switch file.Type() { + case lFileFile: + file.writer = bufio.NewWriterSize(file.fp, bufsize) + case lFileProcess: + writer, err = file.pp.StdinPipe() + if err != nil { + goto errreturn + } + file.writer = bufio.NewWriterSize(writer, bufsize) + } + } + L.Push(LTrue) + return 1 +errreturn: + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 +} + +func ioInput(L *LState) int { + if L.GetTop() == 0 { + L.Push(fileDefIn(L)) + return 1 + } + switch lv := L.Get(1).(type) { + case LString: + file, err := newFile(L, nil, string(lv), os.O_RDONLY, 0600, false, true) + if err != nil { + L.RaiseError(err.Error()) + } + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, file) + L.Push(file) + return 1 + case *LUserData: + if _, ok := lv.Value.(*lFile); ok { + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefInIndex, lv) + L.Push(lv) + return 1 + } + + } + L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) + return 0 +} + +func ioClose(L *LState) int { + if L.GetTop() == 0 { + return fileCloseAux(L, fileDefOut(L).Value.(*lFile)) + } + return fileClose(L) +} + +func ioFlush(L *LState) int { + return fileFlushAux(L, fileDefOut(L).Value.(*lFile)) +} + +func ioLinesIter(L *LState) int { + var file *lFile + toclose := false + if ud, ok := L.Get(1).(*LUserData); ok { + file = ud.Value.(*lFile) + } else { + file = L.Get(UpvalueIndex(2)).(*LUserData).Value.(*lFile) + toclose = true + } + buf, _, err := file.reader.ReadLine() + if err != nil { + if err == io.EOF { + if toclose { + fileCloseAux(L, file) + } + L.Push(LNil) + return 1 + } + L.RaiseError(err.Error()) + } + L.Push(LString(string(buf))) + return 1 +} + +func ioLines(L *LState) int { + if L.GetTop() == 0 { + L.Push(L.Get(UpvalueIndex(2))) + L.Push(fileDefIn(L)) + return 2 + } + + path := L.CheckString(1) + ud, err := newFile(L, nil, path, os.O_RDONLY, os.FileMode(0600), false, true) + if err != nil { + return 0 + } + L.Push(L.NewClosure(ioLinesIter, L.Get(UpvalueIndex(1)), ud)) + return 1 +} + +var ioOpenOpions = []string{"r", "rb", "w", "wb", "a", "ab", "r+", "rb+", "w+", "wb+", "a+", "ab+"} + +func ioOpenFile(L *LState) int { + path := L.CheckString(1) + if L.GetTop() == 1 { + L.Push(LString("r")) + } + mode := os.O_RDONLY + perm := 0600 + writable := true + readable := true + switch ioOpenOpions[L.CheckOption(2, ioOpenOpions)] { + case "r", "rb": + mode = os.O_RDONLY + writable = false + case "w", "wb": + mode = os.O_WRONLY | os.O_CREATE + readable = false + case "a", "ab": + mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE + case "r+", "rb+": + mode = os.O_RDWR + case "w+", "wb+": + mode = os.O_RDWR | os.O_TRUNC | os.O_CREATE + case "a+", "ab+": + mode = os.O_APPEND | os.O_RDWR | os.O_CREATE + } + file, err := newFile(L, nil, path, mode, os.FileMode(perm), writable, readable) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + L.Push(LNumber(1)) // C-Lua compatibility: Original Lua pushes errno to the stack + return 3 + } + L.Push(file) + return 1 + +} + +var ioPopenOptions = []string{"r", "w"} + +func ioPopen(L *LState) int { + cmd := L.CheckString(1) + if L.GetTop() == 1 { + L.Push(LString("r")) + } + var file *LUserData + var err error + + switch ioPopenOptions[L.CheckOption(2, ioPopenOptions)] { + case "r": + file, err = newProcess(L, cmd, false, true) + case "w": + file, err = newProcess(L, cmd, true, false) + } + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + L.Push(file) + return 1 +} + +func ioRead(L *LState) int { + return fileReadAux(L, fileDefIn(L).Value.(*lFile), 1) +} + +func ioType(L *LState) int { + ud, udok := L.Get(1).(*LUserData) + if !udok { + L.Push(LNil) + return 1 + } + file, ok := ud.Value.(*lFile) + if !ok { + L.Push(LNil) + return 1 + } + if file.closed { + L.Push(LString("closed file")) + return 1 + } + L.Push(LString("file")) + return 1 +} + +func ioTmpFile(L *LState) int { + file, err := ioutil.TempFile("", "") + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } + L.G.tempFiles = append(L.G.tempFiles, file) + ud, _ := newFile(L, file, "", 0, os.FileMode(0), true, true) + L.Push(ud) + return 1 +} + +func ioOutput(L *LState) int { + if L.GetTop() == 0 { + L.Push(fileDefOut(L)) + return 1 + } + switch lv := L.Get(1).(type) { + case LString: + file, err := newFile(L, nil, string(lv), os.O_WRONLY|os.O_CREATE, 0600, true, false) + if err != nil { + L.RaiseError(err.Error()) + } + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, file) + L.Push(file) + return 1 + case *LUserData: + if _, ok := lv.Value.(*lFile); ok { + L.Get(UpvalueIndex(1)).(*LTable).RawSetInt(fileDefOutIndex, lv) + L.Push(lv) + return 1 + } + + } + L.ArgError(1, "string or file expedted, but got "+L.Get(1).Type().String()) + return 0 +} + +func ioWrite(L *LState) int { + return fileWriteAux(L, fileDefOut(L).Value.(*lFile), 1) +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/linit.go b/vendor/github.com/yuin/gopher-lua/linit.go new file mode 100644 index 0000000..cd96d66 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/linit.go @@ -0,0 +1,54 @@ +package lua + +const ( + // BaseLibName is here for consistency; the base functions have no namespace/library. + BaseLibName = "" + // LoadLibName is here for consistency; the loading system has no namespace/library. + LoadLibName = "package" + // TabLibName is the name of the table Library. + TabLibName = "table" + // IoLibName is the name of the io Library. + IoLibName = "io" + // OsLibName is the name of the os Library. + OsLibName = "os" + // StringLibName is the name of the string Library. + StringLibName = "string" + // MathLibName is the name of the math Library. + MathLibName = "math" + // DebugLibName is the name of the debug Library. + DebugLibName = "debug" + // ChannelLibName is the name of the channel Library. + ChannelLibName = "channel" + // CoroutineLibName is the name of the coroutine Library. + CoroutineLibName = "coroutine" +) + +type luaLib struct { + libName string + libFunc LGFunction +} + +var luaLibs = []luaLib{ + luaLib{LoadLibName, OpenPackage}, + luaLib{BaseLibName, OpenBase}, + luaLib{TabLibName, OpenTable}, + luaLib{IoLibName, OpenIo}, + luaLib{OsLibName, OpenOs}, + luaLib{StringLibName, OpenString}, + luaLib{MathLibName, OpenMath}, + luaLib{DebugLibName, OpenDebug}, + luaLib{ChannelLibName, OpenChannel}, + luaLib{CoroutineLibName, OpenCoroutine}, +} + +// OpenLibs loads the built-in libraries. It is equivalent to running OpenLoad, +// then OpenBase, then iterating over the other OpenXXX functions in any order. +func (ls *LState) OpenLibs() { + // NB: Map iteration order in Go is deliberately randomised, so must open Load/Base + // prior to iterating. + for _, lib := range luaLibs { + ls.Push(ls.NewFunction(lib.libFunc)) + ls.Push(LString(lib.libName)) + ls.Call(1, 0) + } +} diff --git a/vendor/github.com/yuin/gopher-lua/loadlib.go b/vendor/github.com/yuin/gopher-lua/loadlib.go new file mode 100644 index 0000000..772bb04 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/loadlib.go @@ -0,0 +1,125 @@ +package lua + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +/* load lib {{{ */ + +var loLoaders = []LGFunction{loLoaderPreload, loLoaderLua} + +func loGetPath(env string, defpath string) string { + path := os.Getenv(env) + if len(path) == 0 { + path = defpath + } + path = strings.Replace(path, ";;", ";"+defpath+";", -1) + if os.PathSeparator != '/' { + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + panic(err) + } + path = strings.Replace(path, "!", dir, -1) + } + return path +} + +func loFindFile(L *LState, name, pname string) (string, string) { + name = strings.Replace(name, ".", string(os.PathSeparator), -1) + lv := L.GetField(L.GetField(L.Get(EnvironIndex), "package"), pname) + path, ok := lv.(LString) + if !ok { + L.RaiseError("package.%s must be a string", pname) + } + messages := []string{} + for _, pattern := range strings.Split(string(path), ";") { + luapath := strings.Replace(pattern, "?", name, -1) + if _, err := os.Stat(luapath); err == nil { + return luapath, "" + } else { + messages = append(messages, err.Error()) + } + } + return "", strings.Join(messages, "\n\t") +} + +func OpenPackage(L *LState) int { + packagemod := L.RegisterModule(LoadLibName, loFuncs) + + L.SetField(packagemod, "preload", L.NewTable()) + + loaders := L.CreateTable(len(loLoaders), 0) + for i, loader := range loLoaders { + L.RawSetInt(loaders, i+1, L.NewFunction(loader)) + } + L.SetField(packagemod, "loaders", loaders) + L.SetField(L.Get(RegistryIndex), "_LOADERS", loaders) + + loaded := L.NewTable() + L.SetField(packagemod, "loaded", loaded) + L.SetField(L.Get(RegistryIndex), "_LOADED", loaded) + + L.SetField(packagemod, "path", LString(loGetPath(LuaPath, LuaPathDefault))) + L.SetField(packagemod, "cpath", emptyLString) + + L.Push(packagemod) + return 1 +} + +var loFuncs = map[string]LGFunction{ + "loadlib": loLoadLib, + "seeall": loSeeAll, +} + +func loLoaderPreload(L *LState) int { + name := L.CheckString(1) + preload := L.GetField(L.GetField(L.Get(EnvironIndex), "package"), "preload") + if _, ok := preload.(*LTable); !ok { + L.RaiseError("package.preload must be a table") + } + lv := L.GetField(preload, name) + if lv == LNil { + L.Push(LString(fmt.Sprintf("no field package.preload['%s']", name))) + return 1 + } + L.Push(lv) + return 1 +} + +func loLoaderLua(L *LState) int { + name := L.CheckString(1) + path, msg := loFindFile(L, name, "path") + if len(path) == 0 { + L.Push(LString(msg)) + return 1 + } + fn, err1 := L.LoadFile(path) + if err1 != nil { + L.RaiseError(err1.Error()) + } + L.Push(fn) + return 1 +} + +func loLoadLib(L *LState) int { + L.RaiseError("loadlib is not supported") + return 0 +} + +func loSeeAll(L *LState) int { + mod := L.CheckTable(1) + mt := L.GetMetatable(mod) + if mt == LNil { + mt = L.CreateTable(0, 1) + L.SetMetatable(mod, mt) + } + L.SetField(mt, "__index", L.Get(GlobalsIndex)) + return 0 +} + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/mathlib.go b/vendor/github.com/yuin/gopher-lua/mathlib.go new file mode 100644 index 0000000..0771a48 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/mathlib.go @@ -0,0 +1,231 @@ +package lua + +import ( + "math" + "math/rand" +) + +func OpenMath(L *LState) int { + mod := L.RegisterModule(MathLibName, mathFuncs).(*LTable) + mod.RawSetString("pi", LNumber(math.Pi)) + mod.RawSetString("huge", LNumber(math.MaxFloat64)) + L.Push(mod) + return 1 +} + +var mathFuncs = map[string]LGFunction{ + "abs": mathAbs, + "acos": mathAcos, + "asin": mathAsin, + "atan": mathAtan, + "atan2": mathAtan2, + "ceil": mathCeil, + "cos": mathCos, + "cosh": mathCosh, + "deg": mathDeg, + "exp": mathExp, + "floor": mathFloor, + "fmod": mathFmod, + "frexp": mathFrexp, + "ldexp": mathLdexp, + "log": mathLog, + "log10": mathLog10, + "max": mathMax, + "min": mathMin, + "mod": mathMod, + "modf": mathModf, + "pow": mathPow, + "rad": mathRad, + "random": mathRandom, + "randomseed": mathRandomseed, + "sin": mathSin, + "sinh": mathSinh, + "sqrt": mathSqrt, + "tan": mathTan, + "tanh": mathTanh, +} + +func mathAbs(L *LState) int { + L.Push(LNumber(math.Abs(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAcos(L *LState) int { + L.Push(LNumber(math.Acos(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAsin(L *LState) int { + L.Push(LNumber(math.Asin(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAtan(L *LState) int { + L.Push(LNumber(math.Atan(float64(L.CheckNumber(1))))) + return 1 +} + +func mathAtan2(L *LState) int { + L.Push(LNumber(math.Atan2(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathCeil(L *LState) int { + L.Push(LNumber(math.Ceil(float64(L.CheckNumber(1))))) + return 1 +} + +func mathCos(L *LState) int { + L.Push(LNumber(math.Cos(float64(L.CheckNumber(1))))) + return 1 +} + +func mathCosh(L *LState) int { + L.Push(LNumber(math.Cosh(float64(L.CheckNumber(1))))) + return 1 +} + +func mathDeg(L *LState) int { + L.Push(LNumber(float64(L.CheckNumber(1)) * 180 / math.Pi)) + return 1 +} + +func mathExp(L *LState) int { + L.Push(LNumber(math.Exp(float64(L.CheckNumber(1))))) + return 1 +} + +func mathFloor(L *LState) int { + L.Push(LNumber(math.Floor(float64(L.CheckNumber(1))))) + return 1 +} + +func mathFmod(L *LState) int { + L.Push(LNumber(math.Mod(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathFrexp(L *LState) int { + v1, v2 := math.Frexp(float64(L.CheckNumber(1))) + L.Push(LNumber(v1)) + L.Push(LNumber(v2)) + return 2 +} + +func mathLdexp(L *LState) int { + L.Push(LNumber(math.Ldexp(float64(L.CheckNumber(1)), L.CheckInt(2)))) + return 1 +} + +func mathLog(L *LState) int { + L.Push(LNumber(math.Log(float64(L.CheckNumber(1))))) + return 1 +} + +func mathLog10(L *LState) int { + L.Push(LNumber(math.Log10(float64(L.CheckNumber(1))))) + return 1 +} + +func mathMax(L *LState) int { + if L.GetTop() == 0 { + L.RaiseError("wrong number of arguments") + } + max := L.CheckNumber(1) + top := L.GetTop() + for i := 2; i <= top; i++ { + v := L.CheckNumber(i) + if v > max { + max = v + } + } + L.Push(max) + return 1 +} + +func mathMin(L *LState) int { + if L.GetTop() == 0 { + L.RaiseError("wrong number of arguments") + } + min := L.CheckNumber(1) + top := L.GetTop() + for i := 2; i <= top; i++ { + v := L.CheckNumber(i) + if v < min { + min = v + } + } + L.Push(min) + return 1 +} + +func mathMod(L *LState) int { + lhs := L.CheckNumber(1) + rhs := L.CheckNumber(2) + L.Push(luaModulo(lhs, rhs)) + return 1 +} + +func mathModf(L *LState) int { + v1, v2 := math.Modf(float64(L.CheckNumber(1))) + L.Push(LNumber(v1)) + L.Push(LNumber(v2)) + return 2 +} + +func mathPow(L *LState) int { + L.Push(LNumber(math.Pow(float64(L.CheckNumber(1)), float64(L.CheckNumber(2))))) + return 1 +} + +func mathRad(L *LState) int { + L.Push(LNumber(float64(L.CheckNumber(1)) * math.Pi / 180)) + return 1 +} + +func mathRandom(L *LState) int { + switch L.GetTop() { + case 0: + L.Push(LNumber(rand.Float64())) + case 1: + n := L.CheckInt(1) + L.Push(LNumber(rand.Intn(n-1) + 1)) + default: + min := L.CheckInt(1) + max := L.CheckInt(2) + 1 + L.Push(LNumber(rand.Intn(max-min) + min)) + } + return 1 +} + +func mathRandomseed(L *LState) int { + rand.Seed(L.CheckInt64(1)) + return 0 +} + +func mathSin(L *LState) int { + L.Push(LNumber(math.Sin(float64(L.CheckNumber(1))))) + return 1 +} + +func mathSinh(L *LState) int { + L.Push(LNumber(math.Sinh(float64(L.CheckNumber(1))))) + return 1 +} + +func mathSqrt(L *LState) int { + L.Push(LNumber(math.Sqrt(float64(L.CheckNumber(1))))) + return 1 +} + +func mathTan(L *LState) int { + L.Push(LNumber(math.Tan(float64(L.CheckNumber(1))))) + return 1 +} + +func mathTanh(L *LState) int { + L.Push(LNumber(math.Tanh(float64(L.CheckNumber(1))))) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/opcode.go b/vendor/github.com/yuin/gopher-lua/opcode.go new file mode 100644 index 0000000..91fff1c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/opcode.go @@ -0,0 +1,371 @@ +package lua + +import ( + "fmt" +) + +/* + gopherlua uses Lua 5.1.4's opcodes. + Lua 5.1.4 opcodes layout: + + instruction = 32bit(fixed length) + + +---------------------------------------------+ + |0-5(6bits)|6-13(8bit)|14-22(9bit)|23-31(9bit)| + |==========+==========+===========+===========| + | opcode | A | C | B | + |----------+----------+-----------+-----------| + | opcode | A | Bx(unsigned) | + |----------+----------+-----------+-----------| + | opcode | A | sBx(signed) | + +---------------------------------------------+ +*/ + +const opInvalidInstruction = ^uint32(0) + +const opSizeCode = 6 +const opSizeA = 8 +const opSizeB = 9 +const opSizeC = 9 +const opSizeBx = 18 +const opSizesBx = 18 + +const opMaxArgsA = (1 << opSizeA) - 1 +const opMaxArgsB = (1 << opSizeB) - 1 +const opMaxArgsC = (1 << opSizeC) - 1 +const opMaxArgBx = (1 << opSizeBx) - 1 +const opMaxArgSbx = opMaxArgBx >> 1 + +const ( + OP_MOVE int = iota /* A B R(A) := R(B) */ + OP_MOVEN /* A B R(A) := R(B); followed by R(C) MOVE ops */ + OP_LOADK /* A Bx R(A) := Kst(Bx) */ + OP_LOADBOOL /* A B C R(A) := (Bool)B; if (C) pc++ */ + OP_LOADNIL /* A B R(A) := ... := R(B) := nil */ + OP_GETUPVAL /* A B R(A) := UpValue[B] */ + + OP_GETGLOBAL /* A Bx R(A) := Gbl[Kst(Bx)] */ + OP_GETTABLE /* A B C R(A) := R(B)[RK(C)] */ + OP_GETTABLEKS /* A B C R(A) := R(B)[RK(C)] ; RK(C) is constant string */ + + OP_SETGLOBAL /* A Bx Gbl[Kst(Bx)] := R(A) */ + OP_SETUPVAL /* A B UpValue[B] := R(A) */ + OP_SETTABLE /* A B C R(A)[RK(B)] := RK(C) */ + OP_SETTABLEKS /* A B C R(A)[RK(B)] := RK(C) ; RK(B) is constant string */ + + OP_NEWTABLE /* A B C R(A) := {} (size = BC) */ + + OP_SELF /* A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] */ + + OP_ADD /* A B C R(A) := RK(B) + RK(C) */ + OP_SUB /* A B C R(A) := RK(B) - RK(C) */ + OP_MUL /* A B C R(A) := RK(B) * RK(C) */ + OP_DIV /* A B C R(A) := RK(B) / RK(C) */ + OP_MOD /* A B C R(A) := RK(B) % RK(C) */ + OP_POW /* A B C R(A) := RK(B) ^ RK(C) */ + OP_UNM /* A B R(A) := -R(B) */ + OP_NOT /* A B R(A) := not R(B) */ + OP_LEN /* A B R(A) := length of R(B) */ + + OP_CONCAT /* A B C R(A) := R(B).. ... ..R(C) */ + + OP_JMP /* sBx pc+=sBx */ + + OP_EQ /* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ + OP_LT /* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ + OP_LE /* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ + + OP_TEST /* A C if not (R(A) <=> C) then pc++ */ + OP_TESTSET /* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ + + OP_CALL /* A B C R(A) ... R(A+C-2) := R(A)(R(A+1) ... R(A+B-1)) */ + OP_TAILCALL /* A B C return R(A)(R(A+1) ... R(A+B-1)) */ + OP_RETURN /* A B return R(A) ... R(A+B-2) (see note) */ + + OP_FORLOOP /* A sBx R(A)+=R(A+2); + if R(A) =) R(A)*/ + OP_CLOSURE /* A Bx R(A) := closure(KPROTO[Bx] R(A) ... R(A+n)) */ + + OP_VARARG /* A B R(A) R(A+1) ... R(A+B-1) = vararg */ + + OP_NOP /* NOP */ +) +const opCodeMax = OP_NOP + +type opArgMode int + +const ( + opArgModeN opArgMode = iota + opArgModeU + opArgModeR + opArgModeK +) + +type opType int + +const ( + opTypeABC = iota + opTypeABx + opTypeASbx +) + +type opProp struct { + Name string + IsTest bool + SetRegA bool + ModeArgB opArgMode + ModeArgC opArgMode + Type opType +} + +var opProps = []opProp{ + opProp{"MOVE", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"MOVEN", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"LOADK", false, true, opArgModeK, opArgModeN, opTypeABx}, + opProp{"LOADBOOL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"LOADNIL", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"GETUPVAL", false, true, opArgModeU, opArgModeN, opTypeABC}, + opProp{"GETGLOBAL", false, true, opArgModeK, opArgModeN, opTypeABx}, + opProp{"GETTABLE", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"GETTABLEKS", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"SETGLOBAL", false, false, opArgModeK, opArgModeN, opTypeABx}, + opProp{"SETUPVAL", false, false, opArgModeU, opArgModeN, opTypeABC}, + opProp{"SETTABLE", false, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"SETTABLEKS", false, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"NEWTABLE", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"SELF", false, true, opArgModeR, opArgModeK, opTypeABC}, + opProp{"ADD", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"SUB", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"MUL", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"DIV", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"MOD", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"POW", false, true, opArgModeK, opArgModeK, opTypeABC}, + opProp{"UNM", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"NOT", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"LEN", false, true, opArgModeR, opArgModeN, opTypeABC}, + opProp{"CONCAT", false, true, opArgModeR, opArgModeR, opTypeABC}, + opProp{"JMP", false, false, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"EQ", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"LT", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"LE", true, false, opArgModeK, opArgModeK, opTypeABC}, + opProp{"TEST", true, true, opArgModeR, opArgModeU, opTypeABC}, + opProp{"TESTSET", true, true, opArgModeR, opArgModeU, opTypeABC}, + opProp{"CALL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"TAILCALL", false, true, opArgModeU, opArgModeU, opTypeABC}, + opProp{"RETURN", false, false, opArgModeU, opArgModeN, opTypeABC}, + opProp{"FORLOOP", false, true, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"FORPREP", false, true, opArgModeR, opArgModeN, opTypeASbx}, + opProp{"TFORLOOP", true, false, opArgModeN, opArgModeU, opTypeABC}, + opProp{"SETLIST", false, false, opArgModeU, opArgModeU, opTypeABC}, + opProp{"CLOSE", false, false, opArgModeN, opArgModeN, opTypeABC}, + opProp{"CLOSURE", false, true, opArgModeU, opArgModeN, opTypeABx}, + opProp{"VARARG", false, true, opArgModeU, opArgModeN, opTypeABC}, + opProp{"NOP", false, false, opArgModeR, opArgModeN, opTypeASbx}, +} + +func opGetOpCode(inst uint32) int { + return int(inst >> 26) +} + +func opSetOpCode(inst *uint32, opcode int) { + *inst = (*inst & 0x3ffffff) | uint32(opcode<<26) +} + +func opGetArgA(inst uint32) int { + return int(inst>>18) & 0xff +} + +func opSetArgA(inst *uint32, arg int) { + *inst = (*inst & 0xfc03ffff) | uint32((arg&0xff)<<18) +} + +func opGetArgB(inst uint32) int { + return int(inst & 0x1ff) +} + +func opSetArgB(inst *uint32, arg int) { + *inst = (*inst & 0xfffffe00) | uint32(arg&0x1ff) +} + +func opGetArgC(inst uint32) int { + return int(inst>>9) & 0x1ff +} + +func opSetArgC(inst *uint32, arg int) { + *inst = (*inst & 0xfffc01ff) | uint32((arg&0x1ff)<<9) +} + +func opGetArgBx(inst uint32) int { + return int(inst & 0x3ffff) +} + +func opSetArgBx(inst *uint32, arg int) { + *inst = (*inst & 0xfffc0000) | uint32(arg&0x3ffff) +} + +func opGetArgSbx(inst uint32) int { + return opGetArgBx(inst) - opMaxArgSbx +} + +func opSetArgSbx(inst *uint32, arg int) { + opSetArgBx(inst, arg+opMaxArgSbx) +} + +func opCreateABC(op int, a int, b int, c int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgB(&inst, b) + opSetArgC(&inst, c) + return inst +} + +func opCreateABx(op int, a int, bx int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgBx(&inst, bx) + return inst +} + +func opCreateASbx(op int, a int, sbx int) uint32 { + var inst uint32 = 0 + opSetOpCode(&inst, op) + opSetArgA(&inst, a) + opSetArgSbx(&inst, sbx) + return inst +} + +const opBitRk = 1 << (opSizeB - 1) +const opMaxIndexRk = opBitRk - 1 + +func opIsK(value int) bool { + return bool((value & opBitRk) != 0) +} + +func opIndexK(value int) int { + return value & ^opBitRk +} + +func opRkAsk(value int) int { + return value | opBitRk +} + +func opToString(inst uint32) string { + op := opGetOpCode(inst) + if op > opCodeMax { + return "" + } + prop := &(opProps[op]) + + arga := opGetArgA(inst) + argb := opGetArgB(inst) + argc := opGetArgC(inst) + argbx := opGetArgBx(inst) + argsbx := opGetArgSbx(inst) + + buf := "" + switch prop.Type { + case opTypeABC: + buf = fmt.Sprintf("%s | %d, %d, %d", prop.Name, arga, argb, argc) + case opTypeABx: + buf = fmt.Sprintf("%s | %d, %d", prop.Name, arga, argbx) + case opTypeASbx: + buf = fmt.Sprintf("%s | %d, %d", prop.Name, arga, argsbx) + } + + switch op { + case OP_MOVE: + buf += fmt.Sprintf("; R(%v) := R(%v)", arga, argb) + case OP_MOVEN: + buf += fmt.Sprintf("; R(%v) := R(%v); followed by %v MOVE ops", arga, argb, argc) + case OP_LOADK: + buf += fmt.Sprintf("; R(%v) := Kst(%v)", arga, argbx) + case OP_LOADBOOL: + buf += fmt.Sprintf("; R(%v) := (Bool)%v; if (%v) pc++", arga, argb, argc) + case OP_LOADNIL: + buf += fmt.Sprintf("; R(%v) := ... := R(%v) := nil", arga, argb) + case OP_GETUPVAL: + buf += fmt.Sprintf("; R(%v) := UpValue[%v]", arga, argb) + case OP_GETGLOBAL: + buf += fmt.Sprintf("; R(%v) := Gbl[Kst(%v)]", arga, argbx) + case OP_GETTABLE: + buf += fmt.Sprintf("; R(%v) := R(%v)[RK(%v)]", arga, argb, argc) + case OP_GETTABLEKS: + buf += fmt.Sprintf("; R(%v) := R(%v)[RK(%v)] ; RK(%v) is constant string", arga, argb, argc, argc) + case OP_SETGLOBAL: + buf += fmt.Sprintf("; Gbl[Kst(%v)] := R(%v)", argbx, arga) + case OP_SETUPVAL: + buf += fmt.Sprintf("; UpValue[%v] := R(%v)", argb, arga) + case OP_SETTABLE: + buf += fmt.Sprintf("; R(%v)[RK(%v)] := RK(%v)", arga, argb, argc) + case OP_SETTABLEKS: + buf += fmt.Sprintf("; R(%v)[RK(%v)] := RK(%v) ; RK(%v) is constant string", arga, argb, argc, argb) + case OP_NEWTABLE: + buf += fmt.Sprintf("; R(%v) := {} (size = BC)", arga) + case OP_SELF: + buf += fmt.Sprintf("; R(%v+1) := R(%v); R(%v) := R(%v)[RK(%v)]", arga, argb, arga, argb, argc) + case OP_ADD: + buf += fmt.Sprintf("; R(%v) := RK(%v) + RK(%v)", arga, argb, argc) + case OP_SUB: + buf += fmt.Sprintf("; R(%v) := RK(%v) - RK(%v)", arga, argb, argc) + case OP_MUL: + buf += fmt.Sprintf("; R(%v) := RK(%v) * RK(%v)", arga, argb, argc) + case OP_DIV: + buf += fmt.Sprintf("; R(%v) := RK(%v) / RK(%v)", arga, argb, argc) + case OP_MOD: + buf += fmt.Sprintf("; R(%v) := RK(%v) %% RK(%v)", arga, argb, argc) + case OP_POW: + buf += fmt.Sprintf("; R(%v) := RK(%v) ^ RK(%v)", arga, argb, argc) + case OP_UNM: + buf += fmt.Sprintf("; R(%v) := -R(%v)", arga, argb) + case OP_NOT: + buf += fmt.Sprintf("; R(%v) := not R(%v)", arga, argb) + case OP_LEN: + buf += fmt.Sprintf("; R(%v) := length of R(%v)", arga, argb) + case OP_CONCAT: + buf += fmt.Sprintf("; R(%v) := R(%v).. ... ..R(%v)", arga, argb, argc) + case OP_JMP: + buf += fmt.Sprintf("; pc+=%v", argsbx) + case OP_EQ: + buf += fmt.Sprintf("; if ((RK(%v) == RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_LT: + buf += fmt.Sprintf("; if ((RK(%v) < RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_LE: + buf += fmt.Sprintf("; if ((RK(%v) <= RK(%v)) ~= %v) then pc++", argb, argc, arga) + case OP_TEST: + buf += fmt.Sprintf("; if not (R(%v) <=> %v) then pc++", arga, argc) + case OP_TESTSET: + buf += fmt.Sprintf("; if (R(%v) <=> %v) then R(%v) := R(%v) else pc++", argb, argc, arga, argb) + case OP_CALL: + buf += fmt.Sprintf("; R(%v) ... R(%v+%v-2) := R(%v)(R(%v+1) ... R(%v+%v-1))", arga, arga, argc, arga, arga, arga, argb) + case OP_TAILCALL: + buf += fmt.Sprintf("; return R(%v)(R(%v+1) ... R(%v+%v-1))", arga, arga, arga, argb) + case OP_RETURN: + buf += fmt.Sprintf("; return R(%v) ... R(%v+%v-2)", arga, arga, argb) + case OP_FORLOOP: + buf += fmt.Sprintf("; R(%v)+=R(%v+2); if R(%v) =) R(%v)", arga) + case OP_CLOSURE: + buf += fmt.Sprintf("; R(%v) := closure(KPROTO[%v] R(%v) ... R(%v+n))", arga, argbx, arga, arga) + case OP_VARARG: + buf += fmt.Sprintf("; R(%v) R(%v+1) ... R(%v+%v-1) = vararg", arga, arga, arga, argb) + case OP_NOP: + /* nothing to do */ + } + return buf +} diff --git a/vendor/github.com/yuin/gopher-lua/oslib.go b/vendor/github.com/yuin/gopher-lua/oslib.go new file mode 100644 index 0000000..e186568 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/oslib.go @@ -0,0 +1,206 @@ +package lua + +import ( + "io/ioutil" + "os" + "strings" + "time" +) + +var startedAt time.Time + +func init() { + startedAt = time.Now() +} + +func getIntField(L *LState, tb *LTable, key string, v int) int { + ret := tb.RawGetString(key) + if ln, ok := ret.(LNumber); ok { + return int(ln) + } + return v +} + +func getBoolField(L *LState, tb *LTable, key string, v bool) bool { + ret := tb.RawGetString(key) + if lb, ok := ret.(LBool); ok { + return bool(lb) + } + return v +} + +func OpenOs(L *LState) int { + osmod := L.RegisterModule(OsLibName, osFuncs) + L.Push(osmod) + return 1 +} + +var osFuncs = map[string]LGFunction{ + "clock": osClock, + "difftime": osDiffTime, + "execute": osExecute, + "exit": osExit, + "date": osDate, + "getenv": osGetEnv, + "remove": osRemove, + "rename": osRename, + "setenv": osSetEnv, + "setlocale": osSetLocale, + "time": osTime, + "tmpname": osTmpname, +} + +func osClock(L *LState) int { + L.Push(LNumber(float64(time.Now().Sub(startedAt)) / float64(time.Second))) + return 1 +} + +func osDiffTime(L *LState) int { + L.Push(LNumber(L.CheckInt64(1) - L.CheckInt64(2))) + return 1 +} + +func osExecute(L *LState) int { + var procAttr os.ProcAttr + procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr} + cmd, args := popenArgs(L.CheckString(1)) + args = append([]string{cmd}, args...) + process, err := os.StartProcess(cmd, args, &procAttr) + if err != nil { + L.Push(LNumber(1)) + return 1 + } + + ps, err := process.Wait() + if err != nil || !ps.Success() { + L.Push(LNumber(1)) + return 1 + } + L.Push(LNumber(0)) + return 1 +} + +func osExit(L *LState) int { + L.Close() + os.Exit(L.OptInt(1, 0)) + return 1 +} + +func osDate(L *LState) int { + t := time.Now() + cfmt := "%c" + if L.GetTop() >= 1 { + cfmt = L.CheckString(1) + if strings.HasPrefix(cfmt, "!") { + t = time.Now().UTC() + cfmt = strings.TrimLeft(cfmt, "!") + } + if L.GetTop() >= 2 { + t = time.Unix(L.CheckInt64(2), 0) + } + if strings.HasPrefix(cfmt, "*t") { + ret := L.NewTable() + ret.RawSetString("year", LNumber(t.Year())) + ret.RawSetString("month", LNumber(t.Month())) + ret.RawSetString("day", LNumber(t.Day())) + ret.RawSetString("hour", LNumber(t.Hour())) + ret.RawSetString("min", LNumber(t.Minute())) + ret.RawSetString("sec", LNumber(t.Second())) + ret.RawSetString("wday", LNumber(t.Weekday()+1)) + // TODO yday & dst + ret.RawSetString("yday", LNumber(0)) + ret.RawSetString("isdst", LFalse) + L.Push(ret) + return 1 + } + } + L.Push(LString(strftime(t, cfmt))) + return 1 +} + +func osGetEnv(L *LState) int { + v := os.Getenv(L.CheckString(1)) + if len(v) == 0 { + L.Push(LNil) + } else { + L.Push(LString(v)) + } + return 1 +} + +func osRemove(L *LState) int { + err := os.Remove(L.CheckString(1)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +func osRename(L *LState) int { + err := os.Rename(L.CheckString(1), L.CheckString(2)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +func osSetLocale(L *LState) int { + // setlocale is not supported + L.Push(LFalse) + return 1 +} + +func osSetEnv(L *LState) int { + err := os.Setenv(L.CheckString(1), L.CheckString(2)) + if err != nil { + L.Push(LNil) + L.Push(LString(err.Error())) + return 2 + } else { + L.Push(LTrue) + return 1 + } +} + +func osTime(L *LState) int { + if L.GetTop() == 0 { + L.Push(LNumber(time.Now().Unix())) + } else { + tbl := L.CheckTable(1) + sec := getIntField(L, tbl, "sec", 0) + min := getIntField(L, tbl, "min", 0) + hour := getIntField(L, tbl, "hour", 12) + day := getIntField(L, tbl, "day", -1) + month := getIntField(L, tbl, "month", -1) + year := getIntField(L, tbl, "year", -1) + isdst := getBoolField(L, tbl, "isdst", false) + t := time.Date(year, time.Month(month), day, hour, min, sec, 0, time.Local) + // TODO dst + if false { + print(isdst) + } + L.Push(LNumber(t.Unix())) + } + return 1 +} + +func osTmpname(L *LState) int { + file, err := ioutil.TempFile("", "") + if err != nil { + L.RaiseError("unable to generate a unique filename") + } + file.Close() + os.Remove(file.Name()) // ignore errors + L.Push(LString(file.Name())) + return 1 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/package.go b/vendor/github.com/yuin/gopher-lua/package.go new file mode 100644 index 0000000..9fde3f0 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/package.go @@ -0,0 +1,7 @@ +// GopherLua: VM and compiler for Lua in Go +package lua + +const PackageName = "GopherLua" +const PackageVersion = "0.1" +const PackageAuthors = "Yusuke Inuzuka" +const PackageCopyRight = PackageName + " " + PackageVersion + " Copyright (C) 2015 -2017 " + PackageAuthors diff --git a/vendor/github.com/yuin/gopher-lua/parse/Makefile b/vendor/github.com/yuin/gopher-lua/parse/Makefile new file mode 100644 index 0000000..6dd048c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/Makefile @@ -0,0 +1,4 @@ +all : parser.go + +parser.go : parser.go.y + goyacc -o $@ parser.go.y; [ -f y.output ] && ( rm -f y.output ) diff --git a/vendor/github.com/yuin/gopher-lua/parse/lexer.go b/vendor/github.com/yuin/gopher-lua/parse/lexer.go new file mode 100644 index 0000000..4e54b7c --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/lexer.go @@ -0,0 +1,537 @@ +package parse + +import ( + "bufio" + "bytes" + "fmt" + "github.com/yuin/gopher-lua/ast" + "io" + "reflect" + "strconv" + "strings" +) + +const EOF = -1 +const whitespace1 = 1<<'\t' | 1<<' ' +const whitespace2 = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' ' + +type Error struct { + Pos ast.Position + Message string + Token string +} + +func (e *Error) Error() string { + pos := e.Pos + if pos.Line == EOF { + return fmt.Sprintf("%v at EOF: %s\n", pos.Source, e.Message) + } else { + return fmt.Sprintf("%v line:%d(column:%d) near '%v': %s\n", pos.Source, pos.Line, pos.Column, e.Token, e.Message) + } +} + +func writeChar(buf *bytes.Buffer, c int) { buf.WriteByte(byte(c)) } + +func isDecimal(ch int) bool { return '0' <= ch && ch <= '9' } + +func isIdent(ch int, pos int) bool { + return ch == '_' || 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' || isDecimal(ch) && pos > 0 +} + +func isDigit(ch int) bool { + return '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F' +} + +type Scanner struct { + Pos ast.Position + reader *bufio.Reader +} + +func NewScanner(reader io.Reader, source string) *Scanner { + return &Scanner{ + Pos: ast.Position{source, 1, 0}, + reader: bufio.NewReaderSize(reader, 4096), + } +} + +func (sc *Scanner) Error(tok string, msg string) *Error { return &Error{sc.Pos, msg, tok} } + +func (sc *Scanner) TokenError(tok ast.Token, msg string) *Error { return &Error{tok.Pos, msg, tok.Str} } + +func (sc *Scanner) readNext() int { + ch, err := sc.reader.ReadByte() + if err == io.EOF { + return EOF + } + return int(ch) +} + +func (sc *Scanner) Newline(ch int) { + if ch < 0 { + return + } + sc.Pos.Line += 1 + sc.Pos.Column = 0 + next := sc.Peek() + if ch == '\n' && next == '\r' || ch == '\r' && next == '\n' { + sc.reader.ReadByte() + } +} + +func (sc *Scanner) Next() int { + ch := sc.readNext() + switch ch { + case '\n', '\r': + sc.Newline(ch) + ch = int('\n') + case EOF: + sc.Pos.Line = EOF + sc.Pos.Column = 0 + default: + sc.Pos.Column++ + } + return ch +} + +func (sc *Scanner) Peek() int { + ch := sc.readNext() + if ch != EOF { + sc.reader.UnreadByte() + } + return ch +} + +func (sc *Scanner) skipWhiteSpace(whitespace int64) int { + ch := sc.Next() + for ; whitespace&(1<': + if sc.Peek() == '=' { + tok.Type = TGte + tok.Str = ">=" + sc.Next() + } else { + tok.Type = ch + tok.Str = string(ch) + } + case '.': + ch2 := sc.Peek() + switch { + case isDecimal(ch2): + tok.Type = TNumber + err = sc.scanNumber(ch, buf) + tok.Str = buf.String() + case ch2 == '.': + writeChar(buf, ch) + writeChar(buf, sc.Next()) + if sc.Peek() == '.' { + writeChar(buf, sc.Next()) + tok.Type = T3Comma + } else { + tok.Type = T2Comma + } + default: + tok.Type = '.' + } + tok.Str = buf.String() + case '+', '*', '/', '%', '^', '#', '(', ')', '{', '}', ']', ';', ':', ',': + tok.Type = ch + tok.Str = string(ch) + default: + writeChar(buf, ch) + err = sc.Error(buf.String(), "Invalid token") + goto finally + } + } + +finally: + tok.Name = TokenName(int(tok.Type)) + return tok, err +} + +// yacc interface {{{ + +type Lexer struct { + scanner *Scanner + Stmts []ast.Stmt + PNewLine bool + Token ast.Token + PrevTokenType int +} + +func (lx *Lexer) Lex(lval *yySymType) int { + lx.PrevTokenType = lx.Token.Type + tok, err := lx.scanner.Scan(lx) + if err != nil { + panic(err) + } + if tok.Type < 0 { + return 0 + } + lval.token = tok + lx.Token = tok + return int(tok.Type) +} + +func (lx *Lexer) Error(message string) { + panic(lx.scanner.Error(lx.Token.Str, message)) +} + +func (lx *Lexer) TokenError(tok ast.Token, message string) { + panic(lx.scanner.TokenError(tok, message)) +} + +func Parse(reader io.Reader, name string) (chunk []ast.Stmt, err error) { + lexer := &Lexer{NewScanner(reader, name), nil, false, ast.Token{Str: ""}, TNil} + chunk = nil + defer func() { + if e := recover(); e != nil { + err, _ = e.(error) + } + }() + yyParse(lexer) + chunk = lexer.Stmts + return +} + +// }}} + +// Dump {{{ + +func isInlineDumpNode(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Struct, reflect.Slice, reflect.Interface, reflect.Ptr: + return false + default: + return true + } +} + +func dump(node interface{}, level int, s string) string { + rt := reflect.TypeOf(node) + if fmt.Sprint(rt) == "" { + return strings.Repeat(s, level) + "" + } + + rv := reflect.ValueOf(node) + buf := []string{} + switch rt.Kind() { + case reflect.Slice: + if rv.Len() == 0 { + return strings.Repeat(s, level) + "" + } + for i := 0; i < rv.Len(); i++ { + buf = append(buf, dump(rv.Index(i).Interface(), level, s)) + } + case reflect.Ptr: + vt := rv.Elem() + tt := rt.Elem() + indicies := []int{} + for i := 0; i < tt.NumField(); i++ { + if strings.Index(tt.Field(i).Name, "Base") > -1 { + continue + } + indicies = append(indicies, i) + } + switch { + case len(indicies) == 0: + return strings.Repeat(s, level) + "" + case len(indicies) == 1 && isInlineDumpNode(vt.Field(indicies[0])): + for _, i := range indicies { + buf = append(buf, strings.Repeat(s, level)+"- Node$"+tt.Name()+": "+dump(vt.Field(i).Interface(), 0, s)) + } + default: + buf = append(buf, strings.Repeat(s, level)+"- Node$"+tt.Name()) + for _, i := range indicies { + if isInlineDumpNode(vt.Field(i)) { + inf := dump(vt.Field(i).Interface(), 0, s) + buf = append(buf, strings.Repeat(s, level+1)+tt.Field(i).Name+": "+inf) + } else { + buf = append(buf, strings.Repeat(s, level+1)+tt.Field(i).Name+": ") + buf = append(buf, dump(vt.Field(i).Interface(), level+2, s)) + } + } + } + default: + buf = append(buf, strings.Repeat(s, level)+fmt.Sprint(node)) + } + return strings.Join(buf, "\n") +} + +func Dump(chunk []ast.Stmt) string { + return dump(chunk, 0, " ") +} + +// }} diff --git a/vendor/github.com/yuin/gopher-lua/parse/parser.go b/vendor/github.com/yuin/gopher-lua/parse/parser.go new file mode 100644 index 0000000..f8f59b3 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/parser.go @@ -0,0 +1,1137 @@ +//line parser.go.y:2 +package parse + +import __yyfmt__ "fmt" + +//line parser.go.y:2 +import ( + "github.com/yuin/gopher-lua/ast" +) + +//line parser.go.y:34 +type yySymType struct { + yys int + token ast.Token + + stmts []ast.Stmt + stmt ast.Stmt + + funcname *ast.FuncName + funcexpr *ast.FunctionExpr + + exprlist []ast.Expr + expr ast.Expr + + fieldlist []*ast.Field + field *ast.Field + fieldsep string + + namelist []string + parlist *ast.ParList +} + +const TAnd = 57346 +const TBreak = 57347 +const TDo = 57348 +const TElse = 57349 +const TElseIf = 57350 +const TEnd = 57351 +const TFalse = 57352 +const TFor = 57353 +const TFunction = 57354 +const TIf = 57355 +const TIn = 57356 +const TLocal = 57357 +const TNil = 57358 +const TNot = 57359 +const TOr = 57360 +const TReturn = 57361 +const TRepeat = 57362 +const TThen = 57363 +const TTrue = 57364 +const TUntil = 57365 +const TWhile = 57366 +const TEqeq = 57367 +const TNeq = 57368 +const TLte = 57369 +const TGte = 57370 +const T2Comma = 57371 +const T3Comma = 57372 +const TIdent = 57373 +const TNumber = 57374 +const TString = 57375 +const UNARY = 57376 + +var yyToknames = []string{ + "TAnd", + "TBreak", + "TDo", + "TElse", + "TElseIf", + "TEnd", + "TFalse", + "TFor", + "TFunction", + "TIf", + "TIn", + "TLocal", + "TNil", + "TNot", + "TOr", + "TReturn", + "TRepeat", + "TThen", + "TTrue", + "TUntil", + "TWhile", + "TEqeq", + "TNeq", + "TLte", + "TGte", + "T2Comma", + "T3Comma", + "TIdent", + "TNumber", + "TString", + " {", + " (", + " >", + " <", + " +", + " -", + " *", + " /", + " %", + "UNARY", + " ^", +} +var yyStatenames = []string{} + +const yyEofCode = 1 +const yyErrCode = 2 +const yyMaxDepth = 200 + +//line parser.go.y:514 +func TokenName(c int) string { + if c >= TAnd && c-TAnd < len(yyToknames) { + if yyToknames[c-TAnd] != "" { + return yyToknames[c-TAnd] + } + } + return string([]byte{byte(c)}) +} + +//line yacctab:1 +var yyExca = []int{ + -1, 1, + 1, -1, + -2, 0, + -1, 17, + 46, 31, + 47, 31, + -2, 68, + -1, 93, + 46, 32, + 47, 32, + -2, 68, +} + +const yyNprod = 95 +const yyPrivate = 57344 + +var yyTokenNames []string +var yyStates []string + +const yyLast = 579 + +var yyAct = []int{ + + 24, 88, 50, 23, 45, 84, 56, 65, 137, 153, + 136, 113, 52, 142, 54, 53, 33, 134, 65, 132, + 62, 63, 32, 61, 108, 109, 48, 111, 106, 41, + 42, 105, 49, 155, 166, 81, 82, 83, 138, 104, + 22, 91, 131, 80, 95, 92, 162, 74, 48, 85, + 150, 99, 165, 148, 49, 149, 75, 76, 77, 78, + 79, 67, 80, 107, 106, 148, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 72, 73, 71, 70, 74, 65, 39, 40, + 47, 139, 133, 68, 69, 75, 76, 77, 78, 79, + 60, 80, 141, 144, 143, 146, 145, 31, 67, 147, + 9, 48, 110, 97, 48, 152, 151, 49, 38, 62, + 49, 17, 66, 77, 78, 79, 96, 80, 59, 72, + 73, 71, 70, 74, 154, 102, 91, 156, 55, 157, + 68, 69, 75, 76, 77, 78, 79, 21, 80, 187, + 94, 20, 26, 184, 37, 179, 163, 112, 25, 35, + 178, 93, 170, 172, 27, 171, 164, 173, 19, 159, + 175, 174, 29, 89, 28, 39, 40, 20, 182, 181, + 100, 34, 135, 183, 67, 39, 40, 47, 186, 64, + 51, 1, 90, 87, 36, 130, 86, 30, 66, 18, + 46, 44, 43, 8, 58, 72, 73, 71, 70, 74, + 57, 67, 168, 169, 167, 3, 68, 69, 75, 76, + 77, 78, 79, 160, 80, 66, 4, 2, 0, 0, + 0, 158, 72, 73, 71, 70, 74, 0, 0, 0, + 0, 0, 0, 68, 69, 75, 76, 77, 78, 79, + 26, 80, 37, 0, 0, 0, 25, 35, 140, 0, + 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, + 29, 21, 28, 39, 40, 20, 26, 0, 37, 34, + 0, 0, 25, 35, 0, 0, 0, 0, 27, 0, + 0, 0, 36, 98, 0, 0, 29, 89, 28, 39, + 40, 20, 26, 0, 37, 34, 0, 0, 25, 35, + 0, 0, 0, 0, 27, 67, 90, 176, 36, 0, + 0, 0, 29, 21, 28, 39, 40, 20, 0, 66, + 0, 34, 0, 0, 0, 0, 72, 73, 71, 70, + 74, 0, 67, 0, 36, 0, 0, 68, 69, 75, + 76, 77, 78, 79, 0, 80, 66, 0, 177, 0, + 0, 0, 0, 72, 73, 71, 70, 74, 0, 67, + 0, 185, 0, 0, 68, 69, 75, 76, 77, 78, + 79, 0, 80, 66, 0, 161, 0, 0, 0, 0, + 72, 73, 71, 70, 74, 0, 67, 0, 0, 0, + 0, 68, 69, 75, 76, 77, 78, 79, 0, 80, + 66, 0, 0, 180, 0, 0, 0, 72, 73, 71, + 70, 74, 0, 67, 0, 0, 0, 0, 68, 69, + 75, 76, 77, 78, 79, 0, 80, 66, 0, 0, + 103, 0, 0, 0, 72, 73, 71, 70, 74, 0, + 67, 0, 101, 0, 0, 68, 69, 75, 76, 77, + 78, 79, 0, 80, 66, 0, 0, 0, 0, 0, + 0, 72, 73, 71, 70, 74, 0, 67, 0, 0, + 0, 0, 68, 69, 75, 76, 77, 78, 79, 0, + 80, 66, 0, 0, 0, 0, 0, 0, 72, 73, + 71, 70, 74, 0, 0, 0, 0, 0, 0, 68, + 69, 75, 76, 77, 78, 79, 0, 80, 72, 73, + 71, 70, 74, 0, 0, 0, 0, 0, 0, 68, + 69, 75, 76, 77, 78, 79, 0, 80, 7, 10, + 0, 0, 0, 0, 14, 15, 13, 0, 16, 0, + 0, 0, 6, 12, 0, 0, 0, 11, 0, 0, + 0, 0, 0, 0, 21, 0, 0, 0, 20, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 5, +} +var yyPact = []int{ + + -1000, -1000, 533, -5, -1000, -1000, 292, -1000, -17, 152, + -1000, 292, -1000, 292, 107, 97, 88, -1000, -1000, -1000, + 292, -1000, -1000, -29, 473, -1000, -1000, -1000, -1000, -1000, + -1000, 152, -1000, -1000, 292, 292, 292, 14, -1000, -1000, + 142, 292, 116, 292, 95, -1000, 82, 240, -1000, -1000, + 171, -1000, 446, 112, 419, -7, 17, 14, -24, -1000, + 81, -19, -1000, 104, -42, 292, 292, 292, 292, 292, + 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, + 292, -1, -1, -1, -1000, -11, -1000, -37, -1000, -8, + 292, 473, -29, -1000, 152, 207, -1000, 55, -1000, -40, + -1000, -1000, 292, -1000, 292, 292, 34, -1000, 24, 19, + 14, 292, -1000, -1000, 473, 57, 493, 18, 18, 18, + 18, 18, 18, 18, 83, 83, -1, -1, -1, -1, + -44, -1000, -1000, -14, -1000, 266, -1000, -1000, 292, 180, + -1000, -1000, -1000, 160, 473, -1000, 338, 40, -1000, -1000, + -1000, -1000, -29, -1000, 157, 22, -1000, 473, -12, -1000, + 205, 292, -1000, 154, -1000, -1000, 292, -1000, -1000, 292, + 311, 151, -1000, 473, 146, 392, -1000, 292, -1000, -1000, + -1000, 144, 365, -1000, -1000, -1000, 140, -1000, +} +var yyPgo = []int{ + + 0, 190, 227, 2, 226, 223, 215, 210, 204, 203, + 118, 6, 3, 0, 22, 107, 168, 199, 4, 197, + 5, 195, 16, 193, 1, 182, +} +var yyR1 = []int{ + + 0, 1, 1, 1, 2, 2, 2, 3, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 10, 11, 11, 12, 12, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 14, 15, 15, + 15, 15, 17, 16, 16, 18, 18, 18, 18, 19, + 20, 20, 21, 21, 21, 22, 22, 23, 23, 23, + 24, 24, 24, 25, 25, +} +var yyR2 = []int{ + + 0, 1, 2, 3, 0, 2, 2, 1, 3, 1, + 3, 5, 4, 6, 8, 9, 11, 7, 3, 4, + 4, 2, 0, 5, 1, 2, 1, 1, 3, 1, + 3, 1, 3, 1, 4, 3, 1, 3, 1, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, + 1, 3, 3, 2, 4, 2, 3, 1, 1, 2, + 5, 4, 1, 1, 3, 2, 3, 1, 3, 2, + 3, 5, 1, 1, 1, +} +var yyChk = []int{ + + -1000, -1, -2, -6, -4, 45, 19, 5, -9, -15, + 6, 24, 20, 13, 11, 12, 15, -10, -17, -16, + 35, 31, 45, -12, -13, 16, 10, 22, 32, 30, + -19, -15, -14, -22, 39, 17, 52, 12, -10, 33, + 34, 46, 47, 50, 49, -18, 48, 35, -22, -14, + -3, -1, -13, -3, -13, 31, -11, -7, -8, 31, + 12, -11, 31, -13, -16, 47, 18, 4, 36, 37, + 28, 27, 25, 26, 29, 38, 39, 40, 41, 42, + 44, -13, -13, -13, -20, 35, 54, -23, -24, 31, + 50, -13, -12, -10, -15, -13, 31, 31, 53, -12, + 9, 6, 23, 21, 46, 14, 47, -20, 48, 49, + 31, 46, 53, 53, -13, -13, -13, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13, -13, -13, -13, + -21, 53, 30, -11, 54, -25, 47, 45, 46, -13, + 51, -18, 53, -3, -13, -3, -13, -12, 31, 31, + 31, -20, -12, 53, -3, 47, -24, -13, 51, 9, + -5, 47, 6, -3, 9, 30, 46, 9, 7, 8, + -13, -3, 9, -13, -3, -13, 6, 47, 9, 9, + 21, -3, -13, -3, 9, 6, -3, 9, +} +var yyDef = []int{ + + 4, -2, 1, 2, 5, 6, 24, 26, 0, 9, + 4, 0, 4, 0, 0, 0, 0, -2, 69, 70, + 0, 33, 3, 25, 38, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 0, 0, 0, 0, 68, 67, + 0, 0, 0, 0, 0, 73, 0, 0, 77, 78, + 0, 7, 0, 0, 0, 36, 0, 0, 27, 29, + 0, 21, 36, 0, 70, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 64, 65, 66, 79, 0, 85, 0, 87, 33, + 0, 92, 8, -2, 0, 0, 35, 0, 75, 0, + 10, 4, 0, 4, 0, 0, 0, 18, 0, 0, + 0, 0, 71, 72, 39, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 4, 82, 83, 86, 89, 93, 94, 0, 0, + 34, 74, 76, 0, 12, 22, 0, 0, 37, 28, + 30, 19, 20, 4, 0, 0, 88, 90, 0, 11, + 0, 0, 4, 0, 81, 84, 0, 13, 4, 0, + 0, 0, 80, 91, 0, 0, 4, 0, 17, 14, + 4, 0, 0, 23, 15, 4, 0, 16, +} +var yyTok1 = []int{ + + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 52, 3, 42, 3, 3, + 35, 53, 40, 38, 47, 39, 49, 41, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 48, 45, + 37, 46, 36, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 50, 3, 51, 44, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 34, 3, 54, +} +var yyTok2 = []int{ + + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 43, +} +var yyTok3 = []int{ + 0, +} + +//line yaccpar:1 + +/* parser for yacc output */ + +var yyDebug = 0 + +type yyLexer interface { + Lex(lval *yySymType) int + Error(s string) +} + +const yyFlag = -1000 + +func yyTokname(c int) string { + // 4 is TOKSTART above + if c >= 4 && c-4 < len(yyToknames) { + if yyToknames[c-4] != "" { + return yyToknames[c-4] + } + } + return __yyfmt__.Sprintf("tok-%v", c) +} + +func yyStatname(s int) string { + if s >= 0 && s < len(yyStatenames) { + if yyStatenames[s] != "" { + return yyStatenames[s] + } + } + return __yyfmt__.Sprintf("state-%v", s) +} + +func yylex1(lex yyLexer, lval *yySymType) int { + c := 0 + char := lex.Lex(lval) + if char <= 0 { + c = yyTok1[0] + goto out + } + if char < len(yyTok1) { + c = yyTok1[char] + goto out + } + if char >= yyPrivate { + if char < yyPrivate+len(yyTok2) { + c = yyTok2[char-yyPrivate] + goto out + } + } + for i := 0; i < len(yyTok3); i += 2 { + c = yyTok3[i+0] + if c == char { + c = yyTok3[i+1] + goto out + } + } + +out: + if c == 0 { + c = yyTok2[1] /* unknown char */ + } + if yyDebug >= 3 { + __yyfmt__.Printf("lex %s(%d)\n", yyTokname(c), uint(char)) + } + return c +} + +func yyParse(yylex yyLexer) int { + var yyn int + var yylval yySymType + var yyVAL yySymType + yyS := make([]yySymType, yyMaxDepth) + + Nerrs := 0 /* number of errors */ + Errflag := 0 /* error recovery flag */ + yystate := 0 + yychar := -1 + yyp := -1 + goto yystack + +ret0: + return 0 + +ret1: + return 1 + +yystack: + /* put a state and value onto the stack */ + if yyDebug >= 4 { + __yyfmt__.Printf("char %v in %v\n", yyTokname(yychar), yyStatname(yystate)) + } + + yyp++ + if yyp >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyS[yyp] = yyVAL + yyS[yyp].yys = yystate + +yynewstate: + yyn = yyPact[yystate] + if yyn <= yyFlag { + goto yydefault /* simple state */ + } + if yychar < 0 { + yychar = yylex1(yylex, &yylval) + } + yyn += yychar + if yyn < 0 || yyn >= yyLast { + goto yydefault + } + yyn = yyAct[yyn] + if yyChk[yyn] == yychar { /* valid shift */ + yychar = -1 + yyVAL = yylval + yystate = yyn + if Errflag > 0 { + Errflag-- + } + goto yystack + } + +yydefault: + /* default state action */ + yyn = yyDef[yystate] + if yyn == -2 { + if yychar < 0 { + yychar = yylex1(yylex, &yylval) + } + + /* look through exception table */ + xi := 0 + for { + if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + yyn = yyExca[xi+0] + if yyn < 0 || yyn == yychar { + break + } + } + yyn = yyExca[xi+1] + if yyn < 0 { + goto ret0 + } + } + if yyn == 0 { + /* error ... attempt to resume parsing */ + switch Errflag { + case 0: /* brand new error */ + yylex.Error("syntax error") + Nerrs++ + if yyDebug >= 1 { + __yyfmt__.Printf("%s", yyStatname(yystate)) + __yyfmt__.Printf(" saw %s\n", yyTokname(yychar)) + } + fallthrough + + case 1, 2: /* incompletely recovered error ... try again */ + Errflag = 3 + + /* find a state where "error" is a legal shift action */ + for yyp >= 0 { + yyn = yyPact[yyS[yyp].yys] + yyErrCode + if yyn >= 0 && yyn < yyLast { + yystate = yyAct[yyn] /* simulate a shift of "error" */ + if yyChk[yystate] == yyErrCode { + goto yystack + } + } + + /* the current p has no shift on "error", pop stack */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) + } + yyp-- + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1 + + case 3: /* no shift yet; clobber input char */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yychar)) + } + if yychar == yyEofCode { + goto ret1 + } + yychar = -1 + goto yynewstate /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if yyDebug >= 2 { + __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) + } + + yynt := yyn + yypt := yyp + _ = yypt // guard against "declared and not used" + + yyp -= yyR2[yyn] + yyVAL = yyS[yyp+1] + + /* consult goto table to find next state */ + yyn = yyR1[yyn] + yyg := yyPgo[yyn] + yyj := yyg + yyS[yyp].yys + 1 + + if yyj >= yyLast { + yystate = yyAct[yyg] + } else { + yystate = yyAct[yyj] + if yyChk[yystate] != -yyn { + yystate = yyAct[yyg] + } + } + // dummy call; replaced with literal code + switch yynt { + + case 1: + //line parser.go.y:73 + { + yyVAL.stmts = yyS[yypt-0].stmts + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 2: + //line parser.go.y:79 + { + yyVAL.stmts = append(yyS[yypt-1].stmts, yyS[yypt-0].stmt) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 3: + //line parser.go.y:85 + { + yyVAL.stmts = append(yyS[yypt-2].stmts, yyS[yypt-1].stmt) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = yyVAL.stmts + } + } + case 4: + //line parser.go.y:93 + { + yyVAL.stmts = []ast.Stmt{} + } + case 5: + //line parser.go.y:96 + { + yyVAL.stmts = append(yyS[yypt-1].stmts, yyS[yypt-0].stmt) + } + case 6: + //line parser.go.y:99 + { + yyVAL.stmts = yyS[yypt-1].stmts + } + case 7: + //line parser.go.y:104 + { + yyVAL.stmts = yyS[yypt-0].stmts + } + case 8: + //line parser.go.y:109 + { + yyVAL.stmt = &ast.AssignStmt{Lhs: yyS[yypt-2].exprlist, Rhs: yyS[yypt-0].exprlist} + yyVAL.stmt.SetLine(yyS[yypt-2].exprlist[0].Line()) + } + case 9: + //line parser.go.y:114 + { + if _, ok := yyS[yypt-0].expr.(*ast.FuncCallExpr); !ok { + yylex.(*Lexer).Error("parse error") + } else { + yyVAL.stmt = &ast.FuncCallStmt{Expr: yyS[yypt-0].expr} + yyVAL.stmt.SetLine(yyS[yypt-0].expr.Line()) + } + } + case 10: + //line parser.go.y:122 + { + yyVAL.stmt = &ast.DoBlockStmt{Stmts: yyS[yypt-1].stmts} + yyVAL.stmt.SetLine(yyS[yypt-2].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 11: + //line parser.go.y:127 + { + yyVAL.stmt = &ast.WhileStmt{Condition: yyS[yypt-3].expr, Stmts: yyS[yypt-1].stmts} + yyVAL.stmt.SetLine(yyS[yypt-4].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 12: + //line parser.go.y:132 + { + yyVAL.stmt = &ast.RepeatStmt{Condition: yyS[yypt-0].expr, Stmts: yyS[yypt-2].stmts} + yyVAL.stmt.SetLine(yyS[yypt-3].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].expr.Line()) + } + case 13: + //line parser.go.y:137 + { + yyVAL.stmt = &ast.IfStmt{Condition: yyS[yypt-4].expr, Then: yyS[yypt-2].stmts} + cur := yyVAL.stmt + for _, elseif := range yyS[yypt-1].stmts { + cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} + cur = elseif + } + yyVAL.stmt.SetLine(yyS[yypt-5].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 14: + //line parser.go.y:147 + { + yyVAL.stmt = &ast.IfStmt{Condition: yyS[yypt-6].expr, Then: yyS[yypt-4].stmts} + cur := yyVAL.stmt + for _, elseif := range yyS[yypt-3].stmts { + cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} + cur = elseif + } + cur.(*ast.IfStmt).Else = yyS[yypt-1].stmts + yyVAL.stmt.SetLine(yyS[yypt-7].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 15: + //line parser.go.y:158 + { + yyVAL.stmt = &ast.NumberForStmt{Name: yyS[yypt-7].token.Str, Init: yyS[yypt-5].expr, Limit: yyS[yypt-3].expr, Stmts: yyS[yypt-1].stmts} + yyVAL.stmt.SetLine(yyS[yypt-8].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 16: + //line parser.go.y:163 + { + yyVAL.stmt = &ast.NumberForStmt{Name: yyS[yypt-9].token.Str, Init: yyS[yypt-7].expr, Limit: yyS[yypt-5].expr, Step: yyS[yypt-3].expr, Stmts: yyS[yypt-1].stmts} + yyVAL.stmt.SetLine(yyS[yypt-10].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 17: + //line parser.go.y:168 + { + yyVAL.stmt = &ast.GenericForStmt{Names: yyS[yypt-5].namelist, Exprs: yyS[yypt-3].exprlist, Stmts: yyS[yypt-1].stmts} + yyVAL.stmt.SetLine(yyS[yypt-6].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 18: + //line parser.go.y:173 + { + yyVAL.stmt = &ast.FuncDefStmt{Name: yyS[yypt-1].funcname, Func: yyS[yypt-0].funcexpr} + yyVAL.stmt.SetLine(yyS[yypt-2].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].funcexpr.LastLine()) + } + case 19: + //line parser.go.y:178 + { + yyVAL.stmt = &ast.LocalAssignStmt{Names: []string{yyS[yypt-1].token.Str}, Exprs: []ast.Expr{yyS[yypt-0].funcexpr}} + yyVAL.stmt.SetLine(yyS[yypt-3].token.Pos.Line) + yyVAL.stmt.SetLastLine(yyS[yypt-0].funcexpr.LastLine()) + } + case 20: + //line parser.go.y:183 + { + yyVAL.stmt = &ast.LocalAssignStmt{Names: yyS[yypt-2].namelist, Exprs: yyS[yypt-0].exprlist} + yyVAL.stmt.SetLine(yyS[yypt-3].token.Pos.Line) + } + case 21: + //line parser.go.y:187 + { + yyVAL.stmt = &ast.LocalAssignStmt{Names: yyS[yypt-0].namelist, Exprs: []ast.Expr{}} + yyVAL.stmt.SetLine(yyS[yypt-1].token.Pos.Line) + } + case 22: + //line parser.go.y:193 + { + yyVAL.stmts = []ast.Stmt{} + } + case 23: + //line parser.go.y:196 + { + yyVAL.stmts = append(yyS[yypt-4].stmts, &ast.IfStmt{Condition: yyS[yypt-2].expr, Then: yyS[yypt-0].stmts}) + yyVAL.stmts[len(yyVAL.stmts)-1].SetLine(yyS[yypt-3].token.Pos.Line) + } + case 24: + //line parser.go.y:202 + { + yyVAL.stmt = &ast.ReturnStmt{Exprs: nil} + yyVAL.stmt.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 25: + //line parser.go.y:206 + { + yyVAL.stmt = &ast.ReturnStmt{Exprs: yyS[yypt-0].exprlist} + yyVAL.stmt.SetLine(yyS[yypt-1].token.Pos.Line) + } + case 26: + //line parser.go.y:210 + { + yyVAL.stmt = &ast.BreakStmt{} + yyVAL.stmt.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 27: + //line parser.go.y:216 + { + yyVAL.funcname = yyS[yypt-0].funcname + } + case 28: + //line parser.go.y:219 + { + yyVAL.funcname = &ast.FuncName{Func: nil, Receiver: yyS[yypt-2].funcname.Func, Method: yyS[yypt-0].token.Str} + } + case 29: + //line parser.go.y:224 + { + yyVAL.funcname = &ast.FuncName{Func: &ast.IdentExpr{Value: yyS[yypt-0].token.Str}} + yyVAL.funcname.Func.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 30: + //line parser.go.y:228 + { + key := &ast.StringExpr{Value: yyS[yypt-0].token.Str} + key.SetLine(yyS[yypt-0].token.Pos.Line) + fn := &ast.AttrGetExpr{Object: yyS[yypt-2].funcname.Func, Key: key} + fn.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.funcname = &ast.FuncName{Func: fn} + } + case 31: + //line parser.go.y:237 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 32: + //line parser.go.y:240 + { + yyVAL.exprlist = append(yyS[yypt-2].exprlist, yyS[yypt-0].expr) + } + case 33: + //line parser.go.y:245 + { + yyVAL.expr = &ast.IdentExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 34: + //line parser.go.y:249 + { + yyVAL.expr = &ast.AttrGetExpr{Object: yyS[yypt-3].expr, Key: yyS[yypt-1].expr} + yyVAL.expr.SetLine(yyS[yypt-3].expr.Line()) + } + case 35: + //line parser.go.y:253 + { + key := &ast.StringExpr{Value: yyS[yypt-0].token.Str} + key.SetLine(yyS[yypt-0].token.Pos.Line) + yyVAL.expr = &ast.AttrGetExpr{Object: yyS[yypt-2].expr, Key: key} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 36: + //line parser.go.y:261 + { + yyVAL.namelist = []string{yyS[yypt-0].token.Str} + } + case 37: + //line parser.go.y:264 + { + yyVAL.namelist = append(yyS[yypt-2].namelist, yyS[yypt-0].token.Str) + } + case 38: + //line parser.go.y:269 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 39: + //line parser.go.y:272 + { + yyVAL.exprlist = append(yyS[yypt-2].exprlist, yyS[yypt-0].expr) + } + case 40: + //line parser.go.y:277 + { + yyVAL.expr = &ast.NilExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 41: + //line parser.go.y:281 + { + yyVAL.expr = &ast.FalseExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 42: + //line parser.go.y:285 + { + yyVAL.expr = &ast.TrueExpr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 43: + //line parser.go.y:289 + { + yyVAL.expr = &ast.NumberExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 44: + //line parser.go.y:293 + { + yyVAL.expr = &ast.Comma3Expr{} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 45: + //line parser.go.y:297 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 46: + //line parser.go.y:300 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 47: + //line parser.go.y:303 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 48: + //line parser.go.y:306 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 49: + //line parser.go.y:309 + { + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "or", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 50: + //line parser.go.y:313 + { + yyVAL.expr = &ast.LogicalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "and", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 51: + //line parser.go.y:317 + { + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: ">", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 52: + //line parser.go.y:321 + { + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "<", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 53: + //line parser.go.y:325 + { + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: ">=", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 54: + //line parser.go.y:329 + { + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "<=", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 55: + //line parser.go.y:333 + { + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "==", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 56: + //line parser.go.y:337 + { + yyVAL.expr = &ast.RelationalOpExpr{Lhs: yyS[yypt-2].expr, Operator: "~=", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 57: + //line parser.go.y:341 + { + yyVAL.expr = &ast.StringConcatOpExpr{Lhs: yyS[yypt-2].expr, Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 58: + //line parser.go.y:345 + { + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "+", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 59: + //line parser.go.y:349 + { + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "-", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 60: + //line parser.go.y:353 + { + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "*", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 61: + //line parser.go.y:357 + { + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "/", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 62: + //line parser.go.y:361 + { + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "%", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 63: + //line parser.go.y:365 + { + yyVAL.expr = &ast.ArithmeticOpExpr{Lhs: yyS[yypt-2].expr, Operator: "^", Rhs: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-2].expr.Line()) + } + case 64: + //line parser.go.y:369 + { + yyVAL.expr = &ast.UnaryMinusOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 65: + //line parser.go.y:373 + { + yyVAL.expr = &ast.UnaryNotOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 66: + //line parser.go.y:377 + { + yyVAL.expr = &ast.UnaryLenOpExpr{Expr: yyS[yypt-0].expr} + yyVAL.expr.SetLine(yyS[yypt-0].expr.Line()) + } + case 67: + //line parser.go.y:383 + { + yyVAL.expr = &ast.StringExpr{Value: yyS[yypt-0].token.Str} + yyVAL.expr.SetLine(yyS[yypt-0].token.Pos.Line) + } + case 68: + //line parser.go.y:389 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 69: + //line parser.go.y:392 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 70: + //line parser.go.y:395 + { + yyVAL.expr = yyS[yypt-0].expr + } + case 71: + //line parser.go.y:398 + { + yyVAL.expr = yyS[yypt-1].expr + yyVAL.expr.SetLine(yyS[yypt-2].token.Pos.Line) + } + case 72: + //line parser.go.y:404 + { + yyS[yypt-1].expr.(*ast.FuncCallExpr).AdjustRet = true + yyVAL.expr = yyS[yypt-1].expr + } + case 73: + //line parser.go.y:410 + { + yyVAL.expr = &ast.FuncCallExpr{Func: yyS[yypt-1].expr, Args: yyS[yypt-0].exprlist} + yyVAL.expr.SetLine(yyS[yypt-1].expr.Line()) + } + case 74: + //line parser.go.y:414 + { + yyVAL.expr = &ast.FuncCallExpr{Method: yyS[yypt-1].token.Str, Receiver: yyS[yypt-3].expr, Args: yyS[yypt-0].exprlist} + yyVAL.expr.SetLine(yyS[yypt-3].expr.Line()) + } + case 75: + //line parser.go.y:420 + { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError(yyS[yypt-1].token, "ambiguous syntax (function call x new statement)") + } + yyVAL.exprlist = []ast.Expr{} + } + case 76: + //line parser.go.y:426 + { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError(yyS[yypt-2].token, "ambiguous syntax (function call x new statement)") + } + yyVAL.exprlist = yyS[yypt-1].exprlist + } + case 77: + //line parser.go.y:432 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 78: + //line parser.go.y:435 + { + yyVAL.exprlist = []ast.Expr{yyS[yypt-0].expr} + } + case 79: + //line parser.go.y:440 + { + yyVAL.expr = &ast.FunctionExpr{ParList: yyS[yypt-0].funcexpr.ParList, Stmts: yyS[yypt-0].funcexpr.Stmts} + yyVAL.expr.SetLine(yyS[yypt-1].token.Pos.Line) + yyVAL.expr.SetLastLine(yyS[yypt-0].funcexpr.LastLine()) + } + case 80: + //line parser.go.y:447 + { + yyVAL.funcexpr = &ast.FunctionExpr{ParList: yyS[yypt-3].parlist, Stmts: yyS[yypt-1].stmts} + yyVAL.funcexpr.SetLine(yyS[yypt-4].token.Pos.Line) + yyVAL.funcexpr.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 81: + //line parser.go.y:452 + { + yyVAL.funcexpr = &ast.FunctionExpr{ParList: &ast.ParList{HasVargs: false, Names: []string{}}, Stmts: yyS[yypt-1].stmts} + yyVAL.funcexpr.SetLine(yyS[yypt-3].token.Pos.Line) + yyVAL.funcexpr.SetLastLine(yyS[yypt-0].token.Pos.Line) + } + case 82: + //line parser.go.y:459 + { + yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} + } + case 83: + //line parser.go.y:462 + { + yyVAL.parlist = &ast.ParList{HasVargs: false, Names: []string{}} + yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyS[yypt-0].namelist...) + } + case 84: + //line parser.go.y:466 + { + yyVAL.parlist = &ast.ParList{HasVargs: true, Names: []string{}} + yyVAL.parlist.Names = append(yyVAL.parlist.Names, yyS[yypt-2].namelist...) + } + case 85: + //line parser.go.y:473 + { + yyVAL.expr = &ast.TableExpr{Fields: []*ast.Field{}} + yyVAL.expr.SetLine(yyS[yypt-1].token.Pos.Line) + } + case 86: + //line parser.go.y:477 + { + yyVAL.expr = &ast.TableExpr{Fields: yyS[yypt-1].fieldlist} + yyVAL.expr.SetLine(yyS[yypt-2].token.Pos.Line) + } + case 87: + //line parser.go.y:484 + { + yyVAL.fieldlist = []*ast.Field{yyS[yypt-0].field} + } + case 88: + //line parser.go.y:487 + { + yyVAL.fieldlist = append(yyS[yypt-2].fieldlist, yyS[yypt-0].field) + } + case 89: + //line parser.go.y:490 + { + yyVAL.fieldlist = yyS[yypt-1].fieldlist + } + case 90: + //line parser.go.y:495 + { + yyVAL.field = &ast.Field{Key: &ast.StringExpr{Value: yyS[yypt-2].token.Str}, Value: yyS[yypt-0].expr} + yyVAL.field.Key.SetLine(yyS[yypt-2].token.Pos.Line) + } + case 91: + //line parser.go.y:499 + { + yyVAL.field = &ast.Field{Key: yyS[yypt-3].expr, Value: yyS[yypt-0].expr} + } + case 92: + //line parser.go.y:502 + { + yyVAL.field = &ast.Field{Value: yyS[yypt-0].expr} + } + case 93: + //line parser.go.y:507 + { + yyVAL.fieldsep = "," + } + case 94: + //line parser.go.y:510 + { + yyVAL.fieldsep = ";" + } + } + goto yystack /* stack new state and value */ +} diff --git a/vendor/github.com/yuin/gopher-lua/parse/parser.go.y b/vendor/github.com/yuin/gopher-lua/parse/parser.go.y new file mode 100644 index 0000000..956133d --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/parse/parser.go.y @@ -0,0 +1,524 @@ +%{ +package parse + +import ( + "github.com/yuin/gopher-lua/ast" +) +%} +%type chunk +%type chunk1 +%type block +%type stat +%type elseifs +%type laststat +%type funcname +%type funcname1 +%type varlist +%type var +%type namelist +%type exprlist +%type expr +%type string +%type prefixexp +%type functioncall +%type afunctioncall +%type args +%type function +%type funcbody +%type parlist +%type tableconstructor +%type fieldlist +%type field +%type fieldsep + +%union { + token ast.Token + + stmts []ast.Stmt + stmt ast.Stmt + + funcname *ast.FuncName + funcexpr *ast.FunctionExpr + + exprlist []ast.Expr + expr ast.Expr + + fieldlist []*ast.Field + field *ast.Field + fieldsep string + + namelist []string + parlist *ast.ParList +} + +/* Reserved words */ +%token TAnd TBreak TDo TElse TElseIf TEnd TFalse TFor TFunction TIf TIn TLocal TNil TNot TOr TReturn TRepeat TThen TTrue TUntil TWhile + +/* Literals */ +%token TEqeq TNeq TLte TGte T2Comma T3Comma TIdent TNumber TString '{' '(' + +/* Operators */ +%left TOr +%left TAnd +%left '>' '<' TGte TLte TEqeq TNeq +%right T2Comma +%left '+' '-' +%left '*' '/' '%' +%right UNARY /* not # -(unary) */ +%right '^' + +%% + +chunk: + chunk1 { + $$ = $1 + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } | + chunk1 laststat { + $$ = append($1, $2) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } | + chunk1 laststat ';' { + $$ = append($1, $2) + if l, ok := yylex.(*Lexer); ok { + l.Stmts = $$ + } + } + +chunk1: + { + $$ = []ast.Stmt{} + } | + chunk1 stat { + $$ = append($1, $2) + } | + chunk1 ';' { + $$ = $1 + } + +block: + chunk { + $$ = $1 + } + +stat: + varlist '=' exprlist { + $$ = &ast.AssignStmt{Lhs: $1, Rhs: $3} + $$.SetLine($1[0].Line()) + } | + /* 'stat = functioncal' causes a reduce/reduce conflict */ + prefixexp { + if _, ok := $1.(*ast.FuncCallExpr); !ok { + yylex.(*Lexer).Error("parse error") + } else { + $$ = &ast.FuncCallStmt{Expr: $1} + $$.SetLine($1.Line()) + } + } | + TDo block TEnd { + $$ = &ast.DoBlockStmt{Stmts: $2} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($3.Pos.Line) + } | + TWhile expr TDo block TEnd { + $$ = &ast.WhileStmt{Condition: $2, Stmts: $4} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($5.Pos.Line) + } | + TRepeat block TUntil expr { + $$ = &ast.RepeatStmt{Condition: $4, Stmts: $2} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.Line()) + } | + TIf expr TThen block elseifs TEnd { + $$ = &ast.IfStmt{Condition: $2, Then: $4} + cur := $$ + for _, elseif := range $5 { + cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} + cur = elseif + } + $$.SetLine($1.Pos.Line) + $$.SetLastLine($6.Pos.Line) + } | + TIf expr TThen block elseifs TElse block TEnd { + $$ = &ast.IfStmt{Condition: $2, Then: $4} + cur := $$ + for _, elseif := range $5 { + cur.(*ast.IfStmt).Else = []ast.Stmt{elseif} + cur = elseif + } + cur.(*ast.IfStmt).Else = $7 + $$.SetLine($1.Pos.Line) + $$.SetLastLine($8.Pos.Line) + } | + TFor TIdent '=' expr ',' expr TDo block TEnd { + $$ = &ast.NumberForStmt{Name: $2.Str, Init: $4, Limit: $6, Stmts: $8} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($9.Pos.Line) + } | + TFor TIdent '=' expr ',' expr ',' expr TDo block TEnd { + $$ = &ast.NumberForStmt{Name: $2.Str, Init: $4, Limit: $6, Step:$8, Stmts: $10} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($11.Pos.Line) + } | + TFor namelist TIn exprlist TDo block TEnd { + $$ = &ast.GenericForStmt{Names:$2, Exprs:$4, Stmts: $6} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($7.Pos.Line) + } | + TFunction funcname funcbody { + $$ = &ast.FuncDefStmt{Name: $2, Func: $3} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($3.LastLine()) + } | + TLocal TFunction TIdent funcbody { + $$ = &ast.LocalAssignStmt{Names:[]string{$3.Str}, Exprs: []ast.Expr{$4}} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.LastLine()) + } | + TLocal namelist '=' exprlist { + $$ = &ast.LocalAssignStmt{Names: $2, Exprs:$4} + $$.SetLine($1.Pos.Line) + } | + TLocal namelist { + $$ = &ast.LocalAssignStmt{Names: $2, Exprs:[]ast.Expr{}} + $$.SetLine($1.Pos.Line) + } + +elseifs: + { + $$ = []ast.Stmt{} + } | + elseifs TElseIf expr TThen block { + $$ = append($1, &ast.IfStmt{Condition: $3, Then: $5}) + $$[len($$)-1].SetLine($2.Pos.Line) + } + +laststat: + TReturn { + $$ = &ast.ReturnStmt{Exprs:nil} + $$.SetLine($1.Pos.Line) + } | + TReturn exprlist { + $$ = &ast.ReturnStmt{Exprs:$2} + $$.SetLine($1.Pos.Line) + } | + TBreak { + $$ = &ast.BreakStmt{} + $$.SetLine($1.Pos.Line) + } + +funcname: + funcname1 { + $$ = $1 + } | + funcname1 ':' TIdent { + $$ = &ast.FuncName{Func:nil, Receiver:$1.Func, Method: $3.Str} + } + +funcname1: + TIdent { + $$ = &ast.FuncName{Func: &ast.IdentExpr{Value:$1.Str}} + $$.Func.SetLine($1.Pos.Line) + } | + funcname1 '.' TIdent { + key:= &ast.StringExpr{Value:$3.Str} + key.SetLine($3.Pos.Line) + fn := &ast.AttrGetExpr{Object: $1.Func, Key: key} + fn.SetLine($3.Pos.Line) + $$ = &ast.FuncName{Func: fn} + } + +varlist: + var { + $$ = []ast.Expr{$1} + } | + varlist ',' var { + $$ = append($1, $3) + } + +var: + TIdent { + $$ = &ast.IdentExpr{Value:$1.Str} + $$.SetLine($1.Pos.Line) + } | + prefixexp '[' expr ']' { + $$ = &ast.AttrGetExpr{Object: $1, Key: $3} + $$.SetLine($1.Line()) + } | + prefixexp '.' TIdent { + key := &ast.StringExpr{Value:$3.Str} + key.SetLine($3.Pos.Line) + $$ = &ast.AttrGetExpr{Object: $1, Key: key} + $$.SetLine($1.Line()) + } + +namelist: + TIdent { + $$ = []string{$1.Str} + } | + namelist ',' TIdent { + $$ = append($1, $3.Str) + } + +exprlist: + expr { + $$ = []ast.Expr{$1} + } | + exprlist ',' expr { + $$ = append($1, $3) + } + +expr: + TNil { + $$ = &ast.NilExpr{} + $$.SetLine($1.Pos.Line) + } | + TFalse { + $$ = &ast.FalseExpr{} + $$.SetLine($1.Pos.Line) + } | + TTrue { + $$ = &ast.TrueExpr{} + $$.SetLine($1.Pos.Line) + } | + TNumber { + $$ = &ast.NumberExpr{Value: $1.Str} + $$.SetLine($1.Pos.Line) + } | + T3Comma { + $$ = &ast.Comma3Expr{} + $$.SetLine($1.Pos.Line) + } | + function { + $$ = $1 + } | + prefixexp { + $$ = $1 + } | + string { + $$ = $1 + } | + tableconstructor { + $$ = $1 + } | + expr TOr expr { + $$ = &ast.LogicalOpExpr{Lhs: $1, Operator: "or", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TAnd expr { + $$ = &ast.LogicalOpExpr{Lhs: $1, Operator: "and", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '>' expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: ">", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '<' expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "<", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TGte expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: ">=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TLte expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "<=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TEqeq expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "==", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr TNeq expr { + $$ = &ast.RelationalOpExpr{Lhs: $1, Operator: "~=", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr T2Comma expr { + $$ = &ast.StringConcatOpExpr{Lhs: $1, Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '+' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "+", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '-' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "-", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '*' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "*", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '/' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "/", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '%' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "%", Rhs: $3} + $$.SetLine($1.Line()) + } | + expr '^' expr { + $$ = &ast.ArithmeticOpExpr{Lhs: $1, Operator: "^", Rhs: $3} + $$.SetLine($1.Line()) + } | + '-' expr %prec UNARY { + $$ = &ast.UnaryMinusOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } | + TNot expr %prec UNARY { + $$ = &ast.UnaryNotOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } | + '#' expr %prec UNARY { + $$ = &ast.UnaryLenOpExpr{Expr: $2} + $$.SetLine($2.Line()) + } + +string: + TString { + $$ = &ast.StringExpr{Value: $1.Str} + $$.SetLine($1.Pos.Line) + } + +prefixexp: + var { + $$ = $1 + } | + afunctioncall { + $$ = $1 + } | + functioncall { + $$ = $1 + } | + '(' expr ')' { + $$ = $2 + $$.SetLine($1.Pos.Line) + } + +afunctioncall: + '(' functioncall ')' { + $2.(*ast.FuncCallExpr).AdjustRet = true + $$ = $2 + } + +functioncall: + prefixexp args { + $$ = &ast.FuncCallExpr{Func: $1, Args: $2} + $$.SetLine($1.Line()) + } | + prefixexp ':' TIdent args { + $$ = &ast.FuncCallExpr{Method: $3.Str, Receiver: $1, Args: $4} + $$.SetLine($1.Line()) + } + +args: + '(' ')' { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError($1, "ambiguous syntax (function call x new statement)") + } + $$ = []ast.Expr{} + } | + '(' exprlist ')' { + if yylex.(*Lexer).PNewLine { + yylex.(*Lexer).TokenError($1, "ambiguous syntax (function call x new statement)") + } + $$ = $2 + } | + tableconstructor { + $$ = []ast.Expr{$1} + } | + string { + $$ = []ast.Expr{$1} + } + +function: + TFunction funcbody { + $$ = &ast.FunctionExpr{ParList:$2.ParList, Stmts: $2.Stmts} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($2.LastLine()) + } + +funcbody: + '(' parlist ')' block TEnd { + $$ = &ast.FunctionExpr{ParList: $2, Stmts: $4} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($5.Pos.Line) + } | + '(' ')' block TEnd { + $$ = &ast.FunctionExpr{ParList: &ast.ParList{HasVargs: false, Names: []string{}}, Stmts: $3} + $$.SetLine($1.Pos.Line) + $$.SetLastLine($4.Pos.Line) + } + +parlist: + T3Comma { + $$ = &ast.ParList{HasVargs: true, Names: []string{}} + } | + namelist { + $$ = &ast.ParList{HasVargs: false, Names: []string{}} + $$.Names = append($$.Names, $1...) + } | + namelist ',' T3Comma { + $$ = &ast.ParList{HasVargs: true, Names: []string{}} + $$.Names = append($$.Names, $1...) + } + + +tableconstructor: + '{' '}' { + $$ = &ast.TableExpr{Fields: []*ast.Field{}} + $$.SetLine($1.Pos.Line) + } | + '{' fieldlist '}' { + $$ = &ast.TableExpr{Fields: $2} + $$.SetLine($1.Pos.Line) + } + + +fieldlist: + field { + $$ = []*ast.Field{$1} + } | + fieldlist fieldsep field { + $$ = append($1, $3) + } | + fieldlist fieldsep { + $$ = $1 + } + +field: + TIdent '=' expr { + $$ = &ast.Field{Key: &ast.StringExpr{Value:$1.Str}, Value: $3} + $$.Key.SetLine($1.Pos.Line) + } | + '[' expr ']' '=' expr { + $$ = &ast.Field{Key: $2, Value: $5} + } | + expr { + $$ = &ast.Field{Value: $1} + } + +fieldsep: + ',' { + $$ = "," + } | + ';' { + $$ = ";" + } + +%% + +func TokenName(c int) string { + if c >= TAnd && c-TAnd < len(yyToknames) { + if yyToknames[c-TAnd] != "" { + return yyToknames[c-TAnd] + } + } + return string([]byte{byte(c)}) +} + diff --git a/vendor/github.com/yuin/gopher-lua/pm/pm.go b/vendor/github.com/yuin/gopher-lua/pm/pm.go new file mode 100644 index 0000000..cd13bbd --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/pm/pm.go @@ -0,0 +1,639 @@ +// Lua pattern match functions for Go +package pm + +import ( + "fmt" +) + +const EOS = -1 +const _UNKNOWN = -2 + +/* Error {{{ */ + +type Error struct { + Pos int + Message string +} + +func newError(pos int, message string, args ...interface{}) *Error { + if len(args) == 0 { + return &Error{pos, message} + } + return &Error{pos, fmt.Sprintf(message, args...)} +} + +func (e *Error) Error() string { + switch e.Pos { + case EOS: + return fmt.Sprintf("%s at EOS", e.Message) + case _UNKNOWN: + return fmt.Sprintf("%s", e.Message) + default: + return fmt.Sprintf("%s at %d", e.Message, e.Pos) + } +} + +/* }}} */ + +/* MatchData {{{ */ + +type MatchData struct { + // captured positions + // layout + // xxxx xxxx xxxx xxx0 : caputured positions + // xxxx xxxx xxxx xxx1 : position captured positions + captures []uint32 +} + +func newMatchState() *MatchData { return &MatchData{[]uint32{}} } + +func (st *MatchData) addPosCapture(s, pos int) { + for s+1 >= len(st.captures) { + st.captures = append(st.captures, 0) + } + st.captures[s] = (uint32(pos) << 1) | 1 + st.captures[s+1] = (uint32(pos) << 1) | 1 +} + +func (st *MatchData) setCapture(s, pos int) uint32 { + for s >= len(st.captures) { + st.captures = append(st.captures, 0) + } + v := st.captures[s] + st.captures[s] = (uint32(pos) << 1) + return v +} + +func (st *MatchData) restoreCapture(s int, pos uint32) { st.captures[s] = pos } + +func (st *MatchData) CaptureLength() int { return len(st.captures) } + +func (st *MatchData) IsPosCapture(idx int) bool { return (st.captures[idx] & 1) == 1 } + +func (st *MatchData) Capture(idx int) int { return int(st.captures[idx] >> 1) } + +/* }}} */ + +/* scanner {{{ */ + +type scannerState struct { + Pos int + started bool +} + +type scanner struct { + src []byte + State scannerState + saved scannerState +} + +func newScanner(src []byte) *scanner { + return &scanner{ + src: src, + State: scannerState{ + Pos: 0, + started: false, + }, + saved: scannerState{}, + } +} + +func (sc *scanner) Length() int { return len(sc.src) } + +func (sc *scanner) Next() int { + if !sc.State.started { + sc.State.started = true + if len(sc.src) == 0 { + sc.State.Pos = EOS + } + } else { + sc.State.Pos = sc.NextPos() + } + if sc.State.Pos == EOS { + return EOS + } + return int(sc.src[sc.State.Pos]) +} + +func (sc *scanner) CurrentPos() int { + return sc.State.Pos +} + +func (sc *scanner) NextPos() int { + if sc.State.Pos == EOS || sc.State.Pos >= len(sc.src)-1 { + return EOS + } + if !sc.State.started { + return 0 + } else { + return sc.State.Pos + 1 + } +} + +func (sc *scanner) Peek() int { + cureof := sc.State.Pos == EOS + ch := sc.Next() + if !cureof { + if sc.State.Pos == EOS { + sc.State.Pos = len(sc.src) - 1 + } else { + sc.State.Pos-- + if sc.State.Pos < 0 { + sc.State.Pos = 0 + sc.State.started = false + } + } + } + return ch +} + +func (sc *scanner) Save() { sc.saved = sc.State } + +func (sc *scanner) Restore() { sc.State = sc.saved } + +/* }}} */ + +/* bytecode {{{ */ + +type opCode int + +const ( + opChar opCode = iota + opMatch + opTailMatch + opJmp + opSplit + opSave + opPSave + opBrace + opNumber +) + +type inst struct { + OpCode opCode + Class class + Operand1 int + Operand2 int +} + +/* }}} */ + +/* classes {{{ */ + +type class interface { + Matches(ch int) bool +} + +type dotClass struct{} + +func (pn *dotClass) Matches(ch int) bool { return true } + +type charClass struct { + Ch int +} + +func (pn *charClass) Matches(ch int) bool { return pn.Ch == ch } + +type singleClass struct { + Class int +} + +func (pn *singleClass) Matches(ch int) bool { + ret := false + switch pn.Class { + case 'a', 'A': + ret = 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' + case 'c', 'C': + ret = (0x00 <= ch && ch <= 0x1F) || ch == 0x7F + case 'd', 'D': + ret = '0' <= ch && ch <= '9' + case 'l', 'L': + ret = 'a' <= ch && ch <= 'z' + case 'p', 'P': + ret = (0x21 <= ch && ch <= 0x2f) || (0x30 <= ch && ch <= 0x40) || (0x5b <= ch && ch <= 0x60) || (0x7b <= ch && ch <= 0x7e) + case 's', 'S': + switch ch { + case ' ', '\f', '\n', '\r', '\t', '\v': + ret = true + } + case 'u', 'U': + ret = 'A' <= ch && ch <= 'Z' + case 'w', 'W': + ret = '0' <= ch && ch <= '9' || 'A' <= ch && ch <= 'Z' || 'a' <= ch && ch <= 'z' + case 'x', 'X': + ret = '0' <= ch && ch <= '9' || 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F' + case 'z', 'Z': + ret = ch == 0 + default: + return ch == pn.Class + } + if 'A' <= pn.Class && pn.Class <= 'Z' { + return !ret + } + return ret +} + +type setClass struct { + IsNot bool + Classes []class +} + +func (pn *setClass) Matches(ch int) bool { + for _, class := range pn.Classes { + if class.Matches(ch) { + return !pn.IsNot + } + } + return pn.IsNot +} + +type rangeClass struct { + Begin class + End class +} + +func (pn *rangeClass) Matches(ch int) bool { + switch begin := pn.Begin.(type) { + case *charClass: + end, ok := pn.End.(*charClass) + if !ok { + return false + } + return begin.Ch <= ch && ch <= end.Ch + } + return false +} + +// }}} + +// patterns {{{ + +type pattern interface{} + +type singlePattern struct { + Class class +} + +type seqPattern struct { + MustHead bool + MustTail bool + Patterns []pattern +} + +type repeatPattern struct { + Type int + Class class +} + +type posCapPattern struct{} + +type capPattern struct { + Pattern pattern +} + +type numberPattern struct { + N int +} + +type bracePattern struct { + Begin int + End int +} + +// }}} + +/* parse {{{ */ + +func parseClass(sc *scanner, allowset bool) class { + ch := sc.Next() + switch ch { + case '%': + return &singleClass{sc.Next()} + case '.': + if allowset { + return &dotClass{} + } + return &charClass{ch} + case '[': + if allowset { + return parseClassSet(sc) + } + return &charClass{ch} + //case '^' '$', '(', ')', ']', '*', '+', '-', '?': + // panic(newError(sc.CurrentPos(), "invalid %c", ch)) + case EOS: + panic(newError(sc.CurrentPos(), "unexpected EOS")) + default: + return &charClass{ch} + } +} + +func parseClassSet(sc *scanner) class { + set := &setClass{false, []class{}} + if sc.Peek() == '^' { + set.IsNot = true + sc.Next() + } + isrange := false + for { + ch := sc.Peek() + switch ch { + // case '[': + // panic(newError(sc.CurrentPos(), "'[' can not be nested")) + case EOS: + panic(newError(sc.CurrentPos(), "unexpected EOS")) + case ']': + if len(set.Classes) > 0 { + sc.Next() + goto exit + } + fallthrough + case '-': + if len(set.Classes) > 0 { + sc.Next() + isrange = true + continue + } + fallthrough + default: + set.Classes = append(set.Classes, parseClass(sc, false)) + } + if isrange { + begin := set.Classes[len(set.Classes)-2] + end := set.Classes[len(set.Classes)-1] + set.Classes = set.Classes[0 : len(set.Classes)-2] + set.Classes = append(set.Classes, &rangeClass{begin, end}) + isrange = false + } + } +exit: + if isrange { + set.Classes = append(set.Classes, &charClass{'-'}) + } + + return set +} + +func parsePattern(sc *scanner, toplevel bool) *seqPattern { + pat := &seqPattern{} + if toplevel { + if sc.Peek() == '^' { + sc.Next() + pat.MustHead = true + } + } + for { + ch := sc.Peek() + switch ch { + case '%': + sc.Save() + sc.Next() + switch sc.Peek() { + case '0': + panic(newError(sc.CurrentPos(), "invalid capture index")) + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + pat.Patterns = append(pat.Patterns, &numberPattern{sc.Next() - 48}) + case 'b': + sc.Next() + pat.Patterns = append(pat.Patterns, &bracePattern{sc.Next(), sc.Next()}) + default: + sc.Restore() + pat.Patterns = append(pat.Patterns, &singlePattern{parseClass(sc, true)}) + } + case '.', '[', ']': + pat.Patterns = append(pat.Patterns, &singlePattern{parseClass(sc, true)}) + //case ']': + // panic(newError(sc.CurrentPos(), "invalid ']'")) + case ')': + if toplevel { + panic(newError(sc.CurrentPos(), "invalid ')'")) + } + return pat + case '(': + sc.Next() + if sc.Peek() == ')' { + sc.Next() + pat.Patterns = append(pat.Patterns, &posCapPattern{}) + } else { + ret := &capPattern{parsePattern(sc, false)} + if sc.Peek() != ')' { + panic(newError(sc.CurrentPos(), "unfinished capture")) + } + sc.Next() + pat.Patterns = append(pat.Patterns, ret) + } + case '*', '+', '-', '?': + sc.Next() + if len(pat.Patterns) > 0 { + spat, ok := pat.Patterns[len(pat.Patterns)-1].(*singlePattern) + if ok { + pat.Patterns = pat.Patterns[0 : len(pat.Patterns)-1] + pat.Patterns = append(pat.Patterns, &repeatPattern{ch, spat.Class}) + continue + } + } + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + case '$': + if toplevel && (sc.NextPos() == sc.Length()-1 || sc.NextPos() == EOS) { + pat.MustTail = true + } else { + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + } + sc.Next() + case EOS: + sc.Next() + goto exit + default: + sc.Next() + pat.Patterns = append(pat.Patterns, &singlePattern{&charClass{ch}}) + } + } +exit: + return pat +} + +type iptr struct { + insts []inst + capture int +} + +func compilePattern(p pattern, ps ...*iptr) []inst { + var ptr *iptr + toplevel := false + if len(ps) == 0 { + toplevel = true + ptr = &iptr{[]inst{inst{opSave, nil, 0, -1}}, 2} + } else { + ptr = ps[0] + } + switch pat := p.(type) { + case *singlePattern: + ptr.insts = append(ptr.insts, inst{opChar, pat.Class, -1, -1}) + case *seqPattern: + for _, cp := range pat.Patterns { + compilePattern(cp, ptr) + } + case *repeatPattern: + idx := len(ptr.insts) + switch pat.Type { + case '*': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 1, idx + 3}, + inst{opChar, pat.Class, -1, -1}, + inst{opJmp, nil, idx, -1}) + case '+': + ptr.insts = append(ptr.insts, + inst{opChar, pat.Class, -1, -1}, + inst{opSplit, nil, idx, idx + 2}) + case '-': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 3, idx + 1}, + inst{opChar, pat.Class, -1, -1}, + inst{opJmp, nil, idx, -1}) + case '?': + ptr.insts = append(ptr.insts, + inst{opSplit, nil, idx + 1, idx + 2}, + inst{opChar, pat.Class, -1, -1}) + } + case *posCapPattern: + ptr.insts = append(ptr.insts, inst{opPSave, nil, ptr.capture, -1}) + ptr.capture += 2 + case *capPattern: + c0, c1 := ptr.capture, ptr.capture+1 + ptr.capture += 2 + ptr.insts = append(ptr.insts, inst{opSave, nil, c0, -1}) + compilePattern(pat.Pattern, ptr) + ptr.insts = append(ptr.insts, inst{opSave, nil, c1, -1}) + case *bracePattern: + ptr.insts = append(ptr.insts, inst{opBrace, nil, pat.Begin, pat.End}) + case *numberPattern: + ptr.insts = append(ptr.insts, inst{opNumber, nil, pat.N, -1}) + } + if toplevel { + if p.(*seqPattern).MustTail { + ptr.insts = append(ptr.insts, inst{opSave, nil, 1, -1}, inst{opTailMatch, nil, -1, -1}) + } + ptr.insts = append(ptr.insts, inst{opSave, nil, 1, -1}, inst{opMatch, nil, -1, -1}) + } + return ptr.insts +} + +/* }}} parse */ + +/* VM {{{ */ + +// Simple recursive virtual machine based on the +// "Regular Expression Matching: the Virtual Machine Approach" (https://swtch.com/~rsc/regexp/regexp2.html) +func recursiveVM(src []byte, insts []inst, pc, sp int, ms ...*MatchData) (bool, int, *MatchData) { + var m *MatchData + if len(ms) == 0 { + m = newMatchState() + } else { + m = ms[0] + } +redo: + inst := insts[pc] + switch inst.OpCode { + case opChar: + if sp >= len(src) || !inst.Class.Matches(int(src[sp])) { + return false, sp, m + } + pc++ + sp++ + goto redo + case opMatch: + return true, sp, m + case opTailMatch: + return sp >= len(src), sp, m + case opJmp: + pc = inst.Operand1 + goto redo + case opSplit: + if ok, nsp, _ := recursiveVM(src, insts, inst.Operand1, sp, m); ok { + return true, nsp, m + } + pc = inst.Operand2 + goto redo + case opSave: + s := m.setCapture(inst.Operand1, sp) + if ok, nsp, _ := recursiveVM(src, insts, pc+1, sp, m); ok { + return true, nsp, m + } + m.restoreCapture(inst.Operand1, s) + return false, sp, m + case opPSave: + m.addPosCapture(inst.Operand1, sp+1) + pc++ + goto redo + case opBrace: + if sp >= len(src) || int(src[sp]) != inst.Operand1 { + return false, sp, m + } + count := 1 + for sp = sp + 1; sp < len(src); sp++ { + if int(src[sp]) == inst.Operand2 { + count-- + } + if count == 0 { + pc++ + sp++ + goto redo + } + if int(src[sp]) == inst.Operand1 { + count++ + } + } + return false, sp, m + case opNumber: + idx := inst.Operand1 * 2 + if idx >= m.CaptureLength()-1 { + panic(newError(_UNKNOWN, "invalid capture index")) + } + capture := src[m.Capture(idx):m.Capture(idx+1)] + for i := 0; i < len(capture); i++ { + if i+sp >= len(src) || capture[i] != src[i+sp] { + return false, sp, m + } + } + pc++ + sp += len(capture) + goto redo + } + panic("should not reach here") + return false, sp, m +} + +/* }}} */ + +/* API {{{ */ + +func Find(p string, src []byte, offset, limit int) (matches []*MatchData, err error) { + defer func() { + if v := recover(); v != nil { + if perr, ok := v.(*Error); ok { + err = perr + } else { + panic(v) + } + } + }() + pat := parsePattern(newScanner([]byte(p)), true) + insts := compilePattern(pat) + matches = []*MatchData{} + for sp := offset; sp <= len(src); { + ok, nsp, ms := recursiveVM(src, insts, 0, sp) + sp++ + if ok { + if sp < nsp { + sp = nsp + } + matches = append(matches, ms) + } + if len(matches) == limit || pat.MustHead { + break + } + } + return +} + +/* }}} */ diff --git a/vendor/github.com/yuin/gopher-lua/script_test.go b/vendor/github.com/yuin/gopher-lua/script_test.go new file mode 100644 index 0000000..a98ae5e --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/script_test.go @@ -0,0 +1,87 @@ +package lua + +import ( + "fmt" + "github.com/yuin/gopher-lua/parse" + "os" + "testing" +) + +const maxMemory = 40 + +var gluaTests []string = []string{ + "base.lua", + "coroutine.lua", + "db.lua", + "issues.lua", + "os.lua", + "table.lua", + "vm.lua", + "math.lua", + "strings.lua", +} + +var luaTests []string = []string{ + "attrib.lua", + "calls.lua", + "closure.lua", + "constructs.lua", + "events.lua", + "literals.lua", + "locals.lua", + "math.lua", + "sort.lua", + "strings.lua", + "vararg.lua", + "pm.lua", + "files.lua", +} + +func testScriptCompile(t *testing.T, script string) { + file, err := os.Open(script) + if err != nil { + t.Fatal(err) + return + } + chunk, err2 := parse.Parse(file, script) + if err2 != nil { + t.Fatal(err2) + return + } + parse.Dump(chunk) + proto, err3 := Compile(chunk, script) + if err3 != nil { + t.Fatal(err3) + return + } + proto.String() +} + +func testScriptDir(t *testing.T, tests []string, directory string) { + if err := os.Chdir(directory); err != nil { + t.Error(err) + } + defer os.Chdir("..") + for _, script := range tests { + fmt.Printf("testing %s/%s\n", directory, script) + testScriptCompile(t, script) + L := NewState(Options{ + RegistrySize: 1024 * 20, + CallStackSize: 1024, + IncludeGoStackTrace: true, + }) + L.SetMx(maxMemory) + if err := L.DoFile(script); err != nil { + t.Error(err) + } + L.Close() + } +} + +func TestGlua(t *testing.T) { + testScriptDir(t, gluaTests, "_glua-tests") +} + +func TestLua(t *testing.T) { + testScriptDir(t, luaTests, "_lua5.1-tests") +} diff --git a/vendor/github.com/yuin/gopher-lua/state.go b/vendor/github.com/yuin/gopher-lua/state.go new file mode 100644 index 0000000..b6ea313 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/state.go @@ -0,0 +1,1872 @@ +package lua + +//////////////////////////////////////////////////////// +// This file was generated by go-inline. DO NOT EDIT. // +//////////////////////////////////////////////////////// + +import ( + "context" + "fmt" + "github.com/yuin/gopher-lua/parse" + "io" + "math" + "os" + "runtime" + "strings" + "sync/atomic" + "time" +) + +const MultRet = -1 +const RegistryIndex = -10000 +const EnvironIndex = -10001 +const GlobalsIndex = -10002 + +/* ApiError {{{ */ + +type ApiError struct { + Type ApiErrorType + Object LValue + StackTrace string + // Underlying error. This attribute is set only if the Type is ApiErrorFile or ApiErrorSyntax + Cause error +} + +func newApiError(code ApiErrorType, object LValue) *ApiError { + return &ApiError{code, object, "", nil} +} + +func newApiErrorS(code ApiErrorType, message string) *ApiError { + return newApiError(code, LString(message)) +} + +func newApiErrorE(code ApiErrorType, err error) *ApiError { + return &ApiError{code, LString(err.Error()), "", err} +} + +func (e *ApiError) Error() string { + if len(e.StackTrace) > 0 { + return fmt.Sprintf("%s\n%s", e.Object.String(), e.StackTrace) + } + return e.Object.String() +} + +type ApiErrorType int + +const ( + ApiErrorSyntax ApiErrorType = iota + ApiErrorFile + ApiErrorRun + ApiErrorError + ApiErrorPanic +) + +/* }}} */ + +/* ResumeState {{{ */ + +type ResumeState int + +const ( + ResumeOK ResumeState = iota + ResumeYield + ResumeError +) + +/* }}} */ + +/* P {{{ */ + +type P struct { + Fn LValue + NRet int + Protect bool + Handler *LFunction +} + +/* }}} */ + +/* Options {{{ */ + +// Options is a configuration that is used to create a new LState. +type Options struct { + // Call stack size. This defaults to `lua.CallStackSize`. + CallStackSize int + // Data stack size. This defaults to `lua.RegistrySize`. + RegistrySize int + // Controls whether or not libraries are opened by default + SkipOpenLibs bool + // Tells whether a Go stacktrace should be included in a Lua stacktrace when panics occur. + IncludeGoStackTrace bool +} + +/* }}} */ + +/* Debug {{{ */ + +type Debug struct { + frame *callFrame + Name string + What string + Source string + CurrentLine int + NUpvalues int + LineDefined int + LastLineDefined int +} + +/* }}} */ + +/* callFrame {{{ */ + +type callFrame struct { + Idx int + Fn *LFunction + Parent *callFrame + Pc int + Base int + LocalBase int + ReturnBase int + NArgs int + NRet int + TailCall int +} + +type callFrameStack struct { + array []callFrame + sp int +} + +func newCallFrameStack(size int) *callFrameStack { + return &callFrameStack{ + array: make([]callFrame, size), + sp: 0, + } +} + +func (cs *callFrameStack) IsEmpty() bool { return cs.sp == 0 } + +func (cs *callFrameStack) Clear() { + cs.sp = 0 +} + +func (cs *callFrameStack) Push(v callFrame) { // +inline-start + cs.array[cs.sp] = v + cs.array[cs.sp].Idx = cs.sp + cs.sp++ +} // +inline-end + +func (cs *callFrameStack) Remove(sp int) { + psp := sp - 1 + nsp := sp + 1 + var pre *callFrame + var next *callFrame + if psp > 0 { + pre = &cs.array[psp] + } + if nsp < cs.sp { + next = &cs.array[nsp] + } + if next != nil { + next.Parent = pre + } + for i := sp; i+1 < cs.sp; i++ { + cs.array[i] = cs.array[i+1] + cs.array[i].Idx = i + cs.sp = i + } + cs.sp++ +} + +func (cs *callFrameStack) Sp() int { + return cs.sp +} + +func (cs *callFrameStack) SetSp(sp int) { + cs.sp = sp +} + +func (cs *callFrameStack) Last() *callFrame { + if cs.sp == 0 { + return nil + } + return &cs.array[cs.sp-1] +} + +func (cs *callFrameStack) At(sp int) *callFrame { + return &cs.array[sp] +} + +func (cs *callFrameStack) Pop() *callFrame { + cs.sp-- + return &cs.array[cs.sp] +} + +/* }}} */ + +/* registry {{{ */ + +type registry struct { + array []LValue + top int + alloc *allocator +} + +func newRegistry(size int, alloc *allocator) *registry { + return ®istry{make([]LValue, size), 0, alloc} +} + +func (rg *registry) SetTop(top int) { + oldtop := rg.top + rg.top = top + for i := oldtop; i < rg.top; i++ { + rg.array[i] = LNil + } + for i := rg.top; i < oldtop; i++ { + rg.array[i] = LNil + } +} + +func (rg *registry) Top() int { + return rg.top +} + +func (rg *registry) Push(v LValue) { + rg.array[rg.top] = v + rg.top++ +} + +func (rg *registry) Pop() LValue { + v := rg.array[rg.top-1] + rg.array[rg.top-1] = LNil + rg.top-- + return v +} + +func (rg *registry) Get(reg int) LValue { + return rg.array[reg] +} + +func (rg *registry) CopyRange(regv, start, limit, n int) { // +inline-start + for i := 0; i < n; i++ { + if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[tidx] + } + } + rg.top = regv + n +} // +inline-end + +func (rg *registry) FillNil(regm, n int) { // +inline-start + for i := 0; i < n; i++ { + rg.array[regm+i] = LNil + } + rg.top = regm + n +} // +inline-end + +func (rg *registry) Insert(value LValue, reg int) { + top := rg.Top() + if reg >= top { + rg.Set(reg, value) + return + } + top-- + for ; top >= reg; top-- { + rg.Set(top+1, rg.Get(top)) + } + rg.Set(reg, value) +} + +func (rg *registry) Set(reg int, val LValue) { + rg.array[reg] = val + if reg >= rg.top { + rg.top = reg + 1 + } +} + +func (rg *registry) SetNumber(reg int, val LNumber) { + rg.array[reg] = rg.alloc.LNumber2I(val) + if reg >= rg.top { + rg.top = reg + 1 + } +} /* }}} */ + +/* Global {{{ */ + +func newGlobal() *Global { + return &Global{ + MainThread: nil, + Registry: newLTable(0, 32), + Global: newLTable(0, 64), + builtinMts: make(map[int]LValue), + tempFiles: make([]*os.File, 0, 10), + } +} + +/* }}} */ + +/* package local methods {{{ */ + +func panicWithTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + err.StackTrace = L.stackTrace(0) + panic(err) +} + +func panicWithoutTraceback(L *LState) { + err := newApiError(ApiErrorRun, L.Get(-1)) + panic(err) +} + +func newLState(options Options) *LState { + al := newAllocator(32) + ls := &LState{ + G: newGlobal(), + Parent: nil, + Panic: panicWithTraceback, + Dead: false, + Options: options, + + stop: 0, + reg: newRegistry(options.RegistrySize, al), + stack: newCallFrameStack(options.CallStackSize), + alloc: al, + currentFrame: nil, + wrapped: false, + uvcache: nil, + hasErrorFunc: false, + mainLoop: mainLoop, + ctx: nil, + } + ls.Env = ls.G.Global + return ls +} + +func (ls *LState) printReg() { + println("-------------------------") + println("thread:", ls) + println("top:", ls.reg.Top()) + if ls.currentFrame != nil { + println("function base:", ls.currentFrame.Base) + println("return base:", ls.currentFrame.ReturnBase) + } else { + println("(vm not started)") + } + println("local base:", ls.currentLocalBase()) + for i := 0; i < ls.reg.Top(); i++ { + println(i, ls.reg.Get(i).String()) + } + println("-------------------------") +} + +func (ls *LState) printCallStack() { + println("-------------------------") + for i := 0; i < ls.stack.Sp(); i++ { + print(i) + print(" ") + frame := ls.stack.At(i) + if frame == nil { + break + } + if frame.Fn.IsG { + println("IsG:", true, "Frame:", frame, "Fn:", frame.Fn) + } else { + println("IsG:", false, "Frame:", frame, "Fn:", frame.Fn, "pc:", frame.Pc) + } + } + println("-------------------------") +} + +func (ls *LState) closeAllUpvalues() { // +inline-start + for cf := ls.currentFrame; cf != nil; cf = cf.Parent { + if !cf.Fn.IsG { + ls.closeUpvalues(cf.LocalBase) + } + } +} // +inline-end + +func (ls *LState) raiseError(level int, format string, args ...interface{}) { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + message := format + if len(args) > 0 { + message = fmt.Sprintf(format, args...) + } + if level > 0 { + message = fmt.Sprintf("%v %v", ls.where(level-1, true), message) + } + ls.reg.Push(LString(message)) + ls.Panic(ls) +} + +func (ls *LState) findLocal(frame *callFrame, no int) string { + fn := frame.Fn + if !fn.IsG { + if name, ok := fn.LocalName(no, frame.Pc-1); ok { + return name + } + } + var top int + if ls.currentFrame == frame { + top = ls.reg.Top() + } else if frame.Idx+1 < ls.stack.Sp() { + top = ls.stack.At(frame.Idx + 1).Base + } else { + return "" + } + if top-frame.LocalBase >= no { + return "(*temporary)" + } + return "" +} + +func (ls *LState) where(level int, skipg bool) string { + dbg, ok := ls.GetStack(level) + if !ok { + return "" + } + cf := dbg.frame + proto := cf.Fn.Proto + sourcename := "[G]" + if proto != nil { + sourcename = proto.SourceName + } else if skipg { + return ls.where(level+1, skipg) + } + line := "" + if proto != nil { + line = fmt.Sprintf("%v:", proto.DbgSourcePositions[cf.Pc-1]) + } + return fmt.Sprintf("%v:%v", sourcename, line) +} + +func (ls *LState) stackTrace(level int) string { + buf := []string{} + header := "stack traceback:" + if ls.currentFrame != nil { + i := 0 + for dbg, ok := ls.GetStack(i); ok; dbg, ok = ls.GetStack(i) { + cf := dbg.frame + buf = append(buf, fmt.Sprintf("\t%v in %v", ls.Where(i), ls.formattedFrameFuncName(cf))) + if !cf.Fn.IsG && cf.TailCall > 0 { + for tc := cf.TailCall; tc > 0; tc-- { + buf = append(buf, "\t(tailcall): ?") + i++ + } + } + i++ + } + } + buf = append(buf, fmt.Sprintf("\t%v: %v", "[G]", "?")) + buf = buf[intMax(0, intMin(level, len(buf))):len(buf)] + if len(buf) > 20 { + newbuf := make([]string, 0, 20) + newbuf = append(newbuf, buf[0:7]...) + newbuf = append(newbuf, "\t...") + newbuf = append(newbuf, buf[len(buf)-7:len(buf)]...) + buf = newbuf + } + return fmt.Sprintf("%s\n%s", header, strings.Join(buf, "\n")) +} + +func (ls *LState) formattedFrameFuncName(fr *callFrame) string { + name, ischunk := ls.frameFuncName(fr) + if ischunk { + return name + } + if name[0] != '(' && name[0] != '<' { + return fmt.Sprintf("function '%s'", name) + } + return fmt.Sprintf("function %s", name) +} + +func (ls *LState) rawFrameFuncName(fr *callFrame) string { + name, _ := ls.frameFuncName(fr) + return name +} + +func (ls *LState) frameFuncName(fr *callFrame) (string, bool) { + frame := fr.Parent + if frame == nil { + if ls.Parent == nil { + return "main chunk", true + } else { + return "corountine", true + } + } + if !frame.Fn.IsG { + pc := frame.Pc - 1 + for _, call := range frame.Fn.Proto.DbgCalls { + if call.Pc == pc { + name := call.Name + if (name == "?" || fr.TailCall > 0) && !fr.Fn.IsG { + name = fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined) + } + return name, false + } + } + } + if !fr.Fn.IsG { + return fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined), false + } + return "(anonymous)", false +} + +func (ls *LState) isStarted() bool { + return ls.currentFrame != nil +} + +func (ls *LState) kill() { + ls.Dead = true +} + +func (ls *LState) indexToReg(idx int) int { + base := ls.currentLocalBase() + if idx > 0 { + return base + idx - 1 + } else if idx == 0 { + return -1 + } else { + tidx := ls.reg.Top() + idx + if tidx < base { + return -1 + } + return tidx + } +} + +func (ls *LState) currentLocalBase() int { + base := 0 + if ls.currentFrame != nil { + base = ls.currentFrame.LocalBase + } + return base +} + +func (ls *LState) currentEnv() *LTable { + return ls.Env + /* + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + */ +} + +func (ls *LState) rkValue(idx int) LValue { + /* + if OpIsK(idx) { + return ls.currentFrame.Fn.Proto.Constants[opIndexK(idx)] + } + return ls.reg.Get(ls.currentFrame.LocalBase + idx) + */ + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.Constants[idx & ^opBitRk] + } + return ls.reg.array[ls.currentFrame.LocalBase+idx] +} + +func (ls *LState) rkString(idx int) string { + if (idx & opBitRk) != 0 { + return ls.currentFrame.Fn.Proto.stringConstants[idx & ^opBitRk] + } + return string(ls.reg.array[ls.currentFrame.LocalBase+idx].(LString)) +} + +func (ls *LState) closeUpvalues(idx int) { // +inline-start + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } +} // +inline-end + +func (ls *LState) findUpvalue(idx int) *Upvalue { + var prev *Upvalue + var next *Upvalue + if ls.uvcache != nil { + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index == idx { + return uv + } + if uv.index > idx { + next = uv + break + } + prev = uv + } + } + uv := &Upvalue{reg: ls.reg, index: idx, closed: false} + if prev != nil { + prev.next = uv + } else { + ls.uvcache = uv + } + if next != nil { + uv.next = next + } + return uv +} + +func (ls *LState) metatable(lvalue LValue, rawget bool) LValue { + var metatable LValue = LNil + switch obj := lvalue.(type) { + case *LTable: + metatable = obj.Metatable + case *LUserData: + metatable = obj.Metatable + default: + if table, ok := ls.G.builtinMts[int(obj.Type())]; ok { + metatable = table + } + } + + if !rawget && metatable != LNil { + oldmt := metatable + if tb, ok := metatable.(*LTable); ok { + metatable = tb.RawGetString("__metatable") + if metatable == LNil { + metatable = oldmt + } + } + } + + return metatable +} + +func (ls *LState) metaOp1(lvalue LValue, event string) LValue { + if mt := ls.metatable(lvalue, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaOp2(value1, value2 LValue, event string) LValue { + if mt := ls.metatable(value1, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + if ret := tb.RawGetString(event); ret != LNil { + return ret + } + } + } + if mt := ls.metatable(value2, true); mt != LNil { + if tb, ok := mt.(*LTable); ok { + return tb.RawGetString(event) + } + } + return LNil +} + +func (ls *LState) metaCall(lvalue LValue) (*LFunction, bool) { + if fn, ok := lvalue.(*LFunction); ok { + return fn, false + } + if fn, ok := ls.metaOp1(lvalue, "__call").(*LFunction); ok { + return fn, true + } + return nil, false +} + +func (ls *LState) initCallFrame(cf *callFrame) { // +inline-start + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } +} // +inline-end + +func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) { // +inline-start + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.sp == ls.Options.CallStackSize { + ls.RaiseError("stack overflow") + } + // this section is inlined by go-inline + // source function is 'func (cs *callFrameStack) Push(v callFrame) ' in '_state.go' + { + cs := ls.stack + v := cf + cs.array[cs.sp] = v + cs.array[cs.sp].Idx = cs.sp + cs.sp++ + } + newcf := ls.stack.Last() + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + cf := newcf + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + ls.currentFrame = newcf +} // +inline-end + +func (ls *LState) callR(nargs, nret, rbase int) { + base := ls.reg.Top() - nargs - 1 + if rbase < 0 { + rbase = base + } + lv := ls.reg.Get(base) + fn, meta := ls.metaCall(lv) + ls.pushCallFrame(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: rbase, + NArgs: nargs, + NRet: nret, + Parent: ls.currentFrame, + TailCall: 0, + }, lv, meta) + if ls.G.MainThread == nil { + ls.G.MainThread = ls + ls.G.CurrentThread = ls + ls.mainLoop(ls, nil) + } else { + ls.mainLoop(ls, ls.currentFrame) + } + if nret != MultRet { + ls.reg.SetTop(rbase + nret) + } +} + +func (ls *LState) getField(obj LValue, key LValue) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGet(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String()) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) getFieldString(obj LValue, key string) LValue { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + ret := tb.RawGetString(key) + if ret != LNil { + return ret + } + } + metaindex := ls.metaOp1(curobj, "__index") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String()) + } + return LNil + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.Call(2, 1) + return ls.reg.Pop() + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in gettable") + return nil +} + +func (ls *LState) setField(obj LValue, key LValue, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGet(key) != LNil { + ls.RawSet(tb, key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String()) + } + ls.RawSet(tb, key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(key) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +func (ls *LState) setFieldString(obj LValue, key string, value LValue) { + curobj := obj + for i := 0; i < MaxTableGetLoop; i++ { + tb, istable := curobj.(*LTable) + if istable { + if tb.RawGetString(key) != LNil { + tb.RawSetString(key, value) + return + } + } + metaindex := ls.metaOp1(curobj, "__newindex") + if metaindex == LNil { + if !istable { + ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String()) + } + tb.RawSetString(key, value) + return + } + if metaindex.Type() == LTFunction { + ls.reg.Push(metaindex) + ls.reg.Push(curobj) + ls.reg.Push(LString(key)) + ls.reg.Push(value) + ls.Call(3, 0) + return + } else { + curobj = metaindex + } + } + ls.RaiseError("too many recursions in settable") +} + +/* }}} */ + +/* api methods {{{ */ + +func NewState(opts ...Options) *LState { + var ls *LState + if len(opts) == 0 { + ls = newLState(Options{ + CallStackSize: CallStackSize, + RegistrySize: RegistrySize, + }) + ls.OpenLibs() + } else { + if opts[0].CallStackSize < 1 { + opts[0].CallStackSize = CallStackSize + } + if opts[0].RegistrySize < 128 { + opts[0].RegistrySize = RegistrySize + } + ls = newLState(opts[0]) + if !opts[0].SkipOpenLibs { + ls.OpenLibs() + } + } + return ls +} + +func (ls *LState) Close() { + atomic.AddInt32(&ls.stop, 1) + for _, file := range ls.G.tempFiles { + // ignore errors in these operations + file.Close() + os.Remove(file.Name()) + } +} + +/* registry operations {{{ */ + +func (ls *LState) GetTop() int { + return ls.reg.Top() - ls.currentLocalBase() +} + +func (ls *LState) SetTop(idx int) { + base := ls.currentLocalBase() + newtop := ls.indexToReg(idx) + 1 + if newtop < base { + ls.reg.SetTop(base) + } else { + ls.reg.SetTop(newtop) + } +} + +func (ls *LState) Replace(idx int, value LValue) { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + ls.reg.Set(reg, value) + } + } else if idx == 0 { + } else if idx > RegistryIndex { + if tidx := ls.reg.Top() + idx; tidx >= base { + ls.reg.Set(tidx, value) + } + } else { + switch idx { + case RegistryIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Registry = tb + } else { + ls.RaiseError("registry must be a table(%v)", value.Type().String()) + } + case EnvironIndex: + if ls.currentFrame == nil { + ls.RaiseError("no calling environment") + } + if tb, ok := value.(*LTable); ok { + ls.currentFrame.Fn.Env = tb + } else { + ls.RaiseError("environment must be a table(%v)", value.Type().String()) + } + case GlobalsIndex: + if tb, ok := value.(*LTable); ok { + ls.G.Global = tb + } else { + ls.RaiseError("_G must be a table(%v)", value.Type().String()) + } + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + fn.Upvalues[index].SetValue(value) + } + } + } +} + +func (ls *LState) Get(idx int) LValue { + base := ls.currentLocalBase() + if idx > 0 { + reg := base + idx - 1 + if reg < ls.reg.Top() { + return ls.reg.Get(reg) + } + return LNil + } else if idx == 0 { + return LNil + } else if idx > RegistryIndex { + tidx := ls.reg.Top() + idx + if tidx < base { + return LNil + } + return ls.reg.Get(tidx) + } else { + switch idx { + case RegistryIndex: + return ls.G.Registry + case EnvironIndex: + if ls.currentFrame == nil { + return ls.Env + } + return ls.currentFrame.Fn.Env + case GlobalsIndex: + return ls.G.Global + default: + fn := ls.currentFrame.Fn + index := GlobalsIndex - idx - 1 + if index < len(fn.Upvalues) { + return fn.Upvalues[index].Value() + } + return LNil + } + } + return LNil +} + +func (ls *LState) Push(value LValue) { + ls.reg.Push(value) +} + +func (ls *LState) Pop(n int) { + for i := 0; i < n; i++ { + if ls.GetTop() == 0 { + ls.RaiseError("register underflow") + } + ls.reg.Pop() + } +} + +func (ls *LState) Insert(value LValue, index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + if reg >= top { + ls.reg.Set(reg, value) + return + } + if reg <= ls.currentLocalBase() { + reg = ls.currentLocalBase() + } + top-- + for ; top >= reg; top-- { + ls.reg.Set(top+1, ls.reg.Get(top)) + } + ls.reg.Set(reg, value) +} + +func (ls *LState) Remove(index int) { + reg := ls.indexToReg(index) + top := ls.reg.Top() + switch { + case reg >= top: + return + case reg < ls.currentLocalBase(): + return + case reg == top-1: + ls.Pop(1) + return + } + for i := reg; i < top-1; i++ { + ls.reg.Set(i, ls.reg.Get(i+1)) + } + ls.reg.SetTop(top - 1) +} + +/* }}} */ + +/* object allocation {{{ */ + +func (ls *LState) NewTable() *LTable { + return newLTable(defaultArrayCap, defaultHashCap) +} + +func (ls *LState) CreateTable(acap, hcap int) *LTable { + return newLTable(acap, hcap) +} + +// NewThread returns a new LState that shares with the original state all global objects. +// If the original state has context.Context, the new state has a new child context of the original state and this function returns its cancel function. +func (ls *LState) NewThread() (*LState, context.CancelFunc) { + thread := newLState(ls.Options) + thread.G = ls.G + thread.Env = ls.Env + var f context.CancelFunc = nil + if ls.ctx != nil { + thread.mainLoop = mainLoopWithContext + thread.ctx, f = context.WithCancel(ls.ctx) + } + return thread, f +} + +func (ls *LState) NewUserData() *LUserData { + return &LUserData{ + Env: ls.currentEnv(), + Metatable: LNil, + } +} + +func (ls *LState) NewFunction(fn LGFunction) *LFunction { + return newLFunctionG(fn, ls.currentEnv(), 0) +} + +func (ls *LState) NewClosure(fn LGFunction, upvalues ...LValue) *LFunction { + cl := newLFunctionG(fn, ls.currentEnv(), len(upvalues)) + for i, lv := range upvalues { + cl.Upvalues[i] = &Upvalue{} + cl.Upvalues[i].Close() + cl.Upvalues[i].SetValue(lv) + } + return cl +} + +/* }}} */ + +/* toType {{{ */ + +func (ls *LState) ToBool(n int) bool { + return LVAsBool(ls.Get(n)) +} + +func (ls *LState) ToInt(n int) int { + if lv, ok := ls.Get(n).(LNumber); ok { + return int(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int(num) + } + } + return 0 +} + +func (ls *LState) ToInt64(n int) int64 { + if lv, ok := ls.Get(n).(LNumber); ok { + return int64(lv) + } + if lv, ok := ls.Get(n).(LString); ok { + if num, err := parseNumber(string(lv)); err == nil { + return int64(num) + } + } + return 0 +} + +func (ls *LState) ToNumber(n int) LNumber { + return LVAsNumber(ls.Get(n)) +} + +func (ls *LState) ToString(n int) string { + return LVAsString(ls.Get(n)) +} + +func (ls *LState) ToTable(n int) *LTable { + if lv, ok := ls.Get(n).(*LTable); ok { + return lv + } + return nil +} + +func (ls *LState) ToFunction(n int) *LFunction { + if lv, ok := ls.Get(n).(*LFunction); ok { + return lv + } + return nil +} + +func (ls *LState) ToUserData(n int) *LUserData { + if lv, ok := ls.Get(n).(*LUserData); ok { + return lv + } + return nil +} + +func (ls *LState) ToThread(n int) *LState { + if lv, ok := ls.Get(n).(*LState); ok { + return lv + } + return nil +} + +/* }}} */ + +/* error & debug operations {{{ */ + +// This function is equivalent to luaL_error( http://www.lua.org/manual/5.1/manual.html#luaL_error ). +func (ls *LState) RaiseError(format string, args ...interface{}) { + ls.raiseError(1, format, args...) +} + +// This function is equivalent to lua_error( http://www.lua.org/manual/5.1/manual.html#lua_error ). +func (ls *LState) Error(lv LValue, level int) { + if str, ok := lv.(LString); ok { + ls.raiseError(level, string(str)) + } else { + if !ls.hasErrorFunc { + ls.closeAllUpvalues() + } + ls.Push(lv) + ls.Panic(ls) + } +} + +func (ls *LState) GetInfo(what string, dbg *Debug, fn LValue) (LValue, error) { + if !strings.HasPrefix(what, ">") { + fn = dbg.frame.Fn + } else { + what = what[1:] + } + f, ok := fn.(*LFunction) + if !ok { + return LNil, newApiErrorS(ApiErrorRun, "can not get debug info(an object in not a function)") + } + + retfn := false + for _, c := range what { + switch c { + case 'f': + retfn = true + case 'S': + if dbg.frame != nil && dbg.frame.Parent == nil { + dbg.What = "main" + } else if f.IsG { + dbg.What = "G" + } else if dbg.frame != nil && dbg.frame.TailCall > 0 { + dbg.What = "tail" + } else { + dbg.What = "Lua" + } + if !f.IsG { + dbg.Source = f.Proto.SourceName + dbg.LineDefined = f.Proto.LineDefined + dbg.LastLineDefined = f.Proto.LastLineDefined + } + case 'l': + if !f.IsG && dbg.frame != nil { + if dbg.frame.Pc > 0 { + dbg.CurrentLine = f.Proto.DbgSourcePositions[dbg.frame.Pc-1] + } + } else { + dbg.CurrentLine = -1 + } + case 'u': + dbg.NUpvalues = len(f.Upvalues) + case 'n': + if dbg.frame != nil { + dbg.Name = ls.rawFrameFuncName(dbg.frame) + } + default: + return LNil, newApiErrorS(ApiErrorRun, "invalid what: "+string(c)) + } + } + + if retfn { + return f, nil + } + return LNil, nil + +} + +func (ls *LState) GetStack(level int) (*Debug, bool) { + frame := ls.currentFrame + for ; level > 0 && frame != nil; frame = frame.Parent { + level-- + if !frame.Fn.IsG { + level -= frame.TailCall + } + } + + if level == 0 && frame != nil { + return &Debug{frame: frame}, true + } else if level < 0 && ls.stack.Sp() > 0 { + return &Debug{frame: ls.stack.At(0)}, true + } + return &Debug{}, false +} + +func (ls *LState) GetLocal(dbg *Debug, no int) (string, LValue) { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + return name, ls.reg.Get(frame.LocalBase + no - 1) + } + return "", LNil +} + +func (ls *LState) SetLocal(dbg *Debug, no int, lv LValue) string { + frame := dbg.frame + if name := ls.findLocal(frame, no); len(name) > 0 { + ls.reg.Set(frame.LocalBase+no-1, lv) + return name + } + return "" +} + +func (ls *LState) GetUpvalue(fn *LFunction, no int) (string, LValue) { + if fn.IsG { + return "", LNil + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + return fn.Proto.DbgUpvalues[no], fn.Upvalues[no].Value() + } + return "", LNil +} + +func (ls *LState) SetUpvalue(fn *LFunction, no int, lv LValue) string { + if fn.IsG { + return "" + } + + no-- + if no >= 0 && no < len(fn.Upvalues) { + fn.Upvalues[no].SetValue(lv) + return fn.Proto.DbgUpvalues[no] + } + return "" +} + +/* }}} */ + +/* env operations {{{ */ + +func (ls *LState) GetFEnv(obj LValue) LValue { + switch lv := obj.(type) { + case *LFunction: + return lv.Env + case *LUserData: + return lv.Env + case *LState: + return lv.Env + } + return LNil +} + +func (ls *LState) SetFEnv(obj LValue, env LValue) { + tb, ok := env.(*LTable) + if !ok { + ls.RaiseError("cannot use %v as an environment", env.Type().String()) + } + + switch lv := obj.(type) { + case *LFunction: + lv.Env = tb + case *LUserData: + lv.Env = tb + case *LState: + lv.Env = tb + } + /* do nothing */ +} + +/* }}} */ + +/* table operations {{{ */ + +func (ls *LState) RawGet(tb *LTable, key LValue) LValue { + return tb.RawGet(key) +} + +func (ls *LState) RawGetInt(tb *LTable, key int) LValue { + return tb.RawGetInt(key) +} + +func (ls *LState) GetField(obj LValue, skey string) LValue { + return ls.getFieldString(obj, skey) +} + +func (ls *LState) GetTable(obj LValue, key LValue) LValue { + return ls.getField(obj, key) +} + +func (ls *LState) RawSet(tb *LTable, key LValue, value LValue) { + if n, ok := key.(LNumber); ok && math.IsNaN(float64(n)) { + ls.RaiseError("table index is NaN") + } else if key == LNil { + ls.RaiseError("table index is nil") + } + tb.RawSet(key, value) +} + +func (ls *LState) RawSetInt(tb *LTable, key int, value LValue) { + tb.RawSetInt(key, value) +} + +func (ls *LState) SetField(obj LValue, key string, value LValue) { + ls.setFieldString(obj, key, value) +} + +func (ls *LState) SetTable(obj LValue, key LValue, value LValue) { + ls.setField(obj, key, value) +} + +func (ls *LState) ForEach(tb *LTable, cb func(LValue, LValue)) { + tb.ForEach(cb) +} + +func (ls *LState) GetGlobal(name string) LValue { + return ls.GetField(ls.Get(GlobalsIndex), name) +} + +func (ls *LState) SetGlobal(name string, value LValue) { + ls.SetField(ls.Get(GlobalsIndex), name, value) +} + +func (ls *LState) Next(tb *LTable, key LValue) (LValue, LValue) { + return tb.Next(key) +} + +/* }}} */ + +/* unary operations {{{ */ + +func (ls *LState) ObjLen(v1 LValue) int { + if v1.Type() == LTString { + return len(string(v1.(LString))) + } + op := ls.metaOp1(v1, "__len") + if op.Type() == LTFunction { + ls.Push(op) + ls.Push(v1) + ls.Call(1, 1) + ret := ls.reg.Pop() + if ret.Type() == LTNumber { + return int(ret.(LNumber)) + } + } else if v1.Type() == LTTable { + return v1.(*LTable).Len() + } + return 0 +} + +/* }}} */ + +/* binary operations {{{ */ + +func (ls *LState) Concat(values ...LValue) string { + top := ls.reg.Top() + for _, value := range values { + ls.reg.Push(value) + } + ret := stringConcat(ls, len(values), ls.reg.Top()-1) + ls.reg.SetTop(top) + return LVAsString(ret) +} + +func (ls *LState) LessThan(lhs, rhs LValue) bool { + return lessThan(ls, lhs, rhs) +} + +func (ls *LState) Equal(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, false) +} + +func (ls *LState) RawEqual(lhs, rhs LValue) bool { + return equals(ls, lhs, rhs, true) +} + +/* }}} */ + +/* register operations {{{ */ + +func (ls *LState) Register(name string, fn LGFunction) { + ls.SetGlobal(name, ls.NewFunction(fn)) +} + +/* }}} */ + +/* load and function call operations {{{ */ + +func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) { + chunk, err := parse.Parse(reader, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + proto, err := Compile(chunk, name) + if err != nil { + return nil, newApiErrorE(ApiErrorSyntax, err) + } + return newLFunctionL(proto, ls.currentEnv(), 0), nil +} + +func (ls *LState) Call(nargs, nret int) { + ls.callR(nargs, nret, -1) +} + +func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) { + err = nil + sp := ls.stack.Sp() + base := ls.reg.Top() - nargs - 1 + oldpanic := ls.Panic + ls.Panic = panicWithoutTraceback + if errfunc != nil { + ls.hasErrorFunc = true + } + defer func() { + ls.Panic = oldpanic + ls.hasErrorFunc = false + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + "\n" + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + } + if errfunc != nil { + ls.Push(errfunc) + ls.Push(err.(*ApiError).Object) + ls.Panic = panicWithoutTraceback + defer func() { + ls.Panic = oldpanic + rcv := recover() + if rcv != nil { + if _, ok := rcv.(*ApiError); !ok { + err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv)) + if ls.Options.IncludeGoStackTrace { + buf := make([]byte, 4096) + runtime.Stack(buf, false) + err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + ls.stackTrace(0) + } + } else { + err = rcv.(*ApiError) + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + } + }() + ls.Call(1, 1) + err = newApiError(ApiErrorError, ls.Get(-1)) + } else if len(err.(*ApiError).StackTrace) == 0 { + err.(*ApiError).StackTrace = ls.stackTrace(0) + } + ls.stack.SetSp(sp) + ls.currentFrame = ls.stack.Last() + ls.reg.SetTop(base) + } + ls.stack.SetSp(sp) + if sp == 0 { + ls.currentFrame = nil + } + }() + + ls.Call(nargs, nret) + + return +} + +func (ls *LState) GPCall(fn LGFunction, data LValue) error { + ls.Push(newLFunctionG(fn, ls.currentEnv(), 0)) + ls.Push(data) + return ls.PCall(1, MultRet, nil) +} + +func (ls *LState) CallByParam(cp P, args ...LValue) error { + ls.Push(cp.Fn) + for _, arg := range args { + ls.Push(arg) + } + + if cp.Protect { + return ls.PCall(len(args), cp.NRet, cp.Handler) + } + ls.Call(len(args), cp.NRet) + return nil +} + +/* }}} */ + +/* metatable operations {{{ */ + +func (ls *LState) GetMetatable(obj LValue) LValue { + return ls.metatable(obj, false) +} + +func (ls *LState) SetMetatable(obj LValue, mt LValue) { + switch mt.(type) { + case *LNilType, *LTable: + default: + ls.RaiseError("metatable must be a table or nil, but got %v", mt.Type().String()) + } + + switch v := obj.(type) { + case *LTable: + v.Metatable = mt + case *LUserData: + v.Metatable = mt + default: + ls.G.builtinMts[int(obj.Type())] = mt + } +} + +/* }}} */ + +/* coroutine operations {{{ */ + +func (ls *LState) Status(th *LState) string { + status := "suspended" + if th.Dead { + status = "dead" + } else if ls.G.CurrentThread == th { + status = "running" + } else if ls.Parent == th { + status = "normal" + } + return status +} + +func (ls *LState) Resume(th *LState, fn *LFunction, args ...LValue) (ResumeState, error, []LValue) { + isstarted := th.isStarted() + if !isstarted { + base := 0 + th.stack.Push(callFrame{ + Fn: fn, + Pc: 0, + Base: base, + LocalBase: base + 1, + ReturnBase: base, + NArgs: 0, + NRet: MultRet, + Parent: nil, + TailCall: 0, + }) + } + + if ls.G.CurrentThread == th { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a running thread"), nil + } + if th.Dead { + return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a dead thread"), nil + } + th.Parent = ls + ls.G.CurrentThread = th + if !isstarted { + cf := th.stack.Last() + th.currentFrame = cf + th.SetTop(0) + for _, arg := range args { + th.Push(arg) + } + cf.NArgs = len(args) + th.initCallFrame(cf) + th.Panic = panicWithoutTraceback + } else { + for _, arg := range args { + th.Push(arg) + } + } + top := ls.GetTop() + threadRun(th) + haserror := LVIsFalse(ls.Get(top + 1)) + ret := make([]LValue, 0, ls.GetTop()) + for idx := top + 2; idx <= ls.GetTop(); idx++ { + ret = append(ret, ls.Get(idx)) + } + if len(ret) == 0 { + ret = append(ret, LNil) + } + ls.SetTop(top) + + if haserror { + return ResumeError, newApiError(ApiErrorRun, ret[0]), nil + } else if th.stack.IsEmpty() { + return ResumeOK, nil, ret + } + return ResumeYield, nil, ret +} + +func (ls *LState) Yield(values ...LValue) int { + ls.SetTop(0) + for _, lv := range values { + ls.Push(lv) + } + return -1 +} + +func (ls *LState) XMoveTo(other *LState, n int) { + if ls == other { + return + } + top := ls.GetTop() + n = intMin(n, top) + for i := n; i > 0; i-- { + other.Push(ls.Get(top - i + 1)) + } + ls.SetTop(top - n) +} + +/* }}} */ + +/* GopherLua original APIs {{{ */ + +// Set maximum memory size. This function can only be called from the main thread. +func (ls *LState) SetMx(mx int) { + if ls.Parent != nil { + ls.RaiseError("sub threads are not allowed to set a memory limit") + } + go func() { + limit := uint64(mx * 1024 * 1024) //MB + var s runtime.MemStats + for ls.stop == 0 { + runtime.ReadMemStats(&s) + if s.Alloc >= limit { + fmt.Println("out of memory") + os.Exit(3) + } + time.Sleep(100 * time.Millisecond) + } + }() +} + +// SetContext set a context ctx to this LState. The provided ctx must be non-nil. +func (ls *LState) SetContext(ctx context.Context) { + ls.mainLoop = mainLoopWithContext + ls.ctx = ctx +} + +// Context returns the LState's context. To change the context, use WithContext. +func (ls *LState) Context() context.Context { + return ls.ctx +} + +// RemoveContext removes the context associated with this LState and returns this context. +func (ls *LState) RemoveContext() context.Context { + oldctx := ls.ctx + ls.mainLoop = mainLoop + ls.ctx = nil + return oldctx +} + +// Converts the Lua value at the given acceptable index to the chan LValue. +func (ls *LState) ToChannel(n int) chan LValue { + if lv, ok := ls.Get(n).(LChannel); ok { + return (chan LValue)(lv) + } + return nil +} + +/* }}} */ + +/* }}} */ + +// diff --git a/vendor/github.com/yuin/gopher-lua/state_test.go b/vendor/github.com/yuin/gopher-lua/state_test.go new file mode 100644 index 0000000..46fb5e5 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/state_test.go @@ -0,0 +1,433 @@ +package lua + +import ( + "context" + "strings" + "testing" + "time" +) + +func TestCallStackOverflow(t *testing.T) { + L := NewState(Options{ + CallStackSize: 3, + }) + defer L.Close() + errorIfScriptNotFail(t, L, ` + local function a() + end + local function b() + a() + end + local function c() + print(_printregs()) + b() + end + c() + `, "stack overflow") +} + +func TestSkipOpenLibs(t *testing.T) { + L := NewState(Options{SkipOpenLibs: true}) + defer L.Close() + errorIfScriptNotFail(t, L, `print("")`, + "attempt to call a non-function object") + L2 := NewState() + defer L2.Close() + errorIfScriptFail(t, L2, `print("")`) +} + +func TestGetAndReplace(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LString("a")) + L.Replace(1, LString("b")) + L.Replace(0, LString("c")) + errorIfNotEqual(t, LNil, L.Get(0)) + errorIfNotEqual(t, LNil, L.Get(-10)) + errorIfNotEqual(t, L.Env, L.Get(EnvironIndex)) + errorIfNotEqual(t, LString("b"), L.Get(1)) + L.Push(LString("c")) + L.Push(LString("d")) + L.Replace(-2, LString("e")) + errorIfNotEqual(t, LString("e"), L.Get(-2)) + registry := L.NewTable() + L.Replace(RegistryIndex, registry) + L.G.Registry = registry + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Replace(RegistryIndex, LNil) + return 0 + }, "registry must be a table") + errorIfGFuncFail(t, L, func(L *LState) int { + env := L.NewTable() + L.Replace(EnvironIndex, env) + errorIfNotEqual(t, env, L.Get(EnvironIndex)) + return 0 + }) + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Replace(EnvironIndex, LNil) + return 0 + }, "environment must be a table") + errorIfGFuncFail(t, L, func(L *LState) int { + gbl := L.NewTable() + L.Replace(GlobalsIndex, gbl) + errorIfNotEqual(t, gbl, L.G.Global) + return 0 + }) + errorIfGFuncNotFail(t, L, func(L *LState) int { + L.Replace(GlobalsIndex, LNil) + return 0 + }, "_G must be a table") + + L2 := NewState() + defer L2.Close() + clo := L2.NewClosure(func(L2 *LState) int { + L2.Replace(UpvalueIndex(1), LNumber(3)) + errorIfNotEqual(t, LNumber(3), L2.Get(UpvalueIndex(1))) + return 0 + }, LNumber(1), LNumber(2)) + L2.SetGlobal("clo", clo) + errorIfScriptFail(t, L2, `clo()`) +} + +func TestRemove(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LString("a")) + L.Push(LString("b")) + L.Push(LString("c")) + + L.Remove(4) + errorIfNotEqual(t, LString("a"), L.Get(1)) + errorIfNotEqual(t, LString("b"), L.Get(2)) + errorIfNotEqual(t, LString("c"), L.Get(3)) + errorIfNotEqual(t, 3, L.GetTop()) + + L.Remove(3) + errorIfNotEqual(t, LString("a"), L.Get(1)) + errorIfNotEqual(t, LString("b"), L.Get(2)) + errorIfNotEqual(t, LNil, L.Get(3)) + errorIfNotEqual(t, 2, L.GetTop()) + L.Push(LString("c")) + + L.Remove(-10) + errorIfNotEqual(t, LString("a"), L.Get(1)) + errorIfNotEqual(t, LString("b"), L.Get(2)) + errorIfNotEqual(t, LString("c"), L.Get(3)) + errorIfNotEqual(t, 3, L.GetTop()) + + L.Remove(2) + errorIfNotEqual(t, LString("a"), L.Get(1)) + errorIfNotEqual(t, LString("c"), L.Get(2)) + errorIfNotEqual(t, LNil, L.Get(3)) + errorIfNotEqual(t, 2, L.GetTop()) +} + +func TestToInt(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LNumber(10)) + L.Push(LString("99.9")) + L.Push(L.NewTable()) + errorIfNotEqual(t, 10, L.ToInt(1)) + errorIfNotEqual(t, 99, L.ToInt(2)) + errorIfNotEqual(t, 0, L.ToInt(3)) +} + +func TestToInt64(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LNumber(10)) + L.Push(LString("99.9")) + L.Push(L.NewTable()) + errorIfNotEqual(t, int64(10), L.ToInt64(1)) + errorIfNotEqual(t, int64(99), L.ToInt64(2)) + errorIfNotEqual(t, int64(0), L.ToInt64(3)) +} + +func TestToNumber(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LNumber(10)) + L.Push(LString("99.9")) + L.Push(L.NewTable()) + errorIfNotEqual(t, LNumber(10), L.ToNumber(1)) + errorIfNotEqual(t, LNumber(99.9), L.ToNumber(2)) + errorIfNotEqual(t, LNumber(0), L.ToNumber(3)) +} + +func TestToString(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LNumber(10)) + L.Push(LString("99.9")) + L.Push(L.NewTable()) + errorIfNotEqual(t, "10", L.ToString(1)) + errorIfNotEqual(t, "99.9", L.ToString(2)) + errorIfNotEqual(t, "", L.ToString(3)) +} + +func TestToTable(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LNumber(10)) + L.Push(LString("99.9")) + L.Push(L.NewTable()) + errorIfFalse(t, L.ToTable(1) == nil, "index 1 must be nil") + errorIfFalse(t, L.ToTable(2) == nil, "index 2 must be nil") + errorIfNotEqual(t, L.Get(3), L.ToTable(3)) +} + +func TestToFunction(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LNumber(10)) + L.Push(LString("99.9")) + L.Push(L.NewFunction(func(L *LState) int { return 0 })) + errorIfFalse(t, L.ToFunction(1) == nil, "index 1 must be nil") + errorIfFalse(t, L.ToFunction(2) == nil, "index 2 must be nil") + errorIfNotEqual(t, L.Get(3), L.ToFunction(3)) +} + +func TestToUserData(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LNumber(10)) + L.Push(LString("99.9")) + L.Push(L.NewUserData()) + errorIfFalse(t, L.ToUserData(1) == nil, "index 1 must be nil") + errorIfFalse(t, L.ToUserData(2) == nil, "index 2 must be nil") + errorIfNotEqual(t, L.Get(3), L.ToUserData(3)) +} + +func TestToChannel(t *testing.T) { + L := NewState() + defer L.Close() + L.Push(LNumber(10)) + L.Push(LString("99.9")) + var ch chan LValue + L.Push(LChannel(ch)) + errorIfFalse(t, L.ToChannel(1) == nil, "index 1 must be nil") + errorIfFalse(t, L.ToChannel(2) == nil, "index 2 must be nil") + errorIfNotEqual(t, ch, L.ToChannel(3)) +} + +func TestObjLen(t *testing.T) { + L := NewState() + defer L.Close() + errorIfNotEqual(t, 3, L.ObjLen(LString("abc"))) + tbl := L.NewTable() + tbl.Append(LTrue) + tbl.Append(LTrue) + errorIfNotEqual(t, 2, L.ObjLen(tbl)) + mt := L.NewTable() + L.SetField(mt, "__len", L.NewFunction(func(L *LState) int { + tbl := L.CheckTable(1) + L.Push(LNumber(tbl.Len() + 1)) + return 1 + })) + L.SetMetatable(tbl, mt) + errorIfNotEqual(t, 3, L.ObjLen(tbl)) + errorIfNotEqual(t, 0, L.ObjLen(LNumber(10))) +} + +func TestConcat(t *testing.T) { + L := NewState() + defer L.Close() + errorIfNotEqual(t, "a1c", L.Concat(LString("a"), LNumber(1), LString("c"))) +} + +func TestPCall(t *testing.T) { + L := NewState() + defer L.Close() + L.Register("f1", func(L *LState) int { + panic("panic!") + return 0 + }) + errorIfScriptNotFail(t, L, `f1()`, "panic!") + L.Push(L.GetGlobal("f1")) + err := L.PCall(0, 0, L.NewFunction(func(L *LState) int { + L.Push(LString("by handler")) + return 1 + })) + errorIfFalse(t, strings.Contains(err.Error(), "by handler"), "") + + err = L.PCall(0, 0, L.NewFunction(func(L *LState) int { + L.RaiseError("error!") + return 1 + })) + errorIfFalse(t, strings.Contains(err.Error(), "error!"), "") + + err = L.PCall(0, 0, L.NewFunction(func(L *LState) int { + panic("panicc!") + return 1 + })) + errorIfFalse(t, strings.Contains(err.Error(), "panicc!"), "") +} + +func TestCoroutineApi1(t *testing.T) { + L := NewState() + defer L.Close() + co, _ := L.NewThread() + errorIfScriptFail(t, L, ` + function coro(v) + assert(v == 10) + local ret1, ret2 = coroutine.yield(1,2,3) + assert(ret1 == 11) + assert(ret2 == 12) + coroutine.yield(4) + return 5 + end + `) + fn := L.GetGlobal("coro").(*LFunction) + st, err, values := L.Resume(co, fn, LNumber(10)) + errorIfNotEqual(t, ResumeYield, st) + errorIfNotNil(t, err) + errorIfNotEqual(t, 3, len(values)) + errorIfNotEqual(t, LNumber(1), values[0].(LNumber)) + errorIfNotEqual(t, LNumber(2), values[1].(LNumber)) + errorIfNotEqual(t, LNumber(3), values[2].(LNumber)) + + st, err, values = L.Resume(co, fn, LNumber(11), LNumber(12)) + errorIfNotEqual(t, ResumeYield, st) + errorIfNotNil(t, err) + errorIfNotEqual(t, 1, len(values)) + errorIfNotEqual(t, LNumber(4), values[0].(LNumber)) + + st, err, values = L.Resume(co, fn) + errorIfNotEqual(t, ResumeOK, st) + errorIfNotNil(t, err) + errorIfNotEqual(t, 1, len(values)) + errorIfNotEqual(t, LNumber(5), values[0].(LNumber)) + + L.Register("myyield", func(L *LState) int { + return L.Yield(L.ToNumber(1)) + }) + errorIfScriptFail(t, L, ` + function coro_error() + coroutine.yield(1,2,3) + myyield(4) + assert(false, "--failed--") + end + `) + fn = L.GetGlobal("coro_error").(*LFunction) + co, _ = L.NewThread() + st, err, values = L.Resume(co, fn) + errorIfNotEqual(t, ResumeYield, st) + errorIfNotNil(t, err) + errorIfNotEqual(t, 3, len(values)) + errorIfNotEqual(t, LNumber(1), values[0].(LNumber)) + errorIfNotEqual(t, LNumber(2), values[1].(LNumber)) + errorIfNotEqual(t, LNumber(3), values[2].(LNumber)) + + st, err, values = L.Resume(co, fn) + errorIfNotEqual(t, ResumeYield, st) + errorIfNotNil(t, err) + errorIfNotEqual(t, 1, len(values)) + errorIfNotEqual(t, LNumber(4), values[0].(LNumber)) + + st, err, values = L.Resume(co, fn) + errorIfNotEqual(t, ResumeError, st) + errorIfNil(t, err) + errorIfFalse(t, strings.Contains(err.Error(), "--failed--"), "error message must be '--failed--'") + st, err, values = L.Resume(co, fn) + errorIfNotEqual(t, ResumeError, st) + errorIfNil(t, err) + errorIfFalse(t, strings.Contains(err.Error(), "can not resume a dead thread"), "can not resume a dead thread") + +} + +func TestContextTimeout(t *testing.T) { + L := NewState() + defer L.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + L.SetContext(ctx) + errorIfNotEqual(t, ctx, L.Context()) + err := L.DoString(` + local clock = os.clock + function sleep(n) -- seconds + local t0 = clock() + while clock() - t0 <= n do end + end + sleep(3) + `) + errorIfNil(t, err) + errorIfFalse(t, strings.Contains(err.Error(), "context deadline exceeded"), "execution must be canceled") + + oldctx := L.RemoveContext() + errorIfNotEqual(t, ctx, oldctx) + errorIfNotNil(t, L.ctx) +} + +func TestContextCancel(t *testing.T) { + L := NewState() + defer L.Close() + ctx, cancel := context.WithCancel(context.Background()) + errch := make(chan error, 1) + L.SetContext(ctx) + go func() { + errch <- L.DoString(` + local clock = os.clock + function sleep(n) -- seconds + local t0 = clock() + while clock() - t0 <= n do end + end + sleep(3) + `) + }() + time.Sleep(1 * time.Second) + cancel() + err := <-errch + errorIfNil(t, err) + errorIfFalse(t, strings.Contains(err.Error(), "context canceled"), "execution must be canceled") +} + +func TestContextWithCroutine(t *testing.T) { + L := NewState() + defer L.Close() + ctx, cancel := context.WithCancel(context.Background()) + L.SetContext(ctx) + defer cancel() + L.DoString(` + function coro() + local i = 0 + while true do + coroutine.yield(i) + i = i+1 + end + return i + end + `) + co, cocancel := L.NewThread() + defer cocancel() + fn := L.GetGlobal("coro").(*LFunction) + _, err, values := L.Resume(co, fn) + errorIfNotNil(t, err) + errorIfNotEqual(t, LNumber(0), values[0]) + // cancel the parent context + cancel() + _, err, values = L.Resume(co, fn) + errorIfNil(t, err) + errorIfFalse(t, strings.Contains(err.Error(), "context canceled"), "coroutine execution must be canceled when the parent context is canceled") + +} + +func TestPCallAfterFail(t *testing.T) { + L := NewState() + defer L.Close() + errFn := L.NewFunction(func(L *LState) int { + L.RaiseError("error!") + return 0 + }) + changeError := L.NewFunction(func(L *LState) int { + L.Push(errFn) + err := L.PCall(0, 0, nil) + if err != nil { + L.RaiseError("A New Error") + } + return 0 + }) + L.Push(changeError) + err := L.PCall(0, 0, nil) + errorIfFalse(t, strings.Contains(err.Error(), "A New Error"), "error not propogated correctly") +} diff --git a/vendor/github.com/yuin/gopher-lua/stringlib.go b/vendor/github.com/yuin/gopher-lua/stringlib.go new file mode 100644 index 0000000..f484c2b --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/stringlib.go @@ -0,0 +1,448 @@ +package lua + +import ( + "fmt" + "strings" + + "github.com/yuin/gopher-lua/pm" +) + +const emptyLString LString = LString("") + +func OpenString(L *LState) int { + var mod *LTable + //_, ok := L.G.builtinMts[int(LTString)] + //if !ok { + mod = L.RegisterModule(StringLibName, strFuncs).(*LTable) + gmatch := L.NewClosure(strGmatch, L.NewFunction(strGmatchIter)) + mod.RawSetString("gmatch", gmatch) + mod.RawSetString("gfind", gmatch) + mod.RawSetString("__index", mod) + L.G.builtinMts[int(LTString)] = mod + //} + L.Push(mod) + return 1 +} + +var strFuncs = map[string]LGFunction{ + "byte": strByte, + "char": strChar, + "dump": strDump, + "find": strFind, + "format": strFormat, + "gsub": strGsub, + "len": strLen, + "lower": strLower, + "match": strMatch, + "rep": strRep, + "reverse": strReverse, + "sub": strSub, + "upper": strUpper, +} + +func strByte(L *LState) int { + str := L.CheckString(1) + start := L.OptInt(2, 1) - 1 + end := L.OptInt(3, -1) + l := len(str) + if start < 0 { + start = l + start + 1 + } + if end < 0 { + end = l + end + 1 + } + + if L.GetTop() == 2 { + if start < 0 || start >= l { + return 0 + } + L.Push(LNumber(str[start])) + return 1 + } + + start = intMax(start, 0) + end = intMin(end, l) + if end < 0 || end <= start || start >= l { + return 0 + } + + for i := start; i < end; i++ { + L.Push(LNumber(str[i])) + } + return end - start +} + +func strChar(L *LState) int { + top := L.GetTop() + bytes := make([]byte, L.GetTop()) + for i := 1; i <= top; i++ { + bytes[i-1] = uint8(L.CheckInt(i)) + } + L.Push(LString(string(bytes))) + return 1 +} + +func strDump(L *LState) int { + L.RaiseError("GopherLua does not support the string.dump") + return 0 +} + +func strFind(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + if len(pattern) == 0 { + L.Push(LNumber(1)) + L.Push(LNumber(0)) + return 2 + } + init := luaIndex2StringIndex(str, L.OptInt(3, 1), true) + plain := false + if L.GetTop() == 4 { + plain = LVAsBool(L.Get(4)) + } + + if plain { + pos := strings.Index(str[init:], pattern) + if pos < 0 { + L.Push(LNil) + return 1 + } + L.Push(LNumber(init+pos) + 1) + L.Push(LNumber(init + pos + len(pattern))) + return 2 + } + + mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), init, 1) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.Push(LNil) + return 1 + } + md := mds[0] + L.Push(LNumber(md.Capture(0) + 1)) + L.Push(LNumber(md.Capture(1))) + for i := 2; i < md.CaptureLength(); i += 2 { + if md.IsPosCapture(i) { + L.Push(LNumber(md.Capture(i))) + } else { + L.Push(LString(str[md.Capture(i):md.Capture(i+1)])) + } + } + return md.CaptureLength()/2 + 1 +} + +func strFormat(L *LState) int { + str := L.CheckString(1) + args := make([]interface{}, L.GetTop()-1) + top := L.GetTop() + for i := 2; i <= top; i++ { + args[i-2] = L.Get(i) + } + npat := strings.Count(str, "%") - strings.Count(str, "%%") + L.Push(LString(fmt.Sprintf(str, args[:intMin(npat, len(args))]...))) + return 1 +} + +func strGsub(L *LState) int { + str := L.CheckString(1) + pat := L.CheckString(2) + L.CheckTypes(3, LTString, LTTable, LTFunction) + repl := L.CheckAny(3) + limit := L.OptInt(4, -1) + + mds, err := pm.Find(pat, unsafeFastStringToReadOnlyBytes(str), 0, limit) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.SetTop(1) + L.Push(LNumber(0)) + return 2 + } + switch lv := repl.(type) { + case LString: + L.Push(LString(strGsubStr(L, str, string(lv), mds))) + case *LTable: + L.Push(LString(strGsubTable(L, str, lv, mds))) + case *LFunction: + L.Push(LString(strGsubFunc(L, str, lv, mds))) + } + L.Push(LNumber(len(mds))) + return 2 +} + +type replaceInfo struct { + Indicies []int + String string +} + +func checkCaptureIndex(L *LState, m *pm.MatchData, idx int) { + if idx <= 2 { + return + } + if idx >= m.CaptureLength() { + L.RaiseError("invalid capture index") + } +} + +func capturedString(L *LState, m *pm.MatchData, str string, idx int) string { + checkCaptureIndex(L, m, idx) + if idx >= m.CaptureLength() && idx == 2 { + idx = 0 + } + if m.IsPosCapture(idx) { + return fmt.Sprint(m.Capture(idx)) + } else { + return str[m.Capture(idx):m.Capture(idx+1)] + } + +} + +func strGsubDoReplace(str string, info []replaceInfo) string { + offset := 0 + buf := []byte(str) + for _, replace := range info { + oldlen := len(buf) + b1 := append([]byte(""), buf[0:offset+replace.Indicies[0]]...) + b2 := []byte("") + index2 := offset + replace.Indicies[1] + if index2 <= len(buf) { + b2 = append(b2, buf[index2:len(buf)]...) + } + buf = append(b1, replace.String...) + buf = append(buf, b2...) + offset += len(buf) - oldlen + } + return string(buf) +} + +func strGsubStr(L *LState, str string, repl string, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + start, end := match.Capture(0), match.Capture(1) + sc := newFlagScanner('%', "", "", repl) + for c, eos := sc.Next(); !eos; c, eos = sc.Next() { + if !sc.ChangeFlag { + if sc.HasFlag { + if c >= '0' && c <= '9' { + sc.AppendString(capturedString(L, match, str, 2*(int(c)-48))) + } else { + sc.AppendChar('%') + sc.AppendChar(c) + } + sc.HasFlag = false + } else { + sc.AppendChar(c) + } + } + } + infoList = append(infoList, replaceInfo{[]int{start, end}, sc.String()}) + } + + return strGsubDoReplace(str, infoList) +} + +func strGsubTable(L *LState, str string, repl *LTable, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + idx := 0 + if match.CaptureLength() > 2 { // has captures + idx = 2 + } + var value LValue + if match.IsPosCapture(idx) { + value = L.GetTable(repl, LNumber(match.Capture(idx))) + } else { + value = L.GetField(repl, str[match.Capture(idx):match.Capture(idx+1)]) + } + if !LVIsFalse(value) { + infoList = append(infoList, replaceInfo{[]int{match.Capture(0), match.Capture(1)}, LVAsString(value)}) + } + } + return strGsubDoReplace(str, infoList) +} + +func strGsubFunc(L *LState, str string, repl *LFunction, matches []*pm.MatchData) string { + infoList := make([]replaceInfo, 0, len(matches)) + for _, match := range matches { + start, end := match.Capture(0), match.Capture(1) + L.Push(repl) + nargs := 0 + if match.CaptureLength() > 2 { // has captures + for i := 2; i < match.CaptureLength(); i += 2 { + if match.IsPosCapture(i) { + L.Push(LNumber(match.Capture(i))) + } else { + L.Push(LString(capturedString(L, match, str, i))) + } + nargs++ + } + } else { + L.Push(LString(capturedString(L, match, str, 0))) + nargs++ + } + L.Call(nargs, 1) + ret := L.reg.Pop() + if !LVIsFalse(ret) { + infoList = append(infoList, replaceInfo{[]int{start, end}, LVAsString(ret)}) + } + } + return strGsubDoReplace(str, infoList) +} + +type strMatchData struct { + str string + pos int + matches []*pm.MatchData +} + +func strGmatchIter(L *LState) int { + md := L.CheckUserData(1).Value.(*strMatchData) + str := md.str + matches := md.matches + idx := md.pos + md.pos += 1 + if idx == len(matches) { + return 0 + } + L.Push(L.Get(1)) + match := matches[idx] + if match.CaptureLength() == 2 { + L.Push(LString(str[match.Capture(0):match.Capture(1)])) + return 1 + } + + for i := 2; i < match.CaptureLength(); i += 2 { + if match.IsPosCapture(i) { + L.Push(LNumber(match.Capture(i))) + } else { + L.Push(LString(str[match.Capture(i):match.Capture(i+1)])) + } + } + return match.CaptureLength()/2 - 1 +} + +func strGmatch(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + mds, err := pm.Find(pattern, []byte(str), 0, -1) + if err != nil { + L.RaiseError(err.Error()) + } + L.Push(L.Get(UpvalueIndex(1))) + ud := L.NewUserData() + ud.Value = &strMatchData{str, 0, mds} + L.Push(ud) + return 2 +} + +func strLen(L *LState) int { + str := L.CheckString(1) + L.Push(LNumber(len(str))) + return 1 +} + +func strLower(L *LState) int { + str := L.CheckString(1) + L.Push(LString(strings.ToLower(str))) + return 1 +} + +func strMatch(L *LState) int { + str := L.CheckString(1) + pattern := L.CheckString(2) + offset := L.OptInt(3, 1) + l := len(str) + if offset < 0 { + offset = l + offset + 1 + } + offset-- + if offset < 0 { + offset = 0 + } + + mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), offset, 1) + if err != nil { + L.RaiseError(err.Error()) + } + if len(mds) == 0 { + L.Push(LNil) + return 0 + } + md := mds[0] + nsubs := md.CaptureLength() / 2 + switch nsubs { + case 1: + L.Push(LString(str[md.Capture(0):md.Capture(1)])) + return 1 + default: + for i := 2; i < md.CaptureLength(); i += 2 { + if md.IsPosCapture(i) { + L.Push(LNumber(md.Capture(i))) + } else { + L.Push(LString(str[md.Capture(i):md.Capture(i+1)])) + } + } + return nsubs - 1 + } +} + +func strRep(L *LState) int { + str := L.CheckString(1) + n := L.CheckInt(2) + if n < 0 { + L.Push(emptyLString) + } else { + L.Push(LString(strings.Repeat(str, n))) + } + return 1 +} + +func strReverse(L *LState) int { + str := L.CheckString(1) + bts := []byte(str) + out := make([]byte, len(bts)) + for i, j := 0, len(bts)-1; j >= 0; i, j = i+1, j-1 { + out[i] = bts[j] + } + L.Push(LString(string(out))) + return 1 +} + +func strSub(L *LState) int { + str := L.CheckString(1) + start := luaIndex2StringIndex(str, L.CheckInt(2), true) + end := luaIndex2StringIndex(str, L.OptInt(3, -1), false) + l := len(str) + if start >= l || end < start { + L.Push(emptyLString) + } else { + L.Push(LString(str[start:end])) + } + return 1 +} + +func strUpper(L *LState) int { + str := L.CheckString(1) + L.Push(LString(strings.ToUpper(str))) + return 1 +} + +func luaIndex2StringIndex(str string, i int, start bool) int { + if start && i != 0 { + i -= 1 + } + l := len(str) + if i < 0 { + i = l + i + 1 + } + i = intMax(0, i) + if !start && i > l { + i = l + } + return i +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/table.go b/vendor/github.com/yuin/gopher-lua/table.go new file mode 100644 index 0000000..863a5c6 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/table.go @@ -0,0 +1,374 @@ +package lua + +const defaultArrayCap = 32 +const defaultHashCap = 32 + +type lValueArraySorter struct { + L *LState + Fn *LFunction + Values []LValue +} + +func (lv lValueArraySorter) Len() int { + return len(lv.Values) +} + +func (lv lValueArraySorter) Swap(i, j int) { + lv.Values[i], lv.Values[j] = lv.Values[j], lv.Values[i] +} + +func (lv lValueArraySorter) Less(i, j int) bool { + if lv.Fn != nil { + lv.L.Push(lv.Fn) + lv.L.Push(lv.Values[i]) + lv.L.Push(lv.Values[j]) + lv.L.Call(2, 1) + return LVAsBool(lv.L.reg.Pop()) + } + return lessThan(lv.L, lv.Values[i], lv.Values[j]) +} + +func newLTable(acap int, hcap int) *LTable { + if acap < 0 { + acap = 0 + } + if hcap < 0 { + hcap = 0 + } + tb := <able{} + tb.Metatable = LNil + if acap != 0 { + tb.array = make([]LValue, 0, acap) + } + if hcap != 0 { + tb.strdict = make(map[string]LValue, hcap) + } + return tb +} + +// Len returns length of this LTable. +func (tb *LTable) Len() int { + if tb.array == nil { + return 0 + } + var prev LValue = LNil + for i := len(tb.array) - 1; i >= 0; i-- { + v := tb.array[i] + if prev == LNil && v != LNil { + return i + 1 + } + prev = v + } + return 0 +} + +// Append appends a given LValue to this LTable. +func (tb *LTable) Append(value LValue) { + if value == LNil { + return + } + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + tb.array = append(tb.array, value) +} + +// Insert inserts a given LValue at position `i` in this table. +func (tb *LTable) Insert(i int, value LValue) { + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + if i > len(tb.array) { + tb.RawSetInt(i, value) + return + } + if i <= 0 { + tb.RawSet(LNumber(i), value) + return + } + i -= 1 + tb.array = append(tb.array, LNil) + copy(tb.array[i+1:], tb.array[i:]) + tb.array[i] = value +} + +// MaxN returns a maximum number key that nil value does not exist before it. +func (tb *LTable) MaxN() int { + if tb.array == nil { + return 0 + } + for i := len(tb.array) - 1; i >= 0; i-- { + if tb.array[i] != LNil { + return i + 1 + } + } + return 0 +} + +// Remove removes from this table the element at a given position. +func (tb *LTable) Remove(pos int) LValue { + if tb.array == nil { + return LNil + } + i := pos - 1 + larray := len(tb.array) + oldval := LNil + switch { + case i >= larray: + // nothing to do + case i == larray-1 || i < 0: + oldval = tb.array[larray-1] + tb.array = tb.array[:larray-1] + default: + oldval = tb.array[i] + copy(tb.array[i:], tb.array[i+1:]) + tb.array[larray-1] = nil + tb.array = tb.array[:larray-1] + } + return oldval +} + +// RawSet sets a given LValue to a given index without the __newindex metamethod. +// It is recommended to use `RawSetString` or `RawSetInt` for performance +// if you already know the given LValue is a string or number. +func (tb *LTable) RawSet(key LValue, value LValue) { + switch v := key.(type) { + case LNumber: + if isArrayKey(v) { + if tb.array == nil { + tb.array = make([]LValue, 0, defaultArrayCap) + } + index := int(v) - 1 + alen := len(tb.array) + switch { + case index == alen: + tb.array = append(tb.array, value) + case index > alen: + for i := 0; i < (index - alen); i++ { + tb.array = append(tb.array, LNil) + } + tb.array = append(tb.array, value) + case index < alen: + tb.array[index] = value + } + return + } + case LString: + tb.RawSetString(string(v), value) + return + } + + tb.RawSetH(key, value) +} + +// RawSetInt sets a given LValue at a position `key` without the __newindex metamethod. +func (tb *LTable) RawSetInt(key int, value LValue) { + if key < 1 || key >= MaxArrayIndex { + tb.RawSetH(LNumber(key), value) + return + } + if tb.array == nil { + tb.array = make([]LValue, 0, 32) + } + index := key - 1 + alen := len(tb.array) + switch { + case index == alen: + tb.array = append(tb.array, value) + case index > alen: + for i := 0; i < (index - alen); i++ { + tb.array = append(tb.array, LNil) + } + tb.array = append(tb.array, value) + case index < alen: + tb.array[index] = value + } +} + +// RawSetString sets a given LValue to a given string index without the __newindex metamethod. +func (tb *LTable) RawSetString(key string, value LValue) { + if tb.strdict == nil { + tb.strdict = make(map[string]LValue, defaultHashCap) + } + if tb.keys == nil { + tb.keys = []LValue{} + tb.k2i = map[LValue]int{} + } + + if value == LNil { + // TODO tb.keys and tb.k2i should also be removed + delete(tb.strdict, key) + } else { + tb.strdict[key] = value + lkey := LString(key) + if _, ok := tb.k2i[lkey]; !ok { + tb.k2i[lkey] = len(tb.keys) + tb.keys = append(tb.keys, lkey) + } + } +} + +// RawSetH sets a given LValue to a given index without the __newindex metamethod. +func (tb *LTable) RawSetH(key LValue, value LValue) { + if s, ok := key.(LString); ok { + tb.RawSetString(string(s), value) + return + } + if tb.dict == nil { + tb.dict = make(map[LValue]LValue, len(tb.strdict)) + } + if tb.keys == nil { + tb.keys = []LValue{} + tb.k2i = map[LValue]int{} + } + + if value == LNil { + // TODO tb.keys and tb.k2i should also be removed + delete(tb.dict, key) + } else { + tb.dict[key] = value + if _, ok := tb.k2i[key]; !ok { + tb.k2i[key] = len(tb.keys) + tb.keys = append(tb.keys, key) + } + } +} + +// RawGet returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGet(key LValue) LValue { + switch v := key.(type) { + case LNumber: + if isArrayKey(v) { + if tb.array == nil { + return LNil + } + index := int(v) - 1 + if index >= len(tb.array) { + return LNil + } + return tb.array[index] + } + case LString: + if tb.strdict == nil { + return LNil + } + if ret, ok := tb.strdict[string(v)]; ok { + return ret + } + return LNil + } + if tb.dict == nil { + return LNil + } + if v, ok := tb.dict[key]; ok { + return v + } + return LNil +} + +// RawGetInt returns an LValue at position `key` without __index metamethod. +func (tb *LTable) RawGetInt(key int) LValue { + if tb.array == nil { + return LNil + } + index := int(key) - 1 + if index >= len(tb.array) || index < 0 { + return LNil + } + return tb.array[index] +} + +// RawGet returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGetH(key LValue) LValue { + if s, sok := key.(LString); sok { + if tb.strdict == nil { + return LNil + } + if v, vok := tb.strdict[string(s)]; vok { + return v + } + return LNil + } + if tb.dict == nil { + return LNil + } + if v, ok := tb.dict[key]; ok { + return v + } + return LNil +} + +// RawGetString returns an LValue associated with a given key without __index metamethod. +func (tb *LTable) RawGetString(key string) LValue { + if tb.strdict == nil { + return LNil + } + if v, vok := tb.strdict[string(key)]; vok { + return v + } + return LNil +} + +// ForEach iterates over this table of elements, yielding each in turn to a given function. +func (tb *LTable) ForEach(cb func(LValue, LValue)) { + if tb.array != nil { + for i, v := range tb.array { + if v != LNil { + cb(LNumber(i+1), v) + } + } + } + if tb.strdict != nil { + for k, v := range tb.strdict { + if v != LNil { + cb(LString(k), v) + } + } + } + if tb.dict != nil { + for k, v := range tb.dict { + if v != LNil { + cb(k, v) + } + } + } +} + +// This function is equivalent to lua_next ( http://www.lua.org/manual/5.1/manual.html#lua_next ). +func (tb *LTable) Next(key LValue) (LValue, LValue) { + init := false + if key == LNil { + key = LNumber(0) + init = true + } + + if init || key != LNumber(0) { + if kv, ok := key.(LNumber); ok && isInteger(kv) && int(kv) >= 0 && kv < LNumber(MaxArrayIndex) { + index := int(kv) + if tb.array != nil { + for ; index < len(tb.array); index++ { + if v := tb.array[index]; v != LNil { + return LNumber(index + 1), v + } + } + } + if tb.array == nil || index == len(tb.array) { + if (tb.dict == nil || len(tb.dict) == 0) && (tb.strdict == nil || len(tb.strdict) == 0) { + return LNil, LNil + } + key = tb.keys[0] + if v := tb.RawGetH(key); v != LNil { + return key, v + } + } + } + } + + for i := tb.k2i[key] + 1; i < len(tb.keys); i++ { + key := tb.keys[i] + if v := tb.RawGetH(key); v != LNil { + return key, v + } + } + return LNil, LNil +} diff --git a/vendor/github.com/yuin/gopher-lua/table_test.go b/vendor/github.com/yuin/gopher-lua/table_test.go new file mode 100644 index 0000000..6248296 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/table_test.go @@ -0,0 +1,187 @@ +package lua + +import ( + "testing" +) + +func TestTableNewLTable(t *testing.T) { + tbl := newLTable(-1, -2) + errorIfNotEqual(t, 0, cap(tbl.array)) + + tbl = newLTable(10, 9) + errorIfNotEqual(t, 10, cap(tbl.array)) +} + +func TestTableLen(t *testing.T) { + tbl := newLTable(0, 0) + tbl.RawSetInt(10, LNil) + tbl.RawSetInt(9, LNumber(10)) + tbl.RawSetInt(8, LNil) + tbl.RawSetInt(7, LNumber(10)) + errorIfNotEqual(t, 9, tbl.Len()) + + tbl = newLTable(0, 0) + tbl.Append(LTrue) + tbl.Append(LTrue) + tbl.Append(LTrue) + errorIfNotEqual(t, 3, tbl.Len()) +} + +func TestTableInsert(t *testing.T) { + tbl := newLTable(0, 0) + tbl.Append(LTrue) + tbl.Append(LTrue) + tbl.Append(LTrue) + + tbl.Insert(5, LFalse) + errorIfNotEqual(t, LFalse, tbl.RawGetInt(5)) + errorIfNotEqual(t, 5, tbl.Len()) + + tbl.Insert(-10, LFalse) + errorIfNotEqual(t, LFalse, tbl.RawGet(LNumber(-10))) + errorIfNotEqual(t, 5, tbl.Len()) + + tbl = newLTable(0, 0) + tbl.Append(LNumber(1)) + tbl.Append(LNumber(2)) + tbl.Append(LNumber(3)) + tbl.Insert(1, LNumber(10)) + errorIfNotEqual(t, LNumber(10), tbl.RawGetInt(1)) + errorIfNotEqual(t, LNumber(1), tbl.RawGetInt(2)) + errorIfNotEqual(t, LNumber(2), tbl.RawGetInt(3)) + errorIfNotEqual(t, LNumber(3), tbl.RawGetInt(4)) + errorIfNotEqual(t, 4, tbl.Len()) + + tbl = newLTable(0, 0) + tbl.Insert(5, LNumber(10)) + errorIfNotEqual(t, LNumber(10), tbl.RawGetInt(5)) + +} + +func TestTableMaxN(t *testing.T) { + tbl := newLTable(0, 0) + tbl.Append(LTrue) + tbl.Append(LTrue) + tbl.Append(LTrue) + errorIfNotEqual(t, 3, tbl.MaxN()) + + tbl = newLTable(0, 0) + errorIfNotEqual(t, 0, tbl.MaxN()) + + tbl = newLTable(10, 0) + errorIfNotEqual(t, 0, tbl.MaxN()) +} + +func TestTableRemove(t *testing.T) { + tbl := newLTable(0, 0) + errorIfNotEqual(t, LNil, tbl.Remove(10)) + tbl.Append(LTrue) + errorIfNotEqual(t, LNil, tbl.Remove(10)) + + tbl.Append(LFalse) + tbl.Append(LTrue) + errorIfNotEqual(t, LFalse, tbl.Remove(2)) + errorIfNotEqual(t, 2, tbl.MaxN()) + tbl.Append(LFalse) + errorIfNotEqual(t, LFalse, tbl.Remove(-1)) + errorIfNotEqual(t, 2, tbl.MaxN()) + +} + +func TestTableRawSetInt(t *testing.T) { + tbl := newLTable(0, 0) + tbl.RawSetInt(MaxArrayIndex+1, LTrue) + errorIfNotEqual(t, 0, tbl.MaxN()) + errorIfNotEqual(t, LTrue, tbl.RawGet(LNumber(MaxArrayIndex+1))) + + tbl.RawSetInt(1, LTrue) + tbl.RawSetInt(3, LTrue) + errorIfNotEqual(t, 3, tbl.MaxN()) + errorIfNotEqual(t, LTrue, tbl.RawGetInt(1)) + errorIfNotEqual(t, LNil, tbl.RawGetInt(2)) + errorIfNotEqual(t, LTrue, tbl.RawGetInt(3)) + tbl.RawSetInt(2, LTrue) + errorIfNotEqual(t, LTrue, tbl.RawGetInt(1)) + errorIfNotEqual(t, LTrue, tbl.RawGetInt(2)) + errorIfNotEqual(t, LTrue, tbl.RawGetInt(3)) +} + +func TestTableRawSetH(t *testing.T) { + tbl := newLTable(0, 0) + tbl.RawSetH(LString("key"), LTrue) + tbl.RawSetH(LString("key"), LNil) + _, found := tbl.dict[LString("key")] + errorIfNotEqual(t, false, found) + + tbl.RawSetH(LTrue, LTrue) + tbl.RawSetH(LTrue, LNil) + _, foundb := tbl.dict[LTrue] + errorIfNotEqual(t, false, foundb) +} + +func TestTableRawGetH(t *testing.T) { + tbl := newLTable(0, 0) + errorIfNotEqual(t, LNil, tbl.RawGetH(LNumber(1))) + errorIfNotEqual(t, LNil, tbl.RawGetH(LString("key0"))) + tbl.RawSetH(LString("key0"), LTrue) + tbl.RawSetH(LString("key1"), LFalse) + tbl.RawSetH(LNumber(1), LTrue) + errorIfNotEqual(t, LTrue, tbl.RawGetH(LString("key0"))) + errorIfNotEqual(t, LTrue, tbl.RawGetH(LNumber(1))) + errorIfNotEqual(t, LNil, tbl.RawGetH(LString("notexist"))) + errorIfNotEqual(t, LNil, tbl.RawGetH(LTrue)) +} + +func TestTableForEach(t *testing.T) { + tbl := newLTable(0, 0) + tbl.Append(LNumber(1)) + tbl.Append(LNumber(2)) + tbl.Append(LNumber(3)) + tbl.Append(LNil) + tbl.Append(LNumber(5)) + + tbl.RawSetH(LString("a"), LString("a")) + tbl.RawSetH(LString("b"), LString("b")) + tbl.RawSetH(LString("c"), LString("c")) + + tbl.RawSetH(LTrue, LString("true")) + tbl.RawSetH(LFalse, LString("false")) + + tbl.ForEach(func(key, value LValue) { + switch k := key.(type) { + case LBool: + switch bool(k) { + case true: + errorIfNotEqual(t, LString("true"), value) + case false: + errorIfNotEqual(t, LString("false"), value) + default: + t.Fail() + } + case LNumber: + switch int(k) { + case 1: + errorIfNotEqual(t, LNumber(1), value) + case 2: + errorIfNotEqual(t, LNumber(2), value) + case 3: + errorIfNotEqual(t, LNumber(3), value) + case 4: + errorIfNotEqual(t, LNumber(5), value) + default: + t.Fail() + } + case LString: + switch string(k) { + case "a": + errorIfNotEqual(t, LString("a"), value) + case "b": + errorIfNotEqual(t, LString("b"), value) + case "c": + errorIfNotEqual(t, LString("c"), value) + default: + t.Fail() + } + } + }) +} diff --git a/vendor/github.com/yuin/gopher-lua/tablelib.go b/vendor/github.com/yuin/gopher-lua/tablelib.go new file mode 100644 index 0000000..f3f4607 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/tablelib.go @@ -0,0 +1,100 @@ +package lua + +import ( + "sort" +) + +func OpenTable(L *LState) int { + tabmod := L.RegisterModule(TabLibName, tableFuncs) + L.Push(tabmod) + return 1 +} + +var tableFuncs = map[string]LGFunction{ + "getn": tableGetN, + "concat": tableConcat, + "insert": tableInsert, + "maxn": tableMaxN, + "remove": tableRemove, + "sort": tableSort, +} + +func tableSort(L *LState) int { + tbl := L.CheckTable(1) + sorter := lValueArraySorter{L, nil, tbl.array} + if L.GetTop() != 1 { + sorter.Fn = L.CheckFunction(2) + } + sort.Sort(sorter) + return 0 +} + +func tableGetN(L *LState) int { + L.Push(LNumber(L.CheckTable(1).Len())) + return 1 +} + +func tableMaxN(L *LState) int { + L.Push(LNumber(L.CheckTable(1).MaxN())) + return 1 +} + +func tableRemove(L *LState) int { + tbl := L.CheckTable(1) + if L.GetTop() == 1 { + L.Push(tbl.Remove(-1)) + } else { + L.Push(tbl.Remove(L.CheckInt(2))) + } + return 1 +} + +func tableConcat(L *LState) int { + tbl := L.CheckTable(1) + sep := LString(L.OptString(2, "")) + i := L.OptInt(3, 1) + j := L.OptInt(4, tbl.Len()) + if L.GetTop() == 3 { + if i > tbl.Len() || i < 1 { + L.Push(emptyLString) + return 1 + } + } + i = intMax(intMin(i, tbl.Len()), 1) + j = intMin(intMin(j, tbl.Len()), tbl.Len()) + if i > j { + L.Push(emptyLString) + return 1 + } + //TODO should flushing? + retbottom := L.GetTop() + for ; i <= j; i++ { + v := tbl.RawGetInt(i) + if !LVCanConvToString(v) { + L.RaiseError("invalid value (%s) at index %d in table for concat", v.Type().String(), i) + } + L.Push(v) + if i != j { + L.Push(sep) + } + } + L.Push(stringConcat(L, L.GetTop()-retbottom, L.reg.Top()-1)) + return 1 +} + +func tableInsert(L *LState) int { + tbl := L.CheckTable(1) + nargs := L.GetTop() + if nargs == 1 { + L.RaiseError("wrong number of arguments") + } + + if L.GetTop() == 2 { + tbl.Append(L.Get(2)) + return 0 + } + tbl.Insert(int(L.CheckInt(2)), L.CheckAny(3)) + return 0 +} + +// diff --git a/vendor/github.com/yuin/gopher-lua/testutils_test.go b/vendor/github.com/yuin/gopher-lua/testutils_test.go new file mode 100644 index 0000000..e8c8578 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/testutils_test.go @@ -0,0 +1,78 @@ +package lua + +import ( + "fmt" + "path/filepath" + "regexp" + "runtime" + "testing" +) + +func positionString(level int) string { + _, file, line, _ := runtime.Caller(level + 1) + return fmt.Sprintf("%v:%v:", filepath.Base(file), line) +} + +func errorIfNotEqual(t *testing.T, v1, v2 interface{}) { + if v1 != v2 { + t.Errorf("%v '%v' expected, but got '%v'", positionString(1), v1, v2) + } +} + +func errorIfFalse(t *testing.T, cond bool, msg string, args ...interface{}) { + if !cond { + if len(args) > 0 { + t.Errorf("%v %v", positionString(1), fmt.Sprintf(msg, args...)) + } else { + t.Errorf("%v %v", positionString(1), msg) + } + } +} + +func errorIfNotNil(t *testing.T, v1 interface{}) { + if fmt.Sprint(v1) != "" { + t.Errorf("%v nil expected, but got '%v'", positionString(1), v1) + } +} + +func errorIfNil(t *testing.T, v1 interface{}) { + if fmt.Sprint(v1) == "" { + t.Errorf("%v non-nil value expected, but got nil", positionString(1)) + } +} + +func errorIfScriptFail(t *testing.T, L *LState, script string) { + if err := L.DoString(script); err != nil { + t.Errorf("%v %v", positionString(1), err.Error()) + } +} + +func errorIfGFuncFail(t *testing.T, L *LState, f LGFunction) { + if err := L.GPCall(f, LNil); err != nil { + t.Errorf("%v %v", positionString(1), err.Error()) + } +} + +func errorIfScriptNotFail(t *testing.T, L *LState, script string, pattern string) { + if err := L.DoString(script); err != nil { + reg := regexp.MustCompile(pattern) + if len(reg.FindStringIndex(err.Error())) == 0 { + t.Errorf("%v error message '%v' does not contains given pattern string '%v'.", positionString(1), err.Error(), pattern) + return + } + return + } + t.Errorf("%v script should fail", positionString(1)) +} + +func errorIfGFuncNotFail(t *testing.T, L *LState, f LGFunction, pattern string) { + if err := L.GPCall(f, LNil); err != nil { + reg := regexp.MustCompile(pattern) + if len(reg.FindStringIndex(err.Error())) == 0 { + t.Errorf("%v error message '%v' does not contains given pattern string '%v'.", positionString(1), err.Error(), pattern) + return + } + return + } + t.Errorf("%v LGFunction should fail", positionString(1)) +} diff --git a/vendor/github.com/yuin/gopher-lua/utils.go b/vendor/github.com/yuin/gopher-lua/utils.go new file mode 100644 index 0000000..cb71bf5 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/utils.go @@ -0,0 +1,262 @@ +package lua + +import ( + "bufio" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "time" + "unsafe" +) + +func intMin(a, b int) int { + if a < b { + return a + } else { + return b + } +} + +func intMax(a, b int) int { + if a > b { + return a + } else { + return b + } +} + +func defaultFormat(v interface{}, f fmt.State, c rune) { + buf := make([]string, 0, 10) + buf = append(buf, "%") + for i := 0; i < 128; i++ { + if f.Flag(i) { + buf = append(buf, string(i)) + } + } + + if w, ok := f.Width(); ok { + buf = append(buf, strconv.Itoa(w)) + } + if p, ok := f.Precision(); ok { + buf = append(buf, "."+strconv.Itoa(p)) + } + buf = append(buf, string(c)) + format := strings.Join(buf, "") + fmt.Fprintf(f, format, v) +} + +type flagScanner struct { + flag byte + start string + end string + buf []byte + str string + Length int + Pos int + HasFlag bool + ChangeFlag bool +} + +func newFlagScanner(flag byte, start, end, str string) *flagScanner { + return &flagScanner{flag, start, end, make([]byte, 0, len(str)), str, len(str), 0, false, false} +} + +func (fs *flagScanner) AppendString(str string) { fs.buf = append(fs.buf, str...) } + +func (fs *flagScanner) AppendChar(ch byte) { fs.buf = append(fs.buf, ch) } + +func (fs *flagScanner) String() string { return string(fs.buf) } + +func (fs *flagScanner) Next() (byte, bool) { + c := byte('\000') + fs.ChangeFlag = false + if fs.Pos == fs.Length { + if fs.HasFlag { + fs.AppendString(fs.end) + } + return c, true + } else { + c = fs.str[fs.Pos] + if c == fs.flag { + if fs.Pos < (fs.Length-1) && fs.str[fs.Pos+1] == fs.flag { + fs.HasFlag = false + fs.AppendChar(fs.flag) + fs.Pos += 2 + return fs.Next() + } else if fs.Pos != fs.Length-1 { + if fs.HasFlag { + fs.AppendString(fs.end) + } + fs.AppendString(fs.start) + fs.ChangeFlag = true + fs.HasFlag = true + } + } + } + fs.Pos++ + return c, false +} + +var cDateFlagToGo = map[byte]string{ + 'a': "mon", 'A': "Monday", 'b': "Jan", 'B': "January", 'c': "02 Jan 06 15:04 MST", 'd': "02", + 'F': "2006-01-02", 'H': "15", 'I': "03", 'm': "01", 'M': "04", 'p': "PM", 'P': "pm", 'S': "05", + 'x': "15/04/05", 'X': "15:04:05", 'y': "06", 'Y': "2006", 'z': "-0700", 'Z': "MST"} + +func strftime(t time.Time, cfmt string) string { + sc := newFlagScanner('%', "", "", cfmt) + for c, eos := sc.Next(); !eos; c, eos = sc.Next() { + if !sc.ChangeFlag { + if sc.HasFlag { + if v, ok := cDateFlagToGo[c]; ok { + sc.AppendString(t.Format(v)) + } else { + switch c { + case 'w': + sc.AppendString(fmt.Sprint(int(t.Weekday()))) + default: + sc.AppendChar('%') + sc.AppendChar(c) + } + } + sc.HasFlag = false + } else { + sc.AppendChar(c) + } + } + } + + return sc.String() +} + +func isInteger(v LNumber) bool { + return float64(v) == float64(int64(v)) + //_, frac := math.Modf(float64(v)) + //return frac == 0.0 +} + +func isArrayKey(v LNumber) bool { + return isInteger(v) && v < LNumber(int((^uint(0))>>1)) && v > LNumber(0) && v < LNumber(MaxArrayIndex) +} + +func parseNumber(number string) (LNumber, error) { + var value LNumber + number = strings.Trim(number, " \t\n") + if v, err := strconv.ParseInt(number, 0, LNumberBit); err != nil { + if v2, err2 := strconv.ParseFloat(number, LNumberBit); err2 != nil { + return LNumber(0), err2 + } else { + value = LNumber(v2) + } + } else { + value = LNumber(v) + } + return value, nil +} + +func popenArgs(arg string) (string, []string) { + cmd := "/bin/sh" + args := []string{"-c"} + if LuaOS == "windows" { + cmd = "C:\\Windows\\system32\\cmd.exe" + args = []string{"/c"} + } + args = append(args, arg) + return cmd, args +} + +func isGoroutineSafe(lv LValue) bool { + switch v := lv.(type) { + case *LFunction, *LUserData, *LState: + return false + case *LTable: + return v.Metatable == LNil + default: + return true + } +} + +func readBufioSize(reader *bufio.Reader, size int64) ([]byte, error, bool) { + result := []byte{} + read := int64(0) + var err error + var n int + for read != size { + buf := make([]byte, size-read) + n, err = reader.Read(buf) + if err != nil { + break + } + read += int64(n) + result = append(result, buf[:n]...) + } + e := err + if e != nil && e == io.EOF { + e = nil + } + + return result, e, len(result) == 0 && err == io.EOF +} + +func readBufioLine(reader *bufio.Reader) ([]byte, error, bool) { + result := []byte{} + var buf []byte + var err error + var isprefix bool = true + for isprefix { + buf, isprefix, err = reader.ReadLine() + if err != nil { + break + } + result = append(result, buf...) + } + e := err + if e != nil && e == io.EOF { + e = nil + } + + return result, e, len(result) == 0 && err == io.EOF +} + +func int2Fb(val int) int { + e := 0 + x := val + for x >= 16 { + x = (x + 1) >> 1 + e++ + } + if x < 8 { + return x + } + return ((e + 1) << 3) | (x - 8) +} + +func strCmp(s1, s2 string) int { + len1 := len(s1) + len2 := len(s2) + for i := 0; ; i++ { + c1 := -1 + if i < len1 { + c1 = int(s1[i]) + } + c2 := -1 + if i != len2 { + c2 = int(s2[i]) + } + switch { + case c1 < c2: + return -1 + case c1 > c2: + return +1 + case c1 < 0: + return 0 + } + } +} + +func unsafeFastStringToReadOnlyBytes(s string) []byte { + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := reflect.SliceHeader{sh.Data, sh.Len, sh.Len} + return *(*[]byte)(unsafe.Pointer(&bh)) +} diff --git a/vendor/github.com/yuin/gopher-lua/value.go b/vendor/github.com/yuin/gopher-lua/value.go new file mode 100644 index 0000000..7bcf1f6 --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/value.go @@ -0,0 +1,247 @@ +package lua + +import ( + "context" + "fmt" + "os" +) + +type LValueType int + +const ( + LTNil LValueType = iota + LTBool + LTNumber + LTString + LTFunction + LTUserData + LTThread + LTTable + LTChannel +) + +var lValueNames = [9]string{"nil", "boolean", "number", "string", "function", "userdata", "thread", "table", "channel"} + +func (vt LValueType) String() string { + return lValueNames[int(vt)] +} + +type LValue interface { + String() string + Type() LValueType + // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). + assertFloat64() (float64, bool) + // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). + assertString() (string, bool) + // to reduce `runtime.assertI2T2` costs, this method should be used instead of the type assertion in heavy paths(typically inside the VM). + assertFunction() (*LFunction, bool) +} + +// LVIsFalse returns true if a given LValue is a nil or false otherwise false. +func LVIsFalse(v LValue) bool { return v == LNil || v == LFalse } + +// LVIsFalse returns false if a given LValue is a nil or false otherwise true. +func LVAsBool(v LValue) bool { return v != LNil && v != LFalse } + +// LVAsString returns string representation of a given LValue +// if the LValue is a string or number, otherwise an empty string. +func LVAsString(v LValue) string { + switch sn := v.(type) { + case LString, LNumber: + return sn.String() + default: + return "" + } +} + +// LVCanConvToString returns true if a given LValue is a string or number +// otherwise false. +func LVCanConvToString(v LValue) bool { + switch v.(type) { + case LString, LNumber: + return true + default: + return false + } +} + +// LVAsNumber tries to convert a given LValue to a number. +func LVAsNumber(v LValue) LNumber { + switch lv := v.(type) { + case LNumber: + return lv + case LString: + if num, err := parseNumber(string(lv)); err == nil { + return num + } + } + return LNumber(0) +} + +type LNilType struct{} + +func (nl *LNilType) String() string { return "nil" } +func (nl *LNilType) Type() LValueType { return LTNil } +func (nl *LNilType) assertFloat64() (float64, bool) { return 0, false } +func (nl *LNilType) assertString() (string, bool) { return "", false } +func (nl *LNilType) assertFunction() (*LFunction, bool) { return nil, false } + +var LNil = LValue(&LNilType{}) + +type LBool bool + +func (bl LBool) String() string { + if bool(bl) { + return "true" + } + return "false" +} +func (bl LBool) Type() LValueType { return LTBool } +func (bl LBool) assertFloat64() (float64, bool) { return 0, false } +func (bl LBool) assertString() (string, bool) { return "", false } +func (bl LBool) assertFunction() (*LFunction, bool) { return nil, false } + +var LTrue = LBool(true) +var LFalse = LBool(false) + +type LString string + +func (st LString) String() string { return string(st) } +func (st LString) Type() LValueType { return LTString } +func (st LString) assertFloat64() (float64, bool) { return 0, false } +func (st LString) assertString() (string, bool) { return string(st), true } +func (st LString) assertFunction() (*LFunction, bool) { return nil, false } + +// fmt.Formatter interface +func (st LString) Format(f fmt.State, c rune) { + switch c { + case 'd', 'i': + if nm, err := parseNumber(string(st)); err != nil { + defaultFormat(nm, f, 'd') + } else { + defaultFormat(string(st), f, 's') + } + default: + defaultFormat(string(st), f, c) + } +} + +func (nm LNumber) String() string { + if isInteger(nm) { + return fmt.Sprint(int64(nm)) + } + return fmt.Sprint(float64(nm)) +} + +func (nm LNumber) Type() LValueType { return LTNumber } +func (nm LNumber) assertFloat64() (float64, bool) { return float64(nm), true } +func (nm LNumber) assertString() (string, bool) { return "", false } +func (nm LNumber) assertFunction() (*LFunction, bool) { return nil, false } + +// fmt.Formatter interface +func (nm LNumber) Format(f fmt.State, c rune) { + switch c { + case 'q', 's': + defaultFormat(nm.String(), f, c) + case 'b', 'c', 'd', 'o', 'x', 'X', 'U': + defaultFormat(int64(nm), f, c) + case 'e', 'E', 'f', 'F', 'g', 'G': + defaultFormat(float64(nm), f, c) + case 'i': + defaultFormat(int64(nm), f, 'd') + default: + if isInteger(nm) { + defaultFormat(int64(nm), f, c) + } else { + defaultFormat(float64(nm), f, c) + } + } +} + +type LTable struct { + Metatable LValue + + array []LValue + dict map[LValue]LValue + strdict map[string]LValue + keys []LValue + k2i map[LValue]int +} + +func (tb *LTable) String() string { return fmt.Sprintf("table: %p", tb) } +func (tb *LTable) Type() LValueType { return LTTable } +func (tb *LTable) assertFloat64() (float64, bool) { return 0, false } +func (tb *LTable) assertString() (string, bool) { return "", false } +func (tb *LTable) assertFunction() (*LFunction, bool) { return nil, false } + +type LFunction struct { + IsG bool + Env *LTable + Proto *FunctionProto + GFunction LGFunction + Upvalues []*Upvalue +} +type LGFunction func(*LState) int + +func (fn *LFunction) String() string { return fmt.Sprintf("function: %p", fn) } +func (fn *LFunction) Type() LValueType { return LTFunction } +func (fn *LFunction) assertFloat64() (float64, bool) { return 0, false } +func (fn *LFunction) assertString() (string, bool) { return "", false } +func (fn *LFunction) assertFunction() (*LFunction, bool) { return fn, true } + +type Global struct { + MainThread *LState + CurrentThread *LState + Registry *LTable + Global *LTable + + builtinMts map[int]LValue + tempFiles []*os.File + gccount int32 +} + +type LState struct { + G *Global + Parent *LState + Env *LTable + Panic func(*LState) + Dead bool + Options Options + + stop int32 + reg *registry + stack *callFrameStack + alloc *allocator + currentFrame *callFrame + wrapped bool + uvcache *Upvalue + hasErrorFunc bool + mainLoop func(*LState, *callFrame) + ctx context.Context +} + +func (ls *LState) String() string { return fmt.Sprintf("thread: %p", ls) } +func (ls *LState) Type() LValueType { return LTThread } +func (ls *LState) assertFloat64() (float64, bool) { return 0, false } +func (ls *LState) assertString() (string, bool) { return "", false } +func (ls *LState) assertFunction() (*LFunction, bool) { return nil, false } + +type LUserData struct { + Value interface{} + Env *LTable + Metatable LValue +} + +func (ud *LUserData) String() string { return fmt.Sprintf("userdata: %p", ud) } +func (ud *LUserData) Type() LValueType { return LTUserData } +func (ud *LUserData) assertFloat64() (float64, bool) { return 0, false } +func (ud *LUserData) assertString() (string, bool) { return "", false } +func (ud *LUserData) assertFunction() (*LFunction, bool) { return nil, false } + +type LChannel chan LValue + +func (ch LChannel) String() string { return fmt.Sprintf("channel: %p", ch) } +func (ch LChannel) Type() LValueType { return LTChannel } +func (ch LChannel) assertFloat64() (float64, bool) { return 0, false } +func (ch LChannel) assertString() (string, bool) { return "", false } +func (ch LChannel) assertFunction() (*LFunction, bool) { return nil, false } diff --git a/vendor/github.com/yuin/gopher-lua/vm.go b/vendor/github.com/yuin/gopher-lua/vm.go new file mode 100644 index 0000000..c18399a --- /dev/null +++ b/vendor/github.com/yuin/gopher-lua/vm.go @@ -0,0 +1,1395 @@ +package lua + +//////////////////////////////////////////////////////// +// This file was generated by go-inline. DO NOT EDIT. // +//////////////////////////////////////////////////////// + +import ( + "fmt" + "math" + "strings" +) + +func mainLoop(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } +} + +func mainLoopWithContext(L *LState, baseframe *callFrame) { + var inst uint32 + var cf *callFrame + + if L.stack.IsEmpty() { + return + } + + L.currentFrame = L.stack.Last() + if L.currentFrame.Fn.IsG { + callGFunction(L, false) + return + } + + for { + cf = L.currentFrame + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + select { + case <-L.ctx.Done(): + L.RaiseError(L.ctx.Err().Error()) + return + default: + if jumpTable[int(inst>>26)](L, inst, baseframe) == 1 { + return + } + } + } +} + +func copyReturnValues(L *LState, regv, start, n, b int) { // +inline-start + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + for i := 0; i < n; i++ { + rg.array[regm+i] = LNil + } + rg.top = regm + n + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + for i := 0; i < n; i++ { + if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[tidx] + } + } + rg.top = regv + n + } + } +} // +inline-end + +func switchToParentThread(L *LState, nargs int, haserror bool, kill bool) { + parent := L.Parent + if parent == nil { + L.RaiseError("can not yield from outside of a coroutine") + } + L.G.CurrentThread = parent + L.Parent = nil + if !L.wrapped { + if haserror { + parent.Push(LFalse) + } else { + parent.Push(LTrue) + } + } + L.XMoveTo(parent, nargs) + L.stack.Pop() + offset := L.currentFrame.LocalBase - L.currentFrame.ReturnBase + L.currentFrame = L.stack.Last() + L.reg.SetTop(L.reg.Top() - offset) // remove 'yield' function(including tailcalled functions) + if kill { + L.kill() + } +} + +func callGFunction(L *LState, tailcall bool) bool { + frame := L.currentFrame + gfnret := frame.Fn.GFunction(L) + if tailcall { + L.stack.Remove(L.stack.Sp() - 2) // remove caller lua function frame + L.currentFrame = L.stack.Last() + } + + if gfnret < 0 { + switchToParentThread(L, L.GetTop(), false, false) + return true + } + + wantret := frame.NRet + if wantret == MultRet { + wantret = gfnret + } + + if tailcall && L.Parent != nil && L.stack.Sp() == 1 { + switchToParentThread(L, wantret, false, true) + return true + } + + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + regv := frame.ReturnBase + start := L.reg.Top() - gfnret + limit := -1 + n := wantret + for i := 0; i < n; i++ { + if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[tidx] + } + } + rg.top = regv + n + } + L.stack.Pop() + L.currentFrame = L.stack.Last() + return false +} + +func threadRun(L *LState) { + if L.stack.IsEmpty() { + return + } + + defer func() { + if rcv := recover(); rcv != nil { + var lv LValue + if v, ok := rcv.(*ApiError); ok { + lv = v.Object + } else { + lv = LString(fmt.Sprint(rcv)) + } + if parent := L.Parent; parent != nil { + if L.wrapped { + L.Push(lv) + parent.Panic(L) + } else { + L.SetTop(0) + L.Push(lv) + switchToParentThread(L, 1, true, true) + } + } else { + panic(rcv) + } + } + }() + L.mainLoop(L, nil) +} + +type instFunc func(*LState, uint32, *callFrame) int + +var jumpTable [opCodeMax + 1]instFunc + +func init() { + jumpTable = [opCodeMax + 1]instFunc{ + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, reg.Get(lbase+B)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_MOVEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(lbase+A, reg.Get(lbase+B)) + code := cf.Fn.Proto.Code + pc := cf.Pc + for i := 0; i < C; i++ { + inst = code[pc] + pc++ + A = int(inst>>18) & 0xff //GETA + B = int(inst & 0x1ff) //GETB + reg.Set(lbase+A, reg.Get(lbase+B)) + } + cf.Pc = pc + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADK + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + reg.Set(RA, cf.Fn.Proto.Constants[Bx]) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADBOOL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if B != 0 { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + if C != 0 { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LOADNIL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + for i := RA; i <= lbase+B; i++ { + reg.Set(i, LNil) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + reg.Set(RA, cf.Fn.Upvalues[B].Value()) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + //reg.Set(RA, L.getField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx])) + reg.Set(RA, L.getFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx])) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, L.getField(reg.Get(lbase+B), L.rkValue(C))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_GETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, L.getFieldString(reg.Get(lbase+B), L.rkString(C))) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETGLOBAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + //L.setField(cf.Fn.Env, cf.Fn.Proto.Constants[Bx], reg.Get(RA)) + L.setFieldString(cf.Fn.Env, cf.Fn.Proto.stringConstants[Bx], reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETUPVAL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + cf.Fn.Upvalues[B].SetValue(reg.Get(RA)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setField(reg.Get(RA), L.rkValue(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETTABLEKS + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + L.setFieldString(reg.Get(RA), L.rkString(B), L.rkValue(C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NEWTABLE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + reg.Set(RA, newLTable(B, C)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SELF + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + selfobj := reg.Get(lbase + B) + reg.Set(RA, L.getFieldString(selfobj, L.rkString(C))) + reg.Set(RA+1, selfobj) + return 0 + }, + opArith, // OP_ADD + opArith, // OP_SUB + opArith, // OP_MUL + opArith, // OP_DIV + opArith, // OP_MOD + opArith, // OP_POW + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_UNM + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + unaryv := L.rkValue(B) + if nm, ok := unaryv.(LNumber); ok { + reg.SetNumber(RA, -nm) + } else { + op := L.metaOp1(unaryv, "__unm") + if op.Type() == LTFunction { + reg.Push(op) + reg.Push(unaryv) + L.Call(1, 1) + reg.Set(RA, reg.Pop()) + } else if str, ok1 := unaryv.(LString); ok1 { + if num, err := parseNumber(string(str)); err == nil { + reg.Set(RA, -num) + } else { + L.RaiseError("__unm undefined") + } + } else { + L.RaiseError("__unm undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + if LVIsFalse(reg.Get(lbase + B)) { + reg.Set(RA, LTrue) + } else { + reg.Set(RA, LFalse) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LEN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + switch lv := L.rkValue(B).(type) { + case LString: + reg.SetNumber(RA, LNumber(len(lv))) + default: + op := L.metaOp1(lv, "__len") + if op.Type() == LTFunction { + reg.Push(op) + reg.Push(lv) + L.Call(1, 1) + ret := reg.Pop() + if ret.Type() == LTNumber { + reg.SetNumber(RA, ret.(LNumber)) + } else { + reg.SetNumber(RA, LNumber(0)) + } + } else if lv.Type() == LTTable { + reg.SetNumber(RA, LNumber(lv.(*LTable).Len())) + } else { + L.RaiseError("__len undefined") + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CONCAT + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + RC := lbase + C + RB := lbase + B + reg.Set(RA, stringConcat(L, RC-RB+1, RC)) + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_JMP + cf := L.currentFrame + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_EQ + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := equals(L, L.rkValue(B), L.rkValue(C), false) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LT + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + ret := lessThan(L, L.rkValue(B), L.rkValue(C)) + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_LE + cf := L.currentFrame + A := int(inst>>18) & 0xff //GETA + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + ret := false + + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + ret = v1 <= v2 + } else { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + } else { + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) <= 0 + default: + switch objectRational(L, lhs, rhs, "__le") { + case 1: + ret = true + case 0: + ret = false + default: + ret = !objectRationalWithError(L, rhs, lhs, "__lt") + } + } + } + + v := 1 + if ret { + v = 0 + } + if v == A { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TEST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + C := int(inst>>9) & 0x1ff //GETC + if LVAsBool(reg.Get(RA)) == (C == 0) { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TESTSET + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if value := reg.Get(lbase + B); LVAsBool(value) != (C == 0) { + reg.Set(RA, value) + } else { + cf.Pc++ + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + nret := C - 1 + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) ' in '_state.go' + { + ls := L + cf := callFrame{Fn: callable, Pc: 0, Base: RA, LocalBase: RA + 1, ReturnBase: RA, NArgs: nargs, NRet: nret, Parent: cf, TailCall: 0} + fn := lv + if meta { + cf.NArgs++ + ls.reg.Insert(fn, cf.LocalBase) + } + if cf.Fn == nil { + ls.RaiseError("attempt to call a non-function object") + } + if ls.stack.sp == ls.Options.CallStackSize { + ls.RaiseError("stack overflow") + } + // this section is inlined by go-inline + // source function is 'func (cs *callFrameStack) Push(v callFrame) ' in '_state.go' + { + cs := ls.stack + v := cf + cs.array[cs.sp] = v + cs.array[cs.sp].Idx = cs.sp + cs.sp++ + } + newcf := ls.stack.Last() + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + cf := newcf + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + ls.currentFrame = newcf + } + if callable.IsG && callGFunction(L, false) { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TAILCALL + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nargs := B - 1 + if B == 0 { + nargs = reg.Top() - (RA + 1) + } + lv := reg.Get(RA) + var callable *LFunction + var meta bool + if fn, ok := lv.assertFunction(); ok { + callable = fn + meta = false + } else { + callable, meta = L.metaCall(lv) + } + if callable == nil { + L.RaiseError("attempt to call a non-function object") + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := lbase + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + if callable.IsG { + luaframe := cf + L.pushCallFrame(callFrame{ + Fn: callable, + Pc: 0, + Base: RA, + LocalBase: RA + 1, + ReturnBase: cf.ReturnBase, + NArgs: nargs, + NRet: cf.NRet, + Parent: cf, + TailCall: 0, + }, lv, meta) + if callGFunction(L, true) { + return 1 + } + if L.currentFrame == nil || L.currentFrame.Fn.IsG || luaframe == baseframe { + return 1 + } + } else { + base := cf.Base + cf.Fn = callable + cf.Pc = 0 + cf.Base = RA + cf.LocalBase = RA + 1 + cf.ReturnBase = cf.ReturnBase + cf.NArgs = nargs + cf.NRet = cf.NRet + cf.TailCall++ + lbase := cf.LocalBase + if meta { + cf.NArgs++ + L.reg.Insert(lv, cf.LocalBase) + } + // this section is inlined by go-inline + // source function is 'func (ls *LState) initCallFrame(cf *callFrame) ' in '_state.go' + { + ls := L + if cf.Fn.IsG { + ls.reg.SetTop(cf.LocalBase + cf.NArgs) + } else { + proto := cf.Fn.Proto + nargs := cf.NArgs + np := int(proto.NumParameters) + for i := nargs; i < np; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + nargs = np + } + + if (proto.IsVarArg & VarArgIsVarArg) == 0 { + if nargs < int(proto.NumUsedRegisters) { + nargs = int(proto.NumUsedRegisters) + } + for i := np; i < nargs; i++ { + ls.reg.array[cf.LocalBase+i] = LNil + } + ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters) + } else { + /* swap vararg positions: + closure + namedparam1 <- lbase + namedparam2 + vararg1 + vararg2 + + TO + + closure + nil + nil + vararg1 + vararg2 + namedparam1 <- lbase + namedparam2 + */ + nvarargs := nargs - np + if nvarargs < 0 { + nvarargs = 0 + } + + ls.reg.SetTop(cf.LocalBase + nargs + np) + for i := 0; i < np; i++ { + //ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i)) + ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i] + //ls.reg.Set(cf.LocalBase+i, LNil) + ls.reg.array[cf.LocalBase+i] = LNil + } + + if CompatVarArg { + ls.reg.SetTop(cf.LocalBase + nargs + np + 1) + if (proto.IsVarArg & VarArgNeedsArg) != 0 { + argtb := newLTable(nvarargs, 0) + for i := 0; i < nvarargs; i++ { + argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i)) + } + argtb.RawSetString("n", LNumber(nvarargs)) + //ls.reg.Set(cf.LocalBase+nargs+np, argtb) + ls.reg.array[cf.LocalBase+nargs+np] = argtb + } else { + ls.reg.array[cf.LocalBase+nargs+np] = LNil + } + } + cf.LocalBase += nargs + maxreg := cf.LocalBase + int(proto.NumUsedRegisters) + ls.reg.SetTop(maxreg) + } + } + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + regv := base + start := RA + limit := -1 + n := reg.Top() - RA - 1 + for i := 0; i < n; i++ { + if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[tidx] + } + } + rg.top = regv + n + } + cf.Base = base + cf.LocalBase = base + (cf.LocalBase - lbase + 1) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_RETURN + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := lbase + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + nret := B - 1 + if B == 0 { + nret = reg.Top() - RA + } + n := cf.NRet + if cf.NRet == MultRet { + n = nret + } + + if L.Parent != nil && L.stack.Sp() == 1 { + // this section is inlined by go-inline + // source function is 'func copyReturnValues(L *LState, regv, start, n, b int) ' in '_vm.go' + { + regv := reg.Top() + start := RA + b := B + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + for i := 0; i < n; i++ { + rg.array[regm+i] = LNil + } + rg.top = regm + n + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + for i := 0; i < n; i++ { + if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[tidx] + } + } + rg.top = regv + n + } + } + } + switchToParentThread(L, n, false, true) + return 1 + } + islast := baseframe == L.stack.Pop() || L.stack.IsEmpty() + // this section is inlined by go-inline + // source function is 'func copyReturnValues(L *LState, regv, start, n, b int) ' in '_vm.go' + { + regv := cf.ReturnBase + start := RA + b := B + if b == 1 { + // this section is inlined by go-inline + // source function is 'func (rg *registry) FillNil(regm, n int) ' in '_state.go' + { + rg := L.reg + regm := regv + for i := 0; i < n; i++ { + rg.array[regm+i] = LNil + } + rg.top = regm + n + } + } else { + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := L.reg + limit := -1 + for i := 0; i < n; i++ { + if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[tidx] + } + } + rg.top = regv + n + } + } + } + L.currentFrame = L.stack.Last() + if islast || L.currentFrame == nil || L.currentFrame.Fn.IsG { + return 1 + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORLOOP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { + if limit, ok2 := reg.Get(RA + 1).assertFloat64(); ok2 { + if step, ok3 := reg.Get(RA + 2).assertFloat64(); ok3 { + init += step + reg.SetNumber(RA, LNumber(init)) + if (step > 0 && init <= limit) || (step <= 0 && init >= limit) { + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + cf.Pc += Sbx + reg.SetNumber(RA+3, LNumber(init)) + } else { + reg.SetTop(RA + 1) + } + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement limit must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_FORPREP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Sbx := int(inst&0x3ffff) - opMaxArgSbx //GETSBX + if init, ok1 := reg.Get(RA).assertFloat64(); ok1 { + if step, ok2 := reg.Get(RA + 2).assertFloat64(); ok2 { + reg.SetNumber(RA, LNumber(init-step)) + } else { + L.RaiseError("for statement step must be a number") + } + } else { + L.RaiseError("for statement init must be a number") + } + cf.Pc += Sbx + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_TFORLOOP + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + C := int(inst>>9) & 0x1ff //GETC + nret := C + reg.SetTop(RA + 3 + 2) + reg.Set(RA+3+2, reg.Get(RA+2)) + reg.Set(RA+3+1, reg.Get(RA+1)) + reg.Set(RA+3, reg.Get(RA)) + L.callR(2, nret, RA+3) + if value := reg.Get(RA + 3); value != LNil { + reg.Set(RA+2, value) + pc := cf.Fn.Proto.Code[cf.Pc] + cf.Pc += int(pc&0x3ffff) - opMaxArgSbx + } + cf.Pc++ + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_SETLIST + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + if C == 0 { + C = int(cf.Fn.Proto.Code[cf.Pc]) + cf.Pc++ + } + offset := (C - 1) * FieldsPerFlush + table := reg.Get(RA).(*LTable) + nelem := B + if B == 0 { + nelem = reg.Top() - RA - 1 + } + for i := 1; i <= nelem; i++ { + table.RawSetInt(offset+i, reg.Get(RA+i)) + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSE + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + // this section is inlined by go-inline + // source function is 'func (ls *LState) closeUpvalues(idx int) ' in '_state.go' + { + ls := L + idx := RA + if ls.uvcache != nil { + var prev *Upvalue + for uv := ls.uvcache; uv != nil; uv = uv.next { + if uv.index >= idx { + if prev != nil { + prev.next = nil + } else { + ls.uvcache = nil + } + uv.Close() + } + prev = uv + } + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_CLOSURE + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + Bx := int(inst & 0x3ffff) //GETBX + proto := cf.Fn.Proto.FunctionPrototypes[Bx] + closure := newLFunctionL(proto, cf.Fn.Env, int(proto.NumUpvalues)) + reg.Set(RA, closure) + for i := 0; i < int(proto.NumUpvalues); i++ { + inst = cf.Fn.Proto.Code[cf.Pc] + cf.Pc++ + B := opGetArgB(inst) + switch opGetOpCode(inst) { + case OP_MOVE: + closure.Upvalues[i] = L.findUpvalue(lbase + B) + case OP_GETUPVAL: + closure.Upvalues[i] = cf.Fn.Upvalues[B] + } + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_VARARG + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + B := int(inst & 0x1ff) //GETB + nparams := int(cf.Fn.Proto.NumParameters) + nvarargs := cf.NArgs - nparams + if nvarargs < 0 { + nvarargs = 0 + } + nwant := B - 1 + if B == 0 { + nwant = nvarargs + } + // this section is inlined by go-inline + // source function is 'func (rg *registry) CopyRange(regv, start, limit, n int) ' in '_state.go' + { + rg := reg + regv := RA + start := cf.Base + nparams + 1 + limit := cf.LocalBase + n := nwant + for i := 0; i < n; i++ { + if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 { + rg.array[regv+i] = LNil + } else { + rg.array[regv+i] = rg.array[tidx] + } + } + rg.top = regv + n + } + return 0 + }, + func(L *LState, inst uint32, baseframe *callFrame) int { //OP_NOP + return 0 + }, + } +} + +func opArith(L *LState, inst uint32, baseframe *callFrame) int { //OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW + reg := L.reg + cf := L.currentFrame + lbase := cf.LocalBase + A := int(inst>>18) & 0xff //GETA + RA := lbase + A + opcode := int(inst >> 26) //GETOPCODE + B := int(inst & 0x1ff) //GETB + C := int(inst>>9) & 0x1ff //GETC + lhs := L.rkValue(B) + rhs := L.rkValue(C) + v1, ok1 := lhs.assertFloat64() + v2, ok2 := rhs.assertFloat64() + if ok1 && ok2 { + reg.SetNumber(RA, numberArith(L, opcode, LNumber(v1), LNumber(v2))) + } else { + reg.Set(RA, objectArith(L, opcode, lhs, rhs)) + } + return 0 +} + +func luaModulo(lhs, rhs LNumber) LNumber { + flhs := float64(lhs) + frhs := float64(rhs) + v := math.Mod(flhs, frhs) + if flhs < 0 || frhs < 0 && !(flhs < 0 && frhs < 0) { + v += frhs + } + return LNumber(v) +} + +func numberArith(L *LState, opcode int, lhs, rhs LNumber) LNumber { + switch opcode { + case OP_ADD: + return lhs + rhs + case OP_SUB: + return lhs - rhs + case OP_MUL: + return lhs * rhs + case OP_DIV: + return lhs / rhs + case OP_MOD: + return luaModulo(lhs, rhs) + case OP_POW: + flhs := float64(lhs) + frhs := float64(rhs) + return LNumber(math.Pow(flhs, frhs)) + } + panic("should not reach here") + return LNumber(0) +} + +func objectArith(L *LState, opcode int, lhs, rhs LValue) LValue { + event := "" + switch opcode { + case OP_ADD: + event = "__add" + case OP_SUB: + event = "__sub" + case OP_MUL: + event = "__mul" + case OP_DIV: + event = "__div" + case OP_MOD: + event = "__mod" + case OP_POW: + event = "__pow" + } + op := L.metaOp2(lhs, rhs, event) + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + return L.reg.Pop() + } + if str, ok := lhs.(LString); ok { + if lnum, err := parseNumber(string(str)); err == nil { + lhs = lnum + } + } + if str, ok := rhs.(LString); ok { + if rnum, err := parseNumber(string(str)); err == nil { + rhs = rnum + } + } + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return numberArith(L, opcode, LNumber(v1), LNumber(v2)) + } + } + L.RaiseError(fmt.Sprintf("cannot perform %v operation between %v and %v", + strings.TrimLeft(event, "_"), lhs.Type().String(), rhs.Type().String())) + + return LNil +} + +func stringConcat(L *LState, total, last int) LValue { + rhs := L.reg.Get(last) + total-- + for i := last - 1; total > 0; { + lhs := L.reg.Get(i) + if !(LVCanConvToString(lhs) && LVCanConvToString(rhs)) { + op := L.metaOp2(lhs, rhs, "__concat") + if op.Type() == LTFunction { + L.reg.Push(op) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + rhs = L.reg.Pop() + total-- + i-- + } else { + L.RaiseError("cannot perform concat operation between %v and %v", lhs.Type().String(), rhs.Type().String()) + return LNil + } + } else { + buf := make([]string, total+1) + buf[total] = LVAsString(rhs) + for total > 0 { + lhs = L.reg.Get(i) + if !LVCanConvToString(lhs) { + break + } + buf[total-1] = LVAsString(lhs) + i-- + total-- + } + rhs = LString(strings.Join(buf, "")) + } + } + return rhs +} + +func lessThan(L *LState, lhs, rhs LValue) bool { + // optimization for numbers + if v1, ok1 := lhs.assertFloat64(); ok1 { + if v2, ok2 := rhs.assertFloat64(); ok2 { + return v1 < v2 + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + } + if lhs.Type() != rhs.Type() { + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false + } + ret := false + switch lhs.Type() { + case LTString: + ret = strCmp(string(lhs.(LString)), string(rhs.(LString))) < 0 + default: + ret = objectRationalWithError(L, lhs, rhs, "__lt") + } + return ret +} + +func equals(L *LState, lhs, rhs LValue, raw bool) bool { + if lhs.Type() != rhs.Type() { + return false + } + + ret := false + switch lhs.Type() { + case LTNil: + ret = true + case LTNumber: + v1, _ := lhs.assertFloat64() + v2, _ := rhs.assertFloat64() + ret = v1 == v2 + case LTBool: + ret = bool(lhs.(LBool)) == bool(rhs.(LBool)) + case LTString: + ret = string(lhs.(LString)) == string(rhs.(LString)) + case LTUserData, LTTable: + if lhs == rhs { + ret = true + } else if !raw { + switch objectRational(L, lhs, rhs, "__eq") { + case 1: + ret = true + default: + ret = false + } + } + default: + ret = lhs == rhs + } + return ret +} + +func objectRationalWithError(L *LState, lhs, rhs LValue, event string) bool { + switch objectRational(L, lhs, rhs, event) { + case 1: + return true + case 0: + return false + } + L.RaiseError("attempt to compare %v with %v", lhs.Type().String(), rhs.Type().String()) + return false +} + +func objectRational(L *LState, lhs, rhs LValue, event string) int { + m1 := L.metaOp1(lhs, event) + m2 := L.metaOp1(rhs, event) + if m1.Type() == LTFunction && m1 == m2 { + L.reg.Push(m1) + L.reg.Push(lhs) + L.reg.Push(rhs) + L.Call(2, 1) + if LVAsBool(L.reg.Pop()) { + return 1 + } + return 0 + } + return -1 +} diff --git a/vendor/golang.org/x/crypto/ed25519/ed25519.go b/vendor/golang.org/x/crypto/ed25519/ed25519.go new file mode 100644 index 0000000..4f26b49 --- /dev/null +++ b/vendor/golang.org/x/crypto/ed25519/ed25519.go @@ -0,0 +1,181 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ed25519 implements the Ed25519 signature algorithm. See +// https://ed25519.cr.yp.to/. +// +// These functions are also compatible with the “Ed25519” function defined in +// RFC 8032. +package ed25519 + +// This code is a port of the public domain, “ref10” implementation of ed25519 +// from SUPERCOP. + +import ( + "bytes" + "crypto" + cryptorand "crypto/rand" + "crypto/sha512" + "errors" + "io" + "strconv" + + "golang.org/x/crypto/ed25519/internal/edwards25519" +) + +const ( + // PublicKeySize is the size, in bytes, of public keys as used in this package. + PublicKeySize = 32 + // PrivateKeySize is the size, in bytes, of private keys as used in this package. + PrivateKeySize = 64 + // SignatureSize is the size, in bytes, of signatures generated and verified by this package. + SignatureSize = 64 +) + +// PublicKey is the type of Ed25519 public keys. +type PublicKey []byte + +// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer. +type PrivateKey []byte + +// Public returns the PublicKey corresponding to priv. +func (priv PrivateKey) Public() crypto.PublicKey { + publicKey := make([]byte, PublicKeySize) + copy(publicKey, priv[32:]) + return PublicKey(publicKey) +} + +// Sign signs the given message with priv. +// Ed25519 performs two passes over messages to be signed and therefore cannot +// handle pre-hashed messages. Thus opts.HashFunc() must return zero to +// indicate the message hasn't been hashed. This can be achieved by passing +// crypto.Hash(0) as the value for opts. +func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) { + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("ed25519: cannot sign hashed message") + } + + return Sign(priv, message), nil +} + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) { + if rand == nil { + rand = cryptorand.Reader + } + + privateKey = make([]byte, PrivateKeySize) + publicKey = make([]byte, PublicKeySize) + _, err = io.ReadFull(rand, privateKey[:32]) + if err != nil { + return nil, nil, err + } + + digest := sha512.Sum512(privateKey[:32]) + digest[0] &= 248 + digest[31] &= 127 + digest[31] |= 64 + + var A edwards25519.ExtendedGroupElement + var hBytes [32]byte + copy(hBytes[:], digest[:]) + edwards25519.GeScalarMultBase(&A, &hBytes) + var publicKeyBytes [32]byte + A.ToBytes(&publicKeyBytes) + + copy(privateKey[32:], publicKeyBytes[:]) + copy(publicKey, publicKeyBytes[:]) + + return publicKey, privateKey, nil +} + +// Sign signs the message with privateKey and returns a signature. It will +// panic if len(privateKey) is not PrivateKeySize. +func Sign(privateKey PrivateKey, message []byte) []byte { + if l := len(privateKey); l != PrivateKeySize { + panic("ed25519: bad private key length: " + strconv.Itoa(l)) + } + + h := sha512.New() + h.Write(privateKey[:32]) + + var digest1, messageDigest, hramDigest [64]byte + var expandedSecretKey [32]byte + h.Sum(digest1[:0]) + copy(expandedSecretKey[:], digest1[:]) + expandedSecretKey[0] &= 248 + expandedSecretKey[31] &= 63 + expandedSecretKey[31] |= 64 + + h.Reset() + h.Write(digest1[32:]) + h.Write(message) + h.Sum(messageDigest[:0]) + + var messageDigestReduced [32]byte + edwards25519.ScReduce(&messageDigestReduced, &messageDigest) + var R edwards25519.ExtendedGroupElement + edwards25519.GeScalarMultBase(&R, &messageDigestReduced) + + var encodedR [32]byte + R.ToBytes(&encodedR) + + h.Reset() + h.Write(encodedR[:]) + h.Write(privateKey[32:]) + h.Write(message) + h.Sum(hramDigest[:0]) + var hramDigestReduced [32]byte + edwards25519.ScReduce(&hramDigestReduced, &hramDigest) + + var s [32]byte + edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced) + + signature := make([]byte, SignatureSize) + copy(signature[:], encodedR[:]) + copy(signature[32:], s[:]) + + return signature +} + +// Verify reports whether sig is a valid signature of message by publicKey. It +// will panic if len(publicKey) is not PublicKeySize. +func Verify(publicKey PublicKey, message, sig []byte) bool { + if l := len(publicKey); l != PublicKeySize { + panic("ed25519: bad public key length: " + strconv.Itoa(l)) + } + + if len(sig) != SignatureSize || sig[63]&224 != 0 { + return false + } + + var A edwards25519.ExtendedGroupElement + var publicKeyBytes [32]byte + copy(publicKeyBytes[:], publicKey) + if !A.FromBytes(&publicKeyBytes) { + return false + } + edwards25519.FeNeg(&A.X, &A.X) + edwards25519.FeNeg(&A.T, &A.T) + + h := sha512.New() + h.Write(sig[:32]) + h.Write(publicKey[:]) + h.Write(message) + var digest [64]byte + h.Sum(digest[:0]) + + var hReduced [32]byte + edwards25519.ScReduce(&hReduced, &digest) + + var R edwards25519.ProjectiveGroupElement + var b [32]byte + copy(b[:], sig[32:]) + edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b) + + var checkR [32]byte + R.ToBytes(&checkR) + return bytes.Equal(sig[:32], checkR[:]) +} diff --git a/vendor/golang.org/x/crypto/ed25519/ed25519_test.go b/vendor/golang.org/x/crypto/ed25519/ed25519_test.go new file mode 100644 index 0000000..e272f8a --- /dev/null +++ b/vendor/golang.org/x/crypto/ed25519/ed25519_test.go @@ -0,0 +1,183 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ed25519 + +import ( + "bufio" + "bytes" + "compress/gzip" + "crypto" + "crypto/rand" + "encoding/hex" + "os" + "strings" + "testing" + + "golang.org/x/crypto/ed25519/internal/edwards25519" +) + +type zeroReader struct{} + +func (zeroReader) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = 0 + } + return len(buf), nil +} + +func TestUnmarshalMarshal(t *testing.T) { + pub, _, _ := GenerateKey(rand.Reader) + + var A edwards25519.ExtendedGroupElement + var pubBytes [32]byte + copy(pubBytes[:], pub) + if !A.FromBytes(&pubBytes) { + t.Fatalf("ExtendedGroupElement.FromBytes failed") + } + + var pub2 [32]byte + A.ToBytes(&pub2) + + if pubBytes != pub2 { + t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", pubBytes, pub2) + } +} + +func TestSignVerify(t *testing.T) { + var zero zeroReader + public, private, _ := GenerateKey(zero) + + message := []byte("test message") + sig := Sign(private, message) + if !Verify(public, message, sig) { + t.Errorf("valid signature rejected") + } + + wrongMessage := []byte("wrong message") + if Verify(public, wrongMessage, sig) { + t.Errorf("signature of different message accepted") + } +} + +func TestCryptoSigner(t *testing.T) { + var zero zeroReader + public, private, _ := GenerateKey(zero) + + signer := crypto.Signer(private) + + publicInterface := signer.Public() + public2, ok := publicInterface.(PublicKey) + if !ok { + t.Fatalf("expected PublicKey from Public() but got %T", publicInterface) + } + + if !bytes.Equal(public, public2) { + t.Errorf("public keys do not match: original:%x vs Public():%x", public, public2) + } + + message := []byte("message") + var noHash crypto.Hash + signature, err := signer.Sign(zero, message, noHash) + if err != nil { + t.Fatalf("error from Sign(): %s", err) + } + + if !Verify(public, message, signature) { + t.Errorf("Verify failed on signature from Sign()") + } +} + +func TestGolden(t *testing.T) { + // sign.input.gz is a selection of test cases from + // https://ed25519.cr.yp.to/python/sign.input + testDataZ, err := os.Open("testdata/sign.input.gz") + if err != nil { + t.Fatal(err) + } + defer testDataZ.Close() + testData, err := gzip.NewReader(testDataZ) + if err != nil { + t.Fatal(err) + } + defer testData.Close() + + scanner := bufio.NewScanner(testData) + lineNo := 0 + + for scanner.Scan() { + lineNo++ + + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) != 5 { + t.Fatalf("bad number of parts on line %d", lineNo) + } + + privBytes, _ := hex.DecodeString(parts[0]) + pubKey, _ := hex.DecodeString(parts[1]) + msg, _ := hex.DecodeString(parts[2]) + sig, _ := hex.DecodeString(parts[3]) + // The signatures in the test vectors also include the message + // at the end, but we just want R and S. + sig = sig[:SignatureSize] + + if l := len(pubKey); l != PublicKeySize { + t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l) + } + + var priv [PrivateKeySize]byte + copy(priv[:], privBytes) + copy(priv[32:], pubKey) + + sig2 := Sign(priv[:], msg) + if !bytes.Equal(sig, sig2[:]) { + t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2) + } + + if !Verify(pubKey, msg, sig2) { + t.Errorf("signature failed to verify on line %d", lineNo) + } + } + + if err := scanner.Err(); err != nil { + t.Fatalf("error reading test data: %s", err) + } +} + +func BenchmarkKeyGeneration(b *testing.B) { + var zero zeroReader + for i := 0; i < b.N; i++ { + if _, _, err := GenerateKey(zero); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkSigning(b *testing.B) { + var zero zeroReader + _, priv, err := GenerateKey(zero) + if err != nil { + b.Fatal(err) + } + message := []byte("Hello, world!") + b.ResetTimer() + for i := 0; i < b.N; i++ { + Sign(priv, message) + } +} + +func BenchmarkVerification(b *testing.B) { + var zero zeroReader + pub, priv, err := GenerateKey(zero) + if err != nil { + b.Fatal(err) + } + message := []byte("Hello, world!") + signature := Sign(priv, message) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Verify(pub, message, signature) + } +} diff --git a/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go b/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go new file mode 100644 index 0000000..e39f086 --- /dev/null +++ b/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go @@ -0,0 +1,1422 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package edwards25519 + +// These values are from the public domain, “ref10” implementation of ed25519 +// from SUPERCOP. + +// d is a constant in the Edwards curve equation. +var d = FieldElement{ + -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116, +} + +// d2 is 2*d. +var d2 = FieldElement{ + -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199, +} + +// SqrtM1 is the square-root of -1 in the field. +var SqrtM1 = FieldElement{ + -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482, +} + +// A is a constant in the Montgomery-form of curve25519. +var A = FieldElement{ + 486662, 0, 0, 0, 0, 0, 0, 0, 0, 0, +} + +// bi contains precomputed multiples of the base-point. See the Ed25519 paper +// for a discussion about how these values are used. +var bi = [8]PreComputedGroupElement{ + { + FieldElement{25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, + FieldElement{-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378}, + FieldElement{-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546}, + }, + { + FieldElement{15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024}, + FieldElement{16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574}, + FieldElement{30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357}, + }, + { + FieldElement{10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380}, + FieldElement{4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306}, + FieldElement{19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942}, + }, + { + FieldElement{5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766}, + FieldElement{-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701}, + FieldElement{28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300}, + }, + { + FieldElement{-22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877}, + FieldElement{-6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951}, + FieldElement{4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784}, + }, + { + FieldElement{-25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436}, + FieldElement{25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918}, + FieldElement{23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877}, + }, + { + FieldElement{-33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800}, + FieldElement{-25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305}, + FieldElement{-13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300}, + }, + { + FieldElement{-3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876}, + FieldElement{-24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619}, + FieldElement{-3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683}, + }, +} + +// base contains precomputed multiples of the base-point. See the Ed25519 paper +// for a discussion about how these values are used. +var base = [32][8]PreComputedGroupElement{ + { + { + FieldElement{25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605}, + FieldElement{-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378}, + FieldElement{-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546}, + }, + { + FieldElement{-12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303}, + FieldElement{-21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081}, + FieldElement{26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697}, + }, + { + FieldElement{15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024}, + FieldElement{16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574}, + FieldElement{30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357}, + }, + { + FieldElement{-17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540}, + FieldElement{23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397}, + FieldElement{7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325}, + }, + { + FieldElement{10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380}, + FieldElement{4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306}, + FieldElement{19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942}, + }, + { + FieldElement{-15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777}, + FieldElement{-8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737}, + FieldElement{-18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652}, + }, + { + FieldElement{5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766}, + FieldElement{-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701}, + FieldElement{28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300}, + }, + { + FieldElement{14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726}, + FieldElement{-7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955}, + FieldElement{27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425}, + }, + }, + { + { + FieldElement{-13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171}, + FieldElement{27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510}, + FieldElement{17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660}, + }, + { + FieldElement{-10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639}, + FieldElement{29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963}, + FieldElement{5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950}, + }, + { + FieldElement{-27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568}, + FieldElement{12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335}, + FieldElement{25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628}, + }, + { + FieldElement{-26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007}, + FieldElement{-2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772}, + FieldElement{-22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653}, + }, + { + FieldElement{2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567}, + FieldElement{13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686}, + FieldElement{21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372}, + }, + { + FieldElement{-13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887}, + FieldElement{-23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954}, + FieldElement{-29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953}, + }, + { + FieldElement{24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833}, + FieldElement{-16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532}, + FieldElement{-22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876}, + }, + { + FieldElement{2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268}, + FieldElement{33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214}, + FieldElement{1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038}, + }, + }, + { + { + FieldElement{6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800}, + FieldElement{4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645}, + FieldElement{-4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664}, + }, + { + FieldElement{1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933}, + FieldElement{-25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182}, + FieldElement{-17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222}, + }, + { + FieldElement{-18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991}, + FieldElement{20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880}, + FieldElement{9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092}, + }, + { + FieldElement{-16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295}, + FieldElement{19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788}, + FieldElement{8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553}, + }, + { + FieldElement{-15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026}, + FieldElement{11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347}, + FieldElement{-18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033}, + }, + { + FieldElement{-23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395}, + FieldElement{-27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278}, + FieldElement{1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890}, + }, + { + FieldElement{32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995}, + FieldElement{-30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596}, + FieldElement{-11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891}, + }, + { + FieldElement{31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060}, + FieldElement{11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608}, + FieldElement{-20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606}, + }, + }, + { + { + FieldElement{7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389}, + FieldElement{-19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016}, + FieldElement{-11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341}, + }, + { + FieldElement{-22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505}, + FieldElement{14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553}, + FieldElement{-28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655}, + }, + { + FieldElement{15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220}, + FieldElement{12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631}, + FieldElement{-4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099}, + }, + { + FieldElement{26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556}, + FieldElement{14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749}, + FieldElement{236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930}, + }, + { + FieldElement{1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391}, + FieldElement{5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253}, + FieldElement{20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066}, + }, + { + FieldElement{24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958}, + FieldElement{-11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082}, + FieldElement{-28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383}, + }, + { + FieldElement{-30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521}, + FieldElement{-11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807}, + FieldElement{23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948}, + }, + { + FieldElement{9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134}, + FieldElement{-32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455}, + FieldElement{27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629}, + }, + }, + { + { + FieldElement{-8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069}, + FieldElement{-32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746}, + FieldElement{24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919}, + }, + { + FieldElement{11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837}, + FieldElement{8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906}, + FieldElement{-28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771}, + }, + { + FieldElement{-25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817}, + FieldElement{10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098}, + FieldElement{10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409}, + }, + { + FieldElement{-12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504}, + FieldElement{-26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727}, + FieldElement{28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420}, + }, + { + FieldElement{-32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003}, + FieldElement{-1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605}, + FieldElement{-30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384}, + }, + { + FieldElement{-26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701}, + FieldElement{-23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683}, + FieldElement{29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708}, + }, + { + FieldElement{-3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563}, + FieldElement{-19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260}, + FieldElement{-5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387}, + }, + { + FieldElement{-19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672}, + FieldElement{23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686}, + FieldElement{-24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665}, + }, + }, + { + { + FieldElement{11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182}, + FieldElement{-31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277}, + FieldElement{14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628}, + }, + { + FieldElement{-4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474}, + FieldElement{-26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539}, + FieldElement{-25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822}, + }, + { + FieldElement{-10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970}, + FieldElement{19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756}, + FieldElement{-24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508}, + }, + { + FieldElement{-26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683}, + FieldElement{-10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655}, + FieldElement{-20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158}, + }, + { + FieldElement{-4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125}, + FieldElement{-15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839}, + FieldElement{-20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664}, + }, + { + FieldElement{27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294}, + FieldElement{-18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899}, + FieldElement{-11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070}, + }, + { + FieldElement{3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294}, + FieldElement{-15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949}, + FieldElement{-21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083}, + }, + { + FieldElement{31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420}, + FieldElement{-5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940}, + FieldElement{29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396}, + }, + }, + { + { + FieldElement{-12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567}, + FieldElement{20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127}, + FieldElement{-16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294}, + }, + { + FieldElement{-12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887}, + FieldElement{22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964}, + FieldElement{16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195}, + }, + { + FieldElement{9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244}, + FieldElement{24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999}, + FieldElement{-1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762}, + }, + { + FieldElement{-18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274}, + FieldElement{-33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236}, + FieldElement{-16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605}, + }, + { + FieldElement{-13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761}, + FieldElement{-22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884}, + FieldElement{-6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482}, + }, + { + FieldElement{-24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638}, + FieldElement{-11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490}, + FieldElement{-32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170}, + }, + { + FieldElement{5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736}, + FieldElement{10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124}, + FieldElement{-17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392}, + }, + { + FieldElement{8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029}, + FieldElement{6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048}, + FieldElement{28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958}, + }, + }, + { + { + FieldElement{24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593}, + FieldElement{26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071}, + FieldElement{-11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692}, + }, + { + FieldElement{11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687}, + FieldElement{-160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441}, + FieldElement{-20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001}, + }, + { + FieldElement{-938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460}, + FieldElement{-19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007}, + FieldElement{-21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762}, + }, + { + FieldElement{15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005}, + FieldElement{-9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674}, + FieldElement{4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035}, + }, + { + FieldElement{7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590}, + FieldElement{-2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957}, + FieldElement{-30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812}, + }, + { + FieldElement{33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740}, + FieldElement{-18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122}, + FieldElement{-27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158}, + }, + { + FieldElement{8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885}, + FieldElement{26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140}, + FieldElement{19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857}, + }, + { + FieldElement{801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155}, + FieldElement{19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260}, + FieldElement{19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483}, + }, + }, + { + { + FieldElement{-3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677}, + FieldElement{32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815}, + FieldElement{22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751}, + }, + { + FieldElement{-16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203}, + FieldElement{-11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208}, + FieldElement{1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230}, + }, + { + FieldElement{16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850}, + FieldElement{-21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389}, + FieldElement{-9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968}, + }, + { + FieldElement{-11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689}, + FieldElement{14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880}, + FieldElement{5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304}, + }, + { + FieldElement{30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632}, + FieldElement{-3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412}, + FieldElement{20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566}, + }, + { + FieldElement{-20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038}, + FieldElement{-26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232}, + FieldElement{-1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943}, + }, + { + FieldElement{17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856}, + FieldElement{23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738}, + FieldElement{15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971}, + }, + { + FieldElement{-27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718}, + FieldElement{-13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697}, + FieldElement{-11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883}, + }, + }, + { + { + FieldElement{5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912}, + FieldElement{-26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358}, + FieldElement{3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849}, + }, + { + FieldElement{29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307}, + FieldElement{-14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977}, + FieldElement{-6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335}, + }, + { + FieldElement{-29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644}, + FieldElement{-22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616}, + FieldElement{-27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735}, + }, + { + FieldElement{-21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099}, + FieldElement{29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341}, + FieldElement{-936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336}, + }, + { + FieldElement{-23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646}, + FieldElement{31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425}, + FieldElement{-17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388}, + }, + { + FieldElement{-31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743}, + FieldElement{-16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822}, + FieldElement{-8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462}, + }, + { + FieldElement{18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985}, + FieldElement{9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702}, + FieldElement{-22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797}, + }, + { + FieldElement{21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293}, + FieldElement{27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100}, + FieldElement{19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688}, + }, + }, + { + { + FieldElement{12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186}, + FieldElement{2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610}, + FieldElement{-2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707}, + }, + { + FieldElement{7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220}, + FieldElement{915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025}, + FieldElement{32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044}, + }, + { + FieldElement{32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992}, + FieldElement{-4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027}, + FieldElement{21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197}, + }, + { + FieldElement{8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901}, + FieldElement{31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952}, + FieldElement{19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878}, + }, + { + FieldElement{-28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390}, + FieldElement{32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730}, + FieldElement{2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730}, + }, + { + FieldElement{-19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180}, + FieldElement{-30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272}, + FieldElement{-15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715}, + }, + { + FieldElement{-22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970}, + FieldElement{-31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772}, + FieldElement{-17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865}, + }, + { + FieldElement{15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750}, + FieldElement{20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373}, + FieldElement{32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348}, + }, + }, + { + { + FieldElement{9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144}, + FieldElement{-22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195}, + FieldElement{5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086}, + }, + { + FieldElement{-13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684}, + FieldElement{-8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518}, + FieldElement{-2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233}, + }, + { + FieldElement{-5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793}, + FieldElement{-2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794}, + FieldElement{580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435}, + }, + { + FieldElement{23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921}, + FieldElement{13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518}, + FieldElement{2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563}, + }, + { + FieldElement{14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278}, + FieldElement{-27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024}, + FieldElement{4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030}, + }, + { + FieldElement{10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783}, + FieldElement{27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717}, + FieldElement{6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844}, + }, + { + FieldElement{14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333}, + FieldElement{16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048}, + FieldElement{22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760}, + }, + { + FieldElement{-4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760}, + FieldElement{-15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757}, + FieldElement{-2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112}, + }, + }, + { + { + FieldElement{-19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468}, + FieldElement{3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184}, + FieldElement{10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289}, + }, + { + FieldElement{15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066}, + FieldElement{24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882}, + FieldElement{13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226}, + }, + { + FieldElement{16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101}, + FieldElement{29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279}, + FieldElement{-6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811}, + }, + { + FieldElement{27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709}, + FieldElement{20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714}, + FieldElement{-2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121}, + }, + { + FieldElement{9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464}, + FieldElement{12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847}, + FieldElement{13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400}, + }, + { + FieldElement{4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414}, + FieldElement{-15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158}, + FieldElement{17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045}, + }, + { + FieldElement{-461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415}, + FieldElement{-5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459}, + FieldElement{-31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079}, + }, + { + FieldElement{21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412}, + FieldElement{-20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743}, + FieldElement{-14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836}, + }, + }, + { + { + FieldElement{12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022}, + FieldElement{18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429}, + FieldElement{-6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065}, + }, + { + FieldElement{30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861}, + FieldElement{10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000}, + FieldElement{-33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101}, + }, + { + FieldElement{32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815}, + FieldElement{29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642}, + FieldElement{10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966}, + }, + { + FieldElement{25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574}, + FieldElement{-21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742}, + FieldElement{-18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689}, + }, + { + FieldElement{12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020}, + FieldElement{-10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772}, + FieldElement{3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982}, + }, + { + FieldElement{-14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953}, + FieldElement{-16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218}, + FieldElement{-17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265}, + }, + { + FieldElement{29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073}, + FieldElement{-3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325}, + FieldElement{-11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798}, + }, + { + FieldElement{-4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870}, + FieldElement{-7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863}, + FieldElement{-13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927}, + }, + }, + { + { + FieldElement{-2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267}, + FieldElement{-9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663}, + FieldElement{22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862}, + }, + { + FieldElement{-25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673}, + FieldElement{15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943}, + FieldElement{15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020}, + }, + { + FieldElement{-4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238}, + FieldElement{11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064}, + FieldElement{14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795}, + }, + { + FieldElement{15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052}, + FieldElement{-10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904}, + FieldElement{29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531}, + }, + { + FieldElement{-13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979}, + FieldElement{-5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841}, + FieldElement{10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431}, + }, + { + FieldElement{10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324}, + FieldElement{-31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940}, + FieldElement{10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320}, + }, + { + FieldElement{-15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184}, + FieldElement{14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114}, + FieldElement{30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878}, + }, + { + FieldElement{12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784}, + FieldElement{-2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091}, + FieldElement{-16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585}, + }, + }, + { + { + FieldElement{-8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208}, + FieldElement{10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864}, + FieldElement{17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661}, + }, + { + FieldElement{7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233}, + FieldElement{26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212}, + FieldElement{-12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525}, + }, + { + FieldElement{-24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068}, + FieldElement{9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397}, + FieldElement{-8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988}, + }, + { + FieldElement{5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889}, + FieldElement{32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038}, + FieldElement{14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697}, + }, + { + FieldElement{20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875}, + FieldElement{-25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905}, + FieldElement{-25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656}, + }, + { + FieldElement{11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818}, + FieldElement{27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714}, + FieldElement{10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203}, + }, + { + FieldElement{20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931}, + FieldElement{-30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024}, + FieldElement{-23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084}, + }, + { + FieldElement{-1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204}, + FieldElement{20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817}, + FieldElement{27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667}, + }, + }, + { + { + FieldElement{11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504}, + FieldElement{-12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768}, + FieldElement{-19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255}, + }, + { + FieldElement{6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790}, + FieldElement{1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438}, + FieldElement{-22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333}, + }, + { + FieldElement{17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971}, + FieldElement{31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905}, + FieldElement{29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409}, + }, + { + FieldElement{12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409}, + FieldElement{6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499}, + FieldElement{-8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363}, + }, + { + FieldElement{28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664}, + FieldElement{-11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324}, + FieldElement{-21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940}, + }, + { + FieldElement{13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990}, + FieldElement{-17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914}, + FieldElement{-25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290}, + }, + { + FieldElement{24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257}, + FieldElement{-6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433}, + FieldElement{-16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236}, + }, + { + FieldElement{-12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045}, + FieldElement{11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093}, + FieldElement{-1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347}, + }, + }, + { + { + FieldElement{-28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191}, + FieldElement{-15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507}, + FieldElement{-12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906}, + }, + { + FieldElement{3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018}, + FieldElement{-16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109}, + FieldElement{-23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926}, + }, + { + FieldElement{-24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528}, + FieldElement{8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625}, + FieldElement{-32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286}, + }, + { + FieldElement{2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033}, + FieldElement{27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866}, + FieldElement{21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896}, + }, + { + FieldElement{30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075}, + FieldElement{26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347}, + FieldElement{-22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437}, + }, + { + FieldElement{-5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165}, + FieldElement{-18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588}, + FieldElement{-32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193}, + }, + { + FieldElement{-19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017}, + FieldElement{-28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883}, + FieldElement{21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961}, + }, + { + FieldElement{8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043}, + FieldElement{29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663}, + FieldElement{-20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362}, + }, + }, + { + { + FieldElement{-33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860}, + FieldElement{2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466}, + FieldElement{-24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063}, + }, + { + FieldElement{-26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997}, + FieldElement{-1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295}, + FieldElement{-13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369}, + }, + { + FieldElement{9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385}, + FieldElement{18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109}, + FieldElement{2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906}, + }, + { + FieldElement{4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424}, + FieldElement{-19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185}, + FieldElement{7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962}, + }, + { + FieldElement{-7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325}, + FieldElement{10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593}, + FieldElement{696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404}, + }, + { + FieldElement{-11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644}, + FieldElement{17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801}, + FieldElement{26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804}, + }, + { + FieldElement{-31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884}, + FieldElement{-586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577}, + FieldElement{-9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849}, + }, + { + FieldElement{32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473}, + FieldElement{-8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644}, + FieldElement{-2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319}, + }, + }, + { + { + FieldElement{-11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599}, + FieldElement{-9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768}, + FieldElement{-27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084}, + }, + { + FieldElement{-27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328}, + FieldElement{-15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369}, + FieldElement{20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920}, + }, + { + FieldElement{12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815}, + FieldElement{-32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025}, + FieldElement{-21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397}, + }, + { + FieldElement{-20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448}, + FieldElement{6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981}, + FieldElement{30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165}, + }, + { + FieldElement{32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501}, + FieldElement{17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073}, + FieldElement{-1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861}, + }, + { + FieldElement{14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845}, + FieldElement{-1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211}, + FieldElement{18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870}, + }, + { + FieldElement{10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096}, + FieldElement{33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803}, + FieldElement{-32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168}, + }, + { + FieldElement{30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965}, + FieldElement{-14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505}, + FieldElement{18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598}, + }, + }, + { + { + FieldElement{5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782}, + FieldElement{5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900}, + FieldElement{-31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479}, + }, + { + FieldElement{-12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208}, + FieldElement{8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232}, + FieldElement{17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719}, + }, + { + FieldElement{16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271}, + FieldElement{-4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326}, + FieldElement{-8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132}, + }, + { + FieldElement{14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300}, + FieldElement{8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570}, + FieldElement{15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670}, + }, + { + FieldElement{-2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994}, + FieldElement{-12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913}, + FieldElement{31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317}, + }, + { + FieldElement{-25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730}, + FieldElement{842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096}, + FieldElement{-4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078}, + }, + { + FieldElement{-15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411}, + FieldElement{-19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905}, + FieldElement{-9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654}, + }, + { + FieldElement{-28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870}, + FieldElement{-23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498}, + FieldElement{12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579}, + }, + }, + { + { + FieldElement{14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677}, + FieldElement{10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647}, + FieldElement{-2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743}, + }, + { + FieldElement{-25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468}, + FieldElement{21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375}, + FieldElement{-25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155}, + }, + { + FieldElement{6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725}, + FieldElement{-12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612}, + FieldElement{-10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943}, + }, + { + FieldElement{-30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944}, + FieldElement{30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928}, + FieldElement{9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406}, + }, + { + FieldElement{22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139}, + FieldElement{-8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963}, + FieldElement{-31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693}, + }, + { + FieldElement{1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734}, + FieldElement{-448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680}, + FieldElement{-24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410}, + }, + { + FieldElement{-9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931}, + FieldElement{-16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654}, + FieldElement{22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710}, + }, + { + FieldElement{29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180}, + FieldElement{-26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684}, + FieldElement{-10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895}, + }, + }, + { + { + FieldElement{22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501}, + FieldElement{-11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413}, + FieldElement{6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880}, + }, + { + FieldElement{-8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874}, + FieldElement{22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962}, + FieldElement{-7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899}, + }, + { + FieldElement{21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152}, + FieldElement{9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063}, + FieldElement{7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080}, + }, + { + FieldElement{-9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146}, + FieldElement{-17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183}, + FieldElement{-19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133}, + }, + { + FieldElement{-32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421}, + FieldElement{-3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622}, + FieldElement{-4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197}, + }, + { + FieldElement{2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663}, + FieldElement{31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753}, + FieldElement{4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755}, + }, + { + FieldElement{-9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862}, + FieldElement{-26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118}, + FieldElement{26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171}, + }, + { + FieldElement{15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380}, + FieldElement{16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824}, + FieldElement{28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270}, + }, + }, + { + { + FieldElement{-817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438}, + FieldElement{-31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584}, + FieldElement{-594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562}, + }, + { + FieldElement{30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471}, + FieldElement{18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610}, + FieldElement{19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269}, + }, + { + FieldElement{-30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650}, + FieldElement{14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369}, + FieldElement{19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461}, + }, + { + FieldElement{30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462}, + FieldElement{-5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793}, + FieldElement{-2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218}, + }, + { + FieldElement{-24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226}, + FieldElement{18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019}, + FieldElement{-15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037}, + }, + { + FieldElement{31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171}, + FieldElement{-17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132}, + FieldElement{-28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841}, + }, + { + FieldElement{21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181}, + FieldElement{-33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210}, + FieldElement{-1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040}, + }, + { + FieldElement{3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935}, + FieldElement{24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105}, + FieldElement{-28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814}, + }, + }, + { + { + FieldElement{793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852}, + FieldElement{5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581}, + FieldElement{-4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646}, + }, + { + FieldElement{10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844}, + FieldElement{10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025}, + FieldElement{27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453}, + }, + { + FieldElement{-23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068}, + FieldElement{4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192}, + FieldElement{-17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921}, + }, + { + FieldElement{-9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259}, + FieldElement{-12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426}, + FieldElement{-5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072}, + }, + { + FieldElement{-17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305}, + FieldElement{13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832}, + FieldElement{28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943}, + }, + { + FieldElement{-16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011}, + FieldElement{24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447}, + FieldElement{17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494}, + }, + { + FieldElement{-28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245}, + FieldElement{-20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859}, + FieldElement{28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915}, + }, + { + FieldElement{16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707}, + FieldElement{10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848}, + FieldElement{-11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224}, + }, + }, + { + { + FieldElement{-25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391}, + FieldElement{15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215}, + FieldElement{-23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101}, + }, + { + FieldElement{23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713}, + FieldElement{21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849}, + FieldElement{-7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930}, + }, + { + FieldElement{-29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940}, + FieldElement{-21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031}, + FieldElement{-17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404}, + }, + { + FieldElement{-25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243}, + FieldElement{-23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116}, + FieldElement{-24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525}, + }, + { + FieldElement{-23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509}, + FieldElement{-10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883}, + FieldElement{15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865}, + }, + { + FieldElement{-3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660}, + FieldElement{4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273}, + FieldElement{-28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138}, + }, + { + FieldElement{-25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560}, + FieldElement{-10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135}, + FieldElement{2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941}, + }, + { + FieldElement{-4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739}, + FieldElement{18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756}, + FieldElement{-30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819}, + }, + }, + { + { + FieldElement{-6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347}, + FieldElement{-27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028}, + FieldElement{21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075}, + }, + { + FieldElement{16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799}, + FieldElement{-2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609}, + FieldElement{-25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817}, + }, + { + FieldElement{-23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989}, + FieldElement{-30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523}, + FieldElement{4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278}, + }, + { + FieldElement{31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045}, + FieldElement{19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377}, + FieldElement{24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480}, + }, + { + FieldElement{17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016}, + FieldElement{510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426}, + FieldElement{18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525}, + }, + { + FieldElement{13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396}, + FieldElement{9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080}, + FieldElement{12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892}, + }, + { + FieldElement{15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275}, + FieldElement{11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074}, + FieldElement{20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140}, + }, + { + FieldElement{-16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717}, + FieldElement{-1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101}, + FieldElement{24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127}, + }, + }, + { + { + FieldElement{-12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632}, + FieldElement{-26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415}, + FieldElement{-31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160}, + }, + { + FieldElement{31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876}, + FieldElement{22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625}, + FieldElement{-15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478}, + }, + { + FieldElement{27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164}, + FieldElement{26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595}, + FieldElement{-7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248}, + }, + { + FieldElement{-16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858}, + FieldElement{15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193}, + FieldElement{8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184}, + }, + { + FieldElement{-18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942}, + FieldElement{-1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635}, + FieldElement{21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948}, + }, + { + FieldElement{11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935}, + FieldElement{-25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415}, + FieldElement{-15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416}, + }, + { + FieldElement{-7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018}, + FieldElement{4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778}, + FieldElement{366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659}, + }, + { + FieldElement{-24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385}, + FieldElement{18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503}, + FieldElement{476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329}, + }, + }, + { + { + FieldElement{20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056}, + FieldElement{-13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838}, + FieldElement{24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948}, + }, + { + FieldElement{-3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691}, + FieldElement{-15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118}, + FieldElement{-23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517}, + }, + { + FieldElement{-20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269}, + FieldElement{-6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904}, + FieldElement{-23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589}, + }, + { + FieldElement{-28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193}, + FieldElement{-7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910}, + FieldElement{-30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930}, + }, + { + FieldElement{-7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667}, + FieldElement{25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481}, + FieldElement{-9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876}, + }, + { + FieldElement{22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640}, + FieldElement{-8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278}, + FieldElement{-21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112}, + }, + { + FieldElement{26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272}, + FieldElement{17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012}, + FieldElement{-10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221}, + }, + { + FieldElement{30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046}, + FieldElement{13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345}, + FieldElement{-19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310}, + }, + }, + { + { + FieldElement{19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937}, + FieldElement{31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636}, + FieldElement{-9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008}, + }, + { + FieldElement{-2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429}, + FieldElement{-15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576}, + FieldElement{31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066}, + }, + { + FieldElement{-9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490}, + FieldElement{-12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104}, + FieldElement{33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053}, + }, + { + FieldElement{31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275}, + FieldElement{-20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511}, + FieldElement{22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095}, + }, + { + FieldElement{-28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439}, + FieldElement{23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939}, + FieldElement{-23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424}, + }, + { + FieldElement{2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310}, + FieldElement{3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608}, + FieldElement{-32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079}, + }, + { + FieldElement{-23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101}, + FieldElement{21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418}, + FieldElement{18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576}, + }, + { + FieldElement{30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356}, + FieldElement{9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996}, + FieldElement{-26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099}, + }, + }, + { + { + FieldElement{-26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728}, + FieldElement{-13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658}, + FieldElement{-10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242}, + }, + { + FieldElement{-21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001}, + FieldElement{-4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766}, + FieldElement{18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373}, + }, + { + FieldElement{26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458}, + FieldElement{-17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628}, + FieldElement{-13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657}, + }, + { + FieldElement{-23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062}, + FieldElement{25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616}, + FieldElement{31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014}, + }, + { + FieldElement{24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383}, + FieldElement{-25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814}, + FieldElement{-20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718}, + }, + { + FieldElement{30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417}, + FieldElement{2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222}, + FieldElement{33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444}, + }, + { + FieldElement{-20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597}, + FieldElement{23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970}, + FieldElement{1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799}, + }, + { + FieldElement{-5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647}, + FieldElement{13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511}, + FieldElement{-29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032}, + }, + }, + { + { + FieldElement{9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834}, + FieldElement{-23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461}, + FieldElement{29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062}, + }, + { + FieldElement{-25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516}, + FieldElement{-20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547}, + FieldElement{-24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240}, + }, + { + FieldElement{-17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038}, + FieldElement{-33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741}, + FieldElement{16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103}, + }, + { + FieldElement{-19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747}, + FieldElement{-1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323}, + FieldElement{31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016}, + }, + { + FieldElement{-14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373}, + FieldElement{15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228}, + FieldElement{-2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141}, + }, + { + FieldElement{16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399}, + FieldElement{11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831}, + FieldElement{-185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376}, + }, + { + FieldElement{-32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313}, + FieldElement{-18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958}, + FieldElement{-6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577}, + }, + { + FieldElement{-22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743}, + FieldElement{29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684}, + FieldElement{-20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476}, + }, + }, +} diff --git a/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go b/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go new file mode 100644 index 0000000..5f8b994 --- /dev/null +++ b/vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go @@ -0,0 +1,1771 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package edwards25519 + +// This code is a port of the public domain, “ref10” implementation of ed25519 +// from SUPERCOP. + +// FieldElement represents an element of the field GF(2^255 - 19). An element +// t, entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77 +// t[3]+2^102 t[4]+...+2^230 t[9]. Bounds on each t[i] vary depending on +// context. +type FieldElement [10]int32 + +var zero FieldElement + +func FeZero(fe *FieldElement) { + copy(fe[:], zero[:]) +} + +func FeOne(fe *FieldElement) { + FeZero(fe) + fe[0] = 1 +} + +func FeAdd(dst, a, b *FieldElement) { + dst[0] = a[0] + b[0] + dst[1] = a[1] + b[1] + dst[2] = a[2] + b[2] + dst[3] = a[3] + b[3] + dst[4] = a[4] + b[4] + dst[5] = a[5] + b[5] + dst[6] = a[6] + b[6] + dst[7] = a[7] + b[7] + dst[8] = a[8] + b[8] + dst[9] = a[9] + b[9] +} + +func FeSub(dst, a, b *FieldElement) { + dst[0] = a[0] - b[0] + dst[1] = a[1] - b[1] + dst[2] = a[2] - b[2] + dst[3] = a[3] - b[3] + dst[4] = a[4] - b[4] + dst[5] = a[5] - b[5] + dst[6] = a[6] - b[6] + dst[7] = a[7] - b[7] + dst[8] = a[8] - b[8] + dst[9] = a[9] - b[9] +} + +func FeCopy(dst, src *FieldElement) { + copy(dst[:], src[:]) +} + +// Replace (f,g) with (g,g) if b == 1; +// replace (f,g) with (f,g) if b == 0. +// +// Preconditions: b in {0,1}. +func FeCMove(f, g *FieldElement, b int32) { + b = -b + f[0] ^= b & (f[0] ^ g[0]) + f[1] ^= b & (f[1] ^ g[1]) + f[2] ^= b & (f[2] ^ g[2]) + f[3] ^= b & (f[3] ^ g[3]) + f[4] ^= b & (f[4] ^ g[4]) + f[5] ^= b & (f[5] ^ g[5]) + f[6] ^= b & (f[6] ^ g[6]) + f[7] ^= b & (f[7] ^ g[7]) + f[8] ^= b & (f[8] ^ g[8]) + f[9] ^= b & (f[9] ^ g[9]) +} + +func load3(in []byte) int64 { + var r int64 + r = int64(in[0]) + r |= int64(in[1]) << 8 + r |= int64(in[2]) << 16 + return r +} + +func load4(in []byte) int64 { + var r int64 + r = int64(in[0]) + r |= int64(in[1]) << 8 + r |= int64(in[2]) << 16 + r |= int64(in[3]) << 24 + return r +} + +func FeFromBytes(dst *FieldElement, src *[32]byte) { + h0 := load4(src[:]) + h1 := load3(src[4:]) << 6 + h2 := load3(src[7:]) << 5 + h3 := load3(src[10:]) << 3 + h4 := load3(src[13:]) << 2 + h5 := load4(src[16:]) + h6 := load3(src[20:]) << 7 + h7 := load3(src[23:]) << 5 + h8 := load3(src[26:]) << 4 + h9 := (load3(src[29:]) & 8388607) << 2 + + FeCombine(dst, h0, h1, h2, h3, h4, h5, h6, h7, h8, h9) +} + +// FeToBytes marshals h to s. +// Preconditions: +// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +// +// Write p=2^255-19; q=floor(h/p). +// Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). +// +// Proof: +// Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. +// Also have |h-2^230 h9|<2^230 so |19 2^(-255)(h-2^230 h9)|<1/4. +// +// Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). +// Then 0> 25 + q = (h[0] + q) >> 26 + q = (h[1] + q) >> 25 + q = (h[2] + q) >> 26 + q = (h[3] + q) >> 25 + q = (h[4] + q) >> 26 + q = (h[5] + q) >> 25 + q = (h[6] + q) >> 26 + q = (h[7] + q) >> 25 + q = (h[8] + q) >> 26 + q = (h[9] + q) >> 25 + + // Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. + h[0] += 19 * q + // Goal: Output h-2^255 q, which is between 0 and 2^255-20. + + carry[0] = h[0] >> 26 + h[1] += carry[0] + h[0] -= carry[0] << 26 + carry[1] = h[1] >> 25 + h[2] += carry[1] + h[1] -= carry[1] << 25 + carry[2] = h[2] >> 26 + h[3] += carry[2] + h[2] -= carry[2] << 26 + carry[3] = h[3] >> 25 + h[4] += carry[3] + h[3] -= carry[3] << 25 + carry[4] = h[4] >> 26 + h[5] += carry[4] + h[4] -= carry[4] << 26 + carry[5] = h[5] >> 25 + h[6] += carry[5] + h[5] -= carry[5] << 25 + carry[6] = h[6] >> 26 + h[7] += carry[6] + h[6] -= carry[6] << 26 + carry[7] = h[7] >> 25 + h[8] += carry[7] + h[7] -= carry[7] << 25 + carry[8] = h[8] >> 26 + h[9] += carry[8] + h[8] -= carry[8] << 26 + carry[9] = h[9] >> 25 + h[9] -= carry[9] << 25 + // h10 = carry9 + + // Goal: Output h[0]+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + // Have h[0]+...+2^230 h[9] between 0 and 2^255-1; + // evidently 2^255 h10-2^255 q = 0. + // Goal: Output h[0]+...+2^230 h[9]. + + s[0] = byte(h[0] >> 0) + s[1] = byte(h[0] >> 8) + s[2] = byte(h[0] >> 16) + s[3] = byte((h[0] >> 24) | (h[1] << 2)) + s[4] = byte(h[1] >> 6) + s[5] = byte(h[1] >> 14) + s[6] = byte((h[1] >> 22) | (h[2] << 3)) + s[7] = byte(h[2] >> 5) + s[8] = byte(h[2] >> 13) + s[9] = byte((h[2] >> 21) | (h[3] << 5)) + s[10] = byte(h[3] >> 3) + s[11] = byte(h[3] >> 11) + s[12] = byte((h[3] >> 19) | (h[4] << 6)) + s[13] = byte(h[4] >> 2) + s[14] = byte(h[4] >> 10) + s[15] = byte(h[4] >> 18) + s[16] = byte(h[5] >> 0) + s[17] = byte(h[5] >> 8) + s[18] = byte(h[5] >> 16) + s[19] = byte((h[5] >> 24) | (h[6] << 1)) + s[20] = byte(h[6] >> 7) + s[21] = byte(h[6] >> 15) + s[22] = byte((h[6] >> 23) | (h[7] << 3)) + s[23] = byte(h[7] >> 5) + s[24] = byte(h[7] >> 13) + s[25] = byte((h[7] >> 21) | (h[8] << 4)) + s[26] = byte(h[8] >> 4) + s[27] = byte(h[8] >> 12) + s[28] = byte((h[8] >> 20) | (h[9] << 6)) + s[29] = byte(h[9] >> 2) + s[30] = byte(h[9] >> 10) + s[31] = byte(h[9] >> 18) +} + +func FeIsNegative(f *FieldElement) byte { + var s [32]byte + FeToBytes(&s, f) + return s[0] & 1 +} + +func FeIsNonZero(f *FieldElement) int32 { + var s [32]byte + FeToBytes(&s, f) + var x uint8 + for _, b := range s { + x |= b + } + x |= x >> 4 + x |= x >> 2 + x |= x >> 1 + return int32(x & 1) +} + +// FeNeg sets h = -f +// +// Preconditions: +// |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +// +// Postconditions: +// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +func FeNeg(h, f *FieldElement) { + h[0] = -f[0] + h[1] = -f[1] + h[2] = -f[2] + h[3] = -f[3] + h[4] = -f[4] + h[5] = -f[5] + h[6] = -f[6] + h[7] = -f[7] + h[8] = -f[8] + h[9] = -f[9] +} + +func FeCombine(h *FieldElement, h0, h1, h2, h3, h4, h5, h6, h7, h8, h9 int64) { + var c0, c1, c2, c3, c4, c5, c6, c7, c8, c9 int64 + + /* + |h0| <= (1.1*1.1*2^52*(1+19+19+19+19)+1.1*1.1*2^50*(38+38+38+38+38)) + i.e. |h0| <= 1.2*2^59; narrower ranges for h2, h4, h6, h8 + |h1| <= (1.1*1.1*2^51*(1+1+19+19+19+19+19+19+19+19)) + i.e. |h1| <= 1.5*2^58; narrower ranges for h3, h5, h7, h9 + */ + + c0 = (h0 + (1 << 25)) >> 26 + h1 += c0 + h0 -= c0 << 26 + c4 = (h4 + (1 << 25)) >> 26 + h5 += c4 + h4 -= c4 << 26 + /* |h0| <= 2^25 */ + /* |h4| <= 2^25 */ + /* |h1| <= 1.51*2^58 */ + /* |h5| <= 1.51*2^58 */ + + c1 = (h1 + (1 << 24)) >> 25 + h2 += c1 + h1 -= c1 << 25 + c5 = (h5 + (1 << 24)) >> 25 + h6 += c5 + h5 -= c5 << 25 + /* |h1| <= 2^24; from now on fits into int32 */ + /* |h5| <= 2^24; from now on fits into int32 */ + /* |h2| <= 1.21*2^59 */ + /* |h6| <= 1.21*2^59 */ + + c2 = (h2 + (1 << 25)) >> 26 + h3 += c2 + h2 -= c2 << 26 + c6 = (h6 + (1 << 25)) >> 26 + h7 += c6 + h6 -= c6 << 26 + /* |h2| <= 2^25; from now on fits into int32 unchanged */ + /* |h6| <= 2^25; from now on fits into int32 unchanged */ + /* |h3| <= 1.51*2^58 */ + /* |h7| <= 1.51*2^58 */ + + c3 = (h3 + (1 << 24)) >> 25 + h4 += c3 + h3 -= c3 << 25 + c7 = (h7 + (1 << 24)) >> 25 + h8 += c7 + h7 -= c7 << 25 + /* |h3| <= 2^24; from now on fits into int32 unchanged */ + /* |h7| <= 2^24; from now on fits into int32 unchanged */ + /* |h4| <= 1.52*2^33 */ + /* |h8| <= 1.52*2^33 */ + + c4 = (h4 + (1 << 25)) >> 26 + h5 += c4 + h4 -= c4 << 26 + c8 = (h8 + (1 << 25)) >> 26 + h9 += c8 + h8 -= c8 << 26 + /* |h4| <= 2^25; from now on fits into int32 unchanged */ + /* |h8| <= 2^25; from now on fits into int32 unchanged */ + /* |h5| <= 1.01*2^24 */ + /* |h9| <= 1.51*2^58 */ + + c9 = (h9 + (1 << 24)) >> 25 + h0 += c9 * 19 + h9 -= c9 << 25 + /* |h9| <= 2^24; from now on fits into int32 unchanged */ + /* |h0| <= 1.8*2^37 */ + + c0 = (h0 + (1 << 25)) >> 26 + h1 += c0 + h0 -= c0 << 26 + /* |h0| <= 2^25; from now on fits into int32 unchanged */ + /* |h1| <= 1.01*2^24 */ + + h[0] = int32(h0) + h[1] = int32(h1) + h[2] = int32(h2) + h[3] = int32(h3) + h[4] = int32(h4) + h[5] = int32(h5) + h[6] = int32(h6) + h[7] = int32(h7) + h[8] = int32(h8) + h[9] = int32(h9) +} + +// FeMul calculates h = f * g +// Can overlap h with f or g. +// +// Preconditions: +// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +// |g| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +// +// Postconditions: +// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +// +// Notes on implementation strategy: +// +// Using schoolbook multiplication. +// Karatsuba would save a little in some cost models. +// +// Most multiplications by 2 and 19 are 32-bit precomputations; +// cheaper than 64-bit postcomputations. +// +// There is one remaining multiplication by 19 in the carry chain; +// one *19 precomputation can be merged into this, +// but the resulting data flow is considerably less clean. +// +// There are 12 carries below. +// 10 of them are 2-way parallelizable and vectorizable. +// Can get away with 11 carries, but then data flow is much deeper. +// +// With tighter constraints on inputs, can squeeze carries into int32. +func FeMul(h, f, g *FieldElement) { + f0 := int64(f[0]) + f1 := int64(f[1]) + f2 := int64(f[2]) + f3 := int64(f[3]) + f4 := int64(f[4]) + f5 := int64(f[5]) + f6 := int64(f[6]) + f7 := int64(f[7]) + f8 := int64(f[8]) + f9 := int64(f[9]) + + f1_2 := int64(2 * f[1]) + f3_2 := int64(2 * f[3]) + f5_2 := int64(2 * f[5]) + f7_2 := int64(2 * f[7]) + f9_2 := int64(2 * f[9]) + + g0 := int64(g[0]) + g1 := int64(g[1]) + g2 := int64(g[2]) + g3 := int64(g[3]) + g4 := int64(g[4]) + g5 := int64(g[5]) + g6 := int64(g[6]) + g7 := int64(g[7]) + g8 := int64(g[8]) + g9 := int64(g[9]) + + g1_19 := int64(19 * g[1]) /* 1.4*2^29 */ + g2_19 := int64(19 * g[2]) /* 1.4*2^30; still ok */ + g3_19 := int64(19 * g[3]) + g4_19 := int64(19 * g[4]) + g5_19 := int64(19 * g[5]) + g6_19 := int64(19 * g[6]) + g7_19 := int64(19 * g[7]) + g8_19 := int64(19 * g[8]) + g9_19 := int64(19 * g[9]) + + h0 := f0*g0 + f1_2*g9_19 + f2*g8_19 + f3_2*g7_19 + f4*g6_19 + f5_2*g5_19 + f6*g4_19 + f7_2*g3_19 + f8*g2_19 + f9_2*g1_19 + h1 := f0*g1 + f1*g0 + f2*g9_19 + f3*g8_19 + f4*g7_19 + f5*g6_19 + f6*g5_19 + f7*g4_19 + f8*g3_19 + f9*g2_19 + h2 := f0*g2 + f1_2*g1 + f2*g0 + f3_2*g9_19 + f4*g8_19 + f5_2*g7_19 + f6*g6_19 + f7_2*g5_19 + f8*g4_19 + f9_2*g3_19 + h3 := f0*g3 + f1*g2 + f2*g1 + f3*g0 + f4*g9_19 + f5*g8_19 + f6*g7_19 + f7*g6_19 + f8*g5_19 + f9*g4_19 + h4 := f0*g4 + f1_2*g3 + f2*g2 + f3_2*g1 + f4*g0 + f5_2*g9_19 + f6*g8_19 + f7_2*g7_19 + f8*g6_19 + f9_2*g5_19 + h5 := f0*g5 + f1*g4 + f2*g3 + f3*g2 + f4*g1 + f5*g0 + f6*g9_19 + f7*g8_19 + f8*g7_19 + f9*g6_19 + h6 := f0*g6 + f1_2*g5 + f2*g4 + f3_2*g3 + f4*g2 + f5_2*g1 + f6*g0 + f7_2*g9_19 + f8*g8_19 + f9_2*g7_19 + h7 := f0*g7 + f1*g6 + f2*g5 + f3*g4 + f4*g3 + f5*g2 + f6*g1 + f7*g0 + f8*g9_19 + f9*g8_19 + h8 := f0*g8 + f1_2*g7 + f2*g6 + f3_2*g5 + f4*g4 + f5_2*g3 + f6*g2 + f7_2*g1 + f8*g0 + f9_2*g9_19 + h9 := f0*g9 + f1*g8 + f2*g7 + f3*g6 + f4*g5 + f5*g4 + f6*g3 + f7*g2 + f8*g1 + f9*g0 + + FeCombine(h, h0, h1, h2, h3, h4, h5, h6, h7, h8, h9) +} + +func feSquare(f *FieldElement) (h0, h1, h2, h3, h4, h5, h6, h7, h8, h9 int64) { + f0 := int64(f[0]) + f1 := int64(f[1]) + f2 := int64(f[2]) + f3 := int64(f[3]) + f4 := int64(f[4]) + f5 := int64(f[5]) + f6 := int64(f[6]) + f7 := int64(f[7]) + f8 := int64(f[8]) + f9 := int64(f[9]) + f0_2 := int64(2 * f[0]) + f1_2 := int64(2 * f[1]) + f2_2 := int64(2 * f[2]) + f3_2 := int64(2 * f[3]) + f4_2 := int64(2 * f[4]) + f5_2 := int64(2 * f[5]) + f6_2 := int64(2 * f[6]) + f7_2 := int64(2 * f[7]) + f5_38 := 38 * f5 // 1.31*2^30 + f6_19 := 19 * f6 // 1.31*2^30 + f7_38 := 38 * f7 // 1.31*2^30 + f8_19 := 19 * f8 // 1.31*2^30 + f9_38 := 38 * f9 // 1.31*2^30 + + h0 = f0*f0 + f1_2*f9_38 + f2_2*f8_19 + f3_2*f7_38 + f4_2*f6_19 + f5*f5_38 + h1 = f0_2*f1 + f2*f9_38 + f3_2*f8_19 + f4*f7_38 + f5_2*f6_19 + h2 = f0_2*f2 + f1_2*f1 + f3_2*f9_38 + f4_2*f8_19 + f5_2*f7_38 + f6*f6_19 + h3 = f0_2*f3 + f1_2*f2 + f4*f9_38 + f5_2*f8_19 + f6*f7_38 + h4 = f0_2*f4 + f1_2*f3_2 + f2*f2 + f5_2*f9_38 + f6_2*f8_19 + f7*f7_38 + h5 = f0_2*f5 + f1_2*f4 + f2_2*f3 + f6*f9_38 + f7_2*f8_19 + h6 = f0_2*f6 + f1_2*f5_2 + f2_2*f4 + f3_2*f3 + f7_2*f9_38 + f8*f8_19 + h7 = f0_2*f7 + f1_2*f6 + f2_2*f5 + f3_2*f4 + f8*f9_38 + h8 = f0_2*f8 + f1_2*f7_2 + f2_2*f6 + f3_2*f5_2 + f4*f4 + f9*f9_38 + h9 = f0_2*f9 + f1_2*f8 + f2_2*f7 + f3_2*f6 + f4_2*f5 + + return +} + +// FeSquare calculates h = f*f. Can overlap h with f. +// +// Preconditions: +// |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +// +// Postconditions: +// |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +func FeSquare(h, f *FieldElement) { + h0, h1, h2, h3, h4, h5, h6, h7, h8, h9 := feSquare(f) + FeCombine(h, h0, h1, h2, h3, h4, h5, h6, h7, h8, h9) +} + +// FeSquare2 sets h = 2 * f * f +// +// Can overlap h with f. +// +// Preconditions: +// |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. +// +// Postconditions: +// |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +// See fe_mul.c for discussion of implementation strategy. +func FeSquare2(h, f *FieldElement) { + h0, h1, h2, h3, h4, h5, h6, h7, h8, h9 := feSquare(f) + + h0 += h0 + h1 += h1 + h2 += h2 + h3 += h3 + h4 += h4 + h5 += h5 + h6 += h6 + h7 += h7 + h8 += h8 + h9 += h9 + + FeCombine(h, h0, h1, h2, h3, h4, h5, h6, h7, h8, h9) +} + +func FeInvert(out, z *FieldElement) { + var t0, t1, t2, t3 FieldElement + var i int + + FeSquare(&t0, z) // 2^1 + FeSquare(&t1, &t0) // 2^2 + for i = 1; i < 2; i++ { // 2^3 + FeSquare(&t1, &t1) + } + FeMul(&t1, z, &t1) // 2^3 + 2^0 + FeMul(&t0, &t0, &t1) // 2^3 + 2^1 + 2^0 + FeSquare(&t2, &t0) // 2^4 + 2^2 + 2^1 + FeMul(&t1, &t1, &t2) // 2^4 + 2^3 + 2^2 + 2^1 + 2^0 + FeSquare(&t2, &t1) // 5,4,3,2,1 + for i = 1; i < 5; i++ { // 9,8,7,6,5 + FeSquare(&t2, &t2) + } + FeMul(&t1, &t2, &t1) // 9,8,7,6,5,4,3,2,1,0 + FeSquare(&t2, &t1) // 10..1 + for i = 1; i < 10; i++ { // 19..10 + FeSquare(&t2, &t2) + } + FeMul(&t2, &t2, &t1) // 19..0 + FeSquare(&t3, &t2) // 20..1 + for i = 1; i < 20; i++ { // 39..20 + FeSquare(&t3, &t3) + } + FeMul(&t2, &t3, &t2) // 39..0 + FeSquare(&t2, &t2) // 40..1 + for i = 1; i < 10; i++ { // 49..10 + FeSquare(&t2, &t2) + } + FeMul(&t1, &t2, &t1) // 49..0 + FeSquare(&t2, &t1) // 50..1 + for i = 1; i < 50; i++ { // 99..50 + FeSquare(&t2, &t2) + } + FeMul(&t2, &t2, &t1) // 99..0 + FeSquare(&t3, &t2) // 100..1 + for i = 1; i < 100; i++ { // 199..100 + FeSquare(&t3, &t3) + } + FeMul(&t2, &t3, &t2) // 199..0 + FeSquare(&t2, &t2) // 200..1 + for i = 1; i < 50; i++ { // 249..50 + FeSquare(&t2, &t2) + } + FeMul(&t1, &t2, &t1) // 249..0 + FeSquare(&t1, &t1) // 250..1 + for i = 1; i < 5; i++ { // 254..5 + FeSquare(&t1, &t1) + } + FeMul(out, &t1, &t0) // 254..5,3,1,0 +} + +func fePow22523(out, z *FieldElement) { + var t0, t1, t2 FieldElement + var i int + + FeSquare(&t0, z) + for i = 1; i < 1; i++ { + FeSquare(&t0, &t0) + } + FeSquare(&t1, &t0) + for i = 1; i < 2; i++ { + FeSquare(&t1, &t1) + } + FeMul(&t1, z, &t1) + FeMul(&t0, &t0, &t1) + FeSquare(&t0, &t0) + for i = 1; i < 1; i++ { + FeSquare(&t0, &t0) + } + FeMul(&t0, &t1, &t0) + FeSquare(&t1, &t0) + for i = 1; i < 5; i++ { + FeSquare(&t1, &t1) + } + FeMul(&t0, &t1, &t0) + FeSquare(&t1, &t0) + for i = 1; i < 10; i++ { + FeSquare(&t1, &t1) + } + FeMul(&t1, &t1, &t0) + FeSquare(&t2, &t1) + for i = 1; i < 20; i++ { + FeSquare(&t2, &t2) + } + FeMul(&t1, &t2, &t1) + FeSquare(&t1, &t1) + for i = 1; i < 10; i++ { + FeSquare(&t1, &t1) + } + FeMul(&t0, &t1, &t0) + FeSquare(&t1, &t0) + for i = 1; i < 50; i++ { + FeSquare(&t1, &t1) + } + FeMul(&t1, &t1, &t0) + FeSquare(&t2, &t1) + for i = 1; i < 100; i++ { + FeSquare(&t2, &t2) + } + FeMul(&t1, &t2, &t1) + FeSquare(&t1, &t1) + for i = 1; i < 50; i++ { + FeSquare(&t1, &t1) + } + FeMul(&t0, &t1, &t0) + FeSquare(&t0, &t0) + for i = 1; i < 2; i++ { + FeSquare(&t0, &t0) + } + FeMul(out, &t0, z) +} + +// Group elements are members of the elliptic curve -x^2 + y^2 = 1 + d * x^2 * +// y^2 where d = -121665/121666. +// +// Several representations are used: +// ProjectiveGroupElement: (X:Y:Z) satisfying x=X/Z, y=Y/Z +// ExtendedGroupElement: (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT +// CompletedGroupElement: ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T +// PreComputedGroupElement: (y+x,y-x,2dxy) + +type ProjectiveGroupElement struct { + X, Y, Z FieldElement +} + +type ExtendedGroupElement struct { + X, Y, Z, T FieldElement +} + +type CompletedGroupElement struct { + X, Y, Z, T FieldElement +} + +type PreComputedGroupElement struct { + yPlusX, yMinusX, xy2d FieldElement +} + +type CachedGroupElement struct { + yPlusX, yMinusX, Z, T2d FieldElement +} + +func (p *ProjectiveGroupElement) Zero() { + FeZero(&p.X) + FeOne(&p.Y) + FeOne(&p.Z) +} + +func (p *ProjectiveGroupElement) Double(r *CompletedGroupElement) { + var t0 FieldElement + + FeSquare(&r.X, &p.X) + FeSquare(&r.Z, &p.Y) + FeSquare2(&r.T, &p.Z) + FeAdd(&r.Y, &p.X, &p.Y) + FeSquare(&t0, &r.Y) + FeAdd(&r.Y, &r.Z, &r.X) + FeSub(&r.Z, &r.Z, &r.X) + FeSub(&r.X, &t0, &r.Y) + FeSub(&r.T, &r.T, &r.Z) +} + +func (p *ProjectiveGroupElement) ToBytes(s *[32]byte) { + var recip, x, y FieldElement + + FeInvert(&recip, &p.Z) + FeMul(&x, &p.X, &recip) + FeMul(&y, &p.Y, &recip) + FeToBytes(s, &y) + s[31] ^= FeIsNegative(&x) << 7 +} + +func (p *ExtendedGroupElement) Zero() { + FeZero(&p.X) + FeOne(&p.Y) + FeOne(&p.Z) + FeZero(&p.T) +} + +func (p *ExtendedGroupElement) Double(r *CompletedGroupElement) { + var q ProjectiveGroupElement + p.ToProjective(&q) + q.Double(r) +} + +func (p *ExtendedGroupElement) ToCached(r *CachedGroupElement) { + FeAdd(&r.yPlusX, &p.Y, &p.X) + FeSub(&r.yMinusX, &p.Y, &p.X) + FeCopy(&r.Z, &p.Z) + FeMul(&r.T2d, &p.T, &d2) +} + +func (p *ExtendedGroupElement) ToProjective(r *ProjectiveGroupElement) { + FeCopy(&r.X, &p.X) + FeCopy(&r.Y, &p.Y) + FeCopy(&r.Z, &p.Z) +} + +func (p *ExtendedGroupElement) ToBytes(s *[32]byte) { + var recip, x, y FieldElement + + FeInvert(&recip, &p.Z) + FeMul(&x, &p.X, &recip) + FeMul(&y, &p.Y, &recip) + FeToBytes(s, &y) + s[31] ^= FeIsNegative(&x) << 7 +} + +func (p *ExtendedGroupElement) FromBytes(s *[32]byte) bool { + var u, v, v3, vxx, check FieldElement + + FeFromBytes(&p.Y, s) + FeOne(&p.Z) + FeSquare(&u, &p.Y) + FeMul(&v, &u, &d) + FeSub(&u, &u, &p.Z) // y = y^2-1 + FeAdd(&v, &v, &p.Z) // v = dy^2+1 + + FeSquare(&v3, &v) + FeMul(&v3, &v3, &v) // v3 = v^3 + FeSquare(&p.X, &v3) + FeMul(&p.X, &p.X, &v) + FeMul(&p.X, &p.X, &u) // x = uv^7 + + fePow22523(&p.X, &p.X) // x = (uv^7)^((q-5)/8) + FeMul(&p.X, &p.X, &v3) + FeMul(&p.X, &p.X, &u) // x = uv^3(uv^7)^((q-5)/8) + + var tmpX, tmp2 [32]byte + + FeSquare(&vxx, &p.X) + FeMul(&vxx, &vxx, &v) + FeSub(&check, &vxx, &u) // vx^2-u + if FeIsNonZero(&check) == 1 { + FeAdd(&check, &vxx, &u) // vx^2+u + if FeIsNonZero(&check) == 1 { + return false + } + FeMul(&p.X, &p.X, &SqrtM1) + + FeToBytes(&tmpX, &p.X) + for i, v := range tmpX { + tmp2[31-i] = v + } + } + + if FeIsNegative(&p.X) != (s[31] >> 7) { + FeNeg(&p.X, &p.X) + } + + FeMul(&p.T, &p.X, &p.Y) + return true +} + +func (p *CompletedGroupElement) ToProjective(r *ProjectiveGroupElement) { + FeMul(&r.X, &p.X, &p.T) + FeMul(&r.Y, &p.Y, &p.Z) + FeMul(&r.Z, &p.Z, &p.T) +} + +func (p *CompletedGroupElement) ToExtended(r *ExtendedGroupElement) { + FeMul(&r.X, &p.X, &p.T) + FeMul(&r.Y, &p.Y, &p.Z) + FeMul(&r.Z, &p.Z, &p.T) + FeMul(&r.T, &p.X, &p.Y) +} + +func (p *PreComputedGroupElement) Zero() { + FeOne(&p.yPlusX) + FeOne(&p.yMinusX) + FeZero(&p.xy2d) +} + +func geAdd(r *CompletedGroupElement, p *ExtendedGroupElement, q *CachedGroupElement) { + var t0 FieldElement + + FeAdd(&r.X, &p.Y, &p.X) + FeSub(&r.Y, &p.Y, &p.X) + FeMul(&r.Z, &r.X, &q.yPlusX) + FeMul(&r.Y, &r.Y, &q.yMinusX) + FeMul(&r.T, &q.T2d, &p.T) + FeMul(&r.X, &p.Z, &q.Z) + FeAdd(&t0, &r.X, &r.X) + FeSub(&r.X, &r.Z, &r.Y) + FeAdd(&r.Y, &r.Z, &r.Y) + FeAdd(&r.Z, &t0, &r.T) + FeSub(&r.T, &t0, &r.T) +} + +func geSub(r *CompletedGroupElement, p *ExtendedGroupElement, q *CachedGroupElement) { + var t0 FieldElement + + FeAdd(&r.X, &p.Y, &p.X) + FeSub(&r.Y, &p.Y, &p.X) + FeMul(&r.Z, &r.X, &q.yMinusX) + FeMul(&r.Y, &r.Y, &q.yPlusX) + FeMul(&r.T, &q.T2d, &p.T) + FeMul(&r.X, &p.Z, &q.Z) + FeAdd(&t0, &r.X, &r.X) + FeSub(&r.X, &r.Z, &r.Y) + FeAdd(&r.Y, &r.Z, &r.Y) + FeSub(&r.Z, &t0, &r.T) + FeAdd(&r.T, &t0, &r.T) +} + +func geMixedAdd(r *CompletedGroupElement, p *ExtendedGroupElement, q *PreComputedGroupElement) { + var t0 FieldElement + + FeAdd(&r.X, &p.Y, &p.X) + FeSub(&r.Y, &p.Y, &p.X) + FeMul(&r.Z, &r.X, &q.yPlusX) + FeMul(&r.Y, &r.Y, &q.yMinusX) + FeMul(&r.T, &q.xy2d, &p.T) + FeAdd(&t0, &p.Z, &p.Z) + FeSub(&r.X, &r.Z, &r.Y) + FeAdd(&r.Y, &r.Z, &r.Y) + FeAdd(&r.Z, &t0, &r.T) + FeSub(&r.T, &t0, &r.T) +} + +func geMixedSub(r *CompletedGroupElement, p *ExtendedGroupElement, q *PreComputedGroupElement) { + var t0 FieldElement + + FeAdd(&r.X, &p.Y, &p.X) + FeSub(&r.Y, &p.Y, &p.X) + FeMul(&r.Z, &r.X, &q.yMinusX) + FeMul(&r.Y, &r.Y, &q.yPlusX) + FeMul(&r.T, &q.xy2d, &p.T) + FeAdd(&t0, &p.Z, &p.Z) + FeSub(&r.X, &r.Z, &r.Y) + FeAdd(&r.Y, &r.Z, &r.Y) + FeSub(&r.Z, &t0, &r.T) + FeAdd(&r.T, &t0, &r.T) +} + +func slide(r *[256]int8, a *[32]byte) { + for i := range r { + r[i] = int8(1 & (a[i>>3] >> uint(i&7))) + } + + for i := range r { + if r[i] != 0 { + for b := 1; b <= 6 && i+b < 256; b++ { + if r[i+b] != 0 { + if r[i]+(r[i+b]<= -15 { + r[i] -= r[i+b] << uint(b) + for k := i + b; k < 256; k++ { + if r[k] == 0 { + r[k] = 1 + break + } + r[k] = 0 + } + } else { + break + } + } + } + } + } +} + +// GeDoubleScalarMultVartime sets r = a*A + b*B +// where a = a[0]+256*a[1]+...+256^31 a[31]. +// and b = b[0]+256*b[1]+...+256^31 b[31]. +// B is the Ed25519 base point (x,4/5) with x positive. +func GeDoubleScalarMultVartime(r *ProjectiveGroupElement, a *[32]byte, A *ExtendedGroupElement, b *[32]byte) { + var aSlide, bSlide [256]int8 + var Ai [8]CachedGroupElement // A,3A,5A,7A,9A,11A,13A,15A + var t CompletedGroupElement + var u, A2 ExtendedGroupElement + var i int + + slide(&aSlide, a) + slide(&bSlide, b) + + A.ToCached(&Ai[0]) + A.Double(&t) + t.ToExtended(&A2) + + for i := 0; i < 7; i++ { + geAdd(&t, &A2, &Ai[i]) + t.ToExtended(&u) + u.ToCached(&Ai[i+1]) + } + + r.Zero() + + for i = 255; i >= 0; i-- { + if aSlide[i] != 0 || bSlide[i] != 0 { + break + } + } + + for ; i >= 0; i-- { + r.Double(&t) + + if aSlide[i] > 0 { + t.ToExtended(&u) + geAdd(&t, &u, &Ai[aSlide[i]/2]) + } else if aSlide[i] < 0 { + t.ToExtended(&u) + geSub(&t, &u, &Ai[(-aSlide[i])/2]) + } + + if bSlide[i] > 0 { + t.ToExtended(&u) + geMixedAdd(&t, &u, &bi[bSlide[i]/2]) + } else if bSlide[i] < 0 { + t.ToExtended(&u) + geMixedSub(&t, &u, &bi[(-bSlide[i])/2]) + } + + t.ToProjective(r) + } +} + +// equal returns 1 if b == c and 0 otherwise, assuming that b and c are +// non-negative. +func equal(b, c int32) int32 { + x := uint32(b ^ c) + x-- + return int32(x >> 31) +} + +// negative returns 1 if b < 0 and 0 otherwise. +func negative(b int32) int32 { + return (b >> 31) & 1 +} + +func PreComputedGroupElementCMove(t, u *PreComputedGroupElement, b int32) { + FeCMove(&t.yPlusX, &u.yPlusX, b) + FeCMove(&t.yMinusX, &u.yMinusX, b) + FeCMove(&t.xy2d, &u.xy2d, b) +} + +func selectPoint(t *PreComputedGroupElement, pos int32, b int32) { + var minusT PreComputedGroupElement + bNegative := negative(b) + bAbs := b - (((-bNegative) & b) << 1) + + t.Zero() + for i := int32(0); i < 8; i++ { + PreComputedGroupElementCMove(t, &base[pos][i], equal(bAbs, i+1)) + } + FeCopy(&minusT.yPlusX, &t.yMinusX) + FeCopy(&minusT.yMinusX, &t.yPlusX) + FeNeg(&minusT.xy2d, &t.xy2d) + PreComputedGroupElementCMove(t, &minusT, bNegative) +} + +// GeScalarMultBase computes h = a*B, where +// a = a[0]+256*a[1]+...+256^31 a[31] +// B is the Ed25519 base point (x,4/5) with x positive. +// +// Preconditions: +// a[31] <= 127 +func GeScalarMultBase(h *ExtendedGroupElement, a *[32]byte) { + var e [64]int8 + + for i, v := range a { + e[2*i] = int8(v & 15) + e[2*i+1] = int8((v >> 4) & 15) + } + + // each e[i] is between 0 and 15 and e[63] is between 0 and 7. + + carry := int8(0) + for i := 0; i < 63; i++ { + e[i] += carry + carry = (e[i] + 8) >> 4 + e[i] -= carry << 4 + } + e[63] += carry + // each e[i] is between -8 and 8. + + h.Zero() + var t PreComputedGroupElement + var r CompletedGroupElement + for i := int32(1); i < 64; i += 2 { + selectPoint(&t, i/2, int32(e[i])) + geMixedAdd(&r, h, &t) + r.ToExtended(h) + } + + var s ProjectiveGroupElement + + h.Double(&r) + r.ToProjective(&s) + s.Double(&r) + r.ToProjective(&s) + s.Double(&r) + r.ToProjective(&s) + s.Double(&r) + r.ToExtended(h) + + for i := int32(0); i < 64; i += 2 { + selectPoint(&t, i/2, int32(e[i])) + geMixedAdd(&r, h, &t) + r.ToExtended(h) + } +} + +// The scalars are GF(2^252 + 27742317777372353535851937790883648493). + +// Input: +// a[0]+256*a[1]+...+256^31*a[31] = a +// b[0]+256*b[1]+...+256^31*b[31] = b +// c[0]+256*c[1]+...+256^31*c[31] = c +// +// Output: +// s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l +// where l = 2^252 + 27742317777372353535851937790883648493. +func ScMulAdd(s, a, b, c *[32]byte) { + a0 := 2097151 & load3(a[:]) + a1 := 2097151 & (load4(a[2:]) >> 5) + a2 := 2097151 & (load3(a[5:]) >> 2) + a3 := 2097151 & (load4(a[7:]) >> 7) + a4 := 2097151 & (load4(a[10:]) >> 4) + a5 := 2097151 & (load3(a[13:]) >> 1) + a6 := 2097151 & (load4(a[15:]) >> 6) + a7 := 2097151 & (load3(a[18:]) >> 3) + a8 := 2097151 & load3(a[21:]) + a9 := 2097151 & (load4(a[23:]) >> 5) + a10 := 2097151 & (load3(a[26:]) >> 2) + a11 := (load4(a[28:]) >> 7) + b0 := 2097151 & load3(b[:]) + b1 := 2097151 & (load4(b[2:]) >> 5) + b2 := 2097151 & (load3(b[5:]) >> 2) + b3 := 2097151 & (load4(b[7:]) >> 7) + b4 := 2097151 & (load4(b[10:]) >> 4) + b5 := 2097151 & (load3(b[13:]) >> 1) + b6 := 2097151 & (load4(b[15:]) >> 6) + b7 := 2097151 & (load3(b[18:]) >> 3) + b8 := 2097151 & load3(b[21:]) + b9 := 2097151 & (load4(b[23:]) >> 5) + b10 := 2097151 & (load3(b[26:]) >> 2) + b11 := (load4(b[28:]) >> 7) + c0 := 2097151 & load3(c[:]) + c1 := 2097151 & (load4(c[2:]) >> 5) + c2 := 2097151 & (load3(c[5:]) >> 2) + c3 := 2097151 & (load4(c[7:]) >> 7) + c4 := 2097151 & (load4(c[10:]) >> 4) + c5 := 2097151 & (load3(c[13:]) >> 1) + c6 := 2097151 & (load4(c[15:]) >> 6) + c7 := 2097151 & (load3(c[18:]) >> 3) + c8 := 2097151 & load3(c[21:]) + c9 := 2097151 & (load4(c[23:]) >> 5) + c10 := 2097151 & (load3(c[26:]) >> 2) + c11 := (load4(c[28:]) >> 7) + var carry [23]int64 + + s0 := c0 + a0*b0 + s1 := c1 + a0*b1 + a1*b0 + s2 := c2 + a0*b2 + a1*b1 + a2*b0 + s3 := c3 + a0*b3 + a1*b2 + a2*b1 + a3*b0 + s4 := c4 + a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0 + s5 := c5 + a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0 + s6 := c6 + a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0 + s7 := c7 + a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0 + s8 := c8 + a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0 + s9 := c9 + a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0 + s10 := c10 + a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0 + s11 := c11 + a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0 + s12 := a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1 + s13 := a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2 + s14 := a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3 + s15 := a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4 + s16 := a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5 + s17 := a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6 + s18 := a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7 + s19 := a8*b11 + a9*b10 + a10*b9 + a11*b8 + s20 := a9*b11 + a10*b10 + a11*b9 + s21 := a10*b11 + a11*b10 + s22 := a11 * b11 + s23 := int64(0) + + carry[0] = (s0 + (1 << 20)) >> 21 + s1 += carry[0] + s0 -= carry[0] << 21 + carry[2] = (s2 + (1 << 20)) >> 21 + s3 += carry[2] + s2 -= carry[2] << 21 + carry[4] = (s4 + (1 << 20)) >> 21 + s5 += carry[4] + s4 -= carry[4] << 21 + carry[6] = (s6 + (1 << 20)) >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[8] = (s8 + (1 << 20)) >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[10] = (s10 + (1 << 20)) >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + carry[12] = (s12 + (1 << 20)) >> 21 + s13 += carry[12] + s12 -= carry[12] << 21 + carry[14] = (s14 + (1 << 20)) >> 21 + s15 += carry[14] + s14 -= carry[14] << 21 + carry[16] = (s16 + (1 << 20)) >> 21 + s17 += carry[16] + s16 -= carry[16] << 21 + carry[18] = (s18 + (1 << 20)) >> 21 + s19 += carry[18] + s18 -= carry[18] << 21 + carry[20] = (s20 + (1 << 20)) >> 21 + s21 += carry[20] + s20 -= carry[20] << 21 + carry[22] = (s22 + (1 << 20)) >> 21 + s23 += carry[22] + s22 -= carry[22] << 21 + + carry[1] = (s1 + (1 << 20)) >> 21 + s2 += carry[1] + s1 -= carry[1] << 21 + carry[3] = (s3 + (1 << 20)) >> 21 + s4 += carry[3] + s3 -= carry[3] << 21 + carry[5] = (s5 + (1 << 20)) >> 21 + s6 += carry[5] + s5 -= carry[5] << 21 + carry[7] = (s7 + (1 << 20)) >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[9] = (s9 + (1 << 20)) >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[11] = (s11 + (1 << 20)) >> 21 + s12 += carry[11] + s11 -= carry[11] << 21 + carry[13] = (s13 + (1 << 20)) >> 21 + s14 += carry[13] + s13 -= carry[13] << 21 + carry[15] = (s15 + (1 << 20)) >> 21 + s16 += carry[15] + s15 -= carry[15] << 21 + carry[17] = (s17 + (1 << 20)) >> 21 + s18 += carry[17] + s17 -= carry[17] << 21 + carry[19] = (s19 + (1 << 20)) >> 21 + s20 += carry[19] + s19 -= carry[19] << 21 + carry[21] = (s21 + (1 << 20)) >> 21 + s22 += carry[21] + s21 -= carry[21] << 21 + + s11 += s23 * 666643 + s12 += s23 * 470296 + s13 += s23 * 654183 + s14 -= s23 * 997805 + s15 += s23 * 136657 + s16 -= s23 * 683901 + s23 = 0 + + s10 += s22 * 666643 + s11 += s22 * 470296 + s12 += s22 * 654183 + s13 -= s22 * 997805 + s14 += s22 * 136657 + s15 -= s22 * 683901 + s22 = 0 + + s9 += s21 * 666643 + s10 += s21 * 470296 + s11 += s21 * 654183 + s12 -= s21 * 997805 + s13 += s21 * 136657 + s14 -= s21 * 683901 + s21 = 0 + + s8 += s20 * 666643 + s9 += s20 * 470296 + s10 += s20 * 654183 + s11 -= s20 * 997805 + s12 += s20 * 136657 + s13 -= s20 * 683901 + s20 = 0 + + s7 += s19 * 666643 + s8 += s19 * 470296 + s9 += s19 * 654183 + s10 -= s19 * 997805 + s11 += s19 * 136657 + s12 -= s19 * 683901 + s19 = 0 + + s6 += s18 * 666643 + s7 += s18 * 470296 + s8 += s18 * 654183 + s9 -= s18 * 997805 + s10 += s18 * 136657 + s11 -= s18 * 683901 + s18 = 0 + + carry[6] = (s6 + (1 << 20)) >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[8] = (s8 + (1 << 20)) >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[10] = (s10 + (1 << 20)) >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + carry[12] = (s12 + (1 << 20)) >> 21 + s13 += carry[12] + s12 -= carry[12] << 21 + carry[14] = (s14 + (1 << 20)) >> 21 + s15 += carry[14] + s14 -= carry[14] << 21 + carry[16] = (s16 + (1 << 20)) >> 21 + s17 += carry[16] + s16 -= carry[16] << 21 + + carry[7] = (s7 + (1 << 20)) >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[9] = (s9 + (1 << 20)) >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[11] = (s11 + (1 << 20)) >> 21 + s12 += carry[11] + s11 -= carry[11] << 21 + carry[13] = (s13 + (1 << 20)) >> 21 + s14 += carry[13] + s13 -= carry[13] << 21 + carry[15] = (s15 + (1 << 20)) >> 21 + s16 += carry[15] + s15 -= carry[15] << 21 + + s5 += s17 * 666643 + s6 += s17 * 470296 + s7 += s17 * 654183 + s8 -= s17 * 997805 + s9 += s17 * 136657 + s10 -= s17 * 683901 + s17 = 0 + + s4 += s16 * 666643 + s5 += s16 * 470296 + s6 += s16 * 654183 + s7 -= s16 * 997805 + s8 += s16 * 136657 + s9 -= s16 * 683901 + s16 = 0 + + s3 += s15 * 666643 + s4 += s15 * 470296 + s5 += s15 * 654183 + s6 -= s15 * 997805 + s7 += s15 * 136657 + s8 -= s15 * 683901 + s15 = 0 + + s2 += s14 * 666643 + s3 += s14 * 470296 + s4 += s14 * 654183 + s5 -= s14 * 997805 + s6 += s14 * 136657 + s7 -= s14 * 683901 + s14 = 0 + + s1 += s13 * 666643 + s2 += s13 * 470296 + s3 += s13 * 654183 + s4 -= s13 * 997805 + s5 += s13 * 136657 + s6 -= s13 * 683901 + s13 = 0 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = (s0 + (1 << 20)) >> 21 + s1 += carry[0] + s0 -= carry[0] << 21 + carry[2] = (s2 + (1 << 20)) >> 21 + s3 += carry[2] + s2 -= carry[2] << 21 + carry[4] = (s4 + (1 << 20)) >> 21 + s5 += carry[4] + s4 -= carry[4] << 21 + carry[6] = (s6 + (1 << 20)) >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[8] = (s8 + (1 << 20)) >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[10] = (s10 + (1 << 20)) >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + + carry[1] = (s1 + (1 << 20)) >> 21 + s2 += carry[1] + s1 -= carry[1] << 21 + carry[3] = (s3 + (1 << 20)) >> 21 + s4 += carry[3] + s3 -= carry[3] << 21 + carry[5] = (s5 + (1 << 20)) >> 21 + s6 += carry[5] + s5 -= carry[5] << 21 + carry[7] = (s7 + (1 << 20)) >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[9] = (s9 + (1 << 20)) >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[11] = (s11 + (1 << 20)) >> 21 + s12 += carry[11] + s11 -= carry[11] << 21 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = s0 >> 21 + s1 += carry[0] + s0 -= carry[0] << 21 + carry[1] = s1 >> 21 + s2 += carry[1] + s1 -= carry[1] << 21 + carry[2] = s2 >> 21 + s3 += carry[2] + s2 -= carry[2] << 21 + carry[3] = s3 >> 21 + s4 += carry[3] + s3 -= carry[3] << 21 + carry[4] = s4 >> 21 + s5 += carry[4] + s4 -= carry[4] << 21 + carry[5] = s5 >> 21 + s6 += carry[5] + s5 -= carry[5] << 21 + carry[6] = s6 >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[7] = s7 >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[8] = s8 >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[9] = s9 >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[10] = s10 >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + carry[11] = s11 >> 21 + s12 += carry[11] + s11 -= carry[11] << 21 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = s0 >> 21 + s1 += carry[0] + s0 -= carry[0] << 21 + carry[1] = s1 >> 21 + s2 += carry[1] + s1 -= carry[1] << 21 + carry[2] = s2 >> 21 + s3 += carry[2] + s2 -= carry[2] << 21 + carry[3] = s3 >> 21 + s4 += carry[3] + s3 -= carry[3] << 21 + carry[4] = s4 >> 21 + s5 += carry[4] + s4 -= carry[4] << 21 + carry[5] = s5 >> 21 + s6 += carry[5] + s5 -= carry[5] << 21 + carry[6] = s6 >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[7] = s7 >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[8] = s8 >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[9] = s9 >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[10] = s10 >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + + s[0] = byte(s0 >> 0) + s[1] = byte(s0 >> 8) + s[2] = byte((s0 >> 16) | (s1 << 5)) + s[3] = byte(s1 >> 3) + s[4] = byte(s1 >> 11) + s[5] = byte((s1 >> 19) | (s2 << 2)) + s[6] = byte(s2 >> 6) + s[7] = byte((s2 >> 14) | (s3 << 7)) + s[8] = byte(s3 >> 1) + s[9] = byte(s3 >> 9) + s[10] = byte((s3 >> 17) | (s4 << 4)) + s[11] = byte(s4 >> 4) + s[12] = byte(s4 >> 12) + s[13] = byte((s4 >> 20) | (s5 << 1)) + s[14] = byte(s5 >> 7) + s[15] = byte((s5 >> 15) | (s6 << 6)) + s[16] = byte(s6 >> 2) + s[17] = byte(s6 >> 10) + s[18] = byte((s6 >> 18) | (s7 << 3)) + s[19] = byte(s7 >> 5) + s[20] = byte(s7 >> 13) + s[21] = byte(s8 >> 0) + s[22] = byte(s8 >> 8) + s[23] = byte((s8 >> 16) | (s9 << 5)) + s[24] = byte(s9 >> 3) + s[25] = byte(s9 >> 11) + s[26] = byte((s9 >> 19) | (s10 << 2)) + s[27] = byte(s10 >> 6) + s[28] = byte((s10 >> 14) | (s11 << 7)) + s[29] = byte(s11 >> 1) + s[30] = byte(s11 >> 9) + s[31] = byte(s11 >> 17) +} + +// Input: +// s[0]+256*s[1]+...+256^63*s[63] = s +// +// Output: +// s[0]+256*s[1]+...+256^31*s[31] = s mod l +// where l = 2^252 + 27742317777372353535851937790883648493. +func ScReduce(out *[32]byte, s *[64]byte) { + s0 := 2097151 & load3(s[:]) + s1 := 2097151 & (load4(s[2:]) >> 5) + s2 := 2097151 & (load3(s[5:]) >> 2) + s3 := 2097151 & (load4(s[7:]) >> 7) + s4 := 2097151 & (load4(s[10:]) >> 4) + s5 := 2097151 & (load3(s[13:]) >> 1) + s6 := 2097151 & (load4(s[15:]) >> 6) + s7 := 2097151 & (load3(s[18:]) >> 3) + s8 := 2097151 & load3(s[21:]) + s9 := 2097151 & (load4(s[23:]) >> 5) + s10 := 2097151 & (load3(s[26:]) >> 2) + s11 := 2097151 & (load4(s[28:]) >> 7) + s12 := 2097151 & (load4(s[31:]) >> 4) + s13 := 2097151 & (load3(s[34:]) >> 1) + s14 := 2097151 & (load4(s[36:]) >> 6) + s15 := 2097151 & (load3(s[39:]) >> 3) + s16 := 2097151 & load3(s[42:]) + s17 := 2097151 & (load4(s[44:]) >> 5) + s18 := 2097151 & (load3(s[47:]) >> 2) + s19 := 2097151 & (load4(s[49:]) >> 7) + s20 := 2097151 & (load4(s[52:]) >> 4) + s21 := 2097151 & (load3(s[55:]) >> 1) + s22 := 2097151 & (load4(s[57:]) >> 6) + s23 := (load4(s[60:]) >> 3) + + s11 += s23 * 666643 + s12 += s23 * 470296 + s13 += s23 * 654183 + s14 -= s23 * 997805 + s15 += s23 * 136657 + s16 -= s23 * 683901 + s23 = 0 + + s10 += s22 * 666643 + s11 += s22 * 470296 + s12 += s22 * 654183 + s13 -= s22 * 997805 + s14 += s22 * 136657 + s15 -= s22 * 683901 + s22 = 0 + + s9 += s21 * 666643 + s10 += s21 * 470296 + s11 += s21 * 654183 + s12 -= s21 * 997805 + s13 += s21 * 136657 + s14 -= s21 * 683901 + s21 = 0 + + s8 += s20 * 666643 + s9 += s20 * 470296 + s10 += s20 * 654183 + s11 -= s20 * 997805 + s12 += s20 * 136657 + s13 -= s20 * 683901 + s20 = 0 + + s7 += s19 * 666643 + s8 += s19 * 470296 + s9 += s19 * 654183 + s10 -= s19 * 997805 + s11 += s19 * 136657 + s12 -= s19 * 683901 + s19 = 0 + + s6 += s18 * 666643 + s7 += s18 * 470296 + s8 += s18 * 654183 + s9 -= s18 * 997805 + s10 += s18 * 136657 + s11 -= s18 * 683901 + s18 = 0 + + var carry [17]int64 + + carry[6] = (s6 + (1 << 20)) >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[8] = (s8 + (1 << 20)) >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[10] = (s10 + (1 << 20)) >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + carry[12] = (s12 + (1 << 20)) >> 21 + s13 += carry[12] + s12 -= carry[12] << 21 + carry[14] = (s14 + (1 << 20)) >> 21 + s15 += carry[14] + s14 -= carry[14] << 21 + carry[16] = (s16 + (1 << 20)) >> 21 + s17 += carry[16] + s16 -= carry[16] << 21 + + carry[7] = (s7 + (1 << 20)) >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[9] = (s9 + (1 << 20)) >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[11] = (s11 + (1 << 20)) >> 21 + s12 += carry[11] + s11 -= carry[11] << 21 + carry[13] = (s13 + (1 << 20)) >> 21 + s14 += carry[13] + s13 -= carry[13] << 21 + carry[15] = (s15 + (1 << 20)) >> 21 + s16 += carry[15] + s15 -= carry[15] << 21 + + s5 += s17 * 666643 + s6 += s17 * 470296 + s7 += s17 * 654183 + s8 -= s17 * 997805 + s9 += s17 * 136657 + s10 -= s17 * 683901 + s17 = 0 + + s4 += s16 * 666643 + s5 += s16 * 470296 + s6 += s16 * 654183 + s7 -= s16 * 997805 + s8 += s16 * 136657 + s9 -= s16 * 683901 + s16 = 0 + + s3 += s15 * 666643 + s4 += s15 * 470296 + s5 += s15 * 654183 + s6 -= s15 * 997805 + s7 += s15 * 136657 + s8 -= s15 * 683901 + s15 = 0 + + s2 += s14 * 666643 + s3 += s14 * 470296 + s4 += s14 * 654183 + s5 -= s14 * 997805 + s6 += s14 * 136657 + s7 -= s14 * 683901 + s14 = 0 + + s1 += s13 * 666643 + s2 += s13 * 470296 + s3 += s13 * 654183 + s4 -= s13 * 997805 + s5 += s13 * 136657 + s6 -= s13 * 683901 + s13 = 0 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = (s0 + (1 << 20)) >> 21 + s1 += carry[0] + s0 -= carry[0] << 21 + carry[2] = (s2 + (1 << 20)) >> 21 + s3 += carry[2] + s2 -= carry[2] << 21 + carry[4] = (s4 + (1 << 20)) >> 21 + s5 += carry[4] + s4 -= carry[4] << 21 + carry[6] = (s6 + (1 << 20)) >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[8] = (s8 + (1 << 20)) >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[10] = (s10 + (1 << 20)) >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + + carry[1] = (s1 + (1 << 20)) >> 21 + s2 += carry[1] + s1 -= carry[1] << 21 + carry[3] = (s3 + (1 << 20)) >> 21 + s4 += carry[3] + s3 -= carry[3] << 21 + carry[5] = (s5 + (1 << 20)) >> 21 + s6 += carry[5] + s5 -= carry[5] << 21 + carry[7] = (s7 + (1 << 20)) >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[9] = (s9 + (1 << 20)) >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[11] = (s11 + (1 << 20)) >> 21 + s12 += carry[11] + s11 -= carry[11] << 21 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = s0 >> 21 + s1 += carry[0] + s0 -= carry[0] << 21 + carry[1] = s1 >> 21 + s2 += carry[1] + s1 -= carry[1] << 21 + carry[2] = s2 >> 21 + s3 += carry[2] + s2 -= carry[2] << 21 + carry[3] = s3 >> 21 + s4 += carry[3] + s3 -= carry[3] << 21 + carry[4] = s4 >> 21 + s5 += carry[4] + s4 -= carry[4] << 21 + carry[5] = s5 >> 21 + s6 += carry[5] + s5 -= carry[5] << 21 + carry[6] = s6 >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[7] = s7 >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[8] = s8 >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[9] = s9 >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[10] = s10 >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + carry[11] = s11 >> 21 + s12 += carry[11] + s11 -= carry[11] << 21 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = s0 >> 21 + s1 += carry[0] + s0 -= carry[0] << 21 + carry[1] = s1 >> 21 + s2 += carry[1] + s1 -= carry[1] << 21 + carry[2] = s2 >> 21 + s3 += carry[2] + s2 -= carry[2] << 21 + carry[3] = s3 >> 21 + s4 += carry[3] + s3 -= carry[3] << 21 + carry[4] = s4 >> 21 + s5 += carry[4] + s4 -= carry[4] << 21 + carry[5] = s5 >> 21 + s6 += carry[5] + s5 -= carry[5] << 21 + carry[6] = s6 >> 21 + s7 += carry[6] + s6 -= carry[6] << 21 + carry[7] = s7 >> 21 + s8 += carry[7] + s7 -= carry[7] << 21 + carry[8] = s8 >> 21 + s9 += carry[8] + s8 -= carry[8] << 21 + carry[9] = s9 >> 21 + s10 += carry[9] + s9 -= carry[9] << 21 + carry[10] = s10 >> 21 + s11 += carry[10] + s10 -= carry[10] << 21 + + out[0] = byte(s0 >> 0) + out[1] = byte(s0 >> 8) + out[2] = byte((s0 >> 16) | (s1 << 5)) + out[3] = byte(s1 >> 3) + out[4] = byte(s1 >> 11) + out[5] = byte((s1 >> 19) | (s2 << 2)) + out[6] = byte(s2 >> 6) + out[7] = byte((s2 >> 14) | (s3 << 7)) + out[8] = byte(s3 >> 1) + out[9] = byte(s3 >> 9) + out[10] = byte((s3 >> 17) | (s4 << 4)) + out[11] = byte(s4 >> 4) + out[12] = byte(s4 >> 12) + out[13] = byte((s4 >> 20) | (s5 << 1)) + out[14] = byte(s5 >> 7) + out[15] = byte((s5 >> 15) | (s6 << 6)) + out[16] = byte(s6 >> 2) + out[17] = byte(s6 >> 10) + out[18] = byte((s6 >> 18) | (s7 << 3)) + out[19] = byte(s7 >> 5) + out[20] = byte(s7 >> 13) + out[21] = byte(s8 >> 0) + out[22] = byte(s8 >> 8) + out[23] = byte((s8 >> 16) | (s9 << 5)) + out[24] = byte(s9 >> 3) + out[25] = byte(s9 >> 11) + out[26] = byte((s9 >> 19) | (s10 << 2)) + out[27] = byte(s10 >> 6) + out[28] = byte((s10 >> 14) | (s11 << 7)) + out[29] = byte(s11 >> 1) + out[30] = byte(s11 >> 9) + out[31] = byte(s11 >> 17) +} diff --git a/vendor/golang.org/x/crypto/internal/chacha20/chacha_generic.go b/vendor/golang.org/x/crypto/internal/chacha20/chacha_generic.go new file mode 100644 index 0000000..0f8efdb --- /dev/null +++ b/vendor/golang.org/x/crypto/internal/chacha20/chacha_generic.go @@ -0,0 +1,198 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ChaCha20 implements the core ChaCha20 function as specified in https://tools.ietf.org/html/rfc7539#section-2.3. +package chacha20 + +import "encoding/binary" + +const rounds = 20 + +// core applies the ChaCha20 core function to 16-byte input in, 32-byte key k, +// and 16-byte constant c, and puts the result into 64-byte array out. +func core(out *[64]byte, in *[16]byte, k *[32]byte) { + j0 := uint32(0x61707865) + j1 := uint32(0x3320646e) + j2 := uint32(0x79622d32) + j3 := uint32(0x6b206574) + j4 := binary.LittleEndian.Uint32(k[0:4]) + j5 := binary.LittleEndian.Uint32(k[4:8]) + j6 := binary.LittleEndian.Uint32(k[8:12]) + j7 := binary.LittleEndian.Uint32(k[12:16]) + j8 := binary.LittleEndian.Uint32(k[16:20]) + j9 := binary.LittleEndian.Uint32(k[20:24]) + j10 := binary.LittleEndian.Uint32(k[24:28]) + j11 := binary.LittleEndian.Uint32(k[28:32]) + j12 := binary.LittleEndian.Uint32(in[0:4]) + j13 := binary.LittleEndian.Uint32(in[4:8]) + j14 := binary.LittleEndian.Uint32(in[8:12]) + j15 := binary.LittleEndian.Uint32(in[12:16]) + + x0, x1, x2, x3, x4, x5, x6, x7 := j0, j1, j2, j3, j4, j5, j6, j7 + x8, x9, x10, x11, x12, x13, x14, x15 := j8, j9, j10, j11, j12, j13, j14, j15 + + for i := 0; i < rounds; i += 2 { + x0 += x4 + x12 ^= x0 + x12 = (x12 << 16) | (x12 >> (16)) + x8 += x12 + x4 ^= x8 + x4 = (x4 << 12) | (x4 >> (20)) + x0 += x4 + x12 ^= x0 + x12 = (x12 << 8) | (x12 >> (24)) + x8 += x12 + x4 ^= x8 + x4 = (x4 << 7) | (x4 >> (25)) + x1 += x5 + x13 ^= x1 + x13 = (x13 << 16) | (x13 >> 16) + x9 += x13 + x5 ^= x9 + x5 = (x5 << 12) | (x5 >> 20) + x1 += x5 + x13 ^= x1 + x13 = (x13 << 8) | (x13 >> 24) + x9 += x13 + x5 ^= x9 + x5 = (x5 << 7) | (x5 >> 25) + x2 += x6 + x14 ^= x2 + x14 = (x14 << 16) | (x14 >> 16) + x10 += x14 + x6 ^= x10 + x6 = (x6 << 12) | (x6 >> 20) + x2 += x6 + x14 ^= x2 + x14 = (x14 << 8) | (x14 >> 24) + x10 += x14 + x6 ^= x10 + x6 = (x6 << 7) | (x6 >> 25) + x3 += x7 + x15 ^= x3 + x15 = (x15 << 16) | (x15 >> 16) + x11 += x15 + x7 ^= x11 + x7 = (x7 << 12) | (x7 >> 20) + x3 += x7 + x15 ^= x3 + x15 = (x15 << 8) | (x15 >> 24) + x11 += x15 + x7 ^= x11 + x7 = (x7 << 7) | (x7 >> 25) + x0 += x5 + x15 ^= x0 + x15 = (x15 << 16) | (x15 >> 16) + x10 += x15 + x5 ^= x10 + x5 = (x5 << 12) | (x5 >> 20) + x0 += x5 + x15 ^= x0 + x15 = (x15 << 8) | (x15 >> 24) + x10 += x15 + x5 ^= x10 + x5 = (x5 << 7) | (x5 >> 25) + x1 += x6 + x12 ^= x1 + x12 = (x12 << 16) | (x12 >> 16) + x11 += x12 + x6 ^= x11 + x6 = (x6 << 12) | (x6 >> 20) + x1 += x6 + x12 ^= x1 + x12 = (x12 << 8) | (x12 >> 24) + x11 += x12 + x6 ^= x11 + x6 = (x6 << 7) | (x6 >> 25) + x2 += x7 + x13 ^= x2 + x13 = (x13 << 16) | (x13 >> 16) + x8 += x13 + x7 ^= x8 + x7 = (x7 << 12) | (x7 >> 20) + x2 += x7 + x13 ^= x2 + x13 = (x13 << 8) | (x13 >> 24) + x8 += x13 + x7 ^= x8 + x7 = (x7 << 7) | (x7 >> 25) + x3 += x4 + x14 ^= x3 + x14 = (x14 << 16) | (x14 >> 16) + x9 += x14 + x4 ^= x9 + x4 = (x4 << 12) | (x4 >> 20) + x3 += x4 + x14 ^= x3 + x14 = (x14 << 8) | (x14 >> 24) + x9 += x14 + x4 ^= x9 + x4 = (x4 << 7) | (x4 >> 25) + } + + x0 += j0 + x1 += j1 + x2 += j2 + x3 += j3 + x4 += j4 + x5 += j5 + x6 += j6 + x7 += j7 + x8 += j8 + x9 += j9 + x10 += j10 + x11 += j11 + x12 += j12 + x13 += j13 + x14 += j14 + x15 += j15 + + binary.LittleEndian.PutUint32(out[0:4], x0) + binary.LittleEndian.PutUint32(out[4:8], x1) + binary.LittleEndian.PutUint32(out[8:12], x2) + binary.LittleEndian.PutUint32(out[12:16], x3) + binary.LittleEndian.PutUint32(out[16:20], x4) + binary.LittleEndian.PutUint32(out[20:24], x5) + binary.LittleEndian.PutUint32(out[24:28], x6) + binary.LittleEndian.PutUint32(out[28:32], x7) + binary.LittleEndian.PutUint32(out[32:36], x8) + binary.LittleEndian.PutUint32(out[36:40], x9) + binary.LittleEndian.PutUint32(out[40:44], x10) + binary.LittleEndian.PutUint32(out[44:48], x11) + binary.LittleEndian.PutUint32(out[48:52], x12) + binary.LittleEndian.PutUint32(out[52:56], x13) + binary.LittleEndian.PutUint32(out[56:60], x14) + binary.LittleEndian.PutUint32(out[60:64], x15) +} + +// XORKeyStream crypts bytes from in to out using the given key and counters. +// In and out must overlap entirely or not at all. Counter contains the raw +// ChaCha20 counter bytes (i.e. block counter followed by nonce). +func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) { + var block [64]byte + var counterCopy [16]byte + copy(counterCopy[:], counter[:]) + + for len(in) >= 64 { + core(&block, &counterCopy, key) + for i, x := range block { + out[i] = in[i] ^ x + } + u := uint32(1) + for i := 0; i < 4; i++ { + u += uint32(counterCopy[i]) + counterCopy[i] = byte(u) + u >>= 8 + } + in = in[64:] + out = out[64:] + } + + if len(in) > 0 { + core(&block, &counterCopy, key) + for i, v := range in { + out[i] = v ^ block[i] + } + } +} diff --git a/vendor/golang.org/x/crypto/internal/chacha20/chacha_test.go b/vendor/golang.org/x/crypto/internal/chacha20/chacha_test.go new file mode 100644 index 0000000..b80d34c --- /dev/null +++ b/vendor/golang.org/x/crypto/internal/chacha20/chacha_test.go @@ -0,0 +1,33 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package chacha20 + +import ( + "encoding/hex" + "testing" +) + +func TestCore(t *testing.T) { + // This is just a smoke test that checks the example from + // https://tools.ietf.org/html/rfc7539#section-2.3.2. The + // chacha20poly1305 package contains much more extensive tests of this + // code. + var key [32]byte + for i := range key { + key[i] = byte(i) + } + + var input [16]byte + input[0] = 1 + input[7] = 9 + input[11] = 0x4a + + var out [64]byte + XORKeyStream(out[:], out[:], &input, &key) + const expected = "10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4ed2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e" + if result := hex.EncodeToString(out[:]); result != expected { + t.Errorf("wanted %x but got %x", expected, result) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/client.go b/vendor/golang.org/x/crypto/ssh/agent/client.go new file mode 100644 index 0000000..acb5ad8 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/client.go @@ -0,0 +1,683 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package agent implements the ssh-agent protocol, and provides both +// a client and a server. The client can talk to a standard ssh-agent +// that uses UNIX sockets, and one could implement an alternative +// ssh-agent process using the sample server. +// +// References: +// [PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD +package agent // import "golang.org/x/crypto/ssh/agent" + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + "sync" + + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/ssh" +) + +// Agent represents the capabilities of an ssh-agent. +type Agent interface { + // List returns the identities known to the agent. + List() ([]*Key, error) + + // Sign has the agent sign the data using a protocol 2 key as defined + // in [PROTOCOL.agent] section 2.6.2. + Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) + + // Add adds a private key to the agent. + Add(key AddedKey) error + + // Remove removes all identities with the given public key. + Remove(key ssh.PublicKey) error + + // RemoveAll removes all identities. + RemoveAll() error + + // Lock locks the agent. Sign and Remove will fail, and List will empty an empty list. + Lock(passphrase []byte) error + + // Unlock undoes the effect of Lock + Unlock(passphrase []byte) error + + // Signers returns signers for all the known keys. + Signers() ([]ssh.Signer, error) +} + +// ConstraintExtension describes an optional constraint defined by users. +type ConstraintExtension struct { + // ExtensionName consist of a UTF-8 string suffixed by the + // implementation domain following the naming scheme defined + // in Section 4.2 of [RFC4251], e.g. "foo@example.com". + ExtensionName string + // ExtensionDetails contains the actual content of the extended + // constraint. + ExtensionDetails []byte +} + +// AddedKey describes an SSH key to be added to an Agent. +type AddedKey struct { + // PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or + // *ecdsa.PrivateKey, which will be inserted into the agent. + PrivateKey interface{} + // Certificate, if not nil, is communicated to the agent and will be + // stored with the key. + Certificate *ssh.Certificate + // Comment is an optional, free-form string. + Comment string + // LifetimeSecs, if not zero, is the number of seconds that the + // agent will store the key for. + LifetimeSecs uint32 + // ConfirmBeforeUse, if true, requests that the agent confirm with the + // user before each use of this key. + ConfirmBeforeUse bool + // ConstraintExtensions are the experimental or private-use constraints + // defined by users. + ConstraintExtensions []ConstraintExtension +} + +// See [PROTOCOL.agent], section 3. +const ( + agentRequestV1Identities = 1 + agentRemoveAllV1Identities = 9 + + // 3.2 Requests from client to agent for protocol 2 key operations + agentAddIdentity = 17 + agentRemoveIdentity = 18 + agentRemoveAllIdentities = 19 + agentAddIDConstrained = 25 + + // 3.3 Key-type independent requests from client to agent + agentAddSmartcardKey = 20 + agentRemoveSmartcardKey = 21 + agentLock = 22 + agentUnlock = 23 + agentAddSmartcardKeyConstrained = 26 + + // 3.7 Key constraint identifiers + agentConstrainLifetime = 1 + agentConstrainConfirm = 2 + agentConstrainExtension = 3 +) + +// maxAgentResponseBytes is the maximum agent reply size that is accepted. This +// is a sanity check, not a limit in the spec. +const maxAgentResponseBytes = 16 << 20 + +// Agent messages: +// These structures mirror the wire format of the corresponding ssh agent +// messages found in [PROTOCOL.agent]. + +// 3.4 Generic replies from agent to client +const agentFailure = 5 + +type failureAgentMsg struct{} + +const agentSuccess = 6 + +type successAgentMsg struct{} + +// See [PROTOCOL.agent], section 2.5.2. +const agentRequestIdentities = 11 + +type requestIdentitiesAgentMsg struct{} + +// See [PROTOCOL.agent], section 2.5.2. +const agentIdentitiesAnswer = 12 + +type identitiesAnswerAgentMsg struct { + NumKeys uint32 `sshtype:"12"` + Keys []byte `ssh:"rest"` +} + +// See [PROTOCOL.agent], section 2.6.2. +const agentSignRequest = 13 + +type signRequestAgentMsg struct { + KeyBlob []byte `sshtype:"13"` + Data []byte + Flags uint32 +} + +// See [PROTOCOL.agent], section 2.6.2. + +// 3.6 Replies from agent to client for protocol 2 key operations +const agentSignResponse = 14 + +type signResponseAgentMsg struct { + SigBlob []byte `sshtype:"14"` +} + +type publicKey struct { + Format string + Rest []byte `ssh:"rest"` +} + +// 3.7 Key constraint identifiers +type constrainLifetimeAgentMsg struct { + LifetimeSecs uint32 `sshtype:"1"` +} + +type constrainExtensionAgentMsg struct { + ExtensionName string `sshtype:"3"` + ExtensionDetails []byte + + // Rest is a field used for parsing, not part of message + Rest []byte `ssh:"rest"` +} + +// Key represents a protocol 2 public key as defined in +// [PROTOCOL.agent], section 2.5.2. +type Key struct { + Format string + Blob []byte + Comment string +} + +func clientErr(err error) error { + return fmt.Errorf("agent: client error: %v", err) +} + +// String returns the storage form of an agent key with the format, base64 +// encoded serialized key, and the comment if it is not empty. +func (k *Key) String() string { + s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob) + + if k.Comment != "" { + s += " " + k.Comment + } + + return s +} + +// Type returns the public key type. +func (k *Key) Type() string { + return k.Format +} + +// Marshal returns key blob to satisfy the ssh.PublicKey interface. +func (k *Key) Marshal() []byte { + return k.Blob +} + +// Verify satisfies the ssh.PublicKey interface. +func (k *Key) Verify(data []byte, sig *ssh.Signature) error { + pubKey, err := ssh.ParsePublicKey(k.Blob) + if err != nil { + return fmt.Errorf("agent: bad public key: %v", err) + } + return pubKey.Verify(data, sig) +} + +type wireKey struct { + Format string + Rest []byte `ssh:"rest"` +} + +func parseKey(in []byte) (out *Key, rest []byte, err error) { + var record struct { + Blob []byte + Comment string + Rest []byte `ssh:"rest"` + } + + if err := ssh.Unmarshal(in, &record); err != nil { + return nil, nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(record.Blob, &wk); err != nil { + return nil, nil, err + } + + return &Key{ + Format: wk.Format, + Blob: record.Blob, + Comment: record.Comment, + }, record.Rest, nil +} + +// client is a client for an ssh-agent process. +type client struct { + // conn is typically a *net.UnixConn + conn io.ReadWriter + // mu is used to prevent concurrent access to the agent + mu sync.Mutex +} + +// NewClient returns an Agent that talks to an ssh-agent process over +// the given connection. +func NewClient(rw io.ReadWriter) Agent { + return &client{conn: rw} +} + +// call sends an RPC to the agent. On success, the reply is +// unmarshaled into reply and replyType is set to the first byte of +// the reply, which contains the type of the message. +func (c *client) call(req []byte) (reply interface{}, err error) { + c.mu.Lock() + defer c.mu.Unlock() + + msg := make([]byte, 4+len(req)) + binary.BigEndian.PutUint32(msg, uint32(len(req))) + copy(msg[4:], req) + if _, err = c.conn.Write(msg); err != nil { + return nil, clientErr(err) + } + + var respSizeBuf [4]byte + if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil { + return nil, clientErr(err) + } + respSize := binary.BigEndian.Uint32(respSizeBuf[:]) + if respSize > maxAgentResponseBytes { + return nil, clientErr(err) + } + + buf := make([]byte, respSize) + if _, err = io.ReadFull(c.conn, buf); err != nil { + return nil, clientErr(err) + } + reply, err = unmarshal(buf) + if err != nil { + return nil, clientErr(err) + } + return reply, err +} + +func (c *client) simpleCall(req []byte) error { + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +func (c *client) RemoveAll() error { + return c.simpleCall([]byte{agentRemoveAllIdentities}) +} + +func (c *client) Remove(key ssh.PublicKey) error { + req := ssh.Marshal(&agentRemoveIdentityMsg{ + KeyBlob: key.Marshal(), + }) + return c.simpleCall(req) +} + +func (c *client) Lock(passphrase []byte) error { + req := ssh.Marshal(&agentLockMsg{ + Passphrase: passphrase, + }) + return c.simpleCall(req) +} + +func (c *client) Unlock(passphrase []byte) error { + req := ssh.Marshal(&agentUnlockMsg{ + Passphrase: passphrase, + }) + return c.simpleCall(req) +} + +// List returns the identities known to the agent. +func (c *client) List() ([]*Key, error) { + // see [PROTOCOL.agent] section 2.5.2. + req := []byte{agentRequestIdentities} + + msg, err := c.call(req) + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *identitiesAnswerAgentMsg: + if msg.NumKeys > maxAgentResponseBytes/8 { + return nil, errors.New("agent: too many keys in agent reply") + } + keys := make([]*Key, msg.NumKeys) + data := msg.Keys + for i := uint32(0); i < msg.NumKeys; i++ { + var key *Key + var err error + if key, data, err = parseKey(data); err != nil { + return nil, err + } + keys[i] = key + } + return keys, nil + case *failureAgentMsg: + return nil, errors.New("agent: failed to list keys") + } + panic("unreachable") +} + +// Sign has the agent sign the data using a protocol 2 key as defined +// in [PROTOCOL.agent] section 2.6.2. +func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { + req := ssh.Marshal(signRequestAgentMsg{ + KeyBlob: key.Marshal(), + Data: data, + }) + + msg, err := c.call(req) + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *signResponseAgentMsg: + var sig ssh.Signature + if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil { + return nil, err + } + + return &sig, nil + case *failureAgentMsg: + return nil, errors.New("agent: failed to sign challenge") + } + panic("unreachable") +} + +// unmarshal parses an agent message in packet, returning the parsed +// form and the message type of packet. +func unmarshal(packet []byte) (interface{}, error) { + if len(packet) < 1 { + return nil, errors.New("agent: empty packet") + } + var msg interface{} + switch packet[0] { + case agentFailure: + return new(failureAgentMsg), nil + case agentSuccess: + return new(successAgentMsg), nil + case agentIdentitiesAnswer: + msg = new(identitiesAnswerAgentMsg) + case agentSignResponse: + msg = new(signResponseAgentMsg) + case agentV1IdentitiesAnswer: + msg = new(agentV1IdentityMsg) + default: + return nil, fmt.Errorf("agent: unknown type tag %d", packet[0]) + } + if err := ssh.Unmarshal(packet, msg); err != nil { + return nil, err + } + return msg, nil +} + +type rsaKeyMsg struct { + Type string `sshtype:"17|25"` + N *big.Int + E *big.Int + D *big.Int + Iqmp *big.Int // IQMP = Inverse Q Mod P + P *big.Int + Q *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type dsaKeyMsg struct { + Type string `sshtype:"17|25"` + P *big.Int + Q *big.Int + G *big.Int + Y *big.Int + X *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type ecdsaKeyMsg struct { + Type string `sshtype:"17|25"` + Curve string + KeyBytes []byte + D *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type ed25519KeyMsg struct { + Type string `sshtype:"17|25"` + Pub []byte + Priv []byte + Comments string + Constraints []byte `ssh:"rest"` +} + +// Insert adds a private key to the agent. +func (c *client) insertKey(s interface{}, comment string, constraints []byte) error { + var req []byte + switch k := s.(type) { + case *rsa.PrivateKey: + if len(k.Primes) != 2 { + return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) + } + k.Precompute() + req = ssh.Marshal(rsaKeyMsg{ + Type: ssh.KeyAlgoRSA, + N: k.N, + E: big.NewInt(int64(k.E)), + D: k.D, + Iqmp: k.Precomputed.Qinv, + P: k.Primes[0], + Q: k.Primes[1], + Comments: comment, + Constraints: constraints, + }) + case *dsa.PrivateKey: + req = ssh.Marshal(dsaKeyMsg{ + Type: ssh.KeyAlgoDSA, + P: k.P, + Q: k.Q, + G: k.G, + Y: k.Y, + X: k.X, + Comments: comment, + Constraints: constraints, + }) + case *ecdsa.PrivateKey: + nistID := fmt.Sprintf("nistp%d", k.Params().BitSize) + req = ssh.Marshal(ecdsaKeyMsg{ + Type: "ecdsa-sha2-" + nistID, + Curve: nistID, + KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y), + D: k.D, + Comments: comment, + Constraints: constraints, + }) + case *ed25519.PrivateKey: + req = ssh.Marshal(ed25519KeyMsg{ + Type: ssh.KeyAlgoED25519, + Pub: []byte(*k)[32:], + Priv: []byte(*k), + Comments: comment, + Constraints: constraints, + }) + default: + return fmt.Errorf("agent: unsupported key type %T", s) + } + + // if constraints are present then the message type needs to be changed. + if len(constraints) != 0 { + req[0] = agentAddIDConstrained + } + + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +type rsaCertMsg struct { + Type string `sshtype:"17|25"` + CertBytes []byte + D *big.Int + Iqmp *big.Int // IQMP = Inverse Q Mod P + P *big.Int + Q *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type dsaCertMsg struct { + Type string `sshtype:"17|25"` + CertBytes []byte + X *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type ecdsaCertMsg struct { + Type string `sshtype:"17|25"` + CertBytes []byte + D *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type ed25519CertMsg struct { + Type string `sshtype:"17|25"` + CertBytes []byte + Pub []byte + Priv []byte + Comments string + Constraints []byte `ssh:"rest"` +} + +// Add adds a private key to the agent. If a certificate is given, +// that certificate is added instead as public key. +func (c *client) Add(key AddedKey) error { + var constraints []byte + + if secs := key.LifetimeSecs; secs != 0 { + constraints = append(constraints, ssh.Marshal(constrainLifetimeAgentMsg{secs})...) + } + + if key.ConfirmBeforeUse { + constraints = append(constraints, agentConstrainConfirm) + } + + cert := key.Certificate + if cert == nil { + return c.insertKey(key.PrivateKey, key.Comment, constraints) + } + return c.insertCert(key.PrivateKey, cert, key.Comment, constraints) +} + +func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error { + var req []byte + switch k := s.(type) { + case *rsa.PrivateKey: + if len(k.Primes) != 2 { + return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) + } + k.Precompute() + req = ssh.Marshal(rsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + D: k.D, + Iqmp: k.Precomputed.Qinv, + P: k.Primes[0], + Q: k.Primes[1], + Comments: comment, + Constraints: constraints, + }) + case *dsa.PrivateKey: + req = ssh.Marshal(dsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + X: k.X, + Comments: comment, + Constraints: constraints, + }) + case *ecdsa.PrivateKey: + req = ssh.Marshal(ecdsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + D: k.D, + Comments: comment, + Constraints: constraints, + }) + case *ed25519.PrivateKey: + req = ssh.Marshal(ed25519CertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + Pub: []byte(*k)[32:], + Priv: []byte(*k), + Comments: comment, + Constraints: constraints, + }) + default: + return fmt.Errorf("agent: unsupported key type %T", s) + } + + // if constraints are present then the message type needs to be changed. + if len(constraints) != 0 { + req[0] = agentAddIDConstrained + } + + signer, err := ssh.NewSignerFromKey(s) + if err != nil { + return err + } + if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { + return errors.New("agent: signer and cert have different public key") + } + + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +// Signers provides a callback for client authentication. +func (c *client) Signers() ([]ssh.Signer, error) { + keys, err := c.List() + if err != nil { + return nil, err + } + + var result []ssh.Signer + for _, k := range keys { + result = append(result, &agentKeyringSigner{c, k}) + } + return result, nil +} + +type agentKeyringSigner struct { + agent *client + pub ssh.PublicKey +} + +func (s *agentKeyringSigner) PublicKey() ssh.PublicKey { + return s.pub +} + +func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { + // The agent has its own entropy source, so the rand argument is ignored. + return s.agent.Sign(s.pub, data) +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/client_test.go b/vendor/golang.org/x/crypto/ssh/agent/client_test.go new file mode 100644 index 0000000..266fd6d --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/client_test.go @@ -0,0 +1,379 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "bytes" + "crypto/rand" + "errors" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "testing" + "time" + + "golang.org/x/crypto/ssh" +) + +// startOpenSSHAgent executes ssh-agent, and returns an Agent interface to it. +func startOpenSSHAgent(t *testing.T) (client Agent, socket string, cleanup func()) { + if testing.Short() { + // ssh-agent is not always available, and the key + // types supported vary by platform. + t.Skip("skipping test due to -short") + } + + bin, err := exec.LookPath("ssh-agent") + if err != nil { + t.Skip("could not find ssh-agent") + } + + cmd := exec.Command(bin, "-s") + out, err := cmd.Output() + if err != nil { + t.Fatalf("cmd.Output: %v", err) + } + + /* Output looks like: + + SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK; + SSH_AGENT_PID=15542; export SSH_AGENT_PID; + echo Agent pid 15542; + */ + fields := bytes.Split(out, []byte(";")) + line := bytes.SplitN(fields[0], []byte("="), 2) + line[0] = bytes.TrimLeft(line[0], "\n") + if string(line[0]) != "SSH_AUTH_SOCK" { + t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0]) + } + socket = string(line[1]) + + line = bytes.SplitN(fields[2], []byte("="), 2) + line[0] = bytes.TrimLeft(line[0], "\n") + if string(line[0]) != "SSH_AGENT_PID" { + t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2]) + } + pidStr := line[1] + pid, err := strconv.Atoi(string(pidStr)) + if err != nil { + t.Fatalf("Atoi(%q): %v", pidStr, err) + } + + conn, err := net.Dial("unix", string(socket)) + if err != nil { + t.Fatalf("net.Dial: %v", err) + } + + ac := NewClient(conn) + return ac, socket, func() { + proc, _ := os.FindProcess(pid) + if proc != nil { + proc.Kill() + } + conn.Close() + os.RemoveAll(filepath.Dir(socket)) + } +} + +// startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client. +func startKeyringAgent(t *testing.T) (client Agent, cleanup func()) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + go ServeAgent(NewKeyring(), c2) + + return NewClient(c1), func() { + c1.Close() + c2.Close() + } +} + +func testOpenSSHAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) { + agent, _, cleanup := startOpenSSHAgent(t) + defer cleanup() + + testAgentInterface(t, agent, key, cert, lifetimeSecs) +} + +func testKeyringAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) { + agent, cleanup := startKeyringAgent(t) + defer cleanup() + + testAgentInterface(t, agent, key, cert, lifetimeSecs) +} + +func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) { + signer, err := ssh.NewSignerFromKey(key) + if err != nil { + t.Fatalf("NewSignerFromKey(%T): %v", key, err) + } + // The agent should start up empty. + if keys, err := agent.List(); err != nil { + t.Fatalf("RequestIdentities: %v", err) + } else if len(keys) > 0 { + t.Fatalf("got %d keys, want 0: %v", len(keys), keys) + } + + // Attempt to insert the key, with certificate if specified. + var pubKey ssh.PublicKey + if cert != nil { + err = agent.Add(AddedKey{ + PrivateKey: key, + Certificate: cert, + Comment: "comment", + LifetimeSecs: lifetimeSecs, + }) + pubKey = cert + } else { + err = agent.Add(AddedKey{PrivateKey: key, Comment: "comment", LifetimeSecs: lifetimeSecs}) + pubKey = signer.PublicKey() + } + if err != nil { + t.Fatalf("insert(%T): %v", key, err) + } + + // Did the key get inserted successfully? + if keys, err := agent.List(); err != nil { + t.Fatalf("List: %v", err) + } else if len(keys) != 1 { + t.Fatalf("got %v, want 1 key", keys) + } else if keys[0].Comment != "comment" { + t.Fatalf("key comment: got %v, want %v", keys[0].Comment, "comment") + } else if !bytes.Equal(keys[0].Blob, pubKey.Marshal()) { + t.Fatalf("key mismatch") + } + + // Can the agent make a valid signature? + data := []byte("hello") + sig, err := agent.Sign(pubKey, data) + if err != nil { + t.Fatalf("Sign(%s): %v", pubKey.Type(), err) + } + + if err := pubKey.Verify(data, sig); err != nil { + t.Fatalf("Verify(%s): %v", pubKey.Type(), err) + } + + // If the key has a lifetime, is it removed when it should be? + if lifetimeSecs > 0 { + time.Sleep(time.Second*time.Duration(lifetimeSecs) + 100*time.Millisecond) + keys, err := agent.List() + if err != nil { + t.Fatalf("List: %v", err) + } + if len(keys) > 0 { + t.Fatalf("key not expired") + } + } + +} + +func TestAgent(t *testing.T) { + for _, keyType := range []string{"rsa", "dsa", "ecdsa", "ed25519"} { + testOpenSSHAgent(t, testPrivateKeys[keyType], nil, 0) + testKeyringAgent(t, testPrivateKeys[keyType], nil, 0) + } +} + +func TestCert(t *testing.T) { + cert := &ssh.Certificate{ + Key: testPublicKeys["rsa"], + ValidBefore: ssh.CertTimeInfinity, + CertType: ssh.UserCert, + } + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + + testOpenSSHAgent(t, testPrivateKeys["rsa"], cert, 0) + testKeyringAgent(t, testPrivateKeys["rsa"], cert, 0) +} + +// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and +// therefore is buffered (net.Pipe deadlocks if both sides start with +// a write.) +func netPipe() (net.Conn, net.Conn, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + listener, err = net.Listen("tcp", "[::1]:0") + if err != nil { + return nil, nil, err + } + } + defer listener.Close() + c1, err := net.Dial("tcp", listener.Addr().String()) + if err != nil { + return nil, nil, err + } + + c2, err := listener.Accept() + if err != nil { + c1.Close() + return nil, nil, err + } + + return c1, c2, nil +} + +func TestAuth(t *testing.T) { + agent, _, cleanup := startOpenSSHAgent(t) + defer cleanup() + + a, b, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + + defer a.Close() + defer b.Close() + + if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil { + t.Errorf("Add: %v", err) + } + + serverConf := ssh.ServerConfig{} + serverConf.AddHostKey(testSigners["rsa"]) + serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) { + return nil, nil + } + + return nil, errors.New("pubkey rejected") + } + + go func() { + conn, _, _, err := ssh.NewServerConn(a, &serverConf) + if err != nil { + t.Fatalf("Server: %v", err) + } + conn.Close() + }() + + conf := ssh.ClientConfig{ + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers)) + conn, _, _, err := ssh.NewClientConn(b, "", &conf) + if err != nil { + t.Fatalf("NewClientConn: %v", err) + } + conn.Close() +} + +func TestLockOpenSSHAgent(t *testing.T) { + agent, _, cleanup := startOpenSSHAgent(t) + defer cleanup() + testLockAgent(agent, t) +} + +func TestLockKeyringAgent(t *testing.T) { + agent, cleanup := startKeyringAgent(t) + defer cleanup() + testLockAgent(agent, t) +} + +func testLockAgent(agent Agent, t *testing.T) { + if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment 1"}); err != nil { + t.Errorf("Add: %v", err) + } + if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["dsa"], Comment: "comment dsa"}); err != nil { + t.Errorf("Add: %v", err) + } + if keys, err := agent.List(); err != nil { + t.Errorf("List: %v", err) + } else if len(keys) != 2 { + t.Errorf("Want 2 keys, got %v", keys) + } + + passphrase := []byte("secret") + if err := agent.Lock(passphrase); err != nil { + t.Errorf("Lock: %v", err) + } + + if keys, err := agent.List(); err != nil { + t.Errorf("List: %v", err) + } else if len(keys) != 0 { + t.Errorf("Want 0 keys, got %v", keys) + } + + signer, _ := ssh.NewSignerFromKey(testPrivateKeys["rsa"]) + if _, err := agent.Sign(signer.PublicKey(), []byte("hello")); err == nil { + t.Fatalf("Sign did not fail") + } + + if err := agent.Remove(signer.PublicKey()); err == nil { + t.Fatalf("Remove did not fail") + } + + if err := agent.RemoveAll(); err == nil { + t.Fatalf("RemoveAll did not fail") + } + + if err := agent.Unlock(nil); err == nil { + t.Errorf("Unlock with wrong passphrase succeeded") + } + if err := agent.Unlock(passphrase); err != nil { + t.Errorf("Unlock: %v", err) + } + + if err := agent.Remove(signer.PublicKey()); err != nil { + t.Fatalf("Remove: %v", err) + } + + if keys, err := agent.List(); err != nil { + t.Errorf("List: %v", err) + } else if len(keys) != 1 { + t.Errorf("Want 1 keys, got %v", keys) + } +} + +func testOpenSSHAgentLifetime(t *testing.T) { + agent, _, cleanup := startOpenSSHAgent(t) + defer cleanup() + testAgentLifetime(t, agent) +} + +func testKeyringAgentLifetime(t *testing.T) { + agent, cleanup := startKeyringAgent(t) + defer cleanup() + testAgentLifetime(t, agent) +} + +func testAgentLifetime(t *testing.T, agent Agent) { + for _, keyType := range []string{"rsa", "dsa", "ecdsa"} { + // Add private keys to the agent. + err := agent.Add(AddedKey{ + PrivateKey: testPrivateKeys[keyType], + Comment: "comment", + LifetimeSecs: 1, + }) + if err != nil { + t.Fatalf("add: %v", err) + } + // Add certs to the agent. + cert := &ssh.Certificate{ + Key: testPublicKeys[keyType], + ValidBefore: ssh.CertTimeInfinity, + CertType: ssh.UserCert, + } + cert.SignCert(rand.Reader, testSigners[keyType]) + err = agent.Add(AddedKey{ + PrivateKey: testPrivateKeys[keyType], + Certificate: cert, + Comment: "comment", + LifetimeSecs: 1, + }) + if err != nil { + t.Fatalf("add: %v", err) + } + } + time.Sleep(1100 * time.Millisecond) + if keys, err := agent.List(); err != nil { + t.Errorf("List: %v", err) + } else if len(keys) != 0 { + t.Errorf("Want 0 keys, got %v", len(keys)) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/example_test.go b/vendor/golang.org/x/crypto/ssh/agent/example_test.go new file mode 100644 index 0000000..8556225 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/example_test.go @@ -0,0 +1,41 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent_test + +import ( + "log" + "net" + "os" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" +) + +func ExampleClientAgent() { + // ssh-agent has a UNIX socket under $SSH_AUTH_SOCK + socket := os.Getenv("SSH_AUTH_SOCK") + conn, err := net.Dial("unix", socket) + if err != nil { + log.Fatalf("net.Dial: %v", err) + } + agentClient := agent.NewClient(conn) + config := &ssh.ClientConfig{ + User: "username", + Auth: []ssh.AuthMethod{ + // Use a callback rather than PublicKeys + // so we only consult the agent once the remote server + // wants it. + ssh.PublicKeysCallback(agentClient.Signers), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + sshc, err := ssh.Dial("tcp", "localhost:22", config) + if err != nil { + log.Fatalf("Dial: %v", err) + } + // .. use sshc + sshc.Close() +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/forward.go b/vendor/golang.org/x/crypto/ssh/agent/forward.go new file mode 100644 index 0000000..fd24ba9 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/forward.go @@ -0,0 +1,103 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "errors" + "io" + "net" + "sync" + + "golang.org/x/crypto/ssh" +) + +// RequestAgentForwarding sets up agent forwarding for the session. +// ForwardToAgent or ForwardToRemote should be called to route +// the authentication requests. +func RequestAgentForwarding(session *ssh.Session) error { + ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil) + if err != nil { + return err + } + if !ok { + return errors.New("forwarding request denied") + } + return nil +} + +// ForwardToAgent routes authentication requests to the given keyring. +func ForwardToAgent(client *ssh.Client, keyring Agent) error { + channels := client.HandleChannelOpen(channelType) + if channels == nil { + return errors.New("agent: already have handler for " + channelType) + } + + go func() { + for ch := range channels { + channel, reqs, err := ch.Accept() + if err != nil { + continue + } + go ssh.DiscardRequests(reqs) + go func() { + ServeAgent(keyring, channel) + channel.Close() + }() + } + }() + return nil +} + +const channelType = "auth-agent@openssh.com" + +// ForwardToRemote routes authentication requests to the ssh-agent +// process serving on the given unix socket. +func ForwardToRemote(client *ssh.Client, addr string) error { + channels := client.HandleChannelOpen(channelType) + if channels == nil { + return errors.New("agent: already have handler for " + channelType) + } + conn, err := net.Dial("unix", addr) + if err != nil { + return err + } + conn.Close() + + go func() { + for ch := range channels { + channel, reqs, err := ch.Accept() + if err != nil { + continue + } + go ssh.DiscardRequests(reqs) + go forwardUnixSocket(channel, addr) + } + }() + return nil +} + +func forwardUnixSocket(channel ssh.Channel, addr string) { + conn, err := net.Dial("unix", addr) + if err != nil { + return + } + + var wg sync.WaitGroup + wg.Add(2) + go func() { + io.Copy(conn, channel) + conn.(*net.UnixConn).CloseWrite() + wg.Done() + }() + go func() { + io.Copy(channel, conn) + channel.CloseWrite() + wg.Done() + }() + + wg.Wait() + conn.Close() + channel.Close() +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/keyring.go b/vendor/golang.org/x/crypto/ssh/agent/keyring.go new file mode 100644 index 0000000..a6ba06a --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/keyring.go @@ -0,0 +1,215 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "bytes" + "crypto/rand" + "crypto/subtle" + "errors" + "fmt" + "sync" + "time" + + "golang.org/x/crypto/ssh" +) + +type privKey struct { + signer ssh.Signer + comment string + expire *time.Time +} + +type keyring struct { + mu sync.Mutex + keys []privKey + + locked bool + passphrase []byte +} + +var errLocked = errors.New("agent: locked") + +// NewKeyring returns an Agent that holds keys in memory. It is safe +// for concurrent use by multiple goroutines. +func NewKeyring() Agent { + return &keyring{} +} + +// RemoveAll removes all identities. +func (r *keyring) RemoveAll() error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + r.keys = nil + return nil +} + +// removeLocked does the actual key removal. The caller must already be holding the +// keyring mutex. +func (r *keyring) removeLocked(want []byte) error { + found := false + for i := 0; i < len(r.keys); { + if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) { + found = true + r.keys[i] = r.keys[len(r.keys)-1] + r.keys = r.keys[:len(r.keys)-1] + continue + } else { + i++ + } + } + + if !found { + return errors.New("agent: key not found") + } + return nil +} + +// Remove removes all identities with the given public key. +func (r *keyring) Remove(key ssh.PublicKey) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + return r.removeLocked(key.Marshal()) +} + +// Lock locks the agent. Sign and Remove will fail, and List will return an empty list. +func (r *keyring) Lock(passphrase []byte) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + r.locked = true + r.passphrase = passphrase + return nil +} + +// Unlock undoes the effect of Lock +func (r *keyring) Unlock(passphrase []byte) error { + r.mu.Lock() + defer r.mu.Unlock() + if !r.locked { + return errors.New("agent: not locked") + } + if len(passphrase) != len(r.passphrase) || 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) { + return fmt.Errorf("agent: incorrect passphrase") + } + + r.locked = false + r.passphrase = nil + return nil +} + +// expireKeysLocked removes expired keys from the keyring. If a key was added +// with a lifetimesecs contraint and seconds >= lifetimesecs seconds have +// ellapsed, it is removed. The caller *must* be holding the keyring mutex. +func (r *keyring) expireKeysLocked() { + for _, k := range r.keys { + if k.expire != nil && time.Now().After(*k.expire) { + r.removeLocked(k.signer.PublicKey().Marshal()) + } + } +} + +// List returns the identities known to the agent. +func (r *keyring) List() ([]*Key, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + // section 2.7: locked agents return empty. + return nil, nil + } + + r.expireKeysLocked() + var ids []*Key + for _, k := range r.keys { + pub := k.signer.PublicKey() + ids = append(ids, &Key{ + Format: pub.Type(), + Blob: pub.Marshal(), + Comment: k.comment}) + } + return ids, nil +} + +// Insert adds a private key to the keyring. If a certificate +// is given, that certificate is added as public key. Note that +// any constraints given are ignored. +func (r *keyring) Add(key AddedKey) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + signer, err := ssh.NewSignerFromKey(key.PrivateKey) + + if err != nil { + return err + } + + if cert := key.Certificate; cert != nil { + signer, err = ssh.NewCertSigner(cert, signer) + if err != nil { + return err + } + } + + p := privKey{ + signer: signer, + comment: key.Comment, + } + + if key.LifetimeSecs > 0 { + t := time.Now().Add(time.Duration(key.LifetimeSecs) * time.Second) + p.expire = &t + } + + r.keys = append(r.keys, p) + + return nil +} + +// Sign returns a signature for the data. +func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return nil, errLocked + } + + r.expireKeysLocked() + wanted := key.Marshal() + for _, k := range r.keys { + if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) { + return k.signer.Sign(rand.Reader, data) + } + } + return nil, errors.New("not found") +} + +// Signers returns signers for all the known keys. +func (r *keyring) Signers() ([]ssh.Signer, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return nil, errLocked + } + + r.expireKeysLocked() + s := make([]ssh.Signer, 0, len(r.keys)) + for _, k := range r.keys { + s = append(s, k.signer) + } + return s, nil +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/keyring_test.go b/vendor/golang.org/x/crypto/ssh/agent/keyring_test.go new file mode 100644 index 0000000..e5d50e7 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/keyring_test.go @@ -0,0 +1,76 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import "testing" + +func addTestKey(t *testing.T, a Agent, keyName string) { + err := a.Add(AddedKey{ + PrivateKey: testPrivateKeys[keyName], + Comment: keyName, + }) + if err != nil { + t.Fatalf("failed to add key %q: %v", keyName, err) + } +} + +func removeTestKey(t *testing.T, a Agent, keyName string) { + err := a.Remove(testPublicKeys[keyName]) + if err != nil { + t.Fatalf("failed to remove key %q: %v", keyName, err) + } +} + +func validateListedKeys(t *testing.T, a Agent, expectedKeys []string) { + listedKeys, err := a.List() + if err != nil { + t.Fatalf("failed to list keys: %v", err) + return + } + actualKeys := make(map[string]bool) + for _, key := range listedKeys { + actualKeys[key.Comment] = true + } + + matchedKeys := make(map[string]bool) + for _, expectedKey := range expectedKeys { + if !actualKeys[expectedKey] { + t.Fatalf("expected key %q, but was not found", expectedKey) + } else { + matchedKeys[expectedKey] = true + } + } + + for actualKey := range actualKeys { + if !matchedKeys[actualKey] { + t.Fatalf("key %q was found, but was not expected", actualKey) + } + } +} + +func TestKeyringAddingAndRemoving(t *testing.T) { + keyNames := []string{"dsa", "ecdsa", "rsa", "user"} + + // add all test private keys + k := NewKeyring() + for _, keyName := range keyNames { + addTestKey(t, k, keyName) + } + validateListedKeys(t, k, keyNames) + + // remove a key in the middle + keyToRemove := keyNames[1] + keyNames = append(keyNames[:1], keyNames[2:]...) + + removeTestKey(t, k, keyToRemove) + validateListedKeys(t, k, keyNames) + + // remove all keys + err := k.RemoveAll() + if err != nil { + t.Fatalf("failed to remove all keys: %v", err) + } + validateListedKeys(t, k, []string{}) +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/server.go b/vendor/golang.org/x/crypto/ssh/agent/server.go new file mode 100644 index 0000000..2e4692c --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/server.go @@ -0,0 +1,523 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "encoding/binary" + "errors" + "fmt" + "io" + "log" + "math/big" + + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/ssh" +) + +// Server wraps an Agent and uses it to implement the agent side of +// the SSH-agent, wire protocol. +type server struct { + agent Agent +} + +func (s *server) processRequestBytes(reqData []byte) []byte { + rep, err := s.processRequest(reqData) + if err != nil { + if err != errLocked { + // TODO(hanwen): provide better logging interface? + log.Printf("agent %d: %v", reqData[0], err) + } + return []byte{agentFailure} + } + + if err == nil && rep == nil { + return []byte{agentSuccess} + } + + return ssh.Marshal(rep) +} + +func marshalKey(k *Key) []byte { + var record struct { + Blob []byte + Comment string + } + record.Blob = k.Marshal() + record.Comment = k.Comment + + return ssh.Marshal(&record) +} + +// See [PROTOCOL.agent], section 2.5.1. +const agentV1IdentitiesAnswer = 2 + +type agentV1IdentityMsg struct { + Numkeys uint32 `sshtype:"2"` +} + +type agentRemoveIdentityMsg struct { + KeyBlob []byte `sshtype:"18"` +} + +type agentLockMsg struct { + Passphrase []byte `sshtype:"22"` +} + +type agentUnlockMsg struct { + Passphrase []byte `sshtype:"23"` +} + +func (s *server) processRequest(data []byte) (interface{}, error) { + switch data[0] { + case agentRequestV1Identities: + return &agentV1IdentityMsg{0}, nil + + case agentRemoveAllV1Identities: + return nil, nil + + case agentRemoveIdentity: + var req agentRemoveIdentityMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil { + return nil, err + } + + return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob}) + + case agentRemoveAllIdentities: + return nil, s.agent.RemoveAll() + + case agentLock: + var req agentLockMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + return nil, s.agent.Lock(req.Passphrase) + + case agentUnlock: + var req agentUnlockMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + return nil, s.agent.Unlock(req.Passphrase) + + case agentSignRequest: + var req signRequestAgentMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil { + return nil, err + } + + k := &Key{ + Format: wk.Format, + Blob: req.KeyBlob, + } + + sig, err := s.agent.Sign(k, req.Data) // TODO(hanwen): flags. + if err != nil { + return nil, err + } + return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil + + case agentRequestIdentities: + keys, err := s.agent.List() + if err != nil { + return nil, err + } + + rep := identitiesAnswerAgentMsg{ + NumKeys: uint32(len(keys)), + } + for _, k := range keys { + rep.Keys = append(rep.Keys, marshalKey(k)...) + } + return rep, nil + + case agentAddIDConstrained, agentAddIdentity: + return nil, s.insertIdentity(data) + } + + return nil, fmt.Errorf("unknown opcode %d", data[0]) +} + +func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse bool, extensions []ConstraintExtension, err error) { + for len(constraints) != 0 { + switch constraints[0] { + case agentConstrainLifetime: + lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5]) + constraints = constraints[5:] + case agentConstrainConfirm: + confirmBeforeUse = true + constraints = constraints[1:] + case agentConstrainExtension: + var msg constrainExtensionAgentMsg + if err = ssh.Unmarshal(constraints, &msg); err != nil { + return 0, false, nil, err + } + extensions = append(extensions, ConstraintExtension{ + ExtensionName: msg.ExtensionName, + ExtensionDetails: msg.ExtensionDetails, + }) + constraints = msg.Rest + default: + return 0, false, nil, fmt.Errorf("unknown constraint type: %d", constraints[0]) + } + } + return +} + +func setConstraints(key *AddedKey, constraintBytes []byte) error { + lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(constraintBytes) + if err != nil { + return err + } + + key.LifetimeSecs = lifetimeSecs + key.ConfirmBeforeUse = confirmBeforeUse + key.ConstraintExtensions = constraintExtensions + return nil +} + +func parseRSAKey(req []byte) (*AddedKey, error) { + var k rsaKeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + if k.E.BitLen() > 30 { + return nil, errors.New("agent: RSA public exponent too large") + } + priv := &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + E: int(k.E.Int64()), + N: k.N, + }, + D: k.D, + Primes: []*big.Int{k.P, k.Q}, + } + priv.Precompute() + + addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseEd25519Key(req []byte) (*AddedKey, error) { + var k ed25519KeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + priv := ed25519.PrivateKey(k.Priv) + + addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseDSAKey(req []byte) (*AddedKey, error) { + var k dsaKeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + priv := &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: k.P, + Q: k.Q, + G: k.G, + }, + Y: k.Y, + }, + X: k.X, + } + + addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) { + priv = &ecdsa.PrivateKey{ + D: privScalar, + } + + switch curveName { + case "nistp256": + priv.Curve = elliptic.P256() + case "nistp384": + priv.Curve = elliptic.P384() + case "nistp521": + priv.Curve = elliptic.P521() + default: + return nil, fmt.Errorf("agent: unknown curve %q", curveName) + } + + priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes) + if priv.X == nil || priv.Y == nil { + return nil, errors.New("agent: point not on curve") + } + + return priv, nil +} + +func parseEd25519Cert(req []byte) (*AddedKey, error) { + var k ed25519CertMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + pubKey, err := ssh.ParsePublicKey(k.CertBytes) + if err != nil { + return nil, err + } + priv := ed25519.PrivateKey(k.Priv) + cert, ok := pubKey.(*ssh.Certificate) + if !ok { + return nil, errors.New("agent: bad ED25519 certificate") + } + + addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseECDSAKey(req []byte) (*AddedKey, error) { + var k ecdsaKeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + + priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D) + if err != nil { + return nil, err + } + + addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseRSACert(req []byte) (*AddedKey, error) { + var k rsaCertMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + + pubKey, err := ssh.ParsePublicKey(k.CertBytes) + if err != nil { + return nil, err + } + + cert, ok := pubKey.(*ssh.Certificate) + if !ok { + return nil, errors.New("agent: bad RSA certificate") + } + + // An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go + var rsaPub struct { + Name string + E *big.Int + N *big.Int + } + if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil { + return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err) + } + + if rsaPub.E.BitLen() > 30 { + return nil, errors.New("agent: RSA public exponent too large") + } + + priv := rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + E: int(rsaPub.E.Int64()), + N: rsaPub.N, + }, + D: k.D, + Primes: []*big.Int{k.Q, k.P}, + } + priv.Precompute() + + addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseDSACert(req []byte) (*AddedKey, error) { + var k dsaCertMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + pubKey, err := ssh.ParsePublicKey(k.CertBytes) + if err != nil { + return nil, err + } + cert, ok := pubKey.(*ssh.Certificate) + if !ok { + return nil, errors.New("agent: bad DSA certificate") + } + + // A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go + var w struct { + Name string + P, Q, G, Y *big.Int + } + if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil { + return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err) + } + + priv := &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: w.P, + Q: w.Q, + G: w.G, + }, + Y: w.Y, + }, + X: k.X, + } + + addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseECDSACert(req []byte) (*AddedKey, error) { + var k ecdsaCertMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + + pubKey, err := ssh.ParsePublicKey(k.CertBytes) + if err != nil { + return nil, err + } + cert, ok := pubKey.(*ssh.Certificate) + if !ok { + return nil, errors.New("agent: bad ECDSA certificate") + } + + // An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go + var ecdsaPub struct { + Name string + ID string + Key []byte + } + if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil { + return nil, err + } + + priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D) + if err != nil { + return nil, err + } + + addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func (s *server) insertIdentity(req []byte) error { + var record struct { + Type string `sshtype:"17|25"` + Rest []byte `ssh:"rest"` + } + + if err := ssh.Unmarshal(req, &record); err != nil { + return err + } + + var addedKey *AddedKey + var err error + + switch record.Type { + case ssh.KeyAlgoRSA: + addedKey, err = parseRSAKey(req) + case ssh.KeyAlgoDSA: + addedKey, err = parseDSAKey(req) + case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521: + addedKey, err = parseECDSAKey(req) + case ssh.KeyAlgoED25519: + addedKey, err = parseEd25519Key(req) + case ssh.CertAlgoRSAv01: + addedKey, err = parseRSACert(req) + case ssh.CertAlgoDSAv01: + addedKey, err = parseDSACert(req) + case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01: + addedKey, err = parseECDSACert(req) + case ssh.CertAlgoED25519v01: + addedKey, err = parseEd25519Cert(req) + default: + return fmt.Errorf("agent: not implemented: %q", record.Type) + } + + if err != nil { + return err + } + return s.agent.Add(*addedKey) +} + +// ServeAgent serves the agent protocol on the given connection. It +// returns when an I/O error occurs. +func ServeAgent(agent Agent, c io.ReadWriter) error { + s := &server{agent} + + var length [4]byte + for { + if _, err := io.ReadFull(c, length[:]); err != nil { + return err + } + l := binary.BigEndian.Uint32(length[:]) + if l > maxAgentResponseBytes { + // We also cap requests. + return fmt.Errorf("agent: request too large: %d", l) + } + + req := make([]byte, l) + if _, err := io.ReadFull(c, req); err != nil { + return err + } + + repData := s.processRequestBytes(req) + if len(repData) > maxAgentResponseBytes { + return fmt.Errorf("agent: reply too large: %d bytes", len(repData)) + } + + binary.BigEndian.PutUint32(length[:], uint32(len(repData))) + if _, err := c.Write(length[:]); err != nil { + return err + } + if _, err := c.Write(repData); err != nil { + return err + } + } +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/server_test.go b/vendor/golang.org/x/crypto/ssh/agent/server_test.go new file mode 100644 index 0000000..038018e --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/server_test.go @@ -0,0 +1,259 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "crypto" + "crypto/rand" + "fmt" + pseudorand "math/rand" + "reflect" + "strings" + "testing" + + "golang.org/x/crypto/ssh" +) + +func TestServer(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + client := NewClient(c1) + + go ServeAgent(NewKeyring(), c2) + + testAgentInterface(t, client, testPrivateKeys["rsa"], nil, 0) +} + +func TestLockServer(t *testing.T) { + testLockAgent(NewKeyring(), t) +} + +func TestSetupForwardAgent(t *testing.T) { + a, b, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + + defer a.Close() + defer b.Close() + + _, socket, cleanup := startOpenSSHAgent(t) + defer cleanup() + + serverConf := ssh.ServerConfig{ + NoClientAuth: true, + } + serverConf.AddHostKey(testSigners["rsa"]) + incoming := make(chan *ssh.ServerConn, 1) + go func() { + conn, _, _, err := ssh.NewServerConn(a, &serverConf) + if err != nil { + t.Fatalf("Server: %v", err) + } + incoming <- conn + }() + + conf := ssh.ClientConfig{ + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf) + if err != nil { + t.Fatalf("NewClientConn: %v", err) + } + client := ssh.NewClient(conn, chans, reqs) + + if err := ForwardToRemote(client, socket); err != nil { + t.Fatalf("SetupForwardAgent: %v", err) + } + + server := <-incoming + ch, reqs, err := server.OpenChannel(channelType, nil) + if err != nil { + t.Fatalf("OpenChannel(%q): %v", channelType, err) + } + go ssh.DiscardRequests(reqs) + + agentClient := NewClient(ch) + testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil, 0) + conn.Close() +} + +func TestV1ProtocolMessages(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + c := NewClient(c1) + + go ServeAgent(NewKeyring(), c2) + + testV1ProtocolMessages(t, c.(*client)) +} + +func testV1ProtocolMessages(t *testing.T, c *client) { + reply, err := c.call([]byte{agentRequestV1Identities}) + if err != nil { + t.Fatalf("v1 request all failed: %v", err) + } + if msg, ok := reply.(*agentV1IdentityMsg); !ok || msg.Numkeys != 0 { + t.Fatalf("invalid request all response: %#v", reply) + } + + reply, err = c.call([]byte{agentRemoveAllV1Identities}) + if err != nil { + t.Fatalf("v1 remove all failed: %v", err) + } + if _, ok := reply.(*successAgentMsg); !ok { + t.Fatalf("invalid remove all response: %#v", reply) + } +} + +func verifyKey(sshAgent Agent) error { + keys, err := sshAgent.List() + if err != nil { + return fmt.Errorf("listing keys: %v", err) + } + + if len(keys) != 1 { + return fmt.Errorf("bad number of keys found. expected 1, got %d", len(keys)) + } + + buf := make([]byte, 128) + if _, err := rand.Read(buf); err != nil { + return fmt.Errorf("rand: %v", err) + } + + sig, err := sshAgent.Sign(keys[0], buf) + if err != nil { + return fmt.Errorf("sign: %v", err) + } + + if err := keys[0].Verify(buf, sig); err != nil { + return fmt.Errorf("verify: %v", err) + } + return nil +} + +func addKeyToAgent(key crypto.PrivateKey) error { + sshAgent := NewKeyring() + if err := sshAgent.Add(AddedKey{PrivateKey: key}); err != nil { + return fmt.Errorf("add: %v", err) + } + return verifyKey(sshAgent) +} + +func TestKeyTypes(t *testing.T) { + for k, v := range testPrivateKeys { + if err := addKeyToAgent(v); err != nil { + t.Errorf("error adding key type %s, %v", k, err) + } + if err := addCertToAgentSock(v, nil); err != nil { + t.Errorf("error adding key type %s, %v", k, err) + } + } +} + +func addCertToAgentSock(key crypto.PrivateKey, cert *ssh.Certificate) error { + a, b, err := netPipe() + if err != nil { + return err + } + agentServer := NewKeyring() + go ServeAgent(agentServer, a) + + agentClient := NewClient(b) + if err := agentClient.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil { + return fmt.Errorf("add: %v", err) + } + return verifyKey(agentClient) +} + +func addCertToAgent(key crypto.PrivateKey, cert *ssh.Certificate) error { + sshAgent := NewKeyring() + if err := sshAgent.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil { + return fmt.Errorf("add: %v", err) + } + return verifyKey(sshAgent) +} + +func TestCertTypes(t *testing.T) { + for keyType, key := range testPublicKeys { + cert := &ssh.Certificate{ + ValidPrincipals: []string{"gopher1"}, + ValidAfter: 0, + ValidBefore: ssh.CertTimeInfinity, + Key: key, + Serial: 1, + CertType: ssh.UserCert, + SignatureKey: testPublicKeys["rsa"], + Permissions: ssh.Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + }, + } + if err := cert.SignCert(rand.Reader, testSigners["rsa"]); err != nil { + t.Fatalf("signcert: %v", err) + } + if err := addCertToAgent(testPrivateKeys[keyType], cert); err != nil { + t.Fatalf("%v", err) + } + if err := addCertToAgentSock(testPrivateKeys[keyType], cert); err != nil { + t.Fatalf("%v", err) + } + } +} + +func TestParseConstraints(t *testing.T) { + // Test LifetimeSecs + var msg = constrainLifetimeAgentMsg{pseudorand.Uint32()} + lifetimeSecs, _, _, err := parseConstraints(ssh.Marshal(msg)) + if err != nil { + t.Fatalf("parseConstraints: %v", err) + } + if lifetimeSecs != msg.LifetimeSecs { + t.Errorf("got lifetime %v, want %v", lifetimeSecs, msg.LifetimeSecs) + } + + // Test ConfirmBeforeUse + _, confirmBeforeUse, _, err := parseConstraints([]byte{agentConstrainConfirm}) + if err != nil { + t.Fatalf("%v", err) + } + if !confirmBeforeUse { + t.Error("got comfirmBeforeUse == false") + } + + // Test ConstraintExtensions + var data []byte + var expect []ConstraintExtension + for i := 0; i < 10; i++ { + var ext = ConstraintExtension{ + ExtensionName: fmt.Sprintf("name%d", i), + ExtensionDetails: []byte(fmt.Sprintf("details: %d", i)), + } + expect = append(expect, ext) + data = append(data, agentConstrainExtension) + data = append(data, ssh.Marshal(ext)...) + } + _, _, extensions, err := parseConstraints(data) + if err != nil { + t.Fatalf("%v", err) + } + if !reflect.DeepEqual(expect, extensions) { + t.Errorf("got extension %v, want %v", extensions, expect) + } + + // Test Unknown Constraint + _, _, _, err = parseConstraints([]byte{128}) + if err == nil || !strings.Contains(err.Error(), "unknown constraint") { + t.Errorf("unexpected error: %v", err) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/agent/testdata_test.go b/vendor/golang.org/x/crypto/ssh/agent/testdata_test.go new file mode 100644 index 0000000..cc42a87 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/agent/testdata_test.go @@ -0,0 +1,64 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places: +// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three +// instances. + +package agent + +import ( + "crypto/rand" + "fmt" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/testdata" +) + +var ( + testPrivateKeys map[string]interface{} + testSigners map[string]ssh.Signer + testPublicKeys map[string]ssh.PublicKey +) + +func init() { + var err error + + n := len(testdata.PEMBytes) + testPrivateKeys = make(map[string]interface{}, n) + testSigners = make(map[string]ssh.Signer, n) + testPublicKeys = make(map[string]ssh.PublicKey, n) + for t, k := range testdata.PEMBytes { + testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k) + if err != nil { + panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err)) + } + testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t]) + if err != nil { + panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err)) + } + testPublicKeys[t] = testSigners[t].PublicKey() + } + + // Create a cert and sign it for use in tests. + testCert := &ssh.Certificate{ + Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage + ValidAfter: 0, // unix epoch + ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time. + Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + Key: testPublicKeys["ecdsa"], + SignatureKey: testPublicKeys["rsa"], + Permissions: ssh.Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + }, + } + testCert.SignCert(rand.Reader, testSigners["rsa"]) + testPrivateKeys["cert"] = testPrivateKeys["ecdsa"] + testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"]) + if err != nil { + panic(fmt.Sprintf("Unable to create certificate signer: %v", err)) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/benchmark_test.go b/vendor/golang.org/x/crypto/ssh/benchmark_test.go new file mode 100644 index 0000000..20c3307 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/benchmark_test.go @@ -0,0 +1,123 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "errors" + "io" + "net" + "testing" +) + +type server struct { + *ServerConn + chans <-chan NewChannel +} + +func newServer(c net.Conn, conf *ServerConfig) (*server, error) { + sconn, chans, reqs, err := NewServerConn(c, conf) + if err != nil { + return nil, err + } + go DiscardRequests(reqs) + return &server{sconn, chans}, nil +} + +func (s *server) Accept() (NewChannel, error) { + n, ok := <-s.chans + if !ok { + return nil, io.EOF + } + return n, nil +} + +func sshPipe() (Conn, *server, error) { + c1, c2, err := netPipe() + if err != nil { + return nil, nil, err + } + + clientConf := ClientConfig{ + User: "user", + HostKeyCallback: InsecureIgnoreHostKey(), + } + serverConf := ServerConfig{ + NoClientAuth: true, + } + serverConf.AddHostKey(testSigners["ecdsa"]) + done := make(chan *server, 1) + go func() { + server, err := newServer(c2, &serverConf) + if err != nil { + done <- nil + } + done <- server + }() + + client, _, reqs, err := NewClientConn(c1, "", &clientConf) + if err != nil { + return nil, nil, err + } + + server := <-done + if server == nil { + return nil, nil, errors.New("server handshake failed.") + } + go DiscardRequests(reqs) + + return client, server, nil +} + +func BenchmarkEndToEnd(b *testing.B) { + b.StopTimer() + + client, server, err := sshPipe() + if err != nil { + b.Fatalf("sshPipe: %v", err) + } + + defer client.Close() + defer server.Close() + + size := (1 << 20) + input := make([]byte, size) + output := make([]byte, size) + b.SetBytes(int64(size)) + done := make(chan int, 1) + + go func() { + newCh, err := server.Accept() + if err != nil { + b.Fatalf("Client: %v", err) + } + ch, incoming, err := newCh.Accept() + go DiscardRequests(incoming) + for i := 0; i < b.N; i++ { + if _, err := io.ReadFull(ch, output); err != nil { + b.Fatalf("ReadFull: %v", err) + } + } + ch.Close() + done <- 1 + }() + + ch, in, err := client.OpenChannel("speed", nil) + if err != nil { + b.Fatalf("OpenChannel: %v", err) + } + go DiscardRequests(in) + + b.ResetTimer() + b.StartTimer() + for i := 0; i < b.N; i++ { + if _, err := ch.Write(input); err != nil { + b.Fatalf("WriteFull: %v", err) + } + } + ch.Close() + b.StopTimer() + + <-done +} diff --git a/vendor/golang.org/x/crypto/ssh/buffer.go b/vendor/golang.org/x/crypto/ssh/buffer.go new file mode 100644 index 0000000..1ab07d0 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/buffer.go @@ -0,0 +1,97 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "io" + "sync" +) + +// buffer provides a linked list buffer for data exchange +// between producer and consumer. Theoretically the buffer is +// of unlimited capacity as it does no allocation of its own. +type buffer struct { + // protects concurrent access to head, tail and closed + *sync.Cond + + head *element // the buffer that will be read first + tail *element // the buffer that will be read last + + closed bool +} + +// An element represents a single link in a linked list. +type element struct { + buf []byte + next *element +} + +// newBuffer returns an empty buffer that is not closed. +func newBuffer() *buffer { + e := new(element) + b := &buffer{ + Cond: newCond(), + head: e, + tail: e, + } + return b +} + +// write makes buf available for Read to receive. +// buf must not be modified after the call to write. +func (b *buffer) write(buf []byte) { + b.Cond.L.Lock() + e := &element{buf: buf} + b.tail.next = e + b.tail = e + b.Cond.Signal() + b.Cond.L.Unlock() +} + +// eof closes the buffer. Reads from the buffer once all +// the data has been consumed will receive io.EOF. +func (b *buffer) eof() { + b.Cond.L.Lock() + b.closed = true + b.Cond.Signal() + b.Cond.L.Unlock() +} + +// Read reads data from the internal buffer in buf. Reads will block +// if no data is available, or until the buffer is closed. +func (b *buffer) Read(buf []byte) (n int, err error) { + b.Cond.L.Lock() + defer b.Cond.L.Unlock() + + for len(buf) > 0 { + // if there is data in b.head, copy it + if len(b.head.buf) > 0 { + r := copy(buf, b.head.buf) + buf, b.head.buf = buf[r:], b.head.buf[r:] + n += r + continue + } + // if there is a next buffer, make it the head + if len(b.head.buf) == 0 && b.head != b.tail { + b.head = b.head.next + continue + } + + // if at least one byte has been copied, return + if n > 0 { + break + } + + // if nothing was read, and there is nothing outstanding + // check to see if the buffer is closed. + if b.closed { + err = io.EOF + break + } + // out of buffers, wait for producer + b.Cond.Wait() + } + return +} diff --git a/vendor/golang.org/x/crypto/ssh/buffer_test.go b/vendor/golang.org/x/crypto/ssh/buffer_test.go new file mode 100644 index 0000000..d5781cb --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/buffer_test.go @@ -0,0 +1,87 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "io" + "testing" +) + +var alphabet = []byte("abcdefghijklmnopqrstuvwxyz") + +func TestBufferReadwrite(t *testing.T) { + b := newBuffer() + b.write(alphabet[:10]) + r, _ := b.Read(make([]byte, 10)) + if r != 10 { + t.Fatalf("Expected written == read == 10, written: 10, read %d", r) + } + + b = newBuffer() + b.write(alphabet[:5]) + r, _ = b.Read(make([]byte, 10)) + if r != 5 { + t.Fatalf("Expected written == read == 5, written: 5, read %d", r) + } + + b = newBuffer() + b.write(alphabet[:10]) + r, _ = b.Read(make([]byte, 5)) + if r != 5 { + t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r) + } + + b = newBuffer() + b.write(alphabet[:5]) + b.write(alphabet[5:15]) + r, _ = b.Read(make([]byte, 10)) + r2, _ := b.Read(make([]byte, 10)) + if r != 10 || r2 != 5 || 15 != r+r2 { + t.Fatal("Expected written == read == 15") + } +} + +func TestBufferClose(t *testing.T) { + b := newBuffer() + b.write(alphabet[:10]) + b.eof() + _, err := b.Read(make([]byte, 5)) + if err != nil { + t.Fatal("expected read of 5 to not return EOF") + } + b = newBuffer() + b.write(alphabet[:10]) + b.eof() + r, err := b.Read(make([]byte, 5)) + r2, err2 := b.Read(make([]byte, 10)) + if r != 5 || r2 != 5 || err != nil || err2 != nil { + t.Fatal("expected reads of 5 and 5") + } + + b = newBuffer() + b.write(alphabet[:10]) + b.eof() + r, err = b.Read(make([]byte, 5)) + r2, err2 = b.Read(make([]byte, 10)) + r3, err3 := b.Read(make([]byte, 10)) + if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF { + t.Fatal("expected reads of 5 and 5 and 0, with EOF") + } + + b = newBuffer() + b.write(make([]byte, 5)) + b.write(make([]byte, 10)) + b.eof() + r, err = b.Read(make([]byte, 9)) + r2, err2 = b.Read(make([]byte, 3)) + r3, err3 = b.Read(make([]byte, 3)) + r4, err4 := b.Read(make([]byte, 10)) + if err != nil || err2 != nil || err3 != nil || err4 != io.EOF { + t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4) + } + if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 { + t.Fatal("Expected written == read == 15", r, r2, r3, r4) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/certs.go b/vendor/golang.org/x/crypto/ssh/certs.go new file mode 100644 index 0000000..cfc8ead --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/certs.go @@ -0,0 +1,519 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "sort" + "time" +) + +// These constants from [PROTOCOL.certkeys] represent the algorithm names +// for certificate types supported by this package. +const ( + CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com" + CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com" + CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com" + CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com" + CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com" + CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com" +) + +// Certificate types distinguish between host and user +// certificates. The values can be set in the CertType field of +// Certificate. +const ( + UserCert = 1 + HostCert = 2 +) + +// Signature represents a cryptographic signature. +type Signature struct { + Format string + Blob []byte +} + +// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that +// a certificate does not expire. +const CertTimeInfinity = 1<<64 - 1 + +// An Certificate represents an OpenSSH certificate as defined in +// [PROTOCOL.certkeys]?rev=1.8. +type Certificate struct { + Nonce []byte + Key PublicKey + Serial uint64 + CertType uint32 + KeyId string + ValidPrincipals []string + ValidAfter uint64 + ValidBefore uint64 + Permissions + Reserved []byte + SignatureKey PublicKey + Signature *Signature +} + +// genericCertData holds the key-independent part of the certificate data. +// Overall, certificates contain an nonce, public key fields and +// key-independent fields. +type genericCertData struct { + Serial uint64 + CertType uint32 + KeyId string + ValidPrincipals []byte + ValidAfter uint64 + ValidBefore uint64 + CriticalOptions []byte + Extensions []byte + Reserved []byte + SignatureKey []byte + Signature []byte +} + +func marshalStringList(namelist []string) []byte { + var to []byte + for _, name := range namelist { + s := struct{ N string }{name} + to = append(to, Marshal(&s)...) + } + return to +} + +type optionsTuple struct { + Key string + Value []byte +} + +type optionsTupleValue struct { + Value string +} + +// serialize a map of critical options or extensions +// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation, +// we need two length prefixes for a non-empty string value +func marshalTuples(tups map[string]string) []byte { + keys := make([]string, 0, len(tups)) + for key := range tups { + keys = append(keys, key) + } + sort.Strings(keys) + + var ret []byte + for _, key := range keys { + s := optionsTuple{Key: key} + if value := tups[key]; len(value) > 0 { + s.Value = Marshal(&optionsTupleValue{value}) + } + ret = append(ret, Marshal(&s)...) + } + return ret +} + +// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation, +// we need two length prefixes for a non-empty option value +func parseTuples(in []byte) (map[string]string, error) { + tups := map[string]string{} + var lastKey string + var haveLastKey bool + + for len(in) > 0 { + var key, val, extra []byte + var ok bool + + if key, in, ok = parseString(in); !ok { + return nil, errShortRead + } + keyStr := string(key) + // according to [PROTOCOL.certkeys], the names must be in + // lexical order. + if haveLastKey && keyStr <= lastKey { + return nil, fmt.Errorf("ssh: certificate options are not in lexical order") + } + lastKey, haveLastKey = keyStr, true + // the next field is a data field, which if non-empty has a string embedded + if val, in, ok = parseString(in); !ok { + return nil, errShortRead + } + if len(val) > 0 { + val, extra, ok = parseString(val) + if !ok { + return nil, errShortRead + } + if len(extra) > 0 { + return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value") + } + tups[keyStr] = string(val) + } else { + tups[keyStr] = "" + } + } + return tups, nil +} + +func parseCert(in []byte, privAlgo string) (*Certificate, error) { + nonce, rest, ok := parseString(in) + if !ok { + return nil, errShortRead + } + + key, rest, err := parsePubKey(rest, privAlgo) + if err != nil { + return nil, err + } + + var g genericCertData + if err := Unmarshal(rest, &g); err != nil { + return nil, err + } + + c := &Certificate{ + Nonce: nonce, + Key: key, + Serial: g.Serial, + CertType: g.CertType, + KeyId: g.KeyId, + ValidAfter: g.ValidAfter, + ValidBefore: g.ValidBefore, + } + + for principals := g.ValidPrincipals; len(principals) > 0; { + principal, rest, ok := parseString(principals) + if !ok { + return nil, errShortRead + } + c.ValidPrincipals = append(c.ValidPrincipals, string(principal)) + principals = rest + } + + c.CriticalOptions, err = parseTuples(g.CriticalOptions) + if err != nil { + return nil, err + } + c.Extensions, err = parseTuples(g.Extensions) + if err != nil { + return nil, err + } + c.Reserved = g.Reserved + k, err := ParsePublicKey(g.SignatureKey) + if err != nil { + return nil, err + } + + c.SignatureKey = k + c.Signature, rest, ok = parseSignatureBody(g.Signature) + if !ok || len(rest) > 0 { + return nil, errors.New("ssh: signature parse error") + } + + return c, nil +} + +type openSSHCertSigner struct { + pub *Certificate + signer Signer +} + +// NewCertSigner returns a Signer that signs with the given Certificate, whose +// private key is held by signer. It returns an error if the public key in cert +// doesn't match the key used by signer. +func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) { + if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 { + return nil, errors.New("ssh: signer and cert have different public key") + } + + return &openSSHCertSigner{cert, signer}, nil +} + +func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { + return s.signer.Sign(rand, data) +} + +func (s *openSSHCertSigner) PublicKey() PublicKey { + return s.pub +} + +const sourceAddressCriticalOption = "source-address" + +// CertChecker does the work of verifying a certificate. Its methods +// can be plugged into ClientConfig.HostKeyCallback and +// ServerConfig.PublicKeyCallback. For the CertChecker to work, +// minimally, the IsAuthority callback should be set. +type CertChecker struct { + // SupportedCriticalOptions lists the CriticalOptions that the + // server application layer understands. These are only used + // for user certificates. + SupportedCriticalOptions []string + + // IsUserAuthority should return true if the key is recognized as an + // authority for the given user certificate. This allows for + // certificates to be signed by other certificates. This must be set + // if this CertChecker will be checking user certificates. + IsUserAuthority func(auth PublicKey) bool + + // IsHostAuthority should report whether the key is recognized as + // an authority for this host. This allows for certificates to be + // signed by other keys, and for those other keys to only be valid + // signers for particular hostnames. This must be set if this + // CertChecker will be checking host certificates. + IsHostAuthority func(auth PublicKey, address string) bool + + // Clock is used for verifying time stamps. If nil, time.Now + // is used. + Clock func() time.Time + + // UserKeyFallback is called when CertChecker.Authenticate encounters a + // public key that is not a certificate. It must implement validation + // of user keys or else, if nil, all such keys are rejected. + UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // HostKeyFallback is called when CertChecker.CheckHostKey encounters a + // public key that is not a certificate. It must implement host key + // validation or else, if nil, all such keys are rejected. + HostKeyFallback HostKeyCallback + + // IsRevoked is called for each certificate so that revocation checking + // can be implemented. It should return true if the given certificate + // is revoked and false otherwise. If nil, no certificates are + // considered to have been revoked. + IsRevoked func(cert *Certificate) bool +} + +// CheckHostKey checks a host key certificate. This method can be +// plugged into ClientConfig.HostKeyCallback. +func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error { + cert, ok := key.(*Certificate) + if !ok { + if c.HostKeyFallback != nil { + return c.HostKeyFallback(addr, remote, key) + } + return errors.New("ssh: non-certificate host key") + } + if cert.CertType != HostCert { + return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType) + } + if !c.IsHostAuthority(cert.SignatureKey, addr) { + return fmt.Errorf("ssh: no authorities for hostname: %v", addr) + } + + hostname, _, err := net.SplitHostPort(addr) + if err != nil { + return err + } + + // Pass hostname only as principal for host certificates (consistent with OpenSSH) + return c.CheckCert(hostname, cert) +} + +// Authenticate checks a user certificate. Authenticate can be used as +// a value for ServerConfig.PublicKeyCallback. +func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) { + cert, ok := pubKey.(*Certificate) + if !ok { + if c.UserKeyFallback != nil { + return c.UserKeyFallback(conn, pubKey) + } + return nil, errors.New("ssh: normal key pairs not accepted") + } + + if cert.CertType != UserCert { + return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType) + } + if !c.IsUserAuthority(cert.SignatureKey) { + return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority") + } + + if err := c.CheckCert(conn.User(), cert); err != nil { + return nil, err + } + + return &cert.Permissions, nil +} + +// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and +// the signature of the certificate. +func (c *CertChecker) CheckCert(principal string, cert *Certificate) error { + if c.IsRevoked != nil && c.IsRevoked(cert) { + return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial) + } + + for opt := range cert.CriticalOptions { + // sourceAddressCriticalOption will be enforced by + // serverAuthenticate + if opt == sourceAddressCriticalOption { + continue + } + + found := false + for _, supp := range c.SupportedCriticalOptions { + if supp == opt { + found = true + break + } + } + if !found { + return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt) + } + } + + if len(cert.ValidPrincipals) > 0 { + // By default, certs are valid for all users/hosts. + found := false + for _, p := range cert.ValidPrincipals { + if p == principal { + found = true + break + } + } + if !found { + return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals) + } + } + + clock := c.Clock + if clock == nil { + clock = time.Now + } + + unixNow := clock().Unix() + if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) { + return fmt.Errorf("ssh: cert is not yet valid") + } + if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) { + return fmt.Errorf("ssh: cert has expired") + } + if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil { + return fmt.Errorf("ssh: certificate signature does not verify") + } + + return nil +} + +// SignCert sets c.SignatureKey to the authority's public key and stores a +// Signature, by authority, in the certificate. +func (c *Certificate) SignCert(rand io.Reader, authority Signer) error { + c.Nonce = make([]byte, 32) + if _, err := io.ReadFull(rand, c.Nonce); err != nil { + return err + } + c.SignatureKey = authority.PublicKey() + + sig, err := authority.Sign(rand, c.bytesForSigning()) + if err != nil { + return err + } + c.Signature = sig + return nil +} + +var certAlgoNames = map[string]string{ + KeyAlgoRSA: CertAlgoRSAv01, + KeyAlgoDSA: CertAlgoDSAv01, + KeyAlgoECDSA256: CertAlgoECDSA256v01, + KeyAlgoECDSA384: CertAlgoECDSA384v01, + KeyAlgoECDSA521: CertAlgoECDSA521v01, + KeyAlgoED25519: CertAlgoED25519v01, +} + +// certToPrivAlgo returns the underlying algorithm for a certificate algorithm. +// Panics if a non-certificate algorithm is passed. +func certToPrivAlgo(algo string) string { + for privAlgo, pubAlgo := range certAlgoNames { + if pubAlgo == algo { + return privAlgo + } + } + panic("unknown cert algorithm") +} + +func (cert *Certificate) bytesForSigning() []byte { + c2 := *cert + c2.Signature = nil + out := c2.Marshal() + // Drop trailing signature length. + return out[:len(out)-4] +} + +// Marshal serializes c into OpenSSH's wire format. It is part of the +// PublicKey interface. +func (c *Certificate) Marshal() []byte { + generic := genericCertData{ + Serial: c.Serial, + CertType: c.CertType, + KeyId: c.KeyId, + ValidPrincipals: marshalStringList(c.ValidPrincipals), + ValidAfter: uint64(c.ValidAfter), + ValidBefore: uint64(c.ValidBefore), + CriticalOptions: marshalTuples(c.CriticalOptions), + Extensions: marshalTuples(c.Extensions), + Reserved: c.Reserved, + SignatureKey: c.SignatureKey.Marshal(), + } + if c.Signature != nil { + generic.Signature = Marshal(c.Signature) + } + genericBytes := Marshal(&generic) + keyBytes := c.Key.Marshal() + _, keyBytes, _ = parseString(keyBytes) + prefix := Marshal(&struct { + Name string + Nonce []byte + Key []byte `ssh:"rest"` + }{c.Type(), c.Nonce, keyBytes}) + + result := make([]byte, 0, len(prefix)+len(genericBytes)) + result = append(result, prefix...) + result = append(result, genericBytes...) + return result +} + +// Type returns the key name. It is part of the PublicKey interface. +func (c *Certificate) Type() string { + algo, ok := certAlgoNames[c.Key.Type()] + if !ok { + panic("unknown cert key type " + c.Key.Type()) + } + return algo +} + +// Verify verifies a signature against the certificate's public +// key. It is part of the PublicKey interface. +func (c *Certificate) Verify(data []byte, sig *Signature) error { + return c.Key.Verify(data, sig) +} + +func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) { + format, in, ok := parseString(in) + if !ok { + return + } + + out = &Signature{ + Format: string(format), + } + + if out.Blob, in, ok = parseString(in); !ok { + return + } + + return out, in, ok +} + +func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) { + sigBytes, rest, ok := parseString(in) + if !ok { + return + } + + out, trailing, ok := parseSignatureBody(sigBytes) + if !ok || len(trailing) > 0 { + return nil, nil, false + } + return +} diff --git a/vendor/golang.org/x/crypto/ssh/certs_test.go b/vendor/golang.org/x/crypto/ssh/certs_test.go new file mode 100644 index 0000000..c8e7cf5 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/certs_test.go @@ -0,0 +1,335 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "net" + "reflect" + "testing" + "time" + + "golang.org/x/crypto/ssh/testdata" +) + +// Cert generated by ssh-keygen 6.0p1 Debian-4. +// % ssh-keygen -s ca-key -I test user-key +const exampleSSHCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb1srW/W3ZDjYAO45xLYAwzHBDLsJ4Ux6ICFIkTjb1LEAAAADAQABAAAAYQCkoR51poH0wE8w72cqSB8Sszx+vAhzcMdCO0wqHTj7UNENHWEXGrU0E0UQekD7U+yhkhtoyjbPOVIP7hNa6aRk/ezdh/iUnCIt4Jt1v3Z1h1P+hA4QuYFMHNB+rmjPwAcAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAHcAAAAHc3NoLXJzYQAAAAMBAAEAAABhANFS2kaktpSGc+CcmEKPyw9mJC4nZKxHKTgLVZeaGbFZOvJTNzBspQHdy7Q1uKSfktxpgjZnksiu/tFF9ngyY2KFoc+U88ya95IZUycBGCUbBQ8+bhDtw/icdDGQD5WnUwAAAG8AAAAHc3NoLXJzYQAAAGC8Y9Z2LQKhIhxf52773XaWrXdxP0t3GBVo4A10vUWiYoAGepr6rQIoGGXFxT4B9Gp+nEBJjOwKDXPrAevow0T9ca8gZN+0ykbhSrXLE5Ao48rqr3zP4O1/9P7e6gp0gw8=` + +func TestParseCert(t *testing.T) { + authKeyBytes := []byte(exampleSSHCert) + + key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes) + if err != nil { + t.Fatalf("ParseAuthorizedKey: %v", err) + } + if len(rest) > 0 { + t.Errorf("rest: got %q, want empty", rest) + } + + if _, ok := key.(*Certificate); !ok { + t.Fatalf("got %v (%T), want *Certificate", key, key) + } + + marshaled := MarshalAuthorizedKey(key) + // Before comparison, remove the trailing newline that + // MarshalAuthorizedKey adds. + marshaled = marshaled[:len(marshaled)-1] + if !bytes.Equal(authKeyBytes, marshaled) { + t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes) + } +} + +// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3 +// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub +// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN +// Critical Options: +// force-command /bin/sleep +// source-address 192.168.1.0/24 +// Extensions: +// permit-X11-forwarding +// permit-agent-forwarding +// permit-port-forwarding +// permit-pty +// permit-user-rc +const exampleSSHCertWithOptions = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgDyysCJY0XrO1n03EeRRoITnTPdjENFmWDs9X58PP3VUAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMNAAAAAAAAAAAAAAABAAAACHRlc3RjZXJ0AAAAAAAAAAAAAAAA//////////8AAABLAAAADWZvcmNlLWNvbW1hbmQAAAAOAAAACi9iaW4vc2xlZXAAAAAOc291cmNlLWFkZHJlc3MAAAASAAAADjE5Mi4xNjguMS4wLzI0AAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAwU+c5ui5A8+J/CFpjW8wCa52bEODA808WWQDCSuTG/eMXNf59v9Y8Pk0F1E9dGCosSNyVcB/hacUrc6He+i97+HJCyKavBsE6GDxrjRyxYqAlfcOXi/IVmaUGiO8OQ39d4GHrjToInKvExSUeleQyH4Y4/e27T/pILAqPFL3fyrvMLT5qU9QyIt6zIpa7GBP5+urouNavMprV3zsfIqNBbWypinOQAw823a5wN+zwXnhZrgQiHZ/USG09Y6k98y1dTVz8YHlQVR4D3lpTAsKDKJ5hCH9WU4fdf+lU8OyNGaJ/vz0XNqxcToe1l4numLTnaoSuH89pHryjqurB7lJKwAAAQ8AAAAHc3NoLXJzYQAAAQCaHvUIoPL1zWUHIXLvu96/HU1s/i4CAW2IIEuGgxCUCiFj6vyTyYtgxQxcmbfZf6eaITlS6XJZa7Qq4iaFZh75C1DXTX8labXhRSD4E2t//AIP9MC1rtQC5xo6FmbQ+BoKcDskr+mNACcbRSxs3IL3bwCfWDnIw2WbVox9ZdcthJKk4UoCW4ix4QwdHw7zlddlz++fGEEVhmTbll1SUkycGApPFBsAYRTMupUJcYPIeReBI/m8XfkoMk99bV8ZJQTAd7OekHY2/48Ff53jLmyDjP7kNw1F8OaPtkFs6dGJXta4krmaekPy87j+35In5hFj7yoOqvSbmYUkeX70/GGQ` + +func TestParseCertWithOptions(t *testing.T) { + opts := map[string]string{ + "source-address": "192.168.1.0/24", + "force-command": "/bin/sleep", + } + exts := map[string]string{ + "permit-X11-forwarding": "", + "permit-agent-forwarding": "", + "permit-port-forwarding": "", + "permit-pty": "", + "permit-user-rc": "", + } + authKeyBytes := []byte(exampleSSHCertWithOptions) + + key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes) + if err != nil { + t.Fatalf("ParseAuthorizedKey: %v", err) + } + if len(rest) > 0 { + t.Errorf("rest: got %q, want empty", rest) + } + cert, ok := key.(*Certificate) + if !ok { + t.Fatalf("got %v (%T), want *Certificate", key, key) + } + if !reflect.DeepEqual(cert.CriticalOptions, opts) { + t.Errorf("unexpected critical options - got %v, want %v", cert.CriticalOptions, opts) + } + if !reflect.DeepEqual(cert.Extensions, exts) { + t.Errorf("unexpected Extensions - got %v, want %v", cert.Extensions, exts) + } + marshaled := MarshalAuthorizedKey(key) + // Before comparison, remove the trailing newline that + // MarshalAuthorizedKey adds. + marshaled = marshaled[:len(marshaled)-1] + if !bytes.Equal(authKeyBytes, marshaled) { + t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes) + } +} + +func TestValidateCert(t *testing.T) { + key, _, _, _, err := ParseAuthorizedKey([]byte(exampleSSHCert)) + if err != nil { + t.Fatalf("ParseAuthorizedKey: %v", err) + } + validCert, ok := key.(*Certificate) + if !ok { + t.Fatalf("got %v (%T), want *Certificate", key, key) + } + checker := CertChecker{} + checker.IsUserAuthority = func(k PublicKey) bool { + return bytes.Equal(k.Marshal(), validCert.SignatureKey.Marshal()) + } + + if err := checker.CheckCert("user", validCert); err != nil { + t.Errorf("Unable to validate certificate: %v", err) + } + invalidCert := &Certificate{ + Key: testPublicKeys["rsa"], + SignatureKey: testPublicKeys["ecdsa"], + ValidBefore: CertTimeInfinity, + Signature: &Signature{}, + } + if err := checker.CheckCert("user", invalidCert); err == nil { + t.Error("Invalid cert signature passed validation") + } +} + +func TestValidateCertTime(t *testing.T) { + cert := Certificate{ + ValidPrincipals: []string{"user"}, + Key: testPublicKeys["rsa"], + ValidAfter: 50, + ValidBefore: 100, + } + + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + + for ts, ok := range map[int64]bool{ + 25: false, + 50: true, + 99: true, + 100: false, + 125: false, + } { + checker := CertChecker{ + Clock: func() time.Time { return time.Unix(ts, 0) }, + } + checker.IsUserAuthority = func(k PublicKey) bool { + return bytes.Equal(k.Marshal(), + testPublicKeys["ecdsa"].Marshal()) + } + + if v := checker.CheckCert("user", &cert); (v == nil) != ok { + t.Errorf("Authenticate(%d): %v", ts, v) + } + } +} + +// TODO(hanwen): tests for +// +// host keys: +// * fallbacks + +func TestHostKeyCert(t *testing.T) { + cert := &Certificate{ + ValidPrincipals: []string{"hostname", "hostname.domain", "otherhost"}, + Key: testPublicKeys["rsa"], + ValidBefore: CertTimeInfinity, + CertType: HostCert, + } + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + + checker := &CertChecker{ + IsHostAuthority: func(p PublicKey, addr string) bool { + return addr == "hostname:22" && bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal()) + }, + } + + certSigner, err := NewCertSigner(cert, testSigners["rsa"]) + if err != nil { + t.Errorf("NewCertSigner: %v", err) + } + + for _, test := range []struct { + addr string + succeed bool + }{ + {addr: "hostname:22", succeed: true}, + {addr: "otherhost:22", succeed: false}, // The certificate is valid for 'otherhost' as hostname, but we only recognize the authority of the signer for the address 'hostname:22' + {addr: "lasthost:22", succeed: false}, + } { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + errc := make(chan error) + + go func() { + conf := ServerConfig{ + NoClientAuth: true, + } + conf.AddHostKey(certSigner) + _, _, _, err := NewServerConn(c1, &conf) + errc <- err + }() + + config := &ClientConfig{ + User: "user", + HostKeyCallback: checker.CheckHostKey, + } + _, _, _, err = NewClientConn(c2, test.addr, config) + + if (err == nil) != test.succeed { + t.Fatalf("NewClientConn(%q): %v", test.addr, err) + } + + err = <-errc + if (err == nil) != test.succeed { + t.Fatalf("NewServerConn(%q): %v", test.addr, err) + } + } +} + +func TestCertTypes(t *testing.T) { + var testVars = []struct { + name string + keys func() Signer + }{ + { + name: CertAlgoECDSA256v01, + keys: func() Signer { + s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap256"]) + return s + }, + }, + { + name: CertAlgoECDSA384v01, + keys: func() Signer { + s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap384"]) + return s + }, + }, + { + name: CertAlgoECDSA521v01, + keys: func() Signer { + s, _ := ParsePrivateKey(testdata.PEMBytes["ecdsap521"]) + return s + }, + }, + { + name: CertAlgoED25519v01, + keys: func() Signer { + s, _ := ParsePrivateKey(testdata.PEMBytes["ed25519"]) + return s + }, + }, + { + name: CertAlgoRSAv01, + keys: func() Signer { + s, _ := ParsePrivateKey(testdata.PEMBytes["rsa"]) + return s + }, + }, + { + name: CertAlgoDSAv01, + keys: func() Signer { + s, _ := ParsePrivateKey(testdata.PEMBytes["dsa"]) + return s + }, + }, + } + + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("error generating host key: %v", err) + } + + signer, err := NewSignerFromKey(k) + if err != nil { + t.Fatalf("error generating signer for ssh listener: %v", err) + } + + conf := &ServerConfig{ + PublicKeyCallback: func(c ConnMetadata, k PublicKey) (*Permissions, error) { + return new(Permissions), nil + }, + } + conf.AddHostKey(signer) + + for _, m := range testVars { + t.Run(m.name, func(t *testing.T) { + + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go NewServerConn(c1, conf) + + priv := m.keys() + if err != nil { + t.Fatalf("error generating ssh pubkey: %v", err) + } + + cert := &Certificate{ + CertType: UserCert, + Key: priv.PublicKey(), + } + cert.SignCert(rand.Reader, priv) + + certSigner, err := NewCertSigner(cert, priv) + if err != nil { + t.Fatalf("error generating cert signer: %v", err) + } + + config := &ClientConfig{ + User: "user", + HostKeyCallback: func(h string, r net.Addr, k PublicKey) error { return nil }, + Auth: []AuthMethod{PublicKeys(certSigner)}, + } + + _, _, _, err = NewClientConn(c2, "", config) + if err != nil { + t.Fatalf("error connecting: %v", err) + } + }) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/channel.go b/vendor/golang.org/x/crypto/ssh/channel.go new file mode 100644 index 0000000..c0834c0 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/channel.go @@ -0,0 +1,633 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "log" + "sync" +) + +const ( + minPacketLength = 9 + // channelMaxPacket contains the maximum number of bytes that will be + // sent in a single packet. As per RFC 4253, section 6.1, 32k is also + // the minimum. + channelMaxPacket = 1 << 15 + // We follow OpenSSH here. + channelWindowSize = 64 * channelMaxPacket +) + +// NewChannel represents an incoming request to a channel. It must either be +// accepted for use by calling Accept, or rejected by calling Reject. +type NewChannel interface { + // Accept accepts the channel creation request. It returns the Channel + // and a Go channel containing SSH requests. The Go channel must be + // serviced otherwise the Channel will hang. + Accept() (Channel, <-chan *Request, error) + + // Reject rejects the channel creation request. After calling + // this, no other methods on the Channel may be called. + Reject(reason RejectionReason, message string) error + + // ChannelType returns the type of the channel, as supplied by the + // client. + ChannelType() string + + // ExtraData returns the arbitrary payload for this channel, as supplied + // by the client. This data is specific to the channel type. + ExtraData() []byte +} + +// A Channel is an ordered, reliable, flow-controlled, duplex stream +// that is multiplexed over an SSH connection. +type Channel interface { + // Read reads up to len(data) bytes from the channel. + Read(data []byte) (int, error) + + // Write writes len(data) bytes to the channel. + Write(data []byte) (int, error) + + // Close signals end of channel use. No data may be sent after this + // call. + Close() error + + // CloseWrite signals the end of sending in-band + // data. Requests may still be sent, and the other side may + // still send data + CloseWrite() error + + // SendRequest sends a channel request. If wantReply is true, + // it will wait for a reply and return the result as a + // boolean, otherwise the return value will be false. Channel + // requests are out-of-band messages so they may be sent even + // if the data stream is closed or blocked by flow control. + // If the channel is closed before a reply is returned, io.EOF + // is returned. + SendRequest(name string, wantReply bool, payload []byte) (bool, error) + + // Stderr returns an io.ReadWriter that writes to this channel + // with the extended data type set to stderr. Stderr may + // safely be read and written from a different goroutine than + // Read and Write respectively. + Stderr() io.ReadWriter +} + +// Request is a request sent outside of the normal stream of +// data. Requests can either be specific to an SSH channel, or they +// can be global. +type Request struct { + Type string + WantReply bool + Payload []byte + + ch *channel + mux *mux +} + +// Reply sends a response to a request. It must be called for all requests +// where WantReply is true and is a no-op otherwise. The payload argument is +// ignored for replies to channel-specific requests. +func (r *Request) Reply(ok bool, payload []byte) error { + if !r.WantReply { + return nil + } + + if r.ch == nil { + return r.mux.ackRequest(ok, payload) + } + + return r.ch.ackRequest(ok) +} + +// RejectionReason is an enumeration used when rejecting channel creation +// requests. See RFC 4254, section 5.1. +type RejectionReason uint32 + +const ( + Prohibited RejectionReason = iota + 1 + ConnectionFailed + UnknownChannelType + ResourceShortage +) + +// String converts the rejection reason to human readable form. +func (r RejectionReason) String() string { + switch r { + case Prohibited: + return "administratively prohibited" + case ConnectionFailed: + return "connect failed" + case UnknownChannelType: + return "unknown channel type" + case ResourceShortage: + return "resource shortage" + } + return fmt.Sprintf("unknown reason %d", int(r)) +} + +func min(a uint32, b int) uint32 { + if a < uint32(b) { + return a + } + return uint32(b) +} + +type channelDirection uint8 + +const ( + channelInbound channelDirection = iota + channelOutbound +) + +// channel is an implementation of the Channel interface that works +// with the mux class. +type channel struct { + // R/O after creation + chanType string + extraData []byte + localId, remoteId uint32 + + // maxIncomingPayload and maxRemotePayload are the maximum + // payload sizes of normal and extended data packets for + // receiving and sending, respectively. The wire packet will + // be 9 or 13 bytes larger (excluding encryption overhead). + maxIncomingPayload uint32 + maxRemotePayload uint32 + + mux *mux + + // decided is set to true if an accept or reject message has been sent + // (for outbound channels) or received (for inbound channels). + decided bool + + // direction contains either channelOutbound, for channels created + // locally, or channelInbound, for channels created by the peer. + direction channelDirection + + // Pending internal channel messages. + msg chan interface{} + + // Since requests have no ID, there can be only one request + // with WantReply=true outstanding. This lock is held by a + // goroutine that has such an outgoing request pending. + sentRequestMu sync.Mutex + + incomingRequests chan *Request + + sentEOF bool + + // thread-safe data + remoteWin window + pending *buffer + extPending *buffer + + // windowMu protects myWindow, the flow-control window. + windowMu sync.Mutex + myWindow uint32 + + // writeMu serializes calls to mux.conn.writePacket() and + // protects sentClose and packetPool. This mutex must be + // different from windowMu, as writePacket can block if there + // is a key exchange pending. + writeMu sync.Mutex + sentClose bool + + // packetPool has a buffer for each extended channel ID to + // save allocations during writes. + packetPool map[uint32][]byte +} + +// writePacket sends a packet. If the packet is a channel close, it updates +// sentClose. This method takes the lock c.writeMu. +func (ch *channel) writePacket(packet []byte) error { + ch.writeMu.Lock() + if ch.sentClose { + ch.writeMu.Unlock() + return io.EOF + } + ch.sentClose = (packet[0] == msgChannelClose) + err := ch.mux.conn.writePacket(packet) + ch.writeMu.Unlock() + return err +} + +func (ch *channel) sendMessage(msg interface{}) error { + if debugMux { + log.Printf("send(%d): %#v", ch.mux.chanList.offset, msg) + } + + p := Marshal(msg) + binary.BigEndian.PutUint32(p[1:], ch.remoteId) + return ch.writePacket(p) +} + +// WriteExtended writes data to a specific extended stream. These streams are +// used, for example, for stderr. +func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) { + if ch.sentEOF { + return 0, io.EOF + } + // 1 byte message type, 4 bytes remoteId, 4 bytes data length + opCode := byte(msgChannelData) + headerLength := uint32(9) + if extendedCode > 0 { + headerLength += 4 + opCode = msgChannelExtendedData + } + + ch.writeMu.Lock() + packet := ch.packetPool[extendedCode] + // We don't remove the buffer from packetPool, so + // WriteExtended calls from different goroutines will be + // flagged as errors by the race detector. + ch.writeMu.Unlock() + + for len(data) > 0 { + space := min(ch.maxRemotePayload, len(data)) + if space, err = ch.remoteWin.reserve(space); err != nil { + return n, err + } + if want := headerLength + space; uint32(cap(packet)) < want { + packet = make([]byte, want) + } else { + packet = packet[:want] + } + + todo := data[:space] + + packet[0] = opCode + binary.BigEndian.PutUint32(packet[1:], ch.remoteId) + if extendedCode > 0 { + binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode)) + } + binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo))) + copy(packet[headerLength:], todo) + if err = ch.writePacket(packet); err != nil { + return n, err + } + + n += len(todo) + data = data[len(todo):] + } + + ch.writeMu.Lock() + ch.packetPool[extendedCode] = packet + ch.writeMu.Unlock() + + return n, err +} + +func (ch *channel) handleData(packet []byte) error { + headerLen := 9 + isExtendedData := packet[0] == msgChannelExtendedData + if isExtendedData { + headerLen = 13 + } + if len(packet) < headerLen { + // malformed data packet + return parseError(packet[0]) + } + + var extended uint32 + if isExtendedData { + extended = binary.BigEndian.Uint32(packet[5:]) + } + + length := binary.BigEndian.Uint32(packet[headerLen-4 : headerLen]) + if length == 0 { + return nil + } + if length > ch.maxIncomingPayload { + // TODO(hanwen): should send Disconnect? + return errors.New("ssh: incoming packet exceeds maximum payload size") + } + + data := packet[headerLen:] + if length != uint32(len(data)) { + return errors.New("ssh: wrong packet length") + } + + ch.windowMu.Lock() + if ch.myWindow < length { + ch.windowMu.Unlock() + // TODO(hanwen): should send Disconnect with reason? + return errors.New("ssh: remote side wrote too much") + } + ch.myWindow -= length + ch.windowMu.Unlock() + + if extended == 1 { + ch.extPending.write(data) + } else if extended > 0 { + // discard other extended data. + } else { + ch.pending.write(data) + } + return nil +} + +func (c *channel) adjustWindow(n uint32) error { + c.windowMu.Lock() + // Since myWindow is managed on our side, and can never exceed + // the initial window setting, we don't worry about overflow. + c.myWindow += uint32(n) + c.windowMu.Unlock() + return c.sendMessage(windowAdjustMsg{ + AdditionalBytes: uint32(n), + }) +} + +func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error) { + switch extended { + case 1: + n, err = c.extPending.Read(data) + case 0: + n, err = c.pending.Read(data) + default: + return 0, fmt.Errorf("ssh: extended code %d unimplemented", extended) + } + + if n > 0 { + err = c.adjustWindow(uint32(n)) + // sendWindowAdjust can return io.EOF if the remote + // peer has closed the connection, however we want to + // defer forwarding io.EOF to the caller of Read until + // the buffer has been drained. + if n > 0 && err == io.EOF { + err = nil + } + } + + return n, err +} + +func (c *channel) close() { + c.pending.eof() + c.extPending.eof() + close(c.msg) + close(c.incomingRequests) + c.writeMu.Lock() + // This is not necessary for a normal channel teardown, but if + // there was another error, it is. + c.sentClose = true + c.writeMu.Unlock() + // Unblock writers. + c.remoteWin.close() +} + +// responseMessageReceived is called when a success or failure message is +// received on a channel to check that such a message is reasonable for the +// given channel. +func (ch *channel) responseMessageReceived() error { + if ch.direction == channelInbound { + return errors.New("ssh: channel response message received on inbound channel") + } + if ch.decided { + return errors.New("ssh: duplicate response received for channel") + } + ch.decided = true + return nil +} + +func (ch *channel) handlePacket(packet []byte) error { + switch packet[0] { + case msgChannelData, msgChannelExtendedData: + return ch.handleData(packet) + case msgChannelClose: + ch.sendMessage(channelCloseMsg{PeersID: ch.remoteId}) + ch.mux.chanList.remove(ch.localId) + ch.close() + return nil + case msgChannelEOF: + // RFC 4254 is mute on how EOF affects dataExt messages but + // it is logical to signal EOF at the same time. + ch.extPending.eof() + ch.pending.eof() + return nil + } + + decoded, err := decode(packet) + if err != nil { + return err + } + + switch msg := decoded.(type) { + case *channelOpenFailureMsg: + if err := ch.responseMessageReceived(); err != nil { + return err + } + ch.mux.chanList.remove(msg.PeersID) + ch.msg <- msg + case *channelOpenConfirmMsg: + if err := ch.responseMessageReceived(); err != nil { + return err + } + if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { + return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize) + } + ch.remoteId = msg.MyID + ch.maxRemotePayload = msg.MaxPacketSize + ch.remoteWin.add(msg.MyWindow) + ch.msg <- msg + case *windowAdjustMsg: + if !ch.remoteWin.add(msg.AdditionalBytes) { + return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes) + } + case *channelRequestMsg: + req := Request{ + Type: msg.Request, + WantReply: msg.WantReply, + Payload: msg.RequestSpecificData, + ch: ch, + } + + ch.incomingRequests <- &req + default: + ch.msg <- msg + } + return nil +} + +func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel { + ch := &channel{ + remoteWin: window{Cond: newCond()}, + myWindow: channelWindowSize, + pending: newBuffer(), + extPending: newBuffer(), + direction: direction, + incomingRequests: make(chan *Request, chanSize), + msg: make(chan interface{}, chanSize), + chanType: chanType, + extraData: extraData, + mux: m, + packetPool: make(map[uint32][]byte), + } + ch.localId = m.chanList.add(ch) + return ch +} + +var errUndecided = errors.New("ssh: must Accept or Reject channel") +var errDecidedAlready = errors.New("ssh: can call Accept or Reject only once") + +type extChannel struct { + code uint32 + ch *channel +} + +func (e *extChannel) Write(data []byte) (n int, err error) { + return e.ch.WriteExtended(data, e.code) +} + +func (e *extChannel) Read(data []byte) (n int, err error) { + return e.ch.ReadExtended(data, e.code) +} + +func (ch *channel) Accept() (Channel, <-chan *Request, error) { + if ch.decided { + return nil, nil, errDecidedAlready + } + ch.maxIncomingPayload = channelMaxPacket + confirm := channelOpenConfirmMsg{ + PeersID: ch.remoteId, + MyID: ch.localId, + MyWindow: ch.myWindow, + MaxPacketSize: ch.maxIncomingPayload, + } + ch.decided = true + if err := ch.sendMessage(confirm); err != nil { + return nil, nil, err + } + + return ch, ch.incomingRequests, nil +} + +func (ch *channel) Reject(reason RejectionReason, message string) error { + if ch.decided { + return errDecidedAlready + } + reject := channelOpenFailureMsg{ + PeersID: ch.remoteId, + Reason: reason, + Message: message, + Language: "en", + } + ch.decided = true + return ch.sendMessage(reject) +} + +func (ch *channel) Read(data []byte) (int, error) { + if !ch.decided { + return 0, errUndecided + } + return ch.ReadExtended(data, 0) +} + +func (ch *channel) Write(data []byte) (int, error) { + if !ch.decided { + return 0, errUndecided + } + return ch.WriteExtended(data, 0) +} + +func (ch *channel) CloseWrite() error { + if !ch.decided { + return errUndecided + } + ch.sentEOF = true + return ch.sendMessage(channelEOFMsg{ + PeersID: ch.remoteId}) +} + +func (ch *channel) Close() error { + if !ch.decided { + return errUndecided + } + + return ch.sendMessage(channelCloseMsg{ + PeersID: ch.remoteId}) +} + +// Extended returns an io.ReadWriter that sends and receives data on the given, +// SSH extended stream. Such streams are used, for example, for stderr. +func (ch *channel) Extended(code uint32) io.ReadWriter { + if !ch.decided { + return nil + } + return &extChannel{code, ch} +} + +func (ch *channel) Stderr() io.ReadWriter { + return ch.Extended(1) +} + +func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { + if !ch.decided { + return false, errUndecided + } + + if wantReply { + ch.sentRequestMu.Lock() + defer ch.sentRequestMu.Unlock() + } + + msg := channelRequestMsg{ + PeersID: ch.remoteId, + Request: name, + WantReply: wantReply, + RequestSpecificData: payload, + } + + if err := ch.sendMessage(msg); err != nil { + return false, err + } + + if wantReply { + m, ok := (<-ch.msg) + if !ok { + return false, io.EOF + } + switch m.(type) { + case *channelRequestFailureMsg: + return false, nil + case *channelRequestSuccessMsg: + return true, nil + default: + return false, fmt.Errorf("ssh: unexpected response to channel request: %#v", m) + } + } + + return false, nil +} + +// ackRequest either sends an ack or nack to the channel request. +func (ch *channel) ackRequest(ok bool) error { + if !ch.decided { + return errUndecided + } + + var msg interface{} + if !ok { + msg = channelRequestFailureMsg{ + PeersID: ch.remoteId, + } + } else { + msg = channelRequestSuccessMsg{ + PeersID: ch.remoteId, + } + } + return ch.sendMessage(msg) +} + +func (ch *channel) ChannelType() string { + return ch.chanType +} + +func (ch *channel) ExtraData() []byte { + return ch.extraData +} diff --git a/vendor/golang.org/x/crypto/ssh/cipher.go b/vendor/golang.org/x/crypto/ssh/cipher.go new file mode 100644 index 0000000..9944139 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/cipher.go @@ -0,0 +1,771 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/rc4" + "crypto/subtle" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" + "io/ioutil" + + "golang.org/x/crypto/internal/chacha20" + "golang.org/x/crypto/poly1305" +) + +const ( + packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher. + + // RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations + // MUST be able to process (plus a few more kilobytes for padding and mac). The RFC + // indicates implementations SHOULD be able to handle larger packet sizes, but then + // waffles on about reasonable limits. + // + // OpenSSH caps their maxPacket at 256kB so we choose to do + // the same. maxPacket is also used to ensure that uint32 + // length fields do not overflow, so it should remain well + // below 4G. + maxPacket = 256 * 1024 +) + +// noneCipher implements cipher.Stream and provides no encryption. It is used +// by the transport before the first key-exchange. +type noneCipher struct{} + +func (c noneCipher) XORKeyStream(dst, src []byte) { + copy(dst, src) +} + +func newAESCTR(key, iv []byte) (cipher.Stream, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewCTR(c, iv), nil +} + +func newRC4(key, iv []byte) (cipher.Stream, error) { + return rc4.NewCipher(key) +} + +type streamCipherMode struct { + keySize int + ivSize int + skip int + createFunc func(key, iv []byte) (cipher.Stream, error) +} + +func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) { + if len(key) < c.keySize { + panic("ssh: key length too small for cipher") + } + if len(iv) < c.ivSize { + panic("ssh: iv too small for cipher") + } + + stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize]) + if err != nil { + return nil, err + } + + var streamDump []byte + if c.skip > 0 { + streamDump = make([]byte, 512) + } + + for remainingToDump := c.skip; remainingToDump > 0; { + dumpThisTime := remainingToDump + if dumpThisTime > len(streamDump) { + dumpThisTime = len(streamDump) + } + stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime]) + remainingToDump -= dumpThisTime + } + + return stream, nil +} + +// cipherModes documents properties of supported ciphers. Ciphers not included +// are not supported and will not be negotiated, even if explicitly requested in +// ClientConfig.Crypto.Ciphers. +var cipherModes = map[string]*streamCipherMode{ + // Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms + // are defined in the order specified in the RFC. + "aes128-ctr": {16, aes.BlockSize, 0, newAESCTR}, + "aes192-ctr": {24, aes.BlockSize, 0, newAESCTR}, + "aes256-ctr": {32, aes.BlockSize, 0, newAESCTR}, + + // Ciphers from RFC4345, which introduces security-improved arcfour ciphers. + // They are defined in the order specified in the RFC. + "arcfour128": {16, 0, 1536, newRC4}, + "arcfour256": {32, 0, 1536, newRC4}, + + // Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol. + // Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and + // RC4) has problems with weak keys, and should be used with caution." + // RFC4345 introduces improved versions of Arcfour. + "arcfour": {16, 0, 0, newRC4}, + + // AEAD ciphers are special cased. If we add any more non-stream + // ciphers, we should create a cleaner way to do this. + gcmCipherID: {16, 12, 0, nil}, + chacha20Poly1305ID: {64, 0, 0, nil}, + + // CBC mode is insecure and so is not included in the default config. + // (See http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf). If absolutely + // needed, it's possible to specify a custom Config to enable it. + // You should expect that an active attacker can recover plaintext if + // you do. + aes128cbcID: {16, aes.BlockSize, 0, nil}, + + // 3des-cbc is insecure and is disabled by default. + tripledescbcID: {24, des.BlockSize, 0, nil}, +} + +// prefixLen is the length of the packet prefix that contains the packet length +// and number of padding bytes. +const prefixLen = 5 + +// streamPacketCipher is a packetCipher using a stream cipher. +type streamPacketCipher struct { + mac hash.Hash + cipher cipher.Stream + etm bool + + // The following members are to avoid per-packet allocations. + prefix [prefixLen]byte + seqNumBytes [4]byte + padding [2 * packetSizeMultiple]byte + packetData []byte + macResult []byte +} + +// readPacket reads and decrypt a single packet from the reader argument. +func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) { + if _, err := io.ReadFull(r, s.prefix[:]); err != nil { + return nil, err + } + + var encryptedPaddingLength [1]byte + if s.mac != nil && s.etm { + copy(encryptedPaddingLength[:], s.prefix[4:5]) + s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5]) + } else { + s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) + } + + length := binary.BigEndian.Uint32(s.prefix[0:4]) + paddingLength := uint32(s.prefix[4]) + + var macSize uint32 + if s.mac != nil { + s.mac.Reset() + binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) + s.mac.Write(s.seqNumBytes[:]) + if s.etm { + s.mac.Write(s.prefix[:4]) + s.mac.Write(encryptedPaddingLength[:]) + } else { + s.mac.Write(s.prefix[:]) + } + macSize = uint32(s.mac.Size()) + } + + if length <= paddingLength+1 { + return nil, errors.New("ssh: invalid packet length, packet too small") + } + + if length > maxPacket { + return nil, errors.New("ssh: invalid packet length, packet too large") + } + + // the maxPacket check above ensures that length-1+macSize + // does not overflow. + if uint32(cap(s.packetData)) < length-1+macSize { + s.packetData = make([]byte, length-1+macSize) + } else { + s.packetData = s.packetData[:length-1+macSize] + } + + if _, err := io.ReadFull(r, s.packetData); err != nil { + return nil, err + } + mac := s.packetData[length-1:] + data := s.packetData[:length-1] + + if s.mac != nil && s.etm { + s.mac.Write(data) + } + + s.cipher.XORKeyStream(data, data) + + if s.mac != nil { + if !s.etm { + s.mac.Write(data) + } + s.macResult = s.mac.Sum(s.macResult[:0]) + if subtle.ConstantTimeCompare(s.macResult, mac) != 1 { + return nil, errors.New("ssh: MAC failure") + } + } + + return s.packetData[:length-paddingLength-1], nil +} + +// writePacket encrypts and sends a packet of data to the writer argument +func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + if len(packet) > maxPacket { + return errors.New("ssh: packet too large") + } + + aadlen := 0 + if s.mac != nil && s.etm { + // packet length is not encrypted for EtM modes + aadlen = 4 + } + + paddingLength := packetSizeMultiple - (prefixLen+len(packet)-aadlen)%packetSizeMultiple + if paddingLength < 4 { + paddingLength += packetSizeMultiple + } + + length := len(packet) + 1 + paddingLength + binary.BigEndian.PutUint32(s.prefix[:], uint32(length)) + s.prefix[4] = byte(paddingLength) + padding := s.padding[:paddingLength] + if _, err := io.ReadFull(rand, padding); err != nil { + return err + } + + if s.mac != nil { + s.mac.Reset() + binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) + s.mac.Write(s.seqNumBytes[:]) + + if s.etm { + // For EtM algorithms, the packet length must stay unencrypted, + // but the following data (padding length) must be encrypted + s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5]) + } + + s.mac.Write(s.prefix[:]) + + if !s.etm { + // For non-EtM algorithms, the algorithm is applied on unencrypted data + s.mac.Write(packet) + s.mac.Write(padding) + } + } + + if !(s.mac != nil && s.etm) { + // For EtM algorithms, the padding length has already been encrypted + // and the packet length must remain unencrypted + s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) + } + + s.cipher.XORKeyStream(packet, packet) + s.cipher.XORKeyStream(padding, padding) + + if s.mac != nil && s.etm { + // For EtM algorithms, packet and padding must be encrypted + s.mac.Write(packet) + s.mac.Write(padding) + } + + if _, err := w.Write(s.prefix[:]); err != nil { + return err + } + if _, err := w.Write(packet); err != nil { + return err + } + if _, err := w.Write(padding); err != nil { + return err + } + + if s.mac != nil { + s.macResult = s.mac.Sum(s.macResult[:0]) + if _, err := w.Write(s.macResult); err != nil { + return err + } + } + + return nil +} + +type gcmCipher struct { + aead cipher.AEAD + prefix [4]byte + iv []byte + buf []byte +} + +func newGCMCipher(iv, key []byte) (packetCipher, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + aead, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + return &gcmCipher{ + aead: aead, + iv: iv, + }, nil +} + +const gcmTagSize = 16 + +func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + // Pad out to multiple of 16 bytes. This is different from the + // stream cipher because that encrypts the length too. + padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple) + if padding < 4 { + padding += packetSizeMultiple + } + + length := uint32(len(packet) + int(padding) + 1) + binary.BigEndian.PutUint32(c.prefix[:], length) + if _, err := w.Write(c.prefix[:]); err != nil { + return err + } + + if cap(c.buf) < int(length) { + c.buf = make([]byte, length) + } else { + c.buf = c.buf[:length] + } + + c.buf[0] = padding + copy(c.buf[1:], packet) + if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil { + return err + } + c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:]) + if _, err := w.Write(c.buf); err != nil { + return err + } + c.incIV() + + return nil +} + +func (c *gcmCipher) incIV() { + for i := 4 + 7; i >= 4; i-- { + c.iv[i]++ + if c.iv[i] != 0 { + break + } + } +} + +func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) { + if _, err := io.ReadFull(r, c.prefix[:]); err != nil { + return nil, err + } + length := binary.BigEndian.Uint32(c.prefix[:]) + if length > maxPacket { + return nil, errors.New("ssh: max packet length exceeded") + } + + if cap(c.buf) < int(length+gcmTagSize) { + c.buf = make([]byte, length+gcmTagSize) + } else { + c.buf = c.buf[:length+gcmTagSize] + } + + if _, err := io.ReadFull(r, c.buf); err != nil { + return nil, err + } + + plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:]) + if err != nil { + return nil, err + } + c.incIV() + + padding := plain[0] + if padding < 4 { + // padding is a byte, so it automatically satisfies + // the maximum size, which is 255. + return nil, fmt.Errorf("ssh: illegal padding %d", padding) + } + + if int(padding+1) >= len(plain) { + return nil, fmt.Errorf("ssh: padding %d too large", padding) + } + plain = plain[1 : length-uint32(padding)] + return plain, nil +} + +// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1 +type cbcCipher struct { + mac hash.Hash + macSize uint32 + decrypter cipher.BlockMode + encrypter cipher.BlockMode + + // The following members are to avoid per-packet allocations. + seqNumBytes [4]byte + packetData []byte + macResult []byte + + // Amount of data we should still read to hide which + // verification error triggered. + oracleCamouflage uint32 +} + +func newCBCCipher(c cipher.Block, iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) { + cbc := &cbcCipher{ + mac: macModes[algs.MAC].new(macKey), + decrypter: cipher.NewCBCDecrypter(c, iv), + encrypter: cipher.NewCBCEncrypter(c, iv), + packetData: make([]byte, 1024), + } + if cbc.mac != nil { + cbc.macSize = uint32(cbc.mac.Size()) + } + + return cbc, nil +} + +func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + cbc, err := newCBCCipher(c, iv, key, macKey, algs) + if err != nil { + return nil, err + } + + return cbc, nil +} + +func newTripleDESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) { + c, err := des.NewTripleDESCipher(key) + if err != nil { + return nil, err + } + + cbc, err := newCBCCipher(c, iv, key, macKey, algs) + if err != nil { + return nil, err + } + + return cbc, nil +} + +func maxUInt32(a, b int) uint32 { + if a > b { + return uint32(a) + } + return uint32(b) +} + +const ( + cbcMinPacketSizeMultiple = 8 + cbcMinPacketSize = 16 + cbcMinPaddingSize = 4 +) + +// cbcError represents a verification error that may leak information. +type cbcError string + +func (e cbcError) Error() string { return string(e) } + +func (c *cbcCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) { + p, err := c.readPacketLeaky(seqNum, r) + if err != nil { + if _, ok := err.(cbcError); ok { + // Verification error: read a fixed amount of + // data, to make distinguishing between + // failing MAC and failing length check more + // difficult. + io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage)) + } + } + return p, err +} + +func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error) { + blockSize := c.decrypter.BlockSize() + + // Read the header, which will include some of the subsequent data in the + // case of block ciphers - this is copied back to the payload later. + // How many bytes of payload/padding will be read with this first read. + firstBlockLength := uint32((prefixLen + blockSize - 1) / blockSize * blockSize) + firstBlock := c.packetData[:firstBlockLength] + if _, err := io.ReadFull(r, firstBlock); err != nil { + return nil, err + } + + c.oracleCamouflage = maxPacket + 4 + c.macSize - firstBlockLength + + c.decrypter.CryptBlocks(firstBlock, firstBlock) + length := binary.BigEndian.Uint32(firstBlock[:4]) + if length > maxPacket { + return nil, cbcError("ssh: packet too large") + } + if length+4 < maxUInt32(cbcMinPacketSize, blockSize) { + // The minimum size of a packet is 16 (or the cipher block size, whichever + // is larger) bytes. + return nil, cbcError("ssh: packet too small") + } + // The length of the packet (including the length field but not the MAC) must + // be a multiple of the block size or 8, whichever is larger. + if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 { + return nil, cbcError("ssh: invalid packet length multiple") + } + + paddingLength := uint32(firstBlock[4]) + if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 { + return nil, cbcError("ssh: invalid packet length") + } + + // Positions within the c.packetData buffer: + macStart := 4 + length + paddingStart := macStart - paddingLength + + // Entire packet size, starting before length, ending at end of mac. + entirePacketSize := macStart + c.macSize + + // Ensure c.packetData is large enough for the entire packet data. + if uint32(cap(c.packetData)) < entirePacketSize { + // Still need to upsize and copy, but this should be rare at runtime, only + // on upsizing the packetData buffer. + c.packetData = make([]byte, entirePacketSize) + copy(c.packetData, firstBlock) + } else { + c.packetData = c.packetData[:entirePacketSize] + } + + n, err := io.ReadFull(r, c.packetData[firstBlockLength:]) + if err != nil { + return nil, err + } + c.oracleCamouflage -= uint32(n) + + remainingCrypted := c.packetData[firstBlockLength:macStart] + c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted) + + mac := c.packetData[macStart:] + if c.mac != nil { + c.mac.Reset() + binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum) + c.mac.Write(c.seqNumBytes[:]) + c.mac.Write(c.packetData[:macStart]) + c.macResult = c.mac.Sum(c.macResult[:0]) + if subtle.ConstantTimeCompare(c.macResult, mac) != 1 { + return nil, cbcError("ssh: MAC failure") + } + } + + return c.packetData[prefixLen:paddingStart], nil +} + +func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize()) + + // Length of encrypted portion of the packet (header, payload, padding). + // Enforce minimum padding and packet size. + encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize) + // Enforce block size. + encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize + + length := encLength - 4 + paddingLength := int(length) - (1 + len(packet)) + + // Overall buffer contains: header, payload, padding, mac. + // Space for the MAC is reserved in the capacity but not the slice length. + bufferSize := encLength + c.macSize + if uint32(cap(c.packetData)) < bufferSize { + c.packetData = make([]byte, encLength, bufferSize) + } else { + c.packetData = c.packetData[:encLength] + } + + p := c.packetData + + // Packet header. + binary.BigEndian.PutUint32(p, length) + p = p[4:] + p[0] = byte(paddingLength) + + // Payload. + p = p[1:] + copy(p, packet) + + // Padding. + p = p[len(packet):] + if _, err := io.ReadFull(rand, p); err != nil { + return err + } + + if c.mac != nil { + c.mac.Reset() + binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum) + c.mac.Write(c.seqNumBytes[:]) + c.mac.Write(c.packetData) + // The MAC is now appended into the capacity reserved for it earlier. + c.packetData = c.mac.Sum(c.packetData) + } + + c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength]) + + if _, err := w.Write(c.packetData); err != nil { + return err + } + + return nil +} + +const chacha20Poly1305ID = "chacha20-poly1305@openssh.com" + +// chacha20Poly1305Cipher implements the chacha20-poly1305@openssh.com +// AEAD, which is described here: +// +// https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00 +// +// the methods here also implement padding, which RFC4253 Section 6 +// also requires of stream ciphers. +type chacha20Poly1305Cipher struct { + lengthKey [32]byte + contentKey [32]byte + buf []byte +} + +func newChaCha20Cipher(key []byte) (packetCipher, error) { + if len(key) != 64 { + panic("key length") + } + + c := &chacha20Poly1305Cipher{ + buf: make([]byte, 256), + } + + copy(c.contentKey[:], key[:32]) + copy(c.lengthKey[:], key[32:]) + return c, nil +} + +// The Poly1305 key is obtained by encrypting 32 0-bytes. +var chacha20PolyKeyInput [32]byte + +func (c *chacha20Poly1305Cipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) { + var counter [16]byte + binary.BigEndian.PutUint64(counter[8:], uint64(seqNum)) + + var polyKey [32]byte + chacha20.XORKeyStream(polyKey[:], chacha20PolyKeyInput[:], &counter, &c.contentKey) + + encryptedLength := c.buf[:4] + if _, err := r.Read(encryptedLength); err != nil { + return nil, err + } + + var lenBytes [4]byte + chacha20.XORKeyStream(lenBytes[:], encryptedLength, &counter, &c.lengthKey) + + length := binary.BigEndian.Uint32(lenBytes[:]) + if length > maxPacket { + return nil, errors.New("ssh: invalid packet length, packet too large") + } + + contentEnd := 4 + length + packetEnd := contentEnd + poly1305.TagSize + if uint32(cap(c.buf)) < packetEnd { + c.buf = make([]byte, packetEnd) + copy(c.buf[:], encryptedLength) + } else { + c.buf = c.buf[:packetEnd] + } + + if _, err := r.Read(c.buf[4:packetEnd]); err != nil { + return nil, err + } + + var mac [poly1305.TagSize]byte + copy(mac[:], c.buf[contentEnd:packetEnd]) + + if !poly1305.Verify(&mac, c.buf[:contentEnd], &polyKey) { + return nil, errors.New("ssh: MAC failure") + } + + counter[0] = 1 + + plain := c.buf[4:contentEnd] + chacha20.XORKeyStream(plain, plain, &counter, &c.contentKey) + + padding := plain[0] + if padding < 4 { + // padding is a byte, so it automatically satisfies + // the maximum size, which is 255. + return nil, fmt.Errorf("ssh: illegal padding %d", padding) + } + + if int(padding)+1 >= len(plain) { + return nil, fmt.Errorf("ssh: padding %d too large", padding) + } + + plain = plain[1 : len(plain)-int(padding)] + return plain, nil +} + +func (c *chacha20Poly1305Cipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, payload []byte) error { + var counter [16]byte + binary.BigEndian.PutUint64(counter[8:], uint64(seqNum)) + + var polyKey [32]byte + chacha20.XORKeyStream(polyKey[:], chacha20PolyKeyInput[:], &counter, &c.contentKey) + + // There is no blocksize, so fall back to multiple of 8 byte + // padding, as described in RFC 4253, Sec 6. + const packetSizeMultiple = 8 + + padding := packetSizeMultiple - (1+len(payload))%packetSizeMultiple + if padding < 4 { + padding += packetSizeMultiple + } + + // size (4 bytes), padding (1), payload, padding, tag. + totalLength := 4 + 1 + len(payload) + padding + poly1305.TagSize + if cap(c.buf) < totalLength { + c.buf = make([]byte, totalLength) + } else { + c.buf = c.buf[:totalLength] + } + + binary.BigEndian.PutUint32(c.buf, uint32(1+len(payload)+padding)) + chacha20.XORKeyStream(c.buf, c.buf[:4], &counter, &c.lengthKey) + c.buf[4] = byte(padding) + copy(c.buf[5:], payload) + packetEnd := 5 + len(payload) + padding + if _, err := io.ReadFull(rand, c.buf[5+len(payload):packetEnd]); err != nil { + return err + } + + counter[0] = 1 + chacha20.XORKeyStream(c.buf[4:], c.buf[4:packetEnd], &counter, &c.contentKey) + + var mac [poly1305.TagSize]byte + poly1305.Sum(&mac, c.buf[:packetEnd], &polyKey) + + copy(c.buf[packetEnd:], mac[:]) + + if _, err := w.Write(c.buf); err != nil { + return err + } + return nil +} diff --git a/vendor/golang.org/x/crypto/ssh/cipher_test.go b/vendor/golang.org/x/crypto/ssh/cipher_test.go new file mode 100644 index 0000000..6a35d87 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/cipher_test.go @@ -0,0 +1,130 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/rand" + "testing" +) + +func TestDefaultCiphersExist(t *testing.T) { + for _, cipherAlgo := range supportedCiphers { + if _, ok := cipherModes[cipherAlgo]; !ok { + t.Errorf("default cipher %q is unknown", cipherAlgo) + } + } +} + +func TestPacketCiphers(t *testing.T) { + defaultMac := "hmac-sha2-256" + defaultCipher := "aes128-ctr" + for cipher := range cipherModes { + t.Run("cipher="+cipher, + func(t *testing.T) { testPacketCipher(t, cipher, defaultMac) }) + } + for mac := range macModes { + t.Run("mac="+mac, + func(t *testing.T) { testPacketCipher(t, defaultCipher, mac) }) + } +} + +func testPacketCipher(t *testing.T, cipher, mac string) { + kr := &kexResult{Hash: crypto.SHA1} + algs := directionAlgorithms{ + Cipher: cipher, + MAC: mac, + Compression: "none", + } + client, err := newPacketCipher(clientKeys, algs, kr) + if err != nil { + t.Fatalf("newPacketCipher(client, %q, %q): %v", cipher, mac, err) + } + server, err := newPacketCipher(clientKeys, algs, kr) + if err != nil { + t.Fatalf("newPacketCipher(client, %q, %q): %v", cipher, mac, err) + } + + want := "bla bla" + input := []byte(want) + buf := &bytes.Buffer{} + if err := client.writePacket(0, buf, rand.Reader, input); err != nil { + t.Fatalf("writePacket(%q, %q): %v", cipher, mac, err) + } + + packet, err := server.readPacket(0, buf) + if err != nil { + t.Fatalf("readPacket(%q, %q): %v", cipher, mac, err) + } + + if string(packet) != want { + t.Errorf("roundtrip(%q, %q): got %q, want %q", cipher, mac, packet, want) + } +} + +func TestCBCOracleCounterMeasure(t *testing.T) { + cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil} + defer delete(cipherModes, aes128cbcID) + + kr := &kexResult{Hash: crypto.SHA1} + algs := directionAlgorithms{ + Cipher: aes128cbcID, + MAC: "hmac-sha1", + Compression: "none", + } + client, err := newPacketCipher(clientKeys, algs, kr) + if err != nil { + t.Fatalf("newPacketCipher(client): %v", err) + } + + want := "bla bla" + input := []byte(want) + buf := &bytes.Buffer{} + if err := client.writePacket(0, buf, rand.Reader, input); err != nil { + t.Errorf("writePacket: %v", err) + } + + packetSize := buf.Len() + buf.Write(make([]byte, 2*maxPacket)) + + // We corrupt each byte, but this usually will only test the + // 'packet too large' or 'MAC failure' cases. + lastRead := -1 + for i := 0; i < packetSize; i++ { + server, err := newPacketCipher(clientKeys, algs, kr) + if err != nil { + t.Fatalf("newPacketCipher(client): %v", err) + } + + fresh := &bytes.Buffer{} + fresh.Write(buf.Bytes()) + fresh.Bytes()[i] ^= 0x01 + + before := fresh.Len() + _, err = server.readPacket(0, fresh) + if err == nil { + t.Errorf("corrupt byte %d: readPacket succeeded ", i) + continue + } + if _, ok := err.(cbcError); !ok { + t.Errorf("corrupt byte %d: got %v (%T), want cbcError", i, err, err) + continue + } + + after := fresh.Len() + bytesRead := before - after + if bytesRead < maxPacket { + t.Errorf("corrupt byte %d: read %d bytes, want more than %d", i, bytesRead, maxPacket) + continue + } + + if i > 0 && bytesRead != lastRead { + t.Errorf("corrupt byte %d: read %d bytes, want %d bytes read", i, bytesRead, lastRead) + } + lastRead = bytesRead + } +} diff --git a/vendor/golang.org/x/crypto/ssh/client.go b/vendor/golang.org/x/crypto/ssh/client.go new file mode 100644 index 0000000..6fd1994 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/client.go @@ -0,0 +1,278 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "net" + "os" + "sync" + "time" +) + +// Client implements a traditional SSH client that supports shells, +// subprocesses, TCP port/streamlocal forwarding and tunneled dialing. +type Client struct { + Conn + + forwards forwardList // forwarded tcpip connections from the remote side + mu sync.Mutex + channelHandlers map[string]chan NewChannel +} + +// HandleChannelOpen returns a channel on which NewChannel requests +// for the given type are sent. If the type already is being handled, +// nil is returned. The channel is closed when the connection is closed. +func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel { + c.mu.Lock() + defer c.mu.Unlock() + if c.channelHandlers == nil { + // The SSH channel has been closed. + c := make(chan NewChannel) + close(c) + return c + } + + ch := c.channelHandlers[channelType] + if ch != nil { + return nil + } + + ch = make(chan NewChannel, chanSize) + c.channelHandlers[channelType] = ch + return ch +} + +// NewClient creates a Client on top of the given connection. +func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client { + conn := &Client{ + Conn: c, + channelHandlers: make(map[string]chan NewChannel, 1), + } + + go conn.handleGlobalRequests(reqs) + go conn.handleChannelOpens(chans) + go func() { + conn.Wait() + conn.forwards.closeAll() + }() + go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip")) + go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com")) + return conn +} + +// NewClientConn establishes an authenticated SSH connection using c +// as the underlying transport. The Request and NewChannel channels +// must be serviced or the connection will hang. +func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) { + fullConf := *config + fullConf.SetDefaults() + if fullConf.HostKeyCallback == nil { + c.Close() + return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback") + } + + conn := &connection{ + sshConn: sshConn{conn: c}, + } + + if err := conn.clientHandshake(addr, &fullConf); err != nil { + c.Close() + return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err) + } + conn.mux = newMux(conn.transport) + return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil +} + +// clientHandshake performs the client side key exchange. See RFC 4253 Section +// 7. +func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error { + if config.ClientVersion != "" { + c.clientVersion = []byte(config.ClientVersion) + } else { + c.clientVersion = []byte(packageVersion) + } + var err error + c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion) + if err != nil { + return err + } + + c.transport = newClientTransport( + newTransport(c.sshConn.conn, config.Rand, true /* is client */), + c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr()) + if err := c.transport.waitSession(); err != nil { + return err + } + + c.sessionID = c.transport.getSessionID() + return c.clientAuthenticate(config) +} + +// verifyHostKeySignature verifies the host key obtained in the key +// exchange. +func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error { + sig, rest, ok := parseSignatureBody(result.Signature) + if len(rest) > 0 || !ok { + return errors.New("ssh: signature parse error") + } + + return hostKey.Verify(result.H, sig) +} + +// NewSession opens a new Session for this client. (A session is a remote +// execution of a program.) +func (c *Client) NewSession() (*Session, error) { + ch, in, err := c.OpenChannel("session", nil) + if err != nil { + return nil, err + } + return newSession(ch, in) +} + +func (c *Client) handleGlobalRequests(incoming <-chan *Request) { + for r := range incoming { + // This handles keepalive messages and matches + // the behaviour of OpenSSH. + r.Reply(false, nil) + } +} + +// handleChannelOpens channel open messages from the remote side. +func (c *Client) handleChannelOpens(in <-chan NewChannel) { + for ch := range in { + c.mu.Lock() + handler := c.channelHandlers[ch.ChannelType()] + c.mu.Unlock() + + if handler != nil { + handler <- ch + } else { + ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType())) + } + } + + c.mu.Lock() + for _, ch := range c.channelHandlers { + close(ch) + } + c.channelHandlers = nil + c.mu.Unlock() +} + +// Dial starts a client connection to the given SSH server. It is a +// convenience function that connects to the given network address, +// initiates the SSH handshake, and then sets up a Client. For access +// to incoming channels and requests, use net.Dial with NewClientConn +// instead. +func Dial(network, addr string, config *ClientConfig) (*Client, error) { + conn, err := net.DialTimeout(network, addr, config.Timeout) + if err != nil { + return nil, err + } + c, chans, reqs, err := NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + return NewClient(c, chans, reqs), nil +} + +// HostKeyCallback is the function type used for verifying server +// keys. A HostKeyCallback must return nil if the host key is OK, or +// an error to reject it. It receives the hostname as passed to Dial +// or NewClientConn. The remote address is the RemoteAddr of the +// net.Conn underlying the the SSH connection. +type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error + +// BannerCallback is the function type used for treat the banner sent by +// the server. A BannerCallback receives the message sent by the remote server. +type BannerCallback func(message string) error + +// A ClientConfig structure is used to configure a Client. It must not be +// modified after having been passed to an SSH function. +type ClientConfig struct { + // Config contains configuration that is shared between clients and + // servers. + Config + + // User contains the username to authenticate as. + User string + + // Auth contains possible authentication methods to use with the + // server. Only the first instance of a particular RFC 4252 method will + // be used during authentication. + Auth []AuthMethod + + // HostKeyCallback is called during the cryptographic + // handshake to validate the server's host key. The client + // configuration must supply this callback for the connection + // to succeed. The functions InsecureIgnoreHostKey or + // FixedHostKey can be used for simplistic host key checks. + HostKeyCallback HostKeyCallback + + // BannerCallback is called during the SSH dance to display a custom + // server's message. The client configuration can supply this callback to + // handle it as wished. The function BannerDisplayStderr can be used for + // simplistic display on Stderr. + BannerCallback BannerCallback + + // ClientVersion contains the version identification string that will + // be used for the connection. If empty, a reasonable default is used. + ClientVersion string + + // HostKeyAlgorithms lists the key types that the client will + // accept from the server as host key, in order of + // preference. If empty, a reasonable default is used. Any + // string returned from PublicKey.Type method may be used, or + // any of the CertAlgoXxxx and KeyAlgoXxxx constants. + HostKeyAlgorithms []string + + // Timeout is the maximum amount of time for the TCP connection to establish. + // + // A Timeout of zero means no timeout. + Timeout time.Duration +} + +// InsecureIgnoreHostKey returns a function that can be used for +// ClientConfig.HostKeyCallback to accept any host key. It should +// not be used for production code. +func InsecureIgnoreHostKey() HostKeyCallback { + return func(hostname string, remote net.Addr, key PublicKey) error { + return nil + } +} + +type fixedHostKey struct { + key PublicKey +} + +func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error { + if f.key == nil { + return fmt.Errorf("ssh: required host key was nil") + } + if !bytes.Equal(key.Marshal(), f.key.Marshal()) { + return fmt.Errorf("ssh: host key mismatch") + } + return nil +} + +// FixedHostKey returns a function for use in +// ClientConfig.HostKeyCallback to accept only a specific host key. +func FixedHostKey(key PublicKey) HostKeyCallback { + hk := &fixedHostKey{key} + return hk.check +} + +// BannerDisplayStderr returns a function that can be used for +// ClientConfig.BannerCallback to display banners on os.Stderr. +func BannerDisplayStderr() BannerCallback { + return func(banner string) error { + _, err := os.Stderr.WriteString(banner) + + return err + } +} diff --git a/vendor/golang.org/x/crypto/ssh/client_auth.go b/vendor/golang.org/x/crypto/ssh/client_auth.go new file mode 100644 index 0000000..a1252cb --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/client_auth.go @@ -0,0 +1,510 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" +) + +// clientAuthenticate authenticates with the remote server. See RFC 4252. +func (c *connection) clientAuthenticate(config *ClientConfig) error { + // initiate user auth session + if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil { + return err + } + packet, err := c.transport.readPacket() + if err != nil { + return err + } + var serviceAccept serviceAcceptMsg + if err := Unmarshal(packet, &serviceAccept); err != nil { + return err + } + + // during the authentication phase the client first attempts the "none" method + // then any untried methods suggested by the server. + tried := make(map[string]bool) + var lastMethods []string + + sessionID := c.transport.getSessionID() + for auth := AuthMethod(new(noneAuth)); auth != nil; { + ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand) + if err != nil { + return err + } + if ok { + // success + return nil + } + tried[auth.method()] = true + if methods == nil { + methods = lastMethods + } + lastMethods = methods + + auth = nil + + findNext: + for _, a := range config.Auth { + candidateMethod := a.method() + if tried[candidateMethod] { + continue + } + for _, meth := range methods { + if meth == candidateMethod { + auth = a + break findNext + } + } + } + } + return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried)) +} + +func keys(m map[string]bool) []string { + s := make([]string, 0, len(m)) + + for key := range m { + s = append(s, key) + } + return s +} + +// An AuthMethod represents an instance of an RFC 4252 authentication method. +type AuthMethod interface { + // auth authenticates user over transport t. + // Returns true if authentication is successful. + // If authentication is not successful, a []string of alternative + // method names is returned. If the slice is nil, it will be ignored + // and the previous set of possible methods will be reused. + auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error) + + // method returns the RFC 4252 method name. + method() string +} + +// "none" authentication, RFC 4252 section 5.2. +type noneAuth int + +func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { + if err := c.writePacket(Marshal(&userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: "none", + })); err != nil { + return false, nil, err + } + + return handleAuthResponse(c) +} + +func (n *noneAuth) method() string { + return "none" +} + +// passwordCallback is an AuthMethod that fetches the password through +// a function call, e.g. by prompting the user. +type passwordCallback func() (password string, err error) + +func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { + type passwordAuthMsg struct { + User string `sshtype:"50"` + Service string + Method string + Reply bool + Password string + } + + pw, err := cb() + // REVIEW NOTE: is there a need to support skipping a password attempt? + // The program may only find out that the user doesn't have a password + // when prompting. + if err != nil { + return false, nil, err + } + + if err := c.writePacket(Marshal(&passwordAuthMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + Reply: false, + Password: pw, + })); err != nil { + return false, nil, err + } + + return handleAuthResponse(c) +} + +func (cb passwordCallback) method() string { + return "password" +} + +// Password returns an AuthMethod using the given password. +func Password(secret string) AuthMethod { + return passwordCallback(func() (string, error) { return secret, nil }) +} + +// PasswordCallback returns an AuthMethod that uses a callback for +// fetching a password. +func PasswordCallback(prompt func() (secret string, err error)) AuthMethod { + return passwordCallback(prompt) +} + +type publickeyAuthMsg struct { + User string `sshtype:"50"` + Service string + Method string + // HasSig indicates to the receiver packet that the auth request is signed and + // should be used for authentication of the request. + HasSig bool + Algoname string + PubKey []byte + // Sig is tagged with "rest" so Marshal will exclude it during + // validateKey + Sig []byte `ssh:"rest"` +} + +// publicKeyCallback is an AuthMethod that uses a set of key +// pairs for authentication. +type publicKeyCallback func() ([]Signer, error) + +func (cb publicKeyCallback) method() string { + return "publickey" +} + +func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { + // Authentication is performed by sending an enquiry to test if a key is + // acceptable to the remote. If the key is acceptable, the client will + // attempt to authenticate with the valid key. If not the client will repeat + // the process with the remaining keys. + + signers, err := cb() + if err != nil { + return false, nil, err + } + var methods []string + for _, signer := range signers { + ok, err := validateKey(signer.PublicKey(), user, c) + if err != nil { + return false, nil, err + } + if !ok { + continue + } + + pub := signer.PublicKey() + pubKey := pub.Marshal() + sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + }, []byte(pub.Type()), pubKey)) + if err != nil { + return false, nil, err + } + + // manually wrap the serialized signature in a string + s := Marshal(sign) + sig := make([]byte, stringLength(len(s))) + marshalString(sig, s) + msg := publickeyAuthMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + HasSig: true, + Algoname: pub.Type(), + PubKey: pubKey, + Sig: sig, + } + p := Marshal(&msg) + if err := c.writePacket(p); err != nil { + return false, nil, err + } + var success bool + success, methods, err = handleAuthResponse(c) + if err != nil { + return false, nil, err + } + + // If authentication succeeds or the list of available methods does not + // contain the "publickey" method, do not attempt to authenticate with any + // other keys. According to RFC 4252 Section 7, the latter can occur when + // additional authentication methods are required. + if success || !containsMethod(methods, cb.method()) { + return success, methods, err + } + } + + return false, methods, nil +} + +func containsMethod(methods []string, method string) bool { + for _, m := range methods { + if m == method { + return true + } + } + + return false +} + +// validateKey validates the key provided is acceptable to the server. +func validateKey(key PublicKey, user string, c packetConn) (bool, error) { + pubKey := key.Marshal() + msg := publickeyAuthMsg{ + User: user, + Service: serviceSSH, + Method: "publickey", + HasSig: false, + Algoname: key.Type(), + PubKey: pubKey, + } + if err := c.writePacket(Marshal(&msg)); err != nil { + return false, err + } + + return confirmKeyAck(key, c) +} + +func confirmKeyAck(key PublicKey, c packetConn) (bool, error) { + pubKey := key.Marshal() + algoname := key.Type() + + for { + packet, err := c.readPacket() + if err != nil { + return false, err + } + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return false, err + } + case msgUserAuthPubKeyOk: + var msg userAuthPubKeyOkMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, err + } + if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) { + return false, nil + } + return true, nil + case msgUserAuthFailure: + return false, nil + default: + return false, unexpectedMessageError(msgUserAuthSuccess, packet[0]) + } + } +} + +// PublicKeys returns an AuthMethod that uses the given key +// pairs. +func PublicKeys(signers ...Signer) AuthMethod { + return publicKeyCallback(func() ([]Signer, error) { return signers, nil }) +} + +// PublicKeysCallback returns an AuthMethod that runs the given +// function to obtain a list of key pairs. +func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod { + return publicKeyCallback(getSigners) +} + +// handleAuthResponse returns whether the preceding authentication request succeeded +// along with a list of remaining authentication methods to try next and +// an error if an unexpected response was received. +func handleAuthResponse(c packetConn) (bool, []string, error) { + for { + packet, err := c.readPacket() + if err != nil { + return false, nil, err + } + + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return false, nil, err + } + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, nil, err + } + return false, msg.Methods, nil + case msgUserAuthSuccess: + return true, nil, nil + default: + return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0]) + } + } +} + +func handleBannerResponse(c packetConn, packet []byte) error { + var msg userAuthBannerMsg + if err := Unmarshal(packet, &msg); err != nil { + return err + } + + transport, ok := c.(*handshakeTransport) + if !ok { + return nil + } + + if transport.bannerCallback != nil { + return transport.bannerCallback(msg.Message) + } + + return nil +} + +// KeyboardInteractiveChallenge should print questions, optionally +// disabling echoing (e.g. for passwords), and return all the answers. +// Challenge may be called multiple times in a single session. After +// successful authentication, the server may send a challenge with no +// questions, for which the user and instruction messages should be +// printed. RFC 4256 section 3.3 details how the UI should behave for +// both CLI and GUI environments. +type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error) + +// KeyboardInteractive returns an AuthMethod using a prompt/response +// sequence controlled by the server. +func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod { + return challenge +} + +func (cb KeyboardInteractiveChallenge) method() string { + return "keyboard-interactive" +} + +func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { + type initiateMsg struct { + User string `sshtype:"50"` + Service string + Method string + Language string + Submethods string + } + + if err := c.writePacket(Marshal(&initiateMsg{ + User: user, + Service: serviceSSH, + Method: "keyboard-interactive", + })); err != nil { + return false, nil, err + } + + for { + packet, err := c.readPacket() + if err != nil { + return false, nil, err + } + + // like handleAuthResponse, but with less options. + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return false, nil, err + } + continue + case msgUserAuthInfoRequest: + // OK + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, nil, err + } + return false, msg.Methods, nil + case msgUserAuthSuccess: + return true, nil, nil + default: + return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) + } + + var msg userAuthInfoRequestMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, nil, err + } + + // Manually unpack the prompt/echo pairs. + rest := msg.Prompts + var prompts []string + var echos []bool + for i := 0; i < int(msg.NumPrompts); i++ { + prompt, r, ok := parseString(rest) + if !ok || len(r) == 0 { + return false, nil, errors.New("ssh: prompt format error") + } + prompts = append(prompts, string(prompt)) + echos = append(echos, r[0] != 0) + rest = r[1:] + } + + if len(rest) != 0 { + return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs") + } + + answers, err := cb(msg.User, msg.Instruction, prompts, echos) + if err != nil { + return false, nil, err + } + + if len(answers) != len(prompts) { + return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback") + } + responseLength := 1 + 4 + for _, a := range answers { + responseLength += stringLength(len(a)) + } + serialized := make([]byte, responseLength) + p := serialized + p[0] = msgUserAuthInfoResponse + p = p[1:] + p = marshalUint32(p, uint32(len(answers))) + for _, a := range answers { + p = marshalString(p, []byte(a)) + } + + if err := c.writePacket(serialized); err != nil { + return false, nil, err + } + } +} + +type retryableAuthMethod struct { + authMethod AuthMethod + maxTries int +} + +func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok bool, methods []string, err error) { + for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ { + ok, methods, err = r.authMethod.auth(session, user, c, rand) + if ok || err != nil { // either success or error terminate + return ok, methods, err + } + } + return ok, methods, err +} + +func (r *retryableAuthMethod) method() string { + return r.authMethod.method() +} + +// RetryableAuthMethod is a decorator for other auth methods enabling them to +// be retried up to maxTries before considering that AuthMethod itself failed. +// If maxTries is <= 0, will retry indefinitely +// +// This is useful for interactive clients using challenge/response type +// authentication (e.g. Keyboard-Interactive, Password, etc) where the user +// could mistype their response resulting in the server issuing a +// SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4 +// [keyboard-interactive]); Without this decorator, the non-retryable +// AuthMethod would be removed from future consideration, and never tried again +// (and so the user would never be able to retry their entry). +func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod { + return &retryableAuthMethod{authMethod: auth, maxTries: maxTries} +} diff --git a/vendor/golang.org/x/crypto/ssh/client_auth_test.go b/vendor/golang.org/x/crypto/ssh/client_auth_test.go new file mode 100644 index 0000000..145b57a --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/client_auth_test.go @@ -0,0 +1,628 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/rand" + "errors" + "fmt" + "os" + "strings" + "testing" +) + +type keyboardInteractive map[string]string + +func (cr keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) { + var answers []string + for _, q := range questions { + answers = append(answers, cr[q]) + } + return answers, nil +} + +// reused internally by tests +var clientPassword = "tiger" + +// tryAuth runs a handshake with a given config against an SSH server +// with config serverConfig +func tryAuth(t *testing.T, config *ClientConfig) error { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + certChecker := CertChecker{ + IsUserAuthority: func(k PublicKey) bool { + return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal()) + }, + UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) { + if conn.User() == "testuser" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) { + return nil, nil + } + + return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User()) + }, + IsRevoked: func(c *Certificate) bool { + return c.Serial == 666 + }, + } + + serverConfig := &ServerConfig{ + PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) { + if conn.User() == "testuser" && string(pass) == clientPassword { + return nil, nil + } + return nil, errors.New("password auth failed") + }, + PublicKeyCallback: certChecker.Authenticate, + KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) { + ans, err := challenge("user", + "instruction", + []string{"question1", "question2"}, + []bool{true, true}) + if err != nil { + return nil, err + } + ok := conn.User() == "testuser" && ans[0] == "answer1" && ans[1] == "answer2" + if ok { + challenge("user", "motd", nil, nil) + return nil, nil + } + return nil, errors.New("keyboard-interactive failed") + }, + } + serverConfig.AddHostKey(testSigners["rsa"]) + + go newServer(c1, serverConfig) + _, _, _, err = NewClientConn(c2, "", config) + return err +} + +func TestClientAuthPublicKey(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +func TestAuthMethodPassword(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + Password(clientPassword), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +func TestAuthMethodFallback(t *testing.T) { + var passwordCalled bool + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + PasswordCallback( + func() (string, error) { + passwordCalled = true + return "WRONG", nil + }), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } + + if passwordCalled { + t.Errorf("password auth tried before public-key auth.") + } +} + +func TestAuthMethodWrongPassword(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + Password("wrong"), + PublicKeys(testSigners["rsa"]), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +func TestAuthMethodKeyboardInteractive(t *testing.T) { + answers := keyboardInteractive(map[string]string{ + "question1": "answer1", + "question2": "answer2", + }) + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + KeyboardInteractive(answers.Challenge), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +func TestAuthMethodWrongKeyboardInteractive(t *testing.T) { + answers := keyboardInteractive(map[string]string{ + "question1": "answer1", + "question2": "WRONG", + }) + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + KeyboardInteractive(answers.Challenge), + }, + } + + if err := tryAuth(t, config); err == nil { + t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive") + } +} + +// the mock server will only authenticate ssh-rsa keys +func TestAuthMethodInvalidPublicKey(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["dsa"]), + }, + } + + if err := tryAuth(t, config); err == nil { + t.Fatalf("dsa private key should not have authenticated with rsa public key") + } +} + +// the client should authenticate with the second key +func TestAuthMethodRSAandDSA(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["dsa"], testSigners["rsa"]), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if err := tryAuth(t, config); err != nil { + t.Fatalf("client could not authenticate with rsa key: %v", err) + } +} + +func TestClientHMAC(t *testing.T) { + for _, mac := range supportedMACs { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + }, + Config: Config{ + MACs: []string{mac}, + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if err := tryAuth(t, config); err != nil { + t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err) + } + } +} + +// issue 4285. +func TestClientUnsupportedCipher(t *testing.T) { + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(), + }, + Config: Config{ + Ciphers: []string{"aes128-cbc"}, // not currently supported + }, + } + if err := tryAuth(t, config); err == nil { + t.Errorf("expected no ciphers in common") + } +} + +func TestClientUnsupportedKex(t *testing.T) { + if os.Getenv("GO_BUILDER_NAME") != "" { + t.Skip("skipping known-flaky test on the Go build dashboard; see golang.org/issue/15198") + } + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(), + }, + Config: Config{ + KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") { + t.Errorf("got %v, expected 'common algorithm'", err) + } +} + +func TestClientLoginCert(t *testing.T) { + cert := &Certificate{ + Key: testPublicKeys["rsa"], + ValidBefore: CertTimeInfinity, + CertType: UserCert, + } + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + certSigner, err := NewCertSigner(cert, testSigners["rsa"]) + if err != nil { + t.Fatalf("NewCertSigner: %v", err) + } + + clientConfig := &ClientConfig{ + User: "user", + HostKeyCallback: InsecureIgnoreHostKey(), + } + clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner)) + + // should succeed + if err := tryAuth(t, clientConfig); err != nil { + t.Errorf("cert login failed: %v", err) + } + + // corrupted signature + cert.Signature.Blob[0]++ + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with corrupted sig") + } + + // revoked + cert.Serial = 666 + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("revoked cert login succeeded") + } + cert.Serial = 1 + + // sign with wrong key + cert.SignCert(rand.Reader, testSigners["dsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with non-authoritative key") + } + + // host cert + cert.CertType = HostCert + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with wrong type") + } + cert.CertType = UserCert + + // principal specified + cert.ValidPrincipals = []string{"user"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err != nil { + t.Errorf("cert login failed: %v", err) + } + + // wrong principal specified + cert.ValidPrincipals = []string{"fred"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with wrong principal") + } + cert.ValidPrincipals = nil + + // added critical option + cert.CriticalOptions = map[string]string{"root-access": "yes"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login passed with unrecognized critical option") + } + + // allowed source address + cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24,::42/120"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err != nil { + t.Errorf("cert login with source-address failed: %v", err) + } + + // disallowed source address + cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42,::42"} + cert.SignCert(rand.Reader, testSigners["ecdsa"]) + if err := tryAuth(t, clientConfig); err == nil { + t.Errorf("cert login with source-address succeeded") + } +} + +func testPermissionsPassing(withPermissions bool, t *testing.T) { + serverConfig := &ServerConfig{ + PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) { + if conn.User() == "nopermissions" { + return nil, nil + } + return &Permissions{}, nil + }, + } + serverConfig.AddHostKey(testSigners["rsa"]) + + clientConfig := &ClientConfig{ + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if withPermissions { + clientConfig.User = "permissions" + } else { + clientConfig.User = "nopermissions" + } + + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go NewClientConn(c2, "", clientConfig) + serverConn, err := newServer(c1, serverConfig) + if err != nil { + t.Fatal(err) + } + if p := serverConn.Permissions; (p != nil) != withPermissions { + t.Fatalf("withPermissions is %t, but Permissions object is %#v", withPermissions, p) + } +} + +func TestPermissionsPassing(t *testing.T) { + testPermissionsPassing(true, t) +} + +func TestNoPermissionsPassing(t *testing.T) { + testPermissionsPassing(false, t) +} + +func TestRetryableAuth(t *testing.T) { + n := 0 + passwords := []string{"WRONG1", "WRONG2"} + + config := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + RetryableAuthMethod(PasswordCallback(func() (string, error) { + p := passwords[n] + n++ + return p, nil + }), 2), + PublicKeys(testSigners["rsa"]), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } + if n != 2 { + t.Fatalf("Did not try all passwords") + } +} + +func ExampleRetryableAuthMethod(t *testing.T) { + user := "testuser" + NumberOfPrompts := 3 + + // Normally this would be a callback that prompts the user to answer the + // provided questions + Cb := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) { + return []string{"answer1", "answer2"}, nil + } + + config := &ClientConfig{ + HostKeyCallback: InsecureIgnoreHostKey(), + User: user, + Auth: []AuthMethod{ + RetryableAuthMethod(KeyboardInteractiveChallenge(Cb), NumberOfPrompts), + }, + } + + if err := tryAuth(t, config); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } +} + +// Test if username is received on server side when NoClientAuth is used +func TestClientAuthNone(t *testing.T) { + user := "testuser" + serverConfig := &ServerConfig{ + NoClientAuth: true, + } + serverConfig.AddHostKey(testSigners["rsa"]) + + clientConfig := &ClientConfig{ + User: user, + HostKeyCallback: InsecureIgnoreHostKey(), + } + + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go NewClientConn(c2, "", clientConfig) + serverConn, err := newServer(c1, serverConfig) + if err != nil { + t.Fatalf("newServer: %v", err) + } + if serverConn.User() != user { + t.Fatalf("server: got %q, want %q", serverConn.User(), user) + } +} + +// Test if authentication attempts are limited on server when MaxAuthTries is set +func TestClientAuthMaxAuthTries(t *testing.T) { + user := "testuser" + + serverConfig := &ServerConfig{ + MaxAuthTries: 2, + PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) { + if conn.User() == "testuser" && string(pass) == "right" { + return nil, nil + } + return nil, errors.New("password auth failed") + }, + } + serverConfig.AddHostKey(testSigners["rsa"]) + + expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{ + Reason: 2, + Message: "too many authentication failures", + }) + + for tries := 2; tries < 4; tries++ { + n := tries + clientConfig := &ClientConfig{ + User: user, + Auth: []AuthMethod{ + RetryableAuthMethod(PasswordCallback(func() (string, error) { + n-- + if n == 0 { + return "right", nil + } + return "wrong", nil + }), tries), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go newServer(c1, serverConfig) + _, _, _, err = NewClientConn(c2, "", clientConfig) + if tries > 2 { + if err == nil { + t.Fatalf("client: got no error, want %s", expectedErr) + } else if err.Error() != expectedErr.Error() { + t.Fatalf("client: got %s, want %s", err, expectedErr) + } + } else { + if err != nil { + t.Fatalf("client: got %s, want no error", err) + } + } + } +} + +// Test if authentication attempts are correctly limited on server +// when more public keys are provided then MaxAuthTries +func TestClientAuthMaxAuthTriesPublicKey(t *testing.T) { + signers := []Signer{} + for i := 0; i < 6; i++ { + signers = append(signers, testSigners["dsa"]) + } + + validConfig := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(append([]Signer{testSigners["rsa"]}, signers...)...), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if err := tryAuth(t, validConfig); err != nil { + t.Fatalf("unable to dial remote side: %s", err) + } + + expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{ + Reason: 2, + Message: "too many authentication failures", + }) + invalidConfig := &ClientConfig{ + User: "testuser", + Auth: []AuthMethod{ + PublicKeys(append(signers, testSigners["rsa"])...), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if err := tryAuth(t, invalidConfig); err == nil { + t.Fatalf("client: got no error, want %s", expectedErr) + } else if err.Error() != expectedErr.Error() { + t.Fatalf("client: got %s, want %s", err, expectedErr) + } +} + +// Test whether authentication errors are being properly logged if all +// authentication methods have been exhausted +func TestClientAuthErrorList(t *testing.T) { + publicKeyErr := errors.New("This is an error from PublicKeyCallback") + + clientConfig := &ClientConfig{ + Auth: []AuthMethod{ + PublicKeys(testSigners["rsa"]), + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + serverConfig := &ServerConfig{ + PublicKeyCallback: func(_ ConnMetadata, _ PublicKey) (*Permissions, error) { + return nil, publicKeyErr + }, + } + serverConfig.AddHostKey(testSigners["rsa"]) + + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go NewClientConn(c2, "", clientConfig) + _, err = newServer(c1, serverConfig) + if err == nil { + t.Fatal("newServer: got nil, expected errors") + } + + authErrs, ok := err.(*ServerAuthError) + if !ok { + t.Fatalf("errors: got %T, want *ssh.ServerAuthError", err) + } + for i, e := range authErrs.Errors { + switch i { + case 0: + if e.Error() != "no auth passed yet" { + t.Fatalf("errors: got %v, want no auth passed yet", e.Error()) + } + case 1: + if e != publicKeyErr { + t.Fatalf("errors: got %v, want %v", e, publicKeyErr) + } + default: + t.Fatalf("errors: got %v, expected 2 errors", authErrs.Errors) + } + } +} diff --git a/vendor/golang.org/x/crypto/ssh/client_test.go b/vendor/golang.org/x/crypto/ssh/client_test.go new file mode 100644 index 0000000..81f9599 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/client_test.go @@ -0,0 +1,166 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "strings" + "testing" +) + +func TestClientVersion(t *testing.T) { + for _, tt := range []struct { + name string + version string + multiLine string + wantErr bool + }{ + { + name: "default version", + version: packageVersion, + }, + { + name: "custom version", + version: "SSH-2.0-CustomClientVersionString", + }, + { + name: "good multi line version", + version: packageVersion, + multiLine: strings.Repeat("ignored\r\n", 20), + }, + { + name: "bad multi line version", + version: packageVersion, + multiLine: "bad multi line version", + wantErr: true, + }, + { + name: "long multi line version", + version: packageVersion, + multiLine: strings.Repeat("long multi line version\r\n", 50)[:256], + wantErr: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + go func() { + if tt.multiLine != "" { + c1.Write([]byte(tt.multiLine)) + } + NewClientConn(c1, "", &ClientConfig{ + ClientVersion: tt.version, + HostKeyCallback: InsecureIgnoreHostKey(), + }) + c1.Close() + }() + conf := &ServerConfig{NoClientAuth: true} + conf.AddHostKey(testSigners["rsa"]) + conn, _, _, err := NewServerConn(c2, conf) + if err == nil == tt.wantErr { + t.Fatalf("got err %v; wantErr %t", err, tt.wantErr) + } + if tt.wantErr { + // Don't verify the version on an expected error. + return + } + if got := string(conn.ClientVersion()); got != tt.version { + t.Fatalf("got %q; want %q", got, tt.version) + } + }) + } +} + +func TestHostKeyCheck(t *testing.T) { + for _, tt := range []struct { + name string + wantError string + key PublicKey + }{ + {"no callback", "must specify HostKeyCallback", nil}, + {"correct key", "", testSigners["rsa"].PublicKey()}, + {"mismatch", "mismatch", testSigners["ecdsa"].PublicKey()}, + } { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + serverConf := &ServerConfig{ + NoClientAuth: true, + } + serverConf.AddHostKey(testSigners["rsa"]) + + go NewServerConn(c1, serverConf) + clientConf := ClientConfig{ + User: "user", + } + if tt.key != nil { + clientConf.HostKeyCallback = FixedHostKey(tt.key) + } + + _, _, _, err = NewClientConn(c2, "", &clientConf) + if err != nil { + if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) { + t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError) + } + } else if tt.wantError != "" { + t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError) + } + } +} + +func TestBannerCallback(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + serverConf := &ServerConfig{ + PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) { + return &Permissions{}, nil + }, + BannerCallback: func(conn ConnMetadata) string { + return "Hello World" + }, + } + serverConf.AddHostKey(testSigners["rsa"]) + go NewServerConn(c1, serverConf) + + var receivedBanner string + var bannerCount int + clientConf := ClientConfig{ + Auth: []AuthMethod{ + Password("123"), + }, + User: "user", + HostKeyCallback: InsecureIgnoreHostKey(), + BannerCallback: func(message string) error { + bannerCount++ + receivedBanner = message + return nil + }, + } + + _, _, _, err = NewClientConn(c2, "", &clientConf) + if err != nil { + t.Fatal(err) + } + + if bannerCount != 1 { + t.Errorf("got %d banners; want 1", bannerCount) + } + + expected := "Hello World" + if receivedBanner != expected { + t.Fatalf("got %s; want %s", receivedBanner, expected) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/common.go b/vendor/golang.org/x/crypto/ssh/common.go new file mode 100644 index 0000000..77f9c5b --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/common.go @@ -0,0 +1,374 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto" + "crypto/rand" + "fmt" + "io" + "math" + "sync" + + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" +) + +// These are string constants in the SSH protocol. +const ( + compressionNone = "none" + serviceUserAuth = "ssh-userauth" + serviceSSH = "ssh-connection" +) + +// supportedCiphers specifies the supported ciphers in preference order. +var supportedCiphers = []string{ + "aes128-ctr", "aes192-ctr", "aes256-ctr", + "aes128-gcm@openssh.com", + chacha20Poly1305ID, + "arcfour256", "arcfour128", +} + +// supportedKexAlgos specifies the supported key-exchange algorithms in +// preference order. +var supportedKexAlgos = []string{ + kexAlgoCurve25519SHA256, + // P384 and P521 are not constant-time yet, but since we don't + // reuse ephemeral keys, using them for ECDH should be OK. + kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521, + kexAlgoDH14SHA1, kexAlgoDH1SHA1, +} + +// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods +// of authenticating servers) in preference order. +var supportedHostKeyAlgos = []string{ + CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, + CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01, + + KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, + KeyAlgoRSA, KeyAlgoDSA, + + KeyAlgoED25519, +} + +// supportedMACs specifies a default set of MAC algorithms in preference order. +// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed +// because they have reached the end of their useful life. +var supportedMACs = []string{ + "hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96", +} + +var supportedCompressions = []string{compressionNone} + +// hashFuncs keeps the mapping of supported algorithms to their respective +// hashes needed for signature verification. +var hashFuncs = map[string]crypto.Hash{ + KeyAlgoRSA: crypto.SHA1, + KeyAlgoDSA: crypto.SHA1, + KeyAlgoECDSA256: crypto.SHA256, + KeyAlgoECDSA384: crypto.SHA384, + KeyAlgoECDSA521: crypto.SHA512, + CertAlgoRSAv01: crypto.SHA1, + CertAlgoDSAv01: crypto.SHA1, + CertAlgoECDSA256v01: crypto.SHA256, + CertAlgoECDSA384v01: crypto.SHA384, + CertAlgoECDSA521v01: crypto.SHA512, +} + +// unexpectedMessageError results when the SSH message that we received didn't +// match what we wanted. +func unexpectedMessageError(expected, got uint8) error { + return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected) +} + +// parseError results from a malformed SSH message. +func parseError(tag uint8) error { + return fmt.Errorf("ssh: parse error in message type %d", tag) +} + +func findCommon(what string, client []string, server []string) (common string, err error) { + for _, c := range client { + for _, s := range server { + if c == s { + return c, nil + } + } + } + return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server) +} + +type directionAlgorithms struct { + Cipher string + MAC string + Compression string +} + +// rekeyBytes returns a rekeying intervals in bytes. +func (a *directionAlgorithms) rekeyBytes() int64 { + // According to RFC4344 block ciphers should rekey after + // 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is + // 128. + switch a.Cipher { + case "aes128-ctr", "aes192-ctr", "aes256-ctr", gcmCipherID, aes128cbcID: + return 16 * (1 << 32) + + } + + // For others, stick with RFC4253 recommendation to rekey after 1 Gb of data. + return 1 << 30 +} + +type algorithms struct { + kex string + hostKey string + w directionAlgorithms + r directionAlgorithms +} + +func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) { + result := &algorithms{} + + result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos) + if err != nil { + return + } + + result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos) + if err != nil { + return + } + + result.w.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer) + if err != nil { + return + } + + result.r.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient) + if err != nil { + return + } + + result.w.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer) + if err != nil { + return + } + + result.r.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient) + if err != nil { + return + } + + result.w.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer) + if err != nil { + return + } + + result.r.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient) + if err != nil { + return + } + + return result, nil +} + +// If rekeythreshold is too small, we can't make any progress sending +// stuff. +const minRekeyThreshold uint64 = 256 + +// Config contains configuration data common to both ServerConfig and +// ClientConfig. +type Config struct { + // Rand provides the source of entropy for cryptographic + // primitives. If Rand is nil, the cryptographic random reader + // in package crypto/rand will be used. + Rand io.Reader + + // The maximum number of bytes sent or received after which a + // new key is negotiated. It must be at least 256. If + // unspecified, a size suitable for the chosen cipher is used. + RekeyThreshold uint64 + + // The allowed key exchanges algorithms. If unspecified then a + // default set of algorithms is used. + KeyExchanges []string + + // The allowed cipher algorithms. If unspecified then a sensible + // default is used. + Ciphers []string + + // The allowed MAC algorithms. If unspecified then a sensible default + // is used. + MACs []string +} + +// SetDefaults sets sensible values for unset fields in config. This is +// exported for testing: Configs passed to SSH functions are copied and have +// default values set automatically. +func (c *Config) SetDefaults() { + if c.Rand == nil { + c.Rand = rand.Reader + } + if c.Ciphers == nil { + c.Ciphers = supportedCiphers + } + var ciphers []string + for _, c := range c.Ciphers { + if cipherModes[c] != nil { + // reject the cipher if we have no cipherModes definition + ciphers = append(ciphers, c) + } + } + c.Ciphers = ciphers + + if c.KeyExchanges == nil { + c.KeyExchanges = supportedKexAlgos + } + + if c.MACs == nil { + c.MACs = supportedMACs + } + + if c.RekeyThreshold == 0 { + // cipher specific default + } else if c.RekeyThreshold < minRekeyThreshold { + c.RekeyThreshold = minRekeyThreshold + } else if c.RekeyThreshold >= math.MaxInt64 { + // Avoid weirdness if somebody uses -1 as a threshold. + c.RekeyThreshold = math.MaxInt64 + } +} + +// buildDataSignedForAuth returns the data that is signed in order to prove +// possession of a private key. See RFC 4252, section 7. +func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte { + data := struct { + Session []byte + Type byte + User string + Service string + Method string + Sign bool + Algo []byte + PubKey []byte + }{ + sessionID, + msgUserAuthRequest, + req.User, + req.Service, + req.Method, + true, + algo, + pubKey, + } + return Marshal(data) +} + +func appendU16(buf []byte, n uint16) []byte { + return append(buf, byte(n>>8), byte(n)) +} + +func appendU32(buf []byte, n uint32) []byte { + return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) +} + +func appendU64(buf []byte, n uint64) []byte { + return append(buf, + byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32), + byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) +} + +func appendInt(buf []byte, n int) []byte { + return appendU32(buf, uint32(n)) +} + +func appendString(buf []byte, s string) []byte { + buf = appendU32(buf, uint32(len(s))) + buf = append(buf, s...) + return buf +} + +func appendBool(buf []byte, b bool) []byte { + if b { + return append(buf, 1) + } + return append(buf, 0) +} + +// newCond is a helper to hide the fact that there is no usable zero +// value for sync.Cond. +func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) } + +// window represents the buffer available to clients +// wishing to write to a channel. +type window struct { + *sync.Cond + win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1 + writeWaiters int + closed bool +} + +// add adds win to the amount of window available +// for consumers. +func (w *window) add(win uint32) bool { + // a zero sized window adjust is a noop. + if win == 0 { + return true + } + w.L.Lock() + if w.win+win < win { + w.L.Unlock() + return false + } + w.win += win + // It is unusual that multiple goroutines would be attempting to reserve + // window space, but not guaranteed. Use broadcast to notify all waiters + // that additional window is available. + w.Broadcast() + w.L.Unlock() + return true +} + +// close sets the window to closed, so all reservations fail +// immediately. +func (w *window) close() { + w.L.Lock() + w.closed = true + w.Broadcast() + w.L.Unlock() +} + +// reserve reserves win from the available window capacity. +// If no capacity remains, reserve will block. reserve may +// return less than requested. +func (w *window) reserve(win uint32) (uint32, error) { + var err error + w.L.Lock() + w.writeWaiters++ + w.Broadcast() + for w.win == 0 && !w.closed { + w.Wait() + } + w.writeWaiters-- + if w.win < win { + win = w.win + } + w.win -= win + if w.closed { + err = io.EOF + } + w.L.Unlock() + return win, err +} + +// waitWriterBlocked waits until some goroutine is blocked for further +// writes. It is used in tests only. +func (w *window) waitWriterBlocked() { + w.Cond.L.Lock() + for w.writeWaiters == 0 { + w.Cond.Wait() + } + w.Cond.L.Unlock() +} diff --git a/vendor/golang.org/x/crypto/ssh/connection.go b/vendor/golang.org/x/crypto/ssh/connection.go new file mode 100644 index 0000000..fd6b068 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/connection.go @@ -0,0 +1,143 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "fmt" + "net" +) + +// OpenChannelError is returned if the other side rejects an +// OpenChannel request. +type OpenChannelError struct { + Reason RejectionReason + Message string +} + +func (e *OpenChannelError) Error() string { + return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message) +} + +// ConnMetadata holds metadata for the connection. +type ConnMetadata interface { + // User returns the user ID for this connection. + User() string + + // SessionID returns the session hash, also denoted by H. + SessionID() []byte + + // ClientVersion returns the client's version string as hashed + // into the session ID. + ClientVersion() []byte + + // ServerVersion returns the server's version string as hashed + // into the session ID. + ServerVersion() []byte + + // RemoteAddr returns the remote address for this connection. + RemoteAddr() net.Addr + + // LocalAddr returns the local address for this connection. + LocalAddr() net.Addr +} + +// Conn represents an SSH connection for both server and client roles. +// Conn is the basis for implementing an application layer, such +// as ClientConn, which implements the traditional shell access for +// clients. +type Conn interface { + ConnMetadata + + // SendRequest sends a global request, and returns the + // reply. If wantReply is true, it returns the response status + // and payload. See also RFC4254, section 4. + SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) + + // OpenChannel tries to open an channel. If the request is + // rejected, it returns *OpenChannelError. On success it returns + // the SSH Channel and a Go channel for incoming, out-of-band + // requests. The Go channel must be serviced, or the + // connection will hang. + OpenChannel(name string, data []byte) (Channel, <-chan *Request, error) + + // Close closes the underlying network connection + Close() error + + // Wait blocks until the connection has shut down, and returns the + // error causing the shutdown. + Wait() error + + // TODO(hanwen): consider exposing: + // RequestKeyChange + // Disconnect +} + +// DiscardRequests consumes and rejects all requests from the +// passed-in channel. +func DiscardRequests(in <-chan *Request) { + for req := range in { + if req.WantReply { + req.Reply(false, nil) + } + } +} + +// A connection represents an incoming connection. +type connection struct { + transport *handshakeTransport + sshConn + + // The connection protocol. + *mux +} + +func (c *connection) Close() error { + return c.sshConn.conn.Close() +} + +// sshconn provides net.Conn metadata, but disallows direct reads and +// writes. +type sshConn struct { + conn net.Conn + + user string + sessionID []byte + clientVersion []byte + serverVersion []byte +} + +func dup(src []byte) []byte { + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} + +func (c *sshConn) User() string { + return c.user +} + +func (c *sshConn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +func (c *sshConn) Close() error { + return c.conn.Close() +} + +func (c *sshConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *sshConn) SessionID() []byte { + return dup(c.sessionID) +} + +func (c *sshConn) ClientVersion() []byte { + return dup(c.clientVersion) +} + +func (c *sshConn) ServerVersion() []byte { + return dup(c.serverVersion) +} diff --git a/vendor/golang.org/x/crypto/ssh/doc.go b/vendor/golang.org/x/crypto/ssh/doc.go new file mode 100644 index 0000000..67b7322 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/doc.go @@ -0,0 +1,21 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package ssh implements an SSH client and server. + +SSH is a transport security protocol, an authentication protocol and a +family of application protocols. The most typical application level +protocol is a remote shell and this is specifically implemented. However, +the multiplexed nature of SSH is exposed to users that wish to support +others. + +References: + [PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD + [SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1 + +This package does not fall under the stability promise of the Go language itself, +so its API may be changed when pressing needs arise. +*/ +package ssh // import "golang.org/x/crypto/ssh" diff --git a/vendor/golang.org/x/crypto/ssh/example_test.go b/vendor/golang.org/x/crypto/ssh/example_test.go new file mode 100644 index 0000000..b910c7b --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/example_test.go @@ -0,0 +1,320 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh_test + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "os" + "path/filepath" + "strings" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/terminal" +) + +func ExampleNewServerConn() { + // Public key authentication is done by comparing + // the public key of a received connection + // with the entries in the authorized_keys file. + authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys") + if err != nil { + log.Fatalf("Failed to load authorized_keys, err: %v", err) + } + + authorizedKeysMap := map[string]bool{} + for len(authorizedKeysBytes) > 0 { + pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes) + if err != nil { + log.Fatal(err) + } + + authorizedKeysMap[string(pubKey.Marshal())] = true + authorizedKeysBytes = rest + } + + // An SSH server is represented by a ServerConfig, which holds + // certificate details and handles authentication of ServerConns. + config := &ssh.ServerConfig{ + // Remove to disable password auth. + PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { + // Should use constant-time compare (or better, salt+hash) in + // a production setting. + if c.User() == "testuser" && string(pass) == "tiger" { + return nil, nil + } + return nil, fmt.Errorf("password rejected for %q", c.User()) + }, + + // Remove to disable public key auth. + PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { + if authorizedKeysMap[string(pubKey.Marshal())] { + return &ssh.Permissions{ + // Record the public key used for authentication. + Extensions: map[string]string{ + "pubkey-fp": ssh.FingerprintSHA256(pubKey), + }, + }, nil + } + return nil, fmt.Errorf("unknown public key for %q", c.User()) + }, + } + + privateBytes, err := ioutil.ReadFile("id_rsa") + if err != nil { + log.Fatal("Failed to load private key: ", err) + } + + private, err := ssh.ParsePrivateKey(privateBytes) + if err != nil { + log.Fatal("Failed to parse private key: ", err) + } + + config.AddHostKey(private) + + // Once a ServerConfig has been configured, connections can be + // accepted. + listener, err := net.Listen("tcp", "0.0.0.0:2022") + if err != nil { + log.Fatal("failed to listen for connection: ", err) + } + nConn, err := listener.Accept() + if err != nil { + log.Fatal("failed to accept incoming connection: ", err) + } + + // Before use, a handshake must be performed on the incoming + // net.Conn. + conn, chans, reqs, err := ssh.NewServerConn(nConn, config) + if err != nil { + log.Fatal("failed to handshake: ", err) + } + log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"]) + + // The incoming Request channel must be serviced. + go ssh.DiscardRequests(reqs) + + // Service the incoming Channel channel. + for newChannel := range chans { + // Channels have a type, depending on the application level + // protocol intended. In the case of a shell, the type is + // "session" and ServerShell may be used to present a simple + // terminal interface. + if newChannel.ChannelType() != "session" { + newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") + continue + } + channel, requests, err := newChannel.Accept() + if err != nil { + log.Fatalf("Could not accept channel: %v", err) + } + + // Sessions have out-of-band requests such as "shell", + // "pty-req" and "env". Here we handle only the + // "shell" request. + go func(in <-chan *ssh.Request) { + for req := range in { + req.Reply(req.Type == "shell", nil) + } + }(requests) + + term := terminal.NewTerminal(channel, "> ") + + go func() { + defer channel.Close() + for { + line, err := term.ReadLine() + if err != nil { + break + } + fmt.Println(line) + } + }() + } +} + +func ExampleHostKeyCheck() { + // Every client must provide a host key check. Here is a + // simple-minded parse of OpenSSH's known_hosts file + host := "hostname" + file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + var hostKey ssh.PublicKey + for scanner.Scan() { + fields := strings.Split(scanner.Text(), " ") + if len(fields) != 3 { + continue + } + if strings.Contains(fields[0], host) { + var err error + hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes()) + if err != nil { + log.Fatalf("error parsing %q: %v", fields[2], err) + } + break + } + } + + if hostKey == nil { + log.Fatalf("no hostkey for %s", host) + } + + config := ssh.ClientConfig{ + User: os.Getenv("USER"), + HostKeyCallback: ssh.FixedHostKey(hostKey), + } + + _, err = ssh.Dial("tcp", host+":22", &config) + log.Println(err) +} + +func ExampleDial() { + var hostKey ssh.PublicKey + // An SSH client is represented with a ClientConn. + // + // To authenticate with the remote server you must pass at least one + // implementation of AuthMethod via the Auth field in ClientConfig, + // and provide a HostKeyCallback. + config := &ssh.ClientConfig{ + User: "username", + Auth: []ssh.AuthMethod{ + ssh.Password("yourpassword"), + }, + HostKeyCallback: ssh.FixedHostKey(hostKey), + } + client, err := ssh.Dial("tcp", "yourserver.com:22", config) + if err != nil { + log.Fatal("Failed to dial: ", err) + } + + // Each ClientConn can support multiple interactive sessions, + // represented by a Session. + session, err := client.NewSession() + if err != nil { + log.Fatal("Failed to create session: ", err) + } + defer session.Close() + + // Once a Session is created, you can execute a single command on + // the remote side using the Run method. + var b bytes.Buffer + session.Stdout = &b + if err := session.Run("/usr/bin/whoami"); err != nil { + log.Fatal("Failed to run: " + err.Error()) + } + fmt.Println(b.String()) +} + +func ExamplePublicKeys() { + var hostKey ssh.PublicKey + // A public key may be used to authenticate against the remote + // server by using an unencrypted PEM-encoded private key file. + // + // If you have an encrypted private key, the crypto/x509 package + // can be used to decrypt it. + key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa") + if err != nil { + log.Fatalf("unable to read private key: %v", err) + } + + // Create the Signer for this private key. + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + log.Fatalf("unable to parse private key: %v", err) + } + + config := &ssh.ClientConfig{ + User: "user", + Auth: []ssh.AuthMethod{ + // Use the PublicKeys method for remote authentication. + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.FixedHostKey(hostKey), + } + + // Connect to the remote server and perform the SSH handshake. + client, err := ssh.Dial("tcp", "host.com:22", config) + if err != nil { + log.Fatalf("unable to connect: %v", err) + } + defer client.Close() +} + +func ExampleClient_Listen() { + var hostKey ssh.PublicKey + config := &ssh.ClientConfig{ + User: "username", + Auth: []ssh.AuthMethod{ + ssh.Password("password"), + }, + HostKeyCallback: ssh.FixedHostKey(hostKey), + } + // Dial your ssh server. + conn, err := ssh.Dial("tcp", "localhost:22", config) + if err != nil { + log.Fatal("unable to connect: ", err) + } + defer conn.Close() + + // Request the remote side to open port 8080 on all interfaces. + l, err := conn.Listen("tcp", "0.0.0.0:8080") + if err != nil { + log.Fatal("unable to register tcp forward: ", err) + } + defer l.Close() + + // Serve HTTP with your SSH server acting as a reverse proxy. + http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + fmt.Fprintf(resp, "Hello world!\n") + })) +} + +func ExampleSession_RequestPty() { + var hostKey ssh.PublicKey + // Create client config + config := &ssh.ClientConfig{ + User: "username", + Auth: []ssh.AuthMethod{ + ssh.Password("password"), + }, + HostKeyCallback: ssh.FixedHostKey(hostKey), + } + // Connect to ssh server + conn, err := ssh.Dial("tcp", "localhost:22", config) + if err != nil { + log.Fatal("unable to connect: ", err) + } + defer conn.Close() + // Create a session + session, err := conn.NewSession() + if err != nil { + log.Fatal("unable to create session: ", err) + } + defer session.Close() + // Set up terminal modes + modes := ssh.TerminalModes{ + ssh.ECHO: 0, // disable echoing + ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud + ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud + } + // Request pseudo terminal + if err := session.RequestPty("xterm", 40, 80, modes); err != nil { + log.Fatal("request for pseudo terminal failed: ", err) + } + // Start remote shell + if err := session.Shell(); err != nil { + log.Fatal("failed to start shell: ", err) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/handshake.go b/vendor/golang.org/x/crypto/ssh/handshake.go new file mode 100644 index 0000000..4f7912e --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/handshake.go @@ -0,0 +1,646 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto/rand" + "errors" + "fmt" + "io" + "log" + "net" + "sync" +) + +// debugHandshake, if set, prints messages sent and received. Key +// exchange messages are printed as if DH were used, so the debug +// messages are wrong when using ECDH. +const debugHandshake = false + +// chanSize sets the amount of buffering SSH connections. This is +// primarily for testing: setting chanSize=0 uncovers deadlocks more +// quickly. +const chanSize = 16 + +// keyingTransport is a packet based transport that supports key +// changes. It need not be thread-safe. It should pass through +// msgNewKeys in both directions. +type keyingTransport interface { + packetConn + + // prepareKeyChange sets up a key change. The key change for a + // direction will be effected if a msgNewKeys message is sent + // or received. + prepareKeyChange(*algorithms, *kexResult) error +} + +// handshakeTransport implements rekeying on top of a keyingTransport +// and offers a thread-safe writePacket() interface. +type handshakeTransport struct { + conn keyingTransport + config *Config + + serverVersion []byte + clientVersion []byte + + // hostKeys is non-empty if we are the server. In that case, + // it contains all host keys that can be used to sign the + // connection. + hostKeys []Signer + + // hostKeyAlgorithms is non-empty if we are the client. In that case, + // we accept these key types from the server as host key. + hostKeyAlgorithms []string + + // On read error, incoming is closed, and readError is set. + incoming chan []byte + readError error + + mu sync.Mutex + writeError error + sentInitPacket []byte + sentInitMsg *kexInitMsg + pendingPackets [][]byte // Used when a key exchange is in progress. + + // If the read loop wants to schedule a kex, it pings this + // channel, and the write loop will send out a kex + // message. + requestKex chan struct{} + + // If the other side requests or confirms a kex, its kexInit + // packet is sent here for the write loop to find it. + startKex chan *pendingKex + + // data for host key checking + hostKeyCallback HostKeyCallback + dialAddress string + remoteAddr net.Addr + + // bannerCallback is non-empty if we are the client and it has been set in + // ClientConfig. In that case it is called during the user authentication + // dance to handle a custom server's message. + bannerCallback BannerCallback + + // Algorithms agreed in the last key exchange. + algorithms *algorithms + + readPacketsLeft uint32 + readBytesLeft int64 + + writePacketsLeft uint32 + writeBytesLeft int64 + + // The session ID or nil if first kex did not complete yet. + sessionID []byte +} + +type pendingKex struct { + otherInit []byte + done chan error +} + +func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport { + t := &handshakeTransport{ + conn: conn, + serverVersion: serverVersion, + clientVersion: clientVersion, + incoming: make(chan []byte, chanSize), + requestKex: make(chan struct{}, 1), + startKex: make(chan *pendingKex, 1), + + config: config, + } + t.resetReadThresholds() + t.resetWriteThresholds() + + // We always start with a mandatory key exchange. + t.requestKex <- struct{}{} + return t +} + +func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ClientConfig, dialAddr string, addr net.Addr) *handshakeTransport { + t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) + t.dialAddress = dialAddr + t.remoteAddr = addr + t.hostKeyCallback = config.HostKeyCallback + t.bannerCallback = config.BannerCallback + if config.HostKeyAlgorithms != nil { + t.hostKeyAlgorithms = config.HostKeyAlgorithms + } else { + t.hostKeyAlgorithms = supportedHostKeyAlgos + } + go t.readLoop() + go t.kexLoop() + return t +} + +func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ServerConfig) *handshakeTransport { + t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) + t.hostKeys = config.hostKeys + go t.readLoop() + go t.kexLoop() + return t +} + +func (t *handshakeTransport) getSessionID() []byte { + return t.sessionID +} + +// waitSession waits for the session to be established. This should be +// the first thing to call after instantiating handshakeTransport. +func (t *handshakeTransport) waitSession() error { + p, err := t.readPacket() + if err != nil { + return err + } + if p[0] != msgNewKeys { + return fmt.Errorf("ssh: first packet should be msgNewKeys") + } + + return nil +} + +func (t *handshakeTransport) id() string { + if len(t.hostKeys) > 0 { + return "server" + } + return "client" +} + +func (t *handshakeTransport) printPacket(p []byte, write bool) { + action := "got" + if write { + action = "sent" + } + + if p[0] == msgChannelData || p[0] == msgChannelExtendedData { + log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p)) + } else { + msg, err := decode(p) + log.Printf("%s %s %T %v (%v)", t.id(), action, msg, msg, err) + } +} + +func (t *handshakeTransport) readPacket() ([]byte, error) { + p, ok := <-t.incoming + if !ok { + return nil, t.readError + } + return p, nil +} + +func (t *handshakeTransport) readLoop() { + first := true + for { + p, err := t.readOnePacket(first) + first = false + if err != nil { + t.readError = err + close(t.incoming) + break + } + if p[0] == msgIgnore || p[0] == msgDebug { + continue + } + t.incoming <- p + } + + // Stop writers too. + t.recordWriteError(t.readError) + + // Unblock the writer should it wait for this. + close(t.startKex) + + // Don't close t.requestKex; it's also written to from writePacket. +} + +func (t *handshakeTransport) pushPacket(p []byte) error { + if debugHandshake { + t.printPacket(p, true) + } + return t.conn.writePacket(p) +} + +func (t *handshakeTransport) getWriteError() error { + t.mu.Lock() + defer t.mu.Unlock() + return t.writeError +} + +func (t *handshakeTransport) recordWriteError(err error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.writeError == nil && err != nil { + t.writeError = err + } +} + +func (t *handshakeTransport) requestKeyExchange() { + select { + case t.requestKex <- struct{}{}: + default: + // something already requested a kex, so do nothing. + } +} + +func (t *handshakeTransport) resetWriteThresholds() { + t.writePacketsLeft = packetRekeyThreshold + if t.config.RekeyThreshold > 0 { + t.writeBytesLeft = int64(t.config.RekeyThreshold) + } else if t.algorithms != nil { + t.writeBytesLeft = t.algorithms.w.rekeyBytes() + } else { + t.writeBytesLeft = 1 << 30 + } +} + +func (t *handshakeTransport) kexLoop() { + +write: + for t.getWriteError() == nil { + var request *pendingKex + var sent bool + + for request == nil || !sent { + var ok bool + select { + case request, ok = <-t.startKex: + if !ok { + break write + } + case <-t.requestKex: + break + } + + if !sent { + if err := t.sendKexInit(); err != nil { + t.recordWriteError(err) + break + } + sent = true + } + } + + if err := t.getWriteError(); err != nil { + if request != nil { + request.done <- err + } + break + } + + // We're not servicing t.requestKex, but that is OK: + // we never block on sending to t.requestKex. + + // We're not servicing t.startKex, but the remote end + // has just sent us a kexInitMsg, so it can't send + // another key change request, until we close the done + // channel on the pendingKex request. + + err := t.enterKeyExchange(request.otherInit) + + t.mu.Lock() + t.writeError = err + t.sentInitPacket = nil + t.sentInitMsg = nil + + t.resetWriteThresholds() + + // we have completed the key exchange. Since the + // reader is still blocked, it is safe to clear out + // the requestKex channel. This avoids the situation + // where: 1) we consumed our own request for the + // initial kex, and 2) the kex from the remote side + // caused another send on the requestKex channel, + clear: + for { + select { + case <-t.requestKex: + // + default: + break clear + } + } + + request.done <- t.writeError + + // kex finished. Push packets that we received while + // the kex was in progress. Don't look at t.startKex + // and don't increment writtenSinceKex: if we trigger + // another kex while we are still busy with the last + // one, things will become very confusing. + for _, p := range t.pendingPackets { + t.writeError = t.pushPacket(p) + if t.writeError != nil { + break + } + } + t.pendingPackets = t.pendingPackets[:0] + t.mu.Unlock() + } + + // drain startKex channel. We don't service t.requestKex + // because nobody does blocking sends there. + go func() { + for init := range t.startKex { + init.done <- t.writeError + } + }() + + // Unblock reader. + t.conn.Close() +} + +// The protocol uses uint32 for packet counters, so we can't let them +// reach 1<<32. We will actually read and write more packets than +// this, though: the other side may send more packets, and after we +// hit this limit on writing we will send a few more packets for the +// key exchange itself. +const packetRekeyThreshold = (1 << 31) + +func (t *handshakeTransport) resetReadThresholds() { + t.readPacketsLeft = packetRekeyThreshold + if t.config.RekeyThreshold > 0 { + t.readBytesLeft = int64(t.config.RekeyThreshold) + } else if t.algorithms != nil { + t.readBytesLeft = t.algorithms.r.rekeyBytes() + } else { + t.readBytesLeft = 1 << 30 + } +} + +func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) { + p, err := t.conn.readPacket() + if err != nil { + return nil, err + } + + if t.readPacketsLeft > 0 { + t.readPacketsLeft-- + } else { + t.requestKeyExchange() + } + + if t.readBytesLeft > 0 { + t.readBytesLeft -= int64(len(p)) + } else { + t.requestKeyExchange() + } + + if debugHandshake { + t.printPacket(p, false) + } + + if first && p[0] != msgKexInit { + return nil, fmt.Errorf("ssh: first packet should be msgKexInit") + } + + if p[0] != msgKexInit { + return p, nil + } + + firstKex := t.sessionID == nil + + kex := pendingKex{ + done: make(chan error, 1), + otherInit: p, + } + t.startKex <- &kex + err = <-kex.done + + if debugHandshake { + log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err) + } + + if err != nil { + return nil, err + } + + t.resetReadThresholds() + + // By default, a key exchange is hidden from higher layers by + // translating it into msgIgnore. + successPacket := []byte{msgIgnore} + if firstKex { + // sendKexInit() for the first kex waits for + // msgNewKeys so the authentication process is + // guaranteed to happen over an encrypted transport. + successPacket = []byte{msgNewKeys} + } + + return successPacket, nil +} + +// sendKexInit sends a key change message. +func (t *handshakeTransport) sendKexInit() error { + t.mu.Lock() + defer t.mu.Unlock() + if t.sentInitMsg != nil { + // kexInits may be sent either in response to the other side, + // or because our side wants to initiate a key change, so we + // may have already sent a kexInit. In that case, don't send a + // second kexInit. + return nil + } + + msg := &kexInitMsg{ + KexAlgos: t.config.KeyExchanges, + CiphersClientServer: t.config.Ciphers, + CiphersServerClient: t.config.Ciphers, + MACsClientServer: t.config.MACs, + MACsServerClient: t.config.MACs, + CompressionClientServer: supportedCompressions, + CompressionServerClient: supportedCompressions, + } + io.ReadFull(rand.Reader, msg.Cookie[:]) + + if len(t.hostKeys) > 0 { + for _, k := range t.hostKeys { + msg.ServerHostKeyAlgos = append( + msg.ServerHostKeyAlgos, k.PublicKey().Type()) + } + } else { + msg.ServerHostKeyAlgos = t.hostKeyAlgorithms + } + packet := Marshal(msg) + + // writePacket destroys the contents, so save a copy. + packetCopy := make([]byte, len(packet)) + copy(packetCopy, packet) + + if err := t.pushPacket(packetCopy); err != nil { + return err + } + + t.sentInitMsg = msg + t.sentInitPacket = packet + + return nil +} + +func (t *handshakeTransport) writePacket(p []byte) error { + switch p[0] { + case msgKexInit: + return errors.New("ssh: only handshakeTransport can send kexInit") + case msgNewKeys: + return errors.New("ssh: only handshakeTransport can send newKeys") + } + + t.mu.Lock() + defer t.mu.Unlock() + if t.writeError != nil { + return t.writeError + } + + if t.sentInitMsg != nil { + // Copy the packet so the writer can reuse the buffer. + cp := make([]byte, len(p)) + copy(cp, p) + t.pendingPackets = append(t.pendingPackets, cp) + return nil + } + + if t.writeBytesLeft > 0 { + t.writeBytesLeft -= int64(len(p)) + } else { + t.requestKeyExchange() + } + + if t.writePacketsLeft > 0 { + t.writePacketsLeft-- + } else { + t.requestKeyExchange() + } + + if err := t.pushPacket(p); err != nil { + t.writeError = err + } + + return nil +} + +func (t *handshakeTransport) Close() error { + return t.conn.Close() +} + +func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error { + if debugHandshake { + log.Printf("%s entered key exchange", t.id()) + } + + otherInit := &kexInitMsg{} + if err := Unmarshal(otherInitPacket, otherInit); err != nil { + return err + } + + magics := handshakeMagics{ + clientVersion: t.clientVersion, + serverVersion: t.serverVersion, + clientKexInit: otherInitPacket, + serverKexInit: t.sentInitPacket, + } + + clientInit := otherInit + serverInit := t.sentInitMsg + if len(t.hostKeys) == 0 { + clientInit, serverInit = serverInit, clientInit + + magics.clientKexInit = t.sentInitPacket + magics.serverKexInit = otherInitPacket + } + + var err error + t.algorithms, err = findAgreedAlgorithms(clientInit, serverInit) + if err != nil { + return err + } + + // We don't send FirstKexFollows, but we handle receiving it. + // + // RFC 4253 section 7 defines the kex and the agreement method for + // first_kex_packet_follows. It states that the guessed packet + // should be ignored if the "kex algorithm and/or the host + // key algorithm is guessed wrong (server and client have + // different preferred algorithm), or if any of the other + // algorithms cannot be agreed upon". The other algorithms have + // already been checked above so the kex algorithm and host key + // algorithm are checked here. + if otherInit.FirstKexFollows && (clientInit.KexAlgos[0] != serverInit.KexAlgos[0] || clientInit.ServerHostKeyAlgos[0] != serverInit.ServerHostKeyAlgos[0]) { + // other side sent a kex message for the wrong algorithm, + // which we have to ignore. + if _, err := t.conn.readPacket(); err != nil { + return err + } + } + + kex, ok := kexAlgoMap[t.algorithms.kex] + if !ok { + return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.kex) + } + + var result *kexResult + if len(t.hostKeys) > 0 { + result, err = t.server(kex, t.algorithms, &magics) + } else { + result, err = t.client(kex, t.algorithms, &magics) + } + + if err != nil { + return err + } + + if t.sessionID == nil { + t.sessionID = result.H + } + result.SessionID = t.sessionID + + if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil { + return err + } + if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil { + return err + } + if packet, err := t.conn.readPacket(); err != nil { + return err + } else if packet[0] != msgNewKeys { + return unexpectedMessageError(msgNewKeys, packet[0]) + } + + return nil +} + +func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { + var hostKey Signer + for _, k := range t.hostKeys { + if algs.hostKey == k.PublicKey().Type() { + hostKey = k + } + } + + r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey) + return r, err +} + +func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { + result, err := kex.Client(t.conn, t.config.Rand, magics) + if err != nil { + return nil, err + } + + hostKey, err := ParsePublicKey(result.HostKey) + if err != nil { + return nil, err + } + + if err := verifyHostKeySignature(hostKey, result); err != nil { + return nil, err + } + + err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/vendor/golang.org/x/crypto/ssh/handshake_test.go b/vendor/golang.org/x/crypto/ssh/handshake_test.go new file mode 100644 index 0000000..91d4935 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/handshake_test.go @@ -0,0 +1,559 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/rand" + "errors" + "fmt" + "io" + "net" + "reflect" + "runtime" + "strings" + "sync" + "testing" +) + +type testChecker struct { + calls []string +} + +func (t *testChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error { + if dialAddr == "bad" { + return fmt.Errorf("dialAddr is bad") + } + + if tcpAddr, ok := addr.(*net.TCPAddr); !ok || tcpAddr == nil { + return fmt.Errorf("testChecker: got %T want *net.TCPAddr", addr) + } + + t.calls = append(t.calls, fmt.Sprintf("%s %v %s %x", dialAddr, addr, key.Type(), key.Marshal())) + + return nil +} + +// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and +// therefore is buffered (net.Pipe deadlocks if both sides start with +// a write.) +func netPipe() (net.Conn, net.Conn, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + listener, err = net.Listen("tcp", "[::1]:0") + if err != nil { + return nil, nil, err + } + } + defer listener.Close() + c1, err := net.Dial("tcp", listener.Addr().String()) + if err != nil { + return nil, nil, err + } + + c2, err := listener.Accept() + if err != nil { + c1.Close() + return nil, nil, err + } + + return c1, c2, nil +} + +// noiseTransport inserts ignore messages to check that the read loop +// and the key exchange filters out these messages. +type noiseTransport struct { + keyingTransport +} + +func (t *noiseTransport) writePacket(p []byte) error { + ignore := []byte{msgIgnore} + if err := t.keyingTransport.writePacket(ignore); err != nil { + return err + } + debug := []byte{msgDebug, 1, 2, 3} + if err := t.keyingTransport.writePacket(debug); err != nil { + return err + } + + return t.keyingTransport.writePacket(p) +} + +func addNoiseTransport(t keyingTransport) keyingTransport { + return &noiseTransport{t} +} + +// handshakePair creates two handshakeTransports connected with each +// other. If the noise argument is true, both transports will try to +// confuse the other side by sending ignore and debug messages. +func handshakePair(clientConf *ClientConfig, addr string, noise bool) (client *handshakeTransport, server *handshakeTransport, err error) { + a, b, err := netPipe() + if err != nil { + return nil, nil, err + } + + var trC, trS keyingTransport + + trC = newTransport(a, rand.Reader, true) + trS = newTransport(b, rand.Reader, false) + if noise { + trC = addNoiseTransport(trC) + trS = addNoiseTransport(trS) + } + clientConf.SetDefaults() + + v := []byte("version") + client = newClientTransport(trC, v, v, clientConf, addr, a.RemoteAddr()) + + serverConf := &ServerConfig{} + serverConf.AddHostKey(testSigners["ecdsa"]) + serverConf.AddHostKey(testSigners["rsa"]) + serverConf.SetDefaults() + server = newServerTransport(trS, v, v, serverConf) + + if err := server.waitSession(); err != nil { + return nil, nil, fmt.Errorf("server.waitSession: %v", err) + } + if err := client.waitSession(); err != nil { + return nil, nil, fmt.Errorf("client.waitSession: %v", err) + } + + return client, server, nil +} + +func TestHandshakeBasic(t *testing.T) { + if runtime.GOOS == "plan9" { + t.Skip("see golang.org/issue/7237") + } + + checker := &syncChecker{ + waitCall: make(chan int, 10), + called: make(chan int, 10), + } + + checker.waitCall <- 1 + trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false) + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + + defer trC.Close() + defer trS.Close() + + // Let first kex complete normally. + <-checker.called + + clientDone := make(chan int, 0) + gotHalf := make(chan int, 0) + const N = 20 + + go func() { + defer close(clientDone) + // Client writes a bunch of stuff, and does a key + // change in the middle. This should not confuse the + // handshake in progress. We do this twice, so we test + // that the packet buffer is reset correctly. + for i := 0; i < N; i++ { + p := []byte{msgRequestSuccess, byte(i)} + if err := trC.writePacket(p); err != nil { + t.Fatalf("sendPacket: %v", err) + } + if (i % 10) == 5 { + <-gotHalf + // halfway through, we request a key change. + trC.requestKeyExchange() + + // Wait until we can be sure the key + // change has really started before we + // write more. + <-checker.called + } + if (i % 10) == 7 { + // write some packets until the kex + // completes, to test buffering of + // packets. + checker.waitCall <- 1 + } + } + }() + + // Server checks that client messages come in cleanly + i := 0 + err = nil + for ; i < N; i++ { + var p []byte + p, err = trS.readPacket() + if err != nil { + break + } + if (i % 10) == 5 { + gotHalf <- 1 + } + + want := []byte{msgRequestSuccess, byte(i)} + if bytes.Compare(p, want) != 0 { + t.Errorf("message %d: got %v, want %v", i, p, want) + } + } + <-clientDone + if err != nil && err != io.EOF { + t.Fatalf("server error: %v", err) + } + if i != N { + t.Errorf("received %d messages, want 10.", i) + } + + close(checker.called) + if _, ok := <-checker.called; ok { + // If all went well, we registered exactly 2 key changes: one + // that establishes the session, and one that we requested + // additionally. + t.Fatalf("got another host key checks after 2 handshakes") + } +} + +func TestForceFirstKex(t *testing.T) { + // like handshakePair, but must access the keyingTransport. + checker := &testChecker{} + clientConf := &ClientConfig{HostKeyCallback: checker.Check} + a, b, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + + var trC, trS keyingTransport + + trC = newTransport(a, rand.Reader, true) + + // This is the disallowed packet: + trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})) + + // Rest of the setup. + trS = newTransport(b, rand.Reader, false) + clientConf.SetDefaults() + + v := []byte("version") + client := newClientTransport(trC, v, v, clientConf, "addr", a.RemoteAddr()) + + serverConf := &ServerConfig{} + serverConf.AddHostKey(testSigners["ecdsa"]) + serverConf.AddHostKey(testSigners["rsa"]) + serverConf.SetDefaults() + server := newServerTransport(trS, v, v, serverConf) + + defer client.Close() + defer server.Close() + + // We setup the initial key exchange, but the remote side + // tries to send serviceRequestMsg in cleartext, which is + // disallowed. + + if err := server.waitSession(); err == nil { + t.Errorf("server first kex init should reject unexpected packet") + } +} + +func TestHandshakeAutoRekeyWrite(t *testing.T) { + checker := &syncChecker{ + called: make(chan int, 10), + waitCall: nil, + } + clientConf := &ClientConfig{HostKeyCallback: checker.Check} + clientConf.RekeyThreshold = 500 + trC, trS, err := handshakePair(clientConf, "addr", false) + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + defer trC.Close() + defer trS.Close() + + input := make([]byte, 251) + input[0] = msgRequestSuccess + + done := make(chan int, 1) + const numPacket = 5 + go func() { + defer close(done) + j := 0 + for ; j < numPacket; j++ { + if p, err := trS.readPacket(); err != nil { + break + } else if !bytes.Equal(input, p) { + t.Errorf("got packet type %d, want %d", p[0], input[0]) + } + } + + if j != numPacket { + t.Errorf("got %d, want 5 messages", j) + } + }() + + <-checker.called + + for i := 0; i < numPacket; i++ { + p := make([]byte, len(input)) + copy(p, input) + if err := trC.writePacket(p); err != nil { + t.Errorf("writePacket: %v", err) + } + if i == 2 { + // Make sure the kex is in progress. + <-checker.called + } + + } + <-done +} + +type syncChecker struct { + waitCall chan int + called chan int +} + +func (c *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error { + c.called <- 1 + if c.waitCall != nil { + <-c.waitCall + } + return nil +} + +func TestHandshakeAutoRekeyRead(t *testing.T) { + sync := &syncChecker{ + called: make(chan int, 2), + waitCall: nil, + } + clientConf := &ClientConfig{ + HostKeyCallback: sync.Check, + } + clientConf.RekeyThreshold = 500 + + trC, trS, err := handshakePair(clientConf, "addr", false) + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + defer trC.Close() + defer trS.Close() + + packet := make([]byte, 501) + packet[0] = msgRequestSuccess + if err := trS.writePacket(packet); err != nil { + t.Fatalf("writePacket: %v", err) + } + + // While we read out the packet, a key change will be + // initiated. + done := make(chan int, 1) + go func() { + defer close(done) + if _, err := trC.readPacket(); err != nil { + t.Fatalf("readPacket(client): %v", err) + } + + }() + + <-done + <-sync.called +} + +// errorKeyingTransport generates errors after a given number of +// read/write operations. +type errorKeyingTransport struct { + packetConn + readLeft, writeLeft int +} + +func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error { + return nil +} + +func (n *errorKeyingTransport) getSessionID() []byte { + return nil +} + +func (n *errorKeyingTransport) writePacket(packet []byte) error { + if n.writeLeft == 0 { + n.Close() + return errors.New("barf") + } + + n.writeLeft-- + return n.packetConn.writePacket(packet) +} + +func (n *errorKeyingTransport) readPacket() ([]byte, error) { + if n.readLeft == 0 { + n.Close() + return nil, errors.New("barf") + } + + n.readLeft-- + return n.packetConn.readPacket() +} + +func TestHandshakeErrorHandlingRead(t *testing.T) { + for i := 0; i < 20; i++ { + testHandshakeErrorHandlingN(t, i, -1, false) + } +} + +func TestHandshakeErrorHandlingWrite(t *testing.T) { + for i := 0; i < 20; i++ { + testHandshakeErrorHandlingN(t, -1, i, false) + } +} + +func TestHandshakeErrorHandlingReadCoupled(t *testing.T) { + for i := 0; i < 20; i++ { + testHandshakeErrorHandlingN(t, i, -1, true) + } +} + +func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) { + for i := 0; i < 20; i++ { + testHandshakeErrorHandlingN(t, -1, i, true) + } +} + +// testHandshakeErrorHandlingN runs handshakes, injecting errors. If +// handshakeTransport deadlocks, the go runtime will detect it and +// panic. +func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) { + msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)}) + + a, b := memPipe() + defer a.Close() + defer b.Close() + + key := testSigners["ecdsa"] + serverConf := Config{RekeyThreshold: minRekeyThreshold} + serverConf.SetDefaults() + serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'}) + serverConn.hostKeys = []Signer{key} + go serverConn.readLoop() + go serverConn.kexLoop() + + clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold} + clientConf.SetDefaults() + clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'}) + clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()} + clientConn.hostKeyCallback = InsecureIgnoreHostKey() + go clientConn.readLoop() + go clientConn.kexLoop() + + var wg sync.WaitGroup + + for _, hs := range []packetConn{serverConn, clientConn} { + if !coupled { + wg.Add(2) + go func(c packetConn) { + for i := 0; ; i++ { + str := fmt.Sprintf("%08x", i) + strings.Repeat("x", int(minRekeyThreshold)/4-8) + err := c.writePacket(Marshal(&serviceRequestMsg{str})) + if err != nil { + break + } + } + wg.Done() + c.Close() + }(hs) + go func(c packetConn) { + for { + _, err := c.readPacket() + if err != nil { + break + } + } + wg.Done() + }(hs) + } else { + wg.Add(1) + go func(c packetConn) { + for { + _, err := c.readPacket() + if err != nil { + break + } + if err := c.writePacket(msg); err != nil { + break + } + + } + wg.Done() + }(hs) + } + } + wg.Wait() +} + +func TestDisconnect(t *testing.T) { + if runtime.GOOS == "plan9" { + t.Skip("see golang.org/issue/7237") + } + checker := &testChecker{} + trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false) + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + + defer trC.Close() + defer trS.Close() + + trC.writePacket([]byte{msgRequestSuccess, 0, 0}) + errMsg := &disconnectMsg{ + Reason: 42, + Message: "such is life", + } + trC.writePacket(Marshal(errMsg)) + trC.writePacket([]byte{msgRequestSuccess, 0, 0}) + + packet, err := trS.readPacket() + if err != nil { + t.Fatalf("readPacket 1: %v", err) + } + if packet[0] != msgRequestSuccess { + t.Errorf("got packet %v, want packet type %d", packet, msgRequestSuccess) + } + + _, err = trS.readPacket() + if err == nil { + t.Errorf("readPacket 2 succeeded") + } else if !reflect.DeepEqual(err, errMsg) { + t.Errorf("got error %#v, want %#v", err, errMsg) + } + + _, err = trS.readPacket() + if err == nil { + t.Errorf("readPacket 3 succeeded") + } +} + +func TestHandshakeRekeyDefault(t *testing.T) { + clientConf := &ClientConfig{ + Config: Config{ + Ciphers: []string{"aes128-ctr"}, + }, + HostKeyCallback: InsecureIgnoreHostKey(), + } + trC, trS, err := handshakePair(clientConf, "addr", false) + if err != nil { + t.Fatalf("handshakePair: %v", err) + } + defer trC.Close() + defer trS.Close() + + trC.writePacket([]byte{msgRequestSuccess, 0, 0}) + trC.Close() + + rgb := (1024 + trC.readBytesLeft) >> 30 + wgb := (1024 + trC.writeBytesLeft) >> 30 + + if rgb != 64 { + t.Errorf("got rekey after %dG read, want 64G", rgb) + } + if wgb != 64 { + t.Errorf("got rekey after %dG write, want 64G", wgb) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/kex.go b/vendor/golang.org/x/crypto/ssh/kex.go new file mode 100644 index 0000000..f34bcc0 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/kex.go @@ -0,0 +1,540 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/subtle" + "errors" + "io" + "math/big" + + "golang.org/x/crypto/curve25519" +) + +const ( + kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1" + kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1" + kexAlgoECDH256 = "ecdh-sha2-nistp256" + kexAlgoECDH384 = "ecdh-sha2-nistp384" + kexAlgoECDH521 = "ecdh-sha2-nistp521" + kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org" +) + +// kexResult captures the outcome of a key exchange. +type kexResult struct { + // Session hash. See also RFC 4253, section 8. + H []byte + + // Shared secret. See also RFC 4253, section 8. + K []byte + + // Host key as hashed into H. + HostKey []byte + + // Signature of H. + Signature []byte + + // A cryptographic hash function that matches the security + // level of the key exchange algorithm. It is used for + // calculating H, and for deriving keys from H and K. + Hash crypto.Hash + + // The session ID, which is the first H computed. This is used + // to derive key material inside the transport. + SessionID []byte +} + +// handshakeMagics contains data that is always included in the +// session hash. +type handshakeMagics struct { + clientVersion, serverVersion []byte + clientKexInit, serverKexInit []byte +} + +func (m *handshakeMagics) write(w io.Writer) { + writeString(w, m.clientVersion) + writeString(w, m.serverVersion) + writeString(w, m.clientKexInit) + writeString(w, m.serverKexInit) +} + +// kexAlgorithm abstracts different key exchange algorithms. +type kexAlgorithm interface { + // Server runs server-side key agreement, signing the result + // with a hostkey. + Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error) + + // Client runs the client-side key agreement. Caller is + // responsible for verifying the host key signature. + Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) +} + +// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement. +type dhGroup struct { + g, p, pMinus1 *big.Int +} + +func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) { + if theirPublic.Cmp(bigOne) <= 0 || theirPublic.Cmp(group.pMinus1) >= 0 { + return nil, errors.New("ssh: DH parameter out of bounds") + } + return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil +} + +func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) { + hashFunc := crypto.SHA1 + + var x *big.Int + for { + var err error + if x, err = rand.Int(randSource, group.pMinus1); err != nil { + return nil, err + } + if x.Sign() > 0 { + break + } + } + + X := new(big.Int).Exp(group.g, x, group.p) + kexDHInit := kexDHInitMsg{ + X: X, + } + if err := c.writePacket(Marshal(&kexDHInit)); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var kexDHReply kexDHReplyMsg + if err = Unmarshal(packet, &kexDHReply); err != nil { + return nil, err + } + + ki, err := group.diffieHellman(kexDHReply.Y, x) + if err != nil { + return nil, err + } + + h := hashFunc.New() + magics.write(h) + writeString(h, kexDHReply.HostKey) + writeInt(h, X) + writeInt(h, kexDHReply.Y) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: kexDHReply.HostKey, + Signature: kexDHReply.Signature, + Hash: crypto.SHA1, + }, nil +} + +func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { + hashFunc := crypto.SHA1 + packet, err := c.readPacket() + if err != nil { + return + } + var kexDHInit kexDHInitMsg + if err = Unmarshal(packet, &kexDHInit); err != nil { + return + } + + var y *big.Int + for { + if y, err = rand.Int(randSource, group.pMinus1); err != nil { + return + } + if y.Sign() > 0 { + break + } + } + + Y := new(big.Int).Exp(group.g, y, group.p) + ki, err := group.diffieHellman(kexDHInit.X, y) + if err != nil { + return nil, err + } + + hostKeyBytes := priv.PublicKey().Marshal() + + h := hashFunc.New() + magics.write(h) + writeString(h, hostKeyBytes) + writeInt(h, kexDHInit.X) + writeInt(h, Y) + + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + H := h.Sum(nil) + + // H is already a hash, but the hostkey signing will apply its + // own key-specific hash algorithm. + sig, err := signAndMarshal(priv, randSource, H) + if err != nil { + return nil, err + } + + kexDHReply := kexDHReplyMsg{ + HostKey: hostKeyBytes, + Y: Y, + Signature: sig, + } + packet = Marshal(&kexDHReply) + + err = c.writePacket(packet) + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: crypto.SHA1, + }, nil +} + +// ecdh performs Elliptic Curve Diffie-Hellman key exchange as +// described in RFC 5656, section 4. +type ecdh struct { + curve elliptic.Curve +} + +func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { + ephKey, err := ecdsa.GenerateKey(kex.curve, rand) + if err != nil { + return nil, err + } + + kexInit := kexECDHInitMsg{ + ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y), + } + + serialized := Marshal(&kexInit) + if err := c.writePacket(serialized); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var reply kexECDHReplyMsg + if err = Unmarshal(packet, &reply); err != nil { + return nil, err + } + + x, y, err := unmarshalECKey(kex.curve, reply.EphemeralPubKey) + if err != nil { + return nil, err + } + + // generate shared secret + secret, _ := kex.curve.ScalarMult(x, y, ephKey.D.Bytes()) + + h := ecHash(kex.curve).New() + magics.write(h) + writeString(h, reply.HostKey) + writeString(h, kexInit.ClientPubKey) + writeString(h, reply.EphemeralPubKey) + K := make([]byte, intLength(secret)) + marshalInt(K, secret) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: reply.HostKey, + Signature: reply.Signature, + Hash: ecHash(kex.curve), + }, nil +} + +// unmarshalECKey parses and checks an EC key. +func unmarshalECKey(curve elliptic.Curve, pubkey []byte) (x, y *big.Int, err error) { + x, y = elliptic.Unmarshal(curve, pubkey) + if x == nil { + return nil, nil, errors.New("ssh: elliptic.Unmarshal failure") + } + if !validateECPublicKey(curve, x, y) { + return nil, nil, errors.New("ssh: public key not on curve") + } + return x, y, nil +} + +// validateECPublicKey checks that the point is a valid public key for +// the given curve. See [SEC1], 3.2.2 +func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool { + if x.Sign() == 0 && y.Sign() == 0 { + return false + } + + if x.Cmp(curve.Params().P) >= 0 { + return false + } + + if y.Cmp(curve.Params().P) >= 0 { + return false + } + + if !curve.IsOnCurve(x, y) { + return false + } + + // We don't check if N * PubKey == 0, since + // + // - the NIST curves have cofactor = 1, so this is implicit. + // (We don't foresee an implementation that supports non NIST + // curves) + // + // - for ephemeral keys, we don't need to worry about small + // subgroup attacks. + return true +} + +func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var kexECDHInit kexECDHInitMsg + if err = Unmarshal(packet, &kexECDHInit); err != nil { + return nil, err + } + + clientX, clientY, err := unmarshalECKey(kex.curve, kexECDHInit.ClientPubKey) + if err != nil { + return nil, err + } + + // We could cache this key across multiple users/multiple + // connection attempts, but the benefit is small. OpenSSH + // generates a new key for each incoming connection. + ephKey, err := ecdsa.GenerateKey(kex.curve, rand) + if err != nil { + return nil, err + } + + hostKeyBytes := priv.PublicKey().Marshal() + + serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y) + + // generate shared secret + secret, _ := kex.curve.ScalarMult(clientX, clientY, ephKey.D.Bytes()) + + h := ecHash(kex.curve).New() + magics.write(h) + writeString(h, hostKeyBytes) + writeString(h, kexECDHInit.ClientPubKey) + writeString(h, serializedEphKey) + + K := make([]byte, intLength(secret)) + marshalInt(K, secret) + h.Write(K) + + H := h.Sum(nil) + + // H is already a hash, but the hostkey signing will apply its + // own key-specific hash algorithm. + sig, err := signAndMarshal(priv, rand, H) + if err != nil { + return nil, err + } + + reply := kexECDHReplyMsg{ + EphemeralPubKey: serializedEphKey, + HostKey: hostKeyBytes, + Signature: sig, + } + + serialized := Marshal(&reply) + if err := c.writePacket(serialized); err != nil { + return nil, err + } + + return &kexResult{ + H: H, + K: K, + HostKey: reply.HostKey, + Signature: sig, + Hash: ecHash(kex.curve), + }, nil +} + +var kexAlgoMap = map[string]kexAlgorithm{} + +func init() { + // This is the group called diffie-hellman-group1-sha1 in RFC + // 4253 and Oakley Group 2 in RFC 2409. + p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16) + kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{ + g: new(big.Int).SetInt64(2), + p: p, + pMinus1: new(big.Int).Sub(p, bigOne), + } + + // This is the group called diffie-hellman-group14-sha1 in RFC + // 4253 and Oakley Group 14 in RFC 3526. + p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16) + + kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{ + g: new(big.Int).SetInt64(2), + p: p, + pMinus1: new(big.Int).Sub(p, bigOne), + } + + kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()} + kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()} + kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()} + kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{} +} + +// curve25519sha256 implements the curve25519-sha256@libssh.org key +// agreement protocol, as described in +// https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt +type curve25519sha256 struct{} + +type curve25519KeyPair struct { + priv [32]byte + pub [32]byte +} + +func (kp *curve25519KeyPair) generate(rand io.Reader) error { + if _, err := io.ReadFull(rand, kp.priv[:]); err != nil { + return err + } + curve25519.ScalarBaseMult(&kp.pub, &kp.priv) + return nil +} + +// curve25519Zeros is just an array of 32 zero bytes so that we have something +// convenient to compare against in order to reject curve25519 points with the +// wrong order. +var curve25519Zeros [32]byte + +func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { + var kp curve25519KeyPair + if err := kp.generate(rand); err != nil { + return nil, err + } + if err := c.writePacket(Marshal(&kexECDHInitMsg{kp.pub[:]})); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var reply kexECDHReplyMsg + if err = Unmarshal(packet, &reply); err != nil { + return nil, err + } + if len(reply.EphemeralPubKey) != 32 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong length") + } + + var servPub, secret [32]byte + copy(servPub[:], reply.EphemeralPubKey) + curve25519.ScalarMult(&secret, &kp.priv, &servPub) + if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong order") + } + + h := crypto.SHA256.New() + magics.write(h) + writeString(h, reply.HostKey) + writeString(h, kp.pub[:]) + writeString(h, reply.EphemeralPubKey) + + ki := new(big.Int).SetBytes(secret[:]) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: reply.HostKey, + Signature: reply.Signature, + Hash: crypto.SHA256, + }, nil +} + +func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { + packet, err := c.readPacket() + if err != nil { + return + } + var kexInit kexECDHInitMsg + if err = Unmarshal(packet, &kexInit); err != nil { + return + } + + if len(kexInit.ClientPubKey) != 32 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong length") + } + + var kp curve25519KeyPair + if err := kp.generate(rand); err != nil { + return nil, err + } + + var clientPub, secret [32]byte + copy(clientPub[:], kexInit.ClientPubKey) + curve25519.ScalarMult(&secret, &kp.priv, &clientPub) + if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong order") + } + + hostKeyBytes := priv.PublicKey().Marshal() + + h := crypto.SHA256.New() + magics.write(h) + writeString(h, hostKeyBytes) + writeString(h, kexInit.ClientPubKey) + writeString(h, kp.pub[:]) + + ki := new(big.Int).SetBytes(secret[:]) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + H := h.Sum(nil) + + sig, err := signAndMarshal(priv, rand, H) + if err != nil { + return nil, err + } + + reply := kexECDHReplyMsg{ + EphemeralPubKey: kp.pub[:], + HostKey: hostKeyBytes, + Signature: sig, + } + if err := c.writePacket(Marshal(&reply)); err != nil { + return nil, err + } + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: crypto.SHA256, + }, nil +} diff --git a/vendor/golang.org/x/crypto/ssh/kex_test.go b/vendor/golang.org/x/crypto/ssh/kex_test.go new file mode 100644 index 0000000..12ca0ac --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/kex_test.go @@ -0,0 +1,50 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +// Key exchange tests. + +import ( + "crypto/rand" + "reflect" + "testing" +) + +func TestKexes(t *testing.T) { + type kexResultErr struct { + result *kexResult + err error + } + + for name, kex := range kexAlgoMap { + a, b := memPipe() + + s := make(chan kexResultErr, 1) + c := make(chan kexResultErr, 1) + var magics handshakeMagics + go func() { + r, e := kex.Client(a, rand.Reader, &magics) + a.Close() + c <- kexResultErr{r, e} + }() + go func() { + r, e := kex.Server(b, rand.Reader, &magics, testSigners["ecdsa"]) + b.Close() + s <- kexResultErr{r, e} + }() + + clientRes := <-c + serverRes := <-s + if clientRes.err != nil { + t.Errorf("client: %v", clientRes.err) + } + if serverRes.err != nil { + t.Errorf("server: %v", serverRes.err) + } + if !reflect.DeepEqual(clientRes.result, serverRes.result) { + t.Errorf("kex %q: mismatch %#v, %#v", name, clientRes.result, serverRes.result) + } + } +} diff --git a/vendor/golang.org/x/crypto/ssh/keys.go b/vendor/golang.org/x/crypto/ssh/keys.go new file mode 100644 index 0000000..dadf41a --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/keys.go @@ -0,0 +1,1031 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/md5" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/hex" + "encoding/pem" + "errors" + "fmt" + "io" + "math/big" + "strings" + + "golang.org/x/crypto/ed25519" +) + +// These constants represent the algorithm names for key types supported by this +// package. +const ( + KeyAlgoRSA = "ssh-rsa" + KeyAlgoDSA = "ssh-dss" + KeyAlgoECDSA256 = "ecdsa-sha2-nistp256" + KeyAlgoECDSA384 = "ecdsa-sha2-nistp384" + KeyAlgoECDSA521 = "ecdsa-sha2-nistp521" + KeyAlgoED25519 = "ssh-ed25519" +) + +// parsePubKey parses a public key of the given algorithm. +// Use ParsePublicKey for keys with prepended algorithm. +func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) { + switch algo { + case KeyAlgoRSA: + return parseRSA(in) + case KeyAlgoDSA: + return parseDSA(in) + case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521: + return parseECDSA(in) + case KeyAlgoED25519: + return parseED25519(in) + case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01: + cert, err := parseCert(in, certToPrivAlgo(algo)) + if err != nil { + return nil, nil, err + } + return cert, nil, nil + } + return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo) +} + +// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format +// (see sshd(8) manual page) once the options and key type fields have been +// removed. +func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) { + in = bytes.TrimSpace(in) + + i := bytes.IndexAny(in, " \t") + if i == -1 { + i = len(in) + } + base64Key := in[:i] + + key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key))) + n, err := base64.StdEncoding.Decode(key, base64Key) + if err != nil { + return nil, "", err + } + key = key[:n] + out, err = ParsePublicKey(key) + if err != nil { + return nil, "", err + } + comment = string(bytes.TrimSpace(in[i:])) + return out, comment, nil +} + +// ParseKnownHosts parses an entry in the format of the known_hosts file. +// +// The known_hosts format is documented in the sshd(8) manual page. This +// function will parse a single entry from in. On successful return, marker +// will contain the optional marker value (i.e. "cert-authority" or "revoked") +// or else be empty, hosts will contain the hosts that this entry matches, +// pubKey will contain the public key and comment will contain any trailing +// comment at the end of the line. See the sshd(8) manual page for the various +// forms that a host string can take. +// +// The unparsed remainder of the input will be returned in rest. This function +// can be called repeatedly to parse multiple entries. +// +// If no entries were found in the input then err will be io.EOF. Otherwise a +// non-nil err value indicates a parse error. +func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey, comment string, rest []byte, err error) { + for len(in) > 0 { + end := bytes.IndexByte(in, '\n') + if end != -1 { + rest = in[end+1:] + in = in[:end] + } else { + rest = nil + } + + end = bytes.IndexByte(in, '\r') + if end != -1 { + in = in[:end] + } + + in = bytes.TrimSpace(in) + if len(in) == 0 || in[0] == '#' { + in = rest + continue + } + + i := bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + // Strip out the beginning of the known_host key. + // This is either an optional marker or a (set of) hostname(s). + keyFields := bytes.Fields(in) + if len(keyFields) < 3 || len(keyFields) > 5 { + return "", nil, nil, "", nil, errors.New("ssh: invalid entry in known_hosts data") + } + + // keyFields[0] is either "@cert-authority", "@revoked" or a comma separated + // list of hosts + marker := "" + if keyFields[0][0] == '@' { + marker = string(keyFields[0][1:]) + keyFields = keyFields[1:] + } + + hosts := string(keyFields[0]) + // keyFields[1] contains the key type (e.g. “ssh-rsa”). + // However, that information is duplicated inside the + // base64-encoded key and so is ignored here. + + key := bytes.Join(keyFields[2:], []byte(" ")) + if pubKey, comment, err = parseAuthorizedKey(key); err != nil { + return "", nil, nil, "", nil, err + } + + return marker, strings.Split(hosts, ","), pubKey, comment, rest, nil + } + + return "", nil, nil, "", nil, io.EOF +} + +// ParseAuthorizedKeys parses a public key from an authorized_keys +// file used in OpenSSH according to the sshd(8) manual page. +func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) { + for len(in) > 0 { + end := bytes.IndexByte(in, '\n') + if end != -1 { + rest = in[end+1:] + in = in[:end] + } else { + rest = nil + } + + end = bytes.IndexByte(in, '\r') + if end != -1 { + in = in[:end] + } + + in = bytes.TrimSpace(in) + if len(in) == 0 || in[0] == '#' { + in = rest + continue + } + + i := bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { + return out, comment, options, rest, nil + } + + // No key type recognised. Maybe there's an options field at + // the beginning. + var b byte + inQuote := false + var candidateOptions []string + optionStart := 0 + for i, b = range in { + isEnd := !inQuote && (b == ' ' || b == '\t') + if (b == ',' && !inQuote) || isEnd { + if i-optionStart > 0 { + candidateOptions = append(candidateOptions, string(in[optionStart:i])) + } + optionStart = i + 1 + } + if isEnd { + break + } + if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) { + inQuote = !inQuote + } + } + for i < len(in) && (in[i] == ' ' || in[i] == '\t') { + i++ + } + if i == len(in) { + // Invalid line: unmatched quote + in = rest + continue + } + + in = in[i:] + i = bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { + options = candidateOptions + return out, comment, options, rest, nil + } + + in = rest + continue + } + + return nil, "", nil, nil, errors.New("ssh: no key found") +} + +// ParsePublicKey parses an SSH public key formatted for use in +// the SSH wire protocol according to RFC 4253, section 6.6. +func ParsePublicKey(in []byte) (out PublicKey, err error) { + algo, in, ok := parseString(in) + if !ok { + return nil, errShortRead + } + var rest []byte + out, rest, err = parsePubKey(in, string(algo)) + if len(rest) > 0 { + return nil, errors.New("ssh: trailing junk in public key") + } + + return out, err +} + +// MarshalAuthorizedKey serializes key for inclusion in an OpenSSH +// authorized_keys file. The return value ends with newline. +func MarshalAuthorizedKey(key PublicKey) []byte { + b := &bytes.Buffer{} + b.WriteString(key.Type()) + b.WriteByte(' ') + e := base64.NewEncoder(base64.StdEncoding, b) + e.Write(key.Marshal()) + e.Close() + b.WriteByte('\n') + return b.Bytes() +} + +// PublicKey is an abstraction of different types of public keys. +type PublicKey interface { + // Type returns the key's type, e.g. "ssh-rsa". + Type() string + + // Marshal returns the serialized key data in SSH wire format, + // with the name prefix. + Marshal() []byte + + // Verify that sig is a signature on the given data using this + // key. This function will hash the data appropriately first. + Verify(data []byte, sig *Signature) error +} + +// CryptoPublicKey, if implemented by a PublicKey, +// returns the underlying crypto.PublicKey form of the key. +type CryptoPublicKey interface { + CryptoPublicKey() crypto.PublicKey +} + +// A Signer can create signatures that verify against a public key. +type Signer interface { + // PublicKey returns an associated PublicKey instance. + PublicKey() PublicKey + + // Sign returns raw signature for the given data. This method + // will apply the hash specified for the keytype to the data. + Sign(rand io.Reader, data []byte) (*Signature, error) +} + +type rsaPublicKey rsa.PublicKey + +func (r *rsaPublicKey) Type() string { + return "ssh-rsa" +} + +// parseRSA parses an RSA key according to RFC 4253, section 6.6. +func parseRSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + E *big.Int + N *big.Int + Rest []byte `ssh:"rest"` + } + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + if w.E.BitLen() > 24 { + return nil, nil, errors.New("ssh: exponent too large") + } + e := w.E.Int64() + if e < 3 || e&1 == 0 { + return nil, nil, errors.New("ssh: incorrect exponent") + } + + var key rsa.PublicKey + key.E = int(e) + key.N = w.N + return (*rsaPublicKey)(&key), w.Rest, nil +} + +func (r *rsaPublicKey) Marshal() []byte { + e := new(big.Int).SetInt64(int64(r.E)) + // RSA publickey struct layout should match the struct used by + // parseRSACert in the x/crypto/ssh/agent package. + wirekey := struct { + Name string + E *big.Int + N *big.Int + }{ + KeyAlgoRSA, + e, + r.N, + } + return Marshal(&wirekey) +} + +func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != r.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type()) + } + h := crypto.SHA1.New() + h.Write(data) + digest := h.Sum(nil) + return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob) +} + +func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*rsa.PublicKey)(r) +} + +type dsaPublicKey dsa.PublicKey + +func (k *dsaPublicKey) Type() string { + return "ssh-dss" +} + +func checkDSAParams(param *dsa.Parameters) error { + // SSH specifies FIPS 186-2, which only provided a single size + // (1024 bits) DSA key. FIPS 186-3 allows for larger key + // sizes, which would confuse SSH. + if l := param.P.BitLen(); l != 1024 { + return fmt.Errorf("ssh: unsupported DSA key size %d", l) + } + + return nil +} + +// parseDSA parses an DSA key according to RFC 4253, section 6.6. +func parseDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + P, Q, G, Y *big.Int + Rest []byte `ssh:"rest"` + } + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + param := dsa.Parameters{ + P: w.P, + Q: w.Q, + G: w.G, + } + if err := checkDSAParams(¶m); err != nil { + return nil, nil, err + } + + key := &dsaPublicKey{ + Parameters: param, + Y: w.Y, + } + return key, w.Rest, nil +} + +func (k *dsaPublicKey) Marshal() []byte { + // DSA publickey struct layout should match the struct used by + // parseDSACert in the x/crypto/ssh/agent package. + w := struct { + Name string + P, Q, G, Y *big.Int + }{ + k.Type(), + k.P, + k.Q, + k.G, + k.Y, + } + + return Marshal(&w) +} + +func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + h := crypto.SHA1.New() + h.Write(data) + digest := h.Sum(nil) + + // Per RFC 4253, section 6.6, + // The value for 'dss_signature_blob' is encoded as a string containing + // r, followed by s (which are 160-bit integers, without lengths or + // padding, unsigned, and in network byte order). + // For DSS purposes, sig.Blob should be exactly 40 bytes in length. + if len(sig.Blob) != 40 { + return errors.New("ssh: DSA signature parse error") + } + r := new(big.Int).SetBytes(sig.Blob[:20]) + s := new(big.Int).SetBytes(sig.Blob[20:]) + if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) { + return nil + } + return errors.New("ssh: signature did not verify") +} + +func (k *dsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*dsa.PublicKey)(k) +} + +type dsaPrivateKey struct { + *dsa.PrivateKey +} + +func (k *dsaPrivateKey) PublicKey() PublicKey { + return (*dsaPublicKey)(&k.PrivateKey.PublicKey) +} + +func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) { + h := crypto.SHA1.New() + h.Write(data) + digest := h.Sum(nil) + r, s, err := dsa.Sign(rand, k.PrivateKey, digest) + if err != nil { + return nil, err + } + + sig := make([]byte, 40) + rb := r.Bytes() + sb := s.Bytes() + + copy(sig[20-len(rb):20], rb) + copy(sig[40-len(sb):], sb) + + return &Signature{ + Format: k.PublicKey().Type(), + Blob: sig, + }, nil +} + +type ecdsaPublicKey ecdsa.PublicKey + +func (k *ecdsaPublicKey) Type() string { + return "ecdsa-sha2-" + k.nistID() +} + +func (k *ecdsaPublicKey) nistID() string { + switch k.Params().BitSize { + case 256: + return "nistp256" + case 384: + return "nistp384" + case 521: + return "nistp521" + } + panic("ssh: unsupported ecdsa key size") +} + +type ed25519PublicKey ed25519.PublicKey + +func (k ed25519PublicKey) Type() string { + return KeyAlgoED25519 +} + +func parseED25519(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + KeyBytes []byte + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + key := ed25519.PublicKey(w.KeyBytes) + + return (ed25519PublicKey)(key), w.Rest, nil +} + +func (k ed25519PublicKey) Marshal() []byte { + w := struct { + Name string + KeyBytes []byte + }{ + KeyAlgoED25519, + []byte(k), + } + return Marshal(&w) +} + +func (k ed25519PublicKey) Verify(b []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + + edKey := (ed25519.PublicKey)(k) + if ok := ed25519.Verify(edKey, b, sig.Blob); !ok { + return errors.New("ssh: signature did not verify") + } + + return nil +} + +func (k ed25519PublicKey) CryptoPublicKey() crypto.PublicKey { + return ed25519.PublicKey(k) +} + +func supportedEllipticCurve(curve elliptic.Curve) bool { + return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521() +} + +// ecHash returns the hash to match the given elliptic curve, see RFC +// 5656, section 6.2.1 +func ecHash(curve elliptic.Curve) crypto.Hash { + bitSize := curve.Params().BitSize + switch { + case bitSize <= 256: + return crypto.SHA256 + case bitSize <= 384: + return crypto.SHA384 + } + return crypto.SHA512 +} + +// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1. +func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + Curve string + KeyBytes []byte + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + key := new(ecdsa.PublicKey) + + switch w.Curve { + case "nistp256": + key.Curve = elliptic.P256() + case "nistp384": + key.Curve = elliptic.P384() + case "nistp521": + key.Curve = elliptic.P521() + default: + return nil, nil, errors.New("ssh: unsupported curve") + } + + key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes) + if key.X == nil || key.Y == nil { + return nil, nil, errors.New("ssh: invalid curve point") + } + return (*ecdsaPublicKey)(key), w.Rest, nil +} + +func (k *ecdsaPublicKey) Marshal() []byte { + // See RFC 5656, section 3.1. + keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y) + // ECDSA publickey struct layout should match the struct used by + // parseECDSACert in the x/crypto/ssh/agent package. + w := struct { + Name string + ID string + Key []byte + }{ + k.Type(), + k.nistID(), + keyBytes, + } + + return Marshal(&w) +} + +func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + + h := ecHash(k.Curve).New() + h.Write(data) + digest := h.Sum(nil) + + // Per RFC 5656, section 3.1.2, + // The ecdsa_signature_blob value has the following specific encoding: + // mpint r + // mpint s + var ecSig struct { + R *big.Int + S *big.Int + } + + if err := Unmarshal(sig.Blob, &ecSig); err != nil { + return err + } + + if ecdsa.Verify((*ecdsa.PublicKey)(k), digest, ecSig.R, ecSig.S) { + return nil + } + return errors.New("ssh: signature did not verify") +} + +func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*ecdsa.PublicKey)(k) +} + +// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey, +// *ecdsa.PrivateKey or any other crypto.Signer and returns a +// corresponding Signer instance. ECDSA keys must use P-256, P-384 or +// P-521. DSA keys must use parameter size L1024N160. +func NewSignerFromKey(key interface{}) (Signer, error) { + switch key := key.(type) { + case crypto.Signer: + return NewSignerFromSigner(key) + case *dsa.PrivateKey: + return newDSAPrivateKey(key) + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", key) + } +} + +func newDSAPrivateKey(key *dsa.PrivateKey) (Signer, error) { + if err := checkDSAParams(&key.PublicKey.Parameters); err != nil { + return nil, err + } + + return &dsaPrivateKey{key}, nil +} + +type wrappedSigner struct { + signer crypto.Signer + pubKey PublicKey +} + +// NewSignerFromSigner takes any crypto.Signer implementation and +// returns a corresponding Signer interface. This can be used, for +// example, with keys kept in hardware modules. +func NewSignerFromSigner(signer crypto.Signer) (Signer, error) { + pubKey, err := NewPublicKey(signer.Public()) + if err != nil { + return nil, err + } + + return &wrappedSigner{signer, pubKey}, nil +} + +func (s *wrappedSigner) PublicKey() PublicKey { + return s.pubKey +} + +func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { + var hashFunc crypto.Hash + + switch key := s.pubKey.(type) { + case *rsaPublicKey, *dsaPublicKey: + hashFunc = crypto.SHA1 + case *ecdsaPublicKey: + hashFunc = ecHash(key.Curve) + case ed25519PublicKey: + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", key) + } + + var digest []byte + if hashFunc != 0 { + h := hashFunc.New() + h.Write(data) + digest = h.Sum(nil) + } else { + digest = data + } + + signature, err := s.signer.Sign(rand, digest, hashFunc) + if err != nil { + return nil, err + } + + // crypto.Signer.Sign is expected to return an ASN.1-encoded signature + // for ECDSA and DSA, but that's not the encoding expected by SSH, so + // re-encode. + switch s.pubKey.(type) { + case *ecdsaPublicKey, *dsaPublicKey: + type asn1Signature struct { + R, S *big.Int + } + asn1Sig := new(asn1Signature) + _, err := asn1.Unmarshal(signature, asn1Sig) + if err != nil { + return nil, err + } + + switch s.pubKey.(type) { + case *ecdsaPublicKey: + signature = Marshal(asn1Sig) + + case *dsaPublicKey: + signature = make([]byte, 40) + r := asn1Sig.R.Bytes() + s := asn1Sig.S.Bytes() + copy(signature[20-len(r):20], r) + copy(signature[40-len(s):40], s) + } + } + + return &Signature{ + Format: s.pubKey.Type(), + Blob: signature, + }, nil +} + +// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey, +// or ed25519.PublicKey returns a corresponding PublicKey instance. +// ECDSA keys must use P-256, P-384 or P-521. +func NewPublicKey(key interface{}) (PublicKey, error) { + switch key := key.(type) { + case *rsa.PublicKey: + return (*rsaPublicKey)(key), nil + case *ecdsa.PublicKey: + if !supportedEllipticCurve(key.Curve) { + return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported") + } + return (*ecdsaPublicKey)(key), nil + case *dsa.PublicKey: + return (*dsaPublicKey)(key), nil + case ed25519.PublicKey: + return (ed25519PublicKey)(key), nil + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", key) + } +} + +// ParsePrivateKey returns a Signer from a PEM encoded private key. It supports +// the same keys as ParseRawPrivateKey. +func ParsePrivateKey(pemBytes []byte) (Signer, error) { + key, err := ParseRawPrivateKey(pemBytes) + if err != nil { + return nil, err + } + + return NewSignerFromKey(key) +} + +// ParsePrivateKeyWithPassphrase returns a Signer from a PEM encoded private +// key and passphrase. It supports the same keys as +// ParseRawPrivateKeyWithPassphrase. +func ParsePrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (Signer, error) { + key, err := ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase) + if err != nil { + return nil, err + } + + return NewSignerFromKey(key) +} + +// encryptedBlock tells whether a private key is +// encrypted by examining its Proc-Type header +// for a mention of ENCRYPTED +// according to RFC 1421 Section 4.6.1.1. +func encryptedBlock(block *pem.Block) bool { + return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED") +} + +// ParseRawPrivateKey returns a private key from a PEM encoded private key. It +// supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys. +func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("ssh: no key found") + } + + if encryptedBlock(block) { + return nil, errors.New("ssh: cannot decode encrypted private keys") + } + + switch block.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(block.Bytes) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(block.Bytes) + case "DSA PRIVATE KEY": + return ParseDSAPrivateKey(block.Bytes) + case "OPENSSH PRIVATE KEY": + return parseOpenSSHPrivateKey(block.Bytes) + default: + return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) + } +} + +// ParseRawPrivateKeyWithPassphrase returns a private key decrypted with +// passphrase from a PEM encoded private key. If wrong passphrase, return +// x509.IncorrectPasswordError. +func ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (interface{}, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("ssh: no key found") + } + buf := block.Bytes + + if encryptedBlock(block) { + if x509.IsEncryptedPEMBlock(block) { + var err error + buf, err = x509.DecryptPEMBlock(block, passPhrase) + if err != nil { + if err == x509.IncorrectPasswordError { + return nil, err + } + return nil, fmt.Errorf("ssh: cannot decode encrypted private keys: %v", err) + } + } + } + + switch block.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(buf) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(buf) + case "DSA PRIVATE KEY": + return ParseDSAPrivateKey(buf) + case "OPENSSH PRIVATE KEY": + return parseOpenSSHPrivateKey(buf) + default: + return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) + } +} + +// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as +// specified by the OpenSSL DSA man page. +func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) { + var k struct { + Version int + P *big.Int + Q *big.Int + G *big.Int + Pub *big.Int + Priv *big.Int + } + rest, err := asn1.Unmarshal(der, &k) + if err != nil { + return nil, errors.New("ssh: failed to parse DSA key: " + err.Error()) + } + if len(rest) > 0 { + return nil, errors.New("ssh: garbage after DSA key") + } + + return &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: k.P, + Q: k.Q, + G: k.G, + }, + Y: k.Pub, + }, + X: k.Priv, + }, nil +} + +// Implemented based on the documentation at +// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key +func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) { + magic := append([]byte("openssh-key-v1"), 0) + if !bytes.Equal(magic, key[0:len(magic)]) { + return nil, errors.New("ssh: invalid openssh private key format") + } + remaining := key[len(magic):] + + var w struct { + CipherName string + KdfName string + KdfOpts string + NumKeys uint32 + PubKey []byte + PrivKeyBlock []byte + } + + if err := Unmarshal(remaining, &w); err != nil { + return nil, err + } + + if w.KdfName != "none" || w.CipherName != "none" { + return nil, errors.New("ssh: cannot decode encrypted private keys") + } + + pk1 := struct { + Check1 uint32 + Check2 uint32 + Keytype string + Rest []byte `ssh:"rest"` + }{} + + if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil { + return nil, err + } + + if pk1.Check1 != pk1.Check2 { + return nil, errors.New("ssh: checkint mismatch") + } + + // we only handle ed25519 and rsa keys currently + switch pk1.Keytype { + case KeyAlgoRSA: + // https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773 + key := struct { + N *big.Int + E *big.Int + D *big.Int + Iqmp *big.Int + P *big.Int + Q *big.Int + Comment string + Pad []byte `ssh:"rest"` + }{} + + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } + + for i, b := range key.Pad { + if int(b) != i+1 { + return nil, errors.New("ssh: padding not as expected") + } + } + + pk := &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: key.N, + E: int(key.E.Int64()), + }, + D: key.D, + Primes: []*big.Int{key.P, key.Q}, + } + + if err := pk.Validate(); err != nil { + return nil, err + } + + pk.Precompute() + + return pk, nil + case KeyAlgoED25519: + key := struct { + Pub []byte + Priv []byte + Comment string + Pad []byte `ssh:"rest"` + }{} + + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } + + if len(key.Priv) != ed25519.PrivateKeySize { + return nil, errors.New("ssh: private key unexpected length") + } + + for i, b := range key.Pad { + if int(b) != i+1 { + return nil, errors.New("ssh: padding not as expected") + } + } + + pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) + copy(pk, key.Priv) + return &pk, nil + default: + return nil, errors.New("ssh: unhandled key type") + } +} + +// FingerprintLegacyMD5 returns the user presentation of the key's +// fingerprint as described by RFC 4716 section 4. +func FingerprintLegacyMD5(pubKey PublicKey) string { + md5sum := md5.Sum(pubKey.Marshal()) + hexarray := make([]string, len(md5sum)) + for i, c := range md5sum { + hexarray[i] = hex.EncodeToString([]byte{c}) + } + return strings.Join(hexarray, ":") +} + +// FingerprintSHA256 returns the user presentation of the key's +// fingerprint as unpadded base64 encoded sha256 hash. +// This format was introduced from OpenSSH 6.8. +// https://www.openssh.com/txt/release-6.8 +// https://tools.ietf.org/html/rfc4648#section-3.2 (unpadded base64 encoding) +func FingerprintSHA256(pubKey PublicKey) string { + sha256sum := sha256.Sum256(pubKey.Marshal()) + hash := base64.RawStdEncoding.EncodeToString(sha256sum[:]) + return "SHA256:" + hash +} diff --git a/vendor/golang.org/x/crypto/ssh/keys_test.go b/vendor/golang.org/x/crypto/ssh/keys_test.go new file mode 100644 index 0000000..20ab954 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/keys_test.go @@ -0,0 +1,500 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "fmt" + "reflect" + "strings" + "testing" + + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/ssh/testdata" +) + +func rawKey(pub PublicKey) interface{} { + switch k := pub.(type) { + case *rsaPublicKey: + return (*rsa.PublicKey)(k) + case *dsaPublicKey: + return (*dsa.PublicKey)(k) + case *ecdsaPublicKey: + return (*ecdsa.PublicKey)(k) + case ed25519PublicKey: + return (ed25519.PublicKey)(k) + case *Certificate: + return k + } + panic("unknown key type") +} + +func TestKeyMarshalParse(t *testing.T) { + for _, priv := range testSigners { + pub := priv.PublicKey() + roundtrip, err := ParsePublicKey(pub.Marshal()) + if err != nil { + t.Errorf("ParsePublicKey(%T): %v", pub, err) + } + + k1 := rawKey(pub) + k2 := rawKey(roundtrip) + + if !reflect.DeepEqual(k1, k2) { + t.Errorf("got %#v in roundtrip, want %#v", k2, k1) + } + } +} + +func TestUnsupportedCurves(t *testing.T) { + raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + if err != nil { + t.Fatalf("GenerateKey: %v", err) + } + + if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P-256") { + t.Fatalf("NewPrivateKey should not succeed with P-224, got: %v", err) + } + + if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P-256") { + t.Fatalf("NewPublicKey should not succeed with P-224, got: %v", err) + } +} + +func TestNewPublicKey(t *testing.T) { + for _, k := range testSigners { + raw := rawKey(k.PublicKey()) + // Skip certificates, as NewPublicKey does not support them. + if _, ok := raw.(*Certificate); ok { + continue + } + pub, err := NewPublicKey(raw) + if err != nil { + t.Errorf("NewPublicKey(%#v): %v", raw, err) + } + if !reflect.DeepEqual(k.PublicKey(), pub) { + t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey()) + } + } +} + +func TestKeySignVerify(t *testing.T) { + for _, priv := range testSigners { + pub := priv.PublicKey() + + data := []byte("sign me") + sig, err := priv.Sign(rand.Reader, data) + if err != nil { + t.Fatalf("Sign(%T): %v", priv, err) + } + + if err := pub.Verify(data, sig); err != nil { + t.Errorf("publicKey.Verify(%T): %v", priv, err) + } + sig.Blob[5]++ + if err := pub.Verify(data, sig); err == nil { + t.Errorf("publicKey.Verify on broken sig did not fail") + } + } +} + +func TestParseRSAPrivateKey(t *testing.T) { + key := testPrivateKeys["rsa"] + + rsa, ok := key.(*rsa.PrivateKey) + if !ok { + t.Fatalf("got %T, want *rsa.PrivateKey", rsa) + } + + if err := rsa.Validate(); err != nil { + t.Errorf("Validate: %v", err) + } +} + +func TestParseECPrivateKey(t *testing.T) { + key := testPrivateKeys["ecdsa"] + + ecKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey) + } + + if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) { + t.Fatalf("public key does not validate.") + } +} + +// See Issue https://github.com/golang/go/issues/6650. +func TestParseEncryptedPrivateKeysFails(t *testing.T) { + const wantSubstring = "encrypted" + for i, tt := range testdata.PEMEncryptedKeys { + _, err := ParsePrivateKey(tt.PEMBytes) + if err == nil { + t.Errorf("#%d key %s: ParsePrivateKey successfully parsed, expected an error", i, tt.Name) + continue + } + + if !strings.Contains(err.Error(), wantSubstring) { + t.Errorf("#%d key %s: got error %q, want substring %q", i, tt.Name, err, wantSubstring) + } + } +} + +// Parse encrypted private keys with passphrase +func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) { + data := []byte("sign me") + for _, tt := range testdata.PEMEncryptedKeys { + s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey)) + if err != nil { + t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err) + continue + } + sig, err := s.Sign(rand.Reader, data) + if err != nil { + t.Fatalf("dsa.Sign: %v", err) + } + if err := s.PublicKey().Verify(data, sig); err != nil { + t.Errorf("Verify failed: %v", err) + } + } + + tt := testdata.PEMEncryptedKeys[0] + _, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte("incorrect")) + if err != x509.IncorrectPasswordError { + t.Fatalf("got %v want IncorrectPasswordError", err) + } +} + +func TestParseDSA(t *testing.T) { + // We actually exercise the ParsePrivateKey codepath here, as opposed to + // using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go + // uses. + s, err := ParsePrivateKey(testdata.PEMBytes["dsa"]) + if err != nil { + t.Fatalf("ParsePrivateKey returned error: %s", err) + } + + data := []byte("sign me") + sig, err := s.Sign(rand.Reader, data) + if err != nil { + t.Fatalf("dsa.Sign: %v", err) + } + + if err := s.PublicKey().Verify(data, sig); err != nil { + t.Errorf("Verify failed: %v", err) + } +} + +// Tests for authorized_keys parsing. + +// getTestKey returns a public key, and its base64 encoding. +func getTestKey() (PublicKey, string) { + k := testPublicKeys["rsa"] + + b := &bytes.Buffer{} + e := base64.NewEncoder(base64.StdEncoding, b) + e.Write(k.Marshal()) + e.Close() + + return k, b.String() +} + +func TestMarshalParsePublicKey(t *testing.T) { + pub, pubSerialized := getTestKey() + line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized) + + authKeys := MarshalAuthorizedKey(pub) + actualFields := strings.Fields(string(authKeys)) + if len(actualFields) == 0 { + t.Fatalf("failed authKeys: %v", authKeys) + } + + // drop the comment + expectedFields := strings.Fields(line)[0:2] + + if !reflect.DeepEqual(actualFields, expectedFields) { + t.Errorf("got %v, expected %v", actualFields, expectedFields) + } + + actPub, _, _, _, err := ParseAuthorizedKey([]byte(line)) + if err != nil { + t.Fatalf("cannot parse %v: %v", line, err) + } + if !reflect.DeepEqual(actPub, pub) { + t.Errorf("got %v, expected %v", actPub, pub) + } +} + +type authResult struct { + pubKey PublicKey + options []string + comments string + rest string + ok bool +} + +func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []authResult) { + rest := authKeys + var values []authResult + for len(rest) > 0 { + var r authResult + var err error + r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest) + r.ok = (err == nil) + t.Log(err) + r.rest = string(rest) + values = append(values, r) + } + + if !reflect.DeepEqual(values, expected) { + t.Errorf("got %#v, expected %#v", values, expected) + } +} + +func TestAuthorizedKeyBasic(t *testing.T) { + pub, pubSerialized := getTestKey() + line := "ssh-rsa " + pubSerialized + " user@host" + testAuthorizedKeys(t, []byte(line), + []authResult{ + {pub, nil, "user@host", "", true}, + }) +} + +func TestAuth(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithOptions := []string{ + `# comments to ignore before any keys...`, + ``, + `env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`, + `# comments to ignore, along with a blank line`, + ``, + `env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`, + ``, + `# more comments, plus a invalid entry`, + `ssh-rsa data-that-will-not-parse user@host3`, + } + for _, eol := range []string{"\n", "\r\n"} { + authOptions := strings.Join(authWithOptions, eol) + rest2 := strings.Join(authWithOptions[3:], eol) + rest3 := strings.Join(authWithOptions[6:], eol) + testAuthorizedKeys(t, []byte(authOptions), []authResult{ + {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true}, + {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true}, + {nil, nil, "", "", false}, + }) + } +} + +func TestAuthWithQuotedSpaceInEnv(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`) + testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []authResult{ + {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true}, + }) +} + +func TestAuthWithQuotedCommaInEnv(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`) + testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []authResult{ + {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true}, + }) +} + +func TestAuthWithQuotedQuoteInEnv(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`) + authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`) + testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []authResult{ + {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true}, + }) + + testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []authResult{ + {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true}, + }) +} + +func TestAuthWithInvalidSpace(t *testing.T) { + _, pubSerialized := getTestKey() + authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host +#more to follow but still no valid keys`) + testAuthorizedKeys(t, []byte(authWithInvalidSpace), []authResult{ + {nil, nil, "", "", false}, + }) +} + +func TestAuthWithMissingQuote(t *testing.T) { + pub, pubSerialized := getTestKey() + authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host +env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`) + + testAuthorizedKeys(t, []byte(authWithMissingQuote), []authResult{ + {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true}, + }) +} + +func TestInvalidEntry(t *testing.T) { + authInvalid := []byte(`ssh-rsa`) + _, _, _, _, err := ParseAuthorizedKey(authInvalid) + if err == nil { + t.Errorf("got valid entry for %q", authInvalid) + } +} + +var knownHostsParseTests = []struct { + input string + err string + + marker string + comment string + hosts []string + rest string +}{ + { + "", + "EOF", + + "", "", nil, "", + }, + { + "# Just a comment", + "EOF", + + "", "", nil, "", + }, + { + " \t ", + "EOF", + + "", "", nil, "", + }, + { + "localhost ssh-rsa {RSAPUB}", + "", + + "", "", []string{"localhost"}, "", + }, + { + "localhost\tssh-rsa {RSAPUB}", + "", + + "", "", []string{"localhost"}, "", + }, + { + "localhost\tssh-rsa {RSAPUB}\tcomment comment", + "", + + "", "comment comment", []string{"localhost"}, "", + }, + { + "localhost\tssh-rsa {RSAPUB}\tcomment comment\n", + "", + + "", "comment comment", []string{"localhost"}, "", + }, + { + "localhost\tssh-rsa {RSAPUB}\tcomment comment\r\n", + "", + + "", "comment comment", []string{"localhost"}, "", + }, + { + "localhost\tssh-rsa {RSAPUB}\tcomment comment\r\nnext line", + "", + + "", "comment comment", []string{"localhost"}, "next line", + }, + { + "localhost,[host2:123]\tssh-rsa {RSAPUB}\tcomment comment", + "", + + "", "comment comment", []string{"localhost", "[host2:123]"}, "", + }, + { + "@marker \tlocalhost,[host2:123]\tssh-rsa {RSAPUB}", + "", + + "marker", "", []string{"localhost", "[host2:123]"}, "", + }, + { + "@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd", + "short read", + + "", "", nil, "", + }, +} + +func TestKnownHostsParsing(t *testing.T) { + rsaPub, rsaPubSerialized := getTestKey() + + for i, test := range knownHostsParseTests { + var expectedKey PublicKey + const rsaKeyToken = "{RSAPUB}" + + input := test.input + if strings.Contains(input, rsaKeyToken) { + expectedKey = rsaPub + input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1) + } + + marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input)) + if err != nil { + if len(test.err) == 0 { + t.Errorf("#%d: unexpectedly failed with %q", i, err) + } else if !strings.Contains(err.Error(), test.err) { + t.Errorf("#%d: expected error containing %q, but got %q", i, test.err, err) + } + continue + } else if len(test.err) != 0 { + t.Errorf("#%d: succeeded but expected error including %q", i, test.err) + continue + } + + if !reflect.DeepEqual(expectedKey, pubKey) { + t.Errorf("#%d: expected key %#v, but got %#v", i, expectedKey, pubKey) + } + + if marker != test.marker { + t.Errorf("#%d: expected marker %q, but got %q", i, test.marker, marker) + } + + if comment != test.comment { + t.Errorf("#%d: expected comment %q, but got %q", i, test.comment, comment) + } + + if !reflect.DeepEqual(test.hosts, hosts) { + t.Errorf("#%d: expected hosts %#v, but got %#v", i, test.hosts, hosts) + } + + if rest := string(rest); rest != test.rest { + t.Errorf("#%d: expected remaining input to be %q, but got %q", i, test.rest, rest) + } + } +} + +func TestFingerprintLegacyMD5(t *testing.T) { + pub, _ := getTestKey() + fingerprint := FingerprintLegacyMD5(pub) + want := "fb:61:6d:1a:e3:f0:95:45:3c:a0:79:be:4a:93:63:66" // ssh-keygen -lf -E md5 rsa + if fingerprint != want { + t.Errorf("got fingerprint %q want %q", fingerprint, want) + } +} + +func TestFingerprintSHA256(t *testing.T) { + pub, _ := getTestKey() + fingerprint := FingerprintSHA256(pub) + want := "SHA256:Anr3LjZK8YVpjrxu79myrW9Hrb/wpcMNpVvTq/RcBm8" // ssh-keygen -lf rsa + if fingerprint != want { + t.Errorf("got fingerprint %q want %q", fingerprint, want) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/mac.go b/vendor/golang.org/x/crypto/ssh/mac.go new file mode 100644 index 0000000..c07a062 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/mac.go @@ -0,0 +1,61 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +// Message authentication support + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "hash" +) + +type macMode struct { + keySize int + etm bool + new func(key []byte) hash.Hash +} + +// truncatingMAC wraps around a hash.Hash and truncates the output digest to +// a given size. +type truncatingMAC struct { + length int + hmac hash.Hash +} + +func (t truncatingMAC) Write(data []byte) (int, error) { + return t.hmac.Write(data) +} + +func (t truncatingMAC) Sum(in []byte) []byte { + out := t.hmac.Sum(in) + return out[:len(in)+t.length] +} + +func (t truncatingMAC) Reset() { + t.hmac.Reset() +} + +func (t truncatingMAC) Size() int { + return t.length +} + +func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() } + +var macModes = map[string]*macMode{ + "hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash { + return hmac.New(sha256.New, key) + }}, + "hmac-sha2-256": {32, false, func(key []byte) hash.Hash { + return hmac.New(sha256.New, key) + }}, + "hmac-sha1": {20, false, func(key []byte) hash.Hash { + return hmac.New(sha1.New, key) + }}, + "hmac-sha1-96": {20, false, func(key []byte) hash.Hash { + return truncatingMAC{12, hmac.New(sha1.New, key)} + }}, +} diff --git a/vendor/golang.org/x/crypto/ssh/mempipe_test.go b/vendor/golang.org/x/crypto/ssh/mempipe_test.go new file mode 100644 index 0000000..8697cd6 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/mempipe_test.go @@ -0,0 +1,110 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "io" + "sync" + "testing" +) + +// An in-memory packetConn. It is safe to call Close and writePacket +// from different goroutines. +type memTransport struct { + eof bool + pending [][]byte + write *memTransport + sync.Mutex + *sync.Cond +} + +func (t *memTransport) readPacket() ([]byte, error) { + t.Lock() + defer t.Unlock() + for { + if len(t.pending) > 0 { + r := t.pending[0] + t.pending = t.pending[1:] + return r, nil + } + if t.eof { + return nil, io.EOF + } + t.Cond.Wait() + } +} + +func (t *memTransport) closeSelf() error { + t.Lock() + defer t.Unlock() + if t.eof { + return io.EOF + } + t.eof = true + t.Cond.Broadcast() + return nil +} + +func (t *memTransport) Close() error { + err := t.write.closeSelf() + t.closeSelf() + return err +} + +func (t *memTransport) writePacket(p []byte) error { + t.write.Lock() + defer t.write.Unlock() + if t.write.eof { + return io.EOF + } + c := make([]byte, len(p)) + copy(c, p) + t.write.pending = append(t.write.pending, c) + t.write.Cond.Signal() + return nil +} + +func memPipe() (a, b packetConn) { + t1 := memTransport{} + t2 := memTransport{} + t1.write = &t2 + t2.write = &t1 + t1.Cond = sync.NewCond(&t1.Mutex) + t2.Cond = sync.NewCond(&t2.Mutex) + return &t1, &t2 +} + +func TestMemPipe(t *testing.T) { + a, b := memPipe() + if err := a.writePacket([]byte{42}); err != nil { + t.Fatalf("writePacket: %v", err) + } + if err := a.Close(); err != nil { + t.Fatal("Close: ", err) + } + p, err := b.readPacket() + if err != nil { + t.Fatal("readPacket: ", err) + } + if len(p) != 1 || p[0] != 42 { + t.Fatalf("got %v, want {42}", p) + } + p, err = b.readPacket() + if err != io.EOF { + t.Fatalf("got %v, %v, want EOF", p, err) + } +} + +func TestDoubleClose(t *testing.T) { + a, _ := memPipe() + err := a.Close() + if err != nil { + t.Errorf("Close: %v", err) + } + err = a.Close() + if err != io.EOF { + t.Errorf("expect EOF on double close.") + } +} diff --git a/vendor/golang.org/x/crypto/ssh/messages.go b/vendor/golang.org/x/crypto/ssh/messages.go new file mode 100644 index 0000000..08d2811 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/messages.go @@ -0,0 +1,766 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + "reflect" + "strconv" + "strings" +) + +// These are SSH message type numbers. They are scattered around several +// documents but many were taken from [SSH-PARAMETERS]. +const ( + msgIgnore = 2 + msgUnimplemented = 3 + msgDebug = 4 + msgNewKeys = 21 +) + +// SSH messages: +// +// These structures mirror the wire format of the corresponding SSH messages. +// They are marshaled using reflection with the marshal and unmarshal functions +// in this file. The only wrinkle is that a final member of type []byte with a +// ssh tag of "rest" receives the remainder of a packet when unmarshaling. + +// See RFC 4253, section 11.1. +const msgDisconnect = 1 + +// disconnectMsg is the message that signals a disconnect. It is also +// the error type returned from mux.Wait() +type disconnectMsg struct { + Reason uint32 `sshtype:"1"` + Message string + Language string +} + +func (d *disconnectMsg) Error() string { + return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message) +} + +// See RFC 4253, section 7.1. +const msgKexInit = 20 + +type kexInitMsg struct { + Cookie [16]byte `sshtype:"20"` + KexAlgos []string + ServerHostKeyAlgos []string + CiphersClientServer []string + CiphersServerClient []string + MACsClientServer []string + MACsServerClient []string + CompressionClientServer []string + CompressionServerClient []string + LanguagesClientServer []string + LanguagesServerClient []string + FirstKexFollows bool + Reserved uint32 +} + +// See RFC 4253, section 8. + +// Diffie-Helman +const msgKexDHInit = 30 + +type kexDHInitMsg struct { + X *big.Int `sshtype:"30"` +} + +const msgKexECDHInit = 30 + +type kexECDHInitMsg struct { + ClientPubKey []byte `sshtype:"30"` +} + +const msgKexECDHReply = 31 + +type kexECDHReplyMsg struct { + HostKey []byte `sshtype:"31"` + EphemeralPubKey []byte + Signature []byte +} + +const msgKexDHReply = 31 + +type kexDHReplyMsg struct { + HostKey []byte `sshtype:"31"` + Y *big.Int + Signature []byte +} + +// See RFC 4253, section 10. +const msgServiceRequest = 5 + +type serviceRequestMsg struct { + Service string `sshtype:"5"` +} + +// See RFC 4253, section 10. +const msgServiceAccept = 6 + +type serviceAcceptMsg struct { + Service string `sshtype:"6"` +} + +// See RFC 4252, section 5. +const msgUserAuthRequest = 50 + +type userAuthRequestMsg struct { + User string `sshtype:"50"` + Service string + Method string + Payload []byte `ssh:"rest"` +} + +// Used for debug printouts of packets. +type userAuthSuccessMsg struct { +} + +// See RFC 4252, section 5.1 +const msgUserAuthFailure = 51 + +type userAuthFailureMsg struct { + Methods []string `sshtype:"51"` + PartialSuccess bool +} + +// See RFC 4252, section 5.1 +const msgUserAuthSuccess = 52 + +// See RFC 4252, section 5.4 +const msgUserAuthBanner = 53 + +type userAuthBannerMsg struct { + Message string `sshtype:"53"` + // unused, but required to allow message parsing + Language string +} + +// See RFC 4256, section 3.2 +const msgUserAuthInfoRequest = 60 +const msgUserAuthInfoResponse = 61 + +type userAuthInfoRequestMsg struct { + User string `sshtype:"60"` + Instruction string + DeprecatedLanguage string + NumPrompts uint32 + Prompts []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpen = 90 + +type channelOpenMsg struct { + ChanType string `sshtype:"90"` + PeersID uint32 + PeersWindow uint32 + MaxPacketSize uint32 + TypeSpecificData []byte `ssh:"rest"` +} + +const msgChannelExtendedData = 95 +const msgChannelData = 94 + +// Used for debug print outs of packets. +type channelDataMsg struct { + PeersID uint32 `sshtype:"94"` + Length uint32 + Rest []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpenConfirm = 91 + +type channelOpenConfirmMsg struct { + PeersID uint32 `sshtype:"91"` + MyID uint32 + MyWindow uint32 + MaxPacketSize uint32 + TypeSpecificData []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpenFailure = 92 + +type channelOpenFailureMsg struct { + PeersID uint32 `sshtype:"92"` + Reason RejectionReason + Message string + Language string +} + +const msgChannelRequest = 98 + +type channelRequestMsg struct { + PeersID uint32 `sshtype:"98"` + Request string + WantReply bool + RequestSpecificData []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.4. +const msgChannelSuccess = 99 + +type channelRequestSuccessMsg struct { + PeersID uint32 `sshtype:"99"` +} + +// See RFC 4254, section 5.4. +const msgChannelFailure = 100 + +type channelRequestFailureMsg struct { + PeersID uint32 `sshtype:"100"` +} + +// See RFC 4254, section 5.3 +const msgChannelClose = 97 + +type channelCloseMsg struct { + PeersID uint32 `sshtype:"97"` +} + +// See RFC 4254, section 5.3 +const msgChannelEOF = 96 + +type channelEOFMsg struct { + PeersID uint32 `sshtype:"96"` +} + +// See RFC 4254, section 4 +const msgGlobalRequest = 80 + +type globalRequestMsg struct { + Type string `sshtype:"80"` + WantReply bool + Data []byte `ssh:"rest"` +} + +// See RFC 4254, section 4 +const msgRequestSuccess = 81 + +type globalRequestSuccessMsg struct { + Data []byte `ssh:"rest" sshtype:"81"` +} + +// See RFC 4254, section 4 +const msgRequestFailure = 82 + +type globalRequestFailureMsg struct { + Data []byte `ssh:"rest" sshtype:"82"` +} + +// See RFC 4254, section 5.2 +const msgChannelWindowAdjust = 93 + +type windowAdjustMsg struct { + PeersID uint32 `sshtype:"93"` + AdditionalBytes uint32 +} + +// See RFC 4252, section 7 +const msgUserAuthPubKeyOk = 60 + +type userAuthPubKeyOkMsg struct { + Algo string `sshtype:"60"` + PubKey []byte +} + +// typeTags returns the possible type bytes for the given reflect.Type, which +// should be a struct. The possible values are separated by a '|' character. +func typeTags(structType reflect.Type) (tags []byte) { + tagStr := structType.Field(0).Tag.Get("sshtype") + + for _, tag := range strings.Split(tagStr, "|") { + i, err := strconv.Atoi(tag) + if err == nil { + tags = append(tags, byte(i)) + } + } + + return tags +} + +func fieldError(t reflect.Type, field int, problem string) error { + if problem != "" { + problem = ": " + problem + } + return fmt.Errorf("ssh: unmarshal error for field %s of type %s%s", t.Field(field).Name, t.Name(), problem) +} + +var errShortRead = errors.New("ssh: short read") + +// Unmarshal parses data in SSH wire format into a structure. The out +// argument should be a pointer to struct. If the first member of the +// struct has the "sshtype" tag set to a '|'-separated set of numbers +// in decimal, the packet must start with one of those numbers. In +// case of error, Unmarshal returns a ParseError or +// UnexpectedMessageError. +func Unmarshal(data []byte, out interface{}) error { + v := reflect.ValueOf(out).Elem() + structType := v.Type() + expectedTypes := typeTags(structType) + + var expectedType byte + if len(expectedTypes) > 0 { + expectedType = expectedTypes[0] + } + + if len(data) == 0 { + return parseError(expectedType) + } + + if len(expectedTypes) > 0 { + goodType := false + for _, e := range expectedTypes { + if e > 0 && data[0] == e { + goodType = true + break + } + } + if !goodType { + return fmt.Errorf("ssh: unexpected message type %d (expected one of %v)", data[0], expectedTypes) + } + data = data[1:] + } + + var ok bool + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + t := field.Type() + switch t.Kind() { + case reflect.Bool: + if len(data) < 1 { + return errShortRead + } + field.SetBool(data[0] != 0) + data = data[1:] + case reflect.Array: + if t.Elem().Kind() != reflect.Uint8 { + return fieldError(structType, i, "array of unsupported type") + } + if len(data) < t.Len() { + return errShortRead + } + for j, n := 0, t.Len(); j < n; j++ { + field.Index(j).Set(reflect.ValueOf(data[j])) + } + data = data[t.Len():] + case reflect.Uint64: + var u64 uint64 + if u64, data, ok = parseUint64(data); !ok { + return errShortRead + } + field.SetUint(u64) + case reflect.Uint32: + var u32 uint32 + if u32, data, ok = parseUint32(data); !ok { + return errShortRead + } + field.SetUint(uint64(u32)) + case reflect.Uint8: + if len(data) < 1 { + return errShortRead + } + field.SetUint(uint64(data[0])) + data = data[1:] + case reflect.String: + var s []byte + if s, data, ok = parseString(data); !ok { + return fieldError(structType, i, "") + } + field.SetString(string(s)) + case reflect.Slice: + switch t.Elem().Kind() { + case reflect.Uint8: + if structType.Field(i).Tag.Get("ssh") == "rest" { + field.Set(reflect.ValueOf(data)) + data = nil + } else { + var s []byte + if s, data, ok = parseString(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(s)) + } + case reflect.String: + var nl []string + if nl, data, ok = parseNameList(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(nl)) + default: + return fieldError(structType, i, "slice of unsupported type") + } + case reflect.Ptr: + if t == bigIntType { + var n *big.Int + if n, data, ok = parseInt(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(n)) + } else { + return fieldError(structType, i, "pointer to unsupported type") + } + default: + return fieldError(structType, i, fmt.Sprintf("unsupported type: %v", t)) + } + } + + if len(data) != 0 { + return parseError(expectedType) + } + + return nil +} + +// Marshal serializes the message in msg to SSH wire format. The msg +// argument should be a struct or pointer to struct. If the first +// member has the "sshtype" tag set to a number in decimal, that +// number is prepended to the result. If the last of member has the +// "ssh" tag set to "rest", its contents are appended to the output. +func Marshal(msg interface{}) []byte { + out := make([]byte, 0, 64) + return marshalStruct(out, msg) +} + +func marshalStruct(out []byte, msg interface{}) []byte { + v := reflect.Indirect(reflect.ValueOf(msg)) + msgTypes := typeTags(v.Type()) + if len(msgTypes) > 0 { + out = append(out, msgTypes[0]) + } + + for i, n := 0, v.NumField(); i < n; i++ { + field := v.Field(i) + switch t := field.Type(); t.Kind() { + case reflect.Bool: + var v uint8 + if field.Bool() { + v = 1 + } + out = append(out, v) + case reflect.Array: + if t.Elem().Kind() != reflect.Uint8 { + panic(fmt.Sprintf("array of non-uint8 in field %d: %T", i, field.Interface())) + } + for j, l := 0, t.Len(); j < l; j++ { + out = append(out, uint8(field.Index(j).Uint())) + } + case reflect.Uint32: + out = appendU32(out, uint32(field.Uint())) + case reflect.Uint64: + out = appendU64(out, uint64(field.Uint())) + case reflect.Uint8: + out = append(out, uint8(field.Uint())) + case reflect.String: + s := field.String() + out = appendInt(out, len(s)) + out = append(out, s...) + case reflect.Slice: + switch t.Elem().Kind() { + case reflect.Uint8: + if v.Type().Field(i).Tag.Get("ssh") != "rest" { + out = appendInt(out, field.Len()) + } + out = append(out, field.Bytes()...) + case reflect.String: + offset := len(out) + out = appendU32(out, 0) + if n := field.Len(); n > 0 { + for j := 0; j < n; j++ { + f := field.Index(j) + if j != 0 { + out = append(out, ',') + } + out = append(out, f.String()...) + } + // overwrite length value + binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4)) + } + default: + panic(fmt.Sprintf("slice of unknown type in field %d: %T", i, field.Interface())) + } + case reflect.Ptr: + if t == bigIntType { + var n *big.Int + nValue := reflect.ValueOf(&n) + nValue.Elem().Set(field) + needed := intLength(n) + oldLength := len(out) + + if cap(out)-len(out) < needed { + newOut := make([]byte, len(out), 2*(len(out)+needed)) + copy(newOut, out) + out = newOut + } + out = out[:oldLength+needed] + marshalInt(out[oldLength:], n) + } else { + panic(fmt.Sprintf("pointer to unknown type in field %d: %T", i, field.Interface())) + } + } + } + + return out +} + +var bigOne = big.NewInt(1) + +func parseString(in []byte) (out, rest []byte, ok bool) { + if len(in) < 4 { + return + } + length := binary.BigEndian.Uint32(in) + in = in[4:] + if uint32(len(in)) < length { + return + } + out = in[:length] + rest = in[length:] + ok = true + return +} + +var ( + comma = []byte{','} + emptyNameList = []string{} +) + +func parseNameList(in []byte) (out []string, rest []byte, ok bool) { + contents, rest, ok := parseString(in) + if !ok { + return + } + if len(contents) == 0 { + out = emptyNameList + return + } + parts := bytes.Split(contents, comma) + out = make([]string, len(parts)) + for i, part := range parts { + out[i] = string(part) + } + return +} + +func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) { + contents, rest, ok := parseString(in) + if !ok { + return + } + out = new(big.Int) + + if len(contents) > 0 && contents[0]&0x80 == 0x80 { + // This is a negative number + notBytes := make([]byte, len(contents)) + for i := range notBytes { + notBytes[i] = ^contents[i] + } + out.SetBytes(notBytes) + out.Add(out, bigOne) + out.Neg(out) + } else { + // Positive number + out.SetBytes(contents) + } + ok = true + return +} + +func parseUint32(in []byte) (uint32, []byte, bool) { + if len(in) < 4 { + return 0, nil, false + } + return binary.BigEndian.Uint32(in), in[4:], true +} + +func parseUint64(in []byte) (uint64, []byte, bool) { + if len(in) < 8 { + return 0, nil, false + } + return binary.BigEndian.Uint64(in), in[8:], true +} + +func intLength(n *big.Int) int { + length := 4 /* length bytes */ + if n.Sign() < 0 { + nMinus1 := new(big.Int).Neg(n) + nMinus1.Sub(nMinus1, bigOne) + bitLen := nMinus1.BitLen() + if bitLen%8 == 0 { + // The number will need 0xff padding + length++ + } + length += (bitLen + 7) / 8 + } else if n.Sign() == 0 { + // A zero is the zero length string + } else { + bitLen := n.BitLen() + if bitLen%8 == 0 { + // The number will need 0x00 padding + length++ + } + length += (bitLen + 7) / 8 + } + + return length +} + +func marshalUint32(to []byte, n uint32) []byte { + binary.BigEndian.PutUint32(to, n) + return to[4:] +} + +func marshalUint64(to []byte, n uint64) []byte { + binary.BigEndian.PutUint64(to, n) + return to[8:] +} + +func marshalInt(to []byte, n *big.Int) []byte { + lengthBytes := to + to = to[4:] + length := 0 + + if n.Sign() < 0 { + // A negative number has to be converted to two's-complement + // form. So we'll subtract 1 and invert. If the + // most-significant-bit isn't set then we'll need to pad the + // beginning with 0xff in order to keep the number negative. + nMinus1 := new(big.Int).Neg(n) + nMinus1.Sub(nMinus1, bigOne) + bytes := nMinus1.Bytes() + for i := range bytes { + bytes[i] ^= 0xff + } + if len(bytes) == 0 || bytes[0]&0x80 == 0 { + to[0] = 0xff + to = to[1:] + length++ + } + nBytes := copy(to, bytes) + to = to[nBytes:] + length += nBytes + } else if n.Sign() == 0 { + // A zero is the zero length string + } else { + bytes := n.Bytes() + if len(bytes) > 0 && bytes[0]&0x80 != 0 { + // We'll have to pad this with a 0x00 in order to + // stop it looking like a negative number. + to[0] = 0 + to = to[1:] + length++ + } + nBytes := copy(to, bytes) + to = to[nBytes:] + length += nBytes + } + + lengthBytes[0] = byte(length >> 24) + lengthBytes[1] = byte(length >> 16) + lengthBytes[2] = byte(length >> 8) + lengthBytes[3] = byte(length) + return to +} + +func writeInt(w io.Writer, n *big.Int) { + length := intLength(n) + buf := make([]byte, length) + marshalInt(buf, n) + w.Write(buf) +} + +func writeString(w io.Writer, s []byte) { + var lengthBytes [4]byte + lengthBytes[0] = byte(len(s) >> 24) + lengthBytes[1] = byte(len(s) >> 16) + lengthBytes[2] = byte(len(s) >> 8) + lengthBytes[3] = byte(len(s)) + w.Write(lengthBytes[:]) + w.Write(s) +} + +func stringLength(n int) int { + return 4 + n +} + +func marshalString(to []byte, s []byte) []byte { + to[0] = byte(len(s) >> 24) + to[1] = byte(len(s) >> 16) + to[2] = byte(len(s) >> 8) + to[3] = byte(len(s)) + to = to[4:] + copy(to, s) + return to[len(s):] +} + +var bigIntType = reflect.TypeOf((*big.Int)(nil)) + +// Decode a packet into its corresponding message. +func decode(packet []byte) (interface{}, error) { + var msg interface{} + switch packet[0] { + case msgDisconnect: + msg = new(disconnectMsg) + case msgServiceRequest: + msg = new(serviceRequestMsg) + case msgServiceAccept: + msg = new(serviceAcceptMsg) + case msgKexInit: + msg = new(kexInitMsg) + case msgKexDHInit: + msg = new(kexDHInitMsg) + case msgKexDHReply: + msg = new(kexDHReplyMsg) + case msgUserAuthRequest: + msg = new(userAuthRequestMsg) + case msgUserAuthSuccess: + return new(userAuthSuccessMsg), nil + case msgUserAuthFailure: + msg = new(userAuthFailureMsg) + case msgUserAuthPubKeyOk: + msg = new(userAuthPubKeyOkMsg) + case msgGlobalRequest: + msg = new(globalRequestMsg) + case msgRequestSuccess: + msg = new(globalRequestSuccessMsg) + case msgRequestFailure: + msg = new(globalRequestFailureMsg) + case msgChannelOpen: + msg = new(channelOpenMsg) + case msgChannelData: + msg = new(channelDataMsg) + case msgChannelOpenConfirm: + msg = new(channelOpenConfirmMsg) + case msgChannelOpenFailure: + msg = new(channelOpenFailureMsg) + case msgChannelWindowAdjust: + msg = new(windowAdjustMsg) + case msgChannelEOF: + msg = new(channelEOFMsg) + case msgChannelClose: + msg = new(channelCloseMsg) + case msgChannelRequest: + msg = new(channelRequestMsg) + case msgChannelSuccess: + msg = new(channelRequestSuccessMsg) + case msgChannelFailure: + msg = new(channelRequestFailureMsg) + default: + return nil, unexpectedMessageError(0, packet[0]) + } + if err := Unmarshal(packet, msg); err != nil { + return nil, err + } + return msg, nil +} diff --git a/vendor/golang.org/x/crypto/ssh/messages_test.go b/vendor/golang.org/x/crypto/ssh/messages_test.go new file mode 100644 index 0000000..e790764 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/messages_test.go @@ -0,0 +1,288 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "math/big" + "math/rand" + "reflect" + "testing" + "testing/quick" +) + +var intLengthTests = []struct { + val, length int +}{ + {0, 4 + 0}, + {1, 4 + 1}, + {127, 4 + 1}, + {128, 4 + 2}, + {-1, 4 + 1}, +} + +func TestIntLength(t *testing.T) { + for _, test := range intLengthTests { + v := new(big.Int).SetInt64(int64(test.val)) + length := intLength(v) + if length != test.length { + t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length) + } + } +} + +type msgAllTypes struct { + Bool bool `sshtype:"21"` + Array [16]byte + Uint64 uint64 + Uint32 uint32 + Uint8 uint8 + String string + Strings []string + Bytes []byte + Int *big.Int + Rest []byte `ssh:"rest"` +} + +func (t *msgAllTypes) Generate(rand *rand.Rand, size int) reflect.Value { + m := &msgAllTypes{} + m.Bool = rand.Intn(2) == 1 + randomBytes(m.Array[:], rand) + m.Uint64 = uint64(rand.Int63n(1<<63 - 1)) + m.Uint32 = uint32(rand.Intn((1 << 31) - 1)) + m.Uint8 = uint8(rand.Intn(1 << 8)) + m.String = string(m.Array[:]) + m.Strings = randomNameList(rand) + m.Bytes = m.Array[:] + m.Int = randomInt(rand) + m.Rest = m.Array[:] + return reflect.ValueOf(m) +} + +func TestMarshalUnmarshal(t *testing.T) { + rand := rand.New(rand.NewSource(0)) + iface := &msgAllTypes{} + ty := reflect.ValueOf(iface).Type() + + n := 100 + if testing.Short() { + n = 5 + } + for j := 0; j < n; j++ { + v, ok := quick.Value(ty, rand) + if !ok { + t.Errorf("failed to create value") + break + } + + m1 := v.Elem().Interface() + m2 := iface + + marshaled := Marshal(m1) + if err := Unmarshal(marshaled, m2); err != nil { + t.Errorf("Unmarshal %#v: %s", m1, err) + break + } + + if !reflect.DeepEqual(v.Interface(), m2) { + t.Errorf("got: %#v\nwant:%#v\n%x", m2, m1, marshaled) + break + } + } +} + +func TestUnmarshalEmptyPacket(t *testing.T) { + var b []byte + var m channelRequestSuccessMsg + if err := Unmarshal(b, &m); err == nil { + t.Fatalf("unmarshal of empty slice succeeded") + } +} + +func TestUnmarshalUnexpectedPacket(t *testing.T) { + type S struct { + I uint32 `sshtype:"43"` + S string + B bool + } + + s := S{11, "hello", true} + packet := Marshal(s) + packet[0] = 42 + roundtrip := S{} + err := Unmarshal(packet, &roundtrip) + if err == nil { + t.Fatal("expected error, not nil") + } +} + +func TestMarshalPtr(t *testing.T) { + s := struct { + S string + }{"hello"} + + m1 := Marshal(s) + m2 := Marshal(&s) + if !bytes.Equal(m1, m2) { + t.Errorf("got %q, want %q for marshaled pointer", m2, m1) + } +} + +func TestBareMarshalUnmarshal(t *testing.T) { + type S struct { + I uint32 + S string + B bool + } + + s := S{42, "hello", true} + packet := Marshal(s) + roundtrip := S{} + Unmarshal(packet, &roundtrip) + + if !reflect.DeepEqual(s, roundtrip) { + t.Errorf("got %#v, want %#v", roundtrip, s) + } +} + +func TestBareMarshal(t *testing.T) { + type S2 struct { + I uint32 + } + s := S2{42} + packet := Marshal(s) + i, rest, ok := parseUint32(packet) + if len(rest) > 0 || !ok { + t.Errorf("parseInt(%q): parse error", packet) + } + if i != s.I { + t.Errorf("got %d, want %d", i, s.I) + } +} + +func TestUnmarshalShortKexInitPacket(t *testing.T) { + // This used to panic. + // Issue 11348 + packet := []byte{0x14, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xff, 0xff, 0xff} + kim := &kexInitMsg{} + if err := Unmarshal(packet, kim); err == nil { + t.Error("truncated packet unmarshaled without error") + } +} + +func TestMarshalMultiTag(t *testing.T) { + var res struct { + A uint32 `sshtype:"1|2"` + } + + good1 := struct { + A uint32 `sshtype:"1"` + }{ + 1, + } + good2 := struct { + A uint32 `sshtype:"2"` + }{ + 1, + } + + if e := Unmarshal(Marshal(good1), &res); e != nil { + t.Errorf("error unmarshaling multipart tag: %v", e) + } + + if e := Unmarshal(Marshal(good2), &res); e != nil { + t.Errorf("error unmarshaling multipart tag: %v", e) + } + + bad1 := struct { + A uint32 `sshtype:"3"` + }{ + 1, + } + if e := Unmarshal(Marshal(bad1), &res); e == nil { + t.Errorf("bad struct unmarshaled without error") + } +} + +func randomBytes(out []byte, rand *rand.Rand) { + for i := 0; i < len(out); i++ { + out[i] = byte(rand.Int31()) + } +} + +func randomNameList(rand *rand.Rand) []string { + ret := make([]string, rand.Int31()&15) + for i := range ret { + s := make([]byte, 1+(rand.Int31()&15)) + for j := range s { + s[j] = 'a' + uint8(rand.Int31()&15) + } + ret[i] = string(s) + } + return ret +} + +func randomInt(rand *rand.Rand) *big.Int { + return new(big.Int).SetInt64(int64(int32(rand.Uint32()))) +} + +func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value { + ki := &kexInitMsg{} + randomBytes(ki.Cookie[:], rand) + ki.KexAlgos = randomNameList(rand) + ki.ServerHostKeyAlgos = randomNameList(rand) + ki.CiphersClientServer = randomNameList(rand) + ki.CiphersServerClient = randomNameList(rand) + ki.MACsClientServer = randomNameList(rand) + ki.MACsServerClient = randomNameList(rand) + ki.CompressionClientServer = randomNameList(rand) + ki.CompressionServerClient = randomNameList(rand) + ki.LanguagesClientServer = randomNameList(rand) + ki.LanguagesServerClient = randomNameList(rand) + if rand.Int31()&1 == 1 { + ki.FirstKexFollows = true + } + return reflect.ValueOf(ki) +} + +func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value { + dhi := &kexDHInitMsg{} + dhi.X = randomInt(rand) + return reflect.ValueOf(dhi) +} + +var ( + _kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() + _kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface() + + _kexInit = Marshal(_kexInitMsg) + _kexDHInit = Marshal(_kexDHInitMsg) +) + +func BenchmarkMarshalKexInitMsg(b *testing.B) { + for i := 0; i < b.N; i++ { + Marshal(_kexInitMsg) + } +} + +func BenchmarkUnmarshalKexInitMsg(b *testing.B) { + m := new(kexInitMsg) + for i := 0; i < b.N; i++ { + Unmarshal(_kexInit, m) + } +} + +func BenchmarkMarshalKexDHInitMsg(b *testing.B) { + for i := 0; i < b.N; i++ { + Marshal(_kexDHInitMsg) + } +} + +func BenchmarkUnmarshalKexDHInitMsg(b *testing.B) { + m := new(kexDHInitMsg) + for i := 0; i < b.N; i++ { + Unmarshal(_kexDHInit, m) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/mux.go b/vendor/golang.org/x/crypto/ssh/mux.go new file mode 100644 index 0000000..f190162 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/mux.go @@ -0,0 +1,330 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/binary" + "fmt" + "io" + "log" + "sync" + "sync/atomic" +) + +// debugMux, if set, causes messages in the connection protocol to be +// logged. +const debugMux = false + +// chanList is a thread safe channel list. +type chanList struct { + // protects concurrent access to chans + sync.Mutex + + // chans are indexed by the local id of the channel, which the + // other side should send in the PeersId field. + chans []*channel + + // This is a debugging aid: it offsets all IDs by this + // amount. This helps distinguish otherwise identical + // server/client muxes + offset uint32 +} + +// Assigns a channel ID to the given channel. +func (c *chanList) add(ch *channel) uint32 { + c.Lock() + defer c.Unlock() + for i := range c.chans { + if c.chans[i] == nil { + c.chans[i] = ch + return uint32(i) + c.offset + } + } + c.chans = append(c.chans, ch) + return uint32(len(c.chans)-1) + c.offset +} + +// getChan returns the channel for the given ID. +func (c *chanList) getChan(id uint32) *channel { + id -= c.offset + + c.Lock() + defer c.Unlock() + if id < uint32(len(c.chans)) { + return c.chans[id] + } + return nil +} + +func (c *chanList) remove(id uint32) { + id -= c.offset + c.Lock() + if id < uint32(len(c.chans)) { + c.chans[id] = nil + } + c.Unlock() +} + +// dropAll forgets all channels it knows, returning them in a slice. +func (c *chanList) dropAll() []*channel { + c.Lock() + defer c.Unlock() + var r []*channel + + for _, ch := range c.chans { + if ch == nil { + continue + } + r = append(r, ch) + } + c.chans = nil + return r +} + +// mux represents the state for the SSH connection protocol, which +// multiplexes many channels onto a single packet transport. +type mux struct { + conn packetConn + chanList chanList + + incomingChannels chan NewChannel + + globalSentMu sync.Mutex + globalResponses chan interface{} + incomingRequests chan *Request + + errCond *sync.Cond + err error +} + +// When debugging, each new chanList instantiation has a different +// offset. +var globalOff uint32 + +func (m *mux) Wait() error { + m.errCond.L.Lock() + defer m.errCond.L.Unlock() + for m.err == nil { + m.errCond.Wait() + } + return m.err +} + +// newMux returns a mux that runs over the given connection. +func newMux(p packetConn) *mux { + m := &mux{ + conn: p, + incomingChannels: make(chan NewChannel, chanSize), + globalResponses: make(chan interface{}, 1), + incomingRequests: make(chan *Request, chanSize), + errCond: newCond(), + } + if debugMux { + m.chanList.offset = atomic.AddUint32(&globalOff, 1) + } + + go m.loop() + return m +} + +func (m *mux) sendMessage(msg interface{}) error { + p := Marshal(msg) + if debugMux { + log.Printf("send global(%d): %#v", m.chanList.offset, msg) + } + return m.conn.writePacket(p) +} + +func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) { + if wantReply { + m.globalSentMu.Lock() + defer m.globalSentMu.Unlock() + } + + if err := m.sendMessage(globalRequestMsg{ + Type: name, + WantReply: wantReply, + Data: payload, + }); err != nil { + return false, nil, err + } + + if !wantReply { + return false, nil, nil + } + + msg, ok := <-m.globalResponses + if !ok { + return false, nil, io.EOF + } + switch msg := msg.(type) { + case *globalRequestFailureMsg: + return false, msg.Data, nil + case *globalRequestSuccessMsg: + return true, msg.Data, nil + default: + return false, nil, fmt.Errorf("ssh: unexpected response to request: %#v", msg) + } +} + +// ackRequest must be called after processing a global request that +// has WantReply set. +func (m *mux) ackRequest(ok bool, data []byte) error { + if ok { + return m.sendMessage(globalRequestSuccessMsg{Data: data}) + } + return m.sendMessage(globalRequestFailureMsg{Data: data}) +} + +func (m *mux) Close() error { + return m.conn.Close() +} + +// loop runs the connection machine. It will process packets until an +// error is encountered. To synchronize on loop exit, use mux.Wait. +func (m *mux) loop() { + var err error + for err == nil { + err = m.onePacket() + } + + for _, ch := range m.chanList.dropAll() { + ch.close() + } + + close(m.incomingChannels) + close(m.incomingRequests) + close(m.globalResponses) + + m.conn.Close() + + m.errCond.L.Lock() + m.err = err + m.errCond.Broadcast() + m.errCond.L.Unlock() + + if debugMux { + log.Println("loop exit", err) + } +} + +// onePacket reads and processes one packet. +func (m *mux) onePacket() error { + packet, err := m.conn.readPacket() + if err != nil { + return err + } + + if debugMux { + if packet[0] == msgChannelData || packet[0] == msgChannelExtendedData { + log.Printf("decoding(%d): data packet - %d bytes", m.chanList.offset, len(packet)) + } else { + p, _ := decode(packet) + log.Printf("decoding(%d): %d %#v - %d bytes", m.chanList.offset, packet[0], p, len(packet)) + } + } + + switch packet[0] { + case msgChannelOpen: + return m.handleChannelOpen(packet) + case msgGlobalRequest, msgRequestSuccess, msgRequestFailure: + return m.handleGlobalPacket(packet) + } + + // assume a channel packet. + if len(packet) < 5 { + return parseError(packet[0]) + } + id := binary.BigEndian.Uint32(packet[1:]) + ch := m.chanList.getChan(id) + if ch == nil { + return fmt.Errorf("ssh: invalid channel %d", id) + } + + return ch.handlePacket(packet) +} + +func (m *mux) handleGlobalPacket(packet []byte) error { + msg, err := decode(packet) + if err != nil { + return err + } + + switch msg := msg.(type) { + case *globalRequestMsg: + m.incomingRequests <- &Request{ + Type: msg.Type, + WantReply: msg.WantReply, + Payload: msg.Data, + mux: m, + } + case *globalRequestSuccessMsg, *globalRequestFailureMsg: + m.globalResponses <- msg + default: + panic(fmt.Sprintf("not a global message %#v", msg)) + } + + return nil +} + +// handleChannelOpen schedules a channel to be Accept()ed. +func (m *mux) handleChannelOpen(packet []byte) error { + var msg channelOpenMsg + if err := Unmarshal(packet, &msg); err != nil { + return err + } + + if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { + failMsg := channelOpenFailureMsg{ + PeersID: msg.PeersID, + Reason: ConnectionFailed, + Message: "invalid request", + Language: "en_US.UTF-8", + } + return m.sendMessage(failMsg) + } + + c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData) + c.remoteId = msg.PeersID + c.maxRemotePayload = msg.MaxPacketSize + c.remoteWin.add(msg.PeersWindow) + m.incomingChannels <- c + return nil +} + +func (m *mux) OpenChannel(chanType string, extra []byte) (Channel, <-chan *Request, error) { + ch, err := m.openChannel(chanType, extra) + if err != nil { + return nil, nil, err + } + + return ch, ch.incomingRequests, nil +} + +func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) { + ch := m.newChannel(chanType, channelOutbound, extra) + + ch.maxIncomingPayload = channelMaxPacket + + open := channelOpenMsg{ + ChanType: chanType, + PeersWindow: ch.myWindow, + MaxPacketSize: ch.maxIncomingPayload, + TypeSpecificData: extra, + PeersID: ch.localId, + } + if err := m.sendMessage(open); err != nil { + return nil, err + } + + switch msg := (<-ch.msg).(type) { + case *channelOpenConfirmMsg: + return ch, nil + case *channelOpenFailureMsg: + return nil, &OpenChannelError{msg.Reason, msg.Message} + default: + return nil, fmt.Errorf("ssh: unexpected packet in response to channel open: %T", msg) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/mux_test.go b/vendor/golang.org/x/crypto/ssh/mux_test.go new file mode 100644 index 0000000..25d2181 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/mux_test.go @@ -0,0 +1,505 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "io" + "io/ioutil" + "sync" + "testing" +) + +func muxPair() (*mux, *mux) { + a, b := memPipe() + + s := newMux(a) + c := newMux(b) + + return s, c +} + +// Returns both ends of a channel, and the mux for the the 2nd +// channel. +func channelPair(t *testing.T) (*channel, *channel, *mux) { + c, s := muxPair() + + res := make(chan *channel, 1) + go func() { + newCh, ok := <-s.incomingChannels + if !ok { + t.Fatalf("No incoming channel") + } + if newCh.ChannelType() != "chan" { + t.Fatalf("got type %q want chan", newCh.ChannelType()) + } + ch, _, err := newCh.Accept() + if err != nil { + t.Fatalf("Accept %v", err) + } + res <- ch.(*channel) + }() + + ch, err := c.openChannel("chan", nil) + if err != nil { + t.Fatalf("OpenChannel: %v", err) + } + + return <-res, ch, c +} + +// Test that stderr and stdout can be addressed from different +// goroutines. This is intended for use with the race detector. +func TestMuxChannelExtendedThreadSafety(t *testing.T) { + writer, reader, mux := channelPair(t) + defer writer.Close() + defer reader.Close() + defer mux.Close() + + var wr, rd sync.WaitGroup + magic := "hello world" + + wr.Add(2) + go func() { + io.WriteString(writer, magic) + wr.Done() + }() + go func() { + io.WriteString(writer.Stderr(), magic) + wr.Done() + }() + + rd.Add(2) + go func() { + c, err := ioutil.ReadAll(reader) + if string(c) != magic { + t.Fatalf("stdout read got %q, want %q (error %s)", c, magic, err) + } + rd.Done() + }() + go func() { + c, err := ioutil.ReadAll(reader.Stderr()) + if string(c) != magic { + t.Fatalf("stderr read got %q, want %q (error %s)", c, magic, err) + } + rd.Done() + }() + + wr.Wait() + writer.CloseWrite() + rd.Wait() +} + +func TestMuxReadWrite(t *testing.T) { + s, c, mux := channelPair(t) + defer s.Close() + defer c.Close() + defer mux.Close() + + magic := "hello world" + magicExt := "hello stderr" + go func() { + _, err := s.Write([]byte(magic)) + if err != nil { + t.Fatalf("Write: %v", err) + } + _, err = s.Extended(1).Write([]byte(magicExt)) + if err != nil { + t.Fatalf("Write: %v", err) + } + err = s.Close() + if err != nil { + t.Fatalf("Close: %v", err) + } + }() + + var buf [1024]byte + n, err := c.Read(buf[:]) + if err != nil { + t.Fatalf("server Read: %v", err) + } + got := string(buf[:n]) + if got != magic { + t.Fatalf("server: got %q want %q", got, magic) + } + + n, err = c.Extended(1).Read(buf[:]) + if err != nil { + t.Fatalf("server Read: %v", err) + } + + got = string(buf[:n]) + if got != magicExt { + t.Fatalf("server: got %q want %q", got, magic) + } +} + +func TestMuxChannelOverflow(t *testing.T) { + reader, writer, mux := channelPair(t) + defer reader.Close() + defer writer.Close() + defer mux.Close() + + wDone := make(chan int, 1) + go func() { + if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { + t.Errorf("could not fill window: %v", err) + } + writer.Write(make([]byte, 1)) + wDone <- 1 + }() + writer.remoteWin.waitWriterBlocked() + + // Send 1 byte. + packet := make([]byte, 1+4+4+1) + packet[0] = msgChannelData + marshalUint32(packet[1:], writer.remoteId) + marshalUint32(packet[5:], uint32(1)) + packet[9] = 42 + + if err := writer.mux.conn.writePacket(packet); err != nil { + t.Errorf("could not send packet") + } + if _, err := reader.SendRequest("hello", true, nil); err == nil { + t.Errorf("SendRequest succeeded.") + } + <-wDone +} + +func TestMuxChannelCloseWriteUnblock(t *testing.T) { + reader, writer, mux := channelPair(t) + defer reader.Close() + defer writer.Close() + defer mux.Close() + + wDone := make(chan int, 1) + go func() { + if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { + t.Errorf("could not fill window: %v", err) + } + if _, err := writer.Write(make([]byte, 1)); err != io.EOF { + t.Errorf("got %v, want EOF for unblock write", err) + } + wDone <- 1 + }() + + writer.remoteWin.waitWriterBlocked() + reader.Close() + <-wDone +} + +func TestMuxConnectionCloseWriteUnblock(t *testing.T) { + reader, writer, mux := channelPair(t) + defer reader.Close() + defer writer.Close() + defer mux.Close() + + wDone := make(chan int, 1) + go func() { + if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil { + t.Errorf("could not fill window: %v", err) + } + if _, err := writer.Write(make([]byte, 1)); err != io.EOF { + t.Errorf("got %v, want EOF for unblock write", err) + } + wDone <- 1 + }() + + writer.remoteWin.waitWriterBlocked() + mux.Close() + <-wDone +} + +func TestMuxReject(t *testing.T) { + client, server := muxPair() + defer server.Close() + defer client.Close() + + go func() { + ch, ok := <-server.incomingChannels + if !ok { + t.Fatalf("Accept") + } + if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" { + t.Fatalf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData()) + } + ch.Reject(RejectionReason(42), "message") + }() + + ch, err := client.openChannel("ch", []byte("extra")) + if ch != nil { + t.Fatal("openChannel not rejected") + } + + ocf, ok := err.(*OpenChannelError) + if !ok { + t.Errorf("got %#v want *OpenChannelError", err) + } else if ocf.Reason != 42 || ocf.Message != "message" { + t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message") + } + + want := "ssh: rejected: unknown reason 42 (message)" + if err.Error() != want { + t.Errorf("got %q, want %q", err.Error(), want) + } +} + +func TestMuxChannelRequest(t *testing.T) { + client, server, mux := channelPair(t) + defer server.Close() + defer client.Close() + defer mux.Close() + + var received int + var wg sync.WaitGroup + wg.Add(1) + go func() { + for r := range server.incomingRequests { + received++ + r.Reply(r.Type == "yes", nil) + } + wg.Done() + }() + _, err := client.SendRequest("yes", false, nil) + if err != nil { + t.Fatalf("SendRequest: %v", err) + } + ok, err := client.SendRequest("yes", true, nil) + if err != nil { + t.Fatalf("SendRequest: %v", err) + } + + if !ok { + t.Errorf("SendRequest(yes): %v", ok) + + } + + ok, err = client.SendRequest("no", true, nil) + if err != nil { + t.Fatalf("SendRequest: %v", err) + } + if ok { + t.Errorf("SendRequest(no): %v", ok) + + } + + client.Close() + wg.Wait() + + if received != 3 { + t.Errorf("got %d requests, want %d", received, 3) + } +} + +func TestMuxGlobalRequest(t *testing.T) { + clientMux, serverMux := muxPair() + defer serverMux.Close() + defer clientMux.Close() + + var seen bool + go func() { + for r := range serverMux.incomingRequests { + seen = seen || r.Type == "peek" + if r.WantReply { + err := r.Reply(r.Type == "yes", + append([]byte(r.Type), r.Payload...)) + if err != nil { + t.Errorf("AckRequest: %v", err) + } + } + } + }() + + _, _, err := clientMux.SendRequest("peek", false, nil) + if err != nil { + t.Errorf("SendRequest: %v", err) + } + + ok, data, err := clientMux.SendRequest("yes", true, []byte("a")) + if !ok || string(data) != "yesa" || err != nil { + t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v", + ok, data, err) + } + if ok, data, err := clientMux.SendRequest("yes", true, []byte("a")); !ok || string(data) != "yesa" || err != nil { + t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v", + ok, data, err) + } + + if ok, data, err := clientMux.SendRequest("no", true, []byte("a")); ok || string(data) != "noa" || err != nil { + t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v", + ok, data, err) + } + + if !seen { + t.Errorf("never saw 'peek' request") + } +} + +func TestMuxGlobalRequestUnblock(t *testing.T) { + clientMux, serverMux := muxPair() + defer serverMux.Close() + defer clientMux.Close() + + result := make(chan error, 1) + go func() { + _, _, err := clientMux.SendRequest("hello", true, nil) + result <- err + }() + + <-serverMux.incomingRequests + serverMux.conn.Close() + err := <-result + + if err != io.EOF { + t.Errorf("want EOF, got %v", io.EOF) + } +} + +func TestMuxChannelRequestUnblock(t *testing.T) { + a, b, connB := channelPair(t) + defer a.Close() + defer b.Close() + defer connB.Close() + + result := make(chan error, 1) + go func() { + _, err := a.SendRequest("hello", true, nil) + result <- err + }() + + <-b.incomingRequests + connB.conn.Close() + err := <-result + + if err != io.EOF { + t.Errorf("want EOF, got %v", err) + } +} + +func TestMuxCloseChannel(t *testing.T) { + r, w, mux := channelPair(t) + defer mux.Close() + defer r.Close() + defer w.Close() + + result := make(chan error, 1) + go func() { + var b [1024]byte + _, err := r.Read(b[:]) + result <- err + }() + if err := w.Close(); err != nil { + t.Errorf("w.Close: %v", err) + } + + if _, err := w.Write([]byte("hello")); err != io.EOF { + t.Errorf("got err %v, want io.EOF after Close", err) + } + + if err := <-result; err != io.EOF { + t.Errorf("got %v (%T), want io.EOF", err, err) + } +} + +func TestMuxCloseWriteChannel(t *testing.T) { + r, w, mux := channelPair(t) + defer mux.Close() + + result := make(chan error, 1) + go func() { + var b [1024]byte + _, err := r.Read(b[:]) + result <- err + }() + if err := w.CloseWrite(); err != nil { + t.Errorf("w.CloseWrite: %v", err) + } + + if _, err := w.Write([]byte("hello")); err != io.EOF { + t.Errorf("got err %v, want io.EOF after CloseWrite", err) + } + + if err := <-result; err != io.EOF { + t.Errorf("got %v (%T), want io.EOF", err, err) + } +} + +func TestMuxInvalidRecord(t *testing.T) { + a, b := muxPair() + defer a.Close() + defer b.Close() + + packet := make([]byte, 1+4+4+1) + packet[0] = msgChannelData + marshalUint32(packet[1:], 29348723 /* invalid channel id */) + marshalUint32(packet[5:], 1) + packet[9] = 42 + + a.conn.writePacket(packet) + go a.SendRequest("hello", false, nil) + // 'a' wrote an invalid packet, so 'b' has exited. + req, ok := <-b.incomingRequests + if ok { + t.Errorf("got request %#v after receiving invalid packet", req) + } +} + +func TestZeroWindowAdjust(t *testing.T) { + a, b, mux := channelPair(t) + defer a.Close() + defer b.Close() + defer mux.Close() + + go func() { + io.WriteString(a, "hello") + // bogus adjust. + a.sendMessage(windowAdjustMsg{}) + io.WriteString(a, "world") + a.Close() + }() + + want := "helloworld" + c, _ := ioutil.ReadAll(b) + if string(c) != want { + t.Errorf("got %q want %q", c, want) + } +} + +func TestMuxMaxPacketSize(t *testing.T) { + a, b, mux := channelPair(t) + defer a.Close() + defer b.Close() + defer mux.Close() + + large := make([]byte, a.maxRemotePayload+1) + packet := make([]byte, 1+4+4+1+len(large)) + packet[0] = msgChannelData + marshalUint32(packet[1:], a.remoteId) + marshalUint32(packet[5:], uint32(len(large))) + packet[9] = 42 + + if err := a.mux.conn.writePacket(packet); err != nil { + t.Errorf("could not send packet") + } + + go a.SendRequest("hello", false, nil) + + _, ok := <-b.incomingRequests + if ok { + t.Errorf("connection still alive after receiving large packet.") + } +} + +// Don't ship code with debug=true. +func TestDebug(t *testing.T) { + if debugMux { + t.Error("mux debug switched on") + } + if debugHandshake { + t.Error("handshake debug switched on") + } + if debugTransport { + t.Error("transport debug switched on") + } +} diff --git a/vendor/golang.org/x/crypto/ssh/server.go b/vendor/golang.org/x/crypto/ssh/server.go new file mode 100644 index 0000000..b83d473 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/server.go @@ -0,0 +1,582 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "strings" +) + +// The Permissions type holds fine-grained permissions that are +// specific to a user or a specific authentication method for a user. +// The Permissions value for a successful authentication attempt is +// available in ServerConn, so it can be used to pass information from +// the user-authentication phase to the application layer. +type Permissions struct { + // CriticalOptions indicate restrictions to the default + // permissions, and are typically used in conjunction with + // user certificates. The standard for SSH certificates + // defines "force-command" (only allow the given command to + // execute) and "source-address" (only allow connections from + // the given address). The SSH package currently only enforces + // the "source-address" critical option. It is up to server + // implementations to enforce other critical options, such as + // "force-command", by checking them after the SSH handshake + // is successful. In general, SSH servers should reject + // connections that specify critical options that are unknown + // or not supported. + CriticalOptions map[string]string + + // Extensions are extra functionality that the server may + // offer on authenticated connections. Lack of support for an + // extension does not preclude authenticating a user. Common + // extensions are "permit-agent-forwarding", + // "permit-X11-forwarding". The Go SSH library currently does + // not act on any extension, and it is up to server + // implementations to honor them. Extensions can be used to + // pass data from the authentication callbacks to the server + // application layer. + Extensions map[string]string +} + +// ServerConfig holds server specific configuration data. +type ServerConfig struct { + // Config contains configuration shared between client and server. + Config + + hostKeys []Signer + + // NoClientAuth is true if clients are allowed to connect without + // authenticating. + NoClientAuth bool + + // MaxAuthTries specifies the maximum number of authentication attempts + // permitted per connection. If set to a negative number, the number of + // attempts are unlimited. If set to zero, the number of attempts are limited + // to 6. + MaxAuthTries int + + // PasswordCallback, if non-nil, is called when a user + // attempts to authenticate using a password. + PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) + + // PublicKeyCallback, if non-nil, is called when a client + // offers a public key for authentication. It must return a nil error + // if the given public key can be used to authenticate the + // given user. For example, see CertChecker.Authenticate. A + // call to this function does not guarantee that the key + // offered is in fact used to authenticate. To record any data + // depending on the public key, store it inside a + // Permissions.Extensions entry. + PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // KeyboardInteractiveCallback, if non-nil, is called when + // keyboard-interactive authentication is selected (RFC + // 4256). The client object's Challenge function should be + // used to query the user. The callback may offer multiple + // Challenge rounds. To avoid information leaks, the client + // should be presented a challenge even if the user is + // unknown. + KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) + + // AuthLogCallback, if non-nil, is called to log all authentication + // attempts. + AuthLogCallback func(conn ConnMetadata, method string, err error) + + // ServerVersion is the version identification string to announce in + // the public handshake. + // If empty, a reasonable default is used. + // Note that RFC 4253 section 4.2 requires that this string start with + // "SSH-2.0-". + ServerVersion string + + // BannerCallback, if present, is called and the return string is sent to + // the client after key exchange completed but before authentication. + BannerCallback func(conn ConnMetadata) string +} + +// AddHostKey adds a private key as a host key. If an existing host +// key exists with the same algorithm, it is overwritten. Each server +// config must have at least one host key. +func (s *ServerConfig) AddHostKey(key Signer) { + for i, k := range s.hostKeys { + if k.PublicKey().Type() == key.PublicKey().Type() { + s.hostKeys[i] = key + return + } + } + + s.hostKeys = append(s.hostKeys, key) +} + +// cachedPubKey contains the results of querying whether a public key is +// acceptable for a user. +type cachedPubKey struct { + user string + pubKeyData []byte + result error + perms *Permissions +} + +const maxCachedPubKeys = 16 + +// pubKeyCache caches tests for public keys. Since SSH clients +// will query whether a public key is acceptable before attempting to +// authenticate with it, we end up with duplicate queries for public +// key validity. The cache only applies to a single ServerConn. +type pubKeyCache struct { + keys []cachedPubKey +} + +// get returns the result for a given user/algo/key tuple. +func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) { + for _, k := range c.keys { + if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) { + return k, true + } + } + return cachedPubKey{}, false +} + +// add adds the given tuple to the cache. +func (c *pubKeyCache) add(candidate cachedPubKey) { + if len(c.keys) < maxCachedPubKeys { + c.keys = append(c.keys, candidate) + } +} + +// ServerConn is an authenticated SSH connection, as seen from the +// server +type ServerConn struct { + Conn + + // If the succeeding authentication callback returned a + // non-nil Permissions pointer, it is stored here. + Permissions *Permissions +} + +// NewServerConn starts a new SSH server with c as the underlying +// transport. It starts with a handshake and, if the handshake is +// unsuccessful, it closes the connection and returns an error. The +// Request and NewChannel channels must be serviced, or the connection +// will hang. +func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { + fullConf := *config + fullConf.SetDefaults() + if fullConf.MaxAuthTries == 0 { + fullConf.MaxAuthTries = 6 + } + + s := &connection{ + sshConn: sshConn{conn: c}, + } + perms, err := s.serverHandshake(&fullConf) + if err != nil { + c.Close() + return nil, nil, nil, err + } + return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil +} + +// signAndMarshal signs the data with the appropriate algorithm, +// and serializes the result in SSH wire format. +func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) { + sig, err := k.Sign(rand, data) + if err != nil { + return nil, err + } + + return Marshal(sig), nil +} + +// handshake performs key exchange and user authentication. +func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) { + if len(config.hostKeys) == 0 { + return nil, errors.New("ssh: server has no host keys") + } + + if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && config.KeyboardInteractiveCallback == nil { + return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") + } + + if config.ServerVersion != "" { + s.serverVersion = []byte(config.ServerVersion) + } else { + s.serverVersion = []byte(packageVersion) + } + var err error + s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion) + if err != nil { + return nil, err + } + + tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */) + s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config) + + if err := s.transport.waitSession(); err != nil { + return nil, err + } + + // We just did the key change, so the session ID is established. + s.sessionID = s.transport.getSessionID() + + var packet []byte + if packet, err = s.transport.readPacket(); err != nil { + return nil, err + } + + var serviceRequest serviceRequestMsg + if err = Unmarshal(packet, &serviceRequest); err != nil { + return nil, err + } + if serviceRequest.Service != serviceUserAuth { + return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating") + } + serviceAccept := serviceAcceptMsg{ + Service: serviceUserAuth, + } + if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil { + return nil, err + } + + perms, err := s.serverAuthenticate(config) + if err != nil { + return nil, err + } + s.mux = newMux(s.transport) + return perms, err +} + +func isAcceptableAlgo(algo string) bool { + switch algo { + case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519, + CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01: + return true + } + return false +} + +func checkSourceAddress(addr net.Addr, sourceAddrs string) error { + if addr == nil { + return errors.New("ssh: no address known for client, but source-address match required") + } + + tcpAddr, ok := addr.(*net.TCPAddr) + if !ok { + return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr) + } + + for _, sourceAddr := range strings.Split(sourceAddrs, ",") { + if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil { + if allowedIP.Equal(tcpAddr.IP) { + return nil + } + } else { + _, ipNet, err := net.ParseCIDR(sourceAddr) + if err != nil { + return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err) + } + + if ipNet.Contains(tcpAddr.IP) { + return nil + } + } + } + + return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr) +} + +// ServerAuthError implements the error interface. It appends any authentication +// errors that may occur, and is returned if all of the authentication methods +// provided by the user failed to authenticate. +type ServerAuthError struct { + // Errors contains authentication errors returned by the authentication + // callback methods. + Errors []error +} + +func (l ServerAuthError) Error() string { + var errs []string + for _, err := range l.Errors { + errs = append(errs, err.Error()) + } + return "[" + strings.Join(errs, ", ") + "]" +} + +func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { + sessionID := s.transport.getSessionID() + var cache pubKeyCache + var perms *Permissions + + authFailures := 0 + var authErrs []error + var displayedBanner bool + +userAuthLoop: + for { + if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 { + discMsg := &disconnectMsg{ + Reason: 2, + Message: "too many authentication failures", + } + + if err := s.transport.writePacket(Marshal(discMsg)); err != nil { + return nil, err + } + + return nil, discMsg + } + + var userAuthReq userAuthRequestMsg + if packet, err := s.transport.readPacket(); err != nil { + if err == io.EOF { + return nil, &ServerAuthError{Errors: authErrs} + } + return nil, err + } else if err = Unmarshal(packet, &userAuthReq); err != nil { + return nil, err + } + + if userAuthReq.Service != serviceSSH { + return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service) + } + + s.user = userAuthReq.User + + if !displayedBanner && config.BannerCallback != nil { + displayedBanner = true + msg := config.BannerCallback(s) + if msg != "" { + bannerMsg := &userAuthBannerMsg{ + Message: msg, + } + if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil { + return nil, err + } + } + } + + perms = nil + authErr := errors.New("no auth passed yet") + + switch userAuthReq.Method { + case "none": + if config.NoClientAuth { + authErr = nil + } + + // allow initial attempt of 'none' without penalty + if authFailures == 0 { + authFailures-- + } + case "password": + if config.PasswordCallback == nil { + authErr = errors.New("ssh: password auth not configured") + break + } + payload := userAuthReq.Payload + if len(payload) < 1 || payload[0] != 0 { + return nil, parseError(msgUserAuthRequest) + } + payload = payload[1:] + password, payload, ok := parseString(payload) + if !ok || len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + + perms, authErr = config.PasswordCallback(s, password) + case "keyboard-interactive": + if config.KeyboardInteractiveCallback == nil { + authErr = errors.New("ssh: keyboard-interactive auth not configubred") + break + } + + prompter := &sshClientKeyboardInteractive{s} + perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge) + case "publickey": + if config.PublicKeyCallback == nil { + authErr = errors.New("ssh: publickey auth not configured") + break + } + payload := userAuthReq.Payload + if len(payload) < 1 { + return nil, parseError(msgUserAuthRequest) + } + isQuery := payload[0] == 0 + payload = payload[1:] + algoBytes, payload, ok := parseString(payload) + if !ok { + return nil, parseError(msgUserAuthRequest) + } + algo := string(algoBytes) + if !isAcceptableAlgo(algo) { + authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo) + break + } + + pubKeyData, payload, ok := parseString(payload) + if !ok { + return nil, parseError(msgUserAuthRequest) + } + + pubKey, err := ParsePublicKey(pubKeyData) + if err != nil { + return nil, err + } + + candidate, ok := cache.get(s.user, pubKeyData) + if !ok { + candidate.user = s.user + candidate.pubKeyData = pubKeyData + candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey) + if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" { + candidate.result = checkSourceAddress( + s.RemoteAddr(), + candidate.perms.CriticalOptions[sourceAddressCriticalOption]) + } + cache.add(candidate) + } + + if isQuery { + // The client can query if the given public key + // would be okay. + + if len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + + if candidate.result == nil { + okMsg := userAuthPubKeyOkMsg{ + Algo: algo, + PubKey: pubKeyData, + } + if err = s.transport.writePacket(Marshal(&okMsg)); err != nil { + return nil, err + } + continue userAuthLoop + } + authErr = candidate.result + } else { + sig, payload, ok := parseSignature(payload) + if !ok || len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + // Ensure the public key algo and signature algo + // are supported. Compare the private key + // algorithm name that corresponds to algo with + // sig.Format. This is usually the same, but + // for certs, the names differ. + if !isAcceptableAlgo(sig.Format) { + break + } + signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData) + + if err := pubKey.Verify(signedData, sig); err != nil { + return nil, err + } + + authErr = candidate.result + perms = candidate.perms + } + default: + authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method) + } + + authErrs = append(authErrs, authErr) + + if config.AuthLogCallback != nil { + config.AuthLogCallback(s, userAuthReq.Method, authErr) + } + + if authErr == nil { + break userAuthLoop + } + + authFailures++ + + var failureMsg userAuthFailureMsg + if config.PasswordCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "password") + } + if config.PublicKeyCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "publickey") + } + if config.KeyboardInteractiveCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive") + } + + if len(failureMsg.Methods) == 0 { + return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") + } + + if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil { + return nil, err + } + } + + if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { + return nil, err + } + return perms, nil +} + +// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by +// asking the client on the other side of a ServerConn. +type sshClientKeyboardInteractive struct { + *connection +} + +func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { + if len(questions) != len(echos) { + return nil, errors.New("ssh: echos and questions must have equal length") + } + + var prompts []byte + for i := range questions { + prompts = appendString(prompts, questions[i]) + prompts = appendBool(prompts, echos[i]) + } + + if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{ + Instruction: instruction, + NumPrompts: uint32(len(questions)), + Prompts: prompts, + })); err != nil { + return nil, err + } + + packet, err := c.transport.readPacket() + if err != nil { + return nil, err + } + if packet[0] != msgUserAuthInfoResponse { + return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0]) + } + packet = packet[1:] + + n, packet, ok := parseUint32(packet) + if !ok || int(n) != len(questions) { + return nil, parseError(msgUserAuthInfoResponse) + } + + for i := uint32(0); i < n; i++ { + ans, rest, ok := parseString(packet) + if !ok { + return nil, parseError(msgUserAuthInfoResponse) + } + + answers = append(answers, string(ans)) + packet = rest + } + if len(packet) != 0 { + return nil, errors.New("ssh: junk at end of message") + } + + return answers, nil +} diff --git a/vendor/golang.org/x/crypto/ssh/session.go b/vendor/golang.org/x/crypto/ssh/session.go new file mode 100644 index 0000000..d3321f6 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/session.go @@ -0,0 +1,647 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +// Session implements an interactive session described in +// "RFC 4254, section 6". + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "sync" +) + +type Signal string + +// POSIX signals as listed in RFC 4254 Section 6.10. +const ( + SIGABRT Signal = "ABRT" + SIGALRM Signal = "ALRM" + SIGFPE Signal = "FPE" + SIGHUP Signal = "HUP" + SIGILL Signal = "ILL" + SIGINT Signal = "INT" + SIGKILL Signal = "KILL" + SIGPIPE Signal = "PIPE" + SIGQUIT Signal = "QUIT" + SIGSEGV Signal = "SEGV" + SIGTERM Signal = "TERM" + SIGUSR1 Signal = "USR1" + SIGUSR2 Signal = "USR2" +) + +var signals = map[Signal]int{ + SIGABRT: 6, + SIGALRM: 14, + SIGFPE: 8, + SIGHUP: 1, + SIGILL: 4, + SIGINT: 2, + SIGKILL: 9, + SIGPIPE: 13, + SIGQUIT: 3, + SIGSEGV: 11, + SIGTERM: 15, +} + +type TerminalModes map[uint8]uint32 + +// POSIX terminal mode flags as listed in RFC 4254 Section 8. +const ( + tty_OP_END = 0 + VINTR = 1 + VQUIT = 2 + VERASE = 3 + VKILL = 4 + VEOF = 5 + VEOL = 6 + VEOL2 = 7 + VSTART = 8 + VSTOP = 9 + VSUSP = 10 + VDSUSP = 11 + VREPRINT = 12 + VWERASE = 13 + VLNEXT = 14 + VFLUSH = 15 + VSWTCH = 16 + VSTATUS = 17 + VDISCARD = 18 + IGNPAR = 30 + PARMRK = 31 + INPCK = 32 + ISTRIP = 33 + INLCR = 34 + IGNCR = 35 + ICRNL = 36 + IUCLC = 37 + IXON = 38 + IXANY = 39 + IXOFF = 40 + IMAXBEL = 41 + ISIG = 50 + ICANON = 51 + XCASE = 52 + ECHO = 53 + ECHOE = 54 + ECHOK = 55 + ECHONL = 56 + NOFLSH = 57 + TOSTOP = 58 + IEXTEN = 59 + ECHOCTL = 60 + ECHOKE = 61 + PENDIN = 62 + OPOST = 70 + OLCUC = 71 + ONLCR = 72 + OCRNL = 73 + ONOCR = 74 + ONLRET = 75 + CS7 = 90 + CS8 = 91 + PARENB = 92 + PARODD = 93 + TTY_OP_ISPEED = 128 + TTY_OP_OSPEED = 129 +) + +// A Session represents a connection to a remote command or shell. +type Session struct { + // Stdin specifies the remote process's standard input. + // If Stdin is nil, the remote process reads from an empty + // bytes.Buffer. + Stdin io.Reader + + // Stdout and Stderr specify the remote process's standard + // output and error. + // + // If either is nil, Run connects the corresponding file + // descriptor to an instance of ioutil.Discard. There is a + // fixed amount of buffering that is shared for the two streams. + // If either blocks it may eventually cause the remote + // command to block. + Stdout io.Writer + Stderr io.Writer + + ch Channel // the channel backing this session + started bool // true once Start, Run or Shell is invoked. + copyFuncs []func() error + errors chan error // one send per copyFunc + + // true if pipe method is active + stdinpipe, stdoutpipe, stderrpipe bool + + // stdinPipeWriter is non-nil if StdinPipe has not been called + // and Stdin was specified by the user; it is the write end of + // a pipe connecting Session.Stdin to the stdin channel. + stdinPipeWriter io.WriteCloser + + exitStatus chan error +} + +// SendRequest sends an out-of-band channel request on the SSH channel +// underlying the session. +func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { + return s.ch.SendRequest(name, wantReply, payload) +} + +func (s *Session) Close() error { + return s.ch.Close() +} + +// RFC 4254 Section 6.4. +type setenvRequest struct { + Name string + Value string +} + +// Setenv sets an environment variable that will be applied to any +// command executed by Shell or Run. +func (s *Session) Setenv(name, value string) error { + msg := setenvRequest{ + Name: name, + Value: value, + } + ok, err := s.ch.SendRequest("env", true, Marshal(&msg)) + if err == nil && !ok { + err = errors.New("ssh: setenv failed") + } + return err +} + +// RFC 4254 Section 6.2. +type ptyRequestMsg struct { + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string +} + +// RequestPty requests the association of a pty with the session on the remote host. +func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error { + var tm []byte + for k, v := range termmodes { + kv := struct { + Key byte + Val uint32 + }{k, v} + + tm = append(tm, Marshal(&kv)...) + } + tm = append(tm, tty_OP_END) + req := ptyRequestMsg{ + Term: term, + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + Modelist: string(tm), + } + ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req)) + if err == nil && !ok { + err = errors.New("ssh: pty-req failed") + } + return err +} + +// RFC 4254 Section 6.5. +type subsystemRequestMsg struct { + Subsystem string +} + +// RequestSubsystem requests the association of a subsystem with the session on the remote host. +// A subsystem is a predefined command that runs in the background when the ssh session is initiated +func (s *Session) RequestSubsystem(subsystem string) error { + msg := subsystemRequestMsg{ + Subsystem: subsystem, + } + ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg)) + if err == nil && !ok { + err = errors.New("ssh: subsystem request failed") + } + return err +} + +// RFC 4254 Section 6.7. +type ptyWindowChangeMsg struct { + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 +} + +// WindowChange informs the remote host about a terminal window dimension change to h rows and w columns. +func (s *Session) WindowChange(h, w int) error { + req := ptyWindowChangeMsg{ + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + } + _, err := s.ch.SendRequest("window-change", false, Marshal(&req)) + return err +} + +// RFC 4254 Section 6.9. +type signalMsg struct { + Signal string +} + +// Signal sends the given signal to the remote process. +// sig is one of the SIG* constants. +func (s *Session) Signal(sig Signal) error { + msg := signalMsg{ + Signal: string(sig), + } + + _, err := s.ch.SendRequest("signal", false, Marshal(&msg)) + return err +} + +// RFC 4254 Section 6.5. +type execMsg struct { + Command string +} + +// Start runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Run, Start or Shell. +func (s *Session) Start(cmd string) error { + if s.started { + return errors.New("ssh: session already started") + } + req := execMsg{ + Command: cmd, + } + + ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) + if err == nil && !ok { + err = fmt.Errorf("ssh: command %v failed", cmd) + } + if err != nil { + return err + } + return s.start() +} + +// Run runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Run, Start, Shell, Output, +// or CombinedOutput. +// +// The returned error is nil if the command runs, has no problems +// copying stdin, stdout, and stderr, and exits with a zero exit +// status. +// +// If the remote server does not send an exit status, an error of type +// *ExitMissingError is returned. If the command completes +// unsuccessfully or is interrupted by a signal, the error is of type +// *ExitError. Other error types may be returned for I/O problems. +func (s *Session) Run(cmd string) error { + err := s.Start(cmd) + if err != nil { + return err + } + return s.Wait() +} + +// Output runs cmd on the remote host and returns its standard output. +func (s *Session) Output(cmd string) ([]byte, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + var b bytes.Buffer + s.Stdout = &b + err := s.Run(cmd) + return b.Bytes(), err +} + +type singleWriter struct { + b bytes.Buffer + mu sync.Mutex +} + +func (w *singleWriter) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.b.Write(p) +} + +// CombinedOutput runs cmd on the remote host and returns its combined +// standard output and standard error. +func (s *Session) CombinedOutput(cmd string) ([]byte, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + if s.Stderr != nil { + return nil, errors.New("ssh: Stderr already set") + } + var b singleWriter + s.Stdout = &b + s.Stderr = &b + err := s.Run(cmd) + return b.b.Bytes(), err +} + +// Shell starts a login shell on the remote host. A Session only +// accepts one call to Run, Start, Shell, Output, or CombinedOutput. +func (s *Session) Shell() error { + if s.started { + return errors.New("ssh: session already started") + } + + ok, err := s.ch.SendRequest("shell", true, nil) + if err == nil && !ok { + return errors.New("ssh: could not start shell") + } + if err != nil { + return err + } + return s.start() +} + +func (s *Session) start() error { + s.started = true + + type F func(*Session) + for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { + setupFd(s) + } + + s.errors = make(chan error, len(s.copyFuncs)) + for _, fn := range s.copyFuncs { + go func(fn func() error) { + s.errors <- fn() + }(fn) + } + return nil +} + +// Wait waits for the remote command to exit. +// +// The returned error is nil if the command runs, has no problems +// copying stdin, stdout, and stderr, and exits with a zero exit +// status. +// +// If the remote server does not send an exit status, an error of type +// *ExitMissingError is returned. If the command completes +// unsuccessfully or is interrupted by a signal, the error is of type +// *ExitError. Other error types may be returned for I/O problems. +func (s *Session) Wait() error { + if !s.started { + return errors.New("ssh: session not started") + } + waitErr := <-s.exitStatus + + if s.stdinPipeWriter != nil { + s.stdinPipeWriter.Close() + } + var copyError error + for range s.copyFuncs { + if err := <-s.errors; err != nil && copyError == nil { + copyError = err + } + } + if waitErr != nil { + return waitErr + } + return copyError +} + +func (s *Session) wait(reqs <-chan *Request) error { + wm := Waitmsg{status: -1} + // Wait for msg channel to be closed before returning. + for msg := range reqs { + switch msg.Type { + case "exit-status": + wm.status = int(binary.BigEndian.Uint32(msg.Payload)) + case "exit-signal": + var sigval struct { + Signal string + CoreDumped bool + Error string + Lang string + } + if err := Unmarshal(msg.Payload, &sigval); err != nil { + return err + } + + // Must sanitize strings? + wm.signal = sigval.Signal + wm.msg = sigval.Error + wm.lang = sigval.Lang + default: + // This handles keepalives and matches + // OpenSSH's behaviour. + if msg.WantReply { + msg.Reply(false, nil) + } + } + } + if wm.status == 0 { + return nil + } + if wm.status == -1 { + // exit-status was never sent from server + if wm.signal == "" { + // signal was not sent either. RFC 4254 + // section 6.10 recommends against this + // behavior, but it is allowed, so we let + // clients handle it. + return &ExitMissingError{} + } + wm.status = 128 + if _, ok := signals[Signal(wm.signal)]; ok { + wm.status += signals[Signal(wm.signal)] + } + } + + return &ExitError{wm} +} + +// ExitMissingError is returned if a session is torn down cleanly, but +// the server sends no confirmation of the exit status. +type ExitMissingError struct{} + +func (e *ExitMissingError) Error() string { + return "wait: remote command exited without exit status or exit signal" +} + +func (s *Session) stdin() { + if s.stdinpipe { + return + } + var stdin io.Reader + if s.Stdin == nil { + stdin = new(bytes.Buffer) + } else { + r, w := io.Pipe() + go func() { + _, err := io.Copy(w, s.Stdin) + w.CloseWithError(err) + }() + stdin, s.stdinPipeWriter = r, w + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.ch, stdin) + if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { + err = err1 + } + return err + }) +} + +func (s *Session) stdout() { + if s.stdoutpipe { + return + } + if s.Stdout == nil { + s.Stdout = ioutil.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.Stdout, s.ch) + return err + }) +} + +func (s *Session) stderr() { + if s.stderrpipe { + return + } + if s.Stderr == nil { + s.Stderr = ioutil.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.Stderr, s.ch.Stderr()) + return err + }) +} + +// sessionStdin reroutes Close to CloseWrite. +type sessionStdin struct { + io.Writer + ch Channel +} + +func (s *sessionStdin) Close() error { + return s.ch.CloseWrite() +} + +// StdinPipe returns a pipe that will be connected to the +// remote command's standard input when the command starts. +func (s *Session) StdinPipe() (io.WriteCloser, error) { + if s.Stdin != nil { + return nil, errors.New("ssh: Stdin already set") + } + if s.started { + return nil, errors.New("ssh: StdinPipe after process started") + } + s.stdinpipe = true + return &sessionStdin{s.ch, s.ch}, nil +} + +// StdoutPipe returns a pipe that will be connected to the +// remote command's standard output when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StdoutPipe reader is +// not serviced fast enough it may eventually cause the +// remote command to block. +func (s *Session) StdoutPipe() (io.Reader, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + if s.started { + return nil, errors.New("ssh: StdoutPipe after process started") + } + s.stdoutpipe = true + return s.ch, nil +} + +// StderrPipe returns a pipe that will be connected to the +// remote command's standard error when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StderrPipe reader is +// not serviced fast enough it may eventually cause the +// remote command to block. +func (s *Session) StderrPipe() (io.Reader, error) { + if s.Stderr != nil { + return nil, errors.New("ssh: Stderr already set") + } + if s.started { + return nil, errors.New("ssh: StderrPipe after process started") + } + s.stderrpipe = true + return s.ch.Stderr(), nil +} + +// newSession returns a new interactive session on the remote host. +func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { + s := &Session{ + ch: ch, + } + s.exitStatus = make(chan error, 1) + go func() { + s.exitStatus <- s.wait(reqs) + }() + + return s, nil +} + +// An ExitError reports unsuccessful completion of a remote command. +type ExitError struct { + Waitmsg +} + +func (e *ExitError) Error() string { + return e.Waitmsg.String() +} + +// Waitmsg stores the information about an exited remote command +// as reported by Wait. +type Waitmsg struct { + status int + signal string + msg string + lang string +} + +// ExitStatus returns the exit status of the remote command. +func (w Waitmsg) ExitStatus() int { + return w.status +} + +// Signal returns the exit signal of the remote command if +// it was terminated violently. +func (w Waitmsg) Signal() string { + return w.signal +} + +// Msg returns the exit message given by the remote command +func (w Waitmsg) Msg() string { + return w.msg +} + +// Lang returns the language tag. See RFC 3066 +func (w Waitmsg) Lang() string { + return w.lang +} + +func (w Waitmsg) String() string { + str := fmt.Sprintf("Process exited with status %v", w.status) + if w.signal != "" { + str += fmt.Sprintf(" from signal %v", w.signal) + } + if w.msg != "" { + str += fmt.Sprintf(". Reason was: %v", w.msg) + } + return str +} diff --git a/vendor/golang.org/x/crypto/ssh/session_test.go b/vendor/golang.org/x/crypto/ssh/session_test.go new file mode 100644 index 0000000..7dce6dd --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/session_test.go @@ -0,0 +1,774 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +// Session tests. + +import ( + "bytes" + crypto_rand "crypto/rand" + "errors" + "io" + "io/ioutil" + "math/rand" + "net" + "testing" + + "golang.org/x/crypto/ssh/terminal" +) + +type serverType func(Channel, <-chan *Request, *testing.T) + +// dial constructs a new test server and returns a *ClientConn. +func dial(handler serverType, t *testing.T) *Client { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + + go func() { + defer c1.Close() + conf := ServerConfig{ + NoClientAuth: true, + } + conf.AddHostKey(testSigners["rsa"]) + + _, chans, reqs, err := NewServerConn(c1, &conf) + if err != nil { + t.Fatalf("Unable to handshake: %v", err) + } + go DiscardRequests(reqs) + + for newCh := range chans { + if newCh.ChannelType() != "session" { + newCh.Reject(UnknownChannelType, "unknown channel type") + continue + } + + ch, inReqs, err := newCh.Accept() + if err != nil { + t.Errorf("Accept: %v", err) + continue + } + go func() { + handler(ch, inReqs, t) + }() + } + }() + + config := &ClientConfig{ + User: "testuser", + HostKeyCallback: InsecureIgnoreHostKey(), + } + + conn, chans, reqs, err := NewClientConn(c2, "", config) + if err != nil { + t.Fatalf("unable to dial remote side: %v", err) + } + + return NewClient(conn, chans, reqs) +} + +// Test a simple string is returned to session.Stdout. +func TestSessionShell(t *testing.T) { + conn := dial(shellHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + stdout := new(bytes.Buffer) + session.Stdout = stdout + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %s", err) + } + if err := session.Wait(); err != nil { + t.Fatalf("Remote command did not exit cleanly: %v", err) + } + actual := stdout.String() + if actual != "golang" { + t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual) + } +} + +// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it. + +// Test a simple string is returned via StdoutPipe. +func TestSessionStdoutPipe(t *testing.T) { + conn := dial(shellHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + stdout, err := session.StdoutPipe() + if err != nil { + t.Fatalf("Unable to request StdoutPipe(): %v", err) + } + var buf bytes.Buffer + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + done := make(chan bool, 1) + go func() { + if _, err := io.Copy(&buf, stdout); err != nil { + t.Errorf("Copy of stdout failed: %v", err) + } + done <- true + }() + if err := session.Wait(); err != nil { + t.Fatalf("Remote command did not exit cleanly: %v", err) + } + <-done + actual := buf.String() + if actual != "golang" { + t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual) + } +} + +// Test that a simple string is returned via the Output helper, +// and that stderr is discarded. +func TestSessionOutput(t *testing.T) { + conn := dial(fixedOutputHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + + buf, err := session.Output("") // cmd is ignored by fixedOutputHandler + if err != nil { + t.Error("Remote command did not exit cleanly:", err) + } + w := "this-is-stdout." + g := string(buf) + if g != w { + t.Error("Remote command did not return expected string:") + t.Logf("want %q", w) + t.Logf("got %q", g) + } +} + +// Test that both stdout and stderr are returned +// via the CombinedOutput helper. +func TestSessionCombinedOutput(t *testing.T) { + conn := dial(fixedOutputHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + + buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler + if err != nil { + t.Error("Remote command did not exit cleanly:", err) + } + const stdout = "this-is-stdout." + const stderr = "this-is-stderr." + g := string(buf) + if g != stdout+stderr && g != stderr+stdout { + t.Error("Remote command did not return expected string:") + t.Logf("want %q, or %q", stdout+stderr, stderr+stdout) + t.Logf("got %q", g) + } +} + +// Test non-0 exit status is returned correctly. +func TestExitStatusNonZero(t *testing.T) { + conn := dial(exitStatusNonZeroHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + err = session.Wait() + if err == nil { + t.Fatalf("expected command to fail but it didn't") + } + e, ok := err.(*ExitError) + if !ok { + t.Fatalf("expected *ExitError but got %T", err) + } + if e.ExitStatus() != 15 { + t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus()) + } +} + +// Test 0 exit status is returned correctly. +func TestExitStatusZero(t *testing.T) { + conn := dial(exitStatusZeroHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + err = session.Wait() + if err != nil { + t.Fatalf("expected nil but got %v", err) + } +} + +// Test exit signal and status are both returned correctly. +func TestExitSignalAndStatus(t *testing.T) { + conn := dial(exitSignalAndStatusHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + err = session.Wait() + if err == nil { + t.Fatalf("expected command to fail but it didn't") + } + e, ok := err.(*ExitError) + if !ok { + t.Fatalf("expected *ExitError but got %T", err) + } + if e.Signal() != "TERM" || e.ExitStatus() != 15 { + t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus()) + } +} + +// Test exit signal and status are both returned correctly. +func TestKnownExitSignalOnly(t *testing.T) { + conn := dial(exitSignalHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + err = session.Wait() + if err == nil { + t.Fatalf("expected command to fail but it didn't") + } + e, ok := err.(*ExitError) + if !ok { + t.Fatalf("expected *ExitError but got %T", err) + } + if e.Signal() != "TERM" || e.ExitStatus() != 143 { + t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus()) + } +} + +// Test exit signal and status are both returned correctly. +func TestUnknownExitSignal(t *testing.T) { + conn := dial(exitSignalUnknownHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + err = session.Wait() + if err == nil { + t.Fatalf("expected command to fail but it didn't") + } + e, ok := err.(*ExitError) + if !ok { + t.Fatalf("expected *ExitError but got %T", err) + } + if e.Signal() != "SYS" || e.ExitStatus() != 128 { + t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus()) + } +} + +func TestExitWithoutStatusOrSignal(t *testing.T) { + conn := dial(exitWithoutSignalOrStatus, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatalf("Unable to request new session: %v", err) + } + defer session.Close() + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + err = session.Wait() + if err == nil { + t.Fatalf("expected command to fail but it didn't") + } + if _, ok := err.(*ExitMissingError); !ok { + t.Fatalf("got %T want *ExitMissingError", err) + } +} + +// windowTestBytes is the number of bytes that we'll send to the SSH server. +const windowTestBytes = 16000 * 200 + +// TestServerWindow writes random data to the server. The server is expected to echo +// the same data back, which is compared against the original. +func TestServerWindow(t *testing.T) { + origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes)) + io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes) + origBytes := origBuf.Bytes() + + conn := dial(echoHandler, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatal(err) + } + defer session.Close() + result := make(chan []byte) + + go func() { + defer close(result) + echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes)) + serverStdout, err := session.StdoutPipe() + if err != nil { + t.Errorf("StdoutPipe failed: %v", err) + return + } + n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes) + if err != nil && err != io.EOF { + t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err) + } + result <- echoedBuf.Bytes() + }() + + serverStdin, err := session.StdinPipe() + if err != nil { + t.Fatalf("StdinPipe failed: %v", err) + } + written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes) + if err != nil { + t.Fatalf("failed to copy origBuf to serverStdin: %v", err) + } + if written != windowTestBytes { + t.Fatalf("Wrote only %d of %d bytes to server", written, windowTestBytes) + } + + echoedBytes := <-result + + if !bytes.Equal(origBytes, echoedBytes) { + t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes)) + } +} + +// Verify the client can handle a keepalive packet from the server. +func TestClientHandlesKeepalives(t *testing.T) { + conn := dial(channelKeepaliveSender, t) + defer conn.Close() + session, err := conn.NewSession() + if err != nil { + t.Fatal(err) + } + defer session.Close() + if err := session.Shell(); err != nil { + t.Fatalf("Unable to execute command: %v", err) + } + err = session.Wait() + if err != nil { + t.Fatalf("expected nil but got: %v", err) + } +} + +type exitStatusMsg struct { + Status uint32 +} + +type exitSignalMsg struct { + Signal string + CoreDumped bool + Errmsg string + Lang string +} + +func handleTerminalRequests(in <-chan *Request) { + for req := range in { + ok := false + switch req.Type { + case "shell": + ok = true + if len(req.Payload) > 0 { + // We don't accept any commands, only the default shell. + ok = false + } + case "env": + ok = true + } + req.Reply(ok, nil) + } +} + +func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal { + term := terminal.NewTerminal(ch, prompt) + go handleTerminalRequests(in) + return term +} + +func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + // this string is returned to stdout + shell := newServerShell(ch, in, "> ") + readLine(shell, t) + sendStatus(0, ch, t) +} + +func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + shell := newServerShell(ch, in, "> ") + readLine(shell, t) + sendStatus(15, ch, t) +} + +func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + shell := newServerShell(ch, in, "> ") + readLine(shell, t) + sendStatus(15, ch, t) + sendSignal("TERM", ch, t) +} + +func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + shell := newServerShell(ch, in, "> ") + readLine(shell, t) + sendSignal("TERM", ch, t) +} + +func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + shell := newServerShell(ch, in, "> ") + readLine(shell, t) + sendSignal("SYS", ch, t) +} + +func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + shell := newServerShell(ch, in, "> ") + readLine(shell, t) +} + +func shellHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + // this string is returned to stdout + shell := newServerShell(ch, in, "golang") + readLine(shell, t) + sendStatus(0, ch, t) +} + +// Ignores the command, writes fixed strings to stderr and stdout. +// Strings are "this-is-stdout." and "this-is-stderr.". +func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + _, err := ch.Read(nil) + + req, ok := <-in + if !ok { + t.Fatalf("error: expected channel request, got: %#v", err) + return + } + + // ignore request, always send some text + req.Reply(true, nil) + + _, err = io.WriteString(ch, "this-is-stdout.") + if err != nil { + t.Fatalf("error writing on server: %v", err) + } + _, err = io.WriteString(ch.Stderr(), "this-is-stderr.") + if err != nil { + t.Fatalf("error writing on server: %v", err) + } + sendStatus(0, ch, t) +} + +func readLine(shell *terminal.Terminal, t *testing.T) { + if _, err := shell.ReadLine(); err != nil && err != io.EOF { + t.Errorf("unable to read line: %v", err) + } +} + +func sendStatus(status uint32, ch Channel, t *testing.T) { + msg := exitStatusMsg{ + Status: status, + } + if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil { + t.Errorf("unable to send status: %v", err) + } +} + +func sendSignal(signal string, ch Channel, t *testing.T) { + sig := exitSignalMsg{ + Signal: signal, + CoreDumped: false, + Errmsg: "Process terminated", + Lang: "en-GB-oed", + } + if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil { + t.Errorf("unable to send signal: %v", err) + } +} + +func discardHandler(ch Channel, t *testing.T) { + defer ch.Close() + io.Copy(ioutil.Discard, ch) +} + +func echoHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil { + t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err) + } +} + +// copyNRandomly copies n bytes from src to dst. It uses a variable, and random, +// buffer size to exercise more code paths. +func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) { + var ( + buf = make([]byte, 32*1024) + written int + remaining = n + ) + for remaining > 0 { + l := rand.Intn(1 << 15) + if remaining < l { + l = remaining + } + nr, er := src.Read(buf[:l]) + nw, ew := dst.Write(buf[:nr]) + remaining -= nw + written += nw + if ew != nil { + return written, ew + } + if nr != nw { + return written, io.ErrShortWrite + } + if er != nil && er != io.EOF { + return written, er + } + } + return written, nil +} + +func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + shell := newServerShell(ch, in, "> ") + readLine(shell, t) + if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil { + t.Errorf("unable to send channel keepalive request: %v", err) + } + sendStatus(0, ch, t) +} + +func TestClientWriteEOF(t *testing.T) { + conn := dial(simpleEchoHandler, t) + defer conn.Close() + + session, err := conn.NewSession() + if err != nil { + t.Fatal(err) + } + defer session.Close() + stdin, err := session.StdinPipe() + if err != nil { + t.Fatalf("StdinPipe failed: %v", err) + } + stdout, err := session.StdoutPipe() + if err != nil { + t.Fatalf("StdoutPipe failed: %v", err) + } + + data := []byte(`0000`) + _, err = stdin.Write(data) + if err != nil { + t.Fatalf("Write failed: %v", err) + } + stdin.Close() + + res, err := ioutil.ReadAll(stdout) + if err != nil { + t.Fatalf("Read failed: %v", err) + } + + if !bytes.Equal(data, res) { + t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res) + } +} + +func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) { + defer ch.Close() + data, err := ioutil.ReadAll(ch) + if err != nil { + t.Errorf("handler read error: %v", err) + } + _, err = ch.Write(data) + if err != nil { + t.Errorf("handler write error: %v", err) + } +} + +func TestSessionID(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + serverID := make(chan []byte, 1) + clientID := make(chan []byte, 1) + + serverConf := &ServerConfig{ + NoClientAuth: true, + } + serverConf.AddHostKey(testSigners["ecdsa"]) + clientConf := &ClientConfig{ + HostKeyCallback: InsecureIgnoreHostKey(), + User: "user", + } + + go func() { + conn, chans, reqs, err := NewServerConn(c1, serverConf) + if err != nil { + t.Fatalf("server handshake: %v", err) + } + serverID <- conn.SessionID() + go DiscardRequests(reqs) + for ch := range chans { + ch.Reject(Prohibited, "") + } + }() + + go func() { + conn, chans, reqs, err := NewClientConn(c2, "", clientConf) + if err != nil { + t.Fatalf("client handshake: %v", err) + } + clientID <- conn.SessionID() + go DiscardRequests(reqs) + for ch := range chans { + ch.Reject(Prohibited, "") + } + }() + + s := <-serverID + c := <-clientID + if bytes.Compare(s, c) != 0 { + t.Errorf("server session ID (%x) != client session ID (%x)", s, c) + } else if len(s) == 0 { + t.Errorf("client and server SessionID were empty.") + } +} + +type noReadConn struct { + readSeen bool + net.Conn +} + +func (c *noReadConn) Close() error { + return nil +} + +func (c *noReadConn) Read(b []byte) (int, error) { + c.readSeen = true + return 0, errors.New("noReadConn error") +} + +func TestInvalidServerConfiguration(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + serveConn := noReadConn{Conn: c1} + serverConf := &ServerConfig{} + + NewServerConn(&serveConn, serverConf) + if serveConn.readSeen { + t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key") + } + + serverConf.AddHostKey(testSigners["ecdsa"]) + + NewServerConn(&serveConn, serverConf) + if serveConn.readSeen { + t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method") + } +} + +func TestHostKeyAlgorithms(t *testing.T) { + serverConf := &ServerConfig{ + NoClientAuth: true, + } + serverConf.AddHostKey(testSigners["rsa"]) + serverConf.AddHostKey(testSigners["ecdsa"]) + + connect := func(clientConf *ClientConfig, want string) { + var alg string + clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error { + alg = key.Type() + return nil + } + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go NewServerConn(c1, serverConf) + _, _, _, err = NewClientConn(c2, "", clientConf) + if err != nil { + t.Fatalf("NewClientConn: %v", err) + } + if alg != want { + t.Errorf("selected key algorithm %s, want %s", alg, want) + } + } + + // By default, we get the preferred algorithm, which is ECDSA 256. + + clientConf := &ClientConfig{ + HostKeyCallback: InsecureIgnoreHostKey(), + } + connect(clientConf, KeyAlgoECDSA256) + + // Client asks for RSA explicitly. + clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA} + connect(clientConf, KeyAlgoRSA) + + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + go NewServerConn(c1, serverConf) + clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"} + _, _, _, err = NewClientConn(c2, "", clientConf) + if err == nil { + t.Fatal("succeeded connecting with unknown hostkey algorithm") + } +} diff --git a/vendor/golang.org/x/crypto/ssh/streamlocal.go b/vendor/golang.org/x/crypto/ssh/streamlocal.go new file mode 100644 index 0000000..a2dccc6 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/streamlocal.go @@ -0,0 +1,115 @@ +package ssh + +import ( + "errors" + "io" + "net" +) + +// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message +// with "direct-streamlocal@openssh.com" string. +// +// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding +// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235 +type streamLocalChannelOpenDirectMsg struct { + socketPath string + reserved0 string + reserved1 uint32 +} + +// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message +// with "forwarded-streamlocal@openssh.com" string. +type forwardedStreamLocalPayload struct { + SocketPath string + Reserved0 string +} + +// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message +// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string. +type streamLocalChannelForwardMsg struct { + socketPath string +} + +// ListenUnix is similar to ListenTCP but uses a Unix domain socket. +func (c *Client) ListenUnix(socketPath string) (net.Listener, error) { + m := streamLocalChannelForwardMsg{ + socketPath, + } + // send message + ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m)) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer") + } + ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"}) + + return &unixListener{socketPath, c, ch}, nil +} + +func (c *Client) dialStreamLocal(socketPath string) (Channel, error) { + msg := streamLocalChannelOpenDirectMsg{ + socketPath: socketPath, + } + ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg)) + if err != nil { + return nil, err + } + go DiscardRequests(in) + return ch, err +} + +type unixListener struct { + socketPath string + + conn *Client + in <-chan forward +} + +// Accept waits for and returns the next connection to the listener. +func (l *unixListener) Accept() (net.Conn, error) { + s, ok := <-l.in + if !ok { + return nil, io.EOF + } + ch, incoming, err := s.newCh.Accept() + if err != nil { + return nil, err + } + go DiscardRequests(incoming) + + return &chanConn{ + Channel: ch, + laddr: &net.UnixAddr{ + Name: l.socketPath, + Net: "unix", + }, + raddr: &net.UnixAddr{ + Name: "@", + Net: "unix", + }, + }, nil +} + +// Close closes the listener. +func (l *unixListener) Close() error { + // this also closes the listener. + l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"}) + m := streamLocalChannelForwardMsg{ + l.socketPath, + } + ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m)) + if err == nil && !ok { + err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed") + } + return err +} + +// Addr returns the listener's network address. +func (l *unixListener) Addr() net.Addr { + return &net.UnixAddr{ + Name: l.socketPath, + Net: "unix", + } +} diff --git a/vendor/golang.org/x/crypto/ssh/tcpip.go b/vendor/golang.org/x/crypto/ssh/tcpip.go new file mode 100644 index 0000000..acf1717 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/tcpip.go @@ -0,0 +1,465 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "errors" + "fmt" + "io" + "math/rand" + "net" + "strconv" + "strings" + "sync" + "time" +) + +// Listen requests the remote peer open a listening socket on +// addr. Incoming connections will be available by calling Accept on +// the returned net.Listener. The listener must be serviced, or the +// SSH connection may hang. +// N must be "tcp", "tcp4", "tcp6", or "unix". +func (c *Client) Listen(n, addr string) (net.Listener, error) { + switch n { + case "tcp", "tcp4", "tcp6": + laddr, err := net.ResolveTCPAddr(n, addr) + if err != nil { + return nil, err + } + return c.ListenTCP(laddr) + case "unix": + return c.ListenUnix(addr) + default: + return nil, fmt.Errorf("ssh: unsupported protocol: %s", n) + } +} + +// Automatic port allocation is broken with OpenSSH before 6.0. See +// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In +// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0, +// rather than the actual port number. This means you can never open +// two different listeners with auto allocated ports. We work around +// this by trying explicit ports until we succeed. + +const openSSHPrefix = "OpenSSH_" + +var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano())) + +// isBrokenOpenSSHVersion returns true if the given version string +// specifies a version of OpenSSH that is known to have a bug in port +// forwarding. +func isBrokenOpenSSHVersion(versionStr string) bool { + i := strings.Index(versionStr, openSSHPrefix) + if i < 0 { + return false + } + i += len(openSSHPrefix) + j := i + for ; j < len(versionStr); j++ { + if versionStr[j] < '0' || versionStr[j] > '9' { + break + } + } + version, _ := strconv.Atoi(versionStr[i:j]) + return version < 6 +} + +// autoPortListenWorkaround simulates automatic port allocation by +// trying random ports repeatedly. +func (c *Client) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) { + var sshListener net.Listener + var err error + const tries = 10 + for i := 0; i < tries; i++ { + addr := *laddr + addr.Port = 1024 + portRandomizer.Intn(60000) + sshListener, err = c.ListenTCP(&addr) + if err == nil { + laddr.Port = addr.Port + return sshListener, err + } + } + return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err) +} + +// RFC 4254 7.1 +type channelForwardMsg struct { + addr string + rport uint32 +} + +// ListenTCP requests the remote peer open a listening socket +// on laddr. Incoming connections will be available by calling +// Accept on the returned net.Listener. +func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { + if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) { + return c.autoPortListenWorkaround(laddr) + } + + m := channelForwardMsg{ + laddr.IP.String(), + uint32(laddr.Port), + } + // send message + ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m)) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.New("ssh: tcpip-forward request denied by peer") + } + + // If the original port was 0, then the remote side will + // supply a real port number in the response. + if laddr.Port == 0 { + var p struct { + Port uint32 + } + if err := Unmarshal(resp, &p); err != nil { + return nil, err + } + laddr.Port = int(p.Port) + } + + // Register this forward, using the port number we obtained. + ch := c.forwards.add(laddr) + + return &tcpListener{laddr, c, ch}, nil +} + +// forwardList stores a mapping between remote +// forward requests and the tcpListeners. +type forwardList struct { + sync.Mutex + entries []forwardEntry +} + +// forwardEntry represents an established mapping of a laddr on a +// remote ssh server to a channel connected to a tcpListener. +type forwardEntry struct { + laddr net.Addr + c chan forward +} + +// forward represents an incoming forwarded tcpip connection. The +// arguments to add/remove/lookup should be address as specified in +// the original forward-request. +type forward struct { + newCh NewChannel // the ssh client channel underlying this forward + raddr net.Addr // the raddr of the incoming connection +} + +func (l *forwardList) add(addr net.Addr) chan forward { + l.Lock() + defer l.Unlock() + f := forwardEntry{ + laddr: addr, + c: make(chan forward, 1), + } + l.entries = append(l.entries, f) + return f.c +} + +// See RFC 4254, section 7.2 +type forwardedTCPPayload struct { + Addr string + Port uint32 + OriginAddr string + OriginPort uint32 +} + +// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr. +func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) { + if port == 0 || port > 65535 { + return nil, fmt.Errorf("ssh: port number out of range: %d", port) + } + ip := net.ParseIP(string(addr)) + if ip == nil { + return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr) + } + return &net.TCPAddr{IP: ip, Port: int(port)}, nil +} + +func (l *forwardList) handleChannels(in <-chan NewChannel) { + for ch := range in { + var ( + laddr net.Addr + raddr net.Addr + err error + ) + switch channelType := ch.ChannelType(); channelType { + case "forwarded-tcpip": + var payload forwardedTCPPayload + if err = Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error()) + continue + } + + // RFC 4254 section 7.2 specifies that incoming + // addresses should list the address, in string + // format. It is implied that this should be an IP + // address, as it would be impossible to connect to it + // otherwise. + laddr, err = parseTCPAddr(payload.Addr, payload.Port) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + + case "forwarded-streamlocal@openssh.com": + var payload forwardedStreamLocalPayload + if err = Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error()) + continue + } + laddr = &net.UnixAddr{ + Name: payload.SocketPath, + Net: "unix", + } + raddr = &net.UnixAddr{ + Name: "@", + Net: "unix", + } + default: + panic(fmt.Errorf("ssh: unknown channel type %s", channelType)) + } + if ok := l.forward(laddr, raddr, ch); !ok { + // Section 7.2, implementations MUST reject spurious incoming + // connections. + ch.Reject(Prohibited, "no forward for address") + continue + } + + } +} + +// remove removes the forward entry, and the channel feeding its +// listener. +func (l *forwardList) remove(addr net.Addr) { + l.Lock() + defer l.Unlock() + for i, f := range l.entries { + if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() { + l.entries = append(l.entries[:i], l.entries[i+1:]...) + close(f.c) + return + } + } +} + +// closeAll closes and clears all forwards. +func (l *forwardList) closeAll() { + l.Lock() + defer l.Unlock() + for _, f := range l.entries { + close(f.c) + } + l.entries = nil +} + +func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool { + l.Lock() + defer l.Unlock() + for _, f := range l.entries { + if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() { + f.c <- forward{newCh: ch, raddr: raddr} + return true + } + } + return false +} + +type tcpListener struct { + laddr *net.TCPAddr + + conn *Client + in <-chan forward +} + +// Accept waits for and returns the next connection to the listener. +func (l *tcpListener) Accept() (net.Conn, error) { + s, ok := <-l.in + if !ok { + return nil, io.EOF + } + ch, incoming, err := s.newCh.Accept() + if err != nil { + return nil, err + } + go DiscardRequests(incoming) + + return &chanConn{ + Channel: ch, + laddr: l.laddr, + raddr: s.raddr, + }, nil +} + +// Close closes the listener. +func (l *tcpListener) Close() error { + m := channelForwardMsg{ + l.laddr.IP.String(), + uint32(l.laddr.Port), + } + + // this also closes the listener. + l.conn.forwards.remove(l.laddr) + ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m)) + if err == nil && !ok { + err = errors.New("ssh: cancel-tcpip-forward failed") + } + return err +} + +// Addr returns the listener's network address. +func (l *tcpListener) Addr() net.Addr { + return l.laddr +} + +// Dial initiates a connection to the addr from the remote host. +// The resulting connection has a zero LocalAddr() and RemoteAddr(). +func (c *Client) Dial(n, addr string) (net.Conn, error) { + var ch Channel + switch n { + case "tcp", "tcp4", "tcp6": + // Parse the address into host and numeric port. + host, portString, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.ParseUint(portString, 10, 16) + if err != nil { + return nil, err + } + ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port)) + if err != nil { + return nil, err + } + // Use a zero address for local and remote address. + zeroAddr := &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } + return &chanConn{ + Channel: ch, + laddr: zeroAddr, + raddr: zeroAddr, + }, nil + case "unix": + var err error + ch, err = c.dialStreamLocal(addr) + if err != nil { + return nil, err + } + return &chanConn{ + Channel: ch, + laddr: &net.UnixAddr{ + Name: "@", + Net: "unix", + }, + raddr: &net.UnixAddr{ + Name: addr, + Net: "unix", + }, + }, nil + default: + return nil, fmt.Errorf("ssh: unsupported protocol: %s", n) + } +} + +// DialTCP connects to the remote address raddr on the network net, +// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used +// as the local address for the connection. +func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) { + if laddr == nil { + laddr = &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } + } + ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port) + if err != nil { + return nil, err + } + return &chanConn{ + Channel: ch, + laddr: laddr, + raddr: raddr, + }, nil +} + +// RFC 4254 7.2 +type channelOpenDirectMsg struct { + raddr string + rport uint32 + laddr string + lport uint32 +} + +func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel, error) { + msg := channelOpenDirectMsg{ + raddr: raddr, + rport: uint32(rport), + laddr: laddr, + lport: uint32(lport), + } + ch, in, err := c.OpenChannel("direct-tcpip", Marshal(&msg)) + if err != nil { + return nil, err + } + go DiscardRequests(in) + return ch, err +} + +type tcpChan struct { + Channel // the backing channel +} + +// chanConn fulfills the net.Conn interface without +// the tcpChan having to hold laddr or raddr directly. +type chanConn struct { + Channel + laddr, raddr net.Addr +} + +// LocalAddr returns the local network address. +func (t *chanConn) LocalAddr() net.Addr { + return t.laddr +} + +// RemoteAddr returns the remote network address. +func (t *chanConn) RemoteAddr() net.Addr { + return t.raddr +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. +func (t *chanConn) SetDeadline(deadline time.Time) error { + if err := t.SetReadDeadline(deadline); err != nil { + return err + } + return t.SetWriteDeadline(deadline) +} + +// SetReadDeadline sets the read deadline. +// A zero value for t means Read will not time out. +// After the deadline, the error from Read will implement net.Error +// with Timeout() == true. +func (t *chanConn) SetReadDeadline(deadline time.Time) error { + // for compatibility with previous version, + // the error message contains "tcpChan" + return errors.New("ssh: tcpChan: deadline not supported") +} + +// SetWriteDeadline exists to satisfy the net.Conn interface +// but is not implemented by this type. It always returns an error. +func (t *chanConn) SetWriteDeadline(deadline time.Time) error { + return errors.New("ssh: tcpChan: deadline not supported") +} diff --git a/vendor/golang.org/x/crypto/ssh/tcpip_test.go b/vendor/golang.org/x/crypto/ssh/tcpip_test.go new file mode 100644 index 0000000..f1265cb --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/tcpip_test.go @@ -0,0 +1,20 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "testing" +) + +func TestAutoPortListenBroken(t *testing.T) { + broken := "SSH-2.0-OpenSSH_5.9hh11" + works := "SSH-2.0-OpenSSH_6.1" + if !isBrokenOpenSSHVersion(broken) { + t.Errorf("version %q not marked as broken", broken) + } + if isBrokenOpenSSHVersion(works) { + t.Errorf("version %q marked as broken", works) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go new file mode 100644 index 0000000..9a88759 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go @@ -0,0 +1,951 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import ( + "bytes" + "io" + "sync" + "unicode/utf8" +) + +// EscapeCodes contains escape sequences that can be written to the terminal in +// order to achieve different styles of text. +type EscapeCodes struct { + // Foreground colors + Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte + + // Reset all attributes + Reset []byte +} + +var vt100EscapeCodes = EscapeCodes{ + Black: []byte{keyEscape, '[', '3', '0', 'm'}, + Red: []byte{keyEscape, '[', '3', '1', 'm'}, + Green: []byte{keyEscape, '[', '3', '2', 'm'}, + Yellow: []byte{keyEscape, '[', '3', '3', 'm'}, + Blue: []byte{keyEscape, '[', '3', '4', 'm'}, + Magenta: []byte{keyEscape, '[', '3', '5', 'm'}, + Cyan: []byte{keyEscape, '[', '3', '6', 'm'}, + White: []byte{keyEscape, '[', '3', '7', 'm'}, + + Reset: []byte{keyEscape, '[', '0', 'm'}, +} + +// Terminal contains the state for running a VT100 terminal that is capable of +// reading lines of input. +type Terminal struct { + // AutoCompleteCallback, if non-null, is called for each keypress with + // the full input line and the current position of the cursor (in + // bytes, as an index into |line|). If it returns ok=false, the key + // press is processed normally. Otherwise it returns a replacement line + // and the new cursor position. + AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool) + + // Escape contains a pointer to the escape codes for this terminal. + // It's always a valid pointer, although the escape codes themselves + // may be empty if the terminal doesn't support them. + Escape *EscapeCodes + + // lock protects the terminal and the state in this object from + // concurrent processing of a key press and a Write() call. + lock sync.Mutex + + c io.ReadWriter + prompt []rune + + // line is the current line being entered. + line []rune + // pos is the logical position of the cursor in line + pos int + // echo is true if local echo is enabled + echo bool + // pasteActive is true iff there is a bracketed paste operation in + // progress. + pasteActive bool + + // cursorX contains the current X value of the cursor where the left + // edge is 0. cursorY contains the row number where the first row of + // the current line is 0. + cursorX, cursorY int + // maxLine is the greatest value of cursorY so far. + maxLine int + + termWidth, termHeight int + + // outBuf contains the terminal data to be sent. + outBuf []byte + // remainder contains the remainder of any partial key sequences after + // a read. It aliases into inBuf. + remainder []byte + inBuf [256]byte + + // history contains previously entered commands so that they can be + // accessed with the up and down keys. + history stRingBuffer + // historyIndex stores the currently accessed history entry, where zero + // means the immediately previous entry. + historyIndex int + // When navigating up and down the history it's possible to return to + // the incomplete, initial line. That value is stored in + // historyPending. + historyPending string +} + +// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is +// a local terminal, that terminal must first have been put into raw mode. +// prompt is a string that is written at the start of each input line (i.e. +// "> "). +func NewTerminal(c io.ReadWriter, prompt string) *Terminal { + return &Terminal{ + Escape: &vt100EscapeCodes, + c: c, + prompt: []rune(prompt), + termWidth: 80, + termHeight: 24, + echo: true, + historyIndex: -1, + } +} + +const ( + keyCtrlD = 4 + keyCtrlU = 21 + keyEnter = '\r' + keyEscape = 27 + keyBackspace = 127 + keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota + keyUp + keyDown + keyLeft + keyRight + keyAltLeft + keyAltRight + keyHome + keyEnd + keyDeleteWord + keyDeleteLine + keyClearScreen + keyPasteStart + keyPasteEnd +) + +var ( + crlf = []byte{'\r', '\n'} + pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'} + pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'} +) + +// bytesToKey tries to parse a key sequence from b. If successful, it returns +// the key and the remainder of the input. Otherwise it returns utf8.RuneError. +func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { + if len(b) == 0 { + return utf8.RuneError, nil + } + + if !pasteActive { + switch b[0] { + case 1: // ^A + return keyHome, b[1:] + case 5: // ^E + return keyEnd, b[1:] + case 8: // ^H + return keyBackspace, b[1:] + case 11: // ^K + return keyDeleteLine, b[1:] + case 12: // ^L + return keyClearScreen, b[1:] + case 23: // ^W + return keyDeleteWord, b[1:] + } + } + + if b[0] != keyEscape { + if !utf8.FullRune(b) { + return utf8.RuneError, b + } + r, l := utf8.DecodeRune(b) + return r, b[l:] + } + + if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { + switch b[2] { + case 'A': + return keyUp, b[3:] + case 'B': + return keyDown, b[3:] + case 'C': + return keyRight, b[3:] + case 'D': + return keyLeft, b[3:] + case 'H': + return keyHome, b[3:] + case 'F': + return keyEnd, b[3:] + } + } + + if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { + switch b[5] { + case 'C': + return keyAltRight, b[6:] + case 'D': + return keyAltLeft, b[6:] + } + } + + if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) { + return keyPasteStart, b[6:] + } + + if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) { + return keyPasteEnd, b[6:] + } + + // If we get here then we have a key that we don't recognise, or a + // partial sequence. It's not clear how one should find the end of a + // sequence without knowing them all, but it seems that [a-zA-Z~] only + // appears at the end of a sequence. + for i, c := range b[0:] { + if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' { + return keyUnknown, b[i+1:] + } + } + + return utf8.RuneError, b +} + +// queue appends data to the end of t.outBuf +func (t *Terminal) queue(data []rune) { + t.outBuf = append(t.outBuf, []byte(string(data))...) +} + +var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'} +var space = []rune{' '} + +func isPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// moveCursorToPos appends data to t.outBuf which will move the cursor to the +// given, logical position in the text. +func (t *Terminal) moveCursorToPos(pos int) { + if !t.echo { + return + } + + x := visualLength(t.prompt) + pos + y := x / t.termWidth + x = x % t.termWidth + + up := 0 + if y < t.cursorY { + up = t.cursorY - y + } + + down := 0 + if y > t.cursorY { + down = y - t.cursorY + } + + left := 0 + if x < t.cursorX { + left = t.cursorX - x + } + + right := 0 + if x > t.cursorX { + right = x - t.cursorX + } + + t.cursorX = x + t.cursorY = y + t.move(up, down, left, right) +} + +func (t *Terminal) move(up, down, left, right int) { + movement := make([]rune, 3*(up+down+left+right)) + m := movement + for i := 0; i < up; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'A' + m = m[3:] + } + for i := 0; i < down; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'B' + m = m[3:] + } + for i := 0; i < left; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'D' + m = m[3:] + } + for i := 0; i < right; i++ { + m[0] = keyEscape + m[1] = '[' + m[2] = 'C' + m = m[3:] + } + + t.queue(movement) +} + +func (t *Terminal) clearLineToRight() { + op := []rune{keyEscape, '[', 'K'} + t.queue(op) +} + +const maxLineLength = 4096 + +func (t *Terminal) setLine(newLine []rune, newPos int) { + if t.echo { + t.moveCursorToPos(0) + t.writeLine(newLine) + for i := len(newLine); i < len(t.line); i++ { + t.writeLine(space) + } + t.moveCursorToPos(newPos) + } + t.line = newLine + t.pos = newPos +} + +func (t *Terminal) advanceCursor(places int) { + t.cursorX += places + t.cursorY += t.cursorX / t.termWidth + if t.cursorY > t.maxLine { + t.maxLine = t.cursorY + } + t.cursorX = t.cursorX % t.termWidth + + if places > 0 && t.cursorX == 0 { + // Normally terminals will advance the current position + // when writing a character. But that doesn't happen + // for the last character in a line. However, when + // writing a character (except a new line) that causes + // a line wrap, the position will be advanced two + // places. + // + // So, if we are stopping at the end of a line, we + // need to write a newline so that our cursor can be + // advanced to the next line. + t.outBuf = append(t.outBuf, '\r', '\n') + } +} + +func (t *Terminal) eraseNPreviousChars(n int) { + if n == 0 { + return + } + + if t.pos < n { + n = t.pos + } + t.pos -= n + t.moveCursorToPos(t.pos) + + copy(t.line[t.pos:], t.line[n+t.pos:]) + t.line = t.line[:len(t.line)-n] + if t.echo { + t.writeLine(t.line[t.pos:]) + for i := 0; i < n; i++ { + t.queue(space) + } + t.advanceCursor(n) + t.moveCursorToPos(t.pos) + } +} + +// countToLeftWord returns then number of characters from the cursor to the +// start of the previous word. +func (t *Terminal) countToLeftWord() int { + if t.pos == 0 { + return 0 + } + + pos := t.pos - 1 + for pos > 0 { + if t.line[pos] != ' ' { + break + } + pos-- + } + for pos > 0 { + if t.line[pos] == ' ' { + pos++ + break + } + pos-- + } + + return t.pos - pos +} + +// countToRightWord returns then number of characters from the cursor to the +// start of the next word. +func (t *Terminal) countToRightWord() int { + pos := t.pos + for pos < len(t.line) { + if t.line[pos] == ' ' { + break + } + pos++ + } + for pos < len(t.line) { + if t.line[pos] != ' ' { + break + } + pos++ + } + return pos - t.pos +} + +// visualLength returns the number of visible glyphs in s. +func visualLength(runes []rune) int { + inEscapeSeq := false + length := 0 + + for _, r := range runes { + switch { + case inEscapeSeq: + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { + inEscapeSeq = false + } + case r == '\x1b': + inEscapeSeq = true + default: + length++ + } + } + + return length +} + +// handleKey processes the given key and, optionally, returns a line of text +// that the user has entered. +func (t *Terminal) handleKey(key rune) (line string, ok bool) { + if t.pasteActive && key != keyEnter { + t.addKeyToLine(key) + return + } + + switch key { + case keyBackspace: + if t.pos == 0 { + return + } + t.eraseNPreviousChars(1) + case keyAltLeft: + // move left by a word. + t.pos -= t.countToLeftWord() + t.moveCursorToPos(t.pos) + case keyAltRight: + // move right by a word. + t.pos += t.countToRightWord() + t.moveCursorToPos(t.pos) + case keyLeft: + if t.pos == 0 { + return + } + t.pos-- + t.moveCursorToPos(t.pos) + case keyRight: + if t.pos == len(t.line) { + return + } + t.pos++ + t.moveCursorToPos(t.pos) + case keyHome: + if t.pos == 0 { + return + } + t.pos = 0 + t.moveCursorToPos(t.pos) + case keyEnd: + if t.pos == len(t.line) { + return + } + t.pos = len(t.line) + t.moveCursorToPos(t.pos) + case keyUp: + entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) + if !ok { + return "", false + } + if t.historyIndex == -1 { + t.historyPending = string(t.line) + } + t.historyIndex++ + runes := []rune(entry) + t.setLine(runes, len(runes)) + case keyDown: + switch t.historyIndex { + case -1: + return + case 0: + runes := []rune(t.historyPending) + t.setLine(runes, len(runes)) + t.historyIndex-- + default: + entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) + if ok { + t.historyIndex-- + runes := []rune(entry) + t.setLine(runes, len(runes)) + } + } + case keyEnter: + t.moveCursorToPos(len(t.line)) + t.queue([]rune("\r\n")) + line = string(t.line) + ok = true + t.line = t.line[:0] + t.pos = 0 + t.cursorX = 0 + t.cursorY = 0 + t.maxLine = 0 + case keyDeleteWord: + // Delete zero or more spaces and then one or more characters. + t.eraseNPreviousChars(t.countToLeftWord()) + case keyDeleteLine: + // Delete everything from the current cursor position to the + // end of line. + for i := t.pos; i < len(t.line); i++ { + t.queue(space) + t.advanceCursor(1) + } + t.line = t.line[:t.pos] + t.moveCursorToPos(t.pos) + case keyCtrlD: + // Erase the character under the current position. + // The EOF case when the line is empty is handled in + // readLine(). + if t.pos < len(t.line) { + t.pos++ + t.eraseNPreviousChars(1) + } + case keyCtrlU: + t.eraseNPreviousChars(t.pos) + case keyClearScreen: + // Erases the screen and moves the cursor to the home position. + t.queue([]rune("\x1b[2J\x1b[H")) + t.queue(t.prompt) + t.cursorX, t.cursorY = 0, 0 + t.advanceCursor(visualLength(t.prompt)) + t.setLine(t.line, t.pos) + default: + if t.AutoCompleteCallback != nil { + prefix := string(t.line[:t.pos]) + suffix := string(t.line[t.pos:]) + + t.lock.Unlock() + newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key) + t.lock.Lock() + + if completeOk { + t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos])) + return + } + } + if !isPrintable(key) { + return + } + if len(t.line) == maxLineLength { + return + } + t.addKeyToLine(key) + } + return +} + +// addKeyToLine inserts the given key at the current position in the current +// line. +func (t *Terminal) addKeyToLine(key rune) { + if len(t.line) == cap(t.line) { + newLine := make([]rune, len(t.line), 2*(1+len(t.line))) + copy(newLine, t.line) + t.line = newLine + } + t.line = t.line[:len(t.line)+1] + copy(t.line[t.pos+1:], t.line[t.pos:]) + t.line[t.pos] = key + if t.echo { + t.writeLine(t.line[t.pos:]) + } + t.pos++ + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) writeLine(line []rune) { + for len(line) != 0 { + remainingOnLine := t.termWidth - t.cursorX + todo := len(line) + if todo > remainingOnLine { + todo = remainingOnLine + } + t.queue(line[:todo]) + t.advanceCursor(visualLength(line[:todo])) + line = line[todo:] + } +} + +// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n. +func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) { + for len(buf) > 0 { + i := bytes.IndexByte(buf, '\n') + todo := len(buf) + if i >= 0 { + todo = i + } + + var nn int + nn, err = w.Write(buf[:todo]) + n += nn + if err != nil { + return n, err + } + buf = buf[todo:] + + if i >= 0 { + if _, err = w.Write(crlf); err != nil { + return n, err + } + n++ + buf = buf[1:] + } + } + + return n, nil +} + +func (t *Terminal) Write(buf []byte) (n int, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + if t.cursorX == 0 && t.cursorY == 0 { + // This is the easy case: there's nothing on the screen that we + // have to move out of the way. + return writeWithCRLF(t.c, buf) + } + + // We have a prompt and possibly user input on the screen. We + // have to clear it first. + t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */) + t.cursorX = 0 + t.clearLineToRight() + + for t.cursorY > 0 { + t.move(1 /* up */, 0, 0, 0) + t.cursorY-- + t.clearLineToRight() + } + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + + if n, err = writeWithCRLF(t.c, buf); err != nil { + return + } + + t.writeLine(t.prompt) + if t.echo { + t.writeLine(t.line) + } + + t.moveCursorToPos(t.pos) + + if _, err = t.c.Write(t.outBuf); err != nil { + return + } + t.outBuf = t.outBuf[:0] + return +} + +// ReadPassword temporarily changes the prompt and reads a password, without +// echo, from the terminal. +func (t *Terminal) ReadPassword(prompt string) (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + oldPrompt := t.prompt + t.prompt = []rune(prompt) + t.echo = false + + line, err = t.readLine() + + t.prompt = oldPrompt + t.echo = true + + return +} + +// ReadLine returns a line of input from the terminal. +func (t *Terminal) ReadLine() (line string, err error) { + t.lock.Lock() + defer t.lock.Unlock() + + return t.readLine() +} + +func (t *Terminal) readLine() (line string, err error) { + // t.lock must be held at this point + + if t.cursorX == 0 && t.cursorY == 0 { + t.writeLine(t.prompt) + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + } + + lineIsPasted := t.pasteActive + + for { + rest := t.remainder + lineOk := false + for !lineOk { + var key rune + key, rest = bytesToKey(rest, t.pasteActive) + if key == utf8.RuneError { + break + } + if !t.pasteActive { + if key == keyCtrlD { + if len(t.line) == 0 { + return "", io.EOF + } + } + if key == keyPasteStart { + t.pasteActive = true + if len(t.line) == 0 { + lineIsPasted = true + } + continue + } + } else if key == keyPasteEnd { + t.pasteActive = false + continue + } + if !t.pasteActive { + lineIsPasted = false + } + line, lineOk = t.handleKey(key) + } + if len(rest) > 0 { + n := copy(t.inBuf[:], rest) + t.remainder = t.inBuf[:n] + } else { + t.remainder = nil + } + t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + if lineOk { + if t.echo { + t.historyIndex = -1 + t.history.Add(line) + } + if lineIsPasted { + err = ErrPasteIndicator + } + return + } + + // t.remainder is a slice at the beginning of t.inBuf + // containing a partial key sequence + readBuf := t.inBuf[len(t.remainder):] + var n int + + t.lock.Unlock() + n, err = t.c.Read(readBuf) + t.lock.Lock() + + if err != nil { + return + } + + t.remainder = t.inBuf[:n+len(t.remainder)] + } +} + +// SetPrompt sets the prompt to be used when reading subsequent lines. +func (t *Terminal) SetPrompt(prompt string) { + t.lock.Lock() + defer t.lock.Unlock() + + t.prompt = []rune(prompt) +} + +func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) { + // Move cursor to column zero at the start of the line. + t.move(t.cursorY, 0, t.cursorX, 0) + t.cursorX, t.cursorY = 0, 0 + t.clearLineToRight() + for t.cursorY < numPrevLines { + // Move down a line + t.move(0, 1, 0, 0) + t.cursorY++ + t.clearLineToRight() + } + // Move back to beginning. + t.move(t.cursorY, 0, 0, 0) + t.cursorX, t.cursorY = 0, 0 + + t.queue(t.prompt) + t.advanceCursor(visualLength(t.prompt)) + t.writeLine(t.line) + t.moveCursorToPos(t.pos) +} + +func (t *Terminal) SetSize(width, height int) error { + t.lock.Lock() + defer t.lock.Unlock() + + if width == 0 { + width = 1 + } + + oldWidth := t.termWidth + t.termWidth, t.termHeight = width, height + + switch { + case width == oldWidth: + // If the width didn't change then nothing else needs to be + // done. + return nil + case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0: + // If there is nothing on current line and no prompt printed, + // just do nothing + return nil + case width < oldWidth: + // Some terminals (e.g. xterm) will truncate lines that were + // too long when shinking. Others, (e.g. gnome-terminal) will + // attempt to wrap them. For the former, repainting t.maxLine + // works great, but that behaviour goes badly wrong in the case + // of the latter because they have doubled every full line. + + // We assume that we are working on a terminal that wraps lines + // and adjust the cursor position based on every previous line + // wrapping and turning into two. This causes the prompt on + // xterms to move upwards, which isn't great, but it avoids a + // huge mess with gnome-terminal. + if t.cursorX >= t.termWidth { + t.cursorX = t.termWidth - 1 + } + t.cursorY *= 2 + t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2) + case width > oldWidth: + // If the terminal expands then our position calculations will + // be wrong in the future because we think the cursor is + // |t.pos| chars into the string, but there will be a gap at + // the end of any wrapped line. + // + // But the position will actually be correct until we move, so + // we can move back to the beginning and repaint everything. + t.clearAndRepaintLinePlusNPrevious(t.maxLine) + } + + _, err := t.c.Write(t.outBuf) + t.outBuf = t.outBuf[:0] + return err +} + +type pasteIndicatorError struct{} + +func (pasteIndicatorError) Error() string { + return "terminal: ErrPasteIndicator not correctly handled" +} + +// ErrPasteIndicator may be returned from ReadLine as the error, in addition +// to valid line data. It indicates that bracketed paste mode is enabled and +// that the returned line consists only of pasted data. Programs may wish to +// interpret pasted data more literally than typed data. +var ErrPasteIndicator = pasteIndicatorError{} + +// SetBracketedPasteMode requests that the terminal bracket paste operations +// with markers. Not all terminals support this but, if it is supported, then +// enabling this mode will stop any autocomplete callback from running due to +// pastes. Additionally, any lines that are completely pasted will be returned +// from ReadLine with the error set to ErrPasteIndicator. +func (t *Terminal) SetBracketedPasteMode(on bool) { + if on { + io.WriteString(t.c, "\x1b[?2004h") + } else { + io.WriteString(t.c, "\x1b[?2004l") + } +} + +// stRingBuffer is a ring buffer of strings. +type stRingBuffer struct { + // entries contains max elements. + entries []string + max int + // head contains the index of the element most recently added to the ring. + head int + // size contains the number of elements in the ring. + size int +} + +func (s *stRingBuffer) Add(a string) { + if s.entries == nil { + const defaultNumEntries = 100 + s.entries = make([]string, defaultNumEntries) + s.max = defaultNumEntries + } + + s.head = (s.head + 1) % s.max + s.entries[s.head] = a + if s.size < s.max { + s.size++ + } +} + +// NthPreviousEntry returns the value passed to the nth previous call to Add. +// If n is zero then the immediately prior value is returned, if one, then the +// next most recent, and so on. If such an element doesn't exist then ok is +// false. +func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { + if n >= s.size { + return "", false + } + index := s.head - n + if index < 0 { + index += s.max + } + return s.entries[index], true +} + +// readPasswordLine reads from reader until it finds \n or io.EOF. +// The slice returned does not include the \n. +// readPasswordLine also ignores any \r it finds. +func readPasswordLine(reader io.Reader) ([]byte, error) { + var buf [1]byte + var ret []byte + + for { + n, err := reader.Read(buf[:]) + if n > 0 { + switch buf[0] { + case '\n': + return ret, nil + case '\r': + // remove \r from passwords on Windows + default: + ret = append(ret, buf[0]) + } + continue + } + if err != nil { + if err == io.EOF && len(ret) > 0 { + return ret, nil + } + return ret, err + } + } +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/terminal_test.go b/vendor/golang.org/x/crypto/ssh/terminal/terminal_test.go new file mode 100644 index 0000000..901c72a --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/terminal_test.go @@ -0,0 +1,350 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import ( + "bytes" + "io" + "os" + "testing" +) + +type MockTerminal struct { + toSend []byte + bytesPerRead int + received []byte +} + +func (c *MockTerminal) Read(data []byte) (n int, err error) { + n = len(data) + if n == 0 { + return + } + if n > len(c.toSend) { + n = len(c.toSend) + } + if n == 0 { + return 0, io.EOF + } + if c.bytesPerRead > 0 && n > c.bytesPerRead { + n = c.bytesPerRead + } + copy(data, c.toSend[:n]) + c.toSend = c.toSend[n:] + return +} + +func (c *MockTerminal) Write(data []byte) (n int, err error) { + c.received = append(c.received, data...) + return len(data), nil +} + +func TestClose(t *testing.T) { + c := &MockTerminal{} + ss := NewTerminal(c, "> ") + line, err := ss.ReadLine() + if line != "" { + t.Errorf("Expected empty line but got: %s", line) + } + if err != io.EOF { + t.Errorf("Error should have been EOF but got: %s", err) + } +} + +var keyPressTests = []struct { + in string + line string + err error + throwAwayLines int +}{ + { + err: io.EOF, + }, + { + in: "\r", + line: "", + }, + { + in: "foo\r", + line: "foo", + }, + { + in: "a\x1b[Cb\r", // right + line: "ab", + }, + { + in: "a\x1b[Db\r", // left + line: "ba", + }, + { + in: "a\177b\r", // backspace + line: "b", + }, + { + in: "\x1b[A\r", // up + }, + { + in: "\x1b[B\r", // down + }, + { + in: "line\x1b[A\x1b[B\r", // up then down + line: "line", + }, + { + in: "line1\rline2\x1b[A\r", // recall previous line. + line: "line1", + throwAwayLines: 1, + }, + { + // recall two previous lines and append. + in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r", + line: "line1xxx", + throwAwayLines: 2, + }, + { + // Ctrl-A to move to beginning of line followed by ^K to kill + // line. + in: "a b \001\013\r", + line: "", + }, + { + // Ctrl-A to move to beginning of line, Ctrl-E to move to end, + // finally ^K to kill nothing. + in: "a b \001\005\013\r", + line: "a b ", + }, + { + in: "\027\r", + line: "", + }, + { + in: "a\027\r", + line: "", + }, + { + in: "a \027\r", + line: "", + }, + { + in: "a b\027\r", + line: "a ", + }, + { + in: "a b \027\r", + line: "a ", + }, + { + in: "one two thr\x1b[D\027\r", + line: "one two r", + }, + { + in: "\013\r", + line: "", + }, + { + in: "a\013\r", + line: "a", + }, + { + in: "ab\x1b[D\013\r", + line: "a", + }, + { + in: "Ξεσκεπάζω\r", + line: "Ξεσκεπάζω", + }, + { + in: "£\r\x1b[A\177\r", // non-ASCII char, enter, up, backspace. + line: "", + throwAwayLines: 1, + }, + { + in: "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter. + line: "£", + throwAwayLines: 1, + }, + { + // Ctrl-D at the end of the line should be ignored. + in: "a\004\r", + line: "a", + }, + { + // a, b, left, Ctrl-D should erase the b. + in: "ab\x1b[D\004\r", + line: "a", + }, + { + // a, b, c, d, left, left, ^U should erase to the beginning of + // the line. + in: "abcd\x1b[D\x1b[D\025\r", + line: "cd", + }, + { + // Bracketed paste mode: control sequences should be returned + // verbatim in paste mode. + in: "abc\x1b[200~de\177f\x1b[201~\177\r", + line: "abcde\177", + }, + { + // Enter in bracketed paste mode should still work. + in: "abc\x1b[200~d\refg\x1b[201~h\r", + line: "efgh", + throwAwayLines: 1, + }, + { + // Lines consisting entirely of pasted data should be indicated as such. + in: "\x1b[200~a\r", + line: "a", + err: ErrPasteIndicator, + }, +} + +func TestKeyPresses(t *testing.T) { + for i, test := range keyPressTests { + for j := 1; j < len(test.in); j++ { + c := &MockTerminal{ + toSend: []byte(test.in), + bytesPerRead: j, + } + ss := NewTerminal(c, "> ") + for k := 0; k < test.throwAwayLines; k++ { + _, err := ss.ReadLine() + if err != nil { + t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err) + } + } + line, err := ss.ReadLine() + if line != test.line { + t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line) + break + } + if err != test.err { + t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err) + break + } + } + } +} + +func TestPasswordNotSaved(t *testing.T) { + c := &MockTerminal{ + toSend: []byte("password\r\x1b[A\r"), + bytesPerRead: 1, + } + ss := NewTerminal(c, "> ") + pw, _ := ss.ReadPassword("> ") + if pw != "password" { + t.Fatalf("failed to read password, got %s", pw) + } + line, _ := ss.ReadLine() + if len(line) > 0 { + t.Fatalf("password was saved in history") + } +} + +var setSizeTests = []struct { + width, height int +}{ + {40, 13}, + {80, 24}, + {132, 43}, +} + +func TestTerminalSetSize(t *testing.T) { + for _, setSize := range setSizeTests { + c := &MockTerminal{ + toSend: []byte("password\r\x1b[A\r"), + bytesPerRead: 1, + } + ss := NewTerminal(c, "> ") + ss.SetSize(setSize.width, setSize.height) + pw, _ := ss.ReadPassword("Password: ") + if pw != "password" { + t.Fatalf("failed to read password, got %s", pw) + } + if string(c.received) != "Password: \r\n" { + t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received) + } + } +} + +func TestReadPasswordLineEnd(t *testing.T) { + var tests = []struct { + input string + want string + }{ + {"\n", ""}, + {"\r\n", ""}, + {"test\r\n", "test"}, + {"testtesttesttes\n", "testtesttesttes"}, + {"testtesttesttes\r\n", "testtesttesttes"}, + {"testtesttesttesttest\n", "testtesttesttesttest"}, + {"testtesttesttesttest\r\n", "testtesttesttesttest"}, + } + for _, test := range tests { + buf := new(bytes.Buffer) + if _, err := buf.WriteString(test.input); err != nil { + t.Fatal(err) + } + + have, err := readPasswordLine(buf) + if err != nil { + t.Errorf("readPasswordLine(%q) failed: %v", test.input, err) + continue + } + if string(have) != test.want { + t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want) + continue + } + + if _, err = buf.WriteString(test.input); err != nil { + t.Fatal(err) + } + have, err = readPasswordLine(buf) + if err != nil { + t.Errorf("readPasswordLine(%q) failed: %v", test.input, err) + continue + } + if string(have) != test.want { + t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want) + continue + } + } +} + +func TestMakeRawState(t *testing.T) { + fd := int(os.Stdout.Fd()) + if !IsTerminal(fd) { + t.Skip("stdout is not a terminal; skipping test") + } + + st, err := GetState(fd) + if err != nil { + t.Fatalf("failed to get terminal state from GetState: %s", err) + } + defer Restore(fd, st) + raw, err := MakeRaw(fd) + if err != nil { + t.Fatalf("failed to get terminal state from MakeRaw: %s", err) + } + + if *st != *raw { + t.Errorf("states do not match; was %v, expected %v", raw, st) + } +} + +func TestOutputNewlines(t *testing.T) { + // \n should be changed to \r\n in terminal output. + buf := new(bytes.Buffer) + term := NewTerminal(buf, ">") + + term.Write([]byte("1\n2\n")) + output := string(buf.Bytes()) + const expected = "1\r\n2\r\n" + + if output != expected { + t.Errorf("incorrect output: was %q, expected %q", output, expected) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util.go b/vendor/golang.org/x/crypto/ssh/terminal/util.go new file mode 100644 index 0000000..02dad48 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util.go @@ -0,0 +1,116 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal // import "golang.org/x/crypto/ssh/terminal" + +import ( + "golang.org/x/sys/unix" +) + +// State contains the state of a terminal. +type State struct { + termios unix.Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + oldState := State{termios: *termios} + + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON + termios.Oflag &^= unix.OPOST + termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN + termios.Cflag &^= unix.CSIZE | unix.PARENB + termios.Cflag |= unix.CS8 + termios.Cc[unix.VMIN] = 1 + termios.Cc[unix.VTIME] = 0 + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil { + return nil, err + } + + return &oldState, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return -1, -1, err + } + return int(ws.Col), int(ws.Row), nil +} + +// passwordReader is an io.Reader that reads from a specific file descriptor. +type passwordReader int + +func (r passwordReader) Read(buf []byte) (int, error) { + return unix.Read(int(r), buf) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, err + } + + newState := *termios + newState.Lflag &^= unix.ECHO + newState.Lflag |= unix.ICANON | unix.ISIG + newState.Iflag |= unix.ICRNL + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil { + return nil, err + } + + defer func() { + unix.IoctlSetTermios(fd, ioctlWriteTermios, termios) + }() + + return readPasswordLine(passwordReader(fd)) +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go b/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go new file mode 100644 index 0000000..cb23a59 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go @@ -0,0 +1,12 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd + +package terminal + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TIOCGETA +const ioctlWriteTermios = unix.TIOCSETA diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go b/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go new file mode 100644 index 0000000..5fadfe8 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_linux.go @@ -0,0 +1,10 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package terminal + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS +const ioctlWriteTermios = unix.TCSETS diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go b/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go new file mode 100644 index 0000000..799f049 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go @@ -0,0 +1,58 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "fmt" + "runtime" +) + +type State struct{} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + return false +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go b/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go new file mode 100644 index 0000000..a2e1b57 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go @@ -0,0 +1,128 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package terminal // import "golang.org/x/crypto/ssh/terminal" + +import ( + "golang.org/x/sys/unix" + "io" + "syscall" +) + +// State contains the state of a terminal. +type State struct { + state *unix.Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := unix.IoctlGetTermio(fd, unix.TCGETA) + return err == nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + // see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c + val, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + oldState := *val + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState) + if err != nil { + return nil, err + } + + defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState) + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} + +// MakeRaw puts the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +// see http://cr.illumos.org/~webrev/andy_js/1060/ +func MakeRaw(fd int) (*State, error) { + oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + oldTermios := *oldTermiosPtr + + newTermios := oldTermios + newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + newTermios.Oflag &^= syscall.OPOST + newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB + newTermios.Cflag |= syscall.CS8 + newTermios.Cc[unix.VMIN] = 1 + newTermios.Cc[unix.VTIME] = 0 + + if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil { + return nil, err + } + + return &State{ + state: oldTermiosPtr, + }, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, oldState *State) error { + return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + + return &State{ + state: oldTermiosPtr, + }, nil +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go new file mode 100644 index 0000000..92944f3 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go @@ -0,0 +1,97 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package terminal + +import ( + "os" + + "golang.org/x/sys/windows" +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + err := windows.GetConsoleMode(windows.Handle(fd), &st) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil { + return nil, err + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + return windows.SetConsoleMode(windows.Handle(fd), state.mode) +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info windows.ConsoleScreenBufferInfo + if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { + return 0, 0, err + } + return int(info.Size.X), int(info.Size.Y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { + return nil, err + } + old := st + + st &^= (windows.ENABLE_ECHO_INPUT) + st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { + return nil, err + } + + defer func() { + windows.SetConsoleMode(windows.Handle(fd), old) + }() + + return readPasswordLine(os.NewFile(uintptr(fd), "stdin")) +} diff --git a/vendor/golang.org/x/crypto/ssh/testdata_test.go b/vendor/golang.org/x/crypto/ssh/testdata_test.go new file mode 100644 index 0000000..2da8c79 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/testdata_test.go @@ -0,0 +1,63 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places: +// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three +// instances. + +package ssh + +import ( + "crypto/rand" + "fmt" + + "golang.org/x/crypto/ssh/testdata" +) + +var ( + testPrivateKeys map[string]interface{} + testSigners map[string]Signer + testPublicKeys map[string]PublicKey +) + +func init() { + var err error + + n := len(testdata.PEMBytes) + testPrivateKeys = make(map[string]interface{}, n) + testSigners = make(map[string]Signer, n) + testPublicKeys = make(map[string]PublicKey, n) + for t, k := range testdata.PEMBytes { + testPrivateKeys[t], err = ParseRawPrivateKey(k) + if err != nil { + panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err)) + } + testSigners[t], err = NewSignerFromKey(testPrivateKeys[t]) + if err != nil { + panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err)) + } + testPublicKeys[t] = testSigners[t].PublicKey() + } + + // Create a cert and sign it for use in tests. + testCert := &Certificate{ + Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage + ValidAfter: 0, // unix epoch + ValidBefore: CertTimeInfinity, // The end of currently representable time. + Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + Key: testPublicKeys["ecdsa"], + SignatureKey: testPublicKeys["rsa"], + Permissions: Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + }, + } + testCert.SignCert(rand.Reader, testSigners["rsa"]) + testPrivateKeys["cert"] = testPrivateKeys["ecdsa"] + testSigners["cert"], err = NewCertSigner(testCert, testSigners["ecdsa"]) + if err != nil { + panic(fmt.Sprintf("Unable to create certificate signer: %v", err)) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/transport.go b/vendor/golang.org/x/crypto/ssh/transport.go new file mode 100644 index 0000000..5a56753 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/transport.go @@ -0,0 +1,382 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bufio" + "bytes" + "errors" + "io" + "log" +) + +// debugTransport if set, will print packet types as they go over the +// wire. No message decoding is done, to minimize the impact on timing. +const debugTransport = false + +const ( + gcmCipherID = "aes128-gcm@openssh.com" + aes128cbcID = "aes128-cbc" + tripledescbcID = "3des-cbc" +) + +// packetConn represents a transport that implements packet based +// operations. +type packetConn interface { + // Encrypt and send a packet of data to the remote peer. + writePacket(packet []byte) error + + // Read a packet from the connection. The read is blocking, + // i.e. if error is nil, then the returned byte slice is + // always non-empty. + readPacket() ([]byte, error) + + // Close closes the write-side of the connection. + Close() error +} + +// transport is the keyingTransport that implements the SSH packet +// protocol. +type transport struct { + reader connectionState + writer connectionState + + bufReader *bufio.Reader + bufWriter *bufio.Writer + rand io.Reader + isClient bool + io.Closer +} + +// packetCipher represents a combination of SSH encryption/MAC +// protocol. A single instance should be used for one direction only. +type packetCipher interface { + // writePacket encrypts the packet and writes it to w. The + // contents of the packet are generally scrambled. + writePacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error + + // readPacket reads and decrypts a packet of data. The + // returned packet may be overwritten by future calls of + // readPacket. + readPacket(seqnum uint32, r io.Reader) ([]byte, error) +} + +// connectionState represents one side (read or write) of the +// connection. This is necessary because each direction has its own +// keys, and can even have its own algorithms +type connectionState struct { + packetCipher + seqNum uint32 + dir direction + pendingKeyChange chan packetCipher +} + +// prepareKeyChange sets up key material for a keychange. The key changes in +// both directions are triggered by reading and writing a msgNewKey packet +// respectively. +func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error { + ciph, err := newPacketCipher(t.reader.dir, algs.r, kexResult) + if err != nil { + return err + } + t.reader.pendingKeyChange <- ciph + + ciph, err = newPacketCipher(t.writer.dir, algs.w, kexResult) + if err != nil { + return err + } + t.writer.pendingKeyChange <- ciph + + return nil +} + +func (t *transport) printPacket(p []byte, write bool) { + if len(p) == 0 { + return + } + who := "server" + if t.isClient { + who = "client" + } + what := "read" + if write { + what = "write" + } + + log.Println(what, who, p[0]) +} + +// Read and decrypt next packet. +func (t *transport) readPacket() (p []byte, err error) { + for { + p, err = t.reader.readPacket(t.bufReader) + if err != nil { + break + } + if len(p) == 0 || (p[0] != msgIgnore && p[0] != msgDebug) { + break + } + } + if debugTransport { + t.printPacket(p, false) + } + + return p, err +} + +func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) { + packet, err := s.packetCipher.readPacket(s.seqNum, r) + s.seqNum++ + if err == nil && len(packet) == 0 { + err = errors.New("ssh: zero length packet") + } + + if len(packet) > 0 { + switch packet[0] { + case msgNewKeys: + select { + case cipher := <-s.pendingKeyChange: + s.packetCipher = cipher + default: + return nil, errors.New("ssh: got bogus newkeys message") + } + + case msgDisconnect: + // Transform a disconnect message into an + // error. Since this is lowest level at which + // we interpret message types, doing it here + // ensures that we don't have to handle it + // elsewhere. + var msg disconnectMsg + if err := Unmarshal(packet, &msg); err != nil { + return nil, err + } + return nil, &msg + } + } + + // The packet may point to an internal buffer, so copy the + // packet out here. + fresh := make([]byte, len(packet)) + copy(fresh, packet) + + return fresh, err +} + +func (t *transport) writePacket(packet []byte) error { + if debugTransport { + t.printPacket(packet, true) + } + return t.writer.writePacket(t.bufWriter, t.rand, packet) +} + +func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte) error { + changeKeys := len(packet) > 0 && packet[0] == msgNewKeys + + err := s.packetCipher.writePacket(s.seqNum, w, rand, packet) + if err != nil { + return err + } + if err = w.Flush(); err != nil { + return err + } + s.seqNum++ + if changeKeys { + select { + case cipher := <-s.pendingKeyChange: + s.packetCipher = cipher + default: + panic("ssh: no key material for msgNewKeys") + } + } + return err +} + +func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport { + t := &transport{ + bufReader: bufio.NewReader(rwc), + bufWriter: bufio.NewWriter(rwc), + rand: rand, + reader: connectionState{ + packetCipher: &streamPacketCipher{cipher: noneCipher{}}, + pendingKeyChange: make(chan packetCipher, 1), + }, + writer: connectionState{ + packetCipher: &streamPacketCipher{cipher: noneCipher{}}, + pendingKeyChange: make(chan packetCipher, 1), + }, + Closer: rwc, + } + t.isClient = isClient + + if isClient { + t.reader.dir = serverKeys + t.writer.dir = clientKeys + } else { + t.reader.dir = clientKeys + t.writer.dir = serverKeys + } + + return t +} + +type direction struct { + ivTag []byte + keyTag []byte + macKeyTag []byte +} + +var ( + serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}} + clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} +) + +// generateKeys generates key material for IV, MAC and encryption. +func generateKeys(d direction, algs directionAlgorithms, kex *kexResult) (iv, key, macKey []byte) { + cipherMode := cipherModes[algs.Cipher] + macMode := macModes[algs.MAC] + + iv = make([]byte, cipherMode.ivSize) + key = make([]byte, cipherMode.keySize) + macKey = make([]byte, macMode.keySize) + + generateKeyMaterial(iv, d.ivTag, kex) + generateKeyMaterial(key, d.keyTag, kex) + generateKeyMaterial(macKey, d.macKeyTag, kex) + return +} + +// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as +// described in RFC 4253, section 6.4. direction should either be serverKeys +// (to setup server->client keys) or clientKeys (for client->server keys). +func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (packetCipher, error) { + iv, key, macKey := generateKeys(d, algs, kex) + + switch algs.Cipher { + case chacha20Poly1305ID: + return newChaCha20Cipher(key) + case gcmCipherID: + return newGCMCipher(iv, key) + case aes128cbcID: + return newAESCBCCipher(iv, key, macKey, algs) + case tripledescbcID: + return newTripleDESCBCCipher(iv, key, macKey, algs) + } + + c := &streamPacketCipher{ + mac: macModes[algs.MAC].new(macKey), + etm: macModes[algs.MAC].etm, + } + c.macResult = make([]byte, c.mac.Size()) + + var err error + c.cipher, err = cipherModes[algs.Cipher].createStream(key, iv) + if err != nil { + return nil, err + } + + return c, nil +} + +// generateKeyMaterial fills out with key material generated from tag, K, H +// and sessionId, as specified in RFC 4253, section 7.2. +func generateKeyMaterial(out, tag []byte, r *kexResult) { + var digestsSoFar []byte + + h := r.Hash.New() + for len(out) > 0 { + h.Reset() + h.Write(r.K) + h.Write(r.H) + + if len(digestsSoFar) == 0 { + h.Write(tag) + h.Write(r.SessionID) + } else { + h.Write(digestsSoFar) + } + + digest := h.Sum(nil) + n := copy(out, digest) + out = out[n:] + if len(out) > 0 { + digestsSoFar = append(digestsSoFar, digest...) + } + } +} + +const packageVersion = "SSH-2.0-Go" + +// Sends and receives a version line. The versionLine string should +// be US ASCII, start with "SSH-2.0-", and should not include a +// newline. exchangeVersions returns the other side's version line. +func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) { + // Contrary to the RFC, we do not ignore lines that don't + // start with "SSH-2.0-" to make the library usable with + // nonconforming servers. + for _, c := range versionLine { + // The spec disallows non US-ASCII chars, and + // specifically forbids null chars. + if c < 32 { + return nil, errors.New("ssh: junk character in version line") + } + } + if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil { + return + } + + them, err = readVersion(rw) + return them, err +} + +// maxVersionStringBytes is the maximum number of bytes that we'll +// accept as a version string. RFC 4253 section 4.2 limits this at 255 +// chars +const maxVersionStringBytes = 255 + +// Read version string as specified by RFC 4253, section 4.2. +func readVersion(r io.Reader) ([]byte, error) { + versionString := make([]byte, 0, 64) + var ok bool + var buf [1]byte + + for length := 0; length < maxVersionStringBytes; length++ { + _, err := io.ReadFull(r, buf[:]) + if err != nil { + return nil, err + } + // The RFC says that the version should be terminated with \r\n + // but several SSH servers actually only send a \n. + if buf[0] == '\n' { + if !bytes.HasPrefix(versionString, []byte("SSH-")) { + // RFC 4253 says we need to ignore all version string lines + // except the one containing the SSH version (provided that + // all the lines do not exceed 255 bytes in total). + versionString = versionString[:0] + continue + } + ok = true + break + } + + // non ASCII chars are disallowed, but we are lenient, + // since Go doesn't use null-terminated strings. + + // The RFC allows a comment after a space, however, + // all of it (version and comments) goes into the + // session hash. + versionString = append(versionString, buf[0]) + } + + if !ok { + return nil, errors.New("ssh: overflow reading version string") + } + + // There might be a '\r' on the end which we should remove. + if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' { + versionString = versionString[:len(versionString)-1] + } + return versionString, nil +} diff --git a/vendor/golang.org/x/crypto/ssh/transport_test.go b/vendor/golang.org/x/crypto/ssh/transport_test.go new file mode 100644 index 0000000..8445e1e --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/transport_test.go @@ -0,0 +1,113 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "strings" + "testing" +) + +func TestReadVersion(t *testing.T) { + longVersion := strings.Repeat("SSH-2.0-bla", 50)[:253] + multiLineVersion := strings.Repeat("ignored\r\n", 20) + "SSH-2.0-bla\r\n" + cases := map[string]string{ + "SSH-2.0-bla\r\n": "SSH-2.0-bla", + "SSH-2.0-bla\n": "SSH-2.0-bla", + multiLineVersion: "SSH-2.0-bla", + longVersion + "\r\n": longVersion, + } + + for in, want := range cases { + result, err := readVersion(bytes.NewBufferString(in)) + if err != nil { + t.Errorf("readVersion(%q): %s", in, err) + } + got := string(result) + if got != want { + t.Errorf("got %q, want %q", got, want) + } + } +} + +func TestReadVersionError(t *testing.T) { + longVersion := strings.Repeat("SSH-2.0-bla", 50)[:253] + multiLineVersion := strings.Repeat("ignored\r\n", 50) + "SSH-2.0-bla\r\n" + cases := []string{ + longVersion + "too-long\r\n", + multiLineVersion, + } + for _, in := range cases { + if _, err := readVersion(bytes.NewBufferString(in)); err == nil { + t.Errorf("readVersion(%q) should have failed", in) + } + } +} + +func TestExchangeVersionsBasic(t *testing.T) { + v := "SSH-2.0-bla" + buf := bytes.NewBufferString(v + "\r\n") + them, err := exchangeVersions(buf, []byte("xyz")) + if err != nil { + t.Errorf("exchangeVersions: %v", err) + } + + if want := "SSH-2.0-bla"; string(them) != want { + t.Errorf("got %q want %q for our version", them, want) + } +} + +func TestExchangeVersions(t *testing.T) { + cases := []string{ + "not\x000allowed", + "not allowed\x01\r\n", + } + for _, c := range cases { + buf := bytes.NewBufferString("SSH-2.0-bla\r\n") + if _, err := exchangeVersions(buf, []byte(c)); err == nil { + t.Errorf("exchangeVersions(%q): should have failed", c) + } + } +} + +type closerBuffer struct { + bytes.Buffer +} + +func (b *closerBuffer) Close() error { + return nil +} + +func TestTransportMaxPacketWrite(t *testing.T) { + buf := &closerBuffer{} + tr := newTransport(buf, rand.Reader, true) + huge := make([]byte, maxPacket+1) + err := tr.writePacket(huge) + if err == nil { + t.Errorf("transport accepted write for a huge packet.") + } +} + +func TestTransportMaxPacketReader(t *testing.T) { + var header [5]byte + huge := make([]byte, maxPacket+128) + binary.BigEndian.PutUint32(header[0:], uint32(len(huge))) + // padding. + header[4] = 0 + + buf := &closerBuffer{} + buf.Write(header[:]) + buf.Write(huge) + + tr := newTransport(buf, rand.Reader, true) + _, err := tr.readPacket() + if err == nil { + t.Errorf("transport succeeded reading huge packet.") + } else if !strings.Contains(err.Error(), "large") { + t.Errorf("got %q, should mention %q", err.Error(), "large") + } +} diff --git a/vendor/golang.org/x/sys/windows/asm_windows_386.s b/vendor/golang.org/x/sys/windows/asm_windows_386.s new file mode 100644 index 0000000..1c20dd2 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/asm_windows_386.s @@ -0,0 +1,13 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +// System calls for 386, Windows are implemented in runtime/syscall_windows.goc +// + +TEXT ·getprocaddress(SB), 7, $0-8 + JMP syscall·getprocaddress(SB) + +TEXT ·loadlibrary(SB), 7, $0-4 + JMP syscall·loadlibrary(SB) diff --git a/vendor/golang.org/x/sys/windows/asm_windows_amd64.s b/vendor/golang.org/x/sys/windows/asm_windows_amd64.s new file mode 100644 index 0000000..4d025ab --- /dev/null +++ b/vendor/golang.org/x/sys/windows/asm_windows_amd64.s @@ -0,0 +1,13 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +// System calls for amd64, Windows are implemented in runtime/syscall_windows.goc +// + +TEXT ·getprocaddress(SB), 7, $0-32 + JMP syscall·getprocaddress(SB) + +TEXT ·loadlibrary(SB), 7, $0-8 + JMP syscall·loadlibrary(SB) diff --git a/vendor/golang.org/x/sys/windows/dll_windows.go b/vendor/golang.org/x/sys/windows/dll_windows.go new file mode 100644 index 0000000..e92c05b --- /dev/null +++ b/vendor/golang.org/x/sys/windows/dll_windows.go @@ -0,0 +1,378 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows + +import ( + "sync" + "sync/atomic" + "syscall" + "unsafe" +) + +// DLLError describes reasons for DLL load failures. +type DLLError struct { + Err error + ObjName string + Msg string +} + +func (e *DLLError) Error() string { return e.Msg } + +// Implemented in runtime/syscall_windows.goc; we provide jumps to them in our assembly file. +func loadlibrary(filename *uint16) (handle uintptr, err syscall.Errno) +func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err syscall.Errno) + +// A DLL implements access to a single DLL. +type DLL struct { + Name string + Handle Handle +} + +// LoadDLL loads DLL file into memory. +// +// Warning: using LoadDLL without an absolute path name is subject to +// DLL preloading attacks. To safely load a system DLL, use LazyDLL +// with System set to true, or use LoadLibraryEx directly. +func LoadDLL(name string) (dll *DLL, err error) { + namep, err := UTF16PtrFromString(name) + if err != nil { + return nil, err + } + h, e := loadlibrary(namep) + if e != 0 { + return nil, &DLLError{ + Err: e, + ObjName: name, + Msg: "Failed to load " + name + ": " + e.Error(), + } + } + d := &DLL{ + Name: name, + Handle: Handle(h), + } + return d, nil +} + +// MustLoadDLL is like LoadDLL but panics if load operation failes. +func MustLoadDLL(name string) *DLL { + d, e := LoadDLL(name) + if e != nil { + panic(e) + } + return d +} + +// FindProc searches DLL d for procedure named name and returns *Proc +// if found. It returns an error if search fails. +func (d *DLL) FindProc(name string) (proc *Proc, err error) { + namep, err := BytePtrFromString(name) + if err != nil { + return nil, err + } + a, e := getprocaddress(uintptr(d.Handle), namep) + if e != 0 { + return nil, &DLLError{ + Err: e, + ObjName: name, + Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(), + } + } + p := &Proc{ + Dll: d, + Name: name, + addr: a, + } + return p, nil +} + +// MustFindProc is like FindProc but panics if search fails. +func (d *DLL) MustFindProc(name string) *Proc { + p, e := d.FindProc(name) + if e != nil { + panic(e) + } + return p +} + +// Release unloads DLL d from memory. +func (d *DLL) Release() (err error) { + return FreeLibrary(d.Handle) +} + +// A Proc implements access to a procedure inside a DLL. +type Proc struct { + Dll *DLL + Name string + addr uintptr +} + +// Addr returns the address of the procedure represented by p. +// The return value can be passed to Syscall to run the procedure. +func (p *Proc) Addr() uintptr { + return p.addr +} + +//go:uintptrescapes + +// Call executes procedure p with arguments a. It will panic, if more than 15 arguments +// are supplied. +// +// The returned error is always non-nil, constructed from the result of GetLastError. +// Callers must inspect the primary return value to decide whether an error occurred +// (according to the semantics of the specific function being called) before consulting +// the error. The error will be guaranteed to contain windows.Errno. +func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { + switch len(a) { + case 0: + return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0) + case 1: + return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0) + case 2: + return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0) + case 3: + return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2]) + case 4: + return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0) + case 5: + return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0) + case 6: + return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5]) + case 7: + return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0) + case 8: + return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0) + case 9: + return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]) + case 10: + return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0) + case 11: + return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0) + case 12: + return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11]) + case 13: + return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0) + case 14: + return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0) + case 15: + return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14]) + default: + panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".") + } +} + +// A LazyDLL implements access to a single DLL. +// It will delay the load of the DLL until the first +// call to its Handle method or to one of its +// LazyProc's Addr method. +type LazyDLL struct { + Name string + + // System determines whether the DLL must be loaded from the + // Windows System directory, bypassing the normal DLL search + // path. + System bool + + mu sync.Mutex + dll *DLL // non nil once DLL is loaded +} + +// Load loads DLL file d.Name into memory. It returns an error if fails. +// Load will not try to load DLL, if it is already loaded into memory. +func (d *LazyDLL) Load() error { + // Non-racy version of: + // if d.dll != nil { + if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll))) != nil { + return nil + } + d.mu.Lock() + defer d.mu.Unlock() + if d.dll != nil { + return nil + } + + // kernel32.dll is special, since it's where LoadLibraryEx comes from. + // The kernel already special-cases its name, so it's always + // loaded from system32. + var dll *DLL + var err error + if d.Name == "kernel32.dll" { + dll, err = LoadDLL(d.Name) + } else { + dll, err = loadLibraryEx(d.Name, d.System) + } + if err != nil { + return err + } + + // Non-racy version of: + // d.dll = dll + atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll)), unsafe.Pointer(dll)) + return nil +} + +// mustLoad is like Load but panics if search fails. +func (d *LazyDLL) mustLoad() { + e := d.Load() + if e != nil { + panic(e) + } +} + +// Handle returns d's module handle. +func (d *LazyDLL) Handle() uintptr { + d.mustLoad() + return uintptr(d.dll.Handle) +} + +// NewProc returns a LazyProc for accessing the named procedure in the DLL d. +func (d *LazyDLL) NewProc(name string) *LazyProc { + return &LazyProc{l: d, Name: name} +} + +// NewLazyDLL creates new LazyDLL associated with DLL file. +func NewLazyDLL(name string) *LazyDLL { + return &LazyDLL{Name: name} +} + +// NewLazySystemDLL is like NewLazyDLL, but will only +// search Windows System directory for the DLL if name is +// a base name (like "advapi32.dll"). +func NewLazySystemDLL(name string) *LazyDLL { + return &LazyDLL{Name: name, System: true} +} + +// A LazyProc implements access to a procedure inside a LazyDLL. +// It delays the lookup until the Addr method is called. +type LazyProc struct { + Name string + + mu sync.Mutex + l *LazyDLL + proc *Proc +} + +// Find searches DLL for procedure named p.Name. It returns +// an error if search fails. Find will not search procedure, +// if it is already found and loaded into memory. +func (p *LazyProc) Find() error { + // Non-racy version of: + // if p.proc == nil { + if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil { + p.mu.Lock() + defer p.mu.Unlock() + if p.proc == nil { + e := p.l.Load() + if e != nil { + return e + } + proc, e := p.l.dll.FindProc(p.Name) + if e != nil { + return e + } + // Non-racy version of: + // p.proc = proc + atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc)) + } + } + return nil +} + +// mustFind is like Find but panics if search fails. +func (p *LazyProc) mustFind() { + e := p.Find() + if e != nil { + panic(e) + } +} + +// Addr returns the address of the procedure represented by p. +// The return value can be passed to Syscall to run the procedure. +// It will panic if the procedure cannot be found. +func (p *LazyProc) Addr() uintptr { + p.mustFind() + return p.proc.Addr() +} + +//go:uintptrescapes + +// Call executes procedure p with arguments a. It will panic, if more than 15 arguments +// are supplied. It will also panic if the procedure cannot be found. +// +// The returned error is always non-nil, constructed from the result of GetLastError. +// Callers must inspect the primary return value to decide whether an error occurred +// (according to the semantics of the specific function being called) before consulting +// the error. The error will be guaranteed to contain windows.Errno. +func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { + p.mustFind() + return p.proc.Call(a...) +} + +var canDoSearchSystem32Once struct { + sync.Once + v bool +} + +func initCanDoSearchSystem32() { + // https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says: + // "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows + // Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on + // systems that have KB2533623 installed. To determine whether the + // flags are available, use GetProcAddress to get the address of the + // AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories + // function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_* + // flags can be used with LoadLibraryEx." + canDoSearchSystem32Once.v = (modkernel32.NewProc("AddDllDirectory").Find() == nil) +} + +func canDoSearchSystem32() bool { + canDoSearchSystem32Once.Do(initCanDoSearchSystem32) + return canDoSearchSystem32Once.v +} + +func isBaseName(name string) bool { + for _, c := range name { + if c == ':' || c == '/' || c == '\\' { + return false + } + } + return true +} + +// loadLibraryEx wraps the Windows LoadLibraryEx function. +// +// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx +// +// If name is not an absolute path, LoadLibraryEx searches for the DLL +// in a variety of automatic locations unless constrained by flags. +// See: https://msdn.microsoft.com/en-us/library/ff919712%28VS.85%29.aspx +func loadLibraryEx(name string, system bool) (*DLL, error) { + loadDLL := name + var flags uintptr + if system { + if canDoSearchSystem32() { + const LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 + flags = LOAD_LIBRARY_SEARCH_SYSTEM32 + } else if isBaseName(name) { + // WindowsXP or unpatched Windows machine + // trying to load "foo.dll" out of the system + // folder, but LoadLibraryEx doesn't support + // that yet on their system, so emulate it. + windir, _ := Getenv("WINDIR") // old var; apparently works on XP + if windir == "" { + return nil, errString("%WINDIR% not defined") + } + loadDLL = windir + "\\System32\\" + name + } + } + h, err := LoadLibraryEx(loadDLL, 0, flags) + if err != nil { + return nil, err + } + return &DLL{Name: name, Handle: h}, nil +} + +type errString string + +func (s errString) Error() string { return string(s) } diff --git a/vendor/golang.org/x/sys/windows/env_windows.go b/vendor/golang.org/x/sys/windows/env_windows.go new file mode 100644 index 0000000..bdc71e2 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/env_windows.go @@ -0,0 +1,29 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Windows environment variables. + +package windows + +import "syscall" + +func Getenv(key string) (value string, found bool) { + return syscall.Getenv(key) +} + +func Setenv(key, value string) error { + return syscall.Setenv(key, value) +} + +func Clearenv() { + syscall.Clearenv() +} + +func Environ() []string { + return syscall.Environ() +} + +func Unsetenv(key string) error { + return syscall.Unsetenv(key) +} diff --git a/vendor/golang.org/x/sys/windows/eventlog.go b/vendor/golang.org/x/sys/windows/eventlog.go new file mode 100644 index 0000000..40af946 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/eventlog.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package windows + +const ( + EVENTLOG_SUCCESS = 0 + EVENTLOG_ERROR_TYPE = 1 + EVENTLOG_WARNING_TYPE = 2 + EVENTLOG_INFORMATION_TYPE = 4 + EVENTLOG_AUDIT_SUCCESS = 8 + EVENTLOG_AUDIT_FAILURE = 16 +) + +//sys RegisterEventSource(uncServerName *uint16, sourceName *uint16) (handle Handle, err error) [failretval==0] = advapi32.RegisterEventSourceW +//sys DeregisterEventSource(handle Handle) (err error) = advapi32.DeregisterEventSource +//sys ReportEvent(log Handle, etype uint16, category uint16, eventId uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings **uint16, rawData *byte) (err error) = advapi32.ReportEventW diff --git a/vendor/golang.org/x/sys/windows/exec_windows.go b/vendor/golang.org/x/sys/windows/exec_windows.go new file mode 100644 index 0000000..3606c3a --- /dev/null +++ b/vendor/golang.org/x/sys/windows/exec_windows.go @@ -0,0 +1,97 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Fork, exec, wait, etc. + +package windows + +// EscapeArg rewrites command line argument s as prescribed +// in http://msdn.microsoft.com/en-us/library/ms880421. +// This function returns "" (2 double quotes) if s is empty. +// Alternatively, these transformations are done: +// - every back slash (\) is doubled, but only if immediately +// followed by double quote ("); +// - every double quote (") is escaped by back slash (\); +// - finally, s is wrapped with double quotes (arg -> "arg"), +// but only if there is space or tab inside s. +func EscapeArg(s string) string { + if len(s) == 0 { + return "\"\"" + } + n := len(s) + hasSpace := false + for i := 0; i < len(s); i++ { + switch s[i] { + case '"', '\\': + n++ + case ' ', '\t': + hasSpace = true + } + } + if hasSpace { + n += 2 + } + if n == len(s) { + return s + } + + qs := make([]byte, n) + j := 0 + if hasSpace { + qs[j] = '"' + j++ + } + slashes := 0 + for i := 0; i < len(s); i++ { + switch s[i] { + default: + slashes = 0 + qs[j] = s[i] + case '\\': + slashes++ + qs[j] = s[i] + case '"': + for ; slashes > 0; slashes-- { + qs[j] = '\\' + j++ + } + qs[j] = '\\' + j++ + qs[j] = s[i] + } + j++ + } + if hasSpace { + for ; slashes > 0; slashes-- { + qs[j] = '\\' + j++ + } + qs[j] = '"' + j++ + } + return string(qs[:j]) +} + +func CloseOnExec(fd Handle) { + SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0) +} + +// FullPath retrieves the full path of the specified file. +func FullPath(name string) (path string, err error) { + p, err := UTF16PtrFromString(name) + if err != nil { + return "", err + } + n := uint32(100) + for { + buf := make([]uint16, n) + n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil) + if err != nil { + return "", err + } + if n <= uint32(len(buf)) { + return UTF16ToString(buf[:n]), nil + } + } +} diff --git a/vendor/golang.org/x/sys/windows/memory_windows.go b/vendor/golang.org/x/sys/windows/memory_windows.go new file mode 100644 index 0000000..f80a420 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/memory_windows.go @@ -0,0 +1,26 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows + +const ( + MEM_COMMIT = 0x00001000 + MEM_RESERVE = 0x00002000 + MEM_DECOMMIT = 0x00004000 + MEM_RELEASE = 0x00008000 + MEM_RESET = 0x00080000 + MEM_TOP_DOWN = 0x00100000 + MEM_WRITE_WATCH = 0x00200000 + MEM_PHYSICAL = 0x00400000 + MEM_RESET_UNDO = 0x01000000 + MEM_LARGE_PAGES = 0x20000000 + + PAGE_NOACCESS = 0x01 + PAGE_READONLY = 0x02 + PAGE_READWRITE = 0x04 + PAGE_WRITECOPY = 0x08 + PAGE_EXECUTE_READ = 0x20 + PAGE_EXECUTE_READWRITE = 0x40 + PAGE_EXECUTE_WRITECOPY = 0x80 +) diff --git a/vendor/golang.org/x/sys/windows/mksyscall.go b/vendor/golang.org/x/sys/windows/mksyscall.go new file mode 100644 index 0000000..fb7db0e --- /dev/null +++ b/vendor/golang.org/x/sys/windows/mksyscall.go @@ -0,0 +1,7 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows + +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go diff --git a/vendor/golang.org/x/sys/windows/race.go b/vendor/golang.org/x/sys/windows/race.go new file mode 100644 index 0000000..a74e3e2 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/race.go @@ -0,0 +1,30 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows,race + +package windows + +import ( + "runtime" + "unsafe" +) + +const raceenabled = true + +func raceAcquire(addr unsafe.Pointer) { + runtime.RaceAcquire(addr) +} + +func raceReleaseMerge(addr unsafe.Pointer) { + runtime.RaceReleaseMerge(addr) +} + +func raceReadRange(addr unsafe.Pointer, len int) { + runtime.RaceReadRange(addr, len) +} + +func raceWriteRange(addr unsafe.Pointer, len int) { + runtime.RaceWriteRange(addr, len) +} diff --git a/vendor/golang.org/x/sys/windows/race0.go b/vendor/golang.org/x/sys/windows/race0.go new file mode 100644 index 0000000..e44a3cb --- /dev/null +++ b/vendor/golang.org/x/sys/windows/race0.go @@ -0,0 +1,25 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows,!race + +package windows + +import ( + "unsafe" +) + +const raceenabled = false + +func raceAcquire(addr unsafe.Pointer) { +} + +func raceReleaseMerge(addr unsafe.Pointer) { +} + +func raceReadRange(addr unsafe.Pointer, len int) { +} + +func raceWriteRange(addr unsafe.Pointer, len int) { +} diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go new file mode 100644 index 0000000..f1ec5dc --- /dev/null +++ b/vendor/golang.org/x/sys/windows/security_windows.go @@ -0,0 +1,476 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows + +import ( + "syscall" + "unsafe" +) + +const ( + STANDARD_RIGHTS_REQUIRED = 0xf0000 + STANDARD_RIGHTS_READ = 0x20000 + STANDARD_RIGHTS_WRITE = 0x20000 + STANDARD_RIGHTS_EXECUTE = 0x20000 + STANDARD_RIGHTS_ALL = 0x1F0000 +) + +const ( + NameUnknown = 0 + NameFullyQualifiedDN = 1 + NameSamCompatible = 2 + NameDisplay = 3 + NameUniqueId = 6 + NameCanonical = 7 + NameUserPrincipal = 8 + NameCanonicalEx = 9 + NameServicePrincipal = 10 + NameDnsDomain = 12 +) + +// This function returns 1 byte BOOLEAN rather than the 4 byte BOOL. +// http://blogs.msdn.com/b/drnick/archive/2007/12/19/windows-and-upn-format-credentials.aspx +//sys TranslateName(accName *uint16, accNameFormat uint32, desiredNameFormat uint32, translatedName *uint16, nSize *uint32) (err error) [failretval&0xff==0] = secur32.TranslateNameW +//sys GetUserNameEx(nameFormat uint32, nameBuffre *uint16, nSize *uint32) (err error) [failretval&0xff==0] = secur32.GetUserNameExW + +// TranslateAccountName converts a directory service +// object name from one format to another. +func TranslateAccountName(username string, from, to uint32, initSize int) (string, error) { + u, e := UTF16PtrFromString(username) + if e != nil { + return "", e + } + n := uint32(50) + for { + b := make([]uint16, n) + e = TranslateName(u, from, to, &b[0], &n) + if e == nil { + return UTF16ToString(b[:n]), nil + } + if e != ERROR_INSUFFICIENT_BUFFER { + return "", e + } + if n <= uint32(len(b)) { + return "", e + } + } +} + +const ( + // do not reorder + NetSetupUnknownStatus = iota + NetSetupUnjoined + NetSetupWorkgroupName + NetSetupDomainName +) + +type UserInfo10 struct { + Name *uint16 + Comment *uint16 + UsrComment *uint16 + FullName *uint16 +} + +//sys NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) = netapi32.NetUserGetInfo +//sys NetGetJoinInformation(server *uint16, name **uint16, bufType *uint32) (neterr error) = netapi32.NetGetJoinInformation +//sys NetApiBufferFree(buf *byte) (neterr error) = netapi32.NetApiBufferFree + +const ( + // do not reorder + SidTypeUser = 1 + iota + SidTypeGroup + SidTypeDomain + SidTypeAlias + SidTypeWellKnownGroup + SidTypeDeletedAccount + SidTypeInvalid + SidTypeUnknown + SidTypeComputer + SidTypeLabel +) + +type SidIdentifierAuthority struct { + Value [6]byte +} + +var ( + SECURITY_NULL_SID_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 0}} + SECURITY_WORLD_SID_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 1}} + SECURITY_LOCAL_SID_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 2}} + SECURITY_CREATOR_SID_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 3}} + SECURITY_NON_UNIQUE_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 4}} + SECURITY_NT_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 5}} + SECURITY_MANDATORY_LABEL_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 16}} +) + +const ( + SECURITY_NULL_RID = 0 + SECURITY_WORLD_RID = 0 + SECURITY_LOCAL_RID = 0 + SECURITY_CREATOR_OWNER_RID = 0 + SECURITY_CREATOR_GROUP_RID = 1 + SECURITY_DIALUP_RID = 1 + SECURITY_NETWORK_RID = 2 + SECURITY_BATCH_RID = 3 + SECURITY_INTERACTIVE_RID = 4 + SECURITY_LOGON_IDS_RID = 5 + SECURITY_SERVICE_RID = 6 + SECURITY_LOCAL_SYSTEM_RID = 18 + SECURITY_BUILTIN_DOMAIN_RID = 32 + SECURITY_PRINCIPAL_SELF_RID = 10 + SECURITY_CREATOR_OWNER_SERVER_RID = 0x2 + SECURITY_CREATOR_GROUP_SERVER_RID = 0x3 + SECURITY_LOGON_IDS_RID_COUNT = 0x3 + SECURITY_ANONYMOUS_LOGON_RID = 0x7 + SECURITY_PROXY_RID = 0x8 + SECURITY_ENTERPRISE_CONTROLLERS_RID = 0x9 + SECURITY_SERVER_LOGON_RID = SECURITY_ENTERPRISE_CONTROLLERS_RID + SECURITY_AUTHENTICATED_USER_RID = 0xb + SECURITY_RESTRICTED_CODE_RID = 0xc + SECURITY_NT_NON_UNIQUE_RID = 0x15 +) + +// Predefined domain-relative RIDs for local groups. +// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa379649(v=vs.85).aspx +const ( + DOMAIN_ALIAS_RID_ADMINS = 0x220 + DOMAIN_ALIAS_RID_USERS = 0x221 + DOMAIN_ALIAS_RID_GUESTS = 0x222 + DOMAIN_ALIAS_RID_POWER_USERS = 0x223 + DOMAIN_ALIAS_RID_ACCOUNT_OPS = 0x224 + DOMAIN_ALIAS_RID_SYSTEM_OPS = 0x225 + DOMAIN_ALIAS_RID_PRINT_OPS = 0x226 + DOMAIN_ALIAS_RID_BACKUP_OPS = 0x227 + DOMAIN_ALIAS_RID_REPLICATOR = 0x228 + DOMAIN_ALIAS_RID_RAS_SERVERS = 0x229 + DOMAIN_ALIAS_RID_PREW2KCOMPACCESS = 0x22a + DOMAIN_ALIAS_RID_REMOTE_DESKTOP_USERS = 0x22b + DOMAIN_ALIAS_RID_NETWORK_CONFIGURATION_OPS = 0x22c + DOMAIN_ALIAS_RID_INCOMING_FOREST_TRUST_BUILDERS = 0x22d + DOMAIN_ALIAS_RID_MONITORING_USERS = 0X22e + DOMAIN_ALIAS_RID_LOGGING_USERS = 0x22f + DOMAIN_ALIAS_RID_AUTHORIZATIONACCESS = 0x230 + DOMAIN_ALIAS_RID_TS_LICENSE_SERVERS = 0x231 + DOMAIN_ALIAS_RID_DCOM_USERS = 0x232 + DOMAIN_ALIAS_RID_IUSERS = 0x238 + DOMAIN_ALIAS_RID_CRYPTO_OPERATORS = 0x239 + DOMAIN_ALIAS_RID_CACHEABLE_PRINCIPALS_GROUP = 0x23b + DOMAIN_ALIAS_RID_NON_CACHEABLE_PRINCIPALS_GROUP = 0x23c + DOMAIN_ALIAS_RID_EVENT_LOG_READERS_GROUP = 0x23d + DOMAIN_ALIAS_RID_CERTSVC_DCOM_ACCESS_GROUP = 0x23e +) + +//sys LookupAccountSid(systemName *uint16, sid *SID, name *uint16, nameLen *uint32, refdDomainName *uint16, refdDomainNameLen *uint32, use *uint32) (err error) = advapi32.LookupAccountSidW +//sys LookupAccountName(systemName *uint16, accountName *uint16, sid *SID, sidLen *uint32, refdDomainName *uint16, refdDomainNameLen *uint32, use *uint32) (err error) = advapi32.LookupAccountNameW +//sys ConvertSidToStringSid(sid *SID, stringSid **uint16) (err error) = advapi32.ConvertSidToStringSidW +//sys ConvertStringSidToSid(stringSid *uint16, sid **SID) (err error) = advapi32.ConvertStringSidToSidW +//sys GetLengthSid(sid *SID) (len uint32) = advapi32.GetLengthSid +//sys CopySid(destSidLen uint32, destSid *SID, srcSid *SID) (err error) = advapi32.CopySid +//sys AllocateAndInitializeSid(identAuth *SidIdentifierAuthority, subAuth byte, subAuth0 uint32, subAuth1 uint32, subAuth2 uint32, subAuth3 uint32, subAuth4 uint32, subAuth5 uint32, subAuth6 uint32, subAuth7 uint32, sid **SID) (err error) = advapi32.AllocateAndInitializeSid +//sys FreeSid(sid *SID) (err error) [failretval!=0] = advapi32.FreeSid +//sys EqualSid(sid1 *SID, sid2 *SID) (isEqual bool) = advapi32.EqualSid + +// The security identifier (SID) structure is a variable-length +// structure used to uniquely identify users or groups. +type SID struct{} + +// StringToSid converts a string-format security identifier +// sid into a valid, functional sid. +func StringToSid(s string) (*SID, error) { + var sid *SID + p, e := UTF16PtrFromString(s) + if e != nil { + return nil, e + } + e = ConvertStringSidToSid(p, &sid) + if e != nil { + return nil, e + } + defer LocalFree((Handle)(unsafe.Pointer(sid))) + return sid.Copy() +} + +// LookupSID retrieves a security identifier sid for the account +// and the name of the domain on which the account was found. +// System specify target computer to search. +func LookupSID(system, account string) (sid *SID, domain string, accType uint32, err error) { + if len(account) == 0 { + return nil, "", 0, syscall.EINVAL + } + acc, e := UTF16PtrFromString(account) + if e != nil { + return nil, "", 0, e + } + var sys *uint16 + if len(system) > 0 { + sys, e = UTF16PtrFromString(system) + if e != nil { + return nil, "", 0, e + } + } + n := uint32(50) + dn := uint32(50) + for { + b := make([]byte, n) + db := make([]uint16, dn) + sid = (*SID)(unsafe.Pointer(&b[0])) + e = LookupAccountName(sys, acc, sid, &n, &db[0], &dn, &accType) + if e == nil { + return sid, UTF16ToString(db), accType, nil + } + if e != ERROR_INSUFFICIENT_BUFFER { + return nil, "", 0, e + } + if n <= uint32(len(b)) { + return nil, "", 0, e + } + } +} + +// String converts sid to a string format +// suitable for display, storage, or transmission. +func (sid *SID) String() (string, error) { + var s *uint16 + e := ConvertSidToStringSid(sid, &s) + if e != nil { + return "", e + } + defer LocalFree((Handle)(unsafe.Pointer(s))) + return UTF16ToString((*[256]uint16)(unsafe.Pointer(s))[:]), nil +} + +// Len returns the length, in bytes, of a valid security identifier sid. +func (sid *SID) Len() int { + return int(GetLengthSid(sid)) +} + +// Copy creates a duplicate of security identifier sid. +func (sid *SID) Copy() (*SID, error) { + b := make([]byte, sid.Len()) + sid2 := (*SID)(unsafe.Pointer(&b[0])) + e := CopySid(uint32(len(b)), sid2, sid) + if e != nil { + return nil, e + } + return sid2, nil +} + +// LookupAccount retrieves the name of the account for this sid +// and the name of the first domain on which this sid is found. +// System specify target computer to search for. +func (sid *SID) LookupAccount(system string) (account, domain string, accType uint32, err error) { + var sys *uint16 + if len(system) > 0 { + sys, err = UTF16PtrFromString(system) + if err != nil { + return "", "", 0, err + } + } + n := uint32(50) + dn := uint32(50) + for { + b := make([]uint16, n) + db := make([]uint16, dn) + e := LookupAccountSid(sys, sid, &b[0], &n, &db[0], &dn, &accType) + if e == nil { + return UTF16ToString(b), UTF16ToString(db), accType, nil + } + if e != ERROR_INSUFFICIENT_BUFFER { + return "", "", 0, e + } + if n <= uint32(len(b)) { + return "", "", 0, e + } + } +} + +const ( + // do not reorder + TOKEN_ASSIGN_PRIMARY = 1 << iota + TOKEN_DUPLICATE + TOKEN_IMPERSONATE + TOKEN_QUERY + TOKEN_QUERY_SOURCE + TOKEN_ADJUST_PRIVILEGES + TOKEN_ADJUST_GROUPS + TOKEN_ADJUST_DEFAULT + + TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | + TOKEN_ASSIGN_PRIMARY | + TOKEN_DUPLICATE | + TOKEN_IMPERSONATE | + TOKEN_QUERY | + TOKEN_QUERY_SOURCE | + TOKEN_ADJUST_PRIVILEGES | + TOKEN_ADJUST_GROUPS | + TOKEN_ADJUST_DEFAULT + TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY + TOKEN_WRITE = STANDARD_RIGHTS_WRITE | + TOKEN_ADJUST_PRIVILEGES | + TOKEN_ADJUST_GROUPS | + TOKEN_ADJUST_DEFAULT + TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE +) + +const ( + // do not reorder + TokenUser = 1 + iota + TokenGroups + TokenPrivileges + TokenOwner + TokenPrimaryGroup + TokenDefaultDacl + TokenSource + TokenType + TokenImpersonationLevel + TokenStatistics + TokenRestrictedSids + TokenSessionId + TokenGroupsAndPrivileges + TokenSessionReference + TokenSandBoxInert + TokenAuditPolicy + TokenOrigin + TokenElevationType + TokenLinkedToken + TokenElevation + TokenHasRestrictions + TokenAccessInformation + TokenVirtualizationAllowed + TokenVirtualizationEnabled + TokenIntegrityLevel + TokenUIAccess + TokenMandatoryPolicy + TokenLogonSid + MaxTokenInfoClass +) + +type SIDAndAttributes struct { + Sid *SID + Attributes uint32 +} + +type Tokenuser struct { + User SIDAndAttributes +} + +type Tokenprimarygroup struct { + PrimaryGroup *SID +} + +type Tokengroups struct { + GroupCount uint32 + Groups [1]SIDAndAttributes +} + +// Authorization Functions +//sys checkTokenMembership(tokenHandle Token, sidToCheck *SID, isMember *int32) (err error) = advapi32.CheckTokenMembership +//sys OpenProcessToken(h Handle, access uint32, token *Token) (err error) = advapi32.OpenProcessToken +//sys GetTokenInformation(t Token, infoClass uint32, info *byte, infoLen uint32, returnedLen *uint32) (err error) = advapi32.GetTokenInformation +//sys GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err error) = userenv.GetUserProfileDirectoryW + +// An access token contains the security information for a logon session. +// The system creates an access token when a user logs on, and every +// process executed on behalf of the user has a copy of the token. +// The token identifies the user, the user's groups, and the user's +// privileges. The system uses the token to control access to securable +// objects and to control the ability of the user to perform various +// system-related operations on the local computer. +type Token Handle + +// OpenCurrentProcessToken opens the access token +// associated with current process. +func OpenCurrentProcessToken() (Token, error) { + p, e := GetCurrentProcess() + if e != nil { + return 0, e + } + var t Token + e = OpenProcessToken(p, TOKEN_QUERY, &t) + if e != nil { + return 0, e + } + return t, nil +} + +// Close releases access to access token. +func (t Token) Close() error { + return CloseHandle(Handle(t)) +} + +// getInfo retrieves a specified type of information about an access token. +func (t Token) getInfo(class uint32, initSize int) (unsafe.Pointer, error) { + n := uint32(initSize) + for { + b := make([]byte, n) + e := GetTokenInformation(t, class, &b[0], uint32(len(b)), &n) + if e == nil { + return unsafe.Pointer(&b[0]), nil + } + if e != ERROR_INSUFFICIENT_BUFFER { + return nil, e + } + if n <= uint32(len(b)) { + return nil, e + } + } +} + +// GetTokenUser retrieves access token t user account information. +func (t Token) GetTokenUser() (*Tokenuser, error) { + i, e := t.getInfo(TokenUser, 50) + if e != nil { + return nil, e + } + return (*Tokenuser)(i), nil +} + +// GetTokenGroups retrieves group accounts associated with access token t. +func (t Token) GetTokenGroups() (*Tokengroups, error) { + i, e := t.getInfo(TokenGroups, 50) + if e != nil { + return nil, e + } + return (*Tokengroups)(i), nil +} + +// GetTokenPrimaryGroup retrieves access token t primary group information. +// A pointer to a SID structure representing a group that will become +// the primary group of any objects created by a process using this access token. +func (t Token) GetTokenPrimaryGroup() (*Tokenprimarygroup, error) { + i, e := t.getInfo(TokenPrimaryGroup, 50) + if e != nil { + return nil, e + } + return (*Tokenprimarygroup)(i), nil +} + +// GetUserProfileDirectory retrieves path to the +// root directory of the access token t user's profile. +func (t Token) GetUserProfileDirectory() (string, error) { + n := uint32(100) + for { + b := make([]uint16, n) + e := GetUserProfileDirectory(t, &b[0], &n) + if e == nil { + return UTF16ToString(b), nil + } + if e != ERROR_INSUFFICIENT_BUFFER { + return "", e + } + if n <= uint32(len(b)) { + return "", e + } + } +} + +// IsMember reports whether the access token t is a member of the provided SID. +func (t Token) IsMember(sid *SID) (bool, error) { + var b int32 + if e := checkTokenMembership(t, sid, &b); e != nil { + return false, e + } + return b != 0, nil +} diff --git a/vendor/golang.org/x/sys/windows/service.go b/vendor/golang.org/x/sys/windows/service.go new file mode 100644 index 0000000..a500dd7 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/service.go @@ -0,0 +1,164 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package windows + +const ( + SC_MANAGER_CONNECT = 1 + SC_MANAGER_CREATE_SERVICE = 2 + SC_MANAGER_ENUMERATE_SERVICE = 4 + SC_MANAGER_LOCK = 8 + SC_MANAGER_QUERY_LOCK_STATUS = 16 + SC_MANAGER_MODIFY_BOOT_CONFIG = 32 + SC_MANAGER_ALL_ACCESS = 0xf003f +) + +//sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW + +const ( + SERVICE_KERNEL_DRIVER = 1 + SERVICE_FILE_SYSTEM_DRIVER = 2 + SERVICE_ADAPTER = 4 + SERVICE_RECOGNIZER_DRIVER = 8 + SERVICE_WIN32_OWN_PROCESS = 16 + SERVICE_WIN32_SHARE_PROCESS = 32 + SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS + SERVICE_INTERACTIVE_PROCESS = 256 + SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER + SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS + + SERVICE_BOOT_START = 0 + SERVICE_SYSTEM_START = 1 + SERVICE_AUTO_START = 2 + SERVICE_DEMAND_START = 3 + SERVICE_DISABLED = 4 + + SERVICE_ERROR_IGNORE = 0 + SERVICE_ERROR_NORMAL = 1 + SERVICE_ERROR_SEVERE = 2 + SERVICE_ERROR_CRITICAL = 3 + + SC_STATUS_PROCESS_INFO = 0 + + SERVICE_STOPPED = 1 + SERVICE_START_PENDING = 2 + SERVICE_STOP_PENDING = 3 + SERVICE_RUNNING = 4 + SERVICE_CONTINUE_PENDING = 5 + SERVICE_PAUSE_PENDING = 6 + SERVICE_PAUSED = 7 + SERVICE_NO_CHANGE = 0xffffffff + + SERVICE_ACCEPT_STOP = 1 + SERVICE_ACCEPT_PAUSE_CONTINUE = 2 + SERVICE_ACCEPT_SHUTDOWN = 4 + SERVICE_ACCEPT_PARAMCHANGE = 8 + SERVICE_ACCEPT_NETBINDCHANGE = 16 + SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 32 + SERVICE_ACCEPT_POWEREVENT = 64 + SERVICE_ACCEPT_SESSIONCHANGE = 128 + + SERVICE_CONTROL_STOP = 1 + SERVICE_CONTROL_PAUSE = 2 + SERVICE_CONTROL_CONTINUE = 3 + SERVICE_CONTROL_INTERROGATE = 4 + SERVICE_CONTROL_SHUTDOWN = 5 + SERVICE_CONTROL_PARAMCHANGE = 6 + SERVICE_CONTROL_NETBINDADD = 7 + SERVICE_CONTROL_NETBINDREMOVE = 8 + SERVICE_CONTROL_NETBINDENABLE = 9 + SERVICE_CONTROL_NETBINDDISABLE = 10 + SERVICE_CONTROL_DEVICEEVENT = 11 + SERVICE_CONTROL_HARDWAREPROFILECHANGE = 12 + SERVICE_CONTROL_POWEREVENT = 13 + SERVICE_CONTROL_SESSIONCHANGE = 14 + + SERVICE_ACTIVE = 1 + SERVICE_INACTIVE = 2 + SERVICE_STATE_ALL = 3 + + SERVICE_QUERY_CONFIG = 1 + SERVICE_CHANGE_CONFIG = 2 + SERVICE_QUERY_STATUS = 4 + SERVICE_ENUMERATE_DEPENDENTS = 8 + SERVICE_START = 16 + SERVICE_STOP = 32 + SERVICE_PAUSE_CONTINUE = 64 + SERVICE_INTERROGATE = 128 + SERVICE_USER_DEFINED_CONTROL = 256 + SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL + SERVICE_RUNS_IN_SYSTEM_PROCESS = 1 + SERVICE_CONFIG_DESCRIPTION = 1 + SERVICE_CONFIG_FAILURE_ACTIONS = 2 + + NO_ERROR = 0 + + SC_ENUM_PROCESS_INFO = 0 +) + +type SERVICE_STATUS struct { + ServiceType uint32 + CurrentState uint32 + ControlsAccepted uint32 + Win32ExitCode uint32 + ServiceSpecificExitCode uint32 + CheckPoint uint32 + WaitHint uint32 +} + +type SERVICE_TABLE_ENTRY struct { + ServiceName *uint16 + ServiceProc uintptr +} + +type QUERY_SERVICE_CONFIG struct { + ServiceType uint32 + StartType uint32 + ErrorControl uint32 + BinaryPathName *uint16 + LoadOrderGroup *uint16 + TagId uint32 + Dependencies *uint16 + ServiceStartName *uint16 + DisplayName *uint16 +} + +type SERVICE_DESCRIPTION struct { + Description *uint16 +} + +type SERVICE_STATUS_PROCESS struct { + ServiceType uint32 + CurrentState uint32 + ControlsAccepted uint32 + Win32ExitCode uint32 + ServiceSpecificExitCode uint32 + CheckPoint uint32 + WaitHint uint32 + ProcessId uint32 + ServiceFlags uint32 +} + +type ENUM_SERVICE_STATUS_PROCESS struct { + ServiceName *uint16 + DisplayName *uint16 + ServiceStatusProcess SERVICE_STATUS_PROCESS +} + +//sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle +//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW +//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW +//sys DeleteService(service Handle) (err error) = advapi32.DeleteService +//sys StartService(service Handle, numArgs uint32, argVectors **uint16) (err error) = advapi32.StartServiceW +//sys QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err error) = advapi32.QueryServiceStatus +//sys ControlService(service Handle, control uint32, status *SERVICE_STATUS) (err error) = advapi32.ControlService +//sys StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err error) = advapi32.StartServiceCtrlDispatcherW +//sys SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err error) = advapi32.SetServiceStatus +//sys ChangeServiceConfig(service Handle, serviceType uint32, startType uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16, displayName *uint16) (err error) = advapi32.ChangeServiceConfigW +//sys QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfigW +//sys ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) = advapi32.ChangeServiceConfig2W +//sys QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfig2W +//sys EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) = advapi32.EnumServicesStatusExW diff --git a/vendor/golang.org/x/sys/windows/str.go b/vendor/golang.org/x/sys/windows/str.go new file mode 100644 index 0000000..917cc2a --- /dev/null +++ b/vendor/golang.org/x/sys/windows/str.go @@ -0,0 +1,22 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package windows + +func itoa(val int) string { // do it here rather than with fmt to avoid dependency + if val < 0 { + return "-" + itoa(-val) + } + var buf [32]byte // big enough for int64 + i := len(buf) - 1 + for val >= 10 { + buf[i] = byte(val%10 + '0') + i-- + val /= 10 + } + buf[i] = byte(val + '0') + return string(buf[i:]) +} diff --git a/vendor/golang.org/x/sys/windows/syscall.go b/vendor/golang.org/x/sys/windows/syscall.go new file mode 100644 index 0000000..b07bc23 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/syscall.go @@ -0,0 +1,71 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package windows contains an interface to the low-level operating system +// primitives. OS details vary depending on the underlying system, and +// by default, godoc will display the OS-specific documentation for the current +// system. If you want godoc to display syscall documentation for another +// system, set $GOOS and $GOARCH to the desired system. For example, if +// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS +// to freebsd and $GOARCH to arm. +// The primary use of this package is inside other packages that provide a more +// portable interface to the system, such as "os", "time" and "net". Use +// those packages rather than this one if you can. +// For details of the functions and data types in this package consult +// the manuals for the appropriate operating system. +// These calls return err == nil to indicate success; otherwise +// err represents an operating system error describing the failure and +// holds a value of type syscall.Errno. +package windows // import "golang.org/x/sys/windows" + +import ( + "syscall" +) + +// ByteSliceFromString returns a NUL-terminated slice of bytes +// containing the text of s. If s contains a NUL byte at any +// location, it returns (nil, syscall.EINVAL). +func ByteSliceFromString(s string) ([]byte, error) { + for i := 0; i < len(s); i++ { + if s[i] == 0 { + return nil, syscall.EINVAL + } + } + a := make([]byte, len(s)+1) + copy(a, s) + return a, nil +} + +// BytePtrFromString returns a pointer to a NUL-terminated array of +// bytes containing the text of s. If s contains a NUL byte at any +// location, it returns (nil, syscall.EINVAL). +func BytePtrFromString(s string) (*byte, error) { + a, err := ByteSliceFromString(s) + if err != nil { + return nil, err + } + return &a[0], nil +} + +// Single-word zero for use when we need a valid pointer to 0 bytes. +// See mksyscall.pl. +var _zero uintptr + +func (ts *Timespec) Unix() (sec int64, nsec int64) { + return int64(ts.Sec), int64(ts.Nsec) +} + +func (tv *Timeval) Unix() (sec int64, nsec int64) { + return int64(tv.Sec), int64(tv.Usec) * 1000 +} + +func (ts *Timespec) Nano() int64 { + return int64(ts.Sec)*1e9 + int64(ts.Nsec) +} + +func (tv *Timeval) Nano() int64 { + return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000 +} diff --git a/vendor/golang.org/x/sys/windows/syscall_test.go b/vendor/golang.org/x/sys/windows/syscall_test.go new file mode 100644 index 0000000..d7009e4 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/syscall_test.go @@ -0,0 +1,53 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package windows_test + +import ( + "syscall" + "testing" + + "golang.org/x/sys/windows" +) + +func testSetGetenv(t *testing.T, key, value string) { + err := windows.Setenv(key, value) + if err != nil { + t.Fatalf("Setenv failed to set %q: %v", value, err) + } + newvalue, found := windows.Getenv(key) + if !found { + t.Fatalf("Getenv failed to find %v variable (want value %q)", key, value) + } + if newvalue != value { + t.Fatalf("Getenv(%v) = %q; want %q", key, newvalue, value) + } +} + +func TestEnv(t *testing.T) { + testSetGetenv(t, "TESTENV", "AVALUE") + // make sure TESTENV gets set to "", not deleted + testSetGetenv(t, "TESTENV", "") +} + +func TestGetProcAddressByOrdinal(t *testing.T) { + // Attempt calling shlwapi.dll:IsOS, resolving it by ordinal, as + // suggested in + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb773795.aspx + h, err := windows.LoadLibrary("shlwapi.dll") + if err != nil { + t.Fatalf("Failed to load shlwapi.dll: %s", err) + } + procIsOS, err := windows.GetProcAddressByOrdinal(h, 437) + if err != nil { + t.Fatalf("Could not find shlwapi.dll:IsOS by ordinal: %s", err) + } + const OS_NT = 1 + r, _, _ := syscall.Syscall(procIsOS, 1, OS_NT, 0, 0) + if r == 0 { + t.Error("shlwapi.dll:IsOS(OS_NT) returned 0, expected non-zero value") + } +} diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go new file mode 100644 index 0000000..1e9f4bb --- /dev/null +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -0,0 +1,1153 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Windows system calls. + +package windows + +import ( + errorspkg "errors" + "sync" + "syscall" + "unicode/utf16" + "unsafe" +) + +type Handle uintptr + +const ( + InvalidHandle = ^Handle(0) + + // Flags for DefineDosDevice. + DDD_EXACT_MATCH_ON_REMOVE = 0x00000004 + DDD_NO_BROADCAST_SYSTEM = 0x00000008 + DDD_RAW_TARGET_PATH = 0x00000001 + DDD_REMOVE_DEFINITION = 0x00000002 + + // Return values for GetDriveType. + DRIVE_UNKNOWN = 0 + DRIVE_NO_ROOT_DIR = 1 + DRIVE_REMOVABLE = 2 + DRIVE_FIXED = 3 + DRIVE_REMOTE = 4 + DRIVE_CDROM = 5 + DRIVE_RAMDISK = 6 + + // File system flags from GetVolumeInformation and GetVolumeInformationByHandle. + FILE_CASE_SENSITIVE_SEARCH = 0x00000001 + FILE_CASE_PRESERVED_NAMES = 0x00000002 + FILE_FILE_COMPRESSION = 0x00000010 + FILE_DAX_VOLUME = 0x20000000 + FILE_NAMED_STREAMS = 0x00040000 + FILE_PERSISTENT_ACLS = 0x00000008 + FILE_READ_ONLY_VOLUME = 0x00080000 + FILE_SEQUENTIAL_WRITE_ONCE = 0x00100000 + FILE_SUPPORTS_ENCRYPTION = 0x00020000 + FILE_SUPPORTS_EXTENDED_ATTRIBUTES = 0x00800000 + FILE_SUPPORTS_HARD_LINKS = 0x00400000 + FILE_SUPPORTS_OBJECT_IDS = 0x00010000 + FILE_SUPPORTS_OPEN_BY_FILE_ID = 0x01000000 + FILE_SUPPORTS_REPARSE_POINTS = 0x00000080 + FILE_SUPPORTS_SPARSE_FILES = 0x00000040 + FILE_SUPPORTS_TRANSACTIONS = 0x00200000 + FILE_SUPPORTS_USN_JOURNAL = 0x02000000 + FILE_UNICODE_ON_DISK = 0x00000004 + FILE_VOLUME_IS_COMPRESSED = 0x00008000 + FILE_VOLUME_QUOTAS = 0x00000020 +) + +// StringToUTF16 is deprecated. Use UTF16FromString instead. +// If s contains a NUL byte this function panics instead of +// returning an error. +func StringToUTF16(s string) []uint16 { + a, err := UTF16FromString(s) + if err != nil { + panic("windows: string with NUL passed to StringToUTF16") + } + return a +} + +// UTF16FromString returns the UTF-16 encoding of the UTF-8 string +// s, with a terminating NUL added. If s contains a NUL byte at any +// location, it returns (nil, syscall.EINVAL). +func UTF16FromString(s string) ([]uint16, error) { + for i := 0; i < len(s); i++ { + if s[i] == 0 { + return nil, syscall.EINVAL + } + } + return utf16.Encode([]rune(s + "\x00")), nil +} + +// UTF16ToString returns the UTF-8 encoding of the UTF-16 sequence s, +// with a terminating NUL removed. +func UTF16ToString(s []uint16) string { + for i, v := range s { + if v == 0 { + s = s[0:i] + break + } + } + return string(utf16.Decode(s)) +} + +// StringToUTF16Ptr is deprecated. Use UTF16PtrFromString instead. +// If s contains a NUL byte this function panics instead of +// returning an error. +func StringToUTF16Ptr(s string) *uint16 { return &StringToUTF16(s)[0] } + +// UTF16PtrFromString returns pointer to the UTF-16 encoding of +// the UTF-8 string s, with a terminating NUL added. If s +// contains a NUL byte at any location, it returns (nil, syscall.EINVAL). +func UTF16PtrFromString(s string) (*uint16, error) { + a, err := UTF16FromString(s) + if err != nil { + return nil, err + } + return &a[0], nil +} + +func Getpagesize() int { return 4096 } + +// NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention. +// This is useful when interoperating with Windows code requiring callbacks. +func NewCallback(fn interface{}) uintptr { + return syscall.NewCallback(fn) +} + +// NewCallbackCDecl converts a Go function to a function pointer conforming to the cdecl calling convention. +// This is useful when interoperating with Windows code requiring callbacks. +func NewCallbackCDecl(fn interface{}) uintptr { + return syscall.NewCallbackCDecl(fn) +} + +// windows api calls + +//sys GetLastError() (lasterr error) +//sys LoadLibrary(libname string) (handle Handle, err error) = LoadLibraryW +//sys LoadLibraryEx(libname string, zero Handle, flags uintptr) (handle Handle, err error) = LoadLibraryExW +//sys FreeLibrary(handle Handle) (err error) +//sys GetProcAddress(module Handle, procname string) (proc uintptr, err error) +//sys GetVersion() (ver uint32, err error) +//sys FormatMessage(flags uint32, msgsrc uintptr, msgid uint32, langid uint32, buf []uint16, args *byte) (n uint32, err error) = FormatMessageW +//sys ExitProcess(exitcode uint32) +//sys CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile int32) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW +//sys ReadFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) (err error) +//sys WriteFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) (err error) +//sys SetFilePointer(handle Handle, lowoffset int32, highoffsetptr *int32, whence uint32) (newlowoffset uint32, err error) [failretval==0xffffffff] +//sys CloseHandle(handle Handle) (err error) +//sys GetStdHandle(stdhandle uint32) (handle Handle, err error) [failretval==InvalidHandle] +//sys SetStdHandle(stdhandle uint32, handle Handle) (err error) +//sys findFirstFile1(name *uint16, data *win32finddata1) (handle Handle, err error) [failretval==InvalidHandle] = FindFirstFileW +//sys findNextFile1(handle Handle, data *win32finddata1) (err error) = FindNextFileW +//sys FindClose(handle Handle) (err error) +//sys GetFileInformationByHandle(handle Handle, data *ByHandleFileInformation) (err error) +//sys GetCurrentDirectory(buflen uint32, buf *uint16) (n uint32, err error) = GetCurrentDirectoryW +//sys SetCurrentDirectory(path *uint16) (err error) = SetCurrentDirectoryW +//sys CreateDirectory(path *uint16, sa *SecurityAttributes) (err error) = CreateDirectoryW +//sys RemoveDirectory(path *uint16) (err error) = RemoveDirectoryW +//sys DeleteFile(path *uint16) (err error) = DeleteFileW +//sys MoveFile(from *uint16, to *uint16) (err error) = MoveFileW +//sys MoveFileEx(from *uint16, to *uint16, flags uint32) (err error) = MoveFileExW +//sys GetComputerName(buf *uint16, n *uint32) (err error) = GetComputerNameW +//sys GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) = GetComputerNameExW +//sys SetEndOfFile(handle Handle) (err error) +//sys GetSystemTimeAsFileTime(time *Filetime) +//sys GetSystemTimePreciseAsFileTime(time *Filetime) +//sys GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, err error) [failretval==0xffffffff] +//sys CreateIoCompletionPort(filehandle Handle, cphandle Handle, key uint32, threadcnt uint32) (handle Handle, err error) +//sys GetQueuedCompletionStatus(cphandle Handle, qty *uint32, key *uint32, overlapped **Overlapped, timeout uint32) (err error) +//sys PostQueuedCompletionStatus(cphandle Handle, qty uint32, key uint32, overlapped *Overlapped) (err error) +//sys CancelIo(s Handle) (err error) +//sys CancelIoEx(s Handle, o *Overlapped) (err error) +//sys CreateProcess(appName *uint16, commandLine *uint16, procSecurity *SecurityAttributes, threadSecurity *SecurityAttributes, inheritHandles bool, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *StartupInfo, outProcInfo *ProcessInformation) (err error) = CreateProcessW +//sys OpenProcess(da uint32, inheritHandle bool, pid uint32) (handle Handle, err error) +//sys TerminateProcess(handle Handle, exitcode uint32) (err error) +//sys GetExitCodeProcess(handle Handle, exitcode *uint32) (err error) +//sys GetStartupInfo(startupInfo *StartupInfo) (err error) = GetStartupInfoW +//sys GetCurrentProcess() (pseudoHandle Handle, err error) +//sys GetProcessTimes(handle Handle, creationTime *Filetime, exitTime *Filetime, kernelTime *Filetime, userTime *Filetime) (err error) +//sys DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) +//sys WaitForSingleObject(handle Handle, waitMilliseconds uint32) (event uint32, err error) [failretval==0xffffffff] +//sys GetTempPath(buflen uint32, buf *uint16) (n uint32, err error) = GetTempPathW +//sys CreatePipe(readhandle *Handle, writehandle *Handle, sa *SecurityAttributes, size uint32) (err error) +//sys GetFileType(filehandle Handle) (n uint32, err error) +//sys CryptAcquireContext(provhandle *Handle, container *uint16, provider *uint16, provtype uint32, flags uint32) (err error) = advapi32.CryptAcquireContextW +//sys CryptReleaseContext(provhandle Handle, flags uint32) (err error) = advapi32.CryptReleaseContext +//sys CryptGenRandom(provhandle Handle, buflen uint32, buf *byte) (err error) = advapi32.CryptGenRandom +//sys GetEnvironmentStrings() (envs *uint16, err error) [failretval==nil] = kernel32.GetEnvironmentStringsW +//sys FreeEnvironmentStrings(envs *uint16) (err error) = kernel32.FreeEnvironmentStringsW +//sys GetEnvironmentVariable(name *uint16, buffer *uint16, size uint32) (n uint32, err error) = kernel32.GetEnvironmentVariableW +//sys SetEnvironmentVariable(name *uint16, value *uint16) (err error) = kernel32.SetEnvironmentVariableW +//sys SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error) +//sys GetFileAttributes(name *uint16) (attrs uint32, err error) [failretval==INVALID_FILE_ATTRIBUTES] = kernel32.GetFileAttributesW +//sys SetFileAttributes(name *uint16, attrs uint32) (err error) = kernel32.SetFileAttributesW +//sys GetFileAttributesEx(name *uint16, level uint32, info *byte) (err error) = kernel32.GetFileAttributesExW +//sys GetCommandLine() (cmd *uint16) = kernel32.GetCommandLineW +//sys CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) [failretval==nil] = shell32.CommandLineToArgvW +//sys LocalFree(hmem Handle) (handle Handle, err error) [failretval!=0] +//sys SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) +//sys FlushFileBuffers(handle Handle) (err error) +//sys GetFullPathName(path *uint16, buflen uint32, buf *uint16, fname **uint16) (n uint32, err error) = kernel32.GetFullPathNameW +//sys GetLongPathName(path *uint16, buf *uint16, buflen uint32) (n uint32, err error) = kernel32.GetLongPathNameW +//sys GetShortPathName(longpath *uint16, shortpath *uint16, buflen uint32) (n uint32, err error) = kernel32.GetShortPathNameW +//sys CreateFileMapping(fhandle Handle, sa *SecurityAttributes, prot uint32, maxSizeHigh uint32, maxSizeLow uint32, name *uint16) (handle Handle, err error) = kernel32.CreateFileMappingW +//sys MapViewOfFile(handle Handle, access uint32, offsetHigh uint32, offsetLow uint32, length uintptr) (addr uintptr, err error) +//sys UnmapViewOfFile(addr uintptr) (err error) +//sys FlushViewOfFile(addr uintptr, length uintptr) (err error) +//sys VirtualLock(addr uintptr, length uintptr) (err error) +//sys VirtualUnlock(addr uintptr, length uintptr) (err error) +//sys VirtualAlloc(address uintptr, size uintptr, alloctype uint32, protect uint32) (value uintptr, err error) = kernel32.VirtualAlloc +//sys VirtualFree(address uintptr, size uintptr, freetype uint32) (err error) = kernel32.VirtualFree +//sys VirtualProtect(address uintptr, size uintptr, newprotect uint32, oldprotect *uint32) (err error) = kernel32.VirtualProtect +//sys TransmitFile(s Handle, handle Handle, bytesToWrite uint32, bytsPerSend uint32, overlapped *Overlapped, transmitFileBuf *TransmitFileBuffers, flags uint32) (err error) = mswsock.TransmitFile +//sys ReadDirectoryChanges(handle Handle, buf *byte, buflen uint32, watchSubTree bool, mask uint32, retlen *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) = kernel32.ReadDirectoryChangesW +//sys CertOpenSystemStore(hprov Handle, name *uint16) (store Handle, err error) = crypt32.CertOpenSystemStoreW +//sys CertOpenStore(storeProvider uintptr, msgAndCertEncodingType uint32, cryptProv uintptr, flags uint32, para uintptr) (handle Handle, err error) [failretval==InvalidHandle] = crypt32.CertOpenStore +//sys CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext, err error) [failretval==nil] = crypt32.CertEnumCertificatesInStore +//sys CertAddCertificateContextToStore(store Handle, certContext *CertContext, addDisposition uint32, storeContext **CertContext) (err error) = crypt32.CertAddCertificateContextToStore +//sys CertCloseStore(store Handle, flags uint32) (err error) = crypt32.CertCloseStore +//sys CertGetCertificateChain(engine Handle, leaf *CertContext, time *Filetime, additionalStore Handle, para *CertChainPara, flags uint32, reserved uintptr, chainCtx **CertChainContext) (err error) = crypt32.CertGetCertificateChain +//sys CertFreeCertificateChain(ctx *CertChainContext) = crypt32.CertFreeCertificateChain +//sys CertCreateCertificateContext(certEncodingType uint32, certEncoded *byte, encodedLen uint32) (context *CertContext, err error) [failretval==nil] = crypt32.CertCreateCertificateContext +//sys CertFreeCertificateContext(ctx *CertContext) (err error) = crypt32.CertFreeCertificateContext +//sys CertVerifyCertificateChainPolicy(policyOID uintptr, chain *CertChainContext, para *CertChainPolicyPara, status *CertChainPolicyStatus) (err error) = crypt32.CertVerifyCertificateChainPolicy +//sys RegOpenKeyEx(key Handle, subkey *uint16, options uint32, desiredAccess uint32, result *Handle) (regerrno error) = advapi32.RegOpenKeyExW +//sys RegCloseKey(key Handle) (regerrno error) = advapi32.RegCloseKey +//sys RegQueryInfoKey(key Handle, class *uint16, classLen *uint32, reserved *uint32, subkeysLen *uint32, maxSubkeyLen *uint32, maxClassLen *uint32, valuesLen *uint32, maxValueNameLen *uint32, maxValueLen *uint32, saLen *uint32, lastWriteTime *Filetime) (regerrno error) = advapi32.RegQueryInfoKeyW +//sys RegEnumKeyEx(key Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, classLen *uint32, lastWriteTime *Filetime) (regerrno error) = advapi32.RegEnumKeyExW +//sys RegQueryValueEx(key Handle, name *uint16, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegQueryValueExW +//sys getCurrentProcessId() (pid uint32) = kernel32.GetCurrentProcessId +//sys GetConsoleMode(console Handle, mode *uint32) (err error) = kernel32.GetConsoleMode +//sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode +//sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo +//sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW +//sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW +//sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot +//sys Process32First(snapshot Handle, procEntry *ProcessEntry32) (err error) = kernel32.Process32FirstW +//sys Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error) = kernel32.Process32NextW +//sys DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBufferSize uint32, outBuffer *byte, outBufferSize uint32, bytesReturned *uint32, overlapped *Overlapped) (err error) +// This function returns 1 byte BOOLEAN rather than the 4 byte BOOL. +//sys CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16, flags uint32) (err error) [failretval&0xff==0] = CreateSymbolicLinkW +//sys CreateHardLink(filename *uint16, existingfilename *uint16, reserved uintptr) (err error) [failretval&0xff==0] = CreateHardLinkW +//sys GetCurrentThreadId() (id uint32) +//sys CreateEvent(eventAttrs *SecurityAttributes, manualReset uint32, initialState uint32, name *uint16) (handle Handle, err error) = kernel32.CreateEventW +//sys CreateEventEx(eventAttrs *SecurityAttributes, name *uint16, flags uint32, desiredAccess uint32) (handle Handle, err error) = kernel32.CreateEventExW +//sys OpenEvent(desiredAccess uint32, inheritHandle bool, name *uint16) (handle Handle, err error) = kernel32.OpenEventW +//sys SetEvent(event Handle) (err error) = kernel32.SetEvent +//sys ResetEvent(event Handle) (err error) = kernel32.ResetEvent +//sys PulseEvent(event Handle) (err error) = kernel32.PulseEvent + +// Volume Management Functions +//sys DefineDosDevice(flags uint32, deviceName *uint16, targetPath *uint16) (err error) = DefineDosDeviceW +//sys DeleteVolumeMountPoint(volumeMountPoint *uint16) (err error) = DeleteVolumeMountPointW +//sys FindFirstVolume(volumeName *uint16, bufferLength uint32) (handle Handle, err error) [failretval==InvalidHandle] = FindFirstVolumeW +//sys FindFirstVolumeMountPoint(rootPathName *uint16, volumeMountPoint *uint16, bufferLength uint32) (handle Handle, err error) [failretval==InvalidHandle] = FindFirstVolumeMountPointW +//sys FindNextVolume(findVolume Handle, volumeName *uint16, bufferLength uint32) (err error) = FindNextVolumeW +//sys FindNextVolumeMountPoint(findVolumeMountPoint Handle, volumeMountPoint *uint16, bufferLength uint32) (err error) = FindNextVolumeMountPointW +//sys FindVolumeClose(findVolume Handle) (err error) +//sys FindVolumeMountPointClose(findVolumeMountPoint Handle) (err error) +//sys GetDriveType(rootPathName *uint16) (driveType uint32) = GetDriveTypeW +//sys GetLogicalDrives() (drivesBitMask uint32, err error) [failretval==0] +//sys GetLogicalDriveStrings(bufferLength uint32, buffer *uint16) (n uint32, err error) [failretval==0] = GetLogicalDriveStringsW +//sys GetVolumeInformation(rootPathName *uint16, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) = GetVolumeInformationW +//sys GetVolumeInformationByHandle(file Handle, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) = GetVolumeInformationByHandleW +//sys GetVolumeNameForVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16, bufferlength uint32) (err error) = GetVolumeNameForVolumeMountPointW +//sys GetVolumePathName(fileName *uint16, volumePathName *uint16, bufferLength uint32) (err error) = GetVolumePathNameW +//sys GetVolumePathNamesForVolumeName(volumeName *uint16, volumePathNames *uint16, bufferLength uint32, returnLength *uint32) (err error) = GetVolumePathNamesForVolumeNameW +//sys QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) [failretval==0] = QueryDosDeviceW +//sys SetVolumeLabel(rootPathName *uint16, volumeName *uint16) (err error) = SetVolumeLabelW +//sys SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err error) = SetVolumeMountPointW + +// syscall interface implementation for other packages + +// GetProcAddressByOrdinal retrieves the address of the exported +// function from module by ordinal. +func GetProcAddressByOrdinal(module Handle, ordinal uintptr) (proc uintptr, err error) { + r0, _, e1 := syscall.Syscall(procGetProcAddress.Addr(), 2, uintptr(module), ordinal, 0) + proc = uintptr(r0) + if proc == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func Exit(code int) { ExitProcess(uint32(code)) } + +func makeInheritSa() *SecurityAttributes { + var sa SecurityAttributes + sa.Length = uint32(unsafe.Sizeof(sa)) + sa.InheritHandle = 1 + return &sa +} + +func Open(path string, mode int, perm uint32) (fd Handle, err error) { + if len(path) == 0 { + return InvalidHandle, ERROR_FILE_NOT_FOUND + } + pathp, err := UTF16PtrFromString(path) + if err != nil { + return InvalidHandle, err + } + var access uint32 + switch mode & (O_RDONLY | O_WRONLY | O_RDWR) { + case O_RDONLY: + access = GENERIC_READ + case O_WRONLY: + access = GENERIC_WRITE + case O_RDWR: + access = GENERIC_READ | GENERIC_WRITE + } + if mode&O_CREAT != 0 { + access |= GENERIC_WRITE + } + if mode&O_APPEND != 0 { + access &^= GENERIC_WRITE + access |= FILE_APPEND_DATA + } + sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE) + var sa *SecurityAttributes + if mode&O_CLOEXEC == 0 { + sa = makeInheritSa() + } + var createmode uint32 + switch { + case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL): + createmode = CREATE_NEW + case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC): + createmode = CREATE_ALWAYS + case mode&O_CREAT == O_CREAT: + createmode = OPEN_ALWAYS + case mode&O_TRUNC == O_TRUNC: + createmode = TRUNCATE_EXISTING + default: + createmode = OPEN_EXISTING + } + h, e := CreateFile(pathp, access, sharemode, sa, createmode, FILE_ATTRIBUTE_NORMAL, 0) + return h, e +} + +func Read(fd Handle, p []byte) (n int, err error) { + var done uint32 + e := ReadFile(fd, p, &done, nil) + if e != nil { + if e == ERROR_BROKEN_PIPE { + // NOTE(brainman): work around ERROR_BROKEN_PIPE is returned on reading EOF from stdin + return 0, nil + } + return 0, e + } + if raceenabled { + if done > 0 { + raceWriteRange(unsafe.Pointer(&p[0]), int(done)) + } + raceAcquire(unsafe.Pointer(&ioSync)) + } + return int(done), nil +} + +func Write(fd Handle, p []byte) (n int, err error) { + if raceenabled { + raceReleaseMerge(unsafe.Pointer(&ioSync)) + } + var done uint32 + e := WriteFile(fd, p, &done, nil) + if e != nil { + return 0, e + } + if raceenabled && done > 0 { + raceReadRange(unsafe.Pointer(&p[0]), int(done)) + } + return int(done), nil +} + +var ioSync int64 + +func Seek(fd Handle, offset int64, whence int) (newoffset int64, err error) { + var w uint32 + switch whence { + case 0: + w = FILE_BEGIN + case 1: + w = FILE_CURRENT + case 2: + w = FILE_END + } + hi := int32(offset >> 32) + lo := int32(offset) + // use GetFileType to check pipe, pipe can't do seek + ft, _ := GetFileType(fd) + if ft == FILE_TYPE_PIPE { + return 0, syscall.EPIPE + } + rlo, e := SetFilePointer(fd, lo, &hi, w) + if e != nil { + return 0, e + } + return int64(hi)<<32 + int64(rlo), nil +} + +func Close(fd Handle) (err error) { + return CloseHandle(fd) +} + +var ( + Stdin = getStdHandle(STD_INPUT_HANDLE) + Stdout = getStdHandle(STD_OUTPUT_HANDLE) + Stderr = getStdHandle(STD_ERROR_HANDLE) +) + +func getStdHandle(stdhandle uint32) (fd Handle) { + r, _ := GetStdHandle(stdhandle) + CloseOnExec(r) + return r +} + +const ImplementsGetwd = true + +func Getwd() (wd string, err error) { + b := make([]uint16, 300) + n, e := GetCurrentDirectory(uint32(len(b)), &b[0]) + if e != nil { + return "", e + } + return string(utf16.Decode(b[0:n])), nil +} + +func Chdir(path string) (err error) { + pathp, err := UTF16PtrFromString(path) + if err != nil { + return err + } + return SetCurrentDirectory(pathp) +} + +func Mkdir(path string, mode uint32) (err error) { + pathp, err := UTF16PtrFromString(path) + if err != nil { + return err + } + return CreateDirectory(pathp, nil) +} + +func Rmdir(path string) (err error) { + pathp, err := UTF16PtrFromString(path) + if err != nil { + return err + } + return RemoveDirectory(pathp) +} + +func Unlink(path string) (err error) { + pathp, err := UTF16PtrFromString(path) + if err != nil { + return err + } + return DeleteFile(pathp) +} + +func Rename(oldpath, newpath string) (err error) { + from, err := UTF16PtrFromString(oldpath) + if err != nil { + return err + } + to, err := UTF16PtrFromString(newpath) + if err != nil { + return err + } + return MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING) +} + +func ComputerName() (name string, err error) { + var n uint32 = MAX_COMPUTERNAME_LENGTH + 1 + b := make([]uint16, n) + e := GetComputerName(&b[0], &n) + if e != nil { + return "", e + } + return string(utf16.Decode(b[0:n])), nil +} + +func Ftruncate(fd Handle, length int64) (err error) { + curoffset, e := Seek(fd, 0, 1) + if e != nil { + return e + } + defer Seek(fd, curoffset, 0) + _, e = Seek(fd, length, 0) + if e != nil { + return e + } + e = SetEndOfFile(fd) + if e != nil { + return e + } + return nil +} + +func Gettimeofday(tv *Timeval) (err error) { + var ft Filetime + GetSystemTimeAsFileTime(&ft) + *tv = NsecToTimeval(ft.Nanoseconds()) + return nil +} + +func Pipe(p []Handle) (err error) { + if len(p) != 2 { + return syscall.EINVAL + } + var r, w Handle + e := CreatePipe(&r, &w, makeInheritSa(), 0) + if e != nil { + return e + } + p[0] = r + p[1] = w + return nil +} + +func Utimes(path string, tv []Timeval) (err error) { + if len(tv) != 2 { + return syscall.EINVAL + } + pathp, e := UTF16PtrFromString(path) + if e != nil { + return e + } + h, e := CreateFile(pathp, + FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE, nil, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0) + if e != nil { + return e + } + defer Close(h) + a := NsecToFiletime(tv[0].Nanoseconds()) + w := NsecToFiletime(tv[1].Nanoseconds()) + return SetFileTime(h, nil, &a, &w) +} + +func UtimesNano(path string, ts []Timespec) (err error) { + if len(ts) != 2 { + return syscall.EINVAL + } + pathp, e := UTF16PtrFromString(path) + if e != nil { + return e + } + h, e := CreateFile(pathp, + FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE, nil, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0) + if e != nil { + return e + } + defer Close(h) + a := NsecToFiletime(TimespecToNsec(ts[0])) + w := NsecToFiletime(TimespecToNsec(ts[1])) + return SetFileTime(h, nil, &a, &w) +} + +func Fsync(fd Handle) (err error) { + return FlushFileBuffers(fd) +} + +func Chmod(path string, mode uint32) (err error) { + if mode == 0 { + return syscall.EINVAL + } + p, e := UTF16PtrFromString(path) + if e != nil { + return e + } + attrs, e := GetFileAttributes(p) + if e != nil { + return e + } + if mode&S_IWRITE != 0 { + attrs &^= FILE_ATTRIBUTE_READONLY + } else { + attrs |= FILE_ATTRIBUTE_READONLY + } + return SetFileAttributes(p, attrs) +} + +func LoadGetSystemTimePreciseAsFileTime() error { + return procGetSystemTimePreciseAsFileTime.Find() +} + +func LoadCancelIoEx() error { + return procCancelIoEx.Find() +} + +func LoadSetFileCompletionNotificationModes() error { + return procSetFileCompletionNotificationModes.Find() +} + +// net api calls + +const socket_error = uintptr(^uint32(0)) + +//sys WSAStartup(verreq uint32, data *WSAData) (sockerr error) = ws2_32.WSAStartup +//sys WSACleanup() (err error) [failretval==socket_error] = ws2_32.WSACleanup +//sys WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbob uint32, cbbr *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) [failretval==socket_error] = ws2_32.WSAIoctl +//sys socket(af int32, typ int32, protocol int32) (handle Handle, err error) [failretval==InvalidHandle] = ws2_32.socket +//sys Setsockopt(s Handle, level int32, optname int32, optval *byte, optlen int32) (err error) [failretval==socket_error] = ws2_32.setsockopt +//sys Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) [failretval==socket_error] = ws2_32.getsockopt +//sys bind(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.bind +//sys connect(s Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socket_error] = ws2_32.connect +//sys getsockname(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==socket_error] = ws2_32.getsockname +//sys getpeername(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==socket_error] = ws2_32.getpeername +//sys listen(s Handle, backlog int32) (err error) [failretval==socket_error] = ws2_32.listen +//sys shutdown(s Handle, how int32) (err error) [failretval==socket_error] = ws2_32.shutdown +//sys Closesocket(s Handle) (err error) [failretval==socket_error] = ws2_32.closesocket +//sys AcceptEx(ls Handle, as Handle, buf *byte, rxdatalen uint32, laddrlen uint32, raddrlen uint32, recvd *uint32, overlapped *Overlapped) (err error) = mswsock.AcceptEx +//sys GetAcceptExSockaddrs(buf *byte, rxdatalen uint32, laddrlen uint32, raddrlen uint32, lrsa **RawSockaddrAny, lrsalen *int32, rrsa **RawSockaddrAny, rrsalen *int32) = mswsock.GetAcceptExSockaddrs +//sys WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecv +//sys WSASend(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSASend +//sys WSARecvFrom(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, from *RawSockaddrAny, fromlen *int32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecvFrom +//sys WSASendTo(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, to *RawSockaddrAny, tolen int32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSASendTo +//sys GetHostByName(name string) (h *Hostent, err error) [failretval==nil] = ws2_32.gethostbyname +//sys GetServByName(name string, proto string) (s *Servent, err error) [failretval==nil] = ws2_32.getservbyname +//sys Ntohs(netshort uint16) (u uint16) = ws2_32.ntohs +//sys GetProtoByName(name string) (p *Protoent, err error) [failretval==nil] = ws2_32.getprotobyname +//sys DnsQuery(name string, qtype uint16, options uint32, extra *byte, qrs **DNSRecord, pr *byte) (status error) = dnsapi.DnsQuery_W +//sys DnsRecordListFree(rl *DNSRecord, freetype uint32) = dnsapi.DnsRecordListFree +//sys DnsNameCompare(name1 *uint16, name2 *uint16) (same bool) = dnsapi.DnsNameCompare_W +//sys GetAddrInfoW(nodename *uint16, servicename *uint16, hints *AddrinfoW, result **AddrinfoW) (sockerr error) = ws2_32.GetAddrInfoW +//sys FreeAddrInfoW(addrinfo *AddrinfoW) = ws2_32.FreeAddrInfoW +//sys GetIfEntry(pIfRow *MibIfRow) (errcode error) = iphlpapi.GetIfEntry +//sys GetAdaptersInfo(ai *IpAdapterInfo, ol *uint32) (errcode error) = iphlpapi.GetAdaptersInfo +//sys SetFileCompletionNotificationModes(handle Handle, flags uint8) (err error) = kernel32.SetFileCompletionNotificationModes +//sys WSAEnumProtocols(protocols *int32, protocolBuffer *WSAProtocolInfo, bufferLength *uint32) (n int32, err error) [failretval==-1] = ws2_32.WSAEnumProtocolsW +//sys GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) = iphlpapi.GetAdaptersAddresses +//sys GetACP() (acp uint32) = kernel32.GetACP +//sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar + +// For testing: clients can set this flag to force +// creation of IPv6 sockets to return EAFNOSUPPORT. +var SocketDisableIPv6 bool + +type RawSockaddrInet4 struct { + Family uint16 + Port uint16 + Addr [4]byte /* in_addr */ + Zero [8]uint8 +} + +type RawSockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type RawSockaddr struct { + Family uint16 + Data [14]int8 +} + +type RawSockaddrAny struct { + Addr RawSockaddr + Pad [96]int8 +} + +type Sockaddr interface { + sockaddr() (ptr unsafe.Pointer, len int32, err error) // lowercase; only we can define Sockaddrs +} + +type SockaddrInet4 struct { + Port int + Addr [4]byte + raw RawSockaddrInet4 +} + +func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, int32, error) { + if sa.Port < 0 || sa.Port > 0xFFFF { + return nil, 0, syscall.EINVAL + } + sa.raw.Family = AF_INET + p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) + p[0] = byte(sa.Port >> 8) + p[1] = byte(sa.Port) + for i := 0; i < len(sa.Addr); i++ { + sa.raw.Addr[i] = sa.Addr[i] + } + return unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil +} + +type SockaddrInet6 struct { + Port int + ZoneId uint32 + Addr [16]byte + raw RawSockaddrInet6 +} + +func (sa *SockaddrInet6) sockaddr() (unsafe.Pointer, int32, error) { + if sa.Port < 0 || sa.Port > 0xFFFF { + return nil, 0, syscall.EINVAL + } + sa.raw.Family = AF_INET6 + p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port)) + p[0] = byte(sa.Port >> 8) + p[1] = byte(sa.Port) + sa.raw.Scope_id = sa.ZoneId + for i := 0; i < len(sa.Addr); i++ { + sa.raw.Addr[i] = sa.Addr[i] + } + return unsafe.Pointer(&sa.raw), int32(unsafe.Sizeof(sa.raw)), nil +} + +type SockaddrUnix struct { + Name string +} + +func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, int32, error) { + // TODO(brainman): implement SockaddrUnix.sockaddr() + return nil, 0, syscall.EWINDOWS +} + +func (rsa *RawSockaddrAny) Sockaddr() (Sockaddr, error) { + switch rsa.Addr.Family { + case AF_UNIX: + return nil, syscall.EWINDOWS + + case AF_INET: + pp := (*RawSockaddrInet4)(unsafe.Pointer(rsa)) + sa := new(SockaddrInet4) + p := (*[2]byte)(unsafe.Pointer(&pp.Port)) + sa.Port = int(p[0])<<8 + int(p[1]) + for i := 0; i < len(sa.Addr); i++ { + sa.Addr[i] = pp.Addr[i] + } + return sa, nil + + case AF_INET6: + pp := (*RawSockaddrInet6)(unsafe.Pointer(rsa)) + sa := new(SockaddrInet6) + p := (*[2]byte)(unsafe.Pointer(&pp.Port)) + sa.Port = int(p[0])<<8 + int(p[1]) + sa.ZoneId = pp.Scope_id + for i := 0; i < len(sa.Addr); i++ { + sa.Addr[i] = pp.Addr[i] + } + return sa, nil + } + return nil, syscall.EAFNOSUPPORT +} + +func Socket(domain, typ, proto int) (fd Handle, err error) { + if domain == AF_INET6 && SocketDisableIPv6 { + return InvalidHandle, syscall.EAFNOSUPPORT + } + return socket(int32(domain), int32(typ), int32(proto)) +} + +func SetsockoptInt(fd Handle, level, opt int, value int) (err error) { + v := int32(value) + return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&v)), int32(unsafe.Sizeof(v))) +} + +func Bind(fd Handle, sa Sockaddr) (err error) { + ptr, n, err := sa.sockaddr() + if err != nil { + return err + } + return bind(fd, ptr, n) +} + +func Connect(fd Handle, sa Sockaddr) (err error) { + ptr, n, err := sa.sockaddr() + if err != nil { + return err + } + return connect(fd, ptr, n) +} + +func Getsockname(fd Handle) (sa Sockaddr, err error) { + var rsa RawSockaddrAny + l := int32(unsafe.Sizeof(rsa)) + if err = getsockname(fd, &rsa, &l); err != nil { + return + } + return rsa.Sockaddr() +} + +func Getpeername(fd Handle) (sa Sockaddr, err error) { + var rsa RawSockaddrAny + l := int32(unsafe.Sizeof(rsa)) + if err = getpeername(fd, &rsa, &l); err != nil { + return + } + return rsa.Sockaddr() +} + +func Listen(s Handle, n int) (err error) { + return listen(s, int32(n)) +} + +func Shutdown(fd Handle, how int) (err error) { + return shutdown(fd, int32(how)) +} + +func WSASendto(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, to Sockaddr, overlapped *Overlapped, croutine *byte) (err error) { + rsa, l, err := to.sockaddr() + if err != nil { + return err + } + return WSASendTo(s, bufs, bufcnt, sent, flags, (*RawSockaddrAny)(unsafe.Pointer(rsa)), l, overlapped, croutine) +} + +func LoadGetAddrInfo() error { + return procGetAddrInfoW.Find() +} + +var connectExFunc struct { + once sync.Once + addr uintptr + err error +} + +func LoadConnectEx() error { + connectExFunc.once.Do(func() { + var s Handle + s, connectExFunc.err = Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) + if connectExFunc.err != nil { + return + } + defer CloseHandle(s) + var n uint32 + connectExFunc.err = WSAIoctl(s, + SIO_GET_EXTENSION_FUNCTION_POINTER, + (*byte)(unsafe.Pointer(&WSAID_CONNECTEX)), + uint32(unsafe.Sizeof(WSAID_CONNECTEX)), + (*byte)(unsafe.Pointer(&connectExFunc.addr)), + uint32(unsafe.Sizeof(connectExFunc.addr)), + &n, nil, 0) + }) + return connectExFunc.err +} + +func connectEx(s Handle, name unsafe.Pointer, namelen int32, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) (err error) { + r1, _, e1 := syscall.Syscall9(connectExFunc.addr, 7, uintptr(s), uintptr(name), uintptr(namelen), uintptr(unsafe.Pointer(sendBuf)), uintptr(sendDataLen), uintptr(unsafe.Pointer(bytesSent)), uintptr(unsafe.Pointer(overlapped)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = error(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ConnectEx(fd Handle, sa Sockaddr, sendBuf *byte, sendDataLen uint32, bytesSent *uint32, overlapped *Overlapped) error { + err := LoadConnectEx() + if err != nil { + return errorspkg.New("failed to find ConnectEx: " + err.Error()) + } + ptr, n, err := sa.sockaddr() + if err != nil { + return err + } + return connectEx(fd, ptr, n, sendBuf, sendDataLen, bytesSent, overlapped) +} + +var sendRecvMsgFunc struct { + once sync.Once + sendAddr uintptr + recvAddr uintptr + err error +} + +func loadWSASendRecvMsg() error { + sendRecvMsgFunc.once.Do(func() { + var s Handle + s, sendRecvMsgFunc.err = Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) + if sendRecvMsgFunc.err != nil { + return + } + defer CloseHandle(s) + var n uint32 + sendRecvMsgFunc.err = WSAIoctl(s, + SIO_GET_EXTENSION_FUNCTION_POINTER, + (*byte)(unsafe.Pointer(&WSAID_WSARECVMSG)), + uint32(unsafe.Sizeof(WSAID_WSARECVMSG)), + (*byte)(unsafe.Pointer(&sendRecvMsgFunc.recvAddr)), + uint32(unsafe.Sizeof(sendRecvMsgFunc.recvAddr)), + &n, nil, 0) + if sendRecvMsgFunc.err != nil { + return + } + sendRecvMsgFunc.err = WSAIoctl(s, + SIO_GET_EXTENSION_FUNCTION_POINTER, + (*byte)(unsafe.Pointer(&WSAID_WSASENDMSG)), + uint32(unsafe.Sizeof(WSAID_WSASENDMSG)), + (*byte)(unsafe.Pointer(&sendRecvMsgFunc.sendAddr)), + uint32(unsafe.Sizeof(sendRecvMsgFunc.sendAddr)), + &n, nil, 0) + }) + return sendRecvMsgFunc.err +} + +func WSASendMsg(fd Handle, msg *WSAMsg, flags uint32, bytesSent *uint32, overlapped *Overlapped, croutine *byte) error { + err := loadWSASendRecvMsg() + if err != nil { + return err + } + r1, _, e1 := syscall.Syscall6(sendRecvMsgFunc.sendAddr, 6, uintptr(fd), uintptr(unsafe.Pointer(msg)), uintptr(flags), uintptr(unsafe.Pointer(bytesSent)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine))) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return err +} + +func WSARecvMsg(fd Handle, msg *WSAMsg, bytesReceived *uint32, overlapped *Overlapped, croutine *byte) error { + err := loadWSASendRecvMsg() + if err != nil { + return err + } + r1, _, e1 := syscall.Syscall6(sendRecvMsgFunc.recvAddr, 5, uintptr(fd), uintptr(unsafe.Pointer(msg)), uintptr(unsafe.Pointer(bytesReceived)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return err +} + +// Invented structures to support what package os expects. +type Rusage struct { + CreationTime Filetime + ExitTime Filetime + KernelTime Filetime + UserTime Filetime +} + +type WaitStatus struct { + ExitCode uint32 +} + +func (w WaitStatus) Exited() bool { return true } + +func (w WaitStatus) ExitStatus() int { return int(w.ExitCode) } + +func (w WaitStatus) Signal() Signal { return -1 } + +func (w WaitStatus) CoreDump() bool { return false } + +func (w WaitStatus) Stopped() bool { return false } + +func (w WaitStatus) Continued() bool { return false } + +func (w WaitStatus) StopSignal() Signal { return -1 } + +func (w WaitStatus) Signaled() bool { return false } + +func (w WaitStatus) TrapCause() int { return -1 } + +// Timespec is an invented structure on Windows, but here for +// consistency with the corresponding package for other operating systems. +type Timespec struct { + Sec int64 + Nsec int64 +} + +func TimespecToNsec(ts Timespec) int64 { return int64(ts.Sec)*1e9 + int64(ts.Nsec) } + +func NsecToTimespec(nsec int64) (ts Timespec) { + ts.Sec = nsec / 1e9 + ts.Nsec = nsec % 1e9 + return +} + +// TODO(brainman): fix all needed for net + +func Accept(fd Handle) (nfd Handle, sa Sockaddr, err error) { return 0, nil, syscall.EWINDOWS } +func Recvfrom(fd Handle, p []byte, flags int) (n int, from Sockaddr, err error) { + return 0, nil, syscall.EWINDOWS +} +func Sendto(fd Handle, p []byte, flags int, to Sockaddr) (err error) { return syscall.EWINDOWS } +func SetsockoptTimeval(fd Handle, level, opt int, tv *Timeval) (err error) { return syscall.EWINDOWS } + +// The Linger struct is wrong but we only noticed after Go 1. +// sysLinger is the real system call structure. + +// BUG(brainman): The definition of Linger is not appropriate for direct use +// with Setsockopt and Getsockopt. +// Use SetsockoptLinger instead. + +type Linger struct { + Onoff int32 + Linger int32 +} + +type sysLinger struct { + Onoff uint16 + Linger uint16 +} + +type IPMreq struct { + Multiaddr [4]byte /* in_addr */ + Interface [4]byte /* in_addr */ +} + +type IPv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +func GetsockoptInt(fd Handle, level, opt int) (int, error) { return -1, syscall.EWINDOWS } + +func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) { + sys := sysLinger{Onoff: uint16(l.Onoff), Linger: uint16(l.Linger)} + return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&sys)), int32(unsafe.Sizeof(sys))) +} + +func SetsockoptInet4Addr(fd Handle, level, opt int, value [4]byte) (err error) { + return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&value[0])), 4) +} +func SetsockoptIPMreq(fd Handle, level, opt int, mreq *IPMreq) (err error) { + return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(mreq)), int32(unsafe.Sizeof(*mreq))) +} +func SetsockoptIPv6Mreq(fd Handle, level, opt int, mreq *IPv6Mreq) (err error) { + return syscall.EWINDOWS +} + +func Getpid() (pid int) { return int(getCurrentProcessId()) } + +func FindFirstFile(name *uint16, data *Win32finddata) (handle Handle, err error) { + // NOTE(rsc): The Win32finddata struct is wrong for the system call: + // the two paths are each one uint16 short. Use the correct struct, + // a win32finddata1, and then copy the results out. + // There is no loss of expressivity here, because the final + // uint16, if it is used, is supposed to be a NUL, and Go doesn't need that. + // For Go 1.1, we might avoid the allocation of win32finddata1 here + // by adding a final Bug [2]uint16 field to the struct and then + // adjusting the fields in the result directly. + var data1 win32finddata1 + handle, err = findFirstFile1(name, &data1) + if err == nil { + copyFindData(data, &data1) + } + return +} + +func FindNextFile(handle Handle, data *Win32finddata) (err error) { + var data1 win32finddata1 + err = findNextFile1(handle, &data1) + if err == nil { + copyFindData(data, &data1) + } + return +} + +func getProcessEntry(pid int) (*ProcessEntry32, error) { + snapshot, err := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) + if err != nil { + return nil, err + } + defer CloseHandle(snapshot) + var procEntry ProcessEntry32 + procEntry.Size = uint32(unsafe.Sizeof(procEntry)) + if err = Process32First(snapshot, &procEntry); err != nil { + return nil, err + } + for { + if procEntry.ProcessID == uint32(pid) { + return &procEntry, nil + } + err = Process32Next(snapshot, &procEntry) + if err != nil { + return nil, err + } + } +} + +func Getppid() (ppid int) { + pe, err := getProcessEntry(Getpid()) + if err != nil { + return -1 + } + return int(pe.ParentProcessID) +} + +// TODO(brainman): fix all needed for os +func Fchdir(fd Handle) (err error) { return syscall.EWINDOWS } +func Link(oldpath, newpath string) (err error) { return syscall.EWINDOWS } +func Symlink(path, link string) (err error) { return syscall.EWINDOWS } + +func Fchmod(fd Handle, mode uint32) (err error) { return syscall.EWINDOWS } +func Chown(path string, uid int, gid int) (err error) { return syscall.EWINDOWS } +func Lchown(path string, uid int, gid int) (err error) { return syscall.EWINDOWS } +func Fchown(fd Handle, uid int, gid int) (err error) { return syscall.EWINDOWS } + +func Getuid() (uid int) { return -1 } +func Geteuid() (euid int) { return -1 } +func Getgid() (gid int) { return -1 } +func Getegid() (egid int) { return -1 } +func Getgroups() (gids []int, err error) { return nil, syscall.EWINDOWS } + +type Signal int + +func (s Signal) Signal() {} + +func (s Signal) String() string { + if 0 <= s && int(s) < len(signals) { + str := signals[s] + if str != "" { + return str + } + } + return "signal " + itoa(int(s)) +} + +func LoadCreateSymbolicLink() error { + return procCreateSymbolicLinkW.Find() +} + +// Readlink returns the destination of the named symbolic link. +func Readlink(path string, buf []byte) (n int, err error) { + fd, err := CreateFile(StringToUTF16Ptr(path), GENERIC_READ, 0, nil, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0) + if err != nil { + return -1, err + } + defer CloseHandle(fd) + + rdbbuf := make([]byte, MAXIMUM_REPARSE_DATA_BUFFER_SIZE) + var bytesReturned uint32 + err = DeviceIoControl(fd, FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) + if err != nil { + return -1, err + } + + rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0])) + var s string + switch rdb.ReparseTag { + case IO_REPARSE_TAG_SYMLINK: + data := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer)) + p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0])) + s = UTF16ToString(p[data.PrintNameOffset/2 : (data.PrintNameLength-data.PrintNameOffset)/2]) + case IO_REPARSE_TAG_MOUNT_POINT: + data := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer)) + p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0])) + s = UTF16ToString(p[data.PrintNameOffset/2 : (data.PrintNameLength-data.PrintNameOffset)/2]) + default: + // the path is not a symlink or junction but another type of reparse + // point + return -1, syscall.ENOENT + } + n = copy(buf, []byte(s)) + + return n, nil +} diff --git a/vendor/golang.org/x/sys/windows/syscall_windows_test.go b/vendor/golang.org/x/sys/windows/syscall_windows_test.go new file mode 100644 index 0000000..9c7133c --- /dev/null +++ b/vendor/golang.org/x/sys/windows/syscall_windows_test.go @@ -0,0 +1,107 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "syscall" + "testing" + "unsafe" + + "golang.org/x/sys/windows" +) + +func TestWin32finddata(t *testing.T) { + dir, err := ioutil.TempDir("", "go-build") + if err != nil { + t.Fatalf("failed to create temp directory: %v", err) + } + defer os.RemoveAll(dir) + + path := filepath.Join(dir, "long_name.and_extension") + f, err := os.Create(path) + if err != nil { + t.Fatalf("failed to create %v: %v", path, err) + } + f.Close() + + type X struct { + fd windows.Win32finddata + got byte + pad [10]byte // to protect ourselves + + } + var want byte = 2 // it is unlikely to have this character in the filename + x := X{got: want} + + pathp, _ := windows.UTF16PtrFromString(path) + h, err := windows.FindFirstFile(pathp, &(x.fd)) + if err != nil { + t.Fatalf("FindFirstFile failed: %v", err) + } + err = windows.FindClose(h) + if err != nil { + t.Fatalf("FindClose failed: %v", err) + } + + if x.got != want { + t.Fatalf("memory corruption: want=%d got=%d", want, x.got) + } +} + +func TestFormatMessage(t *testing.T) { + dll := windows.MustLoadDLL("pdh.dll") + + pdhOpenQuery := func(datasrc *uint16, userdata uint32, query *windows.Handle) (errno uintptr) { + r0, _, _ := syscall.Syscall(dll.MustFindProc("PdhOpenQueryW").Addr(), 3, uintptr(unsafe.Pointer(datasrc)), uintptr(userdata), uintptr(unsafe.Pointer(query))) + return r0 + } + + pdhCloseQuery := func(query windows.Handle) (errno uintptr) { + r0, _, _ := syscall.Syscall(dll.MustFindProc("PdhCloseQuery").Addr(), 1, uintptr(query), 0, 0) + return r0 + } + + var q windows.Handle + name, err := windows.UTF16PtrFromString("no_such_source") + if err != nil { + t.Fatal(err) + } + errno := pdhOpenQuery(name, 0, &q) + if errno == 0 { + pdhCloseQuery(q) + t.Fatal("PdhOpenQuery succeeded, but expected to fail.") + } + + const flags uint32 = syscall.FORMAT_MESSAGE_FROM_HMODULE | syscall.FORMAT_MESSAGE_ARGUMENT_ARRAY | syscall.FORMAT_MESSAGE_IGNORE_INSERTS + buf := make([]uint16, 300) + _, err = windows.FormatMessage(flags, uintptr(dll.Handle), uint32(errno), 0, buf, nil) + if err != nil { + t.Fatalf("FormatMessage for handle=%x and errno=%x failed: %v", dll.Handle, errno, err) + } +} + +func abort(funcname string, err error) { + panic(funcname + " failed: " + err.Error()) +} + +func ExampleLoadLibrary() { + h, err := windows.LoadLibrary("kernel32.dll") + if err != nil { + abort("LoadLibrary", err) + } + defer windows.FreeLibrary(h) + proc, err := windows.GetProcAddress(h, "GetVersion") + if err != nil { + abort("GetProcAddress", err) + } + r, _, _ := syscall.Syscall(uintptr(proc), 0, 0, 0, 0) + major := byte(r) + minor := uint8(r >> 8) + build := uint16(r >> 16) + print("windows version ", major, ".", minor, " (Build ", build, ")\n") +} diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go new file mode 100644 index 0000000..52c2037 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -0,0 +1,1333 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows + +import "syscall" + +const ( + // Windows errors. + ERROR_FILE_NOT_FOUND syscall.Errno = 2 + ERROR_PATH_NOT_FOUND syscall.Errno = 3 + ERROR_ACCESS_DENIED syscall.Errno = 5 + ERROR_NO_MORE_FILES syscall.Errno = 18 + ERROR_HANDLE_EOF syscall.Errno = 38 + ERROR_NETNAME_DELETED syscall.Errno = 64 + ERROR_FILE_EXISTS syscall.Errno = 80 + ERROR_BROKEN_PIPE syscall.Errno = 109 + ERROR_BUFFER_OVERFLOW syscall.Errno = 111 + ERROR_INSUFFICIENT_BUFFER syscall.Errno = 122 + ERROR_MOD_NOT_FOUND syscall.Errno = 126 + ERROR_PROC_NOT_FOUND syscall.Errno = 127 + ERROR_ALREADY_EXISTS syscall.Errno = 183 + ERROR_ENVVAR_NOT_FOUND syscall.Errno = 203 + ERROR_MORE_DATA syscall.Errno = 234 + ERROR_OPERATION_ABORTED syscall.Errno = 995 + ERROR_IO_PENDING syscall.Errno = 997 + ERROR_SERVICE_SPECIFIC_ERROR syscall.Errno = 1066 + ERROR_NOT_FOUND syscall.Errno = 1168 + ERROR_PRIVILEGE_NOT_HELD syscall.Errno = 1314 + WSAEACCES syscall.Errno = 10013 + WSAEMSGSIZE syscall.Errno = 10040 + WSAECONNRESET syscall.Errno = 10054 +) + +const ( + // Invented values to support what package os expects. + O_RDONLY = 0x00000 + O_WRONLY = 0x00001 + O_RDWR = 0x00002 + O_CREAT = 0x00040 + O_EXCL = 0x00080 + O_NOCTTY = 0x00100 + O_TRUNC = 0x00200 + O_NONBLOCK = 0x00800 + O_APPEND = 0x00400 + O_SYNC = 0x01000 + O_ASYNC = 0x02000 + O_CLOEXEC = 0x80000 +) + +const ( + // More invented values for signals + SIGHUP = Signal(0x1) + SIGINT = Signal(0x2) + SIGQUIT = Signal(0x3) + SIGILL = Signal(0x4) + SIGTRAP = Signal(0x5) + SIGABRT = Signal(0x6) + SIGBUS = Signal(0x7) + SIGFPE = Signal(0x8) + SIGKILL = Signal(0x9) + SIGSEGV = Signal(0xb) + SIGPIPE = Signal(0xd) + SIGALRM = Signal(0xe) + SIGTERM = Signal(0xf) +) + +var signals = [...]string{ + 1: "hangup", + 2: "interrupt", + 3: "quit", + 4: "illegal instruction", + 5: "trace/breakpoint trap", + 6: "aborted", + 7: "bus error", + 8: "floating point exception", + 9: "killed", + 10: "user defined signal 1", + 11: "segmentation fault", + 12: "user defined signal 2", + 13: "broken pipe", + 14: "alarm clock", + 15: "terminated", +} + +const ( + GENERIC_READ = 0x80000000 + GENERIC_WRITE = 0x40000000 + GENERIC_EXECUTE = 0x20000000 + GENERIC_ALL = 0x10000000 + + FILE_LIST_DIRECTORY = 0x00000001 + FILE_APPEND_DATA = 0x00000004 + FILE_WRITE_ATTRIBUTES = 0x00000100 + + FILE_SHARE_READ = 0x00000001 + FILE_SHARE_WRITE = 0x00000002 + FILE_SHARE_DELETE = 0x00000004 + FILE_ATTRIBUTE_READONLY = 0x00000001 + FILE_ATTRIBUTE_HIDDEN = 0x00000002 + FILE_ATTRIBUTE_SYSTEM = 0x00000004 + FILE_ATTRIBUTE_DIRECTORY = 0x00000010 + FILE_ATTRIBUTE_ARCHIVE = 0x00000020 + FILE_ATTRIBUTE_NORMAL = 0x00000080 + FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400 + + INVALID_FILE_ATTRIBUTES = 0xffffffff + + CREATE_NEW = 1 + CREATE_ALWAYS = 2 + OPEN_EXISTING = 3 + OPEN_ALWAYS = 4 + TRUNCATE_EXISTING = 5 + + FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 + FILE_FLAG_OVERLAPPED = 0x40000000 + + HANDLE_FLAG_INHERIT = 0x00000001 + STARTF_USESTDHANDLES = 0x00000100 + STARTF_USESHOWWINDOW = 0x00000001 + DUPLICATE_CLOSE_SOURCE = 0x00000001 + DUPLICATE_SAME_ACCESS = 0x00000002 + + STD_INPUT_HANDLE = -10 & (1<<32 - 1) + STD_OUTPUT_HANDLE = -11 & (1<<32 - 1) + STD_ERROR_HANDLE = -12 & (1<<32 - 1) + + FILE_BEGIN = 0 + FILE_CURRENT = 1 + FILE_END = 2 + + LANG_ENGLISH = 0x09 + SUBLANG_ENGLISH_US = 0x01 + + FORMAT_MESSAGE_ALLOCATE_BUFFER = 256 + FORMAT_MESSAGE_IGNORE_INSERTS = 512 + FORMAT_MESSAGE_FROM_STRING = 1024 + FORMAT_MESSAGE_FROM_HMODULE = 2048 + FORMAT_MESSAGE_FROM_SYSTEM = 4096 + FORMAT_MESSAGE_ARGUMENT_ARRAY = 8192 + FORMAT_MESSAGE_MAX_WIDTH_MASK = 255 + + MAX_PATH = 260 + MAX_LONG_PATH = 32768 + + MAX_COMPUTERNAME_LENGTH = 15 + + TIME_ZONE_ID_UNKNOWN = 0 + TIME_ZONE_ID_STANDARD = 1 + + TIME_ZONE_ID_DAYLIGHT = 2 + IGNORE = 0 + INFINITE = 0xffffffff + + WAIT_TIMEOUT = 258 + WAIT_ABANDONED = 0x00000080 + WAIT_OBJECT_0 = 0x00000000 + WAIT_FAILED = 0xFFFFFFFF + + PROCESS_TERMINATE = 1 + PROCESS_QUERY_INFORMATION = 0x00000400 + SYNCHRONIZE = 0x00100000 + + FILE_MAP_COPY = 0x01 + FILE_MAP_WRITE = 0x02 + FILE_MAP_READ = 0x04 + FILE_MAP_EXECUTE = 0x20 + + CTRL_C_EVENT = 0 + CTRL_BREAK_EVENT = 1 + + // Windows reserves errors >= 1<<29 for application use. + APPLICATION_ERROR = 1 << 29 +) + +const ( + // Process creation flags. + CREATE_BREAKAWAY_FROM_JOB = 0x01000000 + CREATE_DEFAULT_ERROR_MODE = 0x04000000 + CREATE_NEW_CONSOLE = 0x00000010 + CREATE_NEW_PROCESS_GROUP = 0x00000200 + CREATE_NO_WINDOW = 0x08000000 + CREATE_PROTECTED_PROCESS = 0x00040000 + CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000 + CREATE_SEPARATE_WOW_VDM = 0x00000800 + CREATE_SHARED_WOW_VDM = 0x00001000 + CREATE_SUSPENDED = 0x00000004 + CREATE_UNICODE_ENVIRONMENT = 0x00000400 + DEBUG_ONLY_THIS_PROCESS = 0x00000002 + DEBUG_PROCESS = 0x00000001 + DETACHED_PROCESS = 0x00000008 + EXTENDED_STARTUPINFO_PRESENT = 0x00080000 + INHERIT_PARENT_AFFINITY = 0x00010000 +) + +const ( + // flags for CreateToolhelp32Snapshot + TH32CS_SNAPHEAPLIST = 0x01 + TH32CS_SNAPPROCESS = 0x02 + TH32CS_SNAPTHREAD = 0x04 + TH32CS_SNAPMODULE = 0x08 + TH32CS_SNAPMODULE32 = 0x10 + TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPMODULE | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD + TH32CS_INHERIT = 0x80000000 +) + +const ( + // filters for ReadDirectoryChangesW + FILE_NOTIFY_CHANGE_FILE_NAME = 0x001 + FILE_NOTIFY_CHANGE_DIR_NAME = 0x002 + FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x004 + FILE_NOTIFY_CHANGE_SIZE = 0x008 + FILE_NOTIFY_CHANGE_LAST_WRITE = 0x010 + FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x020 + FILE_NOTIFY_CHANGE_CREATION = 0x040 + FILE_NOTIFY_CHANGE_SECURITY = 0x100 +) + +const ( + // do not reorder + FILE_ACTION_ADDED = iota + 1 + FILE_ACTION_REMOVED + FILE_ACTION_MODIFIED + FILE_ACTION_RENAMED_OLD_NAME + FILE_ACTION_RENAMED_NEW_NAME +) + +const ( + // wincrypt.h + PROV_RSA_FULL = 1 + PROV_RSA_SIG = 2 + PROV_DSS = 3 + PROV_FORTEZZA = 4 + PROV_MS_EXCHANGE = 5 + PROV_SSL = 6 + PROV_RSA_SCHANNEL = 12 + PROV_DSS_DH = 13 + PROV_EC_ECDSA_SIG = 14 + PROV_EC_ECNRA_SIG = 15 + PROV_EC_ECDSA_FULL = 16 + PROV_EC_ECNRA_FULL = 17 + PROV_DH_SCHANNEL = 18 + PROV_SPYRUS_LYNKS = 20 + PROV_RNG = 21 + PROV_INTEL_SEC = 22 + PROV_REPLACE_OWF = 23 + PROV_RSA_AES = 24 + CRYPT_VERIFYCONTEXT = 0xF0000000 + CRYPT_NEWKEYSET = 0x00000008 + CRYPT_DELETEKEYSET = 0x00000010 + CRYPT_MACHINE_KEYSET = 0x00000020 + CRYPT_SILENT = 0x00000040 + CRYPT_DEFAULT_CONTAINER_OPTIONAL = 0x00000080 + + USAGE_MATCH_TYPE_AND = 0 + USAGE_MATCH_TYPE_OR = 1 + + X509_ASN_ENCODING = 0x00000001 + PKCS_7_ASN_ENCODING = 0x00010000 + + CERT_STORE_PROV_MEMORY = 2 + + CERT_STORE_ADD_ALWAYS = 4 + + CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG = 0x00000004 + + CERT_TRUST_NO_ERROR = 0x00000000 + CERT_TRUST_IS_NOT_TIME_VALID = 0x00000001 + CERT_TRUST_IS_REVOKED = 0x00000004 + CERT_TRUST_IS_NOT_SIGNATURE_VALID = 0x00000008 + CERT_TRUST_IS_NOT_VALID_FOR_USAGE = 0x00000010 + CERT_TRUST_IS_UNTRUSTED_ROOT = 0x00000020 + CERT_TRUST_REVOCATION_STATUS_UNKNOWN = 0x00000040 + CERT_TRUST_IS_CYCLIC = 0x00000080 + CERT_TRUST_INVALID_EXTENSION = 0x00000100 + CERT_TRUST_INVALID_POLICY_CONSTRAINTS = 0x00000200 + CERT_TRUST_INVALID_BASIC_CONSTRAINTS = 0x00000400 + CERT_TRUST_INVALID_NAME_CONSTRAINTS = 0x00000800 + CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT = 0x00001000 + CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT = 0x00002000 + CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT = 0x00004000 + CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT = 0x00008000 + CERT_TRUST_IS_OFFLINE_REVOCATION = 0x01000000 + CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY = 0x02000000 + CERT_TRUST_IS_EXPLICIT_DISTRUST = 0x04000000 + CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT = 0x08000000 + + CERT_CHAIN_POLICY_BASE = 1 + CERT_CHAIN_POLICY_AUTHENTICODE = 2 + CERT_CHAIN_POLICY_AUTHENTICODE_TS = 3 + CERT_CHAIN_POLICY_SSL = 4 + CERT_CHAIN_POLICY_BASIC_CONSTRAINTS = 5 + CERT_CHAIN_POLICY_NT_AUTH = 6 + CERT_CHAIN_POLICY_MICROSOFT_ROOT = 7 + CERT_CHAIN_POLICY_EV = 8 + + CERT_E_EXPIRED = 0x800B0101 + CERT_E_ROLE = 0x800B0103 + CERT_E_PURPOSE = 0x800B0106 + CERT_E_UNTRUSTEDROOT = 0x800B0109 + CERT_E_CN_NO_MATCH = 0x800B010F + + AUTHTYPE_CLIENT = 1 + AUTHTYPE_SERVER = 2 +) + +var ( + OID_PKIX_KP_SERVER_AUTH = []byte("1.3.6.1.5.5.7.3.1\x00") + OID_SERVER_GATED_CRYPTO = []byte("1.3.6.1.4.1.311.10.3.3\x00") + OID_SGC_NETSCAPE = []byte("2.16.840.1.113730.4.1\x00") +) + +// Invented values to support what package os expects. +type Timeval struct { + Sec int32 + Usec int32 +} + +func (tv *Timeval) Nanoseconds() int64 { + return (int64(tv.Sec)*1e6 + int64(tv.Usec)) * 1e3 +} + +func NsecToTimeval(nsec int64) (tv Timeval) { + tv.Sec = int32(nsec / 1e9) + tv.Usec = int32(nsec % 1e9 / 1e3) + return +} + +type SecurityAttributes struct { + Length uint32 + SecurityDescriptor uintptr + InheritHandle uint32 +} + +type Overlapped struct { + Internal uintptr + InternalHigh uintptr + Offset uint32 + OffsetHigh uint32 + HEvent Handle +} + +type FileNotifyInformation struct { + NextEntryOffset uint32 + Action uint32 + FileNameLength uint32 + FileName uint16 +} + +type Filetime struct { + LowDateTime uint32 + HighDateTime uint32 +} + +// Nanoseconds returns Filetime ft in nanoseconds +// since Epoch (00:00:00 UTC, January 1, 1970). +func (ft *Filetime) Nanoseconds() int64 { + // 100-nanosecond intervals since January 1, 1601 + nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) + // change starting time to the Epoch (00:00:00 UTC, January 1, 1970) + nsec -= 116444736000000000 + // convert into nanoseconds + nsec *= 100 + return nsec +} + +func NsecToFiletime(nsec int64) (ft Filetime) { + // convert into 100-nanosecond + nsec /= 100 + // change starting time to January 1, 1601 + nsec += 116444736000000000 + // split into high / low + ft.LowDateTime = uint32(nsec & 0xffffffff) + ft.HighDateTime = uint32(nsec >> 32 & 0xffffffff) + return ft +} + +type Win32finddata struct { + FileAttributes uint32 + CreationTime Filetime + LastAccessTime Filetime + LastWriteTime Filetime + FileSizeHigh uint32 + FileSizeLow uint32 + Reserved0 uint32 + Reserved1 uint32 + FileName [MAX_PATH - 1]uint16 + AlternateFileName [13]uint16 +} + +// This is the actual system call structure. +// Win32finddata is what we committed to in Go 1. +type win32finddata1 struct { + FileAttributes uint32 + CreationTime Filetime + LastAccessTime Filetime + LastWriteTime Filetime + FileSizeHigh uint32 + FileSizeLow uint32 + Reserved0 uint32 + Reserved1 uint32 + FileName [MAX_PATH]uint16 + AlternateFileName [14]uint16 +} + +func copyFindData(dst *Win32finddata, src *win32finddata1) { + dst.FileAttributes = src.FileAttributes + dst.CreationTime = src.CreationTime + dst.LastAccessTime = src.LastAccessTime + dst.LastWriteTime = src.LastWriteTime + dst.FileSizeHigh = src.FileSizeHigh + dst.FileSizeLow = src.FileSizeLow + dst.Reserved0 = src.Reserved0 + dst.Reserved1 = src.Reserved1 + + // The src is 1 element bigger than dst, but it must be NUL. + copy(dst.FileName[:], src.FileName[:]) + copy(dst.AlternateFileName[:], src.AlternateFileName[:]) +} + +type ByHandleFileInformation struct { + FileAttributes uint32 + CreationTime Filetime + LastAccessTime Filetime + LastWriteTime Filetime + VolumeSerialNumber uint32 + FileSizeHigh uint32 + FileSizeLow uint32 + NumberOfLinks uint32 + FileIndexHigh uint32 + FileIndexLow uint32 +} + +const ( + GetFileExInfoStandard = 0 + GetFileExMaxInfoLevel = 1 +) + +type Win32FileAttributeData struct { + FileAttributes uint32 + CreationTime Filetime + LastAccessTime Filetime + LastWriteTime Filetime + FileSizeHigh uint32 + FileSizeLow uint32 +} + +// ShowWindow constants +const ( + // winuser.h + SW_HIDE = 0 + SW_NORMAL = 1 + SW_SHOWNORMAL = 1 + SW_SHOWMINIMIZED = 2 + SW_SHOWMAXIMIZED = 3 + SW_MAXIMIZE = 3 + SW_SHOWNOACTIVATE = 4 + SW_SHOW = 5 + SW_MINIMIZE = 6 + SW_SHOWMINNOACTIVE = 7 + SW_SHOWNA = 8 + SW_RESTORE = 9 + SW_SHOWDEFAULT = 10 + SW_FORCEMINIMIZE = 11 +) + +type StartupInfo struct { + Cb uint32 + _ *uint16 + Desktop *uint16 + Title *uint16 + X uint32 + Y uint32 + XSize uint32 + YSize uint32 + XCountChars uint32 + YCountChars uint32 + FillAttribute uint32 + Flags uint32 + ShowWindow uint16 + _ uint16 + _ *byte + StdInput Handle + StdOutput Handle + StdErr Handle +} + +type ProcessInformation struct { + Process Handle + Thread Handle + ProcessId uint32 + ThreadId uint32 +} + +type ProcessEntry32 struct { + Size uint32 + Usage uint32 + ProcessID uint32 + DefaultHeapID uintptr + ModuleID uint32 + Threads uint32 + ParentProcessID uint32 + PriClassBase int32 + Flags uint32 + ExeFile [MAX_PATH]uint16 +} + +type Systemtime struct { + Year uint16 + Month uint16 + DayOfWeek uint16 + Day uint16 + Hour uint16 + Minute uint16 + Second uint16 + Milliseconds uint16 +} + +type Timezoneinformation struct { + Bias int32 + StandardName [32]uint16 + StandardDate Systemtime + StandardBias int32 + DaylightName [32]uint16 + DaylightDate Systemtime + DaylightBias int32 +} + +// Socket related. + +const ( + AF_UNSPEC = 0 + AF_UNIX = 1 + AF_INET = 2 + AF_INET6 = 23 + AF_NETBIOS = 17 + + SOCK_STREAM = 1 + SOCK_DGRAM = 2 + SOCK_RAW = 3 + SOCK_SEQPACKET = 5 + + IPPROTO_IP = 0 + IPPROTO_IPV6 = 0x29 + IPPROTO_TCP = 6 + IPPROTO_UDP = 17 + + SOL_SOCKET = 0xffff + SO_REUSEADDR = 4 + SO_KEEPALIVE = 8 + SO_DONTROUTE = 16 + SO_BROADCAST = 32 + SO_LINGER = 128 + SO_RCVBUF = 0x1002 + SO_SNDBUF = 0x1001 + SO_UPDATE_ACCEPT_CONTEXT = 0x700b + SO_UPDATE_CONNECT_CONTEXT = 0x7010 + + IOC_OUT = 0x40000000 + IOC_IN = 0x80000000 + IOC_VENDOR = 0x18000000 + IOC_INOUT = IOC_IN | IOC_OUT + IOC_WS2 = 0x08000000 + SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_INOUT | IOC_WS2 | 6 + SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4 + SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12 + + // cf. http://support.microsoft.com/default.aspx?scid=kb;en-us;257460 + + IP_TOS = 0x3 + IP_TTL = 0x4 + IP_MULTICAST_IF = 0x9 + IP_MULTICAST_TTL = 0xa + IP_MULTICAST_LOOP = 0xb + IP_ADD_MEMBERSHIP = 0xc + IP_DROP_MEMBERSHIP = 0xd + + IPV6_V6ONLY = 0x1b + IPV6_UNICAST_HOPS = 0x4 + IPV6_MULTICAST_IF = 0x9 + IPV6_MULTICAST_HOPS = 0xa + IPV6_MULTICAST_LOOP = 0xb + IPV6_JOIN_GROUP = 0xc + IPV6_LEAVE_GROUP = 0xd + + MSG_OOB = 0x1 + MSG_PEEK = 0x2 + MSG_DONTROUTE = 0x4 + MSG_WAITALL = 0x8 + + MSG_TRUNC = 0x0100 + MSG_CTRUNC = 0x0200 + MSG_BCAST = 0x0400 + MSG_MCAST = 0x0800 + + SOMAXCONN = 0x7fffffff + + TCP_NODELAY = 1 + + SHUT_RD = 0 + SHUT_WR = 1 + SHUT_RDWR = 2 + + WSADESCRIPTION_LEN = 256 + WSASYS_STATUS_LEN = 128 +) + +type WSABuf struct { + Len uint32 + Buf *byte +} + +type WSAMsg struct { + Name *syscall.RawSockaddrAny + Namelen int32 + Buffers *WSABuf + BufferCount uint32 + Control WSABuf + Flags uint32 +} + +// Invented values to support what package os expects. +const ( + S_IFMT = 0x1f000 + S_IFIFO = 0x1000 + S_IFCHR = 0x2000 + S_IFDIR = 0x4000 + S_IFBLK = 0x6000 + S_IFREG = 0x8000 + S_IFLNK = 0xa000 + S_IFSOCK = 0xc000 + S_ISUID = 0x800 + S_ISGID = 0x400 + S_ISVTX = 0x200 + S_IRUSR = 0x100 + S_IWRITE = 0x80 + S_IWUSR = 0x80 + S_IXUSR = 0x40 +) + +const ( + FILE_TYPE_CHAR = 0x0002 + FILE_TYPE_DISK = 0x0001 + FILE_TYPE_PIPE = 0x0003 + FILE_TYPE_REMOTE = 0x8000 + FILE_TYPE_UNKNOWN = 0x0000 +) + +type Hostent struct { + Name *byte + Aliases **byte + AddrType uint16 + Length uint16 + AddrList **byte +} + +type Protoent struct { + Name *byte + Aliases **byte + Proto uint16 +} + +const ( + DNS_TYPE_A = 0x0001 + DNS_TYPE_NS = 0x0002 + DNS_TYPE_MD = 0x0003 + DNS_TYPE_MF = 0x0004 + DNS_TYPE_CNAME = 0x0005 + DNS_TYPE_SOA = 0x0006 + DNS_TYPE_MB = 0x0007 + DNS_TYPE_MG = 0x0008 + DNS_TYPE_MR = 0x0009 + DNS_TYPE_NULL = 0x000a + DNS_TYPE_WKS = 0x000b + DNS_TYPE_PTR = 0x000c + DNS_TYPE_HINFO = 0x000d + DNS_TYPE_MINFO = 0x000e + DNS_TYPE_MX = 0x000f + DNS_TYPE_TEXT = 0x0010 + DNS_TYPE_RP = 0x0011 + DNS_TYPE_AFSDB = 0x0012 + DNS_TYPE_X25 = 0x0013 + DNS_TYPE_ISDN = 0x0014 + DNS_TYPE_RT = 0x0015 + DNS_TYPE_NSAP = 0x0016 + DNS_TYPE_NSAPPTR = 0x0017 + DNS_TYPE_SIG = 0x0018 + DNS_TYPE_KEY = 0x0019 + DNS_TYPE_PX = 0x001a + DNS_TYPE_GPOS = 0x001b + DNS_TYPE_AAAA = 0x001c + DNS_TYPE_LOC = 0x001d + DNS_TYPE_NXT = 0x001e + DNS_TYPE_EID = 0x001f + DNS_TYPE_NIMLOC = 0x0020 + DNS_TYPE_SRV = 0x0021 + DNS_TYPE_ATMA = 0x0022 + DNS_TYPE_NAPTR = 0x0023 + DNS_TYPE_KX = 0x0024 + DNS_TYPE_CERT = 0x0025 + DNS_TYPE_A6 = 0x0026 + DNS_TYPE_DNAME = 0x0027 + DNS_TYPE_SINK = 0x0028 + DNS_TYPE_OPT = 0x0029 + DNS_TYPE_DS = 0x002B + DNS_TYPE_RRSIG = 0x002E + DNS_TYPE_NSEC = 0x002F + DNS_TYPE_DNSKEY = 0x0030 + DNS_TYPE_DHCID = 0x0031 + DNS_TYPE_UINFO = 0x0064 + DNS_TYPE_UID = 0x0065 + DNS_TYPE_GID = 0x0066 + DNS_TYPE_UNSPEC = 0x0067 + DNS_TYPE_ADDRS = 0x00f8 + DNS_TYPE_TKEY = 0x00f9 + DNS_TYPE_TSIG = 0x00fa + DNS_TYPE_IXFR = 0x00fb + DNS_TYPE_AXFR = 0x00fc + DNS_TYPE_MAILB = 0x00fd + DNS_TYPE_MAILA = 0x00fe + DNS_TYPE_ALL = 0x00ff + DNS_TYPE_ANY = 0x00ff + DNS_TYPE_WINS = 0xff01 + DNS_TYPE_WINSR = 0xff02 + DNS_TYPE_NBSTAT = 0xff01 +) + +const ( + DNS_INFO_NO_RECORDS = 0x251D +) + +const ( + // flags inside DNSRecord.Dw + DnsSectionQuestion = 0x0000 + DnsSectionAnswer = 0x0001 + DnsSectionAuthority = 0x0002 + DnsSectionAdditional = 0x0003 +) + +type DNSSRVData struct { + Target *uint16 + Priority uint16 + Weight uint16 + Port uint16 + Pad uint16 +} + +type DNSPTRData struct { + Host *uint16 +} + +type DNSMXData struct { + NameExchange *uint16 + Preference uint16 + Pad uint16 +} + +type DNSTXTData struct { + StringCount uint16 + StringArray [1]*uint16 +} + +type DNSRecord struct { + Next *DNSRecord + Name *uint16 + Type uint16 + Length uint16 + Dw uint32 + Ttl uint32 + Reserved uint32 + Data [40]byte +} + +const ( + TF_DISCONNECT = 1 + TF_REUSE_SOCKET = 2 + TF_WRITE_BEHIND = 4 + TF_USE_DEFAULT_WORKER = 0 + TF_USE_SYSTEM_THREAD = 16 + TF_USE_KERNEL_APC = 32 +) + +type TransmitFileBuffers struct { + Head uintptr + HeadLength uint32 + Tail uintptr + TailLength uint32 +} + +const ( + IFF_UP = 1 + IFF_BROADCAST = 2 + IFF_LOOPBACK = 4 + IFF_POINTTOPOINT = 8 + IFF_MULTICAST = 16 +) + +const SIO_GET_INTERFACE_LIST = 0x4004747F + +// TODO(mattn): SockaddrGen is union of sockaddr/sockaddr_in/sockaddr_in6_old. +// will be fixed to change variable type as suitable. + +type SockaddrGen [24]byte + +type InterfaceInfo struct { + Flags uint32 + Address SockaddrGen + BroadcastAddress SockaddrGen + Netmask SockaddrGen +} + +type IpAddressString struct { + String [16]byte +} + +type IpMaskString IpAddressString + +type IpAddrString struct { + Next *IpAddrString + IpAddress IpAddressString + IpMask IpMaskString + Context uint32 +} + +const MAX_ADAPTER_NAME_LENGTH = 256 +const MAX_ADAPTER_DESCRIPTION_LENGTH = 128 +const MAX_ADAPTER_ADDRESS_LENGTH = 8 + +type IpAdapterInfo struct { + Next *IpAdapterInfo + ComboIndex uint32 + AdapterName [MAX_ADAPTER_NAME_LENGTH + 4]byte + Description [MAX_ADAPTER_DESCRIPTION_LENGTH + 4]byte + AddressLength uint32 + Address [MAX_ADAPTER_ADDRESS_LENGTH]byte + Index uint32 + Type uint32 + DhcpEnabled uint32 + CurrentIpAddress *IpAddrString + IpAddressList IpAddrString + GatewayList IpAddrString + DhcpServer IpAddrString + HaveWins bool + PrimaryWinsServer IpAddrString + SecondaryWinsServer IpAddrString + LeaseObtained int64 + LeaseExpires int64 +} + +const MAXLEN_PHYSADDR = 8 +const MAX_INTERFACE_NAME_LEN = 256 +const MAXLEN_IFDESCR = 256 + +type MibIfRow struct { + Name [MAX_INTERFACE_NAME_LEN]uint16 + Index uint32 + Type uint32 + Mtu uint32 + Speed uint32 + PhysAddrLen uint32 + PhysAddr [MAXLEN_PHYSADDR]byte + AdminStatus uint32 + OperStatus uint32 + LastChange uint32 + InOctets uint32 + InUcastPkts uint32 + InNUcastPkts uint32 + InDiscards uint32 + InErrors uint32 + InUnknownProtos uint32 + OutOctets uint32 + OutUcastPkts uint32 + OutNUcastPkts uint32 + OutDiscards uint32 + OutErrors uint32 + OutQLen uint32 + DescrLen uint32 + Descr [MAXLEN_IFDESCR]byte +} + +type CertContext struct { + EncodingType uint32 + EncodedCert *byte + Length uint32 + CertInfo uintptr + Store Handle +} + +type CertChainContext struct { + Size uint32 + TrustStatus CertTrustStatus + ChainCount uint32 + Chains **CertSimpleChain + LowerQualityChainCount uint32 + LowerQualityChains **CertChainContext + HasRevocationFreshnessTime uint32 + RevocationFreshnessTime uint32 +} + +type CertSimpleChain struct { + Size uint32 + TrustStatus CertTrustStatus + NumElements uint32 + Elements **CertChainElement + TrustListInfo uintptr + HasRevocationFreshnessTime uint32 + RevocationFreshnessTime uint32 +} + +type CertChainElement struct { + Size uint32 + CertContext *CertContext + TrustStatus CertTrustStatus + RevocationInfo *CertRevocationInfo + IssuanceUsage *CertEnhKeyUsage + ApplicationUsage *CertEnhKeyUsage + ExtendedErrorInfo *uint16 +} + +type CertRevocationInfo struct { + Size uint32 + RevocationResult uint32 + RevocationOid *byte + OidSpecificInfo uintptr + HasFreshnessTime uint32 + FreshnessTime uint32 + CrlInfo uintptr // *CertRevocationCrlInfo +} + +type CertTrustStatus struct { + ErrorStatus uint32 + InfoStatus uint32 +} + +type CertUsageMatch struct { + Type uint32 + Usage CertEnhKeyUsage +} + +type CertEnhKeyUsage struct { + Length uint32 + UsageIdentifiers **byte +} + +type CertChainPara struct { + Size uint32 + RequestedUsage CertUsageMatch + RequstedIssuancePolicy CertUsageMatch + URLRetrievalTimeout uint32 + CheckRevocationFreshnessTime uint32 + RevocationFreshnessTime uint32 + CacheResync *Filetime +} + +type CertChainPolicyPara struct { + Size uint32 + Flags uint32 + ExtraPolicyPara uintptr +} + +type SSLExtraCertChainPolicyPara struct { + Size uint32 + AuthType uint32 + Checks uint32 + ServerName *uint16 +} + +type CertChainPolicyStatus struct { + Size uint32 + Error uint32 + ChainIndex uint32 + ElementIndex uint32 + ExtraPolicyStatus uintptr +} + +const ( + // do not reorder + HKEY_CLASSES_ROOT = 0x80000000 + iota + HKEY_CURRENT_USER + HKEY_LOCAL_MACHINE + HKEY_USERS + HKEY_PERFORMANCE_DATA + HKEY_CURRENT_CONFIG + HKEY_DYN_DATA + + KEY_QUERY_VALUE = 1 + KEY_SET_VALUE = 2 + KEY_CREATE_SUB_KEY = 4 + KEY_ENUMERATE_SUB_KEYS = 8 + KEY_NOTIFY = 16 + KEY_CREATE_LINK = 32 + KEY_WRITE = 0x20006 + KEY_EXECUTE = 0x20019 + KEY_READ = 0x20019 + KEY_WOW64_64KEY = 0x0100 + KEY_WOW64_32KEY = 0x0200 + KEY_ALL_ACCESS = 0xf003f +) + +const ( + // do not reorder + REG_NONE = iota + REG_SZ + REG_EXPAND_SZ + REG_BINARY + REG_DWORD_LITTLE_ENDIAN + REG_DWORD_BIG_ENDIAN + REG_LINK + REG_MULTI_SZ + REG_RESOURCE_LIST + REG_FULL_RESOURCE_DESCRIPTOR + REG_RESOURCE_REQUIREMENTS_LIST + REG_QWORD_LITTLE_ENDIAN + REG_DWORD = REG_DWORD_LITTLE_ENDIAN + REG_QWORD = REG_QWORD_LITTLE_ENDIAN +) + +type AddrinfoW struct { + Flags int32 + Family int32 + Socktype int32 + Protocol int32 + Addrlen uintptr + Canonname *uint16 + Addr uintptr + Next *AddrinfoW +} + +const ( + AI_PASSIVE = 1 + AI_CANONNAME = 2 + AI_NUMERICHOST = 4 +) + +type GUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} + +var WSAID_CONNECTEX = GUID{ + 0x25a207b9, + 0xddf3, + 0x4660, + [8]byte{0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}, +} + +var WSAID_WSASENDMSG = GUID{ + 0xa441e712, + 0x754f, + 0x43ca, + [8]byte{0x84, 0xa7, 0x0d, 0xee, 0x44, 0xcf, 0x60, 0x6d}, +} + +var WSAID_WSARECVMSG = GUID{ + 0xf689d7c8, + 0x6f1f, + 0x436b, + [8]byte{0x8a, 0x53, 0xe5, 0x4f, 0xe3, 0x51, 0xc3, 0x22}, +} + +const ( + FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1 + FILE_SKIP_SET_EVENT_ON_HANDLE = 2 +) + +const ( + WSAPROTOCOL_LEN = 255 + MAX_PROTOCOL_CHAIN = 7 + BASE_PROTOCOL = 1 + LAYERED_PROTOCOL = 0 + + XP1_CONNECTIONLESS = 0x00000001 + XP1_GUARANTEED_DELIVERY = 0x00000002 + XP1_GUARANTEED_ORDER = 0x00000004 + XP1_MESSAGE_ORIENTED = 0x00000008 + XP1_PSEUDO_STREAM = 0x00000010 + XP1_GRACEFUL_CLOSE = 0x00000020 + XP1_EXPEDITED_DATA = 0x00000040 + XP1_CONNECT_DATA = 0x00000080 + XP1_DISCONNECT_DATA = 0x00000100 + XP1_SUPPORT_BROADCAST = 0x00000200 + XP1_SUPPORT_MULTIPOINT = 0x00000400 + XP1_MULTIPOINT_CONTROL_PLANE = 0x00000800 + XP1_MULTIPOINT_DATA_PLANE = 0x00001000 + XP1_QOS_SUPPORTED = 0x00002000 + XP1_UNI_SEND = 0x00008000 + XP1_UNI_RECV = 0x00010000 + XP1_IFS_HANDLES = 0x00020000 + XP1_PARTIAL_MESSAGE = 0x00040000 + XP1_SAN_SUPPORT_SDP = 0x00080000 + + PFL_MULTIPLE_PROTO_ENTRIES = 0x00000001 + PFL_RECOMMENDED_PROTO_ENTRY = 0x00000002 + PFL_HIDDEN = 0x00000004 + PFL_MATCHES_PROTOCOL_ZERO = 0x00000008 + PFL_NETWORKDIRECT_PROVIDER = 0x00000010 +) + +type WSAProtocolInfo struct { + ServiceFlags1 uint32 + ServiceFlags2 uint32 + ServiceFlags3 uint32 + ServiceFlags4 uint32 + ProviderFlags uint32 + ProviderId GUID + CatalogEntryId uint32 + ProtocolChain WSAProtocolChain + Version int32 + AddressFamily int32 + MaxSockAddr int32 + MinSockAddr int32 + SocketType int32 + Protocol int32 + ProtocolMaxOffset int32 + NetworkByteOrder int32 + SecurityScheme int32 + MessageSize uint32 + ProviderReserved uint32 + ProtocolName [WSAPROTOCOL_LEN + 1]uint16 +} + +type WSAProtocolChain struct { + ChainLen int32 + ChainEntries [MAX_PROTOCOL_CHAIN]uint32 +} + +type TCPKeepalive struct { + OnOff uint32 + Time uint32 + Interval uint32 +} + +type symbolicLinkReparseBuffer struct { + SubstituteNameOffset uint16 + SubstituteNameLength uint16 + PrintNameOffset uint16 + PrintNameLength uint16 + Flags uint32 + PathBuffer [1]uint16 +} + +type mountPointReparseBuffer struct { + SubstituteNameOffset uint16 + SubstituteNameLength uint16 + PrintNameOffset uint16 + PrintNameLength uint16 + PathBuffer [1]uint16 +} + +type reparseDataBuffer struct { + ReparseTag uint32 + ReparseDataLength uint16 + Reserved uint16 + + // GenericReparseBuffer + reparseBuffer byte +} + +const ( + FSCTL_GET_REPARSE_POINT = 0x900A8 + MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024 + IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 + IO_REPARSE_TAG_SYMLINK = 0xA000000C + SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 +) + +const ( + ComputerNameNetBIOS = 0 + ComputerNameDnsHostname = 1 + ComputerNameDnsDomain = 2 + ComputerNameDnsFullyQualified = 3 + ComputerNamePhysicalNetBIOS = 4 + ComputerNamePhysicalDnsHostname = 5 + ComputerNamePhysicalDnsDomain = 6 + ComputerNamePhysicalDnsFullyQualified = 7 + ComputerNameMax = 8 +) + +const ( + MOVEFILE_REPLACE_EXISTING = 0x1 + MOVEFILE_COPY_ALLOWED = 0x2 + MOVEFILE_DELAY_UNTIL_REBOOT = 0x4 + MOVEFILE_WRITE_THROUGH = 0x8 + MOVEFILE_CREATE_HARDLINK = 0x10 + MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x20 +) + +const GAA_FLAG_INCLUDE_PREFIX = 0x00000010 + +const ( + IF_TYPE_OTHER = 1 + IF_TYPE_ETHERNET_CSMACD = 6 + IF_TYPE_ISO88025_TOKENRING = 9 + IF_TYPE_PPP = 23 + IF_TYPE_SOFTWARE_LOOPBACK = 24 + IF_TYPE_ATM = 37 + IF_TYPE_IEEE80211 = 71 + IF_TYPE_TUNNEL = 131 + IF_TYPE_IEEE1394 = 144 +) + +type SocketAddress struct { + Sockaddr *syscall.RawSockaddrAny + SockaddrLength int32 +} + +type IpAdapterUnicastAddress struct { + Length uint32 + Flags uint32 + Next *IpAdapterUnicastAddress + Address SocketAddress + PrefixOrigin int32 + SuffixOrigin int32 + DadState int32 + ValidLifetime uint32 + PreferredLifetime uint32 + LeaseLifetime uint32 + OnLinkPrefixLength uint8 +} + +type IpAdapterAnycastAddress struct { + Length uint32 + Flags uint32 + Next *IpAdapterAnycastAddress + Address SocketAddress +} + +type IpAdapterMulticastAddress struct { + Length uint32 + Flags uint32 + Next *IpAdapterMulticastAddress + Address SocketAddress +} + +type IpAdapterDnsServerAdapter struct { + Length uint32 + Reserved uint32 + Next *IpAdapterDnsServerAdapter + Address SocketAddress +} + +type IpAdapterPrefix struct { + Length uint32 + Flags uint32 + Next *IpAdapterPrefix + Address SocketAddress + PrefixLength uint32 +} + +type IpAdapterAddresses struct { + Length uint32 + IfIndex uint32 + Next *IpAdapterAddresses + AdapterName *byte + FirstUnicastAddress *IpAdapterUnicastAddress + FirstAnycastAddress *IpAdapterAnycastAddress + FirstMulticastAddress *IpAdapterMulticastAddress + FirstDnsServerAddress *IpAdapterDnsServerAdapter + DnsSuffix *uint16 + Description *uint16 + FriendlyName *uint16 + PhysicalAddress [syscall.MAX_ADAPTER_ADDRESS_LENGTH]byte + PhysicalAddressLength uint32 + Flags uint32 + Mtu uint32 + IfType uint32 + OperStatus uint32 + Ipv6IfIndex uint32 + ZoneIndices [16]uint32 + FirstPrefix *IpAdapterPrefix + /* more fields might be present here. */ +} + +const ( + IfOperStatusUp = 1 + IfOperStatusDown = 2 + IfOperStatusTesting = 3 + IfOperStatusUnknown = 4 + IfOperStatusDormant = 5 + IfOperStatusNotPresent = 6 + IfOperStatusLowerLayerDown = 7 +) + +// Console related constants used for the mode parameter to SetConsoleMode. See +// https://docs.microsoft.com/en-us/windows/console/setconsolemode for details. + +const ( + ENABLE_PROCESSED_INPUT = 0x1 + ENABLE_LINE_INPUT = 0x2 + ENABLE_ECHO_INPUT = 0x4 + ENABLE_WINDOW_INPUT = 0x8 + ENABLE_MOUSE_INPUT = 0x10 + ENABLE_INSERT_MODE = 0x20 + ENABLE_QUICK_EDIT_MODE = 0x40 + ENABLE_EXTENDED_FLAGS = 0x80 + ENABLE_AUTO_POSITION = 0x100 + ENABLE_VIRTUAL_TERMINAL_INPUT = 0x200 + + ENABLE_PROCESSED_OUTPUT = 0x1 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x2 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4 + DISABLE_NEWLINE_AUTO_RETURN = 0x8 + ENABLE_LVB_GRID_WORLDWIDE = 0x10 +) + +type Coord struct { + X int16 + Y int16 +} + +type SmallRect struct { + Left int16 + Top int16 + Right int16 + Bottom int16 +} + +// Used with GetConsoleScreenBuffer to retreive information about a console +// screen buffer. See +// https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str +// for details. + +type ConsoleScreenBufferInfo struct { + Size Coord + CursorPosition Coord + Attributes uint16 + Window SmallRect + MaximumWindowSize Coord +} diff --git a/vendor/golang.org/x/sys/windows/types_windows_386.go b/vendor/golang.org/x/sys/windows/types_windows_386.go new file mode 100644 index 0000000..fe0ddd0 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/types_windows_386.go @@ -0,0 +1,22 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows + +type WSAData struct { + Version uint16 + HighVersion uint16 + Description [WSADESCRIPTION_LEN + 1]byte + SystemStatus [WSASYS_STATUS_LEN + 1]byte + MaxSockets uint16 + MaxUdpDg uint16 + VendorInfo *byte +} + +type Servent struct { + Name *byte + Aliases **byte + Port uint16 + Proto *byte +} diff --git a/vendor/golang.org/x/sys/windows/types_windows_amd64.go b/vendor/golang.org/x/sys/windows/types_windows_amd64.go new file mode 100644 index 0000000..7e154c2 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/types_windows_amd64.go @@ -0,0 +1,22 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows + +type WSAData struct { + Version uint16 + HighVersion uint16 + MaxSockets uint16 + MaxUdpDg uint16 + VendorInfo *byte + Description [WSADESCRIPTION_LEN + 1]byte + SystemStatus [WSASYS_STATUS_LEN + 1]byte +} + +type Servent struct { + Name *byte + Aliases **byte + Proto *byte + Port uint16 +} diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go new file mode 100644 index 0000000..c7b3b15 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -0,0 +1,2687 @@ +// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT + +package windows + +import ( + "syscall" + "unsafe" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modadvapi32 = NewLazySystemDLL("advapi32.dll") + modkernel32 = NewLazySystemDLL("kernel32.dll") + modshell32 = NewLazySystemDLL("shell32.dll") + modmswsock = NewLazySystemDLL("mswsock.dll") + modcrypt32 = NewLazySystemDLL("crypt32.dll") + modws2_32 = NewLazySystemDLL("ws2_32.dll") + moddnsapi = NewLazySystemDLL("dnsapi.dll") + modiphlpapi = NewLazySystemDLL("iphlpapi.dll") + modsecur32 = NewLazySystemDLL("secur32.dll") + modnetapi32 = NewLazySystemDLL("netapi32.dll") + moduserenv = NewLazySystemDLL("userenv.dll") + + procRegisterEventSourceW = modadvapi32.NewProc("RegisterEventSourceW") + procDeregisterEventSource = modadvapi32.NewProc("DeregisterEventSource") + procReportEventW = modadvapi32.NewProc("ReportEventW") + procOpenSCManagerW = modadvapi32.NewProc("OpenSCManagerW") + procCloseServiceHandle = modadvapi32.NewProc("CloseServiceHandle") + procCreateServiceW = modadvapi32.NewProc("CreateServiceW") + procOpenServiceW = modadvapi32.NewProc("OpenServiceW") + procDeleteService = modadvapi32.NewProc("DeleteService") + procStartServiceW = modadvapi32.NewProc("StartServiceW") + procQueryServiceStatus = modadvapi32.NewProc("QueryServiceStatus") + procControlService = modadvapi32.NewProc("ControlService") + procStartServiceCtrlDispatcherW = modadvapi32.NewProc("StartServiceCtrlDispatcherW") + procSetServiceStatus = modadvapi32.NewProc("SetServiceStatus") + procChangeServiceConfigW = modadvapi32.NewProc("ChangeServiceConfigW") + procQueryServiceConfigW = modadvapi32.NewProc("QueryServiceConfigW") + procChangeServiceConfig2W = modadvapi32.NewProc("ChangeServiceConfig2W") + procQueryServiceConfig2W = modadvapi32.NewProc("QueryServiceConfig2W") + procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW") + procGetLastError = modkernel32.NewProc("GetLastError") + procLoadLibraryW = modkernel32.NewProc("LoadLibraryW") + procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW") + procFreeLibrary = modkernel32.NewProc("FreeLibrary") + procGetProcAddress = modkernel32.NewProc("GetProcAddress") + procGetVersion = modkernel32.NewProc("GetVersion") + procFormatMessageW = modkernel32.NewProc("FormatMessageW") + procExitProcess = modkernel32.NewProc("ExitProcess") + procCreateFileW = modkernel32.NewProc("CreateFileW") + procReadFile = modkernel32.NewProc("ReadFile") + procWriteFile = modkernel32.NewProc("WriteFile") + procSetFilePointer = modkernel32.NewProc("SetFilePointer") + procCloseHandle = modkernel32.NewProc("CloseHandle") + procGetStdHandle = modkernel32.NewProc("GetStdHandle") + procSetStdHandle = modkernel32.NewProc("SetStdHandle") + procFindFirstFileW = modkernel32.NewProc("FindFirstFileW") + procFindNextFileW = modkernel32.NewProc("FindNextFileW") + procFindClose = modkernel32.NewProc("FindClose") + procGetFileInformationByHandle = modkernel32.NewProc("GetFileInformationByHandle") + procGetCurrentDirectoryW = modkernel32.NewProc("GetCurrentDirectoryW") + procSetCurrentDirectoryW = modkernel32.NewProc("SetCurrentDirectoryW") + procCreateDirectoryW = modkernel32.NewProc("CreateDirectoryW") + procRemoveDirectoryW = modkernel32.NewProc("RemoveDirectoryW") + procDeleteFileW = modkernel32.NewProc("DeleteFileW") + procMoveFileW = modkernel32.NewProc("MoveFileW") + procMoveFileExW = modkernel32.NewProc("MoveFileExW") + procGetComputerNameW = modkernel32.NewProc("GetComputerNameW") + procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW") + procSetEndOfFile = modkernel32.NewProc("SetEndOfFile") + procGetSystemTimeAsFileTime = modkernel32.NewProc("GetSystemTimeAsFileTime") + procGetSystemTimePreciseAsFileTime = modkernel32.NewProc("GetSystemTimePreciseAsFileTime") + procGetTimeZoneInformation = modkernel32.NewProc("GetTimeZoneInformation") + procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") + procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") + procPostQueuedCompletionStatus = modkernel32.NewProc("PostQueuedCompletionStatus") + procCancelIo = modkernel32.NewProc("CancelIo") + procCancelIoEx = modkernel32.NewProc("CancelIoEx") + procCreateProcessW = modkernel32.NewProc("CreateProcessW") + procOpenProcess = modkernel32.NewProc("OpenProcess") + procTerminateProcess = modkernel32.NewProc("TerminateProcess") + procGetExitCodeProcess = modkernel32.NewProc("GetExitCodeProcess") + procGetStartupInfoW = modkernel32.NewProc("GetStartupInfoW") + procGetCurrentProcess = modkernel32.NewProc("GetCurrentProcess") + procGetProcessTimes = modkernel32.NewProc("GetProcessTimes") + procDuplicateHandle = modkernel32.NewProc("DuplicateHandle") + procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject") + procGetTempPathW = modkernel32.NewProc("GetTempPathW") + procCreatePipe = modkernel32.NewProc("CreatePipe") + procGetFileType = modkernel32.NewProc("GetFileType") + procCryptAcquireContextW = modadvapi32.NewProc("CryptAcquireContextW") + procCryptReleaseContext = modadvapi32.NewProc("CryptReleaseContext") + procCryptGenRandom = modadvapi32.NewProc("CryptGenRandom") + procGetEnvironmentStringsW = modkernel32.NewProc("GetEnvironmentStringsW") + procFreeEnvironmentStringsW = modkernel32.NewProc("FreeEnvironmentStringsW") + procGetEnvironmentVariableW = modkernel32.NewProc("GetEnvironmentVariableW") + procSetEnvironmentVariableW = modkernel32.NewProc("SetEnvironmentVariableW") + procSetFileTime = modkernel32.NewProc("SetFileTime") + procGetFileAttributesW = modkernel32.NewProc("GetFileAttributesW") + procSetFileAttributesW = modkernel32.NewProc("SetFileAttributesW") + procGetFileAttributesExW = modkernel32.NewProc("GetFileAttributesExW") + procGetCommandLineW = modkernel32.NewProc("GetCommandLineW") + procCommandLineToArgvW = modshell32.NewProc("CommandLineToArgvW") + procLocalFree = modkernel32.NewProc("LocalFree") + procSetHandleInformation = modkernel32.NewProc("SetHandleInformation") + procFlushFileBuffers = modkernel32.NewProc("FlushFileBuffers") + procGetFullPathNameW = modkernel32.NewProc("GetFullPathNameW") + procGetLongPathNameW = modkernel32.NewProc("GetLongPathNameW") + procGetShortPathNameW = modkernel32.NewProc("GetShortPathNameW") + procCreateFileMappingW = modkernel32.NewProc("CreateFileMappingW") + procMapViewOfFile = modkernel32.NewProc("MapViewOfFile") + procUnmapViewOfFile = modkernel32.NewProc("UnmapViewOfFile") + procFlushViewOfFile = modkernel32.NewProc("FlushViewOfFile") + procVirtualLock = modkernel32.NewProc("VirtualLock") + procVirtualUnlock = modkernel32.NewProc("VirtualUnlock") + procVirtualAlloc = modkernel32.NewProc("VirtualAlloc") + procVirtualFree = modkernel32.NewProc("VirtualFree") + procVirtualProtect = modkernel32.NewProc("VirtualProtect") + procTransmitFile = modmswsock.NewProc("TransmitFile") + procReadDirectoryChangesW = modkernel32.NewProc("ReadDirectoryChangesW") + procCertOpenSystemStoreW = modcrypt32.NewProc("CertOpenSystemStoreW") + procCertOpenStore = modcrypt32.NewProc("CertOpenStore") + procCertEnumCertificatesInStore = modcrypt32.NewProc("CertEnumCertificatesInStore") + procCertAddCertificateContextToStore = modcrypt32.NewProc("CertAddCertificateContextToStore") + procCertCloseStore = modcrypt32.NewProc("CertCloseStore") + procCertGetCertificateChain = modcrypt32.NewProc("CertGetCertificateChain") + procCertFreeCertificateChain = modcrypt32.NewProc("CertFreeCertificateChain") + procCertCreateCertificateContext = modcrypt32.NewProc("CertCreateCertificateContext") + procCertFreeCertificateContext = modcrypt32.NewProc("CertFreeCertificateContext") + procCertVerifyCertificateChainPolicy = modcrypt32.NewProc("CertVerifyCertificateChainPolicy") + procRegOpenKeyExW = modadvapi32.NewProc("RegOpenKeyExW") + procRegCloseKey = modadvapi32.NewProc("RegCloseKey") + procRegQueryInfoKeyW = modadvapi32.NewProc("RegQueryInfoKeyW") + procRegEnumKeyExW = modadvapi32.NewProc("RegEnumKeyExW") + procRegQueryValueExW = modadvapi32.NewProc("RegQueryValueExW") + procGetCurrentProcessId = modkernel32.NewProc("GetCurrentProcessId") + procGetConsoleMode = modkernel32.NewProc("GetConsoleMode") + procSetConsoleMode = modkernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo") + procWriteConsoleW = modkernel32.NewProc("WriteConsoleW") + procReadConsoleW = modkernel32.NewProc("ReadConsoleW") + procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot") + procProcess32FirstW = modkernel32.NewProc("Process32FirstW") + procProcess32NextW = modkernel32.NewProc("Process32NextW") + procDeviceIoControl = modkernel32.NewProc("DeviceIoControl") + procCreateSymbolicLinkW = modkernel32.NewProc("CreateSymbolicLinkW") + procCreateHardLinkW = modkernel32.NewProc("CreateHardLinkW") + procGetCurrentThreadId = modkernel32.NewProc("GetCurrentThreadId") + procCreateEventW = modkernel32.NewProc("CreateEventW") + procCreateEventExW = modkernel32.NewProc("CreateEventExW") + procOpenEventW = modkernel32.NewProc("OpenEventW") + procSetEvent = modkernel32.NewProc("SetEvent") + procResetEvent = modkernel32.NewProc("ResetEvent") + procPulseEvent = modkernel32.NewProc("PulseEvent") + procDefineDosDeviceW = modkernel32.NewProc("DefineDosDeviceW") + procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW") + procFindFirstVolumeW = modkernel32.NewProc("FindFirstVolumeW") + procFindFirstVolumeMountPointW = modkernel32.NewProc("FindFirstVolumeMountPointW") + procFindNextVolumeW = modkernel32.NewProc("FindNextVolumeW") + procFindNextVolumeMountPointW = modkernel32.NewProc("FindNextVolumeMountPointW") + procFindVolumeClose = modkernel32.NewProc("FindVolumeClose") + procFindVolumeMountPointClose = modkernel32.NewProc("FindVolumeMountPointClose") + procGetDriveTypeW = modkernel32.NewProc("GetDriveTypeW") + procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives") + procGetLogicalDriveStringsW = modkernel32.NewProc("GetLogicalDriveStringsW") + procGetVolumeInformationW = modkernel32.NewProc("GetVolumeInformationW") + procGetVolumeInformationByHandleW = modkernel32.NewProc("GetVolumeInformationByHandleW") + procGetVolumeNameForVolumeMountPointW = modkernel32.NewProc("GetVolumeNameForVolumeMountPointW") + procGetVolumePathNameW = modkernel32.NewProc("GetVolumePathNameW") + procGetVolumePathNamesForVolumeNameW = modkernel32.NewProc("GetVolumePathNamesForVolumeNameW") + procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW") + procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW") + procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW") + procWSAStartup = modws2_32.NewProc("WSAStartup") + procWSACleanup = modws2_32.NewProc("WSACleanup") + procWSAIoctl = modws2_32.NewProc("WSAIoctl") + procsocket = modws2_32.NewProc("socket") + procsetsockopt = modws2_32.NewProc("setsockopt") + procgetsockopt = modws2_32.NewProc("getsockopt") + procbind = modws2_32.NewProc("bind") + procconnect = modws2_32.NewProc("connect") + procgetsockname = modws2_32.NewProc("getsockname") + procgetpeername = modws2_32.NewProc("getpeername") + proclisten = modws2_32.NewProc("listen") + procshutdown = modws2_32.NewProc("shutdown") + procclosesocket = modws2_32.NewProc("closesocket") + procAcceptEx = modmswsock.NewProc("AcceptEx") + procGetAcceptExSockaddrs = modmswsock.NewProc("GetAcceptExSockaddrs") + procWSARecv = modws2_32.NewProc("WSARecv") + procWSASend = modws2_32.NewProc("WSASend") + procWSARecvFrom = modws2_32.NewProc("WSARecvFrom") + procWSASendTo = modws2_32.NewProc("WSASendTo") + procgethostbyname = modws2_32.NewProc("gethostbyname") + procgetservbyname = modws2_32.NewProc("getservbyname") + procntohs = modws2_32.NewProc("ntohs") + procgetprotobyname = modws2_32.NewProc("getprotobyname") + procDnsQuery_W = moddnsapi.NewProc("DnsQuery_W") + procDnsRecordListFree = moddnsapi.NewProc("DnsRecordListFree") + procDnsNameCompare_W = moddnsapi.NewProc("DnsNameCompare_W") + procGetAddrInfoW = modws2_32.NewProc("GetAddrInfoW") + procFreeAddrInfoW = modws2_32.NewProc("FreeAddrInfoW") + procGetIfEntry = modiphlpapi.NewProc("GetIfEntry") + procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo") + procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") + procWSAEnumProtocolsW = modws2_32.NewProc("WSAEnumProtocolsW") + procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") + procGetACP = modkernel32.NewProc("GetACP") + procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar") + procTranslateNameW = modsecur32.NewProc("TranslateNameW") + procGetUserNameExW = modsecur32.NewProc("GetUserNameExW") + procNetUserGetInfo = modnetapi32.NewProc("NetUserGetInfo") + procNetGetJoinInformation = modnetapi32.NewProc("NetGetJoinInformation") + procNetApiBufferFree = modnetapi32.NewProc("NetApiBufferFree") + procLookupAccountSidW = modadvapi32.NewProc("LookupAccountSidW") + procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW") + procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") + procConvertStringSidToSidW = modadvapi32.NewProc("ConvertStringSidToSidW") + procGetLengthSid = modadvapi32.NewProc("GetLengthSid") + procCopySid = modadvapi32.NewProc("CopySid") + procAllocateAndInitializeSid = modadvapi32.NewProc("AllocateAndInitializeSid") + procFreeSid = modadvapi32.NewProc("FreeSid") + procEqualSid = modadvapi32.NewProc("EqualSid") + procCheckTokenMembership = modadvapi32.NewProc("CheckTokenMembership") + procOpenProcessToken = modadvapi32.NewProc("OpenProcessToken") + procGetTokenInformation = modadvapi32.NewProc("GetTokenInformation") + procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW") +) + +func RegisterEventSource(uncServerName *uint16, sourceName *uint16) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procRegisterEventSourceW.Addr(), 2, uintptr(unsafe.Pointer(uncServerName)), uintptr(unsafe.Pointer(sourceName)), 0) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func DeregisterEventSource(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procDeregisterEventSource.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ReportEvent(log Handle, etype uint16, category uint16, eventId uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings **uint16, rawData *byte) (err error) { + r1, _, e1 := syscall.Syscall9(procReportEventW.Addr(), 9, uintptr(log), uintptr(etype), uintptr(category), uintptr(eventId), uintptr(usrSId), uintptr(numStrings), uintptr(dataSize), uintptr(unsafe.Pointer(strings)), uintptr(unsafe.Pointer(rawData))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procOpenSCManagerW.Addr(), 3, uintptr(unsafe.Pointer(machineName)), uintptr(unsafe.Pointer(databaseName)), uintptr(access)) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CloseServiceHandle(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procCloseServiceHandle.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall15(procCreateServiceW.Addr(), 13, uintptr(mgr), uintptr(unsafe.Pointer(serviceName)), uintptr(unsafe.Pointer(displayName)), uintptr(access), uintptr(srvType), uintptr(startType), uintptr(errCtl), uintptr(unsafe.Pointer(pathName)), uintptr(unsafe.Pointer(loadOrderGroup)), uintptr(unsafe.Pointer(tagId)), uintptr(unsafe.Pointer(dependencies)), uintptr(unsafe.Pointer(serviceStartName)), uintptr(unsafe.Pointer(password)), 0, 0) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procOpenServiceW.Addr(), 3, uintptr(mgr), uintptr(unsafe.Pointer(serviceName)), uintptr(access)) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func DeleteService(service Handle) (err error) { + r1, _, e1 := syscall.Syscall(procDeleteService.Addr(), 1, uintptr(service), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func StartService(service Handle, numArgs uint32, argVectors **uint16) (err error) { + r1, _, e1 := syscall.Syscall(procStartServiceW.Addr(), 3, uintptr(service), uintptr(numArgs), uintptr(unsafe.Pointer(argVectors))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err error) { + r1, _, e1 := syscall.Syscall(procQueryServiceStatus.Addr(), 2, uintptr(service), uintptr(unsafe.Pointer(status)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ControlService(service Handle, control uint32, status *SERVICE_STATUS) (err error) { + r1, _, e1 := syscall.Syscall(procControlService.Addr(), 3, uintptr(service), uintptr(control), uintptr(unsafe.Pointer(status))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err error) { + r1, _, e1 := syscall.Syscall(procStartServiceCtrlDispatcherW.Addr(), 1, uintptr(unsafe.Pointer(serviceTable)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err error) { + r1, _, e1 := syscall.Syscall(procSetServiceStatus.Addr(), 2, uintptr(service), uintptr(unsafe.Pointer(serviceStatus)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ChangeServiceConfig(service Handle, serviceType uint32, startType uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16, displayName *uint16) (err error) { + r1, _, e1 := syscall.Syscall12(procChangeServiceConfigW.Addr(), 11, uintptr(service), uintptr(serviceType), uintptr(startType), uintptr(errorControl), uintptr(unsafe.Pointer(binaryPathName)), uintptr(unsafe.Pointer(loadOrderGroup)), uintptr(unsafe.Pointer(tagId)), uintptr(unsafe.Pointer(dependencies)), uintptr(unsafe.Pointer(serviceStartName)), uintptr(unsafe.Pointer(password)), uintptr(unsafe.Pointer(displayName)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procQueryServiceConfigW.Addr(), 4, uintptr(service), uintptr(unsafe.Pointer(serviceConfig)), uintptr(bufSize), uintptr(unsafe.Pointer(bytesNeeded)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) { + r1, _, e1 := syscall.Syscall(procChangeServiceConfig2W.Addr(), 3, uintptr(service), uintptr(infoLevel), uintptr(unsafe.Pointer(info))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procQueryServiceConfig2W.Addr(), 5, uintptr(service), uintptr(infoLevel), uintptr(unsafe.Pointer(buff)), uintptr(buffSize), uintptr(unsafe.Pointer(bytesNeeded)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) { + r1, _, e1 := syscall.Syscall12(procEnumServicesStatusExW.Addr(), 10, uintptr(mgr), uintptr(infoLevel), uintptr(serviceType), uintptr(serviceState), uintptr(unsafe.Pointer(services)), uintptr(bufSize), uintptr(unsafe.Pointer(bytesNeeded)), uintptr(unsafe.Pointer(servicesReturned)), uintptr(unsafe.Pointer(resumeHandle)), uintptr(unsafe.Pointer(groupName)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetLastError() (lasterr error) { + r0, _, _ := syscall.Syscall(procGetLastError.Addr(), 0, 0, 0, 0) + if r0 != 0 { + lasterr = syscall.Errno(r0) + } + return +} + +func LoadLibrary(libname string) (handle Handle, err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(libname) + if err != nil { + return + } + return _LoadLibrary(_p0) +} + +func _LoadLibrary(libname *uint16) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procLoadLibraryW.Addr(), 1, uintptr(unsafe.Pointer(libname)), 0, 0) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func LoadLibraryEx(libname string, zero Handle, flags uintptr) (handle Handle, err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(libname) + if err != nil { + return + } + return _LoadLibraryEx(_p0, zero, flags) +} + +func _LoadLibraryEx(libname *uint16, zero Handle, flags uintptr) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procLoadLibraryExW.Addr(), 3, uintptr(unsafe.Pointer(libname)), uintptr(zero), uintptr(flags)) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FreeLibrary(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procFreeLibrary.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetProcAddress(module Handle, procname string) (proc uintptr, err error) { + var _p0 *byte + _p0, err = syscall.BytePtrFromString(procname) + if err != nil { + return + } + return _GetProcAddress(module, _p0) +} + +func _GetProcAddress(module Handle, procname *byte) (proc uintptr, err error) { + r0, _, e1 := syscall.Syscall(procGetProcAddress.Addr(), 2, uintptr(module), uintptr(unsafe.Pointer(procname)), 0) + proc = uintptr(r0) + if proc == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetVersion() (ver uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetVersion.Addr(), 0, 0, 0, 0) + ver = uint32(r0) + if ver == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FormatMessage(flags uint32, msgsrc uintptr, msgid uint32, langid uint32, buf []uint16, args *byte) (n uint32, err error) { + var _p0 *uint16 + if len(buf) > 0 { + _p0 = &buf[0] + } + r0, _, e1 := syscall.Syscall9(procFormatMessageW.Addr(), 7, uintptr(flags), uintptr(msgsrc), uintptr(msgid), uintptr(langid), uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), uintptr(unsafe.Pointer(args)), 0, 0) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ExitProcess(exitcode uint32) { + syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0) + return +} + +func CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile int32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0) + handle = Handle(r0) + if handle == InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ReadFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) (err error) { + var _p0 *byte + if len(buf) > 0 { + _p0 = &buf[0] + } + r1, _, e1 := syscall.Syscall6(procReadFile.Addr(), 5, uintptr(handle), uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), uintptr(unsafe.Pointer(done)), uintptr(unsafe.Pointer(overlapped)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WriteFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) (err error) { + var _p0 *byte + if len(buf) > 0 { + _p0 = &buf[0] + } + r1, _, e1 := syscall.Syscall6(procWriteFile.Addr(), 5, uintptr(handle), uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), uintptr(unsafe.Pointer(done)), uintptr(unsafe.Pointer(overlapped)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetFilePointer(handle Handle, lowoffset int32, highoffsetptr *int32, whence uint32) (newlowoffset uint32, err error) { + r0, _, e1 := syscall.Syscall6(procSetFilePointer.Addr(), 4, uintptr(handle), uintptr(lowoffset), uintptr(unsafe.Pointer(highoffsetptr)), uintptr(whence), 0, 0) + newlowoffset = uint32(r0) + if newlowoffset == 0xffffffff { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CloseHandle(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetStdHandle(stdhandle uint32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procGetStdHandle.Addr(), 1, uintptr(stdhandle), 0, 0) + handle = Handle(r0) + if handle == InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetStdHandle(stdhandle uint32, handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func findFirstFile1(name *uint16, data *win32finddata1) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procFindFirstFileW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(data)), 0) + handle = Handle(r0) + if handle == InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func findNextFile1(handle Handle, data *win32finddata1) (err error) { + r1, _, e1 := syscall.Syscall(procFindNextFileW.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(data)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FindClose(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procFindClose.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetFileInformationByHandle(handle Handle, data *ByHandleFileInformation) (err error) { + r1, _, e1 := syscall.Syscall(procGetFileInformationByHandle.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(data)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetCurrentDirectory(buflen uint32, buf *uint16) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetCurrentDirectoryW.Addr(), 2, uintptr(buflen), uintptr(unsafe.Pointer(buf)), 0) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetCurrentDirectory(path *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procSetCurrentDirectoryW.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateDirectory(path *uint16, sa *SecurityAttributes) (err error) { + r1, _, e1 := syscall.Syscall(procCreateDirectoryW.Addr(), 2, uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(sa)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func RemoveDirectory(path *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procRemoveDirectoryW.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func DeleteFile(path *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procDeleteFileW.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func MoveFile(from *uint16, to *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procMoveFileW.Addr(), 2, uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(to)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func MoveFileEx(from *uint16, to *uint16, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procMoveFileExW.Addr(), 3, uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(to)), uintptr(flags)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetComputerName(buf *uint16, n *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetComputerNameW.Addr(), 2, uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(n)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetComputerNameExW.Addr(), 3, uintptr(nametype), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(n))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetEndOfFile(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procSetEndOfFile.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetSystemTimeAsFileTime(time *Filetime) { + syscall.Syscall(procGetSystemTimeAsFileTime.Addr(), 1, uintptr(unsafe.Pointer(time)), 0, 0) + return +} + +func GetSystemTimePreciseAsFileTime(time *Filetime) { + syscall.Syscall(procGetSystemTimePreciseAsFileTime.Addr(), 1, uintptr(unsafe.Pointer(time)), 0, 0) + return +} + +func GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetTimeZoneInformation.Addr(), 1, uintptr(unsafe.Pointer(tzi)), 0, 0) + rc = uint32(r0) + if rc == 0xffffffff { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateIoCompletionPort(filehandle Handle, cphandle Handle, key uint32, threadcnt uint32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(filehandle), uintptr(cphandle), uintptr(key), uintptr(threadcnt), 0, 0) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetQueuedCompletionStatus(cphandle Handle, qty *uint32, key *uint32, overlapped **Overlapped, timeout uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(cphandle), uintptr(unsafe.Pointer(qty)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(overlapped)), uintptr(timeout), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func PostQueuedCompletionStatus(cphandle Handle, qty uint32, key uint32, overlapped *Overlapped) (err error) { + r1, _, e1 := syscall.Syscall6(procPostQueuedCompletionStatus.Addr(), 4, uintptr(cphandle), uintptr(qty), uintptr(key), uintptr(unsafe.Pointer(overlapped)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CancelIo(s Handle) (err error) { + r1, _, e1 := syscall.Syscall(procCancelIo.Addr(), 1, uintptr(s), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CancelIoEx(s Handle, o *Overlapped) (err error) { + r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(s), uintptr(unsafe.Pointer(o)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateProcess(appName *uint16, commandLine *uint16, procSecurity *SecurityAttributes, threadSecurity *SecurityAttributes, inheritHandles bool, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *StartupInfo, outProcInfo *ProcessInformation) (err error) { + var _p0 uint32 + if inheritHandles { + _p0 = 1 + } else { + _p0 = 0 + } + r1, _, e1 := syscall.Syscall12(procCreateProcessW.Addr(), 10, uintptr(unsafe.Pointer(appName)), uintptr(unsafe.Pointer(commandLine)), uintptr(unsafe.Pointer(procSecurity)), uintptr(unsafe.Pointer(threadSecurity)), uintptr(_p0), uintptr(creationFlags), uintptr(unsafe.Pointer(env)), uintptr(unsafe.Pointer(currentDir)), uintptr(unsafe.Pointer(startupInfo)), uintptr(unsafe.Pointer(outProcInfo)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func OpenProcess(da uint32, inheritHandle bool, pid uint32) (handle Handle, err error) { + var _p0 uint32 + if inheritHandle { + _p0 = 1 + } else { + _p0 = 0 + } + r0, _, e1 := syscall.Syscall(procOpenProcess.Addr(), 3, uintptr(da), uintptr(_p0), uintptr(pid)) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func TerminateProcess(handle Handle, exitcode uint32) (err error) { + r1, _, e1 := syscall.Syscall(procTerminateProcess.Addr(), 2, uintptr(handle), uintptr(exitcode), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetExitCodeProcess(handle Handle, exitcode *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetExitCodeProcess.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(exitcode)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetStartupInfo(startupInfo *StartupInfo) (err error) { + r1, _, e1 := syscall.Syscall(procGetStartupInfoW.Addr(), 1, uintptr(unsafe.Pointer(startupInfo)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetCurrentProcess() (pseudoHandle Handle, err error) { + r0, _, e1 := syscall.Syscall(procGetCurrentProcess.Addr(), 0, 0, 0, 0) + pseudoHandle = Handle(r0) + if pseudoHandle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetProcessTimes(handle Handle, creationTime *Filetime, exitTime *Filetime, kernelTime *Filetime, userTime *Filetime) (err error) { + r1, _, e1 := syscall.Syscall6(procGetProcessTimes.Addr(), 5, uintptr(handle), uintptr(unsafe.Pointer(creationTime)), uintptr(unsafe.Pointer(exitTime)), uintptr(unsafe.Pointer(kernelTime)), uintptr(unsafe.Pointer(userTime)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) { + var _p0 uint32 + if bInheritHandle { + _p0 = 1 + } else { + _p0 = 0 + } + r1, _, e1 := syscall.Syscall9(procDuplicateHandle.Addr(), 7, uintptr(hSourceProcessHandle), uintptr(hSourceHandle), uintptr(hTargetProcessHandle), uintptr(unsafe.Pointer(lpTargetHandle)), uintptr(dwDesiredAccess), uintptr(_p0), uintptr(dwOptions), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WaitForSingleObject(handle Handle, waitMilliseconds uint32) (event uint32, err error) { + r0, _, e1 := syscall.Syscall(procWaitForSingleObject.Addr(), 2, uintptr(handle), uintptr(waitMilliseconds), 0) + event = uint32(r0) + if event == 0xffffffff { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetTempPath(buflen uint32, buf *uint16) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetTempPathW.Addr(), 2, uintptr(buflen), uintptr(unsafe.Pointer(buf)), 0) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreatePipe(readhandle *Handle, writehandle *Handle, sa *SecurityAttributes, size uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procCreatePipe.Addr(), 4, uintptr(unsafe.Pointer(readhandle)), uintptr(unsafe.Pointer(writehandle)), uintptr(unsafe.Pointer(sa)), uintptr(size), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetFileType(filehandle Handle) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetFileType.Addr(), 1, uintptr(filehandle), 0, 0) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CryptAcquireContext(provhandle *Handle, container *uint16, provider *uint16, provtype uint32, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procCryptAcquireContextW.Addr(), 5, uintptr(unsafe.Pointer(provhandle)), uintptr(unsafe.Pointer(container)), uintptr(unsafe.Pointer(provider)), uintptr(provtype), uintptr(flags), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CryptReleaseContext(provhandle Handle, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procCryptReleaseContext.Addr(), 2, uintptr(provhandle), uintptr(flags), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CryptGenRandom(provhandle Handle, buflen uint32, buf *byte) (err error) { + r1, _, e1 := syscall.Syscall(procCryptGenRandom.Addr(), 3, uintptr(provhandle), uintptr(buflen), uintptr(unsafe.Pointer(buf))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetEnvironmentStrings() (envs *uint16, err error) { + r0, _, e1 := syscall.Syscall(procGetEnvironmentStringsW.Addr(), 0, 0, 0, 0) + envs = (*uint16)(unsafe.Pointer(r0)) + if envs == nil { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FreeEnvironmentStrings(envs *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procFreeEnvironmentStringsW.Addr(), 1, uintptr(unsafe.Pointer(envs)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetEnvironmentVariable(name *uint16, buffer *uint16, size uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetEnvironmentVariableW.Addr(), 3, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(size)) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetEnvironmentVariable(name *uint16, value *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procSetEnvironmentVariableW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error) { + r1, _, e1 := syscall.Syscall6(procSetFileTime.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(ctime)), uintptr(unsafe.Pointer(atime)), uintptr(unsafe.Pointer(wtime)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetFileAttributes(name *uint16) (attrs uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetFileAttributesW.Addr(), 1, uintptr(unsafe.Pointer(name)), 0, 0) + attrs = uint32(r0) + if attrs == INVALID_FILE_ATTRIBUTES { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetFileAttributes(name *uint16, attrs uint32) (err error) { + r1, _, e1 := syscall.Syscall(procSetFileAttributesW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(attrs), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetFileAttributesEx(name *uint16, level uint32, info *byte) (err error) { + r1, _, e1 := syscall.Syscall(procGetFileAttributesExW.Addr(), 3, uintptr(unsafe.Pointer(name)), uintptr(level), uintptr(unsafe.Pointer(info))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetCommandLine() (cmd *uint16) { + r0, _, _ := syscall.Syscall(procGetCommandLineW.Addr(), 0, 0, 0, 0) + cmd = (*uint16)(unsafe.Pointer(r0)) + return +} + +func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) { + r0, _, e1 := syscall.Syscall(procCommandLineToArgvW.Addr(), 2, uintptr(unsafe.Pointer(cmd)), uintptr(unsafe.Pointer(argc)), 0) + argv = (*[8192]*[8192]uint16)(unsafe.Pointer(r0)) + if argv == nil { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func LocalFree(hmem Handle) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procLocalFree.Addr(), 1, uintptr(hmem), 0, 0) + handle = Handle(r0) + if handle != 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FlushFileBuffers(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procFlushFileBuffers.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetFullPathName(path *uint16, buflen uint32, buf *uint16, fname **uint16) (n uint32, err error) { + r0, _, e1 := syscall.Syscall6(procGetFullPathNameW.Addr(), 4, uintptr(unsafe.Pointer(path)), uintptr(buflen), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(fname)), 0, 0) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetLongPathName(path *uint16, buf *uint16, buflen uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetLongPathNameW.Addr(), 3, uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(buf)), uintptr(buflen)) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetShortPathName(longpath *uint16, shortpath *uint16, buflen uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetShortPathNameW.Addr(), 3, uintptr(unsafe.Pointer(longpath)), uintptr(unsafe.Pointer(shortpath)), uintptr(buflen)) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateFileMapping(fhandle Handle, sa *SecurityAttributes, prot uint32, maxSizeHigh uint32, maxSizeLow uint32, name *uint16) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall6(procCreateFileMappingW.Addr(), 6, uintptr(fhandle), uintptr(unsafe.Pointer(sa)), uintptr(prot), uintptr(maxSizeHigh), uintptr(maxSizeLow), uintptr(unsafe.Pointer(name))) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func MapViewOfFile(handle Handle, access uint32, offsetHigh uint32, offsetLow uint32, length uintptr) (addr uintptr, err error) { + r0, _, e1 := syscall.Syscall6(procMapViewOfFile.Addr(), 5, uintptr(handle), uintptr(access), uintptr(offsetHigh), uintptr(offsetLow), uintptr(length), 0) + addr = uintptr(r0) + if addr == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func UnmapViewOfFile(addr uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procUnmapViewOfFile.Addr(), 1, uintptr(addr), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FlushViewOfFile(addr uintptr, length uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procFlushViewOfFile.Addr(), 2, uintptr(addr), uintptr(length), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func VirtualLock(addr uintptr, length uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procVirtualLock.Addr(), 2, uintptr(addr), uintptr(length), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func VirtualUnlock(addr uintptr, length uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procVirtualUnlock.Addr(), 2, uintptr(addr), uintptr(length), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func VirtualAlloc(address uintptr, size uintptr, alloctype uint32, protect uint32) (value uintptr, err error) { + r0, _, e1 := syscall.Syscall6(procVirtualAlloc.Addr(), 4, uintptr(address), uintptr(size), uintptr(alloctype), uintptr(protect), 0, 0) + value = uintptr(r0) + if value == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func VirtualFree(address uintptr, size uintptr, freetype uint32) (err error) { + r1, _, e1 := syscall.Syscall(procVirtualFree.Addr(), 3, uintptr(address), uintptr(size), uintptr(freetype)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func VirtualProtect(address uintptr, size uintptr, newprotect uint32, oldprotect *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procVirtualProtect.Addr(), 4, uintptr(address), uintptr(size), uintptr(newprotect), uintptr(unsafe.Pointer(oldprotect)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func TransmitFile(s Handle, handle Handle, bytesToWrite uint32, bytsPerSend uint32, overlapped *Overlapped, transmitFileBuf *TransmitFileBuffers, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procTransmitFile.Addr(), 7, uintptr(s), uintptr(handle), uintptr(bytesToWrite), uintptr(bytsPerSend), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(transmitFileBuf)), uintptr(flags), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ReadDirectoryChanges(handle Handle, buf *byte, buflen uint32, watchSubTree bool, mask uint32, retlen *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) { + var _p0 uint32 + if watchSubTree { + _p0 = 1 + } else { + _p0 = 0 + } + r1, _, e1 := syscall.Syscall9(procReadDirectoryChangesW.Addr(), 8, uintptr(handle), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(_p0), uintptr(mask), uintptr(unsafe.Pointer(retlen)), uintptr(unsafe.Pointer(overlapped)), uintptr(completionRoutine), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertOpenSystemStore(hprov Handle, name *uint16) (store Handle, err error) { + r0, _, e1 := syscall.Syscall(procCertOpenSystemStoreW.Addr(), 2, uintptr(hprov), uintptr(unsafe.Pointer(name)), 0) + store = Handle(r0) + if store == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertOpenStore(storeProvider uintptr, msgAndCertEncodingType uint32, cryptProv uintptr, flags uint32, para uintptr) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall6(procCertOpenStore.Addr(), 5, uintptr(storeProvider), uintptr(msgAndCertEncodingType), uintptr(cryptProv), uintptr(flags), uintptr(para), 0) + handle = Handle(r0) + if handle == InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext, err error) { + r0, _, e1 := syscall.Syscall(procCertEnumCertificatesInStore.Addr(), 2, uintptr(store), uintptr(unsafe.Pointer(prevContext)), 0) + context = (*CertContext)(unsafe.Pointer(r0)) + if context == nil { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertAddCertificateContextToStore(store Handle, certContext *CertContext, addDisposition uint32, storeContext **CertContext) (err error) { + r1, _, e1 := syscall.Syscall6(procCertAddCertificateContextToStore.Addr(), 4, uintptr(store), uintptr(unsafe.Pointer(certContext)), uintptr(addDisposition), uintptr(unsafe.Pointer(storeContext)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertCloseStore(store Handle, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procCertCloseStore.Addr(), 2, uintptr(store), uintptr(flags), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertGetCertificateChain(engine Handle, leaf *CertContext, time *Filetime, additionalStore Handle, para *CertChainPara, flags uint32, reserved uintptr, chainCtx **CertChainContext) (err error) { + r1, _, e1 := syscall.Syscall9(procCertGetCertificateChain.Addr(), 8, uintptr(engine), uintptr(unsafe.Pointer(leaf)), uintptr(unsafe.Pointer(time)), uintptr(additionalStore), uintptr(unsafe.Pointer(para)), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(chainCtx)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertFreeCertificateChain(ctx *CertChainContext) { + syscall.Syscall(procCertFreeCertificateChain.Addr(), 1, uintptr(unsafe.Pointer(ctx)), 0, 0) + return +} + +func CertCreateCertificateContext(certEncodingType uint32, certEncoded *byte, encodedLen uint32) (context *CertContext, err error) { + r0, _, e1 := syscall.Syscall(procCertCreateCertificateContext.Addr(), 3, uintptr(certEncodingType), uintptr(unsafe.Pointer(certEncoded)), uintptr(encodedLen)) + context = (*CertContext)(unsafe.Pointer(r0)) + if context == nil { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertFreeCertificateContext(ctx *CertContext) (err error) { + r1, _, e1 := syscall.Syscall(procCertFreeCertificateContext.Addr(), 1, uintptr(unsafe.Pointer(ctx)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CertVerifyCertificateChainPolicy(policyOID uintptr, chain *CertChainContext, para *CertChainPolicyPara, status *CertChainPolicyStatus) (err error) { + r1, _, e1 := syscall.Syscall6(procCertVerifyCertificateChainPolicy.Addr(), 4, uintptr(policyOID), uintptr(unsafe.Pointer(chain)), uintptr(unsafe.Pointer(para)), uintptr(unsafe.Pointer(status)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func RegOpenKeyEx(key Handle, subkey *uint16, options uint32, desiredAccess uint32, result *Handle) (regerrno error) { + r0, _, _ := syscall.Syscall6(procRegOpenKeyExW.Addr(), 5, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(options), uintptr(desiredAccess), uintptr(unsafe.Pointer(result)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func RegCloseKey(key Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegCloseKey.Addr(), 1, uintptr(key), 0, 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func RegQueryInfoKey(key Handle, class *uint16, classLen *uint32, reserved *uint32, subkeysLen *uint32, maxSubkeyLen *uint32, maxClassLen *uint32, valuesLen *uint32, maxValueNameLen *uint32, maxValueLen *uint32, saLen *uint32, lastWriteTime *Filetime) (regerrno error) { + r0, _, _ := syscall.Syscall12(procRegQueryInfoKeyW.Addr(), 12, uintptr(key), uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(classLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(subkeysLen)), uintptr(unsafe.Pointer(maxSubkeyLen)), uintptr(unsafe.Pointer(maxClassLen)), uintptr(unsafe.Pointer(valuesLen)), uintptr(unsafe.Pointer(maxValueNameLen)), uintptr(unsafe.Pointer(maxValueLen)), uintptr(unsafe.Pointer(saLen)), uintptr(unsafe.Pointer(lastWriteTime))) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func RegEnumKeyEx(key Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, classLen *uint32, lastWriteTime *Filetime) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegEnumKeyExW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(class)), uintptr(unsafe.Pointer(classLen)), uintptr(unsafe.Pointer(lastWriteTime)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func RegQueryValueEx(key Handle, name *uint16, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) { + r0, _, _ := syscall.Syscall6(procRegQueryValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen))) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func getCurrentProcessId() (pid uint32) { + r0, _, _ := syscall.Syscall(procGetCurrentProcessId.Addr(), 0, 0, 0, 0) + pid = uint32(r0) + return +} + +func GetConsoleMode(console Handle, mode *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(mode)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetConsoleMode(console Handle, mode uint32) (err error) { + r1, _, e1 := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(console), uintptr(mode), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) { + r1, _, e1 := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(info)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) { + r1, _, e1 := syscall.Syscall6(procWriteConsoleW.Addr(), 5, uintptr(console), uintptr(unsafe.Pointer(buf)), uintptr(towrite), uintptr(unsafe.Pointer(written)), uintptr(unsafe.Pointer(reserved)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) { + r1, _, e1 := syscall.Syscall6(procReadConsoleW.Addr(), 5, uintptr(console), uintptr(unsafe.Pointer(buf)), uintptr(toread), uintptr(unsafe.Pointer(read)), uintptr(unsafe.Pointer(inputControl)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procCreateToolhelp32Snapshot.Addr(), 2, uintptr(flags), uintptr(processId), 0) + handle = Handle(r0) + if handle == InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func Process32First(snapshot Handle, procEntry *ProcessEntry32) (err error) { + r1, _, e1 := syscall.Syscall(procProcess32FirstW.Addr(), 2, uintptr(snapshot), uintptr(unsafe.Pointer(procEntry)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error) { + r1, _, e1 := syscall.Syscall(procProcess32NextW.Addr(), 2, uintptr(snapshot), uintptr(unsafe.Pointer(procEntry)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBufferSize uint32, outBuffer *byte, outBufferSize uint32, bytesReturned *uint32, overlapped *Overlapped) (err error) { + r1, _, e1 := syscall.Syscall9(procDeviceIoControl.Addr(), 8, uintptr(handle), uintptr(ioControlCode), uintptr(unsafe.Pointer(inBuffer)), uintptr(inBufferSize), uintptr(unsafe.Pointer(outBuffer)), uintptr(outBufferSize), uintptr(unsafe.Pointer(bytesReturned)), uintptr(unsafe.Pointer(overlapped)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16, flags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procCreateSymbolicLinkW.Addr(), 3, uintptr(unsafe.Pointer(symlinkfilename)), uintptr(unsafe.Pointer(targetfilename)), uintptr(flags)) + if r1&0xff == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateHardLink(filename *uint16, existingfilename *uint16, reserved uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procCreateHardLinkW.Addr(), 3, uintptr(unsafe.Pointer(filename)), uintptr(unsafe.Pointer(existingfilename)), uintptr(reserved)) + if r1&0xff == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetCurrentThreadId() (id uint32) { + r0, _, _ := syscall.Syscall(procGetCurrentThreadId.Addr(), 0, 0, 0, 0) + id = uint32(r0) + return +} + +func CreateEvent(eventAttrs *SecurityAttributes, manualReset uint32, initialState uint32, name *uint16) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(eventAttrs)), uintptr(manualReset), uintptr(initialState), uintptr(unsafe.Pointer(name)), 0, 0) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func CreateEventEx(eventAttrs *SecurityAttributes, name *uint16, flags uint32, desiredAccess uint32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall6(procCreateEventExW.Addr(), 4, uintptr(unsafe.Pointer(eventAttrs)), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(desiredAccess), 0, 0) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func OpenEvent(desiredAccess uint32, inheritHandle bool, name *uint16) (handle Handle, err error) { + var _p0 uint32 + if inheritHandle { + _p0 = 1 + } else { + _p0 = 0 + } + r0, _, e1 := syscall.Syscall(procOpenEventW.Addr(), 3, uintptr(desiredAccess), uintptr(_p0), uintptr(unsafe.Pointer(name))) + handle = Handle(r0) + if handle == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetEvent(event Handle) (err error) { + r1, _, e1 := syscall.Syscall(procSetEvent.Addr(), 1, uintptr(event), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ResetEvent(event Handle) (err error) { + r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(event), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func PulseEvent(event Handle) (err error) { + r1, _, e1 := syscall.Syscall(procPulseEvent.Addr(), 1, uintptr(event), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func DefineDosDevice(flags uint32, deviceName *uint16, targetPath *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procDefineDosDeviceW.Addr(), 3, uintptr(flags), uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func DeleteVolumeMountPoint(volumeMountPoint *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procDeleteVolumeMountPointW.Addr(), 1, uintptr(unsafe.Pointer(volumeMountPoint)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FindFirstVolume(volumeName *uint16, bufferLength uint32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procFindFirstVolumeW.Addr(), 2, uintptr(unsafe.Pointer(volumeName)), uintptr(bufferLength), 0) + handle = Handle(r0) + if handle == InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FindFirstVolumeMountPoint(rootPathName *uint16, volumeMountPoint *uint16, bufferLength uint32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procFindFirstVolumeMountPointW.Addr(), 3, uintptr(unsafe.Pointer(rootPathName)), uintptr(unsafe.Pointer(volumeMountPoint)), uintptr(bufferLength)) + handle = Handle(r0) + if handle == InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FindNextVolume(findVolume Handle, volumeName *uint16, bufferLength uint32) (err error) { + r1, _, e1 := syscall.Syscall(procFindNextVolumeW.Addr(), 3, uintptr(findVolume), uintptr(unsafe.Pointer(volumeName)), uintptr(bufferLength)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FindNextVolumeMountPoint(findVolumeMountPoint Handle, volumeMountPoint *uint16, bufferLength uint32) (err error) { + r1, _, e1 := syscall.Syscall(procFindNextVolumeMountPointW.Addr(), 3, uintptr(findVolumeMountPoint), uintptr(unsafe.Pointer(volumeMountPoint)), uintptr(bufferLength)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FindVolumeClose(findVolume Handle) (err error) { + r1, _, e1 := syscall.Syscall(procFindVolumeClose.Addr(), 1, uintptr(findVolume), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FindVolumeMountPointClose(findVolumeMountPoint Handle) (err error) { + r1, _, e1 := syscall.Syscall(procFindVolumeMountPointClose.Addr(), 1, uintptr(findVolumeMountPoint), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetDriveType(rootPathName *uint16) (driveType uint32) { + r0, _, _ := syscall.Syscall(procGetDriveTypeW.Addr(), 1, uintptr(unsafe.Pointer(rootPathName)), 0, 0) + driveType = uint32(r0) + return +} + +func GetLogicalDrives() (drivesBitMask uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetLogicalDrives.Addr(), 0, 0, 0, 0) + drivesBitMask = uint32(r0) + if drivesBitMask == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetLogicalDriveStrings(bufferLength uint32, buffer *uint16) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetLogicalDriveStringsW.Addr(), 2, uintptr(bufferLength), uintptr(unsafe.Pointer(buffer)), 0) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetVolumeInformation(rootPathName *uint16, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procGetVolumeInformationW.Addr(), 8, uintptr(unsafe.Pointer(rootPathName)), uintptr(unsafe.Pointer(volumeNameBuffer)), uintptr(volumeNameSize), uintptr(unsafe.Pointer(volumeNameSerialNumber)), uintptr(unsafe.Pointer(maximumComponentLength)), uintptr(unsafe.Pointer(fileSystemFlags)), uintptr(unsafe.Pointer(fileSystemNameBuffer)), uintptr(fileSystemNameSize), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetVolumeInformationByHandle(file Handle, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procGetVolumeInformationByHandleW.Addr(), 8, uintptr(file), uintptr(unsafe.Pointer(volumeNameBuffer)), uintptr(volumeNameSize), uintptr(unsafe.Pointer(volumeNameSerialNumber)), uintptr(unsafe.Pointer(maximumComponentLength)), uintptr(unsafe.Pointer(fileSystemFlags)), uintptr(unsafe.Pointer(fileSystemNameBuffer)), uintptr(fileSystemNameSize), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetVolumeNameForVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16, bufferlength uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetVolumeNameForVolumeMountPointW.Addr(), 3, uintptr(unsafe.Pointer(volumeMountPoint)), uintptr(unsafe.Pointer(volumeName)), uintptr(bufferlength)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetVolumePathName(fileName *uint16, volumePathName *uint16, bufferLength uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetVolumePathNameW.Addr(), 3, uintptr(unsafe.Pointer(fileName)), uintptr(unsafe.Pointer(volumePathName)), uintptr(bufferLength)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetVolumePathNamesForVolumeName(volumeName *uint16, volumePathNames *uint16, bufferLength uint32, returnLength *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetVolumePathNamesForVolumeNameW.Addr(), 4, uintptr(unsafe.Pointer(volumeName)), uintptr(unsafe.Pointer(volumePathNames)), uintptr(bufferLength), uintptr(unsafe.Pointer(returnLength)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max)) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetVolumeLabel(rootPathName *uint16, volumeName *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procSetVolumeLabelW.Addr(), 2, uintptr(unsafe.Pointer(rootPathName)), uintptr(unsafe.Pointer(volumeName)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err error) { + r1, _, e1 := syscall.Syscall(procSetVolumeMountPointW.Addr(), 2, uintptr(unsafe.Pointer(volumeMountPoint)), uintptr(unsafe.Pointer(volumeName)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WSAStartup(verreq uint32, data *WSAData) (sockerr error) { + r0, _, _ := syscall.Syscall(procWSAStartup.Addr(), 2, uintptr(verreq), uintptr(unsafe.Pointer(data)), 0) + if r0 != 0 { + sockerr = syscall.Errno(r0) + } + return +} + +func WSACleanup() (err error) { + r1, _, e1 := syscall.Syscall(procWSACleanup.Addr(), 0, 0, 0, 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbob uint32, cbbr *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) { + r1, _, e1 := syscall.Syscall9(procWSAIoctl.Addr(), 9, uintptr(s), uintptr(iocc), uintptr(unsafe.Pointer(inbuf)), uintptr(cbif), uintptr(unsafe.Pointer(outbuf)), uintptr(cbob), uintptr(unsafe.Pointer(cbbr)), uintptr(unsafe.Pointer(overlapped)), uintptr(completionRoutine)) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func socket(af int32, typ int32, protocol int32) (handle Handle, err error) { + r0, _, e1 := syscall.Syscall(procsocket.Addr(), 3, uintptr(af), uintptr(typ), uintptr(protocol)) + handle = Handle(r0) + if handle == InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func Setsockopt(s Handle, level int32, optname int32, optval *byte, optlen int32) (err error) { + r1, _, e1 := syscall.Syscall6(procsetsockopt.Addr(), 5, uintptr(s), uintptr(level), uintptr(optname), uintptr(unsafe.Pointer(optval)), uintptr(optlen), 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) { + r1, _, e1 := syscall.Syscall6(procgetsockopt.Addr(), 5, uintptr(s), uintptr(level), uintptr(optname), uintptr(unsafe.Pointer(optval)), uintptr(unsafe.Pointer(optlen)), 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func bind(s Handle, name unsafe.Pointer, namelen int32) (err error) { + r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen)) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func connect(s Handle, name unsafe.Pointer, namelen int32) (err error) { + r1, _, e1 := syscall.Syscall(procconnect.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen)) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func getsockname(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) { + r1, _, e1 := syscall.Syscall(procgetsockname.Addr(), 3, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen))) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func getpeername(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) { + r1, _, e1 := syscall.Syscall(procgetpeername.Addr(), 3, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen))) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func listen(s Handle, backlog int32) (err error) { + r1, _, e1 := syscall.Syscall(proclisten.Addr(), 2, uintptr(s), uintptr(backlog), 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func shutdown(s Handle, how int32) (err error) { + r1, _, e1 := syscall.Syscall(procshutdown.Addr(), 2, uintptr(s), uintptr(how), 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func Closesocket(s Handle) (err error) { + r1, _, e1 := syscall.Syscall(procclosesocket.Addr(), 1, uintptr(s), 0, 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func AcceptEx(ls Handle, as Handle, buf *byte, rxdatalen uint32, laddrlen uint32, raddrlen uint32, recvd *uint32, overlapped *Overlapped) (err error) { + r1, _, e1 := syscall.Syscall9(procAcceptEx.Addr(), 8, uintptr(ls), uintptr(as), uintptr(unsafe.Pointer(buf)), uintptr(rxdatalen), uintptr(laddrlen), uintptr(raddrlen), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(overlapped)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetAcceptExSockaddrs(buf *byte, rxdatalen uint32, laddrlen uint32, raddrlen uint32, lrsa **RawSockaddrAny, lrsalen *int32, rrsa **RawSockaddrAny, rrsalen *int32) { + syscall.Syscall9(procGetAcceptExSockaddrs.Addr(), 8, uintptr(unsafe.Pointer(buf)), uintptr(rxdatalen), uintptr(laddrlen), uintptr(raddrlen), uintptr(unsafe.Pointer(lrsa)), uintptr(unsafe.Pointer(lrsalen)), uintptr(unsafe.Pointer(rrsa)), uintptr(unsafe.Pointer(rrsalen)), 0) + return +} + +func WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) { + r1, _, e1 := syscall.Syscall9(procWSARecv.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WSASend(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, overlapped *Overlapped, croutine *byte) (err error) { + r1, _, e1 := syscall.Syscall9(procWSASend.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(sent)), uintptr(flags), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WSARecvFrom(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, from *RawSockaddrAny, fromlen *int32, overlapped *Overlapped, croutine *byte) (err error) { + r1, _, e1 := syscall.Syscall9(procWSARecvFrom.Addr(), 9, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(from)), uintptr(unsafe.Pointer(fromlen)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine))) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WSASendTo(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, to *RawSockaddrAny, tolen int32, overlapped *Overlapped, croutine *byte) (err error) { + r1, _, e1 := syscall.Syscall9(procWSASendTo.Addr(), 9, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(sent)), uintptr(flags), uintptr(unsafe.Pointer(to)), uintptr(tolen), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine))) + if r1 == socket_error { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetHostByName(name string) (h *Hostent, err error) { + var _p0 *byte + _p0, err = syscall.BytePtrFromString(name) + if err != nil { + return + } + return _GetHostByName(_p0) +} + +func _GetHostByName(name *byte) (h *Hostent, err error) { + r0, _, e1 := syscall.Syscall(procgethostbyname.Addr(), 1, uintptr(unsafe.Pointer(name)), 0, 0) + h = (*Hostent)(unsafe.Pointer(r0)) + if h == nil { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetServByName(name string, proto string) (s *Servent, err error) { + var _p0 *byte + _p0, err = syscall.BytePtrFromString(name) + if err != nil { + return + } + var _p1 *byte + _p1, err = syscall.BytePtrFromString(proto) + if err != nil { + return + } + return _GetServByName(_p0, _p1) +} + +func _GetServByName(name *byte, proto *byte) (s *Servent, err error) { + r0, _, e1 := syscall.Syscall(procgetservbyname.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(proto)), 0) + s = (*Servent)(unsafe.Pointer(r0)) + if s == nil { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func Ntohs(netshort uint16) (u uint16) { + r0, _, _ := syscall.Syscall(procntohs.Addr(), 1, uintptr(netshort), 0, 0) + u = uint16(r0) + return +} + +func GetProtoByName(name string) (p *Protoent, err error) { + var _p0 *byte + _p0, err = syscall.BytePtrFromString(name) + if err != nil { + return + } + return _GetProtoByName(_p0) +} + +func _GetProtoByName(name *byte) (p *Protoent, err error) { + r0, _, e1 := syscall.Syscall(procgetprotobyname.Addr(), 1, uintptr(unsafe.Pointer(name)), 0, 0) + p = (*Protoent)(unsafe.Pointer(r0)) + if p == nil { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func DnsQuery(name string, qtype uint16, options uint32, extra *byte, qrs **DNSRecord, pr *byte) (status error) { + var _p0 *uint16 + _p0, status = syscall.UTF16PtrFromString(name) + if status != nil { + return + } + return _DnsQuery(_p0, qtype, options, extra, qrs, pr) +} + +func _DnsQuery(name *uint16, qtype uint16, options uint32, extra *byte, qrs **DNSRecord, pr *byte) (status error) { + r0, _, _ := syscall.Syscall6(procDnsQuery_W.Addr(), 6, uintptr(unsafe.Pointer(name)), uintptr(qtype), uintptr(options), uintptr(unsafe.Pointer(extra)), uintptr(unsafe.Pointer(qrs)), uintptr(unsafe.Pointer(pr))) + if r0 != 0 { + status = syscall.Errno(r0) + } + return +} + +func DnsRecordListFree(rl *DNSRecord, freetype uint32) { + syscall.Syscall(procDnsRecordListFree.Addr(), 2, uintptr(unsafe.Pointer(rl)), uintptr(freetype), 0) + return +} + +func DnsNameCompare(name1 *uint16, name2 *uint16) (same bool) { + r0, _, _ := syscall.Syscall(procDnsNameCompare_W.Addr(), 2, uintptr(unsafe.Pointer(name1)), uintptr(unsafe.Pointer(name2)), 0) + same = r0 != 0 + return +} + +func GetAddrInfoW(nodename *uint16, servicename *uint16, hints *AddrinfoW, result **AddrinfoW) (sockerr error) { + r0, _, _ := syscall.Syscall6(procGetAddrInfoW.Addr(), 4, uintptr(unsafe.Pointer(nodename)), uintptr(unsafe.Pointer(servicename)), uintptr(unsafe.Pointer(hints)), uintptr(unsafe.Pointer(result)), 0, 0) + if r0 != 0 { + sockerr = syscall.Errno(r0) + } + return +} + +func FreeAddrInfoW(addrinfo *AddrinfoW) { + syscall.Syscall(procFreeAddrInfoW.Addr(), 1, uintptr(unsafe.Pointer(addrinfo)), 0, 0) + return +} + +func GetIfEntry(pIfRow *MibIfRow) (errcode error) { + r0, _, _ := syscall.Syscall(procGetIfEntry.Addr(), 1, uintptr(unsafe.Pointer(pIfRow)), 0, 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + +func GetAdaptersInfo(ai *IpAdapterInfo, ol *uint32) (errcode error) { + r0, _, _ := syscall.Syscall(procGetAdaptersInfo.Addr(), 2, uintptr(unsafe.Pointer(ai)), uintptr(unsafe.Pointer(ol)), 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + +func SetFileCompletionNotificationModes(handle Handle, flags uint8) (err error) { + r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(handle), uintptr(flags), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func WSAEnumProtocols(protocols *int32, protocolBuffer *WSAProtocolInfo, bufferLength *uint32) (n int32, err error) { + r0, _, e1 := syscall.Syscall(procWSAEnumProtocolsW.Addr(), 3, uintptr(unsafe.Pointer(protocols)), uintptr(unsafe.Pointer(protocolBuffer)), uintptr(unsafe.Pointer(bufferLength))) + n = int32(r0) + if n == -1 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) { + r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)), 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + +func GetACP() (acp uint32) { + r0, _, _ := syscall.Syscall(procGetACP.Addr(), 0, 0, 0, 0) + acp = uint32(r0) + return +} + +func MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) { + r0, _, e1 := syscall.Syscall6(procMultiByteToWideChar.Addr(), 6, uintptr(codePage), uintptr(dwFlags), uintptr(unsafe.Pointer(str)), uintptr(nstr), uintptr(unsafe.Pointer(wchar)), uintptr(nwchar)) + nwrite = int32(r0) + if nwrite == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func TranslateName(accName *uint16, accNameFormat uint32, desiredNameFormat uint32, translatedName *uint16, nSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procTranslateNameW.Addr(), 5, uintptr(unsafe.Pointer(accName)), uintptr(accNameFormat), uintptr(desiredNameFormat), uintptr(unsafe.Pointer(translatedName)), uintptr(unsafe.Pointer(nSize)), 0) + if r1&0xff == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetUserNameEx(nameFormat uint32, nameBuffre *uint16, nSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetUserNameExW.Addr(), 3, uintptr(nameFormat), uintptr(unsafe.Pointer(nameBuffre)), uintptr(unsafe.Pointer(nSize))) + if r1&0xff == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) { + r0, _, _ := syscall.Syscall6(procNetUserGetInfo.Addr(), 4, uintptr(unsafe.Pointer(serverName)), uintptr(unsafe.Pointer(userName)), uintptr(level), uintptr(unsafe.Pointer(buf)), 0, 0) + if r0 != 0 { + neterr = syscall.Errno(r0) + } + return +} + +func NetGetJoinInformation(server *uint16, name **uint16, bufType *uint32) (neterr error) { + r0, _, _ := syscall.Syscall(procNetGetJoinInformation.Addr(), 3, uintptr(unsafe.Pointer(server)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(bufType))) + if r0 != 0 { + neterr = syscall.Errno(r0) + } + return +} + +func NetApiBufferFree(buf *byte) (neterr error) { + r0, _, _ := syscall.Syscall(procNetApiBufferFree.Addr(), 1, uintptr(unsafe.Pointer(buf)), 0, 0) + if r0 != 0 { + neterr = syscall.Errno(r0) + } + return +} + +func LookupAccountSid(systemName *uint16, sid *SID, name *uint16, nameLen *uint32, refdDomainName *uint16, refdDomainNameLen *uint32, use *uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procLookupAccountSidW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(refdDomainName)), uintptr(unsafe.Pointer(refdDomainNameLen)), uintptr(unsafe.Pointer(use)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func LookupAccountName(systemName *uint16, accountName *uint16, sid *SID, sidLen *uint32, refdDomainName *uint16, refdDomainNameLen *uint32, use *uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidLen)), uintptr(unsafe.Pointer(refdDomainName)), uintptr(unsafe.Pointer(refdDomainNameLen)), uintptr(unsafe.Pointer(use)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ConvertSidToStringSid(sid *SID, stringSid **uint16) (err error) { + r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(stringSid)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func ConvertStringSidToSid(stringSid *uint16, sid **SID) (err error) { + r1, _, e1 := syscall.Syscall(procConvertStringSidToSidW.Addr(), 2, uintptr(unsafe.Pointer(stringSid)), uintptr(unsafe.Pointer(sid)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetLengthSid(sid *SID) (len uint32) { + r0, _, _ := syscall.Syscall(procGetLengthSid.Addr(), 1, uintptr(unsafe.Pointer(sid)), 0, 0) + len = uint32(r0) + return +} + +func CopySid(destSidLen uint32, destSid *SID, srcSid *SID) (err error) { + r1, _, e1 := syscall.Syscall(procCopySid.Addr(), 3, uintptr(destSidLen), uintptr(unsafe.Pointer(destSid)), uintptr(unsafe.Pointer(srcSid))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func AllocateAndInitializeSid(identAuth *SidIdentifierAuthority, subAuth byte, subAuth0 uint32, subAuth1 uint32, subAuth2 uint32, subAuth3 uint32, subAuth4 uint32, subAuth5 uint32, subAuth6 uint32, subAuth7 uint32, sid **SID) (err error) { + r1, _, e1 := syscall.Syscall12(procAllocateAndInitializeSid.Addr(), 11, uintptr(unsafe.Pointer(identAuth)), uintptr(subAuth), uintptr(subAuth0), uintptr(subAuth1), uintptr(subAuth2), uintptr(subAuth3), uintptr(subAuth4), uintptr(subAuth5), uintptr(subAuth6), uintptr(subAuth7), uintptr(unsafe.Pointer(sid)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func FreeSid(sid *SID) (err error) { + r1, _, e1 := syscall.Syscall(procFreeSid.Addr(), 1, uintptr(unsafe.Pointer(sid)), 0, 0) + if r1 != 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func EqualSid(sid1 *SID, sid2 *SID) (isEqual bool) { + r0, _, _ := syscall.Syscall(procEqualSid.Addr(), 2, uintptr(unsafe.Pointer(sid1)), uintptr(unsafe.Pointer(sid2)), 0) + isEqual = r0 != 0 + return +} + +func checkTokenMembership(tokenHandle Token, sidToCheck *SID, isMember *int32) (err error) { + r1, _, e1 := syscall.Syscall(procCheckTokenMembership.Addr(), 3, uintptr(tokenHandle), uintptr(unsafe.Pointer(sidToCheck)), uintptr(unsafe.Pointer(isMember))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func OpenProcessToken(h Handle, access uint32, token *Token) (err error) { + r1, _, e1 := syscall.Syscall(procOpenProcessToken.Addr(), 3, uintptr(h), uintptr(access), uintptr(unsafe.Pointer(token))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetTokenInformation(t Token, infoClass uint32, info *byte, infoLen uint32, returnedLen *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetTokenInformation.Addr(), 5, uintptr(t), uintptr(infoClass), uintptr(unsafe.Pointer(info)), uintptr(infoLen), uintptr(unsafe.Pointer(returnedLen)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetUserProfileDirectoryW.Addr(), 3, uintptr(t), uintptr(unsafe.Pointer(dir)), uintptr(unsafe.Pointer(dirLen))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/vendor/gopkg.in/xmlpath.v2/LICENSE b/vendor/gopkg.in/xmlpath.v2/LICENSE new file mode 100644 index 0000000..4ccd784 --- /dev/null +++ b/vendor/gopkg.in/xmlpath.v2/LICENSE @@ -0,0 +1,187 @@ +Copyright (c) 2013-2014 Canonical Inc. + +This software is licensed under the LGPLv3, included below. + +As a special exception to the GNU Lesser General Public License version 3 +("LGPL3"), the copyright holders of this Library give you permission to +convey to a third party a Combined Work that links statically or dynamically +to this Library without providing any Minimal Corresponding Source or +Minimal Application Code as set out in 4d or providing the installation +information set out in section 4e, provided that you comply with the other +provisions of LGPL3 and provided that you meet, for the Application the +terms and conditions of the license(s) which apply to the Application. + +Except as stated in this special exception, the provisions of LGPL3 will +continue to comply in full to this Library. If you modify this Library, you +may apply this exception to your version of this Library, but you are not +obliged to do so. If you do not wish to do so, delete this exception +statement from your version. This exception does not (and cannot) modify any +license terms which apply to the Application, with which you must still +comply. + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/gopkg.in/xmlpath.v2/README.md b/vendor/gopkg.in/xmlpath.v2/README.md new file mode 100644 index 0000000..4d10f69 --- /dev/null +++ b/vendor/gopkg.in/xmlpath.v2/README.md @@ -0,0 +1,4 @@ +Installation and usage +---------------------- + +See [gopkg.in/xmlpath.v2](https://gopkg.in/xmlpath.v2) for documentation and usage details. diff --git a/vendor/gopkg.in/xmlpath.v2/all_test.go b/vendor/gopkg.in/xmlpath.v2/all_test.go new file mode 100644 index 0000000..dd8ae18 --- /dev/null +++ b/vendor/gopkg.in/xmlpath.v2/all_test.go @@ -0,0 +1,506 @@ +package xmlpath_test + +import ( + "bytes" + "encoding/xml" + "testing" + + . "gopkg.in/check.v1" + "gopkg.in/xmlpath.v2" + "strings" +) + +func Test(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&BasicSuite{}) + +type BasicSuite struct{} + +var trivialXml = []byte(`abcdefg`) + +func (s *BasicSuite) TestRootText(c *C) { + node, err := xmlpath.Parse(bytes.NewBuffer(trivialXml)) + c.Assert(err, IsNil) + path := xmlpath.MustCompile("/") + result, ok := path.String(node) + c.Assert(ok, Equals, true) + c.Assert(result, Equals, "abcdefg") +} + +var htmlTable = []struct { + html string + path string + result interface{} +}{ + {"
  • a
  • b", "/html/body/li", "a"}, + {"
  • a
  • b", "/html/body/li", "a"}, + {"<a>", "/html/body", ""}, + {"", "/html/head/script", "if(1<2||2>1){}"}, + {"\ntext", "/html/body", "text"}, +} + +func (s *BasicSuite) TestHTML(c *C) { + for _, test := range htmlTable { + c.Logf("Running test: %v", test) + node, err := xmlpath.ParseHTML(bytes.NewBuffer([]byte(test.html))) + c.Assert(err, IsNil) + path, err := xmlpath.Compile(test.path) + c.Assert(err, IsNil) + result, ok := path.String(node) + c.Assert(ok, Equals, true) + c.Assert(result, Equals, test.result) + } +} + +func (s *BasicSuite) TestLibraryTable(c *C) { + node, err := xmlpath.Parse(bytes.NewBuffer(libraryXml)) + c.Assert(err, IsNil) + for _, test := range libraryTable { + cmt := Commentf("xml path: %s", test.path) + path, err := xmlpath.Compile(test.path) + if want, ok := test.result.(cerror); ok { + if !strings.Contains(err.Error(), string(want)) { + c.Fatalf("error should contain `%s` but got `%s`", want, err.Error()) + } + c.Assert(path, IsNil, cmt) + continue + } + c.Assert(err, IsNil) + switch want := test.result.(type) { + case string: + got, ok := path.String(node) + c.Assert(ok, Equals, true, cmt) + c.Assert(got, Equals, want, cmt) + c.Assert(path.Exists(node), Equals, true, cmt) + iter := path.Iter(node) + iter.Next() + node := iter.Node() + c.Assert(node.String(), Equals, want, cmt) + c.Assert(string(node.Bytes()), Equals, want, cmt) + case []string: + var alls []string + var allb []string + iter := path.Iter(node) + for iter.Next() { + alls = append(alls, iter.Node().String()) + allb = append(allb, string(iter.Node().Bytes())) + } + c.Assert(alls, DeepEquals, want, cmt) + c.Assert(allb, DeepEquals, want, cmt) + s, sok := path.String(node) + b, bok := path.Bytes(node) + if len(want) == 0 { + c.Assert(sok, Equals, false, cmt) + c.Assert(bok, Equals, false, cmt) + c.Assert(s, Equals, "") + c.Assert(b, IsNil) + } else { + c.Assert(sok, Equals, true, cmt) + c.Assert(bok, Equals, true, cmt) + c.Assert(s, Equals, alls[0], cmt) + c.Assert(string(b), Equals, alls[0], cmt) + c.Assert(path.Exists(node), Equals, true, cmt) + } + case exists: + wantb := bool(want) + ok := path.Exists(node) + c.Assert(ok, Equals, wantb, cmt) + _, ok = path.String(node) + c.Assert(ok, Equals, wantb, cmt) + } + } +} + +type cerror string +type exists bool + +var libraryTable = []struct { + path string + result interface{} +}{ + // These are the examples in the package documentation: + {"/library/book/isbn", "0836217462"}, + {"library/*/isbn", "0836217462"}, + {"/library/book/../book/./isbn", "0836217462"}, + {"/library/book/character[2]/name", "Snoopy"}, + {"/library/book/character[born='1950-10-04']/name", "Snoopy"}, + {"/library/book//node()[@id='PP']/name", "Peppermint Patty"}, + {"//book[author/@id='CMS']/title", "Being a Dog Is a Full-Time Job"}, + {"/library/book/preceding::comment()", " Great book. "}, + {"//*[contains(born,'1922')]/name", "Charles M Schulz"}, + {"//*[@id='PP' or @id='Snoopy']/born", []string{"1966-08-22", "1950-10-04"}}, + + // A few simple + {"/library/book/isbn", exists(true)}, + {"/library/isbn", exists(false)}, + {"/library/book/isbn/bad", exists(false)}, + {"/library/book/bad", exists(false)}, + {"/library/bad/isbn", exists(false)}, + {"/bad/book/isbn", exists(false)}, + + // Simple paths. + {"/library/book/isbn", "0836217462"}, + {"/library/book/author/name", "Charles M Schulz"}, + {"/library/book/author/born", "1922-11-26"}, + {"/library/book/character/name", "Peppermint Patty"}, + {"/library/book/character/qualification", "bold, brash and tomboyish"}, + + // Unrooted path with root node as context. + {"library/book/isbn", "0836217462"}, + + // Multiple entries from simple paths. + {"/library/book/isbn", []string{"0836217462", "0883556316"}}, + {"/library/book/character/name", []string{"Peppermint Patty", "Snoopy", "Schroeder", "Lucy", "Barney Google", "Spark Plug", "Snuffy Smith"}}, + + // Handling of wildcards. + {"/library/book/author/*", []string{"Charles M Schulz", "1922-11-26", "2000-02-12", "Charles M Schulz", "1922-11-26", "2000-02-12"}}, + + // Unsupported axis and note test. + {"/foo()", cerror(`compiling xml path "/foo()":5: unsupported expression: foo()`)}, + {"/node(", cerror(`compiling xml path "/node(":6: node() missing ')'`)}, + {"/foo::node()", cerror(`compiling xml path "/foo::node()":6: unsupported axis: "foo"`)}, + + // The attribute axis. + {"/library/book/title/attribute::lang", "en"}, + {"/library/book/title/@lang", "en"}, + {"/library/book/@available/parent::node()/@id", "b0836217462"}, + {"/library/book/attribute::*", []string{"b0836217462", "true", "b0883556316", "true"}}, + {"/library/book/attribute::text()", cerror(`: text() cannot succeed on axis "attribute"`)}, + + // The self axis. + {"/library/book/isbn/./self::node()", "0836217462"}, + + // The descendant axis. + {"/library/book/isbn/descendant::isbn", exists(false)}, + {"/library/descendant::isbn", []string{"0836217462", "0883556316"}}, + {"/descendant::*/isbn", []string{"0836217462", "0883556316"}}, + {"/descendant::isbn", []string{"0836217462", "0883556316"}}, + + // The descendant-or-self axis. + {"/library/book/isbn/descendant-or-self::isbn", "0836217462"}, + {"/library//isbn", []string{"0836217462", "0883556316"}}, + {"//isbn", []string{"0836217462", "0883556316"}}, + {"/descendant-or-self::node()/child::book/child::*", "0836217462"}, + + // The parent axis. + {"/library/book/isbn/../isbn/parent::node()//title", "Being a Dog Is a Full-Time Job"}, + + // The ancestor axis. + {"/library/book/isbn/ancestor::book/title", "Being a Dog Is a Full-Time Job"}, + {"/library/book/ancestor::book/title", exists(false)}, + + // The ancestor-or-self axis. + {"/library/book/isbn/ancestor-or-self::book/title", "Being a Dog Is a Full-Time Job"}, + {"/library/book/ancestor-or-self::book/title", "Being a Dog Is a Full-Time Job"}, + + // The following axis. + // The first author name must not be included, as it's within the context + // node (author) rather than following it. These queries exercise de-duping + // of nodes, since the following axis runs to the end multiple times. + {"/library/book/author/following::name", []string{"Peppermint Patty", "Snoopy", "Schroeder", "Lucy", "Charles M Schulz", "Barney Google", "Spark Plug", "Snuffy Smith"}}, + {"//following::book/author/name", []string{"Charles M Schulz", "Charles M Schulz"}}, + + // The following-sibling axis. + {"/library/book/quote/following-sibling::node()/name", []string{"Charles M Schulz", "Peppermint Patty", "Snoopy", "Schroeder", "Lucy"}}, + + // The preceding axis. + {"/library/book/author/born/preceding::name", []string{"Charles M Schulz", "Charles M Schulz", "Lucy", "Schroeder", "Snoopy", "Peppermint Patty"}}, + {"/library/book/author/born/preceding::author/name", []string{"Charles M Schulz"}}, + {"/library/book/author/born/preceding::library", exists(false)}, + + // The preceding-sibling axis. + {"/library/book/author/born/preceding-sibling::name", []string{"Charles M Schulz", "Charles M Schulz"}}, + {"/library/book/author/born/preceding::author/name", []string{"Charles M Schulz"}}, + + // Comments. + {"/library/comment()", []string{" Great book. ", " Another great book. "}}, + {"//self::comment()", []string{" Great book. ", " Another great book. "}}, + {`comment("")`, cerror(`: comment() has no arguments`)}, + + // Processing instructions. + {`/library/book/author/processing-instruction()`, `"go rocks"`}, + {`/library/book/author/processing-instruction("echo")`, `"go rocks"`}, + {`/library//processing-instruction("echo")`, `"go rocks"`}, + {`/library/book/author/processing-instruction("foo")`, exists(false)}, + {`/library/book/author/processing-instruction(")`, cerror(`: missing '"'`)}, + + // Predicates. + {"library/book[@id='b0883556316']/isbn", []string{"0883556316"}}, + {"library/book[ @id = 'b0883556316' ]/isbn", []string{"0883556316"}}, + {"library/book[isbn='0836217462']/character[born='1950-10-04']/name", []string{"Snoopy"}}, + {"library/book[quote]/@id", []string{"b0836217462"}}, + {"library/book[./character/born='1922-07-17']/@id", []string{"b0883556316"}}, + {"library/book[2]/isbn", []string{"0883556316"}}, + {"library/book[0]/isbn", cerror(": positions start at 1")}, + {"library/book[-1]/isbn", cerror(": positions must be positive")}, + {"//title[contains(.,'ney Google and')]", "Barney Google and Snuffy Smith"}, + {"//@id[contains(.,'0836')]", "b0836217462"}, + {"//*[contains(born,'1922')]/name", "Charles M Schulz"}, + {"library/book[not(@id)]", exists(false)}, + {"library/book[not(@foo) and @id='b0883556316']/isbn", []string{"0883556316"}}, + + // Multiple predicates. + {"library/book/character[@id='Snoopy' and ./born='1950-10-04']/born", []string{"1950-10-04"}}, + {"library/book/character[@id='Snoopy' or @id='Lucy']/born", []string{"1950-10-04", "1952-03-03"}}, + {"library/book/character[@id='Snoopy' and ./born='1950-10-04' or @id='Lucy' and ./born='1952-03-03']/born", []string{"1950-10-04", "1952-03-03"}}, + {"library/book/character[@id='Snoopy' or @id='Lucy' and @id='NOPE']/born", []string{"1950-10-04"}}, + {"library/book/character[@id='Snoopy' and @id='NOPE' or @id='Lucy']/born", []string{"1952-03-03"}}, + {"library/book/character[(@id='Snoopy' or @id='Lucy') and (./born='1950-10-04' or ./born='1952-03-03')]/born", []string{"1950-10-04", "1952-03-03"}}, + + // Bogus expressions. + {"/foo)", cerror(`compiling xml path "/foo)":4: unexpected ')'`)}, + {"/foo[", cerror(`compiling xml path "/foo[":5: missing name`)}, + {"/foo[@id)]", cerror(`compiling xml path "/foo[@id)]":9: unexpected ')'`)}, + {"/foo[(@id]", cerror(`compiling xml path "/foo[(@id]":9: expected ')'`)}, + + // Whitespace handling. + {" / descendant-or-self :: node() // child :: book / child :: * [ contains( . , '083' ) ] ", "0836217462"}, +} + +var libraryXml = []byte(` + + + + + 0836217462 + Being a Dog Is a Full-Time Job + I'd dog paddle the deepest ocean. + + + Charles M Schulz + 1922-11-26 + 2000-02-12 + + + Peppermint Patty + 1966-08-22 + bold, brash and tomboyish + + + Snoopy + 1950-10-04 + extroverted beagle + + + Schroeder + 1951-05-30 + brought classical music to the Peanuts strip + + + Lucy + 1952-03-03 + bossy, crabby and selfish + + + + + 0883556316 + Barney <i>Google</i> and Snuffy Smith + + Charles M Schulz + 1922-11-26 + 2000-02-12 + + + Barney Google + 1919-01-01 + goggle-eyed, moustached, gloved and top-hatted, bulbous-nosed, cigar-chomping shrimp + + + Spark Plug + 1922-07-17 + brown-eyed, bow-legged nag, seldom races, patched blanket + + + Snuffy Smith + 1934-01-01 + volatile and diminutive moonshiner, ornery little cuss, sawed-off and shiftless + + + +`) + +func (s *BasicSuite) BenchmarkParse(c *C) { + for i := 0; i < c.N; i++ { + _, err := xmlpath.Parse(bytes.NewBuffer(instancesXml)) + c.Assert(err, IsNil) + } +} + +func (s *BasicSuite) BenchmarkSimplePathCompile(c *C) { + var err error + c.ResetTimer() + for i := 0; i < c.N; i++ { + _, err = xmlpath.Compile("/DescribeInstancesResponse/reservationSet/item/groupSet/item/groupId") + } + c.StopTimer() + c.Assert(err, IsNil) +} + +func (s *BasicSuite) BenchmarkSimplePathString(c *C) { + node, err := xmlpath.Parse(bytes.NewBuffer(instancesXml)) + c.Assert(err, IsNil) + path := xmlpath.MustCompile("/DescribeInstancesResponse/reservationSet/item/instancesSet/item/instanceType") + var str string + c.ResetTimer() + for i := 0; i < c.N; i++ { + str, _ = path.String(node) + } + c.StopTimer() + c.Assert(str, Equals, "m1.small") +} + +func (s *BasicSuite) BenchmarkSimplePathStringUnmarshal(c *C) { + // For a vague comparison. + var result struct { + Str string `xml:"reservationSet>item>instancesSet>item>instanceType"` + } + for i := 0; i < c.N; i++ { + xml.Unmarshal(instancesXml, &result) + } + c.StopTimer() + c.Assert(result.Str, Equals, "m1.large") +} + +func (s *BasicSuite) BenchmarkSimplePathExists(c *C) { + node, err := xmlpath.Parse(bytes.NewBuffer(instancesXml)) + c.Assert(err, IsNil) + path := xmlpath.MustCompile("/DescribeInstancesResponse/reservationSet/item/instancesSet/item/instanceType") + var exists bool + c.ResetTimer() + for i := 0; i < c.N; i++ { + exists = path.Exists(node) + } + c.StopTimer() + c.Assert(exists, Equals, true) +} + +var instancesXml = []byte(` + + 98e3c9a4-848c-4d6d-8e8a-b1bdEXAMPLE + + + r-b27e30d9 + 999988887777 + + + sg-67ad940e + default + + + + + i-c5cd56af + ami-1a2b3c4d + + 16 + running + + domU-12-31-39-10-56-34.compute-1.internal + ec2-174-129-165-232.compute-1.amazonaws.com + + GSG_Keypair + 0 + + m1.small + 2010-08-17T01:15:18.000Z + + us-east-1b + + + aki-94c527fd + ari-96c527ff + + disabled + + 10.198.85.190 + 174.129.165.232 + i386 + ebs + /dev/sda1 + + + /dev/sda1 + + vol-a082c1c9 + attached + 2010-08-17T01:15:21.000Z + false + + + + spot + sir-7a688402 + paravirtual + + + xen + + + 854251627541 + + + r-b67e30dd + 999988887777 + + + sg-67ad940e + default + + + + + i-d9cd56b3 + ami-1a2b3c4d + + 16 + running + + domU-12-31-39-10-54-E5.compute-1.internal + ec2-184-73-58-78.compute-1.amazonaws.com + + GSG_Keypair + 0 + + m1.large + 2010-08-17T01:15:19.000Z + + us-east-1b + + + aki-94c527fd + ari-96c527ff + + disabled + + 10.198.87.19 + 184.73.58.78 + i386 + ebs + /dev/sda1 + + + /dev/sda1 + + vol-a282c1cb + attached + 2010-08-17T01:15:23.000Z + false + + + + spot + sir-55a3aa02 + paravirtual + + + xen + + + 854251627541 + + + +`) diff --git a/vendor/gopkg.in/xmlpath.v2/doc.go b/vendor/gopkg.in/xmlpath.v2/doc.go new file mode 100644 index 0000000..e81d737 --- /dev/null +++ b/vendor/gopkg.in/xmlpath.v2/doc.go @@ -0,0 +1,75 @@ +// Package xmlpath implements a strict subset of the XPath specification for the Go language. +// +// The XPath specification is available at: +// +// http://www.w3.org/TR/xpath +// +// Path expressions supported by this package are in the following format, +// with all components being optional: +// +// /axis-name::node-test[predicate]/axis-name::node-test[predicate] +// +// At the moment, xmlpath is compatible with the XPath specification +// to the following extent: +// +// - All axes are supported ("child", "following-sibling", etc) +// - All abbreviated forms are supported (".", "//", etc) +// - All node types except for namespace are supported +// - Predicates may be [N], [path], [not(path)], [path=literal] or [contains(path, literal)] +// - Predicates may be joined with "or", "and", and parenthesis +// - Richer expressions and namespaces are not supported +// +// For example, assuming the following document: +// +// +// +// +// 0836217462 +// Being a Dog Is a Full-Time Job +// I'd dog paddle the deepest ocean. +// +// +// Charles M Schulz +// 1922-11-26 +// 2000-02-12 +// +// +// Peppermint Patty +// 1966-08-22 +// bold, brash and tomboyish +// +// +// Snoopy +// 1950-10-04 +// extroverted beagle +// +// +// +// +// The following examples are valid path expressions, and the first +// match has the indicated value: +// +// /library/book/isbn => "0836217462" +// library/*/isbn => "0836217462" +// /library/book/../book/./isbn => "0836217462" +// /library/book/character[2]/name => "Snoopy" +// /library/book/character[born='1950-10-04']/name => "Snoopy" +// /library/book//node()[@id='PP']/name => "Peppermint Patty" +// //book[author/@id='CMS']/title => "Being a Dog Is a Full-Time Job", +// /library/book/preceding::comment() => " Great book. " +// //*[contains(born,'1922')]/name => "Charles M Schulz" +// //*[@id='PP' or @id='Snoopy']/born => {"1966-08-22", "1950-10-04"} +// +// To run an expression, compile it, and then apply the compiled path to any +// number of context nodes, from one or more parsed xml documents: +// +// path := xmlpath.MustCompile("/library/book/isbn") +// root, err := xmlpath.Parse(file) +// if err != nil { +// log.Fatal(err) +// } +// if value, ok := path.String(root); ok { +// fmt.Println("Found:", value) +// } +// +package xmlpath diff --git a/vendor/gopkg.in/xmlpath.v2/parser.go b/vendor/gopkg.in/xmlpath.v2/parser.go new file mode 100644 index 0000000..64267aa --- /dev/null +++ b/vendor/gopkg.in/xmlpath.v2/parser.go @@ -0,0 +1,396 @@ +package xmlpath + +import ( + "golang.org/x/net/html" + "encoding/xml" + "io" + "strings" +) + +// Node is an item in an xml tree that was compiled to +// be processed via xml paths. A node may represent: +// +// - An element in the xml document () +// - An attribute of an element in the xml document (href="...") +// - A comment in the xml document () +// - A processing instruction in the xml document () +// - Some text within the xml document +// +type Node struct { + kind nodeKind + name xml.Name + attr string + text []byte + + nodes []Node + pos int + end int + + up *Node + down []*Node +} + +type nodeKind int + +const ( + anyNode nodeKind = iota + startNode + endNode + attrNode + textNode + commentNode + procInstNode +) + +// String returns the string value of node. +// +// The string value of a node is: +// +// - For element nodes, the concatenation of all text nodes within the element. +// - For text nodes, the text itself. +// - For attribute nodes, the attribute value. +// - For comment nodes, the text within the comment delimiters. +// - For processing instruction nodes, the content of the instruction. +// +func (node *Node) String() string { + if node.kind == attrNode { + return node.attr + } + return string(node.Bytes()) +} + +// Bytes returns the string value of node as a byte slice. +// See Node.String for a description of what the string value of a node is. +func (node *Node) Bytes() []byte { + if node.kind == attrNode { + return []byte(node.attr) + } + if node.kind != startNode { + return node.text + } + size := 0 + for i := node.pos; i < node.end; i++ { + if node.nodes[i].kind == textNode { + size += len(node.nodes[i].text) + } + } + text := make([]byte, 0, size) + for i := node.pos; i < node.end; i++ { + if node.nodes[i].kind == textNode { + text = append(text, node.nodes[i].text...) + } + } + return text +} + +// equals returns whether the string value of node is equal to s, +// without allocating memory. +func (node *Node) equals(s string) bool { + if node.kind == attrNode { + return s == node.attr + } + if node.kind != startNode { + if len(s) != len(node.text) { + return false + } + for i := range s { + if s[i] != node.text[i] { + return false + } + } + return true + } + si := 0 + for i := node.pos; i < node.end; i++ { + if node.nodes[i].kind == textNode { + for _, c := range node.nodes[i].text { + if si > len(s) { + return false + } + if s[si] != c { + return false + } + si++ + } + } + } + return si == len(s) +} + +// contains returns whether the string value of node contains s, +// without allocating memory. +func (node *Node) contains(s string) (ok bool) { + if len(s) == 0 { + return true + } + if node.kind == attrNode { + return strings.Contains(node.attr, s) + } + s0 := s[0] + for i := node.pos; i < node.end; i++ { + if node.nodes[i].kind == textNode { + text := node.nodes[i].text + NextTry: + for ci, c := range text { + if c != s0 { + continue + } + si := 1 + for ci++; ci < len(text) && si < len(s); ci++ { + if s[si] != text[ci] { + continue NextTry + } + si++ + } + if si == len(s) { + return true + } + for j := i + 1; j < node.end; j++ { + if node.nodes[j].kind == textNode { + for _, c := range node.nodes[j].text { + if s[si] != c { + continue NextTry + } + si++ + if si == len(s) { + return true + } + } + } + } + } + } + } + return false +} + +// Parse reads an xml document from r, parses it, and returns its root node. +func Parse(r io.Reader) (*Node, error) { + return ParseDecoder(xml.NewDecoder(r)) +} + +// ParseDecoder parses the xml document being decoded by d and returns +// its root node. +func ParseDecoder(d *xml.Decoder) (*Node, error) { + var nodes []Node + var text []byte + + // The root node. + nodes = append(nodes, Node{kind: startNode}) + + for { + t, err := d.Token() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + switch t := t.(type) { + case xml.EndElement: + nodes = append(nodes, Node{ + kind: endNode, + }) + case xml.StartElement: + nodes = append(nodes, Node{ + kind: startNode, + name: t.Name, + }) + for _, attr := range t.Attr { + nodes = append(nodes, Node{ + kind: attrNode, + name: attr.Name, + attr: attr.Value, + }) + } + case xml.CharData: + texti := len(text) + text = append(text, t...) + nodes = append(nodes, Node{ + kind: textNode, + text: text[texti : texti+len(t)], + }) + case xml.Comment: + texti := len(text) + text = append(text, t...) + nodes = append(nodes, Node{ + kind: commentNode, + text: text[texti : texti+len(t)], + }) + case xml.ProcInst: + texti := len(text) + text = append(text, t.Inst...) + nodes = append(nodes, Node{ + kind: procInstNode, + name: xml.Name{Local: t.Target}, + text: text[texti : texti+len(t.Inst)], + }) + } + } + + // Close the root node. + nodes = append(nodes, Node{kind: endNode}) + + stack := make([]*Node, 0, len(nodes)) + downs := make([]*Node, len(nodes)) + downCount := 0 + + for pos := range nodes { + + switch nodes[pos].kind { + + case startNode, attrNode, textNode, commentNode, procInstNode: + node := &nodes[pos] + node.nodes = nodes + node.pos = pos + if len(stack) > 0 { + node.up = stack[len(stack)-1] + } + if node.kind == startNode { + stack = append(stack, node) + } else { + node.end = pos + 1 + } + + case endNode: + node := stack[len(stack)-1] + node.end = pos + stack = stack[:len(stack)-1] + + // Compute downs. Doing that here is what enables the + // use of a slice of a contiguous pre-allocated block. + node.down = downs[downCount:downCount] + for i := node.pos + 1; i < node.end; i++ { + if nodes[i].up == node { + switch nodes[i].kind { + case startNode, textNode, commentNode, procInstNode: + node.down = append(node.down, &nodes[i]) + downCount++ + } + } + } + if len(stack) == 0 { + return node, nil + } + } + } + return nil, io.EOF +} + +// ParseHTML reads an HTML document from r, parses it using a proper HTML +// parser, and returns its root node. +// +// The document will be processed as a properly structured HTML document, +// emulating the behavior of a browser when processing it. This includes +// putting the content inside proper and tags, if the +// provided text misses them. +func ParseHTML(r io.Reader) (*Node, error) { + ns, err := html.ParseFragment(r, nil) + if err != nil { + return nil, err + } + + var nodes []Node + var text []byte + + n := ns[0] + + // The root node. + nodes = append(nodes, Node{kind: startNode}) + + for n != nil { + switch n.Type { + case html.DocumentNode: + case html.ElementNode: + nodes = append(nodes, Node{ + kind: startNode, + name: xml.Name{Local: n.Data, Space: n.Namespace}, + }) + for _, attr := range n.Attr { + nodes = append(nodes, Node{ + kind: attrNode, + name: xml.Name{Local: attr.Key, Space: attr.Namespace}, + attr: attr.Val, + }) + } + case html.TextNode: + texti := len(text) + text = append(text, n.Data...) + nodes = append(nodes, Node{ + kind: textNode, + text: text[texti : texti+len(n.Data)], + }) + case html.CommentNode: + texti := len(text) + text = append(text, n.Data...) + nodes = append(nodes, Node{ + kind: commentNode, + text: text[texti : texti+len(n.Data)], + }) + } + + if n.FirstChild != nil { + n = n.FirstChild + continue + } + + for n != nil { + if n.Type == html.ElementNode { + nodes = append(nodes, Node{kind: endNode}) + } + if n.NextSibling != nil { + n = n.NextSibling + break + } + n = n.Parent + } + } + + // Close the root node. + nodes = append(nodes, Node{kind: endNode}) + + stack := make([]*Node, 0, len(nodes)) + downs := make([]*Node, len(nodes)) + downCount := 0 + + for pos := range nodes { + + switch nodes[pos].kind { + + case startNode, attrNode, textNode, commentNode, procInstNode: + node := &nodes[pos] + node.nodes = nodes + node.pos = pos + if len(stack) > 0 { + node.up = stack[len(stack)-1] + } + if node.kind == startNode { + stack = append(stack, node) + } else { + node.end = pos + 1 + } + + case endNode: + node := stack[len(stack)-1] + node.end = pos + stack = stack[:len(stack)-1] + + // Compute downs. Doing that here is what enables the + // use of a slice of a contiguous pre-allocated block. + node.down = downs[downCount:downCount] + for i := node.pos + 1; i < node.end; i++ { + if nodes[i].up == node { + switch nodes[i].kind { + case startNode, textNode, commentNode, procInstNode: + node.down = append(node.down, &nodes[i]) + downCount++ + } + } + } + if len(stack) == 0 { + return node, nil + } + } + } + return nil, io.EOF +} diff --git a/vendor/gopkg.in/xmlpath.v2/path.go b/vendor/gopkg.in/xmlpath.v2/path.go new file mode 100644 index 0000000..db38ed5 --- /dev/null +++ b/vendor/gopkg.in/xmlpath.v2/path.go @@ -0,0 +1,790 @@ +package xmlpath + +import ( + "fmt" + "strconv" + "unicode/utf8" +) + +// Path is a compiled path that can be applied to a context +// node to obtain a matching node set. +// A single Path can be applied concurrently to any number +// of context nodes. +type Path struct { + path string + steps []pathStep +} + +// Iter returns an iterator that goes over the list of nodes +// that p matches on the given context. +func (p *Path) Iter(context *Node) *Iter { + iter := Iter{ + make([]pathStepState, len(p.steps)), + make([]bool, len(context.nodes)), + } + for i := range p.steps { + iter.state[i].step = &p.steps[i] + } + iter.state[0].init(context) + return &iter +} + +// Exists returns whether any nodes match p on the given context. +func (p *Path) Exists(context *Node) bool { + return p.Iter(context).Next() +} + +// String returns the string value of the first node matched +// by p on the given context. +// +// See the documentation of Node.String. +func (p *Path) String(context *Node) (s string, ok bool) { + iter := p.Iter(context) + if iter.Next() { + return iter.Node().String(), true + } + return "", false +} + +// Bytes returns as a byte slice the string value of the first +// node matched by p on the given context. +// +// See the documentation of Node.String. +func (p *Path) Bytes(node *Node) (b []byte, ok bool) { + iter := p.Iter(node) + if iter.Next() { + return iter.Node().Bytes(), true + } + return nil, false +} + +// Iter iterates over node sets. +type Iter struct { + state []pathStepState + seen []bool +} + +// Node returns the current node. +// Must only be called after Iter.Next returns true. +func (iter *Iter) Node() *Node { + state := iter.state[len(iter.state)-1] + if state.pos == 0 { + panic("Iter.Node called before Iter.Next") + } + if state.node == nil { + panic("Iter.Node called after Iter.Next false") + } + return state.node +} + +// Next iterates to the next node in the set, if any, and +// returns whether there is a node available. +func (iter *Iter) Next() bool { + tip := len(iter.state) - 1 +outer: + for { + for !iter.state[tip].next() { + tip-- + if tip == -1 { + return false + } + } + for tip < len(iter.state)-1 { + tip++ + iter.state[tip].init(iter.state[tip-1].node) + if !iter.state[tip].next() { + tip-- + continue outer + } + } + if iter.seen[iter.state[tip].node.pos] { + continue + } + iter.seen[iter.state[tip].node.pos] = true + return true + } + panic("unreachable") +} + +type pathStepState struct { + step *pathStep + node *Node + pos int + idx int + aux int +} + +func (s *pathStepState) init(node *Node) { + s.node = node + s.pos = 0 + s.idx = 0 + s.aux = 0 +} + +func (s *pathStepState) next() bool { + for s._next() { + s.pos++ + if s.step.pred == nil || s.test(s.step.pred) { + return true + } + } + return false +} + +func (s *pathStepState) test(pred predicate) bool { + switch pred := pred.(type) { + case positionPredicate: + if pred.pos == s.pos { + return true + } + case existsPredicate: + if pred.path.Exists(s.node) { + return true + } + case equalsPredicate: + iter := pred.path.Iter(s.node) + for iter.Next() { + if iter.Node().equals(pred.value) { + return true + } + } + case containsPredicate: + iter := pred.path.Iter(s.node) + for iter.Next() { + if iter.Node().contains(pred.value) { + return true + } + } + case notPredicate: + iter := pred.path.Iter(s.node) + if !iter.Next() { + return true + } + case andPredicate: + for _, sub := range pred.sub { + if !s.test(sub) { + return false + } + } + return true + case orPredicate: + for _, sub := range pred.sub { + if s.test(sub) { + return true + } + } + default: + panic(fmt.Sprintf("internal error: unknown predicate type: %#v", s.step.pred)) + } + return false +} + +func (s *pathStepState) _next() bool { + if s.node == nil { + return false + } + if s.step.root && s.idx == 0 { + for s.node.up != nil { + s.node = s.node.up + } + } + + switch s.step.axis { + + case "self": + if s.idx == 0 && s.step.match(s.node) { + s.idx++ + return true + } + + case "parent": + if s.idx == 0 && s.node.up != nil && s.step.match(s.node.up) { + s.idx++ + s.node = s.node.up + return true + } + + case "ancestor", "ancestor-or-self": + if s.idx == 0 && s.step.axis == "ancestor-or-self" { + s.idx++ + if s.step.match(s.node) { + return true + } + } + for s.node.up != nil { + s.node = s.node.up + s.idx++ + if s.step.match(s.node) { + return true + } + } + + case "child": + var down []*Node + if s.idx == 0 { + down = s.node.down + } else { + down = s.node.up.down + } + for s.idx < len(down) { + node := down[s.idx] + s.idx++ + if s.step.match(node) { + s.node = node + return true + } + } + + case "descendant", "descendant-or-self": + if s.idx == 0 { + s.idx = s.node.pos + s.aux = s.node.end + if s.step.axis == "descendant" { + s.idx++ + } + } + for s.idx < s.aux { + node := &s.node.nodes[s.idx] + s.idx++ + if node.kind == attrNode { + continue + } + if s.step.match(node) { + s.node = node + return true + } + } + + case "following": + if s.idx == 0 { + s.idx = s.node.end + } + for s.idx < len(s.node.nodes) { + node := &s.node.nodes[s.idx] + s.idx++ + if node.kind == attrNode { + continue + } + if s.step.match(node) { + s.node = node + return true + } + } + + case "following-sibling": + var down []*Node + if s.node.up != nil { + down = s.node.up.down + if s.idx == 0 { + for s.idx < len(down) { + node := down[s.idx] + s.idx++ + if node == s.node { + break + } + } + } + } + for s.idx < len(down) { + node := down[s.idx] + s.idx++ + if s.step.match(node) { + s.node = node + return true + } + } + + case "preceding": + if s.idx == 0 { + s.aux = s.node.pos // Detect ancestors. + s.idx = s.node.pos - 1 + } + for s.idx >= 0 { + node := &s.node.nodes[s.idx] + s.idx-- + if node.kind == attrNode { + continue + } + if node == s.node.nodes[s.aux].up { + s.aux = s.node.nodes[s.aux].up.pos + continue + } + if s.step.match(node) { + s.node = node + return true + } + } + + case "preceding-sibling": + var down []*Node + if s.node.up != nil { + down = s.node.up.down + if s.aux == 0 { + s.aux = 1 + for s.idx < len(down) { + node := down[s.idx] + s.idx++ + if node == s.node { + s.idx-- + break + } + } + } + } + for s.idx >= 0 { + node := down[s.idx] + s.idx-- + if s.step.match(node) { + s.node = node + return true + } + } + + case "attribute": + if s.idx == 0 { + s.idx = s.node.pos + 1 + s.aux = s.node.end + } + for s.idx < s.aux { + node := &s.node.nodes[s.idx] + s.idx++ + if node.kind != attrNode { + break + } + if s.step.match(node) { + s.node = node + return true + } + } + + } + + s.node = nil + return false +} + +type positionPredicate struct { + pos int +} + +type existsPredicate struct { + path *Path +} + +type equalsPredicate struct { + path *Path + value string +} + +type containsPredicate struct { + path *Path + value string +} + +type notPredicate struct { + path *Path +} + +type andPredicate struct { + sub []predicate +} + +type orPredicate struct { + sub []predicate +} + +// predicate is a marker interface for predicate types. +type predicate interface { + predicate() +} + +func (positionPredicate) predicate() {} +func (existsPredicate) predicate() {} +func (equalsPredicate) predicate() {} +func (containsPredicate) predicate() {} +func (notPredicate) predicate() {} +func (andPredicate) predicate() {} +func (orPredicate) predicate() {} + +type pathStep struct { + root bool + axis string + name string + kind nodeKind + pred predicate +} + +func (step *pathStep) match(node *Node) bool { + return node.kind != endNode && + (step.kind == anyNode || step.kind == node.kind) && + (step.name == "*" || node.name.Local == step.name) +} + +// MustCompile returns the compiled path, and panics if +// there are any errors. +func MustCompile(path string) *Path { + e, err := Compile(path) + if err != nil { + panic(err) + } + return e +} + +// Compile returns the compiled path. +func Compile(path string) (*Path, error) { + c := pathCompiler{path, 0} + if path == "" { + return nil, c.errorf("empty path") + } + p, err := c.parsePath() + if err != nil { + return nil, err + } + return p, nil +} + +type pathCompiler struct { + path string + i int +} + +func (c *pathCompiler) errorf(format string, args ...interface{}) error { + return fmt.Errorf("compiling xml path %q:%d: %s", c.path, c.i, fmt.Sprintf(format, args...)) +} + +func (c *pathCompiler) parsePath() (path *Path, err error) { + var steps []pathStep + var start = c.i + for { + step := pathStep{axis: "child"} + + c.skipSpaces() + if c.i == 0 && c.skipByte('/') { + c.skipSpaces() + step.root = true + if len(c.path) == 1 { + step.name = "*" + } + } + if c.peekByte('/') { + step.axis = "descendant-or-self" + step.name = "*" + } else if c.skipByte('@') { + mark := c.i + if !c.skipName() { + return nil, c.errorf("missing name after @") + } + step.axis = "attribute" + step.name = c.path[mark:c.i] + step.kind = attrNode + } else { + mark := c.i + if c.skipName() { + step.name = c.path[mark:c.i] + c.skipSpaces() + } + if step.name == "" { + return nil, c.errorf("missing name") + } else if step.name == "*" { + step.kind = startNode + } else if step.name == "." { + step.axis = "self" + step.name = "*" + } else if step.name == ".." { + step.axis = "parent" + step.name = "*" + } else { + if c.skipByte(':') { + if !c.skipByte(':') { + return nil, c.errorf("missing ':'") + } + c.skipSpaces() + switch step.name { + case "attribute": + step.kind = attrNode + case "self", "child", "parent": + case "descendant", "descendant-or-self": + case "ancestor", "ancestor-or-self": + case "following", "following-sibling": + case "preceding", "preceding-sibling": + default: + return nil, c.errorf("unsupported axis: %q", step.name) + } + step.axis = step.name + + mark = c.i + if !c.skipName() { + return nil, c.errorf("missing name") + } + step.name = c.path[mark:c.i] + + c.skipSpaces() + } + if c.skipByte('(') { + c.skipSpaces() + conflict := step.kind != anyNode + switch step.name { + case "node": + // must be anyNode + case "text": + step.kind = textNode + case "comment": + step.kind = commentNode + case "processing-instruction": + step.kind = procInstNode + default: + return nil, c.errorf("unsupported expression: %s()", step.name) + } + if conflict { + return nil, c.errorf("%s() cannot succeed on axis %q", step.name, step.axis) + } + + name := step.name + literal, err := c.parseLiteral() + if err == errNoLiteral { + step.name = "*" + } else if err != nil { + return nil, c.errorf("%v", err) + } else if step.kind == procInstNode { + c.skipSpaces() + step.name = literal + } else { + return nil, c.errorf("%s() has no arguments", name) + } + if !c.skipByte(')') { + return nil, c.errorf("%s() missing ')'", name) + } + c.skipSpaces() + } else if step.name == "*" && step.kind == anyNode { + step.kind = startNode + } + } + } + if c.skipByte('[') { + c.skipSpaces() + type state struct { + sub []predicate + and bool + } + var stack []state + var sub []predicate + var and bool + NextPred: + if c.skipByte('(') { + stack = append(stack, state{sub, and}) + sub = nil + and = false + } + var next predicate + if pos, ok := c.parseInt(); ok { + if pos == 0 { + return nil, c.errorf("positions start at 1") + } + next = positionPredicate{pos} + } else if c.skipString("contains(") { + path, err := c.parsePath() + if err != nil { + return nil, err + } + c.skipSpaces() + if !c.skipByte(',') { + return nil, c.errorf("contains() expected ',' followed by a literal string") + } + c.skipSpaces() + value, err := c.parseLiteral() + if err != nil { + return nil, err + } + c.skipSpaces() + if !c.skipByte(')') { + return nil, c.errorf("contains() missing ')'") + } + next = containsPredicate{path, value} + } else if c.skipString("not(") { + // TODO Generalize to handle any predicate expression. + path, err := c.parsePath() + if err != nil { + return nil, err + } + c.skipSpaces() + if !c.skipByte(')') { + return nil, c.errorf("not() missing ')'") + } + next = notPredicate{path} + } else { + path, err := c.parsePath() + if err != nil { + return nil, err + } + if path.path[0] == '-' { + if _, err = strconv.Atoi(path.path); err == nil { + return nil, c.errorf("positions must be positive") + } + } + c.skipSpaces() + if c.skipByte('=') { + c.skipSpaces() + value, err := c.parseLiteral() + if err != nil { + return nil, c.errorf("%v", err) + } + next = equalsPredicate{path, value} + } else { + next = existsPredicate{path} + } + } + HandleNext: + if and { + p := sub[len(sub)-1].(andPredicate) + p.sub = append(p.sub, next) + sub[len(sub)-1] = p + } else { + sub = append(sub, next) + } + if c.skipSpaces() { + mark := c.i + if c.skipString("and") && c.skipSpaces() { + if !and { + and = true + sub[len(sub)-1] = andPredicate{[]predicate{sub[len(sub)-1]}} + } + goto NextPred + } else if c.skipString("or") && c.skipSpaces() { + and = false + goto NextPred + } else { + c.i = mark + } + } + if c.skipByte(')') { + if len(stack) == 0 { + return nil, c.errorf("unexpected ')'") + } + if len(sub) == 1 { + next = sub[0] + } else { + next = orPredicate{sub} + } + s := stack[len(stack)-1] + stack = stack[:len(stack)-1] + sub = s.sub + and = s.and + goto HandleNext + } + if len(stack) > 0 { + return nil, c.errorf("expected ')'") + } + if len(sub) == 1 { + step.pred = sub[0] + } else { + step.pred = orPredicate{sub} + } + if !c.skipByte(']') { + return nil, c.errorf("expected ']'") + } + c.skipSpaces() + } + steps = append(steps, step) + //fmt.Printf("step: %#v\n", step) + if !c.skipByte('/') { + if (start == 0 || start == c.i) && c.i < len(c.path) { + return nil, c.errorf("unexpected %q", c.path[c.i]) + } + return &Path{steps: steps, path: c.path[start:c.i]}, nil + } + } + panic("unreachable") +} + +var errNoLiteral = fmt.Errorf("expected a literal string") + +func (c *pathCompiler) parseLiteral() (string, error) { + if c.skipByte('"') { + mark := c.i + if !c.skipByteFind('"') { + return "", fmt.Errorf(`missing '"'`) + } + return c.path[mark : c.i-1], nil + } + if c.skipByte('\'') { + mark := c.i + if !c.skipByteFind('\'') { + return "", fmt.Errorf(`missing "'"`) + } + return c.path[mark : c.i-1], nil + } + return "", errNoLiteral +} + +func (c *pathCompiler) parseInt() (v int, ok bool) { + mark := c.i + for c.i < len(c.path) && c.path[c.i] >= '0' && c.path[c.i] <= '9' { + v *= 10 + v += int(c.path[c.i]) - '0' + c.i++ + } + if c.i == mark { + return 0, false + } + return v, true +} + +func (c *pathCompiler) skipByte(b byte) bool { + if c.i < len(c.path) && c.path[c.i] == b { + c.i++ + return true + } + return false +} + +func (c *pathCompiler) skipByteFind(b byte) bool { + for i := c.i; i < len(c.path); i++ { + if c.path[i] == b { + c.i = i + 1 + return true + } + } + return false +} + +func (c *pathCompiler) peekByte(b byte) bool { + return c.i < len(c.path) && c.path[c.i] == b +} + +func (c *pathCompiler) skipSpaces() bool { + mark := c.i + for c.i < len(c.path) { + if c.path[c.i] != ' ' { + break + } + c.i++ + } + return c.i != mark +} + +func (c *pathCompiler) skipString(s string) bool { + if c.i+len(s) <= len(c.path) && c.path[c.i:c.i+len(s)] == s { + c.i += len(s) + return true + } + return false +} + +func (c *pathCompiler) skipName() bool { + if c.i >= len(c.path) { + return false + } + if c.path[c.i] == '*' { + c.i++ + return true + } + start := c.i + for c.i < len(c.path) && (c.path[c.i] >= utf8.RuneSelf || isNameByte(c.path[c.i])) { + c.i++ + } + return c.i > start +} + +func isNameByte(c byte) bool { + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c == '.' || c == '-' +} diff --git a/vendor/gopkg.in/yaml.v2/.travis.yml b/vendor/gopkg.in/yaml.v2/.travis.yml new file mode 100644 index 0000000..004172a --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.4 + - 1.5 + - 1.6 + - tip + +go_import_path: gopkg.in/yaml.v2 diff --git a/vendor/gopkg.in/yaml.v2/LICENSE b/vendor/gopkg.in/yaml.v2/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml new file mode 100644 index 0000000..8da58fb --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml @@ -0,0 +1,31 @@ +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original copyright and license: + + apic.go + emitterc.go + parserc.go + readerc.go + scannerc.go + writerc.go + yamlh.go + yamlprivateh.go + +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gopkg.in/yaml.v2/README.md b/vendor/gopkg.in/yaml.v2/README.md new file mode 100644 index 0000000..2ed3314 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/README.md @@ -0,0 +1,135 @@ +# YAML support for the Go language + +Introduction +------------ + +The yaml package enables Go programs to comfortably encode and decode YAML +values. It was developed within [Canonical](https://www.canonical.com) as +part of the [juju](https://juju.ubuntu.com) project, and is based on a +pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) +C library to parse and generate YAML data quickly and reliably. + +Compatibility +------------- + +The yaml package supports most of YAML 1.1 and 1.2, including support for +anchors, tags, map merging, etc. Multi-document unmarshalling is not yet +implemented, and base-60 floats from YAML 1.1 are purposefully not +supported since they're a poor design and are gone in YAML 1.2. + +Installation and usage +---------------------- + +The import path for the package is *gopkg.in/yaml.v2*. + +To install it, run: + + go get gopkg.in/yaml.v2 + +API documentation +----------------- + +If opened in a browser, the import path itself leads to the API documentation: + + * [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) + +API stability +------------- + +The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in). + + +License +------- + +The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details. + + +Example +------- + +Some more examples can be found in the "examples" folder. + +```Go +package main + +import ( + "fmt" + "log" + + "gopkg.in/yaml.v2" +) + +var data = ` +a: Easy! +b: + c: 2 + d: [3, 4] +` + +// Note: struct fields must be public in order for unmarshal to +// correctly populate the data. +type T struct { + A string + B struct { + RenamedC int `yaml:"c"` + D []int `yaml:",flow"` + } +} + +func main() { + t := T{} + + err := yaml.Unmarshal([]byte(data), &t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t:\n%v\n\n", t) + + d, err := yaml.Marshal(&t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t dump:\n%s\n\n", string(d)) + + m := make(map[interface{}]interface{}) + + err = yaml.Unmarshal([]byte(data), &m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m:\n%v\n\n", m) + + d, err = yaml.Marshal(&m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m dump:\n%s\n\n", string(d)) +} +``` + +This example will generate the following output: + +``` +--- t: +{Easy! {2 [3 4]}} + +--- t dump: +a: Easy! +b: + c: 2 + d: [3, 4] + + +--- m: +map[a:Easy! b:map[c:2 d:[3 4]]] + +--- m dump: +a: Easy! +b: + c: 2 + d: + - 3 + - 4 +``` + diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go new file mode 100644 index 0000000..95ec014 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -0,0 +1,742 @@ +package yaml + +import ( + "io" + "os" +) + +func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { + //fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens)) + + // Check if we can move the queue at the beginning of the buffer. + if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) { + if parser.tokens_head != len(parser.tokens) { + copy(parser.tokens, parser.tokens[parser.tokens_head:]) + } + parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head] + parser.tokens_head = 0 + } + parser.tokens = append(parser.tokens, *token) + if pos < 0 { + return + } + copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:]) + parser.tokens[parser.tokens_head+pos] = *token +} + +// Create a new parser object. +func yaml_parser_initialize(parser *yaml_parser_t) bool { + *parser = yaml_parser_t{ + raw_buffer: make([]byte, 0, input_raw_buffer_size), + buffer: make([]byte, 0, input_buffer_size), + } + return true +} + +// Destroy a parser object. +func yaml_parser_delete(parser *yaml_parser_t) { + *parser = yaml_parser_t{} +} + +// String read handler. +func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + if parser.input_pos == len(parser.input) { + return 0, io.EOF + } + n = copy(buffer, parser.input[parser.input_pos:]) + parser.input_pos += n + return n, nil +} + +// File read handler. +func yaml_file_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + return parser.input_file.Read(buffer) +} + +// Set a string input. +func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_string_read_handler + parser.input = input + parser.input_pos = 0 +} + +// Set a file input. +func yaml_parser_set_input_file(parser *yaml_parser_t, file *os.File) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_file_read_handler + parser.input_file = file +} + +// Set the source encoding. +func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { + if parser.encoding != yaml_ANY_ENCODING { + panic("must set the encoding only once") + } + parser.encoding = encoding +} + +// Create a new emitter object. +func yaml_emitter_initialize(emitter *yaml_emitter_t) bool { + *emitter = yaml_emitter_t{ + buffer: make([]byte, output_buffer_size), + raw_buffer: make([]byte, 0, output_raw_buffer_size), + states: make([]yaml_emitter_state_t, 0, initial_stack_size), + events: make([]yaml_event_t, 0, initial_queue_size), + } + return true +} + +// Destroy an emitter object. +func yaml_emitter_delete(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{} +} + +// String write handler. +func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + *emitter.output_buffer = append(*emitter.output_buffer, buffer...) + return nil +} + +// File write handler. +func yaml_file_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + _, err := emitter.output_file.Write(buffer) + return err +} + +// Set a string output. +func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_string_write_handler + emitter.output_buffer = output_buffer +} + +// Set a file output. +func yaml_emitter_set_output_file(emitter *yaml_emitter_t, file io.Writer) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_file_write_handler + emitter.output_file = file +} + +// Set the output encoding. +func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) { + if emitter.encoding != yaml_ANY_ENCODING { + panic("must set the output encoding only once") + } + emitter.encoding = encoding +} + +// Set the canonical output style. +func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) { + emitter.canonical = canonical +} + +//// Set the indentation increment. +func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) { + if indent < 2 || indent > 9 { + indent = 2 + } + emitter.best_indent = indent +} + +// Set the preferred line width. +func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) { + if width < 0 { + width = -1 + } + emitter.best_width = width +} + +// Set if unescaped non-ASCII characters are allowed. +func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) { + emitter.unicode = unicode +} + +// Set the preferred line break character. +func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { + emitter.line_break = line_break +} + +///* +// * Destroy a token object. +// */ +// +//YAML_DECLARE(void) +//yaml_token_delete(yaml_token_t *token) +//{ +// assert(token); // Non-NULL token object expected. +// +// switch (token.type) +// { +// case YAML_TAG_DIRECTIVE_TOKEN: +// yaml_free(token.data.tag_directive.handle); +// yaml_free(token.data.tag_directive.prefix); +// break; +// +// case YAML_ALIAS_TOKEN: +// yaml_free(token.data.alias.value); +// break; +// +// case YAML_ANCHOR_TOKEN: +// yaml_free(token.data.anchor.value); +// break; +// +// case YAML_TAG_TOKEN: +// yaml_free(token.data.tag.handle); +// yaml_free(token.data.tag.suffix); +// break; +// +// case YAML_SCALAR_TOKEN: +// yaml_free(token.data.scalar.value); +// break; +// +// default: +// break; +// } +// +// memset(token, 0, sizeof(yaml_token_t)); +//} +// +///* +// * Check if a string is a valid UTF-8 sequence. +// * +// * Check 'reader.c' for more details on UTF-8 encoding. +// */ +// +//static int +//yaml_check_utf8(yaml_char_t *start, size_t length) +//{ +// yaml_char_t *end = start+length; +// yaml_char_t *pointer = start; +// +// while (pointer < end) { +// unsigned char octet; +// unsigned int width; +// unsigned int value; +// size_t k; +// +// octet = pointer[0]; +// width = (octet & 0x80) == 0x00 ? 1 : +// (octet & 0xE0) == 0xC0 ? 2 : +// (octet & 0xF0) == 0xE0 ? 3 : +// (octet & 0xF8) == 0xF0 ? 4 : 0; +// value = (octet & 0x80) == 0x00 ? octet & 0x7F : +// (octet & 0xE0) == 0xC0 ? octet & 0x1F : +// (octet & 0xF0) == 0xE0 ? octet & 0x0F : +// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; +// if (!width) return 0; +// if (pointer+width > end) return 0; +// for (k = 1; k < width; k ++) { +// octet = pointer[k]; +// if ((octet & 0xC0) != 0x80) return 0; +// value = (value << 6) + (octet & 0x3F); +// } +// if (!((width == 1) || +// (width == 2 && value >= 0x80) || +// (width == 3 && value >= 0x800) || +// (width == 4 && value >= 0x10000))) return 0; +// +// pointer += width; +// } +// +// return 1; +//} +// + +// Create STREAM-START. +func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) bool { + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + encoding: encoding, + } + return true +} + +// Create STREAM-END. +func yaml_stream_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + } + return true +} + +// Create DOCUMENT-START. +func yaml_document_start_event_initialize(event *yaml_event_t, version_directive *yaml_version_directive_t, + tag_directives []yaml_tag_directive_t, implicit bool) bool { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: implicit, + } + return true +} + +// Create DOCUMENT-END. +func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) bool { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + implicit: implicit, + } + return true +} + +///* +// * Create ALIAS. +// */ +// +//YAML_DECLARE(int) +//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t) +//{ +// mark yaml_mark_t = { 0, 0, 0 } +// anchor_copy *yaml_char_t = NULL +// +// assert(event) // Non-NULL event object is expected. +// assert(anchor) // Non-NULL anchor is expected. +// +// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0 +// +// anchor_copy = yaml_strdup(anchor) +// if (!anchor_copy) +// return 0 +// +// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark) +// +// return 1 +//} + +// Create SCALAR. +func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + anchor: anchor, + tag: tag, + value: value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-START. +func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-END. +func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + } + return true +} + +// Create MAPPING-START. +func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) bool { + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create MAPPING-END. +func yaml_mapping_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + } + return true +} + +// Destroy an event object. +func yaml_event_delete(event *yaml_event_t) { + *event = yaml_event_t{} +} + +///* +// * Create a document object. +// */ +// +//YAML_DECLARE(int) +//yaml_document_initialize(document *yaml_document_t, +// version_directive *yaml_version_directive_t, +// tag_directives_start *yaml_tag_directive_t, +// tag_directives_end *yaml_tag_directive_t, +// start_implicit int, end_implicit int) +//{ +// struct { +// error yaml_error_type_t +// } context +// struct { +// start *yaml_node_t +// end *yaml_node_t +// top *yaml_node_t +// } nodes = { NULL, NULL, NULL } +// version_directive_copy *yaml_version_directive_t = NULL +// struct { +// start *yaml_tag_directive_t +// end *yaml_tag_directive_t +// top *yaml_tag_directive_t +// } tag_directives_copy = { NULL, NULL, NULL } +// value yaml_tag_directive_t = { NULL, NULL } +// mark yaml_mark_t = { 0, 0, 0 } +// +// assert(document) // Non-NULL document object is expected. +// assert((tag_directives_start && tag_directives_end) || +// (tag_directives_start == tag_directives_end)) +// // Valid tag directives are expected. +// +// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error +// +// if (version_directive) { +// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)) +// if (!version_directive_copy) goto error +// version_directive_copy.major = version_directive.major +// version_directive_copy.minor = version_directive.minor +// } +// +// if (tag_directives_start != tag_directives_end) { +// tag_directive *yaml_tag_directive_t +// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) +// goto error +// for (tag_directive = tag_directives_start +// tag_directive != tag_directives_end; tag_directive ++) { +// assert(tag_directive.handle) +// assert(tag_directive.prefix) +// if (!yaml_check_utf8(tag_directive.handle, +// strlen((char *)tag_directive.handle))) +// goto error +// if (!yaml_check_utf8(tag_directive.prefix, +// strlen((char *)tag_directive.prefix))) +// goto error +// value.handle = yaml_strdup(tag_directive.handle) +// value.prefix = yaml_strdup(tag_directive.prefix) +// if (!value.handle || !value.prefix) goto error +// if (!PUSH(&context, tag_directives_copy, value)) +// goto error +// value.handle = NULL +// value.prefix = NULL +// } +// } +// +// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, +// tag_directives_copy.start, tag_directives_copy.top, +// start_implicit, end_implicit, mark, mark) +// +// return 1 +// +//error: +// STACK_DEL(&context, nodes) +// yaml_free(version_directive_copy) +// while (!STACK_EMPTY(&context, tag_directives_copy)) { +// value yaml_tag_directive_t = POP(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// } +// STACK_DEL(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// +// return 0 +//} +// +///* +// * Destroy a document object. +// */ +// +//YAML_DECLARE(void) +//yaml_document_delete(document *yaml_document_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// tag_directive *yaml_tag_directive_t +// +// context.error = YAML_NO_ERROR // Eliminate a compliler warning. +// +// assert(document) // Non-NULL document object is expected. +// +// while (!STACK_EMPTY(&context, document.nodes)) { +// node yaml_node_t = POP(&context, document.nodes) +// yaml_free(node.tag) +// switch (node.type) { +// case YAML_SCALAR_NODE: +// yaml_free(node.data.scalar.value) +// break +// case YAML_SEQUENCE_NODE: +// STACK_DEL(&context, node.data.sequence.items) +// break +// case YAML_MAPPING_NODE: +// STACK_DEL(&context, node.data.mapping.pairs) +// break +// default: +// assert(0) // Should not happen. +// } +// } +// STACK_DEL(&context, document.nodes) +// +// yaml_free(document.version_directive) +// for (tag_directive = document.tag_directives.start +// tag_directive != document.tag_directives.end +// tag_directive++) { +// yaml_free(tag_directive.handle) +// yaml_free(tag_directive.prefix) +// } +// yaml_free(document.tag_directives.start) +// +// memset(document, 0, sizeof(yaml_document_t)) +//} +// +///** +// * Get a document node. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_node(document *yaml_document_t, index int) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (index > 0 && document.nodes.start + index <= document.nodes.top) { +// return document.nodes.start + index - 1 +// } +// return NULL +//} +// +///** +// * Get the root object. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_root_node(document *yaml_document_t) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (document.nodes.top != document.nodes.start) { +// return document.nodes.start +// } +// return NULL +//} +// +///* +// * Add a scalar node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_scalar(document *yaml_document_t, +// tag *yaml_char_t, value *yaml_char_t, length int, +// style yaml_scalar_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// value_copy *yaml_char_t = NULL +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// assert(value) // Non-NULL value is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (length < 0) { +// length = strlen((char *)value) +// } +// +// if (!yaml_check_utf8(value, length)) goto error +// value_copy = yaml_malloc(length+1) +// if (!value_copy) goto error +// memcpy(value_copy, value, length) +// value_copy[length] = '\0' +// +// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// yaml_free(tag_copy) +// yaml_free(value_copy) +// +// return 0 +//} +// +///* +// * Add a sequence node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_sequence(document *yaml_document_t, +// tag *yaml_char_t, style yaml_sequence_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_item_t +// end *yaml_node_item_t +// top *yaml_node_item_t +// } items = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error +// +// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, items) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Add a mapping node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_mapping(document *yaml_document_t, +// tag *yaml_char_t, style yaml_mapping_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_pair_t +// end *yaml_node_pair_t +// top *yaml_node_pair_t +// } pairs = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error +// +// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, pairs) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Append an item to a sequence node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_sequence_item(document *yaml_document_t, +// sequence int, item int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// assert(document) // Non-NULL document is required. +// assert(sequence > 0 +// && document.nodes.start + sequence <= document.nodes.top) +// // Valid sequence id is required. +// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE) +// // A sequence node is required. +// assert(item > 0 && document.nodes.start + item <= document.nodes.top) +// // Valid item id is required. +// +// if (!PUSH(&context, +// document.nodes.start[sequence-1].data.sequence.items, item)) +// return 0 +// +// return 1 +//} +// +///* +// * Append a pair of a key and a value to a mapping node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_mapping_pair(document *yaml_document_t, +// mapping int, key int, value int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// pair yaml_node_pair_t +// +// assert(document) // Non-NULL document is required. +// assert(mapping > 0 +// && document.nodes.start + mapping <= document.nodes.top) +// // Valid mapping id is required. +// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE) +// // A mapping node is required. +// assert(key > 0 && document.nodes.start + key <= document.nodes.top) +// // Valid key id is required. +// assert(value > 0 && document.nodes.start + value <= document.nodes.top) +// // Valid value id is required. +// +// pair.key = key +// pair.value = value +// +// if (!PUSH(&context, +// document.nodes.start[mapping-1].data.mapping.pairs, pair)) +// return 0 +// +// return 1 +//} +// +// diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go new file mode 100644 index 0000000..e85eb2e --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -0,0 +1,685 @@ +package yaml + +import ( + "encoding" + "encoding/base64" + "fmt" + "math" + "reflect" + "strconv" + "time" +) + +const ( + documentNode = 1 << iota + mappingNode + sequenceNode + scalarNode + aliasNode +) + +type node struct { + kind int + line, column int + tag string + value string + implicit bool + children []*node + anchors map[string]*node +} + +// ---------------------------------------------------------------------------- +// Parser, produces a node tree out of a libyaml event stream. + +type parser struct { + parser yaml_parser_t + event yaml_event_t + doc *node +} + +func newParser(b []byte) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + + if len(b) == 0 { + b = []byte{'\n'} + } + + yaml_parser_set_input_string(&p.parser, b) + + p.skip() + if p.event.typ != yaml_STREAM_START_EVENT { + panic("expected stream start event, got " + strconv.Itoa(int(p.event.typ))) + } + p.skip() + return &p +} + +func (p *parser) destroy() { + if p.event.typ != yaml_NO_EVENT { + yaml_event_delete(&p.event) + } + yaml_parser_delete(&p.parser) +} + +func (p *parser) skip() { + if p.event.typ != yaml_NO_EVENT { + if p.event.typ == yaml_STREAM_END_EVENT { + failf("attempted to go past the end of stream; corrupted value?") + } + yaml_event_delete(&p.event) + } + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } +} + +func (p *parser) fail() { + var where string + var line int + if p.parser.problem_mark.line != 0 { + line = p.parser.problem_mark.line + } else if p.parser.context_mark.line != 0 { + line = p.parser.context_mark.line + } + if line != 0 { + where = "line " + strconv.Itoa(line) + ": " + } + var msg string + if len(p.parser.problem) > 0 { + msg = p.parser.problem + } else { + msg = "unknown problem parsing YAML content" + } + failf("%s%s", where, msg) +} + +func (p *parser) anchor(n *node, anchor []byte) { + if anchor != nil { + p.doc.anchors[string(anchor)] = n + } +} + +func (p *parser) parse() *node { + switch p.event.typ { + case yaml_SCALAR_EVENT: + return p.scalar() + case yaml_ALIAS_EVENT: + return p.alias() + case yaml_MAPPING_START_EVENT: + return p.mapping() + case yaml_SEQUENCE_START_EVENT: + return p.sequence() + case yaml_DOCUMENT_START_EVENT: + return p.document() + case yaml_STREAM_END_EVENT: + // Happens when attempting to decode an empty buffer. + return nil + default: + panic("attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ))) + } +} + +func (p *parser) node(kind int) *node { + return &node{ + kind: kind, + line: p.event.start_mark.line, + column: p.event.start_mark.column, + } +} + +func (p *parser) document() *node { + n := p.node(documentNode) + n.anchors = make(map[string]*node) + p.doc = n + p.skip() + n.children = append(n.children, p.parse()) + if p.event.typ != yaml_DOCUMENT_END_EVENT { + panic("expected end of document event but got " + strconv.Itoa(int(p.event.typ))) + } + p.skip() + return n +} + +func (p *parser) alias() *node { + n := p.node(aliasNode) + n.value = string(p.event.anchor) + p.skip() + return n +} + +func (p *parser) scalar() *node { + n := p.node(scalarNode) + n.value = string(p.event.value) + n.tag = string(p.event.tag) + n.implicit = p.event.implicit + p.anchor(n, p.event.anchor) + p.skip() + return n +} + +func (p *parser) sequence() *node { + n := p.node(sequenceNode) + p.anchor(n, p.event.anchor) + p.skip() + for p.event.typ != yaml_SEQUENCE_END_EVENT { + n.children = append(n.children, p.parse()) + } + p.skip() + return n +} + +func (p *parser) mapping() *node { + n := p.node(mappingNode) + p.anchor(n, p.event.anchor) + p.skip() + for p.event.typ != yaml_MAPPING_END_EVENT { + n.children = append(n.children, p.parse(), p.parse()) + } + p.skip() + return n +} + +// ---------------------------------------------------------------------------- +// Decoder, unmarshals a node into a provided value. + +type decoder struct { + doc *node + aliases map[string]bool + mapType reflect.Type + terrors []string + strict bool +} + +var ( + mapItemType = reflect.TypeOf(MapItem{}) + durationType = reflect.TypeOf(time.Duration(0)) + defaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) + ifaceType = defaultMapType.Elem() +) + +func newDecoder(strict bool) *decoder { + d := &decoder{mapType: defaultMapType, strict: strict} + d.aliases = make(map[string]bool) + return d +} + +func (d *decoder) terror(n *node, tag string, out reflect.Value) { + if n.tag != "" { + tag = n.tag + } + value := n.value + if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG { + if len(value) > 10 { + value = " `" + value[:7] + "...`" + } else { + value = " `" + value + "`" + } + } + d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type())) +} + +func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) { + terrlen := len(d.terrors) + err := u.UnmarshalYAML(func(v interface{}) (err error) { + defer handleErr(&err) + d.unmarshal(n, reflect.ValueOf(v)) + if len(d.terrors) > terrlen { + issues := d.terrors[terrlen:] + d.terrors = d.terrors[:terrlen] + return &TypeError{issues} + } + return nil + }) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +// d.prepare initializes and dereferences pointers and calls UnmarshalYAML +// if a value is found to implement it. +// It returns the initialized and dereferenced out value, whether +// unmarshalling was already done by UnmarshalYAML, and if so whether +// its types unmarshalled appropriately. +// +// If n holds a null value, prepare returns before doing anything. +func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { + if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) { + return out, false, false + } + again := true + for again { + again = false + if out.Kind() == reflect.Ptr { + if out.IsNil() { + out.Set(reflect.New(out.Type().Elem())) + } + out = out.Elem() + again = true + } + if out.CanAddr() { + if u, ok := out.Addr().Interface().(Unmarshaler); ok { + good = d.callUnmarshaler(n, u) + return out, true, good + } + } + } + return out, false, false +} + +func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + switch n.kind { + case documentNode: + return d.document(n, out) + case aliasNode: + return d.alias(n, out) + } + out, unmarshaled, good := d.prepare(n, out) + if unmarshaled { + return good + } + switch n.kind { + case scalarNode: + good = d.scalar(n, out) + case mappingNode: + good = d.mapping(n, out) + case sequenceNode: + good = d.sequence(n, out) + default: + panic("internal error: unknown node kind: " + strconv.Itoa(n.kind)) + } + return good +} + +func (d *decoder) document(n *node, out reflect.Value) (good bool) { + if len(n.children) == 1 { + d.doc = n + d.unmarshal(n.children[0], out) + return true + } + return false +} + +func (d *decoder) alias(n *node, out reflect.Value) (good bool) { + an, ok := d.doc.anchors[n.value] + if !ok { + failf("unknown anchor '%s' referenced", n.value) + } + if d.aliases[n.value] { + failf("anchor '%s' value contains itself", n.value) + } + d.aliases[n.value] = true + good = d.unmarshal(an, out) + delete(d.aliases, n.value) + return good +} + +var zeroValue reflect.Value + +func resetMap(out reflect.Value) { + for _, k := range out.MapKeys() { + out.SetMapIndex(k, zeroValue) + } +} + +func (d *decoder) scalar(n *node, out reflect.Value) (good bool) { + var tag string + var resolved interface{} + if n.tag == "" && !n.implicit { + tag = yaml_STR_TAG + resolved = n.value + } else { + tag, resolved = resolve(n.tag, n.value) + if tag == yaml_BINARY_TAG { + data, err := base64.StdEncoding.DecodeString(resolved.(string)) + if err != nil { + failf("!!binary value contains invalid base64 data") + } + resolved = string(data) + } + } + if resolved == nil { + if out.Kind() == reflect.Map && !out.CanAddr() { + resetMap(out) + } else { + out.Set(reflect.Zero(out.Type())) + } + return true + } + if s, ok := resolved.(string); ok && out.CanAddr() { + if u, ok := out.Addr().Interface().(encoding.TextUnmarshaler); ok { + err := u.UnmarshalText([]byte(s)) + if err != nil { + fail(err) + } + return true + } + } + switch out.Kind() { + case reflect.String: + if tag == yaml_BINARY_TAG { + out.SetString(resolved.(string)) + good = true + } else if resolved != nil { + out.SetString(n.value) + good = true + } + case reflect.Interface: + if resolved == nil { + out.Set(reflect.Zero(out.Type())) + } else { + out.Set(reflect.ValueOf(resolved)) + } + good = true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch resolved := resolved.(type) { + case int: + if !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + good = true + } + case int64: + if !out.OverflowInt(resolved) { + out.SetInt(resolved) + good = true + } + case uint64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + good = true + } + case float64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + good = true + } + case string: + if out.Type() == durationType { + d, err := time.ParseDuration(resolved) + if err == nil { + out.SetInt(int64(d)) + good = true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch resolved := resolved.(type) { + case int: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + good = true + } + case int64: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + good = true + } + case uint64: + if !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + good = true + } + case float64: + if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + good = true + } + } + case reflect.Bool: + switch resolved := resolved.(type) { + case bool: + out.SetBool(resolved) + good = true + } + case reflect.Float32, reflect.Float64: + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + good = true + case int64: + out.SetFloat(float64(resolved)) + good = true + case uint64: + out.SetFloat(float64(resolved)) + good = true + case float64: + out.SetFloat(resolved) + good = true + } + case reflect.Ptr: + if out.Type().Elem() == reflect.TypeOf(resolved) { + // TODO DOes this make sense? When is out a Ptr except when decoding a nil value? + elem := reflect.New(out.Type().Elem()) + elem.Elem().Set(reflect.ValueOf(resolved)) + out.Set(elem) + good = true + } + } + if !good { + d.terror(n, tag, out) + } + return good +} + +func settableValueOf(i interface{}) reflect.Value { + v := reflect.ValueOf(i) + sv := reflect.New(v.Type()).Elem() + sv.Set(v) + return sv +} + +func (d *decoder) sequence(n *node, out reflect.Value) (good bool) { + l := len(n.children) + + var iface reflect.Value + switch out.Kind() { + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Interface: + // No type hints. Will have to use a generic sequence. + iface = out + out = settableValueOf(make([]interface{}, l)) + default: + d.terror(n, yaml_SEQ_TAG, out) + return false + } + et := out.Type().Elem() + + j := 0 + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + if ok := d.unmarshal(n.children[i], e); ok { + out.Index(j).Set(e) + j++ + } + } + out.Set(out.Slice(0, j)) + if iface.IsValid() { + iface.Set(out) + } + return true +} + +func (d *decoder) mapping(n *node, out reflect.Value) (good bool) { + switch out.Kind() { + case reflect.Struct: + return d.mappingStruct(n, out) + case reflect.Slice: + return d.mappingSlice(n, out) + case reflect.Map: + // okay + case reflect.Interface: + if d.mapType.Kind() == reflect.Map { + iface := out + out = reflect.MakeMap(d.mapType) + iface.Set(out) + } else { + slicev := reflect.New(d.mapType).Elem() + if !d.mappingSlice(n, slicev) { + return false + } + out.Set(slicev) + return true + } + default: + d.terror(n, yaml_MAP_TAG, out) + return false + } + outt := out.Type() + kt := outt.Key() + et := outt.Elem() + + mapType := d.mapType + if outt.Key() == ifaceType && outt.Elem() == ifaceType { + d.mapType = outt + } + + if out.IsNil() { + out.Set(reflect.MakeMap(outt)) + } + l := len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + k := reflect.New(kt).Elem() + if d.unmarshal(n.children[i], k) { + kkind := k.Kind() + if kkind == reflect.Interface { + kkind = k.Elem().Kind() + } + if kkind == reflect.Map || kkind == reflect.Slice { + failf("invalid map key: %#v", k.Interface()) + } + e := reflect.New(et).Elem() + if d.unmarshal(n.children[i+1], e) { + out.SetMapIndex(k, e) + } + } + } + d.mapType = mapType + return true +} + +func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) { + outt := out.Type() + if outt.Elem() != mapItemType { + d.terror(n, yaml_MAP_TAG, out) + return false + } + + mapType := d.mapType + d.mapType = outt + + var slice []MapItem + var l = len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + item := MapItem{} + k := reflect.ValueOf(&item.Key).Elem() + if d.unmarshal(n.children[i], k) { + v := reflect.ValueOf(&item.Value).Elem() + if d.unmarshal(n.children[i+1], v) { + slice = append(slice, item) + } + } + } + out.Set(reflect.ValueOf(slice)) + d.mapType = mapType + return true +} + +func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + name := settableValueOf("") + l := len(n.children) + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) + elemType = inlineMap.Type().Elem() + } + + for i := 0; i < l; i += 2 { + ni := n.children[i] + if isMerge(ni) { + d.merge(n.children[i+1], out) + continue + } + if !d.unmarshal(ni, name) { + continue + } + if info, ok := sinfo.FieldsMap[name.String()]; ok { + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = out.FieldByIndex(info.Inline) + } + d.unmarshal(n.children[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + d.unmarshal(n.children[i+1], value) + inlineMap.SetMapIndex(name, value) + } else if d.strict { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in struct %s", ni.line+1, name.String(), out.Type())) + } + } + return true +} + +func failWantMap() { + failf("map merge requires map or sequence of maps as the value") +} + +func (d *decoder) merge(n *node, out reflect.Value) { + switch n.kind { + case mappingNode: + d.unmarshal(n, out) + case aliasNode: + an, ok := d.doc.anchors[n.value] + if ok && an.kind != mappingNode { + failWantMap() + } + d.unmarshal(n, out) + case sequenceNode: + // Step backwards as earlier nodes take precedence. + for i := len(n.children) - 1; i >= 0; i-- { + ni := n.children[i] + if ni.kind == aliasNode { + an, ok := d.doc.anchors[ni.value] + if ok && an.kind != mappingNode { + failWantMap() + } + } else if ni.kind != mappingNode { + failWantMap() + } + d.unmarshal(ni, out) + } + default: + failWantMap() + } +} + +func isMerge(n *node) bool { + return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG) +} diff --git a/vendor/gopkg.in/yaml.v2/decode_test.go b/vendor/gopkg.in/yaml.v2/decode_test.go new file mode 100644 index 0000000..e5366c2 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/decode_test.go @@ -0,0 +1,1032 @@ +package yaml_test + +import ( + "errors" + . "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + "math" + "net" + "reflect" + "strings" + "time" +) + +var unmarshalIntTest = 123 + +var unmarshalTests = []struct { + data string + value interface{} +}{ + { + "", + &struct{}{}, + }, { + "{}", &struct{}{}, + }, { + "v: hi", + map[string]string{"v": "hi"}, + }, { + "v: hi", map[string]interface{}{"v": "hi"}, + }, { + "v: true", + map[string]string{"v": "true"}, + }, { + "v: true", + map[string]interface{}{"v": true}, + }, { + "v: 10", + map[string]interface{}{"v": 10}, + }, { + "v: 0b10", + map[string]interface{}{"v": 2}, + }, { + "v: 0xA", + map[string]interface{}{"v": 10}, + }, { + "v: 4294967296", + map[string]int64{"v": 4294967296}, + }, { + "v: 0.1", + map[string]interface{}{"v": 0.1}, + }, { + "v: .1", + map[string]interface{}{"v": 0.1}, + }, { + "v: .Inf", + map[string]interface{}{"v": math.Inf(+1)}, + }, { + "v: -.Inf", + map[string]interface{}{"v": math.Inf(-1)}, + }, { + "v: -10", + map[string]interface{}{"v": -10}, + }, { + "v: -.1", + map[string]interface{}{"v": -0.1}, + }, + + // Simple values. + { + "123", + &unmarshalIntTest, + }, + + // Floats from spec + { + "canonical: 6.8523e+5", + map[string]interface{}{"canonical": 6.8523e+5}, + }, { + "expo: 685.230_15e+03", + map[string]interface{}{"expo": 685.23015e+03}, + }, { + "fixed: 685_230.15", + map[string]interface{}{"fixed": 685230.15}, + }, { + "neginf: -.inf", + map[string]interface{}{"neginf": math.Inf(-1)}, + }, { + "fixed: 685_230.15", + map[string]float64{"fixed": 685230.15}, + }, + //{"sexa: 190:20:30.15", map[string]interface{}{"sexa": 0}}, // Unsupported + //{"notanum: .NaN", map[string]interface{}{"notanum": math.NaN()}}, // Equality of NaN fails. + + // Bools from spec + { + "canonical: y", + map[string]interface{}{"canonical": true}, + }, { + "answer: NO", + map[string]interface{}{"answer": false}, + }, { + "logical: True", + map[string]interface{}{"logical": true}, + }, { + "option: on", + map[string]interface{}{"option": true}, + }, { + "option: on", + map[string]bool{"option": true}, + }, + // Ints from spec + { + "canonical: 685230", + map[string]interface{}{"canonical": 685230}, + }, { + "decimal: +685_230", + map[string]interface{}{"decimal": 685230}, + }, { + "octal: 02472256", + map[string]interface{}{"octal": 685230}, + }, { + "hexa: 0x_0A_74_AE", + map[string]interface{}{"hexa": 685230}, + }, { + "bin: 0b1010_0111_0100_1010_1110", + map[string]interface{}{"bin": 685230}, + }, { + "bin: -0b101010", + map[string]interface{}{"bin": -42}, + }, { + "decimal: +685_230", + map[string]int{"decimal": 685230}, + }, + + //{"sexa: 190:20:30", map[string]interface{}{"sexa": 0}}, // Unsupported + + // Nulls from spec + { + "empty:", + map[string]interface{}{"empty": nil}, + }, { + "canonical: ~", + map[string]interface{}{"canonical": nil}, + }, { + "english: null", + map[string]interface{}{"english": nil}, + }, { + "~: null key", + map[interface{}]string{nil: "null key"}, + }, { + "empty:", + map[string]*bool{"empty": nil}, + }, + + // Flow sequence + { + "seq: [A,B]", + map[string]interface{}{"seq": []interface{}{"A", "B"}}, + }, { + "seq: [A,B,C,]", + map[string][]string{"seq": []string{"A", "B", "C"}}, + }, { + "seq: [A,1,C]", + map[string][]string{"seq": []string{"A", "1", "C"}}, + }, { + "seq: [A,1,C]", + map[string][]int{"seq": []int{1}}, + }, { + "seq: [A,1,C]", + map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}, + }, + // Block sequence + { + "seq:\n - A\n - B", + map[string]interface{}{"seq": []interface{}{"A", "B"}}, + }, { + "seq:\n - A\n - B\n - C", + map[string][]string{"seq": []string{"A", "B", "C"}}, + }, { + "seq:\n - A\n - 1\n - C", + map[string][]string{"seq": []string{"A", "1", "C"}}, + }, { + "seq:\n - A\n - 1\n - C", + map[string][]int{"seq": []int{1}}, + }, { + "seq:\n - A\n - 1\n - C", + map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}, + }, + + // Literal block scalar + { + "scalar: | # Comment\n\n literal\n\n \ttext\n\n", + map[string]string{"scalar": "\nliteral\n\n\ttext\n"}, + }, + + // Folded block scalar + { + "scalar: > # Comment\n\n folded\n line\n \n next\n line\n * one\n * two\n\n last\n line\n\n", + map[string]string{"scalar": "\nfolded line\nnext line\n * one\n * two\n\nlast line\n"}, + }, + + // Map inside interface with no type hints. + { + "a: {b: c}", + map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": "c"}}, + }, + + // Structs and type conversions. + { + "hello: world", + &struct{ Hello string }{"world"}, + }, { + "a: {b: c}", + &struct{ A struct{ B string } }{struct{ B string }{"c"}}, + }, { + "a: {b: c}", + &struct{ A *struct{ B string } }{&struct{ B string }{"c"}}, + }, { + "a: {b: c}", + &struct{ A map[string]string }{map[string]string{"b": "c"}}, + }, { + "a: {b: c}", + &struct{ A *map[string]string }{&map[string]string{"b": "c"}}, + }, { + "a:", + &struct{ A map[string]string }{}, + }, { + "a: 1", + &struct{ A int }{1}, + }, { + "a: 1", + &struct{ A float64 }{1}, + }, { + "a: 1.0", + &struct{ A int }{1}, + }, { + "a: 1.0", + &struct{ A uint }{1}, + }, { + "a: [1, 2]", + &struct{ A []int }{[]int{1, 2}}, + }, { + "a: 1", + &struct{ B int }{0}, + }, { + "a: 1", + &struct { + B int "a" + }{1}, + }, { + "a: y", + &struct{ A bool }{true}, + }, + + // Some cross type conversions + { + "v: 42", + map[string]uint{"v": 42}, + }, { + "v: -42", + map[string]uint{}, + }, { + "v: 4294967296", + map[string]uint64{"v": 4294967296}, + }, { + "v: -4294967296", + map[string]uint64{}, + }, + + // int + { + "int_max: 2147483647", + map[string]int{"int_max": math.MaxInt32}, + }, + { + "int_min: -2147483648", + map[string]int{"int_min": math.MinInt32}, + }, + { + "int_overflow: 9223372036854775808", // math.MaxInt64 + 1 + map[string]int{}, + }, + + // int64 + { + "int64_max: 9223372036854775807", + map[string]int64{"int64_max": math.MaxInt64}, + }, + { + "int64_max_base2: 0b111111111111111111111111111111111111111111111111111111111111111", + map[string]int64{"int64_max_base2": math.MaxInt64}, + }, + { + "int64_min: -9223372036854775808", + map[string]int64{"int64_min": math.MinInt64}, + }, + { + "int64_neg_base2: -0b111111111111111111111111111111111111111111111111111111111111111", + map[string]int64{"int64_neg_base2": -math.MaxInt64}, + }, + { + "int64_overflow: 9223372036854775808", // math.MaxInt64 + 1 + map[string]int64{}, + }, + + // uint + { + "uint_min: 0", + map[string]uint{"uint_min": 0}, + }, + { + "uint_max: 4294967295", + map[string]uint{"uint_max": math.MaxUint32}, + }, + { + "uint_underflow: -1", + map[string]uint{}, + }, + + // uint64 + { + "uint64_min: 0", + map[string]uint{"uint64_min": 0}, + }, + { + "uint64_max: 18446744073709551615", + map[string]uint64{"uint64_max": math.MaxUint64}, + }, + { + "uint64_max_base2: 0b1111111111111111111111111111111111111111111111111111111111111111", + map[string]uint64{"uint64_max_base2": math.MaxUint64}, + }, + { + "uint64_maxint64: 9223372036854775807", + map[string]uint64{"uint64_maxint64": math.MaxInt64}, + }, + { + "uint64_underflow: -1", + map[string]uint64{}, + }, + + // float32 + { + "float32_max: 3.40282346638528859811704183484516925440e+38", + map[string]float32{"float32_max": math.MaxFloat32}, + }, + { + "float32_nonzero: 1.401298464324817070923729583289916131280e-45", + map[string]float32{"float32_nonzero": math.SmallestNonzeroFloat32}, + }, + { + "float32_maxuint64: 18446744073709551615", + map[string]float32{"float32_maxuint64": float32(math.MaxUint64)}, + }, + { + "float32_maxuint64+1: 18446744073709551616", + map[string]float32{"float32_maxuint64+1": float32(math.MaxUint64 + 1)}, + }, + + // float64 + { + "float64_max: 1.797693134862315708145274237317043567981e+308", + map[string]float64{"float64_max": math.MaxFloat64}, + }, + { + "float64_nonzero: 4.940656458412465441765687928682213723651e-324", + map[string]float64{"float64_nonzero": math.SmallestNonzeroFloat64}, + }, + { + "float64_maxuint64: 18446744073709551615", + map[string]float64{"float64_maxuint64": float64(math.MaxUint64)}, + }, + { + "float64_maxuint64+1: 18446744073709551616", + map[string]float64{"float64_maxuint64+1": float64(math.MaxUint64 + 1)}, + }, + + // Overflow cases. + { + "v: 4294967297", + map[string]int32{}, + }, { + "v: 128", + map[string]int8{}, + }, + + // Quoted values. + { + "'1': '\"2\"'", + map[interface{}]interface{}{"1": "\"2\""}, + }, { + "v:\n- A\n- 'B\n\n C'\n", + map[string][]string{"v": []string{"A", "B\nC"}}, + }, + + // Explicit tags. + { + "v: !!float '1.1'", + map[string]interface{}{"v": 1.1}, + }, { + "v: !!null ''", + map[string]interface{}{"v": nil}, + }, { + "%TAG !y! tag:yaml.org,2002:\n---\nv: !y!int '1'", + map[string]interface{}{"v": 1}, + }, + + // Non-specific tag (Issue #75) + { + "v: ! test", + map[string]interface{}{"v": "test"}, + }, + + // Anchors and aliases. + { + "a: &x 1\nb: &y 2\nc: *x\nd: *y\n", + &struct{ A, B, C, D int }{1, 2, 1, 2}, + }, { + "a: &a {c: 1}\nb: *a", + &struct { + A, B struct { + C int + } + }{struct{ C int }{1}, struct{ C int }{1}}, + }, { + "a: &a [1, 2]\nb: *a", + &struct{ B []int }{[]int{1, 2}}, + }, { + "b: *a\na: &a {c: 1}", + &struct { + A, B struct { + C int + } + }{struct{ C int }{1}, struct{ C int }{1}}, + }, + + // Bug #1133337 + { + "foo: ''", + map[string]*string{"foo": new(string)}, + }, { + "foo: null", + map[string]*string{"foo": nil}, + }, { + "foo: null", + map[string]string{"foo": ""}, + }, { + "foo: null", + map[string]interface{}{"foo": nil}, + }, + + // Support for ~ + { + "foo: ~", + map[string]*string{"foo": nil}, + }, { + "foo: ~", + map[string]string{"foo": ""}, + }, { + "foo: ~", + map[string]interface{}{"foo": nil}, + }, + + // Ignored field + { + "a: 1\nb: 2\n", + &struct { + A int + B int "-" + }{1, 0}, + }, + + // Bug #1191981 + { + "" + + "%YAML 1.1\n" + + "--- !!str\n" + + `"Generic line break (no glyph)\n\` + "\n" + + ` Generic line break (glyphed)\n\` + "\n" + + ` Line separator\u2028\` + "\n" + + ` Paragraph separator\u2029"` + "\n", + "" + + "Generic line break (no glyph)\n" + + "Generic line break (glyphed)\n" + + "Line separator\u2028Paragraph separator\u2029", + }, + + // Struct inlining + { + "a: 1\nb: 2\nc: 3\n", + &struct { + A int + C inlineB `yaml:",inline"` + }{1, inlineB{2, inlineC{3}}}, + }, + + // Map inlining + { + "a: 1\nb: 2\nc: 3\n", + &struct { + A int + C map[string]int `yaml:",inline"` + }{1, map[string]int{"b": 2, "c": 3}}, + }, + + // bug 1243827 + { + "a: -b_c", + map[string]interface{}{"a": "-b_c"}, + }, + { + "a: +b_c", + map[string]interface{}{"a": "+b_c"}, + }, + { + "a: 50cent_of_dollar", + map[string]interface{}{"a": "50cent_of_dollar"}, + }, + + // Duration + { + "a: 3s", + map[string]time.Duration{"a": 3 * time.Second}, + }, + + // Issue #24. + { + "a: ", + map[string]string{"a": ""}, + }, + + // Base 60 floats are obsolete and unsupported. + { + "a: 1:1\n", + map[string]string{"a": "1:1"}, + }, + + // Binary data. + { + "a: !!binary gIGC\n", + map[string]string{"a": "\x80\x81\x82"}, + }, { + "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n", + map[string]string{"a": strings.Repeat("\x90", 54)}, + }, { + "a: !!binary |\n " + strings.Repeat("A", 70) + "\n ==\n", + map[string]string{"a": strings.Repeat("\x00", 52)}, + }, + + // Ordered maps. + { + "{b: 2, a: 1, d: 4, c: 3, sub: {e: 5}}", + &yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}}, + }, + + // Issue #39. + { + "a:\n b:\n c: d\n", + map[string]struct{ B interface{} }{"a": {map[interface{}]interface{}{"c": "d"}}}, + }, + + // Custom map type. + { + "a: {b: c}", + M{"a": M{"b": "c"}}, + }, + + // Support encoding.TextUnmarshaler. + { + "a: 1.2.3.4\n", + map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)}, + }, + { + "a: 2015-02-24T18:19:39Z\n", + map[string]time.Time{"a": time.Unix(1424801979, 0).In(time.UTC)}, + }, + + // Encode empty lists as zero-length slices. + { + "a: []", + &struct{ A []int }{[]int{}}, + }, + + // UTF-16-LE + { + "\xff\xfe\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00\n\x00", + M{"ñoño": "very yes"}, + }, + // UTF-16-LE with surrogate. + { + "\xff\xfe\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00 \x00=\xd8\xd4\xdf\n\x00", + M{"ñoño": "very yes 🟔"}, + }, + + // UTF-16-BE + { + "\xfe\xff\x00\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00\n", + M{"ñoño": "very yes"}, + }, + // UTF-16-BE with surrogate. + { + "\xfe\xff\x00\xf1\x00o\x00\xf1\x00o\x00:\x00 \x00v\x00e\x00r\x00y\x00 \x00y\x00e\x00s\x00 \xd8=\xdf\xd4\x00\n", + M{"ñoño": "very yes 🟔"}, + }, + + // YAML Float regex shouldn't match this + { + "a: 123456e1\n", + M{"a": "123456e1"}, + }, { + "a: 123456E1\n", + M{"a": "123456E1"}, + }, +} + +type M map[interface{}]interface{} + +type inlineB struct { + B int + inlineC `yaml:",inline"` +} + +type inlineC struct { + C int +} + +func (s *S) TestUnmarshal(c *C) { + for i, item := range unmarshalTests { + c.Logf("test %d: %q", i, item.data) + t := reflect.ValueOf(item.value).Type() + var value interface{} + switch t.Kind() { + case reflect.Map: + value = reflect.MakeMap(t).Interface() + case reflect.String: + value = reflect.New(t).Interface() + case reflect.Ptr: + value = reflect.New(t.Elem()).Interface() + default: + c.Fatalf("missing case for %s", t) + } + err := yaml.Unmarshal([]byte(item.data), value) + if _, ok := err.(*yaml.TypeError); !ok { + c.Assert(err, IsNil) + } + if t.Kind() == reflect.String { + c.Assert(*value.(*string), Equals, item.value) + } else { + c.Assert(value, DeepEquals, item.value) + } + } +} + +func (s *S) TestUnmarshalNaN(c *C) { + value := map[string]interface{}{} + err := yaml.Unmarshal([]byte("notanum: .NaN"), &value) + c.Assert(err, IsNil) + c.Assert(math.IsNaN(value["notanum"].(float64)), Equals, true) +} + +var unmarshalErrorTests = []struct { + data, error string +}{ + {"v: !!float 'error'", "yaml: cannot decode !!str `error` as a !!float"}, + {"v: [A,", "yaml: line 1: did not find expected node content"}, + {"v:\n- [A,", "yaml: line 2: did not find expected node content"}, + {"a: *b\n", "yaml: unknown anchor 'b' referenced"}, + {"a: &a\n b: *a\n", "yaml: anchor 'a' value contains itself"}, + {"value: -", "yaml: block sequence entries are not allowed in this context"}, + {"a: !!binary ==", "yaml: !!binary value contains invalid base64 data"}, + {"{[.]}", `yaml: invalid map key: \[\]interface \{\}\{"\."\}`}, + {"{{.}}", `yaml: invalid map key: map\[interface\ \{\}\]interface \{\}\{".":interface \{\}\(nil\)\}`}, + {"%TAG !%79! tag:yaml.org,2002:\n---\nv: !%79!int '1'", "yaml: did not find expected whitespace"}, +} + +func (s *S) TestUnmarshalErrors(c *C) { + for _, item := range unmarshalErrorTests { + var value interface{} + err := yaml.Unmarshal([]byte(item.data), &value) + c.Assert(err, ErrorMatches, item.error, Commentf("Partial unmarshal: %#v", value)) + } +} + +var unmarshalerTests = []struct { + data, tag string + value interface{} +}{ + {"_: {hi: there}", "!!map", map[interface{}]interface{}{"hi": "there"}}, + {"_: [1,A]", "!!seq", []interface{}{1, "A"}}, + {"_: 10", "!!int", 10}, + {"_: null", "!!null", nil}, + {`_: BAR!`, "!!str", "BAR!"}, + {`_: "BAR!"`, "!!str", "BAR!"}, + {"_: !!foo 'BAR!'", "!!foo", "BAR!"}, + {`_: ""`, "!!str", ""}, +} + +var unmarshalerResult = map[int]error{} + +type unmarshalerType struct { + value interface{} +} + +func (o *unmarshalerType) UnmarshalYAML(unmarshal func(v interface{}) error) error { + if err := unmarshal(&o.value); err != nil { + return err + } + if i, ok := o.value.(int); ok { + if result, ok := unmarshalerResult[i]; ok { + return result + } + } + return nil +} + +type unmarshalerPointer struct { + Field *unmarshalerType "_" +} + +type unmarshalerValue struct { + Field unmarshalerType "_" +} + +func (s *S) TestUnmarshalerPointerField(c *C) { + for _, item := range unmarshalerTests { + obj := &unmarshalerPointer{} + err := yaml.Unmarshal([]byte(item.data), obj) + c.Assert(err, IsNil) + if item.value == nil { + c.Assert(obj.Field, IsNil) + } else { + c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value)) + c.Assert(obj.Field.value, DeepEquals, item.value) + } + } +} + +func (s *S) TestUnmarshalerValueField(c *C) { + for _, item := range unmarshalerTests { + obj := &unmarshalerValue{} + err := yaml.Unmarshal([]byte(item.data), obj) + c.Assert(err, IsNil) + c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value)) + c.Assert(obj.Field.value, DeepEquals, item.value) + } +} + +func (s *S) TestUnmarshalerWholeDocument(c *C) { + obj := &unmarshalerType{} + err := yaml.Unmarshal([]byte(unmarshalerTests[0].data), obj) + c.Assert(err, IsNil) + value, ok := obj.value.(map[interface{}]interface{}) + c.Assert(ok, Equals, true, Commentf("value: %#v", obj.value)) + c.Assert(value["_"], DeepEquals, unmarshalerTests[0].value) +} + +func (s *S) TestUnmarshalerTypeError(c *C) { + unmarshalerResult[2] = &yaml.TypeError{[]string{"foo"}} + unmarshalerResult[4] = &yaml.TypeError{[]string{"bar"}} + defer func() { + delete(unmarshalerResult, 2) + delete(unmarshalerResult, 4) + }() + + type T struct { + Before int + After int + M map[string]*unmarshalerType + } + var v T + data := `{before: A, m: {abc: 1, def: 2, ghi: 3, jkl: 4}, after: B}` + err := yaml.Unmarshal([]byte(data), &v) + c.Assert(err, ErrorMatches, ""+ + "yaml: unmarshal errors:\n"+ + " line 1: cannot unmarshal !!str `A` into int\n"+ + " foo\n"+ + " bar\n"+ + " line 1: cannot unmarshal !!str `B` into int") + c.Assert(v.M["abc"], NotNil) + c.Assert(v.M["def"], IsNil) + c.Assert(v.M["ghi"], NotNil) + c.Assert(v.M["jkl"], IsNil) + + c.Assert(v.M["abc"].value, Equals, 1) + c.Assert(v.M["ghi"].value, Equals, 3) +} + +type proxyTypeError struct{} + +func (v *proxyTypeError) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + var a int32 + var b int64 + if err := unmarshal(&s); err != nil { + panic(err) + } + if s == "a" { + if err := unmarshal(&b); err == nil { + panic("should have failed") + } + return unmarshal(&a) + } + if err := unmarshal(&a); err == nil { + panic("should have failed") + } + return unmarshal(&b) +} + +func (s *S) TestUnmarshalerTypeErrorProxying(c *C) { + type T struct { + Before int + After int + M map[string]*proxyTypeError + } + var v T + data := `{before: A, m: {abc: a, def: b}, after: B}` + err := yaml.Unmarshal([]byte(data), &v) + c.Assert(err, ErrorMatches, ""+ + "yaml: unmarshal errors:\n"+ + " line 1: cannot unmarshal !!str `A` into int\n"+ + " line 1: cannot unmarshal !!str `a` into int32\n"+ + " line 1: cannot unmarshal !!str `b` into int64\n"+ + " line 1: cannot unmarshal !!str `B` into int") +} + +type failingUnmarshaler struct{} + +var failingErr = errors.New("failingErr") + +func (ft *failingUnmarshaler) UnmarshalYAML(unmarshal func(interface{}) error) error { + return failingErr +} + +func (s *S) TestUnmarshalerError(c *C) { + err := yaml.Unmarshal([]byte("a: b"), &failingUnmarshaler{}) + c.Assert(err, Equals, failingErr) +} + +type sliceUnmarshaler []int + +func (su *sliceUnmarshaler) UnmarshalYAML(unmarshal func(interface{}) error) error { + var slice []int + err := unmarshal(&slice) + if err == nil { + *su = slice + return nil + } + + var intVal int + err = unmarshal(&intVal) + if err == nil { + *su = []int{intVal} + return nil + } + + return err +} + +func (s *S) TestUnmarshalerRetry(c *C) { + var su sliceUnmarshaler + err := yaml.Unmarshal([]byte("[1, 2, 3]"), &su) + c.Assert(err, IsNil) + c.Assert(su, DeepEquals, sliceUnmarshaler([]int{1, 2, 3})) + + err = yaml.Unmarshal([]byte("1"), &su) + c.Assert(err, IsNil) + c.Assert(su, DeepEquals, sliceUnmarshaler([]int{1})) +} + +// From http://yaml.org/type/merge.html +var mergeTests = ` +anchors: + list: + - &CENTER { "x": 1, "y": 2 } + - &LEFT { "x": 0, "y": 2 } + - &BIG { "r": 10 } + - &SMALL { "r": 1 } + +# All the following maps are equal: + +plain: + # Explicit keys + "x": 1 + "y": 2 + "r": 10 + label: center/big + +mergeOne: + # Merge one map + << : *CENTER + "r": 10 + label: center/big + +mergeMultiple: + # Merge multiple maps + << : [ *CENTER, *BIG ] + label: center/big + +override: + # Override + << : [ *BIG, *LEFT, *SMALL ] + "x": 1 + label: center/big + +shortTag: + # Explicit short merge tag + !!merge "<<" : [ *CENTER, *BIG ] + label: center/big + +longTag: + # Explicit merge long tag + ! "<<" : [ *CENTER, *BIG ] + label: center/big + +inlineMap: + # Inlined map + << : {"x": 1, "y": 2, "r": 10} + label: center/big + +inlineSequenceMap: + # Inlined map in sequence + << : [ *CENTER, {"r": 10} ] + label: center/big +` + +func (s *S) TestMerge(c *C) { + var want = map[interface{}]interface{}{ + "x": 1, + "y": 2, + "r": 10, + "label": "center/big", + } + + var m map[interface{}]interface{} + err := yaml.Unmarshal([]byte(mergeTests), &m) + c.Assert(err, IsNil) + for name, test := range m { + if name == "anchors" { + continue + } + c.Assert(test, DeepEquals, want, Commentf("test %q failed", name)) + } +} + +func (s *S) TestMergeStruct(c *C) { + type Data struct { + X, Y, R int + Label string + } + want := Data{1, 2, 10, "center/big"} + + var m map[string]Data + err := yaml.Unmarshal([]byte(mergeTests), &m) + c.Assert(err, IsNil) + for name, test := range m { + if name == "anchors" { + continue + } + c.Assert(test, Equals, want, Commentf("test %q failed", name)) + } +} + +var unmarshalNullTests = []func() interface{}{ + func() interface{} { var v interface{}; v = "v"; return &v }, + func() interface{} { var s = "s"; return &s }, + func() interface{} { var s = "s"; sptr := &s; return &sptr }, + func() interface{} { var i = 1; return &i }, + func() interface{} { var i = 1; iptr := &i; return &iptr }, + func() interface{} { m := map[string]int{"s": 1}; return &m }, + func() interface{} { m := map[string]int{"s": 1}; return m }, +} + +func (s *S) TestUnmarshalNull(c *C) { + for _, test := range unmarshalNullTests { + item := test() + zero := reflect.Zero(reflect.TypeOf(item).Elem()).Interface() + err := yaml.Unmarshal([]byte("null"), item) + c.Assert(err, IsNil) + if reflect.TypeOf(item).Kind() == reflect.Map { + c.Assert(reflect.ValueOf(item).Interface(), DeepEquals, reflect.MakeMap(reflect.TypeOf(item)).Interface()) + } else { + c.Assert(reflect.ValueOf(item).Elem().Interface(), DeepEquals, zero) + } + } +} + +func (s *S) TestUnmarshalSliceOnPreset(c *C) { + // Issue #48. + v := struct{ A []int }{[]int{1}} + yaml.Unmarshal([]byte("a: [2]"), &v) + c.Assert(v.A, DeepEquals, []int{2}) +} + +func (s *S) TestUnmarshalStrict(c *C) { + v := struct{ A, B int }{} + + err := yaml.UnmarshalStrict([]byte("a: 1\nb: 2"), &v) + c.Check(err, IsNil) + err = yaml.Unmarshal([]byte("a: 1\nb: 2\nc: 3"), &v) + c.Check(err, IsNil) + err = yaml.UnmarshalStrict([]byte("a: 1\nb: 2\nc: 3"), &v) + c.Check(err, ErrorMatches, "yaml: unmarshal errors:\n line 3: field c not found in struct struct { A int; B int }") +} + +//var data []byte +//func init() { +// var err error +// data, err = ioutil.ReadFile("/tmp/file.yaml") +// if err != nil { +// panic(err) +// } +//} +// +//func (s *S) BenchmarkUnmarshal(c *C) { +// var err error +// for i := 0; i < c.N; i++ { +// var v map[string]interface{} +// err = yaml.Unmarshal(data, &v) +// } +// if err != nil { +// panic(err) +// } +//} +// +//func (s *S) BenchmarkMarshal(c *C) { +// var v map[string]interface{} +// yaml.Unmarshal(data, &v) +// c.ResetTimer() +// for i := 0; i < c.N; i++ { +// yaml.Marshal(&v) +// } +//} diff --git a/vendor/gopkg.in/yaml.v2/emitterc.go b/vendor/gopkg.in/yaml.v2/emitterc.go new file mode 100644 index 0000000..dcaf502 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/emitterc.go @@ -0,0 +1,1684 @@ +package yaml + +import ( + "bytes" +) + +// Flush the buffer if needed. +func flush(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + return yaml_emitter_flush(emitter) + } + return true +} + +// Put a character to the output buffer. +func put(emitter *yaml_emitter_t, value byte) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + emitter.buffer[emitter.buffer_pos] = value + emitter.buffer_pos++ + emitter.column++ + return true +} + +// Put a line break to the output buffer. +func put_break(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + switch emitter.line_break { + case yaml_CR_BREAK: + emitter.buffer[emitter.buffer_pos] = '\r' + emitter.buffer_pos += 1 + case yaml_LN_BREAK: + emitter.buffer[emitter.buffer_pos] = '\n' + emitter.buffer_pos += 1 + case yaml_CRLN_BREAK: + emitter.buffer[emitter.buffer_pos+0] = '\r' + emitter.buffer[emitter.buffer_pos+1] = '\n' + emitter.buffer_pos += 2 + default: + panic("unknown line break setting") + } + emitter.column = 0 + emitter.line++ + return true +} + +// Copy a character from a string into buffer. +func write(emitter *yaml_emitter_t, s []byte, i *int) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + p := emitter.buffer_pos + w := width(s[*i]) + switch w { + case 4: + emitter.buffer[p+3] = s[*i+3] + fallthrough + case 3: + emitter.buffer[p+2] = s[*i+2] + fallthrough + case 2: + emitter.buffer[p+1] = s[*i+1] + fallthrough + case 1: + emitter.buffer[p+0] = s[*i+0] + default: + panic("unknown character width") + } + emitter.column++ + emitter.buffer_pos += w + *i += w + return true +} + +// Write a whole string into buffer. +func write_all(emitter *yaml_emitter_t, s []byte) bool { + for i := 0; i < len(s); { + if !write(emitter, s, &i) { + return false + } + } + return true +} + +// Copy a line break character from a string into buffer. +func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool { + if s[*i] == '\n' { + if !put_break(emitter) { + return false + } + *i++ + } else { + if !write(emitter, s, i) { + return false + } + emitter.column = 0 + emitter.line++ + } + return true +} + +// Set an emitter error and return false. +func yaml_emitter_set_emitter_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_EMITTER_ERROR + emitter.problem = problem + return false +} + +// Emit an event. +func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.events = append(emitter.events, *event) + for !yaml_emitter_need_more_events(emitter) { + event := &emitter.events[emitter.events_head] + if !yaml_emitter_analyze_event(emitter, event) { + return false + } + if !yaml_emitter_state_machine(emitter, event) { + return false + } + yaml_event_delete(event) + emitter.events_head++ + } + return true +} + +// Check if we need to accumulate more events before emitting. +// +// We accumulate extra +// - 1 event for DOCUMENT-START +// - 2 events for SEQUENCE-START +// - 3 events for MAPPING-START +// +func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { + if emitter.events_head == len(emitter.events) { + return true + } + var accumulate int + switch emitter.events[emitter.events_head].typ { + case yaml_DOCUMENT_START_EVENT: + accumulate = 1 + break + case yaml_SEQUENCE_START_EVENT: + accumulate = 2 + break + case yaml_MAPPING_START_EVENT: + accumulate = 3 + break + default: + return false + } + if len(emitter.events)-emitter.events_head > accumulate { + return false + } + var level int + for i := emitter.events_head; i < len(emitter.events); i++ { + switch emitter.events[i].typ { + case yaml_STREAM_START_EVENT, yaml_DOCUMENT_START_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT: + level++ + case yaml_STREAM_END_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_END_EVENT, yaml_MAPPING_END_EVENT: + level-- + } + if level == 0 { + return false + } + } + return true +} + +// Append a directive to the directives stack. +func yaml_emitter_append_tag_directive(emitter *yaml_emitter_t, value *yaml_tag_directive_t, allow_duplicates bool) bool { + for i := 0; i < len(emitter.tag_directives); i++ { + if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive") + } + } + + // [Go] Do we actually need to copy this given garbage collection + // and the lack of deallocating destructors? + tag_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(tag_copy.handle, value.handle) + copy(tag_copy.prefix, value.prefix) + emitter.tag_directives = append(emitter.tag_directives, tag_copy) + return true +} + +// Increase the indentation level. +func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool) bool { + emitter.indents = append(emitter.indents, emitter.indent) + if emitter.indent < 0 { + if flow { + emitter.indent = emitter.best_indent + } else { + emitter.indent = 0 + } + } else if !indentless { + emitter.indent += emitter.best_indent + } + return true +} + +// State dispatcher. +func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bool { + switch emitter.state { + default: + case yaml_EMIT_STREAM_START_STATE: + return yaml_emitter_emit_stream_start(emitter, event) + + case yaml_EMIT_FIRST_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, true) + + case yaml_EMIT_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, false) + + case yaml_EMIT_DOCUMENT_CONTENT_STATE: + return yaml_emitter_emit_document_content(emitter, event) + + case yaml_EMIT_DOCUMENT_END_STATE: + return yaml_emitter_emit_document_end(emitter, event) + + case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, true) + + case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, false) + + case yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, true) + + case yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, false) + + case yaml_EMIT_END_STATE: + return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END") + } + panic("invalid emitter state") +} + +// Expect STREAM-START. +func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_STREAM_START_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START") + } + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = event.encoding + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = yaml_UTF8_ENCODING + } + } + if emitter.best_indent < 2 || emitter.best_indent > 9 { + emitter.best_indent = 2 + } + if emitter.best_width >= 0 && emitter.best_width <= emitter.best_indent*2 { + emitter.best_width = 80 + } + if emitter.best_width < 0 { + emitter.best_width = 1<<31 - 1 + } + if emitter.line_break == yaml_ANY_BREAK { + emitter.line_break = yaml_LN_BREAK + } + + emitter.indent = -1 + emitter.line = 0 + emitter.column = 0 + emitter.whitespace = true + emitter.indention = true + + if emitter.encoding != yaml_UTF8_ENCODING { + if !yaml_emitter_write_bom(emitter) { + return false + } + } + emitter.state = yaml_EMIT_FIRST_DOCUMENT_START_STATE + return true +} + +// Expect DOCUMENT-START or STREAM-END. +func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + + if event.typ == yaml_DOCUMENT_START_EVENT { + + if event.version_directive != nil { + if !yaml_emitter_analyze_version_directive(emitter, event.version_directive) { + return false + } + } + + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_analyze_tag_directive(emitter, tag_directive) { + return false + } + if !yaml_emitter_append_tag_directive(emitter, tag_directive, false) { + return false + } + } + + for i := 0; i < len(default_tag_directives); i++ { + tag_directive := &default_tag_directives[i] + if !yaml_emitter_append_tag_directive(emitter, tag_directive, true) { + return false + } + } + + implicit := event.implicit + if !first || emitter.canonical { + implicit = false + } + + if emitter.open_ended && (event.version_directive != nil || len(event.tag_directives) > 0) { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if event.version_directive != nil { + implicit = false + if !yaml_emitter_write_indicator(emitter, []byte("%YAML"), true, false, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("1.1"), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if len(event.tag_directives) > 0 { + implicit = false + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_write_indicator(emitter, []byte("%TAG"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_handle(emitter, tag_directive.handle) { + return false + } + if !yaml_emitter_write_tag_content(emitter, tag_directive.prefix, true) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if yaml_emitter_check_empty_document(emitter) { + implicit = false + } + if !implicit { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) { + return false + } + if emitter.canonical { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE + return true + } + + if event.typ == yaml_STREAM_END_EVENT { + if emitter.open_ended { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_END_STATE + return true + } + + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END") +} + +// Expect the root node. +func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE) + return yaml_emitter_emit_node(emitter, event, true, false, false, false) +} + +// Expect DOCUMENT-END. +func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_DOCUMENT_END_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END") + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !event.implicit { + // [Go] Allocate the slice elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_DOCUMENT_START_STATE + emitter.tag_directives = emitter.tag_directives[:0] + return true +} + +// Expect a flow item node. +func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a flow key node. +func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_MAPPING_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if !emitter.canonical && yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, false) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a flow value node. +func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, false) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block item node. +func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, emitter.mapping_context && !emitter.indention) { + return false + } + } + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'-'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a block key node. +func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, false) { + return false + } + } + if event.typ == yaml_MAPPING_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block value node. +func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, true) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a node. +func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, + root bool, sequence bool, mapping bool, simple_key bool) bool { + + emitter.root_context = root + emitter.sequence_context = sequence + emitter.mapping_context = mapping + emitter.simple_key_context = simple_key + + switch event.typ { + case yaml_ALIAS_EVENT: + return yaml_emitter_emit_alias(emitter, event) + case yaml_SCALAR_EVENT: + return yaml_emitter_emit_scalar(emitter, event) + case yaml_SEQUENCE_START_EVENT: + return yaml_emitter_emit_sequence_start(emitter, event) + case yaml_MAPPING_START_EVENT: + return yaml_emitter_emit_mapping_start(emitter, event) + default: + return yaml_emitter_set_emitter_error(emitter, + "expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS") + } +} + +// Expect ALIAS. +func yaml_emitter_emit_alias(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SCALAR. +func yaml_emitter_emit_scalar(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_select_scalar_style(emitter, event) { + return false + } + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + if !yaml_emitter_process_scalar(emitter) { + return false + } + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SEQUENCE-START. +func yaml_emitter_emit_sequence_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.sequence_style() == yaml_FLOW_SEQUENCE_STYLE || + yaml_emitter_check_empty_sequence(emitter) { + emitter.state = yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE + } + return true +} + +// Expect MAPPING-START. +func yaml_emitter_emit_mapping_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.mapping_style() == yaml_FLOW_MAPPING_STYLE || + yaml_emitter_check_empty_mapping(emitter) { + emitter.state = yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE + } + return true +} + +// Check if the document content is an empty scalar. +func yaml_emitter_check_empty_document(emitter *yaml_emitter_t) bool { + return false // [Go] Huh? +} + +// Check if the next events represent an empty sequence. +func yaml_emitter_check_empty_sequence(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_SEQUENCE_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_SEQUENCE_END_EVENT +} + +// Check if the next events represent an empty mapping. +func yaml_emitter_check_empty_mapping(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_MAPPING_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_MAPPING_END_EVENT +} + +// Check if the next node can be expressed as a simple key. +func yaml_emitter_check_simple_key(emitter *yaml_emitter_t) bool { + length := 0 + switch emitter.events[emitter.events_head].typ { + case yaml_ALIAS_EVENT: + length += len(emitter.anchor_data.anchor) + case yaml_SCALAR_EVENT: + if emitter.scalar_data.multiline { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + + len(emitter.scalar_data.value) + case yaml_SEQUENCE_START_EVENT: + if !yaml_emitter_check_empty_sequence(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + case yaml_MAPPING_START_EVENT: + if !yaml_emitter_check_empty_mapping(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + default: + return false + } + return length <= 128 +} + +// Determine an acceptable scalar style. +func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 + if no_tag && !event.implicit && !event.quoted_implicit { + return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified") + } + + style := event.scalar_style() + if style == yaml_ANY_SCALAR_STYLE { + style = yaml_PLAIN_SCALAR_STYLE + } + if emitter.canonical { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + if emitter.simple_key_context && emitter.scalar_data.multiline { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + if style == yaml_PLAIN_SCALAR_STYLE { + if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || + emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if len(emitter.scalar_data.value) == 0 && (emitter.flow_level > 0 || emitter.simple_key_context) { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if no_tag && !event.implicit { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_SINGLE_QUOTED_SCALAR_STYLE { + if !emitter.scalar_data.single_quoted_allowed { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_LITERAL_SCALAR_STYLE || style == yaml_FOLDED_SCALAR_STYLE { + if !emitter.scalar_data.block_allowed || emitter.flow_level > 0 || emitter.simple_key_context { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + + if no_tag && !event.quoted_implicit && style != yaml_PLAIN_SCALAR_STYLE { + emitter.tag_data.handle = []byte{'!'} + } + emitter.scalar_data.style = style + return true +} + +// Write an achor. +func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { + if emitter.anchor_data.anchor == nil { + return true + } + c := []byte{'&'} + if emitter.anchor_data.alias { + c[0] = '*' + } + if !yaml_emitter_write_indicator(emitter, c, true, false, false) { + return false + } + return yaml_emitter_write_anchor(emitter, emitter.anchor_data.anchor) +} + +// Write a tag. +func yaml_emitter_process_tag(emitter *yaml_emitter_t) bool { + if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { + return true + } + if len(emitter.tag_data.handle) > 0 { + if !yaml_emitter_write_tag_handle(emitter, emitter.tag_data.handle) { + return false + } + if len(emitter.tag_data.suffix) > 0 { + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + } + } else { + // [Go] Allocate these slices elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("!<"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, false, false, false) { + return false + } + } + return true +} + +// Write a scalar. +func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool { + switch emitter.scalar_data.style { + case yaml_PLAIN_SCALAR_STYLE: + return yaml_emitter_write_plain_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_SINGLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_single_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_DOUBLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_double_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_LITERAL_SCALAR_STYLE: + return yaml_emitter_write_literal_scalar(emitter, emitter.scalar_data.value) + + case yaml_FOLDED_SCALAR_STYLE: + return yaml_emitter_write_folded_scalar(emitter, emitter.scalar_data.value) + } + panic("unknown scalar style") +} + +// Check if a %YAML directive is valid. +func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool { + if version_directive.major != 1 || version_directive.minor != 1 { + return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive") + } + return true +} + +// Check if a %TAG directive is valid. +func yaml_emitter_analyze_tag_directive(emitter *yaml_emitter_t, tag_directive *yaml_tag_directive_t) bool { + handle := tag_directive.handle + prefix := tag_directive.prefix + if len(handle) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty") + } + if handle[0] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'") + } + if handle[len(handle)-1] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'") + } + for i := 1; i < len(handle)-1; i += width(handle[i]) { + if !is_alpha(handle, i) { + return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only") + } + } + if len(prefix) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty") + } + return true +} + +// Check if an anchor is valid. +func yaml_emitter_analyze_anchor(emitter *yaml_emitter_t, anchor []byte, alias bool) bool { + if len(anchor) == 0 { + problem := "anchor value must not be empty" + if alias { + problem = "alias value must not be empty" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + for i := 0; i < len(anchor); i += width(anchor[i]) { + if !is_alpha(anchor, i) { + problem := "anchor value must contain alphanumerical characters only" + if alias { + problem = "alias value must contain alphanumerical characters only" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + } + emitter.anchor_data.anchor = anchor + emitter.anchor_data.alias = alias + return true +} + +// Check if a tag is valid. +func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool { + if len(tag) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty") + } + for i := 0; i < len(emitter.tag_directives); i++ { + tag_directive := &emitter.tag_directives[i] + if bytes.HasPrefix(tag, tag_directive.prefix) { + emitter.tag_data.handle = tag_directive.handle + emitter.tag_data.suffix = tag[len(tag_directive.prefix):] + return true + } + } + emitter.tag_data.suffix = tag + return true +} + +// Check if a scalar is valid. +func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool { + var ( + block_indicators = false + flow_indicators = false + line_breaks = false + special_characters = false + + leading_space = false + leading_break = false + trailing_space = false + trailing_break = false + break_space = false + space_break = false + + preceded_by_whitespace = false + followed_by_whitespace = false + previous_space = false + previous_break = false + ) + + emitter.scalar_data.value = value + + if len(value) == 0 { + emitter.scalar_data.multiline = false + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = false + return true + } + + if len(value) >= 3 && ((value[0] == '-' && value[1] == '-' && value[2] == '-') || (value[0] == '.' && value[1] == '.' && value[2] == '.')) { + block_indicators = true + flow_indicators = true + } + + preceded_by_whitespace = true + for i, w := 0, 0; i < len(value); i += w { + w = width(value[i]) + followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) + + if i == 0 { + switch value[i] { + case '#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + flow_indicators = true + block_indicators = true + case '?', ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '-': + if followed_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } else { + switch value[i] { + case ',', '?', '[', ']', '{', '}': + flow_indicators = true + case ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '#': + if preceded_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } + + if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode { + special_characters = true + } + if is_space(value, i) { + if i == 0 { + leading_space = true + } + if i+width(value[i]) == len(value) { + trailing_space = true + } + if previous_break { + break_space = true + } + previous_space = true + previous_break = false + } else if is_break(value, i) { + line_breaks = true + if i == 0 { + leading_break = true + } + if i+width(value[i]) == len(value) { + trailing_break = true + } + if previous_space { + space_break = true + } + previous_space = false + previous_break = true + } else { + previous_space = false + previous_break = false + } + + // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. + preceded_by_whitespace = is_blankz(value, i) + } + + emitter.scalar_data.multiline = line_breaks + emitter.scalar_data.flow_plain_allowed = true + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = true + + if leading_space || leading_break || trailing_space || trailing_break { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if trailing_space { + emitter.scalar_data.block_allowed = false + } + if break_space { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || special_characters { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + emitter.scalar_data.block_allowed = false + } + if line_breaks { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if flow_indicators { + emitter.scalar_data.flow_plain_allowed = false + } + if block_indicators { + emitter.scalar_data.block_plain_allowed = false + } + return true +} + +// Check if the event data is valid. +func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + emitter.anchor_data.anchor = nil + emitter.tag_data.handle = nil + emitter.tag_data.suffix = nil + emitter.scalar_data.value = nil + + switch event.typ { + case yaml_ALIAS_EVENT: + if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) { + return false + } + + case yaml_SCALAR_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || (!event.implicit && !event.quoted_implicit)) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + if !yaml_emitter_analyze_scalar(emitter, event.value) { + return false + } + + case yaml_SEQUENCE_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + + case yaml_MAPPING_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + } + return true +} + +// Write the BOM character. +func yaml_emitter_write_bom(emitter *yaml_emitter_t) bool { + if !flush(emitter) { + return false + } + pos := emitter.buffer_pos + emitter.buffer[pos+0] = '\xEF' + emitter.buffer[pos+1] = '\xBB' + emitter.buffer[pos+2] = '\xBF' + emitter.buffer_pos += 3 + return true +} + +func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool { + indent := emitter.indent + if indent < 0 { + indent = 0 + } + if !emitter.indention || emitter.column > indent || (emitter.column == indent && !emitter.whitespace) { + if !put_break(emitter) { + return false + } + } + for emitter.column < indent { + if !put(emitter, ' ') { + return false + } + } + emitter.whitespace = true + emitter.indention = true + return true +} + +func yaml_emitter_write_indicator(emitter *yaml_emitter_t, indicator []byte, need_whitespace, is_whitespace, is_indention bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, indicator) { + return false + } + emitter.whitespace = is_whitespace + emitter.indention = (emitter.indention && is_indention) + emitter.open_ended = false + return true +} + +func yaml_emitter_write_anchor(emitter *yaml_emitter_t, value []byte) bool { + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_handle(emitter *yaml_emitter_t, value []byte) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_whitespace bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + for i := 0; i < len(value); { + var must_write bool + switch value[i] { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\'', '(', ')', '[', ']': + must_write = true + default: + must_write = is_alpha(value, i) + } + if must_write { + if !write(emitter, value, &i) { + return false + } + } else { + w := width(value[i]) + for k := 0; k < w; k++ { + octet := value[i] + i++ + if !put(emitter, '%') { + return false + } + + c := octet >> 4 + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + + c = octet & 0x0f + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + } + } + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + + emitter.whitespace = false + emitter.indention = false + if emitter.root_context { + emitter.open_ended = true + } + + return true +} + +func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, true, false, false) { + return false + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if value[i] == '\'' { + if !put(emitter, '\'') { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + spaces := false + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, true, false, false) { + return false + } + + for i := 0; i < len(value); { + if !is_printable(value, i) || (!emitter.unicode && !is_ascii(value, i)) || + is_bom(value, i) || is_break(value, i) || + value[i] == '"' || value[i] == '\\' { + + octet := value[i] + + var w int + var v rune + switch { + case octet&0x80 == 0x00: + w, v = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, v = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, v = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, v = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = value[i+k] + v = (v << 6) + (rune(octet) & 0x3F) + } + i += w + + if !put(emitter, '\\') { + return false + } + + var ok bool + switch v { + case 0x00: + ok = put(emitter, '0') + case 0x07: + ok = put(emitter, 'a') + case 0x08: + ok = put(emitter, 'b') + case 0x09: + ok = put(emitter, 't') + case 0x0A: + ok = put(emitter, 'n') + case 0x0b: + ok = put(emitter, 'v') + case 0x0c: + ok = put(emitter, 'f') + case 0x0d: + ok = put(emitter, 'r') + case 0x1b: + ok = put(emitter, 'e') + case 0x22: + ok = put(emitter, '"') + case 0x5c: + ok = put(emitter, '\\') + case 0x85: + ok = put(emitter, 'N') + case 0xA0: + ok = put(emitter, '_') + case 0x2028: + ok = put(emitter, 'L') + case 0x2029: + ok = put(emitter, 'P') + default: + if v <= 0xFF { + ok = put(emitter, 'x') + w = 2 + } else if v <= 0xFFFF { + ok = put(emitter, 'u') + w = 4 + } else { + ok = put(emitter, 'U') + w = 8 + } + for k := (w - 1) * 4; ok && k >= 0; k -= 4 { + digit := byte((v >> uint(k)) & 0x0F) + if digit < 10 { + ok = put(emitter, digit+'0') + } else { + ok = put(emitter, digit+'A'-10) + } + } + } + if !ok { + return false + } + spaces = false + } else if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if is_space(value, i+1) { + if !put(emitter, '\\') { + return false + } + } + i += width(value[i]) + } else if !write(emitter, value, &i) { + return false + } + spaces = true + } else { + if !write(emitter, value, &i) { + return false + } + spaces = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { + if is_space(value, 0) || is_break(value, 0) { + indent_hint := []byte{'0' + byte(emitter.best_indent)} + if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { + return false + } + } + + emitter.open_ended = false + + var chomp_hint [1]byte + if len(value) == 0 { + chomp_hint[0] = '-' + } else { + i := len(value) - 1 + for value[i]&0xC0 == 0x80 { + i-- + } + if !is_break(value, i) { + chomp_hint[0] = '-' + } else if i == 0 { + chomp_hint[0] = '+' + emitter.open_ended = true + } else { + i-- + for value[i]&0xC0 == 0x80 { + i-- + } + if is_break(value, i) { + chomp_hint[0] = '+' + emitter.open_ended = true + } + } + } + if chomp_hint[0] != 0 { + if !yaml_emitter_write_indicator(emitter, chomp_hint[:], false, false, false) { + return false + } + } + return true +} + +func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'|'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + breaks := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + + return true +} + +func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + + breaks := true + leading_spaces := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !breaks && !leading_spaces && value[i] == '\n' { + k := 0 + for is_break(value, k) { + k += width(value[k]) + } + if !is_blankz(value, k) { + if !put_break(emitter) { + return false + } + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + leading_spaces = is_blank(value, i) + } + if !breaks && is_space(value, i) && !is_space(value, i+1) && emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + emitter.indention = false + breaks = false + } + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/encode.go b/vendor/gopkg.in/yaml.v2/encode.go new file mode 100644 index 0000000..84f8499 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/encode.go @@ -0,0 +1,306 @@ +package yaml + +import ( + "encoding" + "fmt" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" +) + +type encoder struct { + emitter yaml_emitter_t + event yaml_event_t + out []byte + flow bool +} + +func newEncoder() (e *encoder) { + e = &encoder{} + e.must(yaml_emitter_initialize(&e.emitter)) + yaml_emitter_set_output_string(&e.emitter, &e.out) + yaml_emitter_set_unicode(&e.emitter, true) + e.must(yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)) + e.emit() + e.must(yaml_document_start_event_initialize(&e.event, nil, nil, true)) + e.emit() + return e +} + +func (e *encoder) finish() { + e.must(yaml_document_end_event_initialize(&e.event, true)) + e.emit() + e.emitter.open_ended = false + e.must(yaml_stream_end_event_initialize(&e.event)) + e.emit() +} + +func (e *encoder) destroy() { + yaml_emitter_delete(&e.emitter) +} + +func (e *encoder) emit() { + // This will internally delete the e.event value. + if !yaml_emitter_emit(&e.emitter, &e.event) && e.event.typ != yaml_DOCUMENT_END_EVENT && e.event.typ != yaml_STREAM_END_EVENT { + e.must(false) + } +} + +func (e *encoder) must(ok bool) { + if !ok { + msg := e.emitter.problem + if msg == "" { + msg = "unknown problem generating YAML content" + } + failf("%s", msg) + } +} + +func (e *encoder) marshal(tag string, in reflect.Value) { + if !in.IsValid() { + e.nilv() + return + } + iface := in.Interface() + if m, ok := iface.(Marshaler); ok { + v, err := m.MarshalYAML() + if err != nil { + fail(err) + } + if v == nil { + e.nilv() + return + } + in = reflect.ValueOf(v) + } else if m, ok := iface.(encoding.TextMarshaler); ok { + text, err := m.MarshalText() + if err != nil { + fail(err) + } + in = reflect.ValueOf(string(text)) + } + switch in.Kind() { + case reflect.Interface: + if in.IsNil() { + e.nilv() + } else { + e.marshal(tag, in.Elem()) + } + case reflect.Map: + e.mapv(tag, in) + case reflect.Ptr: + if in.IsNil() { + e.nilv() + } else { + e.marshal(tag, in.Elem()) + } + case reflect.Struct: + e.structv(tag, in) + case reflect.Slice: + if in.Type().Elem() == mapItemType { + e.itemsv(tag, in) + } else { + e.slicev(tag, in) + } + case reflect.String: + e.stringv(tag, in) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if in.Type() == durationType { + e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String())) + } else { + e.intv(tag, in) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + e.uintv(tag, in) + case reflect.Float32, reflect.Float64: + e.floatv(tag, in) + case reflect.Bool: + e.boolv(tag, in) + default: + panic("cannot marshal type: " + in.Type().String()) + } +} + +func (e *encoder) mapv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + keys := keyList(in.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + e.marshal("", k) + e.marshal("", in.MapIndex(k)) + } + }) +} + +func (e *encoder) itemsv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem) + for _, item := range slice { + e.marshal("", reflect.ValueOf(item.Key)) + e.marshal("", reflect.ValueOf(item.Value)) + } + }) +} + +func (e *encoder) structv(tag string, in reflect.Value) { + sinfo, err := getStructInfo(in.Type()) + if err != nil { + panic(err) + } + e.mappingv(tag, func() { + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = in.FieldByIndex(info.Inline) + } + if info.OmitEmpty && isZero(value) { + continue + } + e.marshal("", reflect.ValueOf(info.Key)) + e.flow = info.Flow + e.marshal("", value) + } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + e.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String())) + } + e.marshal("", k) + e.flow = false + e.marshal("", m.MapIndex(k)) + } + } + } + }) +} + +func (e *encoder) mappingv(tag string, f func()) { + implicit := tag == "" + style := yaml_BLOCK_MAPPING_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_MAPPING_STYLE + } + e.must(yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + f() + e.must(yaml_mapping_end_event_initialize(&e.event)) + e.emit() +} + +func (e *encoder) slicev(tag string, in reflect.Value) { + implicit := tag == "" + style := yaml_BLOCK_SEQUENCE_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + n := in.Len() + for i := 0; i < n; i++ { + e.marshal("", in.Index(i)) + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.emit() +} + +// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1. +// +// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported +// in YAML 1.2 and by this package, but these should be marshalled quoted for +// the time being for compatibility with other parsers. +func isBase60Float(s string) (result bool) { + // Fast path. + if s == "" { + return false + } + c := s[0] + if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 { + return false + } + // Do the full match. + return base60float.MatchString(s) +} + +// From http://yaml.org/type/float.html, except the regular expression there +// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix. +var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) + +func (e *encoder) stringv(tag string, in reflect.Value) { + var style yaml_scalar_style_t + s := in.String() + rtag, rs := resolve("", s) + if rtag == yaml_BINARY_TAG { + if tag == "" || tag == yaml_STR_TAG { + tag = rtag + s = rs.(string) + } else if tag == yaml_BINARY_TAG { + failf("explicitly tagged !!binary data must be base64-encoded") + } else { + failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) + } + } + if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } else if strings.Contains(s, "\n") { + style = yaml_LITERAL_SCALAR_STYLE + } else { + style = yaml_PLAIN_SCALAR_STYLE + } + e.emitScalar(s, "", tag, style) +} + +func (e *encoder) boolv(tag string, in reflect.Value) { + var s string + if in.Bool() { + s = "true" + } else { + s = "false" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) intv(tag string, in reflect.Value) { + s := strconv.FormatInt(in.Int(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) uintv(tag string, in reflect.Value) { + s := strconv.FormatUint(in.Uint(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) floatv(tag string, in reflect.Value) { + // FIXME: Handle 64 bits here. + s := strconv.FormatFloat(float64(in.Float()), 'g', -1, 32) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) nilv() { + e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) { + implicit := tag == "" + e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)) + e.emit() +} diff --git a/vendor/gopkg.in/yaml.v2/encode_test.go b/vendor/gopkg.in/yaml.v2/encode_test.go new file mode 100644 index 0000000..84099bd --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/encode_test.go @@ -0,0 +1,501 @@ +package yaml_test + +import ( + "fmt" + "math" + "strconv" + "strings" + "time" + + . "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + "net" + "os" +) + +var marshalIntTest = 123 + +var marshalTests = []struct { + value interface{} + data string +}{ + { + nil, + "null\n", + }, { + &struct{}{}, + "{}\n", + }, { + map[string]string{"v": "hi"}, + "v: hi\n", + }, { + map[string]interface{}{"v": "hi"}, + "v: hi\n", + }, { + map[string]string{"v": "true"}, + "v: \"true\"\n", + }, { + map[string]string{"v": "false"}, + "v: \"false\"\n", + }, { + map[string]interface{}{"v": true}, + "v: true\n", + }, { + map[string]interface{}{"v": false}, + "v: false\n", + }, { + map[string]interface{}{"v": 10}, + "v: 10\n", + }, { + map[string]interface{}{"v": -10}, + "v: -10\n", + }, { + map[string]uint{"v": 42}, + "v: 42\n", + }, { + map[string]interface{}{"v": int64(4294967296)}, + "v: 4294967296\n", + }, { + map[string]int64{"v": int64(4294967296)}, + "v: 4294967296\n", + }, { + map[string]uint64{"v": 4294967296}, + "v: 4294967296\n", + }, { + map[string]interface{}{"v": "10"}, + "v: \"10\"\n", + }, { + map[string]interface{}{"v": 0.1}, + "v: 0.1\n", + }, { + map[string]interface{}{"v": float64(0.1)}, + "v: 0.1\n", + }, { + map[string]interface{}{"v": -0.1}, + "v: -0.1\n", + }, { + map[string]interface{}{"v": math.Inf(+1)}, + "v: .inf\n", + }, { + map[string]interface{}{"v": math.Inf(-1)}, + "v: -.inf\n", + }, { + map[string]interface{}{"v": math.NaN()}, + "v: .nan\n", + }, { + map[string]interface{}{"v": nil}, + "v: null\n", + }, { + map[string]interface{}{"v": ""}, + "v: \"\"\n", + }, { + map[string][]string{"v": []string{"A", "B"}}, + "v:\n- A\n- B\n", + }, { + map[string][]string{"v": []string{"A", "B\nC"}}, + "v:\n- A\n- |-\n B\n C\n", + }, { + map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}}, + "v:\n- A\n- 1\n- B:\n - 2\n - 3\n", + }, { + map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}}, + "a:\n b: c\n", + }, { + map[string]interface{}{"a": "-"}, + "a: '-'\n", + }, + + // Simple values. + { + &marshalIntTest, + "123\n", + }, + + // Structures + { + &struct{ Hello string }{"world"}, + "hello: world\n", + }, { + &struct { + A struct { + B string + } + }{struct{ B string }{"c"}}, + "a:\n b: c\n", + }, { + &struct { + A *struct { + B string + } + }{&struct{ B string }{"c"}}, + "a:\n b: c\n", + }, { + &struct { + A *struct { + B string + } + }{}, + "a: null\n", + }, { + &struct{ A int }{1}, + "a: 1\n", + }, { + &struct{ A []int }{[]int{1, 2}}, + "a:\n- 1\n- 2\n", + }, { + &struct { + B int "a" + }{1}, + "a: 1\n", + }, { + &struct{ A bool }{true}, + "a: true\n", + }, + + // Conditional flag + { + &struct { + A int "a,omitempty" + B int "b,omitempty" + }{1, 0}, + "a: 1\n", + }, { + &struct { + A int "a,omitempty" + B int "b,omitempty" + }{0, 0}, + "{}\n", + }, { + &struct { + A *struct{ X, y int } "a,omitempty,flow" + }{&struct{ X, y int }{1, 2}}, + "a: {x: 1}\n", + }, { + &struct { + A *struct{ X, y int } "a,omitempty,flow" + }{nil}, + "{}\n", + }, { + &struct { + A *struct{ X, y int } "a,omitempty,flow" + }{&struct{ X, y int }{}}, + "a: {x: 0}\n", + }, { + &struct { + A struct{ X, y int } "a,omitempty,flow" + }{struct{ X, y int }{1, 2}}, + "a: {x: 1}\n", + }, { + &struct { + A struct{ X, y int } "a,omitempty,flow" + }{struct{ X, y int }{0, 1}}, + "{}\n", + }, { + &struct { + A float64 "a,omitempty" + B float64 "b,omitempty" + }{1, 0}, + "a: 1\n", + }, + + // Flow flag + { + &struct { + A []int "a,flow" + }{[]int{1, 2}}, + "a: [1, 2]\n", + }, { + &struct { + A map[string]string "a,flow" + }{map[string]string{"b": "c", "d": "e"}}, + "a: {b: c, d: e}\n", + }, { + &struct { + A struct { + B, D string + } "a,flow" + }{struct{ B, D string }{"c", "e"}}, + "a: {b: c, d: e}\n", + }, + + // Unexported field + { + &struct { + u int + A int + }{0, 1}, + "a: 1\n", + }, + + // Ignored field + { + &struct { + A int + B int "-" + }{1, 2}, + "a: 1\n", + }, + + // Struct inlining + { + &struct { + A int + C inlineB `yaml:",inline"` + }{1, inlineB{2, inlineC{3}}}, + "a: 1\nb: 2\nc: 3\n", + }, + + // Map inlining + { + &struct { + A int + C map[string]int `yaml:",inline"` + }{1, map[string]int{"b": 2, "c": 3}}, + "a: 1\nb: 2\nc: 3\n", + }, + + // Duration + { + map[string]time.Duration{"a": 3 * time.Second}, + "a: 3s\n", + }, + + // Issue #24: bug in map merging logic. + { + map[string]string{"a": ""}, + "a: \n", + }, + + // Issue #34: marshal unsupported base 60 floats quoted for compatibility + // with old YAML 1.1 parsers. + { + map[string]string{"a": "1:1"}, + "a: \"1:1\"\n", + }, + + // Binary data. + { + map[string]string{"a": "\x00"}, + "a: \"\\0\"\n", + }, { + map[string]string{"a": "\x80\x81\x82"}, + "a: !!binary gIGC\n", + }, { + map[string]string{"a": strings.Repeat("\x90", 54)}, + "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n", + }, + + // Ordered maps. + { + &yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}}, + "b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n", + }, + + // Encode unicode as utf-8 rather than in escaped form. + { + map[string]string{"a": "你好"}, + "a: 你好\n", + }, + + // Support encoding.TextMarshaler. + { + map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)}, + "a: 1.2.3.4\n", + }, + { + map[string]time.Time{"a": time.Unix(1424801979, 0)}, + "a: 2015-02-24T18:19:39Z\n", + }, + + // Ensure strings containing ": " are quoted (reported as PR #43, but not reproducible). + { + map[string]string{"a": "b: c"}, + "a: 'b: c'\n", + }, + + // Containing hash mark ('#') in string should be quoted + { + map[string]string{"a": "Hello #comment"}, + "a: 'Hello #comment'\n", + }, + { + map[string]string{"a": "你好 #comment"}, + "a: '你好 #comment'\n", + }, +} + +func (s *S) TestMarshal(c *C) { + defer os.Setenv("TZ", os.Getenv("TZ")) + os.Setenv("TZ", "UTC") + for _, item := range marshalTests { + data, err := yaml.Marshal(item.value) + c.Assert(err, IsNil) + c.Assert(string(data), Equals, item.data) + } +} + +var marshalErrorTests = []struct { + value interface{} + error string + panic string +}{{ + value: &struct { + B int + inlineB ",inline" + }{1, inlineB{2, inlineC{3}}}, + panic: `Duplicated key 'b' in struct struct \{ B int; .*`, +}, { + value: &struct { + A int + B map[string]int ",inline" + }{1, map[string]int{"a": 2}}, + panic: `Can't have key "a" in inlined map; conflicts with struct field`, +}} + +func (s *S) TestMarshalErrors(c *C) { + for _, item := range marshalErrorTests { + if item.panic != "" { + c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic) + } else { + _, err := yaml.Marshal(item.value) + c.Assert(err, ErrorMatches, item.error) + } + } +} + +func (s *S) TestMarshalTypeCache(c *C) { + var data []byte + var err error + func() { + type T struct{ A int } + data, err = yaml.Marshal(&T{}) + c.Assert(err, IsNil) + }() + func() { + type T struct{ B int } + data, err = yaml.Marshal(&T{}) + c.Assert(err, IsNil) + }() + c.Assert(string(data), Equals, "b: 0\n") +} + +var marshalerTests = []struct { + data string + value interface{} +}{ + {"_:\n hi: there\n", map[interface{}]interface{}{"hi": "there"}}, + {"_:\n- 1\n- A\n", []interface{}{1, "A"}}, + {"_: 10\n", 10}, + {"_: null\n", nil}, + {"_: BAR!\n", "BAR!"}, +} + +type marshalerType struct { + value interface{} +} + +func (o marshalerType) MarshalText() ([]byte, error) { + panic("MarshalText called on type with MarshalYAML") +} + +func (o marshalerType) MarshalYAML() (interface{}, error) { + return o.value, nil +} + +type marshalerValue struct { + Field marshalerType "_" +} + +func (s *S) TestMarshaler(c *C) { + for _, item := range marshalerTests { + obj := &marshalerValue{} + obj.Field.value = item.value + data, err := yaml.Marshal(obj) + c.Assert(err, IsNil) + c.Assert(string(data), Equals, string(item.data)) + } +} + +func (s *S) TestMarshalerWholeDocument(c *C) { + obj := &marshalerType{} + obj.value = map[string]string{"hello": "world!"} + data, err := yaml.Marshal(obj) + c.Assert(err, IsNil) + c.Assert(string(data), Equals, "hello: world!\n") +} + +type failingMarshaler struct{} + +func (ft *failingMarshaler) MarshalYAML() (interface{}, error) { + return nil, failingErr +} + +func (s *S) TestMarshalerError(c *C) { + _, err := yaml.Marshal(&failingMarshaler{}) + c.Assert(err, Equals, failingErr) +} + +func (s *S) TestSortedOutput(c *C) { + order := []interface{}{ + false, + true, + 1, + uint(1), + 1.0, + 1.1, + 1.2, + 2, + uint(2), + 2.0, + 2.1, + "", + ".1", + ".2", + ".a", + "1", + "2", + "a!10", + "a/2", + "a/10", + "a~10", + "ab/1", + "b/1", + "b/01", + "b/2", + "b/02", + "b/3", + "b/03", + "b1", + "b01", + "b3", + "c2.10", + "c10.2", + "d1", + "d12", + "d12a", + } + m := make(map[interface{}]int) + for _, k := range order { + m[k] = 1 + } + data, err := yaml.Marshal(m) + c.Assert(err, IsNil) + out := "\n" + string(data) + last := 0 + for i, k := range order { + repr := fmt.Sprint(k) + if s, ok := k.(string); ok { + if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil { + repr = `"` + repr + `"` + } + } + index := strings.Index(out, "\n"+repr+":") + if index == -1 { + c.Fatalf("%#v is not in the output: %#v", k, out) + } + if index < last { + c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out) + } + last = index + } +} diff --git a/vendor/gopkg.in/yaml.v2/example_embedded_test.go b/vendor/gopkg.in/yaml.v2/example_embedded_test.go new file mode 100644 index 0000000..171c093 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/example_embedded_test.go @@ -0,0 +1,41 @@ +package yaml_test + +import ( + "fmt" + "log" + + "gopkg.in/yaml.v2" +) + +// An example showing how to unmarshal embedded +// structs from YAML. + +type StructA struct { + A string `yaml:"a"` +} + +type StructB struct { + // Embedded structs are not treated as embedded in YAML by default. To do that, + // add the ",inline" annotation below + StructA `yaml:",inline"` + B string `yaml:"b"` +} + +var data = ` +a: a string from struct A +b: a string from struct B +` + +func ExampleUnmarshal_embedded() { + var b StructB + + err := yaml.Unmarshal([]byte(data), &b) + if err != nil { + log.Fatalf("cannot unmarshal data: %v", err) + } + fmt.Println(b.A) + fmt.Println(b.B) + // Output: + // a string from struct A + // a string from struct B +} diff --git a/vendor/gopkg.in/yaml.v2/parserc.go b/vendor/gopkg.in/yaml.v2/parserc.go new file mode 100644 index 0000000..81d05df --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/parserc.go @@ -0,0 +1,1095 @@ +package yaml + +import ( + "bytes" +) + +// The parser implements the following grammar: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// implicit_document ::= block_node DOCUMENT-END* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// block_node_or_indentless_sequence ::= +// ALIAS +// | properties (block_content | indentless_block_sequence)? +// | block_content +// | indentless_block_sequence +// block_node ::= ALIAS +// | properties block_content? +// | block_content +// flow_node ::= ALIAS +// | properties flow_content? +// | flow_content +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// block_content ::= block_collection | flow_collection | SCALAR +// flow_content ::= flow_collection | SCALAR +// block_collection ::= block_sequence | block_mapping +// flow_collection ::= flow_sequence | flow_mapping +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// block_mapping ::= BLOCK-MAPPING_START +// ((KEY block_node_or_indentless_sequence?)? +// (VALUE block_node_or_indentless_sequence?)?)* +// BLOCK-END +// flow_sequence ::= FLOW-SEQUENCE-START +// (flow_sequence_entry FLOW-ENTRY)* +// flow_sequence_entry? +// FLOW-SEQUENCE-END +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// flow_mapping ::= FLOW-MAPPING-START +// (flow_mapping_entry FLOW-ENTRY)* +// flow_mapping_entry? +// FLOW-MAPPING-END +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + +// Peek the next token in the token queue. +func peek_token(parser *yaml_parser_t) *yaml_token_t { + if parser.token_available || yaml_parser_fetch_more_tokens(parser) { + return &parser.tokens[parser.tokens_head] + } + return nil +} + +// Remove the next token from the queue (must be called after peek_token). +func skip_token(parser *yaml_parser_t) { + parser.token_available = false + parser.tokens_parsed++ + parser.stream_end_produced = parser.tokens[parser.tokens_head].typ == yaml_STREAM_END_TOKEN + parser.tokens_head++ +} + +// Get the next event. +func yaml_parser_parse(parser *yaml_parser_t, event *yaml_event_t) bool { + // Erase the event object. + *event = yaml_event_t{} + + // No events after the end of the stream or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR || parser.state == yaml_PARSE_END_STATE { + return true + } + + // Generate the next event. + return yaml_parser_state_machine(parser, event) +} + +// Set parser error. +func yaml_parser_set_parser_error(parser *yaml_parser_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +func yaml_parser_set_parser_error_context(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +// State dispatcher. +func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool { + //trace("yaml_parser_state_machine", "state:", parser.state.String()) + + switch parser.state { + case yaml_PARSE_STREAM_START_STATE: + return yaml_parser_parse_stream_start(parser, event) + + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, true) + + case yaml_PARSE_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, false) + + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return yaml_parser_parse_document_content(parser, event) + + case yaml_PARSE_DOCUMENT_END_STATE: + return yaml_parser_parse_document_end(parser, event) + + case yaml_PARSE_BLOCK_NODE_STATE: + return yaml_parser_parse_node(parser, event, true, false) + + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return yaml_parser_parse_node(parser, event, true, true) + + case yaml_PARSE_FLOW_NODE_STATE: + return yaml_parser_parse_node(parser, event, false, false) + + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, true) + + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, false) + + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_indentless_sequence_entry(parser, event) + + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, true) + + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, false) + + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return yaml_parser_parse_block_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, true) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, false) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event) + + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, true) + + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, true) + + default: + panic("invalid parser state") + } +} + +// Parse the production: +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ +func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_STREAM_START_TOKEN { + return yaml_parser_set_parser_error(parser, "did not find expected ", token.start_mark) + } + parser.state = yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + encoding: token.encoding, + } + skip_token(parser) + return true +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* +func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { + + token := peek_token(parser) + if token == nil { + return false + } + + // Parse extra document end indicators. + if !implicit { + for token.typ == yaml_DOCUMENT_END_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + if implicit && token.typ != yaml_VERSION_DIRECTIVE_TOKEN && + token.typ != yaml_TAG_DIRECTIVE_TOKEN && + token.typ != yaml_DOCUMENT_START_TOKEN && + token.typ != yaml_STREAM_END_TOKEN { + // Parse an implicit document. + if !yaml_parser_process_directives(parser, nil, nil) { + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_BLOCK_NODE_STATE + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + } else if token.typ != yaml_STREAM_END_TOKEN { + // Parse an explicit document. + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + start_mark := token.start_mark + if !yaml_parser_process_directives(parser, &version_directive, &tag_directives) { + return false + } + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_DOCUMENT_START_TOKEN { + yaml_parser_set_parser_error(parser, + "did not find expected ", token.start_mark) + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_DOCUMENT_CONTENT_STATE + end_mark := token.end_mark + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: false, + } + skip_token(parser) + + } else { + // Parse the stream end. + parser.state = yaml_PARSE_END_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + } + + return true +} + +// Parse the productions: +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** +// +func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN || + token.typ == yaml_TAG_DIRECTIVE_TOKEN || + token.typ == yaml_DOCUMENT_START_TOKEN || + token.typ == yaml_DOCUMENT_END_TOKEN || + token.typ == yaml_STREAM_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + return yaml_parser_process_empty_scalar(parser, event, + token.start_mark) + } + return yaml_parser_parse_node(parser, event, true, false) +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// +func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + start_mark := token.start_mark + end_mark := token.start_mark + + implicit := true + if token.typ == yaml_DOCUMENT_END_TOKEN { + end_mark = token.end_mark + skip_token(parser) + implicit = false + } + + parser.tag_directives = parser.tag_directives[:0] + + parser.state = yaml_PARSE_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + start_mark: start_mark, + end_mark: end_mark, + implicit: implicit, + } + return true +} + +// Parse the productions: +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** +func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { + //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_ALIAS_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + anchor: token.value, + } + skip_token(parser) + return true + } + + start_mark := token.start_mark + end_mark := token.start_mark + + var tag_token bool + var tag_handle, tag_suffix, anchor []byte + var tag_mark yaml_mark_t + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + start_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } else if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + start_mark = token.start_mark + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + var tag []byte + if tag_token { + if len(tag_handle) == 0 { + tag = tag_suffix + tag_suffix = nil + } else { + for i := range parser.tag_directives { + if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { + tag = append([]byte(nil), parser.tag_directives[i].prefix...) + tag = append(tag, tag_suffix...) + break + } + } + if len(tag) == 0 { + yaml_parser_set_parser_error_context(parser, + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark) + return false + } + } + } + + implicit := len(tag) == 0 + if indentless_sequence && token.typ == yaml_BLOCK_ENTRY_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_SCALAR_TOKEN { + var plain_implicit, quoted_implicit bool + end_mark = token.end_mark + if (len(tag) == 0 && token.style == yaml_PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { + plain_implicit = true + } else if len(tag) == 0 { + quoted_implicit = true + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + value: token.value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(token.style), + } + skip_token(parser) + return true + } + if token.typ == yaml_FLOW_SEQUENCE_START_TOKEN { + // [Go] Some of the events below can be merged as they differ only on style. + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_FLOW_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), + } + return true + } + if len(anchor) > 0 || len(tag) > 0 { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + quoted_implicit: false, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true + } + + context := "while parsing a flow node" + if block { + context = "while parsing a block node" + } + yaml_parser_set_parser_error_context(parser, context, start_mark, + "did not find expected node content", token.start_mark) + return false +} + +// Parse the productions: +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* +// +func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } else { + parser.state = yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } + if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block collection", context_mark, + "did not find expected '-' indicator", token.start_mark) +} + +// Parse the productions: +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * +func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && + token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be token.end_mark? + } + return true +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* +// +// BLOCK-END +// ********* +// +func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_KEY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } else { + parser.state = yaml_PARSE_BLOCK_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } else if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block mapping", context_mark, + "did not find expected key", token.start_mark) +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? +// +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END +// +// +func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow sequence", context_mark, + "did not find expected ',' or ']'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + implicit: true, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + skip_token(parser) + return true + } else if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true +} + +// +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + mark := token.end_mark + skip_token(parser) + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be end_mark? + } + return true +} + +// Parse the productions: +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * +// +func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow mapping", context_mark, + "did not find expected ',' or '}'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } else { + parser.state = yaml_PARSE_FLOW_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + } else if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true +} + +// Parse the productions: +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * +// +func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { + token := peek_token(parser) + if token == nil { + return false + } + if empty { + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Generate an empty scalar event. +func yaml_parser_process_empty_scalar(parser *yaml_parser_t, event *yaml_event_t, mark yaml_mark_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: mark, + end_mark: mark, + value: nil, // Empty + implicit: true, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true +} + +var default_tag_directives = []yaml_tag_directive_t{ + {[]byte("!"), []byte("!")}, + {[]byte("!!"), []byte("tag:yaml.org,2002:")}, +} + +// Parse directives. +func yaml_parser_process_directives(parser *yaml_parser_t, + version_directive_ref **yaml_version_directive_t, + tag_directives_ref *[]yaml_tag_directive_t) bool { + + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + + token := peek_token(parser) + if token == nil { + return false + } + + for token.typ == yaml_VERSION_DIRECTIVE_TOKEN || token.typ == yaml_TAG_DIRECTIVE_TOKEN { + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN { + if version_directive != nil { + yaml_parser_set_parser_error(parser, + "found duplicate %YAML directive", token.start_mark) + return false + } + if token.major != 1 || token.minor != 1 { + yaml_parser_set_parser_error(parser, + "found incompatible YAML document", token.start_mark) + return false + } + version_directive = &yaml_version_directive_t{ + major: token.major, + minor: token.minor, + } + } else if token.typ == yaml_TAG_DIRECTIVE_TOKEN { + value := yaml_tag_directive_t{ + handle: token.value, + prefix: token.prefix, + } + if !yaml_parser_append_tag_directive(parser, value, false, token.start_mark) { + return false + } + tag_directives = append(tag_directives, value) + } + + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + + for i := range default_tag_directives { + if !yaml_parser_append_tag_directive(parser, default_tag_directives[i], true, token.start_mark) { + return false + } + } + + if version_directive_ref != nil { + *version_directive_ref = version_directive + } + if tag_directives_ref != nil { + *tag_directives_ref = tag_directives + } + return true +} + +// Append a tag directive to the directives stack. +func yaml_parser_append_tag_directive(parser *yaml_parser_t, value yaml_tag_directive_t, allow_duplicates bool, mark yaml_mark_t) bool { + for i := range parser.tag_directives { + if bytes.Equal(value.handle, parser.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark) + } + } + + // [Go] I suspect the copy is unnecessary. This was likely done + // because there was no way to track ownership of the data. + value_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(value_copy.handle, value.handle) + copy(value_copy.prefix, value.prefix) + parser.tag_directives = append(parser.tag_directives, value_copy) + return true +} diff --git a/vendor/gopkg.in/yaml.v2/readerc.go b/vendor/gopkg.in/yaml.v2/readerc.go new file mode 100644 index 0000000..f450791 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/readerc.go @@ -0,0 +1,394 @@ +package yaml + +import ( + "io" +) + +// Set the reader error and return 0. +func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { + parser.error = yaml_READER_ERROR + parser.problem = problem + parser.problem_offset = offset + parser.problem_value = value + return false +} + +// Byte order marks. +const ( + bom_UTF8 = "\xef\xbb\xbf" + bom_UTF16LE = "\xff\xfe" + bom_UTF16BE = "\xfe\xff" +) + +// Determine the input stream encoding by checking the BOM symbol. If no BOM is +// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. +func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { + // Ensure that we had enough bytes in the raw buffer. + for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { + if !yaml_parser_update_raw_buffer(parser) { + return false + } + } + + // Determine the encoding. + buf := parser.raw_buffer + pos := parser.raw_buffer_pos + avail := len(buf) - pos + if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { + parser.encoding = yaml_UTF16LE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { + parser.encoding = yaml_UTF16BE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { + parser.encoding = yaml_UTF8_ENCODING + parser.raw_buffer_pos += 3 + parser.offset += 3 + } else { + parser.encoding = yaml_UTF8_ENCODING + } + return true +} + +// Update the raw buffer. +func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { + size_read := 0 + + // Return if the raw buffer is full. + if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { + return true + } + + // Return on EOF. + if parser.eof { + return true + } + + // Move the remaining bytes in the raw buffer to the beginning. + if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { + copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) + } + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] + parser.raw_buffer_pos = 0 + + // Call the read handler to fill the buffer. + size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] + if err == io.EOF { + parser.eof = true + } else if err != nil { + return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) + } + return true +} + +// Ensure that the buffer contains at least `length` characters. +// Return true on success, false on failure. +// +// The length is supposed to be significantly less that the buffer size. +func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { + if parser.read_handler == nil { + panic("read handler must be set") + } + + // If the EOF flag is set and the raw buffer is empty, do nothing. + if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { + return true + } + + // Return if the buffer contains enough characters. + if parser.unread >= length { + return true + } + + // Determine the input encoding if it is not known yet. + if parser.encoding == yaml_ANY_ENCODING { + if !yaml_parser_determine_encoding(parser) { + return false + } + } + + // Move the unread characters to the beginning of the buffer. + buffer_len := len(parser.buffer) + if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { + copy(parser.buffer, parser.buffer[parser.buffer_pos:]) + buffer_len -= parser.buffer_pos + parser.buffer_pos = 0 + } else if parser.buffer_pos == buffer_len { + buffer_len = 0 + parser.buffer_pos = 0 + } + + // Open the whole buffer for writing, and cut it before returning. + parser.buffer = parser.buffer[:cap(parser.buffer)] + + // Fill the buffer until it has enough characters. + first := true + for parser.unread < length { + + // Fill the raw buffer if necessary. + if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { + if !yaml_parser_update_raw_buffer(parser) { + parser.buffer = parser.buffer[:buffer_len] + return false + } + } + first = false + + // Decode the raw buffer. + inner: + for parser.raw_buffer_pos != len(parser.raw_buffer) { + var value rune + var width int + + raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos + + // Decode the next character. + switch parser.encoding { + case yaml_UTF8_ENCODING: + // Decode a UTF-8 character. Check RFC 3629 + // (http://www.ietf.org/rfc/rfc3629.txt) for more details. + // + // The following table (taken from the RFC) is used for + // decoding. + // + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+------------------------------------ + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + // Additionally, the characters in the range 0xD800-0xDFFF + // are prohibited as they are reserved for use with UTF-16 + // surrogate pairs. + + // Determine the length of the UTF-8 sequence. + octet := parser.raw_buffer[parser.raw_buffer_pos] + switch { + case octet&0x80 == 0x00: + width = 1 + case octet&0xE0 == 0xC0: + width = 2 + case octet&0xF0 == 0xE0: + width = 3 + case octet&0xF8 == 0xF0: + width = 4 + default: + // The leading octet is invalid. + return yaml_parser_set_reader_error(parser, + "invalid leading UTF-8 octet", + parser.offset, int(octet)) + } + + // Check if the raw buffer contains an incomplete character. + if width > raw_unread { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-8 octet sequence", + parser.offset, -1) + } + break inner + } + + // Decode the leading octet. + switch { + case octet&0x80 == 0x00: + value = rune(octet & 0x7F) + case octet&0xE0 == 0xC0: + value = rune(octet & 0x1F) + case octet&0xF0 == 0xE0: + value = rune(octet & 0x0F) + case octet&0xF8 == 0xF0: + value = rune(octet & 0x07) + default: + value = 0 + } + + // Check and decode the trailing octets. + for k := 1; k < width; k++ { + octet = parser.raw_buffer[parser.raw_buffer_pos+k] + + // Check if the octet is valid. + if (octet & 0xC0) != 0x80 { + return yaml_parser_set_reader_error(parser, + "invalid trailing UTF-8 octet", + parser.offset+k, int(octet)) + } + + // Decode the octet. + value = (value << 6) + rune(octet&0x3F) + } + + // Check the length of the sequence against the value. + switch { + case width == 1: + case width == 2 && value >= 0x80: + case width == 3 && value >= 0x800: + case width == 4 && value >= 0x10000: + default: + return yaml_parser_set_reader_error(parser, + "invalid length of a UTF-8 sequence", + parser.offset, -1) + } + + // Check the range of the value. + if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { + return yaml_parser_set_reader_error(parser, + "invalid Unicode character", + parser.offset, int(value)) + } + + case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: + var low, high int + if parser.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + low, high = 1, 0 + } + + // The UTF-16 encoding is not as simple as one might + // naively think. Check RFC 2781 + // (http://www.ietf.org/rfc/rfc2781.txt). + // + // Normally, two subsequent bytes describe a Unicode + // character. However a special technique (called a + // surrogate pair) is used for specifying character + // values larger than 0xFFFF. + // + // A surrogate pair consists of two pseudo-characters: + // high surrogate area (0xD800-0xDBFF) + // low surrogate area (0xDC00-0xDFFF) + // + // The following formulas are used for decoding + // and encoding characters using surrogate pairs: + // + // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) + // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) + // W1 = 110110yyyyyyyyyy + // W2 = 110111xxxxxxxxxx + // + // where U is the character value, W1 is the high surrogate + // area, W2 is the low surrogate area. + + // Check for incomplete UTF-16 character. + if raw_unread < 2 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 character", + parser.offset, -1) + } + break inner + } + + // Get the character. + value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) + + // Check for unexpected low surrogate area. + if value&0xFC00 == 0xDC00 { + return yaml_parser_set_reader_error(parser, + "unexpected low surrogate area", + parser.offset, int(value)) + } + + // Check for a high surrogate area. + if value&0xFC00 == 0xD800 { + width = 4 + + // Check for incomplete surrogate pair. + if raw_unread < 4 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 surrogate pair", + parser.offset, -1) + } + break inner + } + + // Get the next character. + value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) + + // Check for a low surrogate area. + if value2&0xFC00 != 0xDC00 { + return yaml_parser_set_reader_error(parser, + "expected low surrogate area", + parser.offset+2, int(value2)) + } + + // Generate the value of the surrogate pair. + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) + } else { + width = 2 + } + + default: + panic("impossible") + } + + // Check if the character is in the allowed range: + // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) + // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) + // | [#x10000-#x10FFFF] (32 bit) + switch { + case value == 0x09: + case value == 0x0A: + case value == 0x0D: + case value >= 0x20 && value <= 0x7E: + case value == 0x85: + case value >= 0xA0 && value <= 0xD7FF: + case value >= 0xE000 && value <= 0xFFFD: + case value >= 0x10000 && value <= 0x10FFFF: + default: + return yaml_parser_set_reader_error(parser, + "control characters are not allowed", + parser.offset, int(value)) + } + + // Move the raw pointers. + parser.raw_buffer_pos += width + parser.offset += width + + // Finally put the character into the buffer. + if value <= 0x7F { + // 0000 0000-0000 007F . 0xxxxxxx + parser.buffer[buffer_len+0] = byte(value) + buffer_len += 1 + } else if value <= 0x7FF { + // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) + parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) + buffer_len += 2 + } else if value <= 0xFFFF { + // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) + buffer_len += 3 + } else { + // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) + buffer_len += 4 + } + + parser.unread++ + } + + // On EOF, put NUL into the buffer and return. + if parser.eof { + parser.buffer[buffer_len] = 0 + buffer_len++ + parser.unread++ + break + } + } + parser.buffer = parser.buffer[:buffer_len] + return true +} diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go new file mode 100644 index 0000000..232313c --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -0,0 +1,208 @@ +package yaml + +import ( + "encoding/base64" + "math" + "regexp" + "strconv" + "strings" + "unicode/utf8" +) + +type resolveMapItem struct { + value interface{} + tag string +} + +var resolveTable = make([]byte, 256) +var resolveMap = make(map[string]resolveMapItem) + +func init() { + t := resolveTable + t[int('+')] = 'S' // Sign + t[int('-')] = 'S' + for _, c := range "0123456789" { + t[int(c)] = 'D' // Digit + } + for _, c := range "yYnNtTfFoO~" { + t[int(c)] = 'M' // In map + } + t[int('.')] = '.' // Float (potentially in map) + + var resolveMapList = []struct { + v interface{} + tag string + l []string + }{ + {true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}}, + {true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}}, + {true, yaml_BOOL_TAG, []string{"on", "On", "ON"}}, + {false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}}, + {false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}}, + {false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}}, + {nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}}, + {math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}}, + {math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}}, + {"<<", yaml_MERGE_TAG, []string{"<<"}}, + } + + m := resolveMap + for _, item := range resolveMapList { + for _, s := range item.l { + m[s] = resolveMapItem{item.v, item.tag} + } + } +} + +const longTagPrefix = "tag:yaml.org,2002:" + +func shortTag(tag string) string { + // TODO This can easily be made faster and produce less garbage. + if strings.HasPrefix(tag, longTagPrefix) { + return "!!" + tag[len(longTagPrefix):] + } + return tag +} + +func longTag(tag string) string { + if strings.HasPrefix(tag, "!!") { + return longTagPrefix + tag[2:] + } + return tag +} + +func resolvableTag(tag string) bool { + switch tag { + case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG: + return true + } + return false +} + +var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`) + +func resolve(tag string, in string) (rtag string, out interface{}) { + if !resolvableTag(tag) { + return tag, in + } + + defer func() { + switch tag { + case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG: + return + } + failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) + }() + + // Any data is accepted as a !!str or !!binary. + // Otherwise, the prefix is enough of a hint about what it might be. + hint := byte('N') + if in != "" { + hint = resolveTable[in[0]] + } + if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG { + // Handle things we can lookup in a map. + if item, ok := resolveMap[in]; ok { + return item.tag, item.value + } + + // Base 60 floats are a bad idea, were dropped in YAML 1.2, and + // are purposefully unsupported here. They're still quoted on + // the way out for compatibility with other parser, though. + + switch hint { + case 'M': + // We've already checked the map above. + + case '.': + // Not in the map, so maybe a normal float. + floatv, err := strconv.ParseFloat(in, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + + case 'D', 'S': + // Int, float, or timestamp. + plain := strings.Replace(in, "_", "", -1) + intv, err := strconv.ParseInt(plain, 0, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain, 0, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + if yamlStyleFloat.MatchString(plain) { + floatv, err := strconv.ParseFloat(plain, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + } + if strings.HasPrefix(plain, "0b") { + intv, err := strconv.ParseInt(plain[2:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 2, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + } else if strings.HasPrefix(plain, "-0b") { + intv, err := strconv.ParseInt(plain[3:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, -int(intv) + } else { + return yaml_INT_TAG, -intv + } + } + } + // XXX Handle timestamps here. + + default: + panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")") + } + } + if tag == yaml_BINARY_TAG { + return yaml_BINARY_TAG, in + } + if utf8.ValidString(in) { + return yaml_STR_TAG, in + } + return yaml_BINARY_TAG, encodeBase64(in) +} + +// encodeBase64 encodes s as base64 that is broken up into multiple lines +// as appropriate for the resulting length. +func encodeBase64(s string) string { + const lineLen = 70 + encLen := base64.StdEncoding.EncodedLen(len(s)) + lines := encLen/lineLen + 1 + buf := make([]byte, encLen*2+lines) + in := buf[0:encLen] + out := buf[encLen:] + base64.StdEncoding.Encode(in, []byte(s)) + k := 0 + for i := 0; i < len(in); i += lineLen { + j := i + lineLen + if j > len(in) { + j = len(in) + } + k += copy(out[k:], in[i:j]) + if lines > 1 { + out[k] = '\n' + k++ + } + } + return string(out[:k]) +} diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go new file mode 100644 index 0000000..0744844 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -0,0 +1,2711 @@ +package yaml + +import ( + "bytes" + "fmt" +) + +// Introduction +// ************ +// +// The following notes assume that you are familiar with the YAML specification +// (http://yaml.org/spec/1.2/spec.html). We mostly follow it, although in +// some cases we are less restrictive that it requires. +// +// The process of transforming a YAML stream into a sequence of events is +// divided on two steps: Scanning and Parsing. +// +// The Scanner transforms the input stream into a sequence of tokens, while the +// parser transform the sequence of tokens produced by the Scanner into a +// sequence of parsing events. +// +// The Scanner is rather clever and complicated. The Parser, on the contrary, +// is a straightforward implementation of a recursive-descendant parser (or, +// LL(1) parser, as it is usually called). +// +// Actually there are two issues of Scanning that might be called "clever", the +// rest is quite straightforward. The issues are "block collection start" and +// "simple keys". Both issues are explained below in details. +// +// Here the Scanning step is explained and implemented. We start with the list +// of all the tokens produced by the Scanner together with short descriptions. +// +// Now, tokens: +// +// STREAM-START(encoding) # The stream start. +// STREAM-END # The stream end. +// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. +// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. +// DOCUMENT-START # '---' +// DOCUMENT-END # '...' +// BLOCK-SEQUENCE-START # Indentation increase denoting a block +// BLOCK-MAPPING-START # sequence or a block mapping. +// BLOCK-END # Indentation decrease. +// FLOW-SEQUENCE-START # '[' +// FLOW-SEQUENCE-END # ']' +// BLOCK-SEQUENCE-START # '{' +// BLOCK-SEQUENCE-END # '}' +// BLOCK-ENTRY # '-' +// FLOW-ENTRY # ',' +// KEY # '?' or nothing (simple keys). +// VALUE # ':' +// ALIAS(anchor) # '*anchor' +// ANCHOR(anchor) # '&anchor' +// TAG(handle,suffix) # '!handle!suffix' +// SCALAR(value,style) # A scalar. +// +// The following two tokens are "virtual" tokens denoting the beginning and the +// end of the stream: +// +// STREAM-START(encoding) +// STREAM-END +// +// We pass the information about the input stream encoding with the +// STREAM-START token. +// +// The next two tokens are responsible for tags: +// +// VERSION-DIRECTIVE(major,minor) +// TAG-DIRECTIVE(handle,prefix) +// +// Example: +// +// %YAML 1.1 +// %TAG ! !foo +// %TAG !yaml! tag:yaml.org,2002: +// --- +// +// The correspoding sequence of tokens: +// +// STREAM-START(utf-8) +// VERSION-DIRECTIVE(1,1) +// TAG-DIRECTIVE("!","!foo") +// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") +// DOCUMENT-START +// STREAM-END +// +// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole +// line. +// +// The document start and end indicators are represented by: +// +// DOCUMENT-START +// DOCUMENT-END +// +// Note that if a YAML stream contains an implicit document (without '---' +// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be +// produced. +// +// In the following examples, we present whole documents together with the +// produced tokens. +// +// 1. An implicit document: +// +// 'a scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// STREAM-END +// +// 2. An explicit document: +// +// --- +// 'a scalar' +// ... +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// SCALAR("a scalar",single-quoted) +// DOCUMENT-END +// STREAM-END +// +// 3. Several documents in a stream: +// +// 'a scalar' +// --- +// 'another scalar' +// --- +// 'yet another scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// DOCUMENT-START +// SCALAR("another scalar",single-quoted) +// DOCUMENT-START +// SCALAR("yet another scalar",single-quoted) +// STREAM-END +// +// We have already introduced the SCALAR token above. The following tokens are +// used to describe aliases, anchors, tag, and scalars: +// +// ALIAS(anchor) +// ANCHOR(anchor) +// TAG(handle,suffix) +// SCALAR(value,style) +// +// The following series of examples illustrate the usage of these tokens: +// +// 1. A recursive sequence: +// +// &A [ *A ] +// +// Tokens: +// +// STREAM-START(utf-8) +// ANCHOR("A") +// FLOW-SEQUENCE-START +// ALIAS("A") +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A tagged scalar: +// +// !!float "3.14" # A good approximation. +// +// Tokens: +// +// STREAM-START(utf-8) +// TAG("!!","float") +// SCALAR("3.14",double-quoted) +// STREAM-END +// +// 3. Various scalar styles: +// +// --- # Implicit empty plain scalars do not produce tokens. +// --- a plain scalar +// --- 'a single-quoted scalar' +// --- "a double-quoted scalar" +// --- |- +// a literal scalar +// --- >- +// a folded +// scalar +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// DOCUMENT-START +// SCALAR("a plain scalar",plain) +// DOCUMENT-START +// SCALAR("a single-quoted scalar",single-quoted) +// DOCUMENT-START +// SCALAR("a double-quoted scalar",double-quoted) +// DOCUMENT-START +// SCALAR("a literal scalar",literal) +// DOCUMENT-START +// SCALAR("a folded scalar",folded) +// STREAM-END +// +// Now it's time to review collection-related tokens. We will start with +// flow collections: +// +// FLOW-SEQUENCE-START +// FLOW-SEQUENCE-END +// FLOW-MAPPING-START +// FLOW-MAPPING-END +// FLOW-ENTRY +// KEY +// VALUE +// +// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and +// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' +// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the +// indicators '?' and ':', which are used for denoting mapping keys and values, +// are represented by the KEY and VALUE tokens. +// +// The following examples show flow collections: +// +// 1. A flow sequence: +// +// [item 1, item 2, item 3] +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-SEQUENCE-START +// SCALAR("item 1",plain) +// FLOW-ENTRY +// SCALAR("item 2",plain) +// FLOW-ENTRY +// SCALAR("item 3",plain) +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A flow mapping: +// +// { +// a simple key: a value, # Note that the KEY token is produced. +// ? a complex key: another value, +// } +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// FLOW-ENTRY +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// FLOW-ENTRY +// FLOW-MAPPING-END +// STREAM-END +// +// A simple key is a key which is not denoted by the '?' indicator. Note that +// the Scanner still produce the KEY token whenever it encounters a simple key. +// +// For scanning block collections, the following tokens are used (note that we +// repeat KEY and VALUE here): +// +// BLOCK-SEQUENCE-START +// BLOCK-MAPPING-START +// BLOCK-END +// BLOCK-ENTRY +// KEY +// VALUE +// +// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation +// increase that precedes a block collection (cf. the INDENT token in Python). +// The token BLOCK-END denote indentation decrease that ends a block collection +// (cf. the DEDENT token in Python). However YAML has some syntax pecularities +// that makes detections of these tokens more complex. +// +// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators +// '-', '?', and ':' correspondingly. +// +// The following examples show how the tokens BLOCK-SEQUENCE-START, +// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: +// +// 1. Block sequences: +// +// - item 1 +// - item 2 +// - +// - item 3.1 +// - item 3.2 +// - +// key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 3.1",plain) +// BLOCK-ENTRY +// SCALAR("item 3.2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Block mappings: +// +// a simple key: a value # The KEY token is produced here. +// ? a complex key +// : another value +// a mapping: +// key 1: value 1 +// key 2: value 2 +// a sequence: +// - item 1 +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// KEY +// SCALAR("a mapping",plain) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML does not always require to start a new block collection from a new +// line. If the current line contains only '-', '?', and ':' indicators, a new +// block collection may start at the current line. The following examples +// illustrate this case: +// +// 1. Collections in a sequence: +// +// - - item 1 +// - item 2 +// - key 1: value 1 +// key 2: value 2 +// - ? complex key +// : complex value +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("complex key") +// VALUE +// SCALAR("complex value") +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Collections in a mapping: +// +// ? a sequence +// : - item 1 +// - item 2 +// ? a mapping +// : key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// KEY +// SCALAR("a mapping",plain) +// VALUE +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML also permits non-indented sequences if they are included into a block +// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: +// +// key: +// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key",plain) +// VALUE +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// + +// Ensure that the buffer contains the required number of characters. +// Return true on success, false on failure (reader error or memory error). +func cache(parser *yaml_parser_t, length int) bool { + // [Go] This was inlined: !cache(A, B) -> unread < B && !update(A, B) + return parser.unread >= length || yaml_parser_update_buffer(parser, length) +} + +// Advance the buffer pointer. +func skip(parser *yaml_parser_t) { + parser.mark.index++ + parser.mark.column++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) +} + +func skip_line(parser *yaml_parser_t) { + if is_crlf(parser.buffer, parser.buffer_pos) { + parser.mark.index += 2 + parser.mark.column = 0 + parser.mark.line++ + parser.unread -= 2 + parser.buffer_pos += 2 + } else if is_break(parser.buffer, parser.buffer_pos) { + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) + } +} + +// Copy a character to a string buffer and advance pointers. +func read(parser *yaml_parser_t, s []byte) []byte { + w := width(parser.buffer[parser.buffer_pos]) + if w == 0 { + panic("invalid character sequence") + } + if len(s) == 0 { + s = make([]byte, 0, 32) + } + if w == 1 && len(s)+w <= cap(s) { + s = s[:len(s)+1] + s[len(s)-1] = parser.buffer[parser.buffer_pos] + parser.buffer_pos++ + } else { + s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) + parser.buffer_pos += w + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + return s +} + +// Copy a line break character to a string buffer and advance pointers. +func read_line(parser *yaml_parser_t, s []byte) []byte { + buf := parser.buffer + pos := parser.buffer_pos + switch { + case buf[pos] == '\r' && buf[pos+1] == '\n': + // CR LF . LF + s = append(s, '\n') + parser.buffer_pos += 2 + parser.mark.index++ + parser.unread-- + case buf[pos] == '\r' || buf[pos] == '\n': + // CR|LF . LF + s = append(s, '\n') + parser.buffer_pos += 1 + case buf[pos] == '\xC2' && buf[pos+1] == '\x85': + // NEL . LF + s = append(s, '\n') + parser.buffer_pos += 2 + case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): + // LS|PS . LS|PS + s = append(s, buf[parser.buffer_pos:pos+3]...) + parser.buffer_pos += 3 + default: + return s + } + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + return s +} + +// Get the next token. +func yaml_parser_scan(parser *yaml_parser_t, token *yaml_token_t) bool { + // Erase the token object. + *token = yaml_token_t{} // [Go] Is this necessary? + + // No tokens after STREAM-END or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR { + return true + } + + // Ensure that the tokens queue contains enough tokens. + if !parser.token_available { + if !yaml_parser_fetch_more_tokens(parser) { + return false + } + } + + // Fetch the next token from the queue. + *token = parser.tokens[parser.tokens_head] + parser.tokens_head++ + parser.tokens_parsed++ + parser.token_available = false + + if token.typ == yaml_STREAM_END_TOKEN { + parser.stream_end_produced = true + } + return true +} + +// Set the scanner error and return false. +func yaml_parser_set_scanner_error(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string) bool { + parser.error = yaml_SCANNER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = parser.mark + return false +} + +func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, context_mark yaml_mark_t, problem string) bool { + context := "while parsing a tag" + if directive { + context = "while parsing a %TAG directive" + } + return yaml_parser_set_scanner_error(parser, context, context_mark, problem) +} + +func trace(args ...interface{}) func() { + pargs := append([]interface{}{"+++"}, args...) + fmt.Println(pargs...) + pargs = append([]interface{}{"---"}, args...) + return func() { fmt.Println(pargs...) } +} + +// Ensure that the tokens queue contains at least one token which can be +// returned to the Parser. +func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { + // While we need more tokens to fetch, do it. + for { + // Check if we really need to fetch more tokens. + need_more_tokens := false + + if parser.tokens_head == len(parser.tokens) { + // Queue is empty. + need_more_tokens = true + } else { + // Check if any potential simple key may occupy the head position. + if !yaml_parser_stale_simple_keys(parser) { + return false + } + + for i := range parser.simple_keys { + simple_key := &parser.simple_keys[i] + if simple_key.possible && simple_key.token_number == parser.tokens_parsed { + need_more_tokens = true + break + } + } + } + + // We are finished. + if !need_more_tokens { + break + } + // Fetch the next token. + if !yaml_parser_fetch_next_token(parser) { + return false + } + } + + parser.token_available = true + return true +} + +// The dispatcher for token fetchers. +func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool { + // Ensure that the buffer is initialized. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we just started scanning. Fetch STREAM-START then. + if !parser.stream_start_produced { + return yaml_parser_fetch_stream_start(parser) + } + + // Eat whitespaces and comments until we reach the next token. + if !yaml_parser_scan_to_next_token(parser) { + return false + } + + // Remove obsolete potential simple keys. + if !yaml_parser_stale_simple_keys(parser) { + return false + } + + // Check the indentation level against the current column. + if !yaml_parser_unroll_indent(parser, parser.mark.column) { + return false + } + + // Ensure that the buffer contains at least 4 characters. 4 is the length + // of the longest indicators ('--- ' and '... '). + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + // Is it the end of the stream? + if is_z(parser.buffer, parser.buffer_pos) { + return yaml_parser_fetch_stream_end(parser) + } + + // Is it a directive? + if parser.mark.column == 0 && parser.buffer[parser.buffer_pos] == '%' { + return yaml_parser_fetch_directive(parser) + } + + buf := parser.buffer + pos := parser.buffer_pos + + // Is it the document start indicator? + if parser.mark.column == 0 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_START_TOKEN) + } + + // Is it the document end indicator? + if parser.mark.column == 0 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN) + } + + // Is it the flow sequence start indicator? + if buf[pos] == '[' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN) + } + + // Is it the flow mapping start indicator? + if parser.buffer[parser.buffer_pos] == '{' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_MAPPING_START_TOKEN) + } + + // Is it the flow sequence end indicator? + if parser.buffer[parser.buffer_pos] == ']' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_SEQUENCE_END_TOKEN) + } + + // Is it the flow mapping end indicator? + if parser.buffer[parser.buffer_pos] == '}' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_MAPPING_END_TOKEN) + } + + // Is it the flow entry indicator? + if parser.buffer[parser.buffer_pos] == ',' { + return yaml_parser_fetch_flow_entry(parser) + } + + // Is it the block entry indicator? + if parser.buffer[parser.buffer_pos] == '-' && is_blankz(parser.buffer, parser.buffer_pos+1) { + return yaml_parser_fetch_block_entry(parser) + } + + // Is it the key indicator? + if parser.buffer[parser.buffer_pos] == '?' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_key(parser) + } + + // Is it the value indicator? + if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_value(parser) + } + + // Is it an alias? + if parser.buffer[parser.buffer_pos] == '*' { + return yaml_parser_fetch_anchor(parser, yaml_ALIAS_TOKEN) + } + + // Is it an anchor? + if parser.buffer[parser.buffer_pos] == '&' { + return yaml_parser_fetch_anchor(parser, yaml_ANCHOR_TOKEN) + } + + // Is it a tag? + if parser.buffer[parser.buffer_pos] == '!' { + return yaml_parser_fetch_tag(parser) + } + + // Is it a literal scalar? + if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, true) + } + + // Is it a folded scalar? + if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, false) + } + + // Is it a single-quoted scalar? + if parser.buffer[parser.buffer_pos] == '\'' { + return yaml_parser_fetch_flow_scalar(parser, true) + } + + // Is it a double-quoted scalar? + if parser.buffer[parser.buffer_pos] == '"' { + return yaml_parser_fetch_flow_scalar(parser, false) + } + + // Is it a plain scalar? + // + // A plain scalar may start with any non-blank characters except + // + // '-', '?', ':', ',', '[', ']', '{', '}', + // '#', '&', '*', '!', '|', '>', '\'', '\"', + // '%', '@', '`'. + // + // In the block context (and, for the '-' indicator, in the flow context + // too), it may also start with the characters + // + // '-', '?', ':' + // + // if it is followed by a non-space character. + // + // The last rule is more restrictive than the specification requires. + // [Go] Make this logic more reasonable. + //switch parser.buffer[parser.buffer_pos] { + //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': + //} + if !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || + parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || + parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || + (parser.buffer[parser.buffer_pos] == '-' && !is_blank(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level == 0 && + (parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && + !is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_plain_scalar(parser) + } + + // If we don't determine the token type so far, it is an error. + return yaml_parser_set_scanner_error(parser, + "while scanning for the next token", parser.mark, + "found character that cannot start any token") +} + +// Check the list of potential simple keys and remove the positions that +// cannot contain simple keys anymore. +func yaml_parser_stale_simple_keys(parser *yaml_parser_t) bool { + // Check for a potential simple key for each flow level. + for i := range parser.simple_keys { + simple_key := &parser.simple_keys[i] + + // The specification requires that a simple key + // + // - is limited to a single line, + // - is shorter than 1024 characters. + if simple_key.possible && (simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index) { + + // Check if the potential simple key to be removed is required. + if simple_key.required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key.mark, + "could not find expected ':'") + } + simple_key.possible = false + } + } + return true +} + +// Check if a simple key may start at the current position and add it if +// needed. +func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { + // A simple key is required at the current position if the scanner is in + // the block context and the current column coincides with the indentation + // level. + + required := parser.flow_level == 0 && parser.indent == parser.mark.column + + // A simple key is required only when it is the first token in the current + // line. Therefore it is always allowed. But we add a check anyway. + if required && !parser.simple_key_allowed { + panic("should not happen") + } + + // + // If the current position may start a simple key, save it. + // + if parser.simple_key_allowed { + simple_key := yaml_simple_key_t{ + possible: true, + required: required, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + } + simple_key.mark = parser.mark + + if !yaml_parser_remove_simple_key(parser) { + return false + } + parser.simple_keys[len(parser.simple_keys)-1] = simple_key + } + return true +} + +// Remove a potential simple key at the current flow level. +func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { + i := len(parser.simple_keys) - 1 + if parser.simple_keys[i].possible { + // If the key is required, it is an error. + if parser.simple_keys[i].required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", parser.simple_keys[i].mark, + "could not find expected ':'") + } + } + // Remove the key from the stack. + parser.simple_keys[i].possible = false + return true +} + +// Increase the flow level and resize the simple key list if needed. +func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { + // Reset the simple key on the next level. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + // Increase the flow level. + parser.flow_level++ + return true +} + +// Decrease the flow level. +func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { + if parser.flow_level > 0 { + parser.flow_level-- + parser.simple_keys = parser.simple_keys[:len(parser.simple_keys)-1] + } + return true +} + +// Push the current indentation level to the stack and set the new level +// the current column is greater than the indentation level. In this case, +// append or insert the specified token into the token queue. +func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml_token_type_t, mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + if parser.indent < column { + // Push the current indentation level to the stack and set the new + // indentation level. + parser.indents = append(parser.indents, parser.indent) + parser.indent = column + + // Create a token and insert it into the queue. + token := yaml_token_t{ + typ: typ, + start_mark: mark, + end_mark: mark, + } + if number > -1 { + number -= parser.tokens_parsed + } + yaml_insert_token(parser, number, &token) + } + return true +} + +// Pop indentation levels from the indents stack until the current level +// becomes less or equal to the column. For each indentation level, append +// the BLOCK-END token. +func yaml_parser_unroll_indent(parser *yaml_parser_t, column int) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + // Loop through the indentation levels in the stack. + for parser.indent > column { + // Create a token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + + // Pop the indentation level. + parser.indent = parser.indents[len(parser.indents)-1] + parser.indents = parser.indents[:len(parser.indents)-1] + } + return true +} + +// Initialize the scanner and produce the STREAM-START token. +func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { + + // Set the initial indentation. + parser.indent = -1 + + // Initialize the simple key stack. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + // A simple key is allowed at the beginning of the stream. + parser.simple_key_allowed = true + + // We have started. + parser.stream_start_produced = true + + // Create the STREAM-START token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_START_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + encoding: parser.encoding, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the STREAM-END token and shut down the scanner. +func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool { + + // Force new line. + if parser.mark.column != 0 { + parser.mark.column = 0 + parser.mark.line++ + } + + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the STREAM-END token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. +func yaml_parser_fetch_directive(parser *yaml_parser_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. + token := yaml_token_t{} + if !yaml_parser_scan_directive(parser, &token) { + return false + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the DOCUMENT-START or DOCUMENT-END token. +func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Consume the token. + start_mark := parser.mark + + skip(parser) + skip(parser) + skip(parser) + + end_mark := parser.mark + + // Create the DOCUMENT-START or DOCUMENT-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. +func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // The indicators '[' and '{' may start a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // Increase the flow level. + if !yaml_parser_increase_flow_level(parser) { + return false + } + + // A simple key may follow the indicators '[' and '{'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. +func yaml_parser_fetch_flow_collection_end(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset any potential simple key on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Decrease the flow level. + if !yaml_parser_decrease_flow_level(parser) { + return false + } + + // No simple keys after the indicators ']' and '}'. + parser.simple_key_allowed = false + + // Consume the token. + + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-ENTRY token. +func yaml_parser_fetch_flow_entry(parser *yaml_parser_t) bool { + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after ','. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_FLOW_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the BLOCK-ENTRY token. +func yaml_parser_fetch_block_entry(parser *yaml_parser_t) bool { + // Check if the scanner is in the block context. + if parser.flow_level == 0 { + // Check if we are allowed to start a new entry. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "block sequence entries are not allowed in this context") + } + // Add the BLOCK-SEQUENCE-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_SEQUENCE_START_TOKEN, parser.mark) { + return false + } + } else { + // It is an error for the '-' indicator to occur in the flow context, + // but we let the Parser detect and report about it because the Parser + // is able to point to the context. + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '-'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the BLOCK-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the KEY token. +func yaml_parser_fetch_key(parser *yaml_parser_t) bool { + + // In the block context, additional checks are required. + if parser.flow_level == 0 { + // Check if we are allowed to start a new key (not nessesary simple). + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping keys are not allowed in this context") + } + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '?' in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the KEY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the VALUE token. +func yaml_parser_fetch_value(parser *yaml_parser_t) bool { + + simple_key := &parser.simple_keys[len(parser.simple_keys)-1] + + // Have we found a simple key? + if simple_key.possible { + // Create the KEY token and insert it into the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: simple_key.mark, + end_mark: simple_key.mark, + } + yaml_insert_token(parser, simple_key.token_number-parser.tokens_parsed, &token) + + // In the block context, we may need to add the BLOCK-MAPPING-START token. + if !yaml_parser_roll_indent(parser, simple_key.mark.column, + simple_key.token_number, + yaml_BLOCK_MAPPING_START_TOKEN, simple_key.mark) { + return false + } + + // Remove the simple key. + simple_key.possible = false + + // A simple key cannot follow another simple key. + parser.simple_key_allowed = false + + } else { + // The ':' indicator follows a complex key. + + // In the block context, extra checks are required. + if parser.flow_level == 0 { + + // Check if we are allowed to start a complex value. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping values are not allowed in this context") + } + + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Simple keys after ':' are allowed in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + } + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the VALUE token and append it to the queue. + token := yaml_token_t{ + typ: yaml_VALUE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the ALIAS or ANCHOR token. +func yaml_parser_fetch_anchor(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // An anchor or an alias could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow an anchor or an alias. + parser.simple_key_allowed = false + + // Create the ALIAS or ANCHOR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_anchor(parser, &token, typ) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the TAG token. +func yaml_parser_fetch_tag(parser *yaml_parser_t) bool { + // A tag could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a tag. + parser.simple_key_allowed = false + + // Create the TAG token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_tag(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. +func yaml_parser_fetch_block_scalar(parser *yaml_parser_t, literal bool) bool { + // Remove any potential simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // A simple key may follow a block scalar. + parser.simple_key_allowed = true + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_block_scalar(parser, &token, literal) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. +func yaml_parser_fetch_flow_scalar(parser *yaml_parser_t, single bool) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_flow_scalar(parser, &token, single) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,plain) token. +func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_plain_scalar(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Eat whitespaces and comments until the next token is found. +func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool { + + // Until the next token is not found. + for { + // Allow the BOM mark to start a line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.mark.column == 0 && is_bom(parser.buffer, parser.buffer_pos) { + skip(parser) + } + + // Eat whitespaces. + // Tabs are allowed: + // - in the flow context + // - in the block context, but not at the beginning of the line or + // after '-', '?', or ':' (complex value). + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Eat a comment until a line break. + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // If it is a line break, eat it. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + + // In the block context, a new line may start a simple key. + if parser.flow_level == 0 { + parser.simple_key_allowed = true + } + } else { + break // We have found a token. + } + } + + return true +} + +// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { + // Eat '%'. + start_mark := parser.mark + skip(parser) + + // Scan the directive name. + var name []byte + if !yaml_parser_scan_directive_name(parser, start_mark, &name) { + return false + } + + // Is it a YAML directive? + if bytes.Equal(name, []byte("YAML")) { + // Scan the VERSION directive value. + var major, minor int8 + if !yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor) { + return false + } + end_mark := parser.mark + + // Create a VERSION-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_VERSION_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + major: major, + minor: minor, + } + + // Is it a TAG directive? + } else if bytes.Equal(name, []byte("TAG")) { + // Scan the TAG directive value. + var handle, prefix []byte + if !yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix) { + return false + } + end_mark := parser.mark + + // Create a TAG-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_TAG_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + prefix: prefix, + } + + // Unknown directive. + } else { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unknown directive name") + return false + } + + // Eat the rest of the line including any comments. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + return true +} + +// Scan the directive name. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^ +// +func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { + // Consume the directive name. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + var s []byte + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the name is empty. + if len(s) == 0 { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "could not find expected directive name") + return false + } + + // Check for an blank character after the name. + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unexpected non-alphabetical character") + return false + } + *name = s + return true +} + +// Scan the value of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^ +func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the major version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, major) { + return false + } + + // Eat '.'. + if parser.buffer[parser.buffer_pos] != '.' { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected digit or '.' character") + } + + skip(parser) + + // Consume the minor version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, minor) { + return false + } + return true +} + +const max_number_length = 2 + +// Scan the version number of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^ +// %YAML 1.1 # a comment \n +// ^ +func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark yaml_mark_t, number *int8) bool { + + // Repeat while the next character is digit. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var value, length int8 + for is_digit(parser.buffer, parser.buffer_pos) { + // Check if the number is too long. + length++ + if length > max_number_length { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "found extremely long version number") + } + value = value*10 + int8(as_digit(parser.buffer, parser.buffer_pos)) + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the number was present. + if length == 0 { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected version number") + } + *number = value + return true +} + +// Scan the value of a TAG-DIRECTIVE token. +// +// Scope: +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { + var handle_value, prefix_value []byte + + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a handle. + if !yaml_parser_scan_tag_handle(parser, true, start_mark, &handle_value) { + return false + } + + // Expect a whitespace. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blank(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace") + return false + } + + // Eat whitespaces. + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a prefix. + if !yaml_parser_scan_tag_uri(parser, true, nil, start_mark, &prefix_value) { + return false + } + + // Expect a whitespace or line break. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace or line break") + return false + } + + *handle = handle_value + *prefix = prefix_value + return true +} + +func yaml_parser_scan_anchor(parser *yaml_parser_t, token *yaml_token_t, typ yaml_token_type_t) bool { + var s []byte + + // Eat the indicator character. + start_mark := parser.mark + skip(parser) + + // Consume the value. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + end_mark := parser.mark + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if len(s) == 0 || + !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || + parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '`') { + context := "while scanning an alias" + if typ == yaml_ANCHOR_TOKEN { + context = "while scanning an anchor" + } + yaml_parser_set_scanner_error(parser, context, start_mark, + "did not find expected alphabetic or numeric character") + return false + } + + // Create a token. + *token = yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + value: s, + } + + return true +} + +/* + * Scan a TAG token. + */ + +func yaml_parser_scan_tag(parser *yaml_parser_t, token *yaml_token_t) bool { + var handle, suffix []byte + + start_mark := parser.mark + + // Check if the tag is in the canonical form. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + if parser.buffer[parser.buffer_pos+1] == '<' { + // Keep the handle as '' + + // Eat '!<' + skip(parser) + skip(parser) + + // Consume the tag value. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + + // Check for '>' and eat it. + if parser.buffer[parser.buffer_pos] != '>' { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find the expected '>'") + return false + } + + skip(parser) + } else { + // The tag has either the '!suffix' or the '!handle!suffix' form. + + // First, try to scan a handle. + if !yaml_parser_scan_tag_handle(parser, false, start_mark, &handle) { + return false + } + + // Check if it is, indeed, handle. + if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { + // Scan the suffix now. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + } else { + // It wasn't a handle after all. Scan the rest of the tag. + if !yaml_parser_scan_tag_uri(parser, false, handle, start_mark, &suffix) { + return false + } + + // Set the handle to '!'. + handle = []byte{'!'} + + // A special case: the '!' tag. Set the handle to '' and the + // suffix to '!'. + if len(suffix) == 0 { + handle, suffix = suffix, handle + } + } + } + + // Check the character which ends the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find expected whitespace or line break") + return false + } + + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_TAG_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + suffix: suffix, + } + return true +} + +// Scan a tag handle. +func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, handle *[]byte) bool { + // Check the initial '!' character. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] != '!' { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + + var s []byte + + // Copy the '!' character. + s = read(parser, s) + + // Copy all subsequent alphabetical and numerical characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the trailing character is '!' and copy it. + if parser.buffer[parser.buffer_pos] == '!' { + s = read(parser, s) + } else { + // It's either the '!' tag or not really a tag handle. If it's a %TAG + // directive, it's an error. If it's a tag token, it must be a part of URI. + if directive && string(s) != "!" { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + } + + *handle = s + return true +} + +// Scan a tag. +func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { + //size_t length = head ? strlen((char *)head) : 0 + var s []byte + hasTag := len(head) > 0 + + // Copy the head if needed. + // + // Note that we don't copy the leading '!' character. + if len(head) > 1 { + s = append(s, head[1:]...) + } + + // Scan the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // The set of characters that may appear in URI is as follows: + // + // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', + // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', + // '%'. + // [Go] Convert this into more reasonable logic. + for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' || + parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '=' || + parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '$' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '.' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '~' || + parser.buffer[parser.buffer_pos] == '*' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '(' || parser.buffer[parser.buffer_pos] == ')' || + parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || + parser.buffer[parser.buffer_pos] == '%' { + // Check if it is a URI-escape sequence. + if parser.buffer[parser.buffer_pos] == '%' { + if !yaml_parser_scan_uri_escapes(parser, directive, start_mark, &s) { + return false + } + } else { + s = read(parser, s) + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + hasTag = true + } + + if !hasTag { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected tag URI") + return false + } + *uri = s + return true +} + +// Decode an URI-escape sequence corresponding to a single UTF-8 character. +func yaml_parser_scan_uri_escapes(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, s *[]byte) bool { + + // Decode the required number of characters. + w := 1024 + for w > 0 { + // Check for a URI-escaped octet. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + + if !(parser.buffer[parser.buffer_pos] == '%' && + is_hex(parser.buffer, parser.buffer_pos+1) && + is_hex(parser.buffer, parser.buffer_pos+2)) { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find URI escaped octet") + } + + // Get the octet. + octet := byte((as_hex(parser.buffer, parser.buffer_pos+1) << 4) + as_hex(parser.buffer, parser.buffer_pos+2)) + + // If it is the leading octet, determine the length of the UTF-8 sequence. + if w == 1024 { + w = width(octet) + if w == 0 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect leading UTF-8 octet") + } + } else { + // Check if the trailing octet is correct. + if octet&0xC0 != 0x80 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect trailing UTF-8 octet") + } + } + + // Copy the octet and move the pointers. + *s = append(*s, octet) + skip(parser) + skip(parser) + skip(parser) + w-- + } + return true +} + +// Scan a block scalar. +func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, literal bool) bool { + // Eat the indicator '|' or '>'. + start_mark := parser.mark + skip(parser) + + // Scan the additional block scalar indicators. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check for a chomping indicator. + var chomping, increment int + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + // Set the chomping method and eat the indicator. + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + + // Check for an indentation indicator. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_digit(parser.buffer, parser.buffer_pos) { + // Check that the indentation is greater than 0. + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + + // Get the indentation level and eat the indicator. + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + } + + } else if is_digit(parser.buffer, parser.buffer_pos) { + // Do the same as above, but in the opposite order. + + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + } + } + + // Eat whitespaces and comments to the end of the line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + end_mark := parser.mark + + // Set the indentation level if it was specified. + var indent int + if increment > 0 { + if parser.indent >= 0 { + indent = parser.indent + increment + } else { + indent = increment + } + } + + // Scan the leading line breaks and determine the indentation level if needed. + var s, leading_break, trailing_breaks []byte + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + + // Scan the block scalar content. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var leading_blank, trailing_blank bool + for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { + // We are at the beginning of a non-empty line. + + // Is it a trailing whitespace? + trailing_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Check if we need to fold the leading line break. + if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { + // Do we need to join the lines by space? + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } + } else { + s = append(s, leading_break...) + } + leading_break = leading_break[:0] + + // Append the remaining line breaks. + s = append(s, trailing_breaks...) + trailing_breaks = trailing_breaks[:0] + + // Is it a leading whitespace? + leading_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Consume the current line. + for !is_breakz(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + leading_break = read_line(parser, leading_break) + + // Eat the following indentation spaces and line breaks. + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + } + + // Chomp the tail. + if chomping != -1 { + s = append(s, leading_break...) + } + if chomping == 1 { + s = append(s, trailing_breaks...) + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_LITERAL_SCALAR_STYLE, + } + if !literal { + token.style = yaml_FOLDED_SCALAR_STYLE + } + return true +} + +// Scan indentation spaces and line breaks for a block scalar. Determine the +// indentation level if needed. +func yaml_parser_scan_block_scalar_breaks(parser *yaml_parser_t, indent *int, breaks *[]byte, start_mark yaml_mark_t, end_mark *yaml_mark_t) bool { + *end_mark = parser.mark + + // Eat the indentation spaces and line breaks. + max_indent := 0 + for { + // Eat the indentation spaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for (*indent == 0 || parser.mark.column < *indent) && is_space(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.mark.column > max_indent { + max_indent = parser.mark.column + } + + // Check for a tab character messing the indentation. + if (*indent == 0 || parser.mark.column < *indent) && is_tab(parser.buffer, parser.buffer_pos) { + return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found a tab character where an indentation space is expected") + } + + // Have we found a non-empty line? + if !is_break(parser.buffer, parser.buffer_pos) { + break + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + // [Go] Should really be returning breaks instead. + *breaks = read_line(parser, *breaks) + *end_mark = parser.mark + } + + // Determine the indentation level if needed. + if *indent == 0 { + *indent = max_indent + if *indent < parser.indent+1 { + *indent = parser.indent + 1 + } + if *indent < 1 { + *indent = 1 + } + } + return true +} + +// Scan a quoted scalar. +func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, single bool) bool { + // Eat the left quote. + start_mark := parser.mark + skip(parser) + + // Consume the content of the quoted scalar. + var s, leading_break, trailing_breaks, whitespaces []byte + for { + // Check that there are no document indicators at the beginning of the line. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected document indicator") + return false + } + + // Check for EOF. + if is_z(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected end of stream") + return false + } + + // Consume non-blank characters. + leading_blanks := false + for !is_blankz(parser.buffer, parser.buffer_pos) { + if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { + // Is is an escaped single quote. + s = append(s, '\'') + skip(parser) + skip(parser) + + } else if single && parser.buffer[parser.buffer_pos] == '\'' { + // It is a right single quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '"' { + // It is a right double quote. + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' && is_break(parser.buffer, parser.buffer_pos+1) { + // It is an escaped line break. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + skip(parser) + skip_line(parser) + leading_blanks = true + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' { + // It is an escape sequence. + code_length := 0 + + // Check the escape character. + switch parser.buffer[parser.buffer_pos+1] { + case '0': + s = append(s, 0) + case 'a': + s = append(s, '\x07') + case 'b': + s = append(s, '\x08') + case 't', '\t': + s = append(s, '\x09') + case 'n': + s = append(s, '\x0A') + case 'v': + s = append(s, '\x0B') + case 'f': + s = append(s, '\x0C') + case 'r': + s = append(s, '\x0D') + case 'e': + s = append(s, '\x1B') + case ' ': + s = append(s, '\x20') + case '"': + s = append(s, '"') + case '\'': + s = append(s, '\'') + case '\\': + s = append(s, '\\') + case 'N': // NEL (#x85) + s = append(s, '\xC2') + s = append(s, '\x85') + case '_': // #xA0 + s = append(s, '\xC2') + s = append(s, '\xA0') + case 'L': // LS (#x2028) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA8') + case 'P': // PS (#x2029) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA9') + case 'x': + code_length = 2 + case 'u': + code_length = 4 + case 'U': + code_length = 8 + default: + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found unknown escape character") + return false + } + + skip(parser) + skip(parser) + + // Consume an arbitrary escape code. + if code_length > 0 { + var value int + + // Scan the character value. + if parser.unread < code_length && !yaml_parser_update_buffer(parser, code_length) { + return false + } + for k := 0; k < code_length; k++ { + if !is_hex(parser.buffer, parser.buffer_pos+k) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "did not find expected hexdecimal number") + return false + } + value = (value << 4) + as_hex(parser.buffer, parser.buffer_pos+k) + } + + // Check the value and write the character. + if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found invalid Unicode character escape code") + return false + } + if value <= 0x7F { + s = append(s, byte(value)) + } else if value <= 0x7FF { + s = append(s, byte(0xC0+(value>>6))) + s = append(s, byte(0x80+(value&0x3F))) + } else if value <= 0xFFFF { + s = append(s, byte(0xE0+(value>>12))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } else { + s = append(s, byte(0xF0+(value>>18))) + s = append(s, byte(0x80+((value>>12)&0x3F))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } + + // Advance the pointer. + for k := 0; k < code_length; k++ { + skip(parser) + } + } + } else { + // It is a non-escaped non-blank character. + s = read(parser, s) + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Check if we are at the end of the scalar. + if single { + if parser.buffer[parser.buffer_pos] == '\'' { + break + } + } else { + if parser.buffer[parser.buffer_pos] == '"' { + break + } + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Join the whitespaces or fold line breaks. + if leading_blanks { + // Do we need to fold line breaks? + if len(leading_break) > 0 && leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Eat the right quote. + skip(parser) + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_SINGLE_QUOTED_SCALAR_STYLE, + } + if !single { + token.style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + return true +} + +// Scan a plain scalar. +func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) bool { + + var s, leading_break, trailing_breaks, whitespaces []byte + var leading_blanks bool + var indent = parser.indent + 1 + + start_mark := parser.mark + end_mark := parser.mark + + // Consume the content of the plain scalar. + for { + // Check for a document indicator. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + break + } + + // Check for a comment. + if parser.buffer[parser.buffer_pos] == '#' { + break + } + + // Consume non-blank characters. + for !is_blankz(parser.buffer, parser.buffer_pos) { + + // Check for 'x:x' in the flow context. TODO: Fix the test "spec-08-13". + if parser.flow_level > 0 && + parser.buffer[parser.buffer_pos] == ':' && + !is_blankz(parser.buffer, parser.buffer_pos+1) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found unexpected ':'") + return false + } + + // Check for indicators that may end a plain scalar. + if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level > 0 && + (parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}')) { + break + } + + // Check if we need to join whitespaces and breaks. + if leading_blanks || len(whitespaces) > 0 { + if leading_blanks { + // Do we need to fold line breaks? + if leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + leading_blanks = false + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Copy the character. + s = read(parser, s) + + end_mark = parser.mark + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Is it the end? + if !(is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos)) { + break + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + + // Check for tab character that abuse indentation. + if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found a tab character that violate indentation") + return false + } + + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check indentation level. + if parser.flow_level == 0 && parser.mark.column < indent { + break + } + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_PLAIN_SCALAR_STYLE, + } + + // Note that we change the 'simple_key_allowed' flag. + if leading_blanks { + parser.simple_key_allowed = true + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/sorter.go b/vendor/gopkg.in/yaml.v2/sorter.go new file mode 100644 index 0000000..5958822 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/sorter.go @@ -0,0 +1,104 @@ +package yaml + +import ( + "reflect" + "unicode" +) + +type keyList []reflect.Value + +func (l keyList) Len() int { return len(l) } +func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyList) Less(i, j int) bool { + a := l[i] + b := l[j] + ak := a.Kind() + bk := b.Kind() + for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { + a = a.Elem() + ak = a.Kind() + } + for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { + b = b.Elem() + bk = b.Kind() + } + af, aok := keyFloat(a) + bf, bok := keyFloat(b) + if aok && bok { + if af != bf { + return af < bf + } + if ak != bk { + return ak < bk + } + return numLess(a, b) + } + if ak != reflect.String || bk != reflect.String { + return ak < bk + } + ar, br := []rune(a.String()), []rune(b.String()) + for i := 0; i < len(ar) && i < len(br); i++ { + if ar[i] == br[i] { + continue + } + al := unicode.IsLetter(ar[i]) + bl := unicode.IsLetter(br[i]) + if al && bl { + return ar[i] < br[i] + } + if al || bl { + return bl + } + var ai, bi int + var an, bn int64 + for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { + an = an*10 + int64(ar[ai]-'0') + } + for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { + bn = bn*10 + int64(br[bi]-'0') + } + if an != bn { + return an < bn + } + if ai != bi { + return ai < bi + } + return ar[i] < br[i] + } + return len(ar) < len(br) +} + +// keyFloat returns a float value for v if it is a number/bool +// and whether it is a number/bool or not. +func keyFloat(v reflect.Value) (f float64, ok bool) { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(v.Int()), true + case reflect.Float32, reflect.Float64: + return v.Float(), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return float64(v.Uint()), true + case reflect.Bool: + if v.Bool() { + return 1, true + } + return 0, true + } + return 0, false +} + +// numLess returns whether a < b. +// a and b must necessarily have the same kind. +func numLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return a.Int() < b.Int() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Bool: + return !a.Bool() && b.Bool() + } + panic("not a number") +} diff --git a/vendor/gopkg.in/yaml.v2/suite_test.go b/vendor/gopkg.in/yaml.v2/suite_test.go new file mode 100644 index 0000000..c5cf1ed --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/suite_test.go @@ -0,0 +1,12 @@ +package yaml_test + +import ( + . "gopkg.in/check.v1" + "testing" +) + +func Test(t *testing.T) { TestingT(t) } + +type S struct{} + +var _ = Suite(&S{}) diff --git a/vendor/gopkg.in/yaml.v2/writerc.go b/vendor/gopkg.in/yaml.v2/writerc.go new file mode 100644 index 0000000..190362f --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/writerc.go @@ -0,0 +1,89 @@ +package yaml + +// Set the writer error and return false. +func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_WRITER_ERROR + emitter.problem = problem + return false +} + +// Flush the output buffer. +func yaml_emitter_flush(emitter *yaml_emitter_t) bool { + if emitter.write_handler == nil { + panic("write handler not set") + } + + // Check if the buffer is empty. + if emitter.buffer_pos == 0 { + return true + } + + // If the output encoding is UTF-8, we don't need to recode the buffer. + if emitter.encoding == yaml_UTF8_ENCODING { + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + return true + } + + // Recode the buffer into the raw buffer. + var low, high int + if emitter.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + high, low = 1, 0 + } + + pos := 0 + for pos < emitter.buffer_pos { + // See the "reader.c" code for more details on UTF-8 encoding. Note + // that we assume that the buffer contains a valid UTF-8 sequence. + + // Read the next UTF-8 character. + octet := emitter.buffer[pos] + + var w int + var value rune + switch { + case octet&0x80 == 0x00: + w, value = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, value = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, value = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, value = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = emitter.buffer[pos+k] + value = (value << 6) + (rune(octet) & 0x3F) + } + pos += w + + // Write the character. + if value < 0x10000 { + var b [2]byte + b[high] = byte(value >> 8) + b[low] = byte(value & 0xFF) + emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1]) + } else { + // Write the character using a surrogate pair (check "reader.c"). + var b [4]byte + value -= 0x10000 + b[high] = byte(0xD8 + (value >> 18)) + b[low] = byte((value >> 10) & 0xFF) + b[high+2] = byte(0xDC + ((value >> 8) & 0xFF)) + b[low+2] = byte(value & 0xFF) + emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1], b[2], b[3]) + } + } + + // Write the raw buffer. + if err := emitter.write_handler(emitter, emitter.raw_buffer); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + emitter.raw_buffer = emitter.raw_buffer[:0] + return true +} diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v2/yaml.go new file mode 100644 index 0000000..5e3c2da --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yaml.go @@ -0,0 +1,357 @@ +// Package yaml implements YAML support for the Go language. +// +// Source code and other details for the project are available at GitHub: +// +// https://github.com/go-yaml/yaml +// +package yaml + +import ( + "errors" + "fmt" + "reflect" + "strings" + "sync" +) + +// MapSlice encodes and decodes as a YAML map. +// The order of keys is preserved when encoding and decoding. +type MapSlice []MapItem + +// MapItem is an item in a MapSlice. +type MapItem struct { + Key, Value interface{} +} + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a YAML document. The UnmarshalYAML +// method receives a function that may be called to unmarshal the original +// YAML value into a field or variable. It is safe to call the unmarshal +// function parameter more than once if necessary. +type Unmarshaler interface { + UnmarshalYAML(unmarshal func(interface{}) error) error +} + +// The Marshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. The returned value +// is marshaled in place of the original value implementing Marshaler. +// +// If an error is returned by MarshalYAML, the marshaling procedure stops +// and returns with the provided error. +type Marshaler interface { + MarshalYAML() (interface{}, error) +} + +// Unmarshal decodes the first document found within the in byte slice +// and assigns decoded values into the out value. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as out +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary for unmarshalling +// the provided data. The out parameter must not be nil. +// +// The type of the decoded values should be compatible with the respective +// values in out. If one or more values cannot be decoded due to a type +// mismatches, decoding continues partially until the end of the YAML +// content, and a *yaml.TypeError is returned with details for all +// missed values. +// +// Struct fields are only unmarshalled if they are exported (have an +// upper case first letter), and are unmarshalled using the field name +// lowercased as the default key. Custom keys may be defined via the +// "yaml" name in the field tag: the content preceding the first comma +// is used as the key, and the following comma-separated options are +// used to tweak the marshalling process (see Marshal). +// Conflicting names result in a runtime error. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Marshal for the format of tags and a list of +// supported tag options. +// +func Unmarshal(in []byte, out interface{}) (err error) { + return unmarshal(in, out, false) +} + +// UnmarshalStrict is like Unmarshal except that any fields that are found +// in the data that do not have corresponding struct members will result in +// an error. +func UnmarshalStrict(in []byte, out interface{}) (err error) { + return unmarshal(in, out, true) +} + +func unmarshal(in []byte, out interface{}, strict bool) (err error) { + defer handleErr(&err) + d := newDecoder(strict) + p := newParser(in) + defer p.destroy() + node := p.parse() + if node != nil { + v := reflect.ValueOf(out) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + d.unmarshal(node, v) + } + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Marshal serializes the value provided into a YAML document. The structure +// of the generated document will reflect the structure of the value itself. +// Maps and pointers (to struct, string, int, etc) are accepted as the in value. +// +// Struct fields are only unmarshalled if they are exported (have an upper case +// first letter), and are unmarshalled using the field name lowercased as the +// default key. Custom keys may be defined via the "yaml" name in the field +// tag: the content preceding the first comma is used as the key, and the +// following comma-separated options are used to tweak the marshalling process. +// Conflicting names result in a runtime error. +// +// The field tag format accepted is: +// +// `(...) yaml:"[][,[,]]" (...)` +// +// The following flags are currently supported: +// +// omitempty Only include the field if it's not set to the zero +// value for the type or to empty slices or maps. +// Does not apply to zero valued structs. +// +// flow Marshal using a flow style (useful for structs, +// sequences and maps). +// +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. +// +// In addition, if the key is "-", the field is ignored. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" +// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" +// +func Marshal(in interface{}) (out []byte, err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshal("", reflect.ValueOf(in)) + e.finish() + out = e.out + return +} + +func handleErr(err *error) { + if v := recover(); v != nil { + if e, ok := v.(yamlError); ok { + *err = e.err + } else { + panic(v) + } + } +} + +type yamlError struct { + err error +} + +func fail(err error) { + panic(yamlError{err}) +} + +func failf(format string, args ...interface{}) { + panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) +} + +// A TypeError is returned by Unmarshal when one or more fields in +// the YAML document cannot be properly decoded into the requested +// types. When this error is returned, the value is still +// unmarshaled partially. +type TypeError struct { + Errors []string +} + +func (e *TypeError) Error() string { + return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n ")) +} + +// -------------------------------------------------------------------------- +// Maintain a mapping of keys to structure field indexes + +// The code in this section was copied from mgo/bson. + +// structInfo holds details for the serialization of fields of +// a given struct. +type structInfo struct { + FieldsMap map[string]fieldInfo + FieldsList []fieldInfo + + // InlineMap is the number of the field in the struct that + // contains an ,inline map, or -1 if there's none. + InlineMap int +} + +type fieldInfo struct { + Key string + Num int + OmitEmpty bool + Flow bool + + // Inline holds the field index if the field is part of an inlined struct. + Inline []int +} + +var structMap = make(map[reflect.Type]*structInfo) +var fieldMapMutex sync.RWMutex + +func getStructInfo(st reflect.Type) (*structInfo, error) { + fieldMapMutex.RLock() + sinfo, found := structMap[st] + fieldMapMutex.RUnlock() + if found { + return sinfo, nil + } + + n := st.NumField() + fieldsMap := make(map[string]fieldInfo) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 + for i := 0; i != n; i++ { + field := st.Field(i) + if field.PkgPath != "" && !field.Anonymous { + continue // Private field + } + + info := fieldInfo{Num: i} + + tag := field.Tag.Get("yaml") + if tag == "" && strings.Index(string(field.Tag), ":") < 0 { + tag = string(field.Tag) + } + if tag == "-" { + continue + } + + inline := false + fields := strings.Split(tag, ",") + if len(fields) > 1 { + for _, flag := range fields[1:] { + switch flag { + case "omitempty": + info.OmitEmpty = true + case "flow": + info.Flow = true + case "inline": + inline = true + default: + return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)) + } + } + tag = fields[0] + } + + if inline { + switch field.Type.Kind() { + case reflect.Map: + if inlineMap >= 0 { + return nil, errors.New("Multiple ,inline maps in struct " + st.String()) + } + if field.Type.Key() != reflect.TypeOf("") { + return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) + } + inlineMap = info.Num + case reflect.Struct: + sinfo, err := getStructInfo(field.Type) + if err != nil { + return nil, err + } + for _, finfo := range sinfo.FieldsList { + if _, found := fieldsMap[finfo.Key]; found { + msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + if finfo.Inline == nil { + finfo.Inline = []int{i, finfo.Num} + } else { + finfo.Inline = append([]int{i}, finfo.Inline...) + } + fieldsMap[finfo.Key] = finfo + fieldsList = append(fieldsList, finfo) + } + default: + //return nil, errors.New("Option ,inline needs a struct value or map field") + return nil, errors.New("Option ,inline needs a struct value field") + } + continue + } + + if tag != "" { + info.Key = tag + } else { + info.Key = strings.ToLower(field.Name) + } + + if _, found = fieldsMap[info.Key]; found { + msg := "Duplicated key '" + info.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + + fieldsList = append(fieldsList, info) + fieldsMap[info.Key] = info + } + + sinfo = &structInfo{fieldsMap, fieldsList, inlineMap} + + fieldMapMutex.Lock() + structMap[st] = sinfo + fieldMapMutex.Unlock() + return sinfo, nil +} + +func isZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice: + return v.Len() == 0 + case reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // Private field + } + if !isZero(v.Field(i)) { + return false + } + } + return true + } + return false +} diff --git a/vendor/gopkg.in/yaml.v2/yamlh.go b/vendor/gopkg.in/yaml.v2/yamlh.go new file mode 100644 index 0000000..3caeca0 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlh.go @@ -0,0 +1,716 @@ +package yaml + +import ( + "io" +) + +// The version directive data. +type yaml_version_directive_t struct { + major int8 // The major version number. + minor int8 // The minor version number. +} + +// The tag directive data. +type yaml_tag_directive_t struct { + handle []byte // The tag handle. + prefix []byte // The tag prefix. +} + +type yaml_encoding_t int + +// The stream encoding. +const ( + // Let the parser choose the encoding. + yaml_ANY_ENCODING yaml_encoding_t = iota + + yaml_UTF8_ENCODING // The default UTF-8 encoding. + yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. + yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. +) + +type yaml_break_t int + +// Line break types. +const ( + // Let the parser choose the break type. + yaml_ANY_BREAK yaml_break_t = iota + + yaml_CR_BREAK // Use CR for line breaks (Mac style). + yaml_LN_BREAK // Use LN for line breaks (Unix style). + yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style). +) + +type yaml_error_type_t int + +// Many bad things could happen with the parser and emitter. +const ( + // No error is produced. + yaml_NO_ERROR yaml_error_type_t = iota + + yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory. + yaml_READER_ERROR // Cannot read or decode the input stream. + yaml_SCANNER_ERROR // Cannot scan the input stream. + yaml_PARSER_ERROR // Cannot parse the input stream. + yaml_COMPOSER_ERROR // Cannot compose a YAML document. + yaml_WRITER_ERROR // Cannot write to the output stream. + yaml_EMITTER_ERROR // Cannot emit a YAML stream. +) + +// The pointer position. +type yaml_mark_t struct { + index int // The position index. + line int // The position line. + column int // The position column. +} + +// Node Styles + +type yaml_style_t int8 + +type yaml_scalar_style_t yaml_style_t + +// Scalar styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota + + yaml_PLAIN_SCALAR_STYLE // The plain scalar style. + yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. + yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. + yaml_LITERAL_SCALAR_STYLE // The literal scalar style. + yaml_FOLDED_SCALAR_STYLE // The folded scalar style. +) + +type yaml_sequence_style_t yaml_style_t + +// Sequence styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota + + yaml_BLOCK_SEQUENCE_STYLE // The block sequence style. + yaml_FLOW_SEQUENCE_STYLE // The flow sequence style. +) + +type yaml_mapping_style_t yaml_style_t + +// Mapping styles. +const ( + // Let the emitter choose the style. + yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota + + yaml_BLOCK_MAPPING_STYLE // The block mapping style. + yaml_FLOW_MAPPING_STYLE // The flow mapping style. +) + +// Tokens + +type yaml_token_type_t int + +// Token types. +const ( + // An empty token. + yaml_NO_TOKEN yaml_token_type_t = iota + + yaml_STREAM_START_TOKEN // A STREAM-START token. + yaml_STREAM_END_TOKEN // A STREAM-END token. + + yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. + yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. + yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token. + yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token. + + yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. + yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. + yaml_BLOCK_END_TOKEN // A BLOCK-END token. + + yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. + yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. + yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. + yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. + + yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. + yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. + yaml_KEY_TOKEN // A KEY token. + yaml_VALUE_TOKEN // A VALUE token. + + yaml_ALIAS_TOKEN // An ALIAS token. + yaml_ANCHOR_TOKEN // An ANCHOR token. + yaml_TAG_TOKEN // A TAG token. + yaml_SCALAR_TOKEN // A SCALAR token. +) + +func (tt yaml_token_type_t) String() string { + switch tt { + case yaml_NO_TOKEN: + return "yaml_NO_TOKEN" + case yaml_STREAM_START_TOKEN: + return "yaml_STREAM_START_TOKEN" + case yaml_STREAM_END_TOKEN: + return "yaml_STREAM_END_TOKEN" + case yaml_VERSION_DIRECTIVE_TOKEN: + return "yaml_VERSION_DIRECTIVE_TOKEN" + case yaml_TAG_DIRECTIVE_TOKEN: + return "yaml_TAG_DIRECTIVE_TOKEN" + case yaml_DOCUMENT_START_TOKEN: + return "yaml_DOCUMENT_START_TOKEN" + case yaml_DOCUMENT_END_TOKEN: + return "yaml_DOCUMENT_END_TOKEN" + case yaml_BLOCK_SEQUENCE_START_TOKEN: + return "yaml_BLOCK_SEQUENCE_START_TOKEN" + case yaml_BLOCK_MAPPING_START_TOKEN: + return "yaml_BLOCK_MAPPING_START_TOKEN" + case yaml_BLOCK_END_TOKEN: + return "yaml_BLOCK_END_TOKEN" + case yaml_FLOW_SEQUENCE_START_TOKEN: + return "yaml_FLOW_SEQUENCE_START_TOKEN" + case yaml_FLOW_SEQUENCE_END_TOKEN: + return "yaml_FLOW_SEQUENCE_END_TOKEN" + case yaml_FLOW_MAPPING_START_TOKEN: + return "yaml_FLOW_MAPPING_START_TOKEN" + case yaml_FLOW_MAPPING_END_TOKEN: + return "yaml_FLOW_MAPPING_END_TOKEN" + case yaml_BLOCK_ENTRY_TOKEN: + return "yaml_BLOCK_ENTRY_TOKEN" + case yaml_FLOW_ENTRY_TOKEN: + return "yaml_FLOW_ENTRY_TOKEN" + case yaml_KEY_TOKEN: + return "yaml_KEY_TOKEN" + case yaml_VALUE_TOKEN: + return "yaml_VALUE_TOKEN" + case yaml_ALIAS_TOKEN: + return "yaml_ALIAS_TOKEN" + case yaml_ANCHOR_TOKEN: + return "yaml_ANCHOR_TOKEN" + case yaml_TAG_TOKEN: + return "yaml_TAG_TOKEN" + case yaml_SCALAR_TOKEN: + return "yaml_SCALAR_TOKEN" + } + return "" +} + +// The token structure. +type yaml_token_t struct { + // The token type. + typ yaml_token_type_t + + // The start/end of the token. + start_mark, end_mark yaml_mark_t + + // The stream encoding (for yaml_STREAM_START_TOKEN). + encoding yaml_encoding_t + + // The alias/anchor/scalar value or tag/tag directive handle + // (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN). + value []byte + + // The tag suffix (for yaml_TAG_TOKEN). + suffix []byte + + // The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN). + prefix []byte + + // The scalar style (for yaml_SCALAR_TOKEN). + style yaml_scalar_style_t + + // The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN). + major, minor int8 +} + +// Events + +type yaml_event_type_t int8 + +// Event types. +const ( + // An empty event. + yaml_NO_EVENT yaml_event_type_t = iota + + yaml_STREAM_START_EVENT // A STREAM-START event. + yaml_STREAM_END_EVENT // A STREAM-END event. + yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event. + yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event. + yaml_ALIAS_EVENT // An ALIAS event. + yaml_SCALAR_EVENT // A SCALAR event. + yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event. + yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event. + yaml_MAPPING_START_EVENT // A MAPPING-START event. + yaml_MAPPING_END_EVENT // A MAPPING-END event. +) + +// The event structure. +type yaml_event_t struct { + + // The event type. + typ yaml_event_type_t + + // The start and end of the event. + start_mark, end_mark yaml_mark_t + + // The document encoding (for yaml_STREAM_START_EVENT). + encoding yaml_encoding_t + + // The version directive (for yaml_DOCUMENT_START_EVENT). + version_directive *yaml_version_directive_t + + // The list of tag directives (for yaml_DOCUMENT_START_EVENT). + tag_directives []yaml_tag_directive_t + + // The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT). + anchor []byte + + // The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + tag []byte + + // The scalar value (for yaml_SCALAR_EVENT). + value []byte + + // Is the document start/end indicator implicit, or the tag optional? + // (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT). + implicit bool + + // Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT). + quoted_implicit bool + + // The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + style yaml_style_t +} + +func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) } +func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) } +func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) } + +// Nodes + +const ( + yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. + yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. + yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. + yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. + yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. + yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. + + yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. + yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. + + // Not in original libyaml. + yaml_BINARY_TAG = "tag:yaml.org,2002:binary" + yaml_MERGE_TAG = "tag:yaml.org,2002:merge" + + yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str. + yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq. + yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map. +) + +type yaml_node_type_t int + +// Node types. +const ( + // An empty node. + yaml_NO_NODE yaml_node_type_t = iota + + yaml_SCALAR_NODE // A scalar node. + yaml_SEQUENCE_NODE // A sequence node. + yaml_MAPPING_NODE // A mapping node. +) + +// An element of a sequence node. +type yaml_node_item_t int + +// An element of a mapping node. +type yaml_node_pair_t struct { + key int // The key of the element. + value int // The value of the element. +} + +// The node structure. +type yaml_node_t struct { + typ yaml_node_type_t // The node type. + tag []byte // The node tag. + + // The node data. + + // The scalar parameters (for yaml_SCALAR_NODE). + scalar struct { + value []byte // The scalar value. + length int // The length of the scalar value. + style yaml_scalar_style_t // The scalar style. + } + + // The sequence parameters (for YAML_SEQUENCE_NODE). + sequence struct { + items_data []yaml_node_item_t // The stack of sequence items. + style yaml_sequence_style_t // The sequence style. + } + + // The mapping parameters (for yaml_MAPPING_NODE). + mapping struct { + pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value). + pairs_start *yaml_node_pair_t // The beginning of the stack. + pairs_end *yaml_node_pair_t // The end of the stack. + pairs_top *yaml_node_pair_t // The top of the stack. + style yaml_mapping_style_t // The mapping style. + } + + start_mark yaml_mark_t // The beginning of the node. + end_mark yaml_mark_t // The end of the node. + +} + +// The document structure. +type yaml_document_t struct { + + // The document nodes. + nodes []yaml_node_t + + // The version directive. + version_directive *yaml_version_directive_t + + // The list of tag directives. + tag_directives_data []yaml_tag_directive_t + tag_directives_start int // The beginning of the tag directives list. + tag_directives_end int // The end of the tag directives list. + + start_implicit int // Is the document start indicator implicit? + end_implicit int // Is the document end indicator implicit? + + // The start/end of the document. + start_mark, end_mark yaml_mark_t +} + +// The prototype of a read handler. +// +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. +// +// [in,out] data A pointer to an application data specified by +// yaml_parser_set_input(). +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. +// +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. +type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) + +// This structure holds information about a potential simple key. +type yaml_simple_key_t struct { + possible bool // Is a simple key possible? + required bool // Is a simple key required? + token_number int // The number of the token. + mark yaml_mark_t // The position mark. +} + +// The states of the parser. +type yaml_parser_state_t int + +const ( + yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota + + yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. + yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. + yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_PARSE_BLOCK_NODE_STATE // Expect a block node. + yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence. + yaml_PARSE_FLOW_NODE_STATE // Expect a flow node. + yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. + yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. + yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. + yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. + yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. + yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. + yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. + yaml_PARSE_END_STATE // Expect nothing. +) + +func (ps yaml_parser_state_t) String() string { + switch ps { + case yaml_PARSE_STREAM_START_STATE: + return "yaml_PARSE_STREAM_START_STATE" + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_START_STATE: + return "yaml_PARSE_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return "yaml_PARSE_DOCUMENT_CONTENT_STATE" + case yaml_PARSE_DOCUMENT_END_STATE: + return "yaml_PARSE_DOCUMENT_END_STATE" + case yaml_PARSE_BLOCK_NODE_STATE: + return "yaml_PARSE_BLOCK_NODE_STATE" + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE" + case yaml_PARSE_FLOW_NODE_STATE: + return "yaml_PARSE_FLOW_NODE_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" + case yaml_PARSE_END_STATE: + return "yaml_PARSE_END_STATE" + } + return "" +} + +// This structure holds aliases data. +type yaml_alias_data_t struct { + anchor []byte // The anchor. + index int // The node id. + mark yaml_mark_t // The anchor mark. +} + +// The parser structure. +// +// All members are internal. Manage the structure using the +// yaml_parser_ family of functions. +type yaml_parser_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + + problem string // Error description. + + // The byte about which the problem occurred. + problem_offset int + problem_value int + problem_mark yaml_mark_t + + // The error context. + context string + context_mark yaml_mark_t + + // Reader stuff + + read_handler yaml_read_handler_t // Read handler. + + input_file io.Reader // File input data. + input []byte // String input data. + input_pos int + + eof bool // EOF flag + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + unread int // The number of unread characters in the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The input encoding. + + offset int // The offset of the current position (in bytes). + mark yaml_mark_t // The mark of the current position. + + // Scanner stuff + + stream_start_produced bool // Have we started to scan the input stream? + stream_end_produced bool // Have we reached the end of the input stream? + + flow_level int // The number of unclosed '[' and '{' indicators. + + tokens []yaml_token_t // The tokens queue. + tokens_head int // The head of the tokens queue. + tokens_parsed int // The number of tokens fetched from the queue. + token_available bool // Does the tokens queue contain a token ready for dequeueing. + + indent int // The current indentation level. + indents []int // The indentation levels stack. + + simple_key_allowed bool // May a simple key occur at the current position? + simple_keys []yaml_simple_key_t // The stack of simple keys. + + // Parser stuff + + state yaml_parser_state_t // The current parser state. + states []yaml_parser_state_t // The parser states stack. + marks []yaml_mark_t // The stack of marks. + tag_directives []yaml_tag_directive_t // The list of TAG directives. + + // Dumper stuff + + aliases []yaml_alias_data_t // The alias data. + + document *yaml_document_t // The currently parsed document. +} + +// Emitter Definitions + +// The prototype of a write handler. +// +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. +// +// @param[in,out] data A pointer to an application data specified by +// yaml_emitter_set_output(). +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. +// +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. +// +type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error + +type yaml_emitter_state_t int + +// The emitter states. +const ( + // Expect STREAM-START. + yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota + + yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. + yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. + yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. + yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. + yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. + yaml_EMIT_END_STATE // Expect nothing. +) + +// The emitter structure. +// +// All members are internal. Manage the structure using the @c yaml_emitter_ +// family of functions. +type yaml_emitter_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + problem string // Error description. + + // Writer stuff + + write_handler yaml_write_handler_t // Write handler. + + output_buffer *[]byte // String output data. + output_file io.Writer // File output data. + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The stream encoding. + + // Emitter stuff + + canonical bool // If the output is in the canonical style? + best_indent int // The number of indentation spaces. + best_width int // The preferred width of the output lines. + unicode bool // Allow unescaped non-ASCII characters? + line_break yaml_break_t // The preferred line break. + + state yaml_emitter_state_t // The current emitter state. + states []yaml_emitter_state_t // The stack of states. + + events []yaml_event_t // The event queue. + events_head int // The head of the event queue. + + indents []int // The stack of indentation levels. + + tag_directives []yaml_tag_directive_t // The list of tag directives. + + indent int // The current indentation level. + + flow_level int // The current flow level. + + root_context bool // Is it the document root context? + sequence_context bool // Is it a sequence context? + mapping_context bool // Is it a mapping context? + simple_key_context bool // Is it a simple mapping key context? + + line int // The current line. + column int // The current column. + whitespace bool // If the last character was a whitespace? + indention bool // If the last character was an indentation character (' ', '-', '?', ':')? + open_ended bool // If an explicit document end is required? + + // Anchor analysis. + anchor_data struct { + anchor []byte // The anchor value. + alias bool // Is it an alias? + } + + // Tag analysis. + tag_data struct { + handle []byte // The tag handle. + suffix []byte // The tag suffix. + } + + // Scalar analysis. + scalar_data struct { + value []byte // The scalar value. + multiline bool // Does the scalar contain line breaks? + flow_plain_allowed bool // Can the scalar be expessed in the flow plain style? + block_plain_allowed bool // Can the scalar be expressed in the block plain style? + single_quoted_allowed bool // Can the scalar be expressed in the single quoted style? + block_allowed bool // Can the scalar be expressed in the literal or folded styles? + style yaml_scalar_style_t // The output style. + } + + // Dumper stuff + + opened bool // If the stream was already opened? + closed bool // If the stream was already closed? + + // The information associated with the document nodes. + anchors *struct { + references int // The number of references. + anchor int // The anchor id. + serialized bool // If the node has been emitted? + } + + last_anchor_id int // The last assigned anchor id. + + document *yaml_document_t // The currently emitted document. +} diff --git a/vendor/gopkg.in/yaml.v2/yamlprivateh.go b/vendor/gopkg.in/yaml.v2/yamlprivateh.go new file mode 100644 index 0000000..8110ce3 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlprivateh.go @@ -0,0 +1,173 @@ +package yaml + +const ( + // The size of the input raw buffer. + input_raw_buffer_size = 512 + + // The size of the input buffer. + // It should be possible to decode the whole raw buffer. + input_buffer_size = input_raw_buffer_size * 3 + + // The size of the output buffer. + output_buffer_size = 128 + + // The size of the output raw buffer. + // It should be possible to encode the whole output buffer. + output_raw_buffer_size = (output_buffer_size*2 + 2) + + // The size of other stacks and queues. + initial_stack_size = 16 + initial_queue_size = 16 + initial_string_size = 16 +) + +// Check if the character at the specified position is an alphabetical +// character, a digit, '_', or '-'. +func is_alpha(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' +} + +// Check if the character at the specified position is a digit. +func is_digit(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' +} + +// Get the value of a digit. +func as_digit(b []byte, i int) int { + return int(b[i]) - '0' +} + +// Check if the character at the specified position is a hex-digit. +func is_hex(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' +} + +// Get the value of a hex-digit. +func as_hex(b []byte, i int) int { + bi := b[i] + if bi >= 'A' && bi <= 'F' { + return int(bi) - 'A' + 10 + } + if bi >= 'a' && bi <= 'f' { + return int(bi) - 'a' + 10 + } + return int(bi) - '0' +} + +// Check if the character is ASCII. +func is_ascii(b []byte, i int) bool { + return b[i] <= 0x7F +} + +// Check if the character at the start of the buffer can be printed unescaped. +func is_printable(b []byte, i int) bool { + return ((b[i] == 0x0A) || // . == #x0A + (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E + (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF + (b[i] > 0xC2 && b[i] < 0xED) || + (b[i] == 0xED && b[i+1] < 0xA0) || + (b[i] == 0xEE) || + (b[i] == 0xEF && // #xE000 <= . <= #xFFFD + !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF + !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) +} + +// Check if the character at the specified position is NUL. +func is_z(b []byte, i int) bool { + return b[i] == 0x00 +} + +// Check if the beginning of the buffer is a BOM. +func is_bom(b []byte, i int) bool { + return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF +} + +// Check if the character at the specified position is space. +func is_space(b []byte, i int) bool { + return b[i] == ' ' +} + +// Check if the character at the specified position is tab. +func is_tab(b []byte, i int) bool { + return b[i] == '\t' +} + +// Check if the character at the specified position is blank (space or tab). +func is_blank(b []byte, i int) bool { + //return is_space(b, i) || is_tab(b, i) + return b[i] == ' ' || b[i] == '\t' +} + +// Check if the character at the specified position is a line break. +func is_break(b []byte, i int) bool { + return (b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) +} + +func is_crlf(b []byte, i int) bool { + return b[i] == '\r' && b[i+1] == '\n' +} + +// Check if the character is a line break or NUL. +func is_breakz(b []byte, i int) bool { + //return is_break(b, i) || is_z(b, i) + return ( // is_break: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + // is_z: + b[i] == 0) +} + +// Check if the character is a line break, space, or NUL. +func is_spacez(b []byte, i int) bool { + //return is_space(b, i) || is_breakz(b, i) + return ( // is_space: + b[i] == ' ' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Check if the character is a line break, space, tab, or NUL. +func is_blankz(b []byte, i int) bool { + //return is_blank(b, i) || is_breakz(b, i) + return ( // is_blank: + b[i] == ' ' || b[i] == '\t' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Determine the width of the character. +func width(b byte) int { + // Don't replace these by a switch without first + // confirming that it is being inlined. + if b&0x80 == 0x00 { + return 1 + } + if b&0xE0 == 0xC0 { + return 2 + } + if b&0xF0 == 0xE0 { + return 3 + } + if b&0xF8 == 0xF0 { + return 4 + } + return 0 + +} diff --git a/vendor/layeh.com/gopher-json/LICENSE b/vendor/layeh.com/gopher-json/LICENSE new file mode 100644 index 0000000..b3dbff0 --- /dev/null +++ b/vendor/layeh.com/gopher-json/LICENSE @@ -0,0 +1,22 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/layeh.com/gopher-json/README.md b/vendor/layeh.com/gopher-json/README.md new file mode 100644 index 0000000..a0e7c34 --- /dev/null +++ b/vendor/layeh.com/gopher-json/README.md @@ -0,0 +1,7 @@ +# gopher-json [![GoDoc](https://godoc.org/layeh.com/gopher-json?status.svg)](https://godoc.org/layeh.com/gopher-json) + +Package json is a simple JSON encoder/decoder for [gopher-lua](https://github.com/yuin/gopher-lua). + +## License + +Public domain. diff --git a/vendor/layeh.com/gopher-json/doc.go b/vendor/layeh.com/gopher-json/doc.go new file mode 100644 index 0000000..d50b2da --- /dev/null +++ b/vendor/layeh.com/gopher-json/doc.go @@ -0,0 +1,20 @@ +// Package json is a simple JSON encoder/decoder for gopher-lua. +// +// Documentation +// +// The following functions are exposed by the library: +// decode(string): Decodes a JSON string. Returns nil and an error string if +// the string could not be decoded. +// encode(value): Encodes a value into a JSON string. Returns nil and an error +// string if the value could not be encoded. +// +// Example +// +// Below is an example usage of the library: +// import ( +// luajson "layeh.com/gopher-json" +// ) +// +// L := lua.NewState() +// luajson.Preload(s) +package json // import "layeh.com/gopher-json" diff --git a/vendor/layeh.com/gopher-json/json.go b/vendor/layeh.com/gopher-json/json.go new file mode 100644 index 0000000..e876be8 --- /dev/null +++ b/vendor/layeh.com/gopher-json/json.go @@ -0,0 +1,173 @@ +package json + +import ( + "encoding/json" + "errors" + "strconv" + + "github.com/yuin/gopher-lua" +) + +// Preload adds json to the given Lua state's package.preload table. After it +// has been preloaded, it can be loaded using require: +// +// local json = require("json") +func Preload(L *lua.LState) { + L.PreloadModule("json", Loader) +} + +// Loader is the module loader function. +func Loader(L *lua.LState) int { + t := L.NewTable() + L.SetFuncs(t, api) + L.Push(t) + return 1 +} + +var api = map[string]lua.LGFunction{ + "decode": apiDecode, + "encode": apiEncode, +} + +func apiDecode(L *lua.LState) int { + str := L.CheckString(1) + + value, err := Decode(L, []byte(str)) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(value) + return 1 +} + +func apiEncode(L *lua.LState) int { + value := L.CheckAny(1) + + data, err := Encode(value) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + L.Push(lua.LString(string(data))) + return 1 +} + +var ( + errFunction = errors.New("cannot encode function to JSON") + errChannel = errors.New("cannot encode channel to JSON") + errState = errors.New("cannot encode state to JSON") + errUserData = errors.New("cannot encode userdata to JSON") + errNested = errors.New("cannot encode recursively nested tables to JSON") +) + +type jsonValue struct { + lua.LValue + visited map[*lua.LTable]bool +} + +func (j jsonValue) MarshalJSON() ([]byte, error) { + return encode(j.LValue, j.visited) +} + +// Encode returns the JSON encoding of value. +func Encode(value lua.LValue) ([]byte, error) { + return encode(value, make(map[*lua.LTable]bool)) +} + +func encode(value lua.LValue, visited map[*lua.LTable]bool) (data []byte, err error) { + switch converted := value.(type) { + case lua.LBool: + data, err = json.Marshal(converted) + case lua.LChannel: + err = errChannel + case lua.LNumber: + data, err = json.Marshal(converted) + case *lua.LFunction: + err = errFunction + case *lua.LNilType: + data, err = json.Marshal(converted) + case *lua.LState: + err = errState + case lua.LString: + data, err = json.Marshal(converted) + case *lua.LTable: + var arr []jsonValue + var obj map[string]jsonValue + + if visited[converted] { + panic(errNested) + } + visited[converted] = true + + converted.ForEach(func(k lua.LValue, v lua.LValue) { + i, numberKey := k.(lua.LNumber) + if numberKey && obj == nil { + index := int(i) - 1 + if index != len(arr) { + // map out of order; convert to map + obj = make(map[string]jsonValue) + for i, value := range arr { + obj[strconv.Itoa(i+1)] = value + } + obj[strconv.Itoa(index+1)] = jsonValue{v, visited} + return + } + arr = append(arr, jsonValue{v, visited}) + return + } + if obj == nil { + obj = make(map[string]jsonValue) + for i, value := range arr { + obj[strconv.Itoa(i+1)] = value + } + } + obj[k.String()] = jsonValue{v, visited} + }) + if obj != nil { + data, err = json.Marshal(obj) + } else { + data, err = json.Marshal(arr) + } + case *lua.LUserData: + // TODO: call metatable __tostring? + err = errUserData + } + return +} + +// Decode converts the JSON encoded data to Lua values. +func Decode(L *lua.LState, data []byte) (lua.LValue, error) { + var value interface{} + err := json.Unmarshal(data, &value) + if err != nil { + return nil, err + } + return decode(L, value), nil +} + +func decode(L *lua.LState, value interface{}) lua.LValue { + switch converted := value.(type) { + case bool: + return lua.LBool(converted) + case float64: + return lua.LNumber(converted) + case string: + return lua.LString(converted) + case []interface{}: + arr := L.CreateTable(len(converted), 0) + for _, item := range converted { + arr.Append(decode(L, item)) + } + return arr + case map[string]interface{}: + tbl := L.CreateTable(0, len(converted)) + for key, item := range converted { + tbl.RawSetH(lua.LString(key), decode(L, item)) + } + return tbl + } + return lua.LNil +} diff --git a/vendor/layeh.com/gopher-json/json_test.go b/vendor/layeh.com/gopher-json/json_test.go new file mode 100644 index 0000000..8f5a167 --- /dev/null +++ b/vendor/layeh.com/gopher-json/json_test.go @@ -0,0 +1,75 @@ +package json + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +func TestSimple(t *testing.T) { + const str = ` + local json = require("json") + assert(type(json) == "table") + assert(type(json.decode) == "function") + assert(type(json.encode) == "function") + + assert(json.encode(true) == "true") + assert(json.encode(1) == "1") + assert(json.encode(-10) == "-10") + assert(json.encode(nil) == "{}") + + local obj = {"a",1,"b",2,"c",3} + local jsonStr = json.encode(obj) + local jsonObj = json.decode(jsonStr) + for i = 1, #obj do + assert(obj[i] == jsonObj[i]) + end + + local obj = {name="Tim",number=12345} + local jsonStr = json.encode(obj) + local jsonObj = json.decode(jsonStr) + assert(obj.name == jsonObj.name) + assert(obj.number == jsonObj.number) + + local obj = {"a","b",what="c",[5]="asd"} + local jsonStr = json.encode(obj) + local jsonObj = json.decode(jsonStr) + assert(obj[1] == jsonObj["1"]) + assert(obj[2] == jsonObj["2"]) + assert(obj.what == jsonObj["what"]) + assert(obj[5] == jsonObj["5"]) + + assert(json.decode("null") == nil) + + assert(json.decode(json.encode({person={name = "tim",}})).person.name == "tim") + + local obj = { + abc = 123, + def = nil, + } + local obj2 = { + obj = obj, + } + obj.obj2 = obj2 + assert(json.encode(obj) == nil) + ` + s := lua.NewState() + Preload(s) + if err := s.DoString(str); err != nil { + t.Error(err) + } +} + +func TestCustomRequire(t *testing.T) { + const str = ` + local j = require("JSON") + assert(type(j) == "table") + assert(type(j.decode) == "function") + assert(type(j.encode) == "function") + ` + s := lua.NewState() + s.PreloadModule("JSON", Loader) + if err := s.DoString(str); err != nil { + t.Error(err) + } +} diff --git a/vendor/layeh.com/gopher-luar/.gitignore b/vendor/layeh.com/gopher-luar/.gitignore new file mode 100644 index 0000000..85e7c1d --- /dev/null +++ b/vendor/layeh.com/gopher-luar/.gitignore @@ -0,0 +1 @@ +/.idea/ diff --git a/vendor/layeh.com/gopher-luar/LICENSE b/vendor/layeh.com/gopher-luar/LICENSE new file mode 100644 index 0000000..14e2f77 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/layeh.com/gopher-luar/Makefile b/vendor/layeh.com/gopher-luar/Makefile new file mode 100644 index 0000000..2ad6a9f --- /dev/null +++ b/vendor/layeh.com/gopher-luar/Makefile @@ -0,0 +1,6 @@ +coverage-report: + go test -coverprofile=coverage.out + go tool cover -html=coverage.out + rm coverage.out + +.PHONY: coverage-report diff --git a/vendor/layeh.com/gopher-luar/README.md b/vendor/layeh.com/gopher-luar/README.md new file mode 100644 index 0000000..471a829 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/README.md @@ -0,0 +1,7 @@ +# gopher-luar [![GoDoc](https://godoc.org/layeh.com/gopher-luar?status.svg)](https://godoc.org/layeh.com/gopher-luar) + +custom type reflection for [gopher-lua](https://github.com/yuin/gopher-lua). + +## License + +MPL 2.0 diff --git a/vendor/layeh.com/gopher-luar/array.go b/vendor/layeh.com/gopher-luar/array.go new file mode 100644 index 0000000..6385e07 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/array.go @@ -0,0 +1,124 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +func arrayIndex(L *lua.LState) int { + ref, mt := check(L, 1) + ref = reflect.Indirect(ref) + key := L.CheckAny(2) + + switch converted := key.(type) { + case lua.LNumber: + index := int(converted) + if index < 1 || index > ref.Len() { + L.ArgError(2, "index out of range") + } + val := ref.Index(index - 1) + if (val.Kind() == reflect.Struct || val.Kind() == reflect.Array) && val.CanAddr() { + val = val.Addr() + } + L.Push(New(L, val.Interface())) + case lua.LString: + if fn := mt.method(string(converted)); fn != nil { + L.Push(fn) + return 1 + } + return 0 + default: + L.ArgError(2, "must be a number or string") + } + return 1 +} + +func arrayPtrIndex(L *lua.LState) int { + ref, mt := check(L, 1) + ref = ref.Elem() + key := L.CheckAny(2) + + switch converted := key.(type) { + case lua.LNumber: + index := int(converted) + if index < 1 || index > ref.Len() { + L.ArgError(2, "index out of range") + } + val := ref.Index(index - 1) + if (val.Kind() == reflect.Struct || val.Kind() == reflect.Array) && val.CanAddr() { + val = val.Addr() + } + L.Push(New(L, val.Interface())) + case lua.LString: + if fn := mt.method(string(converted)); fn != nil { + L.Push(fn) + return 1 + } + + mt = MT(L, ref.Interface()) + if fn := mt.method(string(converted)); fn != nil { + L.Push(fn) + return 1 + } + + return 0 + default: + L.ArgError(2, "must be a number or string") + } + return 1 +} + +func arrayPtrNewIndex(L *lua.LState) int { + ref, _ := check(L, 1) + ref = ref.Elem() + + index := L.CheckInt(2) + value := L.CheckAny(3) + if index < 1 || index > ref.Len() { + L.ArgError(2, "index out of range") + } + hint := ref.Type().Elem() + val, err := lValueToReflect(L, value, hint, nil) + if err != nil { + L.ArgError(3, err.Error()) + } + ref.Index(index - 1).Set(val) + return 0 +} + +func arrayLen(L *lua.LState) int { + ref, _ := check(L, 1) + ref = reflect.Indirect(ref) + + L.Push(lua.LNumber(ref.Len())) + return 1 +} + +func arrayCall(L *lua.LState) int { + ref, _ := check(L, 1) + ref = reflect.Indirect(ref) + + i := 0 + fn := func(L *lua.LState) int { + if i >= ref.Len() { + return 0 + } + item := ref.Index(i).Interface() + L.Push(lua.LNumber(i + 1)) + L.Push(New(L, item)) + i++ + return 2 + } + + L.Push(L.NewFunction(fn)) + return 1 +} + +func arrayEq(L *lua.LState) int { + ref1, _ := check(L, 1) + ref2, _ := check(L, 2) + + L.Push(lua.LBool(ref1.Interface() == ref2.Interface())) + return 1 +} diff --git a/vendor/layeh.com/gopher-luar/array_test.go b/vendor/layeh.com/gopher-luar/array_test.go new file mode 100644 index 0000000..18a6ba3 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/array_test.go @@ -0,0 +1,72 @@ +package luar + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +type TestArrayOneString [1]string + +func (o TestArrayOneString) Get() string { + return o[0] +} + +func Test_array(t *testing.T) { + L := lua.NewState() + defer L.Close() + + type Elem struct { + V [2]string + } + + var elem Elem + elem.V[0] = "Hello" + elem.V[1] = "World" + + var arr TestArrayOneString + arr[0] = "Test" + + L.SetGlobal("e", New(L, &elem)) + L.SetGlobal("arr", New(L, arr)) + + testReturn(t, L, `return #e.V, e.V[1], e.V[2]`, "2", "Hello", "World") + testReturn(t, L, `e.V[1] = "World"; e.V[2] = "Hello"`) + testReturn(t, L, `return #e.V, e.V[1], e.V[2]`, "2", "World", "Hello") + + testReturn(t, L, `return #arr, arr[1]`, "1", "Test") + testReturn(t, L, `return arr:Get()`, "Test") + + testError(t, L, `e.V[1] = nil`, "cannot use nil as type string") +} + +func Test_array_iterator(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := [...]string{"x", "y"} + + L.SetGlobal("a", New(L, a)) + L.SetGlobal("ap", New(L, &a)) + + testReturn(t, L, `local itr = a(); local a, b = itr(); local c, d = itr(); return a, b, c, d`, "1", "x", "2", "y") + testReturn(t, L, `local itr = ap(); local a, b = itr(); local c, d = itr(); return a, b, c, d`, "1", "x", "2", "y") +} + +func Test_array_eq(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := [...]string{"x", "y"} + b := [...]string{"x", "y"} + + L.SetGlobal("a", New(L, a)) + L.SetGlobal("ap", New(L, &a)) + L.SetGlobal("b", New(L, b)) + L.SetGlobal("bp", New(L, &b)) + + testReturn(t, L, `return a == b`, "true") + testReturn(t, L, `return a ~= b`, "false") + testReturn(t, L, `return ap == nil`, "false") + testReturn(t, L, `return ap == bp`, "false") +} diff --git a/vendor/layeh.com/gopher-luar/cache.go b/vendor/layeh.com/gopher-luar/cache.go new file mode 100644 index 0000000..3fdeebc --- /dev/null +++ b/vendor/layeh.com/gopher-luar/cache.go @@ -0,0 +1,201 @@ +package luar + +import ( + "container/list" + "reflect" + + "github.com/yuin/gopher-lua" +) + +func addMethods(L *lua.LState, c *Config, vtype reflect.Type, tbl *lua.LTable, ptrReceiver bool) { + for i := 0; i < vtype.NumMethod(); i++ { + method := vtype.Method(i) + if method.PkgPath != "" { + continue + } + namesFn := c.MethodNames + if namesFn == nil { + namesFn = defaultMethodNames + } + fn := funcWrapper(L, method.Func, ptrReceiver) + for _, name := range namesFn(vtype, method) { + tbl.RawSetString(name, fn) + } + } +} + +func addFields(L *lua.LState, c *Config, vtype reflect.Type, tbl *lua.LTable) { + type element struct { + Type reflect.Type + Index []int + } + + queue := list.New() + queue.PushFront(element{ + Type: vtype, + }) + + namesFn := c.FieldNames + if namesFn == nil { + namesFn = defaultFieldNames + } + + for queue.Len() > 0 { + e := queue.Back() + elem := e.Value.(element) + vtype := elem.Type + fields: + for i := 0; i < vtype.NumField(); i++ { + field := vtype.Field(i) + if field.PkgPath != "" && !field.Anonymous { + continue + } + names := namesFn(vtype, field) + for _, key := range names { + if tbl.RawGetString(key) != lua.LNil { + continue fields + } + } + + ud := L.NewUserData() + ud.Value = append(elem.Index[:len(elem.Index):len(elem.Index)], i) + for _, key := range names { + tbl.RawSetString(key, ud) + } + if field.Anonymous { + t := field.Type + if field.Type.Kind() != reflect.Struct { + if field.Type.Kind() != reflect.Ptr || field.Type.Elem().Kind() != reflect.Struct { + continue + } + t = field.Type.Elem() + } + queue.PushFront(element{ + Type: t, + Index: append(elem.Index[:len(elem.Index):len(elem.Index)], i), + }) + } + } + + queue.Remove(e) + } +} + +func getMetatable(L *lua.LState, vtype reflect.Type) *lua.LTable { + config := GetConfig(L) + + if v := config.regular[vtype]; v != nil { + return v + } + + var ( + mt *lua.LTable + methods = L.CreateTable(0, vtype.NumMethod()) + ) + + switch vtype.Kind() { + case reflect.Array: + mt = L.CreateTable(0, 7) + + mt.RawSetString("__index", L.NewFunction(arrayIndex)) + mt.RawSetString("__len", L.NewFunction(arrayLen)) + mt.RawSetString("__call", L.NewFunction(arrayCall)) + mt.RawSetString("__eq", L.NewFunction(arrayEq)) + + addMethods(L, config, vtype, methods, false) + case reflect.Chan: + mt = L.CreateTable(0, 8) + + mt.RawSetString("__index", L.NewFunction(chanIndex)) + mt.RawSetString("__len", L.NewFunction(chanLen)) + mt.RawSetString("__eq", L.NewFunction(chanEq)) + mt.RawSetString("__call", L.NewFunction(chanCall)) + mt.RawSetString("__unm", L.NewFunction(chanUnm)) + + addMethods(L, config, vtype, methods, false) + case reflect.Map: + mt = L.CreateTable(0, 7) + + mt.RawSetString("__index", L.NewFunction(mapIndex)) + mt.RawSetString("__newindex", L.NewFunction(mapNewIndex)) + mt.RawSetString("__len", L.NewFunction(mapLen)) + mt.RawSetString("__call", L.NewFunction(mapCall)) + + addMethods(L, config, vtype, methods, false) + case reflect.Slice: + mt = L.CreateTable(0, 8) + + mt.RawSetString("__index", L.NewFunction(sliceIndex)) + mt.RawSetString("__newindex", L.NewFunction(sliceNewIndex)) + mt.RawSetString("__len", L.NewFunction(sliceLen)) + mt.RawSetString("__call", L.NewFunction(sliceCall)) + mt.RawSetString("__add", L.NewFunction(sliceAdd)) + + addMethods(L, config, vtype, methods, false) + case reflect.Struct: + mt = L.CreateTable(0, 6) + + fields := L.CreateTable(0, vtype.NumField()) + addFields(L, config, vtype, fields) + mt.RawSetString("fields", fields) + + mt.RawSetString("__index", L.NewFunction(structIndex)) + mt.RawSetString("__eq", L.NewFunction(structEq)) + + addMethods(L, config, vtype, methods, false) + case reflect.Ptr: + switch vtype.Elem().Kind() { + case reflect.Array: + mt = L.CreateTable(0, 10) + + mt.RawSetString("__index", L.NewFunction(arrayPtrIndex)) + mt.RawSetString("__newindex", L.NewFunction(arrayPtrNewIndex)) + mt.RawSetString("__call", L.NewFunction(arrayCall)) // same as non-pointer + mt.RawSetString("__len", L.NewFunction(arrayLen)) // same as non-pointer + case reflect.Struct: + mt = L.CreateTable(0, 8) + + mt.RawSetString("__index", L.NewFunction(structPtrIndex)) + mt.RawSetString("__newindex", L.NewFunction(structPtrNewIndex)) + default: + mt = L.CreateTable(0, 7) + + mt.RawSetString("__index", L.NewFunction(ptrIndex)) + } + + mt.RawSetString("__eq", L.NewFunction(ptrEq)) + mt.RawSetString("__pow", L.NewFunction(ptrPow)) + mt.RawSetString("__unm", L.NewFunction(ptrUnm)) + + addMethods(L, config, vtype, methods, true) + default: + panic("unexpected kind " + vtype.Kind().String()) + } + + mt.RawSetString("__tostring", L.NewFunction(tostring)) + mt.RawSetString("__metatable", lua.LString("gopher-luar")) + mt.RawSetString("methods", methods) + + config.regular[vtype] = mt + return mt +} + +func getMetatableFromValue(L *lua.LState, value reflect.Value) *lua.LTable { + vtype := value.Type() + return getMetatable(L, vtype) +} + +func getTypeMetatable(L *lua.LState, t reflect.Type) *lua.LTable { + config := GetConfig(L) + + if v := config.types; v != nil { + return v + } + + mt := L.CreateTable(0, 2) + mt.RawSetString("__call", L.NewFunction(typeCall)) + mt.RawSetString("__eq", L.NewFunction(typeEq)) + + config.types = mt + return mt +} diff --git a/vendor/layeh.com/gopher-luar/chan.go b/vendor/layeh.com/gopher-luar/chan.go new file mode 100644 index 0000000..715a510 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/chan.go @@ -0,0 +1,81 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +func chanIndex(L *lua.LState) int { + _, mt := check(L, 1) + key := L.CheckString(2) + + if fn := mt.method(key); fn != nil { + L.Push(fn) + return 1 + } + + return 0 +} + +func chanLen(L *lua.LState) int { + ref, _ := check(L, 1) + + L.Push(lua.LNumber(ref.Len())) + return 1 +} + +func chanEq(L *lua.LState) int { + ref1, _ := check(L, 1) + ref2, _ := check(L, 2) + + L.Push(lua.LBool(ref1.Pointer() == ref2.Pointer())) + return 1 +} + +func chanCall(L *lua.LState) int { + ref, _ := check(L, 1) + + switch L.GetTop() { + // Receive + case 1: + if ref.Type().ChanDir()&reflect.RecvDir == 0 { + L.ArgError(1, "receive from send-only type "+ref.Type().String()) + } + value, ok := ref.Recv() + if ok { + L.Push(New(L, value.Interface())) + L.Push(lua.LTrue) + } else { + L.Push(lua.LNil) + L.Push(lua.LFalse) + } + return 2 + + // Send + case 2: + if ref.Type().ChanDir()&reflect.SendDir == 0 { + L.ArgError(1, "send to receive-only type "+ref.Type().String()) + } + value := L.CheckAny(2) + + hint := ref.Type().Elem() + convertedValue, err := lValueToReflect(L, value, hint, nil) + if err != nil { + L.ArgError(2, err.Error()) + } + + ref.Send(convertedValue) + return 0 + + default: + L.RaiseError("expecting 1 or 2 arguments, got %d", L.GetTop()) + panic("never reaches") + } +} + +func chanUnm(L *lua.LState) int { + ref, _ := check(L, 1) + ref.Close() + return 0 +} diff --git a/vendor/layeh.com/gopher-luar/chan_test.go b/vendor/layeh.com/gopher-luar/chan_test.go new file mode 100644 index 0000000..d292a4d --- /dev/null +++ b/vendor/layeh.com/gopher-luar/chan_test.go @@ -0,0 +1,65 @@ +package luar + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_chan(t *testing.T) { + L := lua.NewState() + defer L.Close() + + ch := make(chan string) + go func() { + ch <- "Tim" + name, ok := <-ch + if name != "John" || !ok { + t.Fatal("invalid value") + } + + close(ch) + }() + + L.SetGlobal("ch", New(L, ch)) + + testReturn(t, L, `return ch()`, "Tim", "true") + testReturn(t, L, `ch("John")`) + testReturn(t, L, `return ch()`, "nil", "false") +} + +type TestChanString chan string + +func (*TestChanString) Test() string { + return "TestChanString.Test" +} + +func (TestChanString) Test2() string { + return "TestChanString.Test2" +} + +func Test_chan_pointermethod(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := make(TestChanString) + b := &a + + L.SetGlobal("b", New(L, b)) + + testReturn(t, L, `return b:Test()`, "TestChanString.Test") + testReturn(t, L, `return b:Test2()`, "TestChanString.Test2") +} + +func Test_chan_invaliddirection(t *testing.T) { + L := lua.NewState() + defer L.Close() + + ch := make(chan string) + + L.SetGlobal("send", New(L, (chan<- string)(ch))) + testError(t, L, `send()`, "receive from send-only type chan<- string") + + L.SetGlobal("receive", New(L, (<-chan string)(ch))) + testError(t, L, `receive("hello")`, "send to receive-only type <-chan string") +} diff --git a/vendor/layeh.com/gopher-luar/config.go b/vendor/layeh.com/gopher-luar/config.go new file mode 100644 index 0000000..b3f4736 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/config.go @@ -0,0 +1,74 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +// Config is used to define luar behaviour for a particular *lua.LState. +type Config struct { + // The name generating function that defines under which names Go + // struct fields will be accessed. + // + // If nil, the default behaviour is used: + // - if the "luar" tag of the field is "", the field name and its name + // with a lowercase first letter is returned + // - if the tag is "-", no name is returned (i.e. the field is not + // accessible) + // - for any other tag value, that value is returned + FieldNames func(s reflect.Type, f reflect.StructField) []string + + // The name generating function that defines under which names Go + // methods will be accessed. + // + // If nil, the default behaviour is used: + // - the method name and its name with a lowercase first letter + MethodNames func(t reflect.Type, m reflect.Method) []string + + regular map[reflect.Type]*lua.LTable + types *lua.LTable +} + +func newConfig() *Config { + return &Config{ + regular: make(map[reflect.Type]*lua.LTable), + } +} + +// GetConfig returns the luar configuration options for the given *lua.LState. +func GetConfig(L *lua.LState) *Config { + const registryKey = "github.com/layeh/gopher-luar" + + registry := L.Get(lua.RegistryIndex).(*lua.LTable) + lConfig, ok := registry.RawGetString(registryKey).(*lua.LUserData) + if !ok { + lConfig = L.NewUserData() + lConfig.Value = newConfig() + registry.RawSetString(registryKey, lConfig) + } + return lConfig.Value.(*Config) +} + +func defaultFieldNames(s reflect.Type, f reflect.StructField) []string { + const tagName = "luar" + + tag := f.Tag.Get(tagName) + if tag == "-" { + return nil + } + if tag != "" { + return []string{tag} + } + return []string{ + f.Name, + getUnexportedName(f.Name), + } +} + +func defaultMethodNames(t reflect.Type, m reflect.Method) []string { + return []string{ + m.Name, + getUnexportedName(m.Name), + } +} diff --git a/vendor/layeh.com/gopher-luar/config_test.go b/vendor/layeh.com/gopher-luar/config_test.go new file mode 100644 index 0000000..2049914 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/config_test.go @@ -0,0 +1,79 @@ +package luar + +import ( + "reflect" + "testing" + + "github.com/yuin/gopher-lua" + "strings" +) + +func Test_config(t *testing.T) { + L := lua.NewState() + defer L.Close() + + config := GetConfig(L) + if config == nil { + t.Fatal("expecting non-nil config") + } + + if config.FieldNames != nil { + t.Fatal("expected config.FieldName to be nil") + } + + if config.MethodNames != nil { + t.Fatal("expected config.MethodName to be nil") + } +} + +func Test_config_fieldnames(t *testing.T) { + L := lua.NewState() + defer L.Close() + + config := GetConfig(L) + config.FieldNames = func(s reflect.Type, f reflect.StructField) []string { + return []string{strings.ToLower(f.Name)} + } + + type S struct { + Name string + Age int `luar:"AGE"` + } + s := S{ + Name: "Tim", + Age: 89, + } + + L.SetGlobal("s", New(L, &s)) + + testReturn(t, L, `return s.Name`, `nil`) + testReturn(t, L, `return s.name`, `Tim`) + testReturn(t, L, `return s.AGE`, `nil`) + testReturn(t, L, `return s.age`, `89`) +} + +type TestConfigMethodnames []string + +func (t TestConfigMethodnames) Len() int { + return len(t) +} + +func Test_config_methodnames(t *testing.T) { + L := lua.NewState() + defer L.Close() + + config := GetConfig(L) + config.MethodNames = func(s reflect.Type, m reflect.Method) []string { + return []string{strings.ToLower(m.Name) + "gth"} + } + + v := TestConfigMethodnames{ + "hello", + "world", + } + + L.SetGlobal("v", New(L, v)) + + testError(t, L, `return v:len()`, `attempt to call a non-function object`) + testReturn(t, L, `return v:length()`, `2`) +} diff --git a/vendor/layeh.com/gopher-luar/doc.go b/vendor/layeh.com/gopher-luar/doc.go new file mode 100644 index 0000000..ece2245 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/doc.go @@ -0,0 +1,45 @@ +// Package luar provides custom type reflection to gopher-lua +// (https://github.com/yuin/gopher-lua). +// +// Go to Lua conversions +// +// See documentation of New function. +// +// Lua to Go conversions +// +// Lua types are automatically converted to match the output Go type (e.g. +// setting a struct field from Lua). +// +// lua.LNil can be converted to any channel, func, interface, map, pointer, +// slice, unsafepointer, or uintptr value. +// +// lua.LBool values are converted to bool. +// +// lua.LNumber values are converted to float64. +// +// lua.LString values are converted to string. +// +// lua.LChannel values are converted to lua.LChannel. +// +// *lua.LTable values can be converted to an array, slice, map, struct, or +// struct pointer. If the table is being assigned with no type information (i.e. +// to an interface{}), the converted value will have the type +// map[interface{}]interface{}. +// +// The Value field of *lua.LUserData values are converted rather than the +// *lua.LUserData value itself. +// +// *lua.LState values are converted to *lua.LState. +// +// *lua.LFunction values are converted to Go functions. If the function is +// being assigned with no type information (i.e. to a interface{}), the function +// will have the signature func(...interface{}) []interface{}. The arguments +// and return values will be converted using the standard luar conversion rules. +// +// Thread safety +// +// This package accesses and modifies the Lua state's registry. This happens +// when functions like New are called, and potentially when luar-created values +// are used. It is your responsibility to ensure that concurrent access of the +// state's registry does not happen. +package luar // import "layeh.com/gopher-luar" diff --git a/vendor/layeh.com/gopher-luar/examples_test.go b/vendor/layeh.com/gopher-luar/examples_test.go new file mode 100644 index 0000000..be61169 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/examples_test.go @@ -0,0 +1,53 @@ +package luar + +import ( + "github.com/yuin/gopher-lua" +) + +func ExampleLState() { + const code = ` + print(sum(1, 2, 3, 4, 5)) + ` + + L := lua.NewState() + defer L.Close() + + sum := func(L *LState) int { + total := 0 + for i := 1; i <= L.GetTop(); i++ { + total += L.CheckInt(i) + } + L.Push(lua.LNumber(total)) + return 1 + } + + L.SetGlobal("sum", New(L, sum)) + + if err := L.DoString(code); err != nil { + panic(err) + } + // Output: + // 15 +} + +func ExampleNewType() { + L := lua.NewState() + defer L.Close() + + type Song struct { + Title string + Artist string + } + + L.SetGlobal("Song", NewType(L, Song{})) + if err := L.DoString(` + s = Song() + s.Title = "Montana" + s.Artist = "Tycho" + print(s.Artist .. " - " .. s.Title) + `); err != nil { + panic(err) + } + // Output: + // Tycho - Montana +} diff --git a/vendor/layeh.com/gopher-luar/func.go b/vendor/layeh.com/gopher-luar/func.go new file mode 100644 index 0000000..462aad5 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/func.go @@ -0,0 +1,146 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +// LState is an wrapper for gopher-lua's LState. It should be used when you +// wish to have a function/method with the standard "func(*lua.LState) int" +// signature. +type LState struct { + *lua.LState +} + +var ( + refTypeLStatePtr reflect.Type + refTypeLuaLValue reflect.Type + refTypeInt reflect.Type + refTypeEmptyIface reflect.Type +) + +func init() { + refTypeLStatePtr = reflect.TypeOf(&LState{}) + refTypeLuaLValue = reflect.TypeOf((*lua.LValue)(nil)).Elem() + refTypeInt = reflect.TypeOf(int(0)) + refTypeEmptyIface = reflect.TypeOf((*interface{})(nil)).Elem() +} + +func getFunc(L *lua.LState) (ref reflect.Value, refType reflect.Type) { + ref = L.Get(lua.UpvalueIndex(1)).(*lua.LUserData).Value.(reflect.Value) + refType = ref.Type() + return +} + +func isPtrReceiverMethod(L *lua.LState) bool { + return bool(L.Get(lua.UpvalueIndex(2)).(lua.LBool)) +} + +func funcIsBypass(t reflect.Type) bool { + if t.NumIn() == 1 && t.NumOut() == 1 && t.In(0) == refTypeLStatePtr && t.Out(0) == refTypeInt { + return true + } + if t.NumIn() == 2 && t.NumOut() == 1 && t.In(1) == refTypeLStatePtr && t.Out(0) == refTypeInt { + return true + } + return false +} + +func funcBypass(L *lua.LState) int { + ref, refType := getFunc(L) + + convertedPtr := false + var receiver reflect.Value + var ud lua.LValue + + luarState := LState{L} + args := make([]reflect.Value, 0, 2) + if refType.NumIn() == 2 { + receiverHint := refType.In(0) + ud = L.Get(1) + var err error + if isPtrReceiverMethod(L) { + receiver, err = lValueToReflect(L, ud, receiverHint, &convertedPtr) + } else { + receiver, err = lValueToReflect(L, ud, receiverHint, nil) + } + if err != nil { + L.ArgError(1, err.Error()) + } + args = append(args, receiver) + L.Remove(1) + } + args = append(args, reflect.ValueOf(&luarState)) + ret := ref.Call(args)[0].Interface().(int) + if convertedPtr { + ud.(*lua.LUserData).Value = receiver.Elem().Interface() + } + return ret +} + +func funcRegular(L *lua.LState) int { + ref, refType := getFunc(L) + + top := L.GetTop() + expected := refType.NumIn() + variadic := refType.IsVariadic() + if !variadic && top != expected { + L.RaiseError("invalid number of function arguments (%d expected, got %d)", expected, top) + } + if variadic && top < expected-1 { + L.RaiseError("invalid number of function arguments (%d or more expected, got %d)", expected-1, top) + } + + convertedPtr := false + var receiver reflect.Value + var ud lua.LValue + + args := make([]reflect.Value, top) + for i := 0; i < L.GetTop(); i++ { + var hint reflect.Type + if variadic && i >= expected-1 { + hint = refType.In(expected - 1).Elem() + } else { + hint = refType.In(i) + } + var arg reflect.Value + var err error + if i == 0 && isPtrReceiverMethod(L) { + ud = L.Get(1) + v := ud + arg, err = lValueToReflect(L, v, hint, &convertedPtr) + if err != nil { + L.ArgError(1, err.Error()) + } + receiver = arg + } else { + v := L.Get(i + 1) + arg, err = lValueToReflect(L, v, hint, nil) + if err != nil { + L.ArgError(i+1, err.Error()) + } + } + args[i] = arg + } + ret := ref.Call(args) + + if convertedPtr { + ud.(*lua.LUserData).Value = receiver.Elem().Interface() + } + + for _, val := range ret { + L.Push(New(L, val.Interface())) + } + return len(ret) +} + +func funcWrapper(L *lua.LState, fn reflect.Value, isPtrReceiverMethod bool) *lua.LFunction { + up := L.NewUserData() + up.Value = fn + + if funcIsBypass(fn.Type()) { + return L.NewClosure(funcBypass, up, lua.LBool(isPtrReceiverMethod)) + } + return L.NewClosure(funcRegular, up, lua.LBool(isPtrReceiverMethod)) +} diff --git a/vendor/layeh.com/gopher-luar/func_test.go b/vendor/layeh.com/gopher-luar/func_test.go new file mode 100644 index 0000000..468c83b --- /dev/null +++ b/vendor/layeh.com/gopher-luar/func_test.go @@ -0,0 +1,241 @@ +package luar + +import ( + "reflect" + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_func_variableargs(t *testing.T) { + L := lua.NewState() + defer L.Close() + + fn := func(str string, extra ...int) { + switch str { + case "a": + if len(extra) != 3 || extra[0] != 1 || extra[1] != 2 || extra[2] != 3 { + t.Fatalf("unexpected variable arguments: %v", extra) + } + case "b": + if len(extra) != 0 { + t.Fatalf("unexpected variable arguments: %v", extra) + } + case "c": + if len(extra) != 1 || extra[0] != 4 { + t.Fatalf("unexpected variable arguments: %v", extra) + } + } + } + + L.SetGlobal("fn", New(L, fn)) + + testReturn(t, L, `return fn("a", 1, 2, 3)`) + testReturn(t, L, `return fn("b")`) + testReturn(t, L, `return fn("c", 4)`) +} + +func Test_func_structarg(t *testing.T) { + L := lua.NewState() + defer L.Close() + + tim := &StructTestPerson{ + Name: "Tim", + } + + fn := func(p *StructTestPerson) string { + return "Hello, " + p.Name + } + + L.SetGlobal("person", New(L, tim)) + L.SetGlobal("getHello", New(L, fn)) + + testReturn(t, L, `return getHello(person)`, "Hello, Tim") +} + +func Test_func_nilreference(t *testing.T) { + L := lua.NewState() + defer L.Close() + + var fn func() + + L.SetGlobal("fn", New(L, fn)) + + testReturn(t, L, `return fn`, "nil") +} + +func Test_func_arrayarg(t *testing.T) { + L := lua.NewState() + defer L.Close() + + arr := [3]int{1, 2, 3} + fn := func(val [3]int) { + if val != arr { + t.Fatalf("expecting %v, got %v", arr, val) + } + } + + L.SetGlobal("fn", New(L, fn)) + L.SetGlobal("arr", New(L, arr)) + + testReturn(t, L, `return fn(arr)`) +} + +func Test_func_luareturntype(t *testing.T) { + L := lua.NewState() + defer L.Close() + + fn := func(x ...float64) *lua.LTable { + tbl := L.NewTable() + for i := len(x) - 1; i >= 0; i-- { + tbl.Insert(len(x)-i, lua.LNumber(x[i])) + } + return tbl + } + + L.SetGlobal("fn", New(L, fn)) + + testReturn( + t, + L, + ` + local t = {} + for _, x in ipairs(fn(1, 2, 3)) do + table.insert(t, x) + end + return t[1], t[2], t[3]`, + "3", "2", "1", + ) + + testReturn( + t, + L, + ` + local t = {} + for _, x in ipairs(fn()) do + table.insert(t, x) + end + return t[1]`, + "nil", + ) +} + +type TestLuaFuncRef struct { + F1 *lua.LFunction +} + +func Test_func_luafuncref(t *testing.T) { + L := lua.NewState() + defer L.Close() + + e := &TestLuaFuncRef{} + L.SetGlobal("e", New(L, e)) + + testReturn( + t, + L, + ` + e.F1 = function(str) + return "Hello World", 123 + end + `, + ) + + L.Push(e.F1) + L.Call(0, 2) + + if L.GetTop() != 2 || L.Get(1).String() != "Hello World" || L.Get(2).String() != "123" { + t.Fatal("incorrect return values") + } +} + +type TestFuncCall struct { + Fn func(a string) (string, int) + Fn2 func(a string, b ...int) string +} + +func Test_func_luafunccall(t *testing.T) { + L := lua.NewState() + defer L.Close() + + e := &TestFuncCall{} + L.SetGlobal("x", New(L, e)) + + testReturn( + t, + L, + ` + i = 0 + x.Fn = function(str) + i = i + 1 + return ">" .. str .. "<", i + end + + x.Fn2 = function(str, a, b, c) + if type(a) == "number" and type(b) == "number" and type(c) == "number" then + return str + end + return "" + end + `, + ) + + if str, i := e.Fn("A"); str != ">A<" || i != 1 { + t.Fatal("unexpected return values") + } + + if str, i := e.Fn("B"); str != ">B<" || i != 2 { + t.Fatal("unexpected return values") + } + + if val := e.Fn2("hello", 1, 2); val != "" { + t.Fatal("unexpected return value") + } + + if val := e.Fn2("hello", 1, 2, 3); val != "hello" { + t.Fatal("unexpected return value") + } + + if L.GetTop() != 0 { + t.Fatalf("expecting GetTop to return 0, got %d", L.GetTop()) + } +} + +func Test_func_argerror(t *testing.T) { + L := lua.NewState() + defer L.Close() + + fn := func(v uint8) { + } + + L.SetGlobal("fn", New(L, fn)) + + testError(t, L, `fn("hello world")`, "bad argument #1 to fn (cannot use hello world (type lua.LString) as type uint8)") +} + +func Test_func_conversionstack(t *testing.T) { + L := lua.NewState() + defer L.Close() + + var fn interface{} + L.SetGlobal("fn", New(L, &fn)) + if err := L.DoString(`_ = fn ^ function() return "hello", true, 123 end`); err != nil { + t.Fatal(err) + } + + callable, ok := fn.(func(...interface{}) []interface{}) + if !ok { + t.Fatal("invalid function signature") + } + values := callable() + + expected := []interface{}{ + string("hello"), + bool(true), + float64(123), + } + + if !reflect.DeepEqual(expected, values) { + t.Fatalf("expected return %#v, got %#v", expected, values) + } +} diff --git a/vendor/layeh.com/gopher-luar/helpers_test.go b/vendor/layeh.com/gopher-luar/helpers_test.go new file mode 100644 index 0000000..bd3eebc --- /dev/null +++ b/vendor/layeh.com/gopher-luar/helpers_test.go @@ -0,0 +1,83 @@ +package luar + +import ( + "runtime/debug" + "strconv" + "strings" + "testing" + + "github.com/yuin/gopher-lua" +) + +type StructTestPerson struct { + Name string + Age int + Friend *StructTestPerson + LastAddSum int +} + +func (p StructTestPerson) Hello() string { + return "Hello, " + p.Name +} + +func (p StructTestPerson) String() string { + return p.Name + " (" + strconv.Itoa(p.Age) + ")" +} + +func (p *StructTestPerson) AddNumbers(L *LState) int { + sum := 0 + for i := L.GetTop(); i >= 1; i-- { + sum += L.CheckInt(i) + } + L.Push(lua.LString(p.Name + " counts: " + strconv.Itoa(sum))) + p.LastAddSum = sum + return 1 +} + +func (p *StructTestPerson) IncreaseAge() { + p.Age++ +} + +func testReturn(t *testing.T, L *lua.LState, code string, values ...string) { + top := L.GetTop() + if err := L.DoString(code); err != nil { + t.Fatalf("%s\n\n%s", err, debug.Stack()) + } + + valid := true + newTop := L.GetTop() + + if newTop-top != len(values) { + valid = false + } else { + for i, expect := range values { + // TODO: strong typing + val := L.Get(top + i + 1).String() + if val != expect { + valid = false + } + } + } + + if !valid { + got := make([]string, newTop-top) + for i := 0; i < len(got); i++ { + got[i] = L.Get(top + i + 1).String() + } + + t.Fatalf("bad return values: expecting %#v, got %#v\n\n%s", values, got, debug.Stack()) + } + + L.SetTop(top) +} + +func testError(t *testing.T, L *lua.LState, code, error string) { + err := L.DoString(code) + if err == nil { + t.Fatalf("expecting error, got nil\n\n%s", debug.Stack()) + } + + if s := err.Error(); strings.Index(s, error) == -1 { + t.Fatalf("error substring '%s' not found in '%s'\n\n%s", error, s, debug.Stack()) + } +} diff --git a/vendor/layeh.com/gopher-luar/luar.go b/vendor/layeh.com/gopher-luar/luar.go new file mode 100644 index 0000000..58ed9ec --- /dev/null +++ b/vendor/layeh.com/gopher-luar/luar.go @@ -0,0 +1,482 @@ +package luar + +import ( + "fmt" + "reflect" + + "github.com/yuin/gopher-lua" +) + +// New creates and returns a new lua.LValue for the given value. Values are +// converted in the following manner: +// +// A nil value (untyped, or a nil channel, function, map, pointer, or slice) is +// converted to lua.LNil. +// +// A lua.LValue value is returned without conversion. +// +// Boolean values are converted to lua.LBool. +// +// String values are converted to lua.LString. +// +// Real numeric values (ints, uints, and floats) are converted to lua.LNumber. +// +// Functions are converted to *lua.LFunction. When called from Lua, Lua values +// are converted to Go using the rules described in the package documentation, +// and Go return values converted to Lua values using the rules described by +// New. +// +// If a function has the signature: +// func(*LState) int // *LState defined in this package, not in lua +// The argument and return value conversions described above are skipped, and +// the function is called with the arguments passed on the Lua stack. Return +// values are pushed to the stack and the number of return values is returned +// from the function. +// +// Arrays, channels, maps, pointers, slices, and structs are all converted to +// *lua.LUserData with its Value field set to value. The userdata's metatable +// is set to a table generated for value's type. The type's method set is +// callable from the Lua type. If the type implements the fmt.Stringer +// interface, that method will be used when the value is passed to the Lua +// tostring function. +// +// With arrays, the # operator returns the array's length. Array elements can +// be accessed with the index operator (array[index]). Calling an array +// (array()) returns an iterator over the array that can be used in a for loop. +// Two arrays of the same type can be compared for equality. Additionally, a +// pointer to an array allows the array elements to be modified +// (array[index] = value). +// +// With channels, the # operator returns the number of elements buffered in the +// channel. Two channels of the same type can be compared for equality (i.e. if +// they were created with the same make call). Calling a channel value with +// no arguments reads one element from the channel, returning the value and a +// boolean indicating if the channel is closed. Calling a channel value with +// one argument sends the argument to the channel. The channel's unary minus +// operator closes the channel (_ = -channel). +// +// With maps, the # operator returns the number of elements in the map. Map +// elements can be accessed using the index operator (map[key]) and also set +// (map[key] = value). Calling a map value returns an iterator over the map that +// can be used in a for loop. If a map's key type is string, map values take +// priority over methods. +// +// With slices, the # operator returns the length of the slice. Slice elements +// can be accessed using the index operator (slice[index]) and also set +// (slice[index] = value). Calling a slice returns an iterator over its elements +// that can be used in a for loop. Elements can be appended to a slice using the +// add operator (new_slice = slice + element). +// +// With structs, fields can be accessed using the index operator +// (struct[field]). As a special case, accessing field that is an array or +// struct field will return a pointer to that value. Structs of the same type +// can be tested for equality. Additionally, a pointer to a struct can have its +// fields set (struct[field] = value). +// +// Struct field accessibility can be changed by setting the field's luar tag. +// If the tag is empty (default), the field is accessed by its name and its +// name with a lowercase first letter (e.g. "Field1" would be accessible using +// "Field1" or "field1"). If the tag is "-", the field will not be accessible. +// Any other tag value makes the field accessible through that name. +// +// Pointer values can be compared for equality. The pointed to value can be +// changed using the pow operator (pointer = pointer ^ value). A pointer can be +// dereferenced using the unary minus operator (value = -pointer). +// +// All other values (complex numbers, unsafepointer, uintptr) are converted to +// *lua.LUserData with its Value field set to value and no custom metatable. +// +func New(L *lua.LState, value interface{}) lua.LValue { + if value == nil { + return lua.LNil + } + if lval, ok := value.(lua.LValue); ok { + return lval + } + + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice: + if val.IsNil() { + return lua.LNil + } + } + + switch val.Kind() { + case reflect.Bool: + return lua.LBool(val.Bool()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return lua.LNumber(float64(val.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return lua.LNumber(float64(val.Uint())) + case reflect.Float32, reflect.Float64: + return lua.LNumber(val.Float()) + case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice, reflect.Struct: + ud := L.NewUserData() + ud.Value = val.Interface() + ud.Metatable = getMetatableFromValue(L, val) + return ud + case reflect.Func: + return funcWrapper(L, val, false) + case reflect.String: + return lua.LString(val.String()) + default: + ud := L.NewUserData() + ud.Value = val.Interface() + return ud + } +} + +// NewType returns a new type generator for the given value's type. +// +// When the returned lua.LValue is called, a new value will be created that is +// dependent on value's type: +// +// If value is a channel, the first argument optionally specifies the channel's +// buffer size (defaults to 1). The new channel is returned. +// +// If value is a map, a new map is returned. +// +// If value is a slice, the first argument optionally specifies the slices's +// length (defaults to 0), and the second argument optionally specifies the +// slice's capacity (defaults to the first argument). The new slice is returned. +// +// All other types return a new pointer to the zero value of value's type. +func NewType(L *lua.LState, value interface{}) lua.LValue { + val := reflect.TypeOf(value) + ud := L.NewUserData() + ud.Value = val + ud.Metatable = getTypeMetatable(L, val) + + return ud +} + +type conversionError struct { + Lua lua.LValue + Hint reflect.Type +} + +func (c conversionError) Error() string { + if _, isNil := c.Lua.(*lua.LNilType); isNil { + return fmt.Sprintf("cannot use nil as type %s", c.Hint) + } + + var val interface{} + + if userData, ok := c.Lua.(*lua.LUserData); ok { + val = userData.Value + } else { + val = c.Lua + } + + return fmt.Sprintf("cannot use %v (type %T) as type %s", val, val, c.Hint) +} + +type structFieldError struct { + Field string + Type reflect.Type +} + +func (s structFieldError) Error() string { + return `type ` + s.Type.String() + ` has no field ` + s.Field +} + +func lValueToReflect(L *lua.LState, v lua.LValue, hint reflect.Type, tryConvertPtr *bool) (reflect.Value, error) { + visited := make(map[*lua.LTable]reflect.Value) + return lValueToReflectInner(L, v, hint, visited, tryConvertPtr) +} + +func lValueToReflectInner(L *lua.LState, v lua.LValue, hint reflect.Type, visited map[*lua.LTable]reflect.Value, tryConvertPtr *bool) (reflect.Value, error) { + if hint.Implements(refTypeLuaLValue) { + return reflect.ValueOf(v), nil + } + + isPtr := false + + switch converted := v.(type) { + case lua.LBool: + val := reflect.ValueOf(bool(converted)) + if !val.Type().ConvertibleTo(hint) { + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + return val.Convert(hint), nil + case lua.LChannel: + val := reflect.ValueOf(converted) + if !val.Type().ConvertibleTo(hint) { + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + return val.Convert(hint), nil + case lua.LNumber: + val := reflect.ValueOf(float64(converted)) + if !val.Type().ConvertibleTo(hint) { + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + return val.Convert(hint), nil + case *lua.LFunction: + emptyIfaceHint := false + switch { + case hint == refTypeEmptyIface: + emptyIfaceHint = true + inOut := []reflect.Type{ + reflect.SliceOf(refTypeEmptyIface), + } + hint = reflect.FuncOf(inOut, inOut, true) + case hint.Kind() != reflect.Func: + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + + fn := func(args []reflect.Value) []reflect.Value { + thread, cancelFunc := L.NewThread() + defer thread.Close() + if cancelFunc != nil { + defer cancelFunc() + } + thread.Push(converted) + defer thread.SetTop(0) + + argCount := 0 + for i, arg := range args { + if i+1 == len(args) && hint.IsVariadic() { + // arg is a varadic slice + for j := 0; j < arg.Len(); j++ { + arg := arg.Index(j) + thread.Push(New(thread, arg.Interface())) + argCount++ + } + break + } + + thread.Push(New(thread, arg.Interface())) + argCount++ + } + + thread.Call(argCount, lua.MultRet) + top := thread.GetTop() + + switch { + case emptyIfaceHint: + ret := reflect.MakeSlice(reflect.SliceOf(refTypeEmptyIface), top, top) + + for i := 1; i <= top; i++ { + item, err := lValueToReflect(thread, thread.Get(i), refTypeEmptyIface, nil) + if err != nil { + panic(err) + } + ret.Index(i - 1).Set(item) + } + + return []reflect.Value{ret} + + case top == hint.NumOut(): + ret := make([]reflect.Value, top) + + var err error + for i := 1; i <= top; i++ { + outHint := hint.Out(i - 1) + item := thread.Get(i) + ret[i-1], err = lValueToReflect(thread, item, outHint, nil) + if err != nil { + panic(err) + } + } + + return ret + + default: + panic(fmt.Errorf("expecting %d return values, got %d", hint.NumOut(), top)) + } + } + return reflect.MakeFunc(hint, fn), nil + case *lua.LNilType: + switch hint.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer, reflect.Uintptr: + return reflect.Zero(hint), nil + default: + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + case *lua.LState: + val := reflect.ValueOf(converted) + if !val.Type().ConvertibleTo(hint) { + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + return val.Convert(hint), nil + case lua.LString: + val := reflect.ValueOf(string(converted)) + if !val.Type().ConvertibleTo(hint) { + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + return val.Convert(hint), nil + case *lua.LTable: + if existing := visited[converted]; existing.IsValid() { + return existing, nil + } + + if hint == refTypeEmptyIface { + hint = reflect.MapOf(refTypeEmptyIface, refTypeEmptyIface) + } + + switch { + case hint.Kind() == reflect.Array: + elemType := hint.Elem() + length := converted.Len() + if length != hint.Len() { + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + s := reflect.New(hint).Elem() + visited[converted] = s + + for i := 0; i < length; i++ { + value := converted.RawGetInt(i + 1) + elemValue, err := lValueToReflectInner(L, value, elemType, visited, nil) + if err != nil { + return reflect.Value{}, err + } + s.Index(i).Set(elemValue) + } + + return s, nil + + case hint.Kind() == reflect.Slice: + elemType := hint.Elem() + length := converted.Len() + s := reflect.MakeSlice(hint, length, length) + visited[converted] = s + + for i := 0; i < length; i++ { + value := converted.RawGetInt(i + 1) + elemValue, err := lValueToReflectInner(L, value, elemType, visited, nil) + if err != nil { + return reflect.Value{}, err + } + s.Index(i).Set(elemValue) + } + + return s, nil + + case hint.Kind() == reflect.Map: + keyType := hint.Key() + elemType := hint.Elem() + s := reflect.MakeMap(hint) + visited[converted] = s + + for key := lua.LNil; ; { + var value lua.LValue + key, value = converted.Next(key) + if key == lua.LNil { + break + } + + lKey, err := lValueToReflectInner(L, key, keyType, visited, nil) + if err != nil { + return reflect.Value{}, err + } + lValue, err := lValueToReflectInner(L, value, elemType, visited, nil) + if err != nil { + return reflect.Value{}, err + } + s.SetMapIndex(lKey, lValue) + } + + return s, nil + + case hint.Kind() == reflect.Ptr && hint.Elem().Kind() == reflect.Struct: + hint = hint.Elem() + isPtr = true + fallthrough + case hint.Kind() == reflect.Struct: + s := reflect.New(hint) + visited[converted] = s + + t := s.Elem() + + mt := &Metatable{ + LTable: getMetatable(L, hint), + } + + for key := lua.LNil; ; { + var value lua.LValue + key, value = converted.Next(key) + if key == lua.LNil { + break + } + if _, ok := key.(lua.LString); !ok { + continue + } + + fieldName := key.String() + index := mt.fieldIndex(fieldName) + if index == nil { + return reflect.Value{}, structFieldError{ + Type: hint, + Field: fieldName, + } + } + field := hint.FieldByIndex(index) + + lValue, err := lValueToReflectInner(L, value, field.Type, visited, nil) + if err != nil { + return reflect.Value{}, nil + } + t.FieldByIndex(field.Index).Set(lValue) + } + + if isPtr { + return s, nil + } + + return t, nil + + default: + return reflect.Value{}, conversionError{ + Lua: v, + Hint: hint, + } + } + case *lua.LUserData: + val := reflect.ValueOf(converted.Value) + if tryConvertPtr != nil && val.Kind() != reflect.Ptr && hint.Kind() == reflect.Ptr && val.Type() == hint.Elem() { + newVal := reflect.New(hint.Elem()) + newVal.Elem().Set(val) + val = newVal + *tryConvertPtr = true + } else { + if !val.Type().ConvertibleTo(hint) { + return reflect.Value{}, conversionError{ + Lua: converted, + Hint: hint, + } + } + val = val.Convert(hint) + if tryConvertPtr != nil { + *tryConvertPtr = false + } + } + return val, nil + default: + panic("never reaches") + } +} diff --git a/vendor/layeh.com/gopher-luar/luar_test.go b/vendor/layeh.com/gopher-luar/luar_test.go new file mode 100644 index 0000000..d60cab3 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/luar_test.go @@ -0,0 +1,318 @@ +package luar + +import ( + "reflect" + "strings" + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_luar_complex128(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := complex(float64(1), float64(2)) + + L.SetGlobal("a", New(L, a)) + + testReturn(t, L, `b = a`) + + b := L.GetGlobal("b").(*lua.LUserData).Value.(complex128) + if a != b { + t.Fatalf("expected a = b, got %v", b) + } +} + +type ChanAlias chan string + +func (ChanAlias) Test() string { + return `I'm a "chan string" alias` +} + +func (ChanAlias) hidden() { +} + +type SliceAlias []string + +func (s SliceAlias) Len() int { + return len(s) +} + +func (s *SliceAlias) Append(v string) { + *s = append(*s, v) +} + +type MapAlias map[string]int + +func (m MapAlias) Y() int { + return len(m) +} + +func Test_type_methods(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := make(ChanAlias) + var b SliceAlias = []string{"Hello", "world"} + c := MapAlias{ + "x": 15, + } + + L.SetGlobal("a", New(L, a)) + L.SetGlobal("b", New(L, &b)) + L.SetGlobal("c", New(L, c)) + + testReturn(t, L, `return a:Test()`, `I'm a "chan string" alias`) + testReturn(t, L, `len1 = b:Len(); b:Append("!")`) + testReturn(t, L, `return len1, b:len()`, "2", "3") + testReturn(t, L, `return c.x, c:y()`, "15", "1") +} + +func Test_comparisons(t *testing.T) { + L := lua.NewState() + defer L.Close() + + { + s := make([]int, 10) + L.SetGlobal("s1", New(L, s)) + L.SetGlobal("sp1", New(L, &s)) + L.SetGlobal("s2", New(L, s)) + L.SetGlobal("sp2", New(L, &s)) + } + { + m := make(map[string]int, 10) + L.SetGlobal("m1", New(L, m)) + L.SetGlobal("mp1", New(L, &m)) + L.SetGlobal("m2", New(L, m)) + L.SetGlobal("mp2", New(L, &m)) + } + { + c := make(chan string) + L.SetGlobal("c1", New(L, c)) + L.SetGlobal("cp1", New(L, &c)) + L.SetGlobal("c2", New(L, c)) + + c3 := make(chan string) + L.SetGlobal("c3", New(L, c3)) + } + { + s := "" + L.SetGlobal("sp1", New(L, &s)) + L.SetGlobal("sp2", New(L, &s)) + } + + testReturn(t, L, `return s1 == s1`, "true") + testReturn(t, L, `return sp1 == sp2`, "true") + + testReturn(t, L, `return m1 == m1`, "true") + testReturn(t, L, `return mp1 == mp2`, "true") + + testReturn(t, L, `return c1 == c1`, "true") + testReturn(t, L, `return c1 == c3`, "false") + + testReturn(t, L, `return sp1 == sp2`, "true") +} + +type TestSliceConversion struct { + S []string +} + +func Test_sliceconversion(t *testing.T) { + L := lua.NewState() + defer L.Close() + + e := &TestSliceConversion{} + L.SetGlobal("e", New(L, e)) + + testReturn(t, L, `e.S = {"a", "b", "", "c"}`) + + valid := true + expecting := []string{"a", "b", "", "c"} + if len(e.S) != len(expecting) { + valid = false + } else { + for i, item := range e.S { + if item != expecting[i] { + valid = false + break + } + } + } + + if !valid { + t.Fatalf("expecting %#v, got %#v", expecting, e.S) + } +} + +type TestMapConversion struct { + S map[string]string +} + +func Test_mapconversion(t *testing.T) { + L := lua.NewState() + defer L.Close() + + e := &TestMapConversion{} + L.SetGlobal("e", New(L, e)) + + testReturn(t, L, `e.S = {b = nil, c = "hello"}`) + + valid := true + expecting := map[string]string{ + "c": "hello", + } + + if len(e.S) != len(expecting) { + valid = false + } else { + for key, value := range e.S { + expected, ok := expecting[key] + if !ok || value != expected { + valid = false + break + } + } + } + + if !valid { + t.Fatalf("expecting %#v, got %#v", expecting, e.S) + } + + if _, ok := e.S["b"]; ok { + t.Fatal(`e.S["b"] should not be set`) + } +} + +func Test_udconversion(t *testing.T) { + L := lua.NewState() + defer L.Close() + + ud := L.NewUserData() + ud.Value = "hello world" + L.SetGlobal("ud", ud) + + var out int + L.SetGlobal("out", New(L, &out)) + + testError(t, L, `_ = out ^ ud`, "cannot use hello world (type string) as type int") +} + +func Test_arrayconversion(t *testing.T) { + L := lua.NewState() + defer L.Close() + + var arr [3]int + L.SetGlobal("arr", New(L, &arr)) + testReturn(t, L, `arr = arr ^ {10, 20, 11}; return arr[1], arr[2], arr[3]`, "10", "20", "11") +} + +type Test_interface_struct struct{} + +func Test_interface(t *testing.T) { + tbl := []struct { + Code string + Var func(L *lua.LState) lua.LValue + Expected interface{} + ExpectedType reflect.Type + }{ + { + Code: `nil`, + Expected: interface{}(nil), + }, + { + Code: `"Hello"`, + Expected: string("Hello"), + }, + { + Code: `true`, + Expected: bool(true), + }, + { + Code: `1`, + Expected: float64(1), + }, + { + Code: `function(a, b) end`, + ExpectedType: reflect.TypeOf(func(...interface{}) []interface{} { + return nil + }), + }, + { + Code: `{hello = "world", [123] = 321}`, + Expected: map[interface{}]interface{}{ + string("hello"): string("world"), + float64(123): float64(321), + }, + }, + { + Code: `var`, + Var: func(L *lua.LState) lua.LValue { + ud := L.NewUserData() + ud.Value = "Hello World" + return ud + }, + Expected: string("Hello World"), + }, + // TODO: LChannel + // TODO: *LState + } + + for _, cur := range tbl { + func() { + L := lua.NewState() + defer L.Close() + + var out interface{} = Test_interface_struct{} + L.SetGlobal("out", New(L, &out)) + + if cur.Var != nil { + L.SetGlobal("var", cur.Var(L)) + } + + if err := L.DoString(`_ = out ^ ` + cur.Code); err != nil { + t.Fatal(err) + } + + if cur.ExpectedType != nil { + if reflect.TypeOf(out) != cur.ExpectedType { + t.Fatalf("expected conversion of %#v = type %s, got type %s\n", cur.Code, cur.ExpectedType, reflect.TypeOf(out)) + } + } else if !reflect.DeepEqual(out, cur.Expected) { + t.Fatalf("expected conversion of %#v = %#v (%T), got %#v (%T)\n", cur.Code, cur.Expected, cur.Expected, out, out) + } + }() + + } +} + +func Test_recursivetable(t *testing.T) { + L := lua.NewState() + defer L.Close() + + var x interface{} + L.SetGlobal("x", New(L, &x)) + + if err := L.DoString(`local tbl = {}; tbl.inner = tbl; _ = x ^ tbl`); err != nil { + t.Fatal(err) + } +} + +func Test_tostringfallback(t *testing.T) { + L := lua.NewState() + defer L.Close() + + type Struct struct { + } + var out string + + L.SetGlobal("struct", New(L, &Struct{})) + L.SetGlobal("out", New(L, &out)) + if err := L.DoString(`_ = out ^ tostring(struct)`); err != nil { + t.Fatal(err) + } + + if !strings.HasPrefix(out, "userdata: ") { + t.Fatalf("invalid tostring %#v\n", out) + } +} diff --git a/vendor/layeh.com/gopher-luar/map.go b/vendor/layeh.com/gopher-luar/map.go new file mode 100644 index 0000000..16f94f6 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/map.go @@ -0,0 +1,76 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +func mapIndex(L *lua.LState) int { + ref, mt := check(L, 1) + key := L.CheckAny(2) + + convertedKey, err := lValueToReflect(L, key, ref.Type().Key(), nil) + if err == nil { + item := ref.MapIndex(convertedKey) + if item.IsValid() { + L.Push(New(L, item.Interface())) + return 1 + } + } + + if lstring, ok := key.(lua.LString); ok { + if fn := mt.method(string(lstring)); fn != nil { + L.Push(fn) + return 1 + } + } + + return 0 +} + +func mapNewIndex(L *lua.LState) int { + ref, _ := check(L, 1) + key := L.CheckAny(2) + value := L.CheckAny(3) + + keyHint := ref.Type().Key() + convertedKey, err := lValueToReflect(L, key, keyHint, nil) + if err != nil { + L.ArgError(2, err.Error()) + } + var convertedValue reflect.Value + if value != lua.LNil { + convertedValue, err = lValueToReflect(L, value, ref.Type().Elem(), nil) + if err != nil { + L.ArgError(3, err.Error()) + } + } + ref.SetMapIndex(convertedKey, convertedValue) + return 0 +} + +func mapLen(L *lua.LState) int { + ref, _ := check(L, 1) + + L.Push(lua.LNumber(ref.Len())) + return 1 +} + +func mapCall(L *lua.LState) int { + ref, _ := check(L, 1) + + keys := ref.MapKeys() + i := 0 + fn := func(L *lua.LState) int { + if i >= len(keys) { + return 0 + } + L.Push(New(L, keys[i].Interface())) + L.Push(New(L, ref.MapIndex(keys[i]).Interface())) + i++ + return 2 + } + L.Push(L.NewFunction(fn)) + return 1 +} diff --git a/vendor/layeh.com/gopher-luar/map_test.go b/vendor/layeh.com/gopher-luar/map_test.go new file mode 100644 index 0000000..c9dcc82 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/map_test.go @@ -0,0 +1,96 @@ +package luar + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_map(t *testing.T) { + L := lua.NewState() + defer L.Close() + + thangs := map[string]int{ + "ABC": 123, + "DEF": 456, + } + + L.SetGlobal("thangs", New(L, thangs)) + + testReturn(t, L, `return thangs.ABC`, "123") + testReturn(t, L, `return thangs.DEF`, "456") + testReturn(t, L, `return thangs.GHI`, "nil") + + if err := L.DoString(`thangs.GHI = 789`); err != nil { + t.Fatal(err) + } + + testReturn(t, L, `thangs.ABC = nil`) + + if v := thangs["GHI"]; v != 789 { + t.Fatalf(`expecting thangs["GHI"] = 789, got %d`, v) + } + + if _, ok := thangs["ABC"]; ok { + t.Fatal(`expecting thangs["ABC"] to be unset`) + } +} + +func Test_map_iterator(t *testing.T) { + L := lua.NewState() + defer L.Close() + + countries := map[string]string{ + "JP": "Japan", + "CA": "Canada", + "FR": "France", + } + + L.SetGlobal("countries", New(L, countries)) + + testReturn(t, L, `return #countries`, "3") + + const code = ` + sorted = {} + for k, v in countries() do + table.insert(sorted, v) + end + table.sort(sorted)` + + if err := L.DoString(code); err != nil { + t.Fatal(err) + } + + testReturn(t, L, `return #sorted, sorted[1], sorted[2], sorted[3]`, "3", "Canada", "France", "Japan") +} + +type TestMapUsers map[uint32]string + +func (m TestMapUsers) Find(name string) uint32 { + for id, n := range m { + if name == n { + return id + } + } + return 0 +} + +func Test_map_methods(t *testing.T) { + L := lua.NewState() + defer L.Close() + + type User struct { + Name string + } + + users := TestMapUsers{ + 1: "Tim", + } + + L.SetGlobal("users", New(L, users)) + + testReturn(t, L, `return users[1]`, "Tim") + testReturn(t, L, `return users[3]`, "nil") + testReturn(t, L, `return users:Find("Tim")`, "1") + testReturn(t, L, `return users:Find("Steve")`, "0") +} diff --git a/vendor/layeh.com/gopher-luar/metatable.go b/vendor/layeh.com/gopher-luar/metatable.go new file mode 100644 index 0000000..859ca6e --- /dev/null +++ b/vendor/layeh.com/gopher-luar/metatable.go @@ -0,0 +1,42 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +// Metatable is the Lua metatable for a Go type. +type Metatable struct { + *lua.LTable +} + +// MT returns the metatable for value's type. nil is returned if value's type +// does not use a custom metatable. +func MT(L *lua.LState, value interface{}) *Metatable { + val := reflect.ValueOf(value) + switch val.Type().Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice, reflect.Struct: + return &Metatable{ + LTable: getMetatableFromValue(L, val), + } + default: + return nil + } +} + +func (m *Metatable) method(name string) lua.LValue { + methods := m.RawGetString("methods").(*lua.LTable) + if fn := methods.RawGetString(name); fn != lua.LNil { + return fn + } + return nil +} + +func (m *Metatable) fieldIndex(name string) []int { + fields := m.RawGetString("fields").(*lua.LTable) + if index := fields.RawGetString(name); index != lua.LNil { + return index.(*lua.LUserData).Value.([]int) + } + return nil +} diff --git a/vendor/layeh.com/gopher-luar/metatable_test.go b/vendor/layeh.com/gopher-luar/metatable_test.go new file mode 100644 index 0000000..cd317ae --- /dev/null +++ b/vendor/layeh.com/gopher-luar/metatable_test.go @@ -0,0 +1,37 @@ +package luar + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_metatable(t *testing.T) { + L := lua.NewState() + defer L.Close() + + tbl := []struct { + Value interface{} + CustomMT bool + }{ + {"hello", false}, + {123, false}, + {1.23, false}, + {struct{}{}, true}, + {&struct{}{}, true}, + {[]string{}, true}, + {make(chan string), true}, + {(*string)(nil), true}, + {func() {}, false}, + {map[string]int{}, true}, + } + + for _, v := range tbl { + mt := MT(L, v.Value) + if v.CustomMT && mt == nil { + t.Fatalf("expected to have custom MT for %#v\n", v.Value) + } else if !v.CustomMT && mt != nil { + t.Fatalf("unexpected custom MT for %#v\n", v.Value) + } + } +} diff --git a/vendor/layeh.com/gopher-luar/ptr.go b/vendor/layeh.com/gopher-luar/ptr.go new file mode 100644 index 0000000..5b3c085 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/ptr.go @@ -0,0 +1,76 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +func checkPtr(L *lua.LState, idx int) (ref reflect.Value, mt *Metatable) { + ud := L.CheckUserData(idx) + ref = reflect.ValueOf(ud.Value) + kind := reflect.Ptr + if ref.Kind() != kind { + L.ArgError(idx, "expecting "+kind.String()) + } + mt = &Metatable{LTable: ud.Metatable.(*lua.LTable)} + return +} + +func ptrIndex(L *lua.LState) int { + ref, mt := checkPtr(L, 1) + key := L.CheckString(2) + + if fn := mt.method(key); fn != nil { + L.Push(fn) + return 1 + } + + // fallback to non-pointer method + ref = ref.Elem() + mt = MT(L, ref.Interface()) + if fn := mt.method(key); fn != nil { + L.Push(fn) + return 1 + } + + return 0 +} + +func ptrPow(L *lua.LState) int { + ref, _ := checkPtr(L, 1) + val := L.CheckAny(2) + + if ref.IsNil() { + L.RaiseError("cannot dereference nil pointer") + } + elem := ref.Elem() + if !elem.CanSet() { + L.RaiseError("unable to set pointer value") + } + value, err := lValueToReflect(L, val, elem.Type(), nil) + if err != nil { + L.ArgError(2, err.Error()) + } + elem.Set(value) + L.SetTop(1) + return 1 +} + +func ptrUnm(L *lua.LState) int { + ref, _ := checkPtr(L, 1) + elem := ref.Elem() + if !elem.CanInterface() { + L.RaiseError("cannot interface pointer type " + elem.String()) + } + L.Push(New(L, elem.Interface())) + return 1 +} + +func ptrEq(L *lua.LState) int { + ref1, _ := checkPtr(L, 1) + ref2, _ := checkPtr(L, 2) + + L.Push(lua.LBool(ref1.Pointer() == ref2.Pointer())) + return 1 +} diff --git a/vendor/layeh.com/gopher-luar/ptr_test.go b/vendor/layeh.com/gopher-luar/ptr_test.go new file mode 100644 index 0000000..c87d8e0 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/ptr_test.go @@ -0,0 +1,134 @@ +package luar + +import ( + "testing" + + "github.com/yuin/gopher-lua" + "strings" +) + +func Test_ptr(t *testing.T) { + L := lua.NewState() + defer L.Close() + + str := "hello" + + L.SetGlobal("ptr", New(L, &str)) + + testReturn(t, L, `return -ptr`, "hello") +} + +func Test_ptr_comparison(t *testing.T) { + L := lua.NewState() + defer L.Close() + + var ptr1 *string + str := "hello" + + L.SetGlobal("ptr1", New(L, ptr1)) + L.SetGlobal("ptr2", New(L, &str)) + + testReturn(t, L, `return ptr1 == nil`, "true") + testReturn(t, L, `return ptr2 == nil`, "false") + testReturn(t, L, `return ptr1 == ptr2`, "false") +} + +func Test_ptr_assignment(t *testing.T) { + L := lua.NewState() + defer L.Close() + + str := "hello" + + L.SetGlobal("str", New(L, &str)) + + testReturn(t, L, `return tostring(-str)`, "hello") + testReturn(t, L, `str = str ^ "world"; return (string.match(tostring(str), "userdata:"))`, "userdata:") + testReturn(t, L, `return tostring(-str)`, "world") +} + +type TestPtrNested struct { + *TestPtrNestedChild +} + +type TestPtrNestedChild struct { + Value *string + StructTestPerson +} + +func Test_ptr_nested(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := TestPtrNested{ + TestPtrNestedChild: &TestPtrNestedChild{ + StructTestPerson: StructTestPerson{ + Name: "Tim", + }, + }, + } + + L.SetGlobal("a", New(L, &a)) + L.SetGlobal("str_ptr", NewType(L, "")) + + testReturn(t, L, `return a.Value == nil`, "true") + testReturn(t, L, `a.Value = str_ptr(); _ = a.Value ^ "hello"`) + testReturn(t, L, `return a.Value == nil`, "false") + testReturn(t, L, `return -a.Value`, "hello") + testReturn(t, L, `return a.Name`, "Tim") +} + +func Test_ptr_assignstruct(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := &StructTestPerson{ + Name: "tim", + } + b := &StructTestPerson{ + Name: "bob", + } + + L.SetGlobal("a", New(L, a)) + L.SetGlobal("b", New(L, b)) + + testReturn(t, L, `return a.Name`, "tim") + testReturn(t, L, `_ = a ^ -b`) + testReturn(t, L, `return a.Name`, "bob") +} + +type TestStringType string + +func (s *TestStringType) ToUpper() { + *s = TestStringType(strings.ToUpper(string(*s))) +} + +func Test_ptr_nested2(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := [...]StructTestPerson{ + {Name: "Tim"}, + } + s := []StructTestPerson{ + {Name: "Tim", Age: 32}, + } + + str := TestStringType("Hello World") + + L.SetGlobal("a", New(L, &a)) + L.SetGlobal("s", New(L, s)) + L.SetGlobal("p", New(L, &s[0])) + L.SetGlobal("str", New(L, &str)) + + testReturn(t, L, `return a[1]:AddNumbers(1, 2, 3, 4, 5)`, "Tim counts: 15") + testReturn(t, L, `return s[1]:AddNumbers(1, 2, 3, 4)`, "Tim counts: 10") + testReturn(t, L, `return s[1].LastAddSum`, "10") + testReturn(t, L, `return p:AddNumbers(1, 2, 3, 4, 5)`, "Tim counts: 15") + testReturn(t, L, `return p.LastAddSum`, "15") + + testReturn(t, L, `return p.Age`, "32") + testReturn(t, L, `p:IncreaseAge(); return p.Age`, "33") + + testReturn(t, L, `return -str`, "Hello World") + testReturn(t, L, `str:ToUpper(); return -str`, "HELLO WORLD") +} diff --git a/vendor/layeh.com/gopher-luar/race_test.go b/vendor/layeh.com/gopher-luar/race_test.go new file mode 100644 index 0000000..51b3e71 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/race_test.go @@ -0,0 +1,42 @@ +// +build race + +package luar + +import ( + "runtime" + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_functhread(t *testing.T) { + L := lua.NewState() + defer L.Close() + + var fn func(x int) string + L.SetGlobal("fn", New(L, &fn)) + + if err := L.DoString(`_ = fn ^ function(x) return tostring(x) .. "!" end`); err != nil { + t.Fatal(err) + } + + done := make(chan struct{}) + defer close(done) + go func() { + for { + select { + case <-done: + return + default: + } + + L.Push(lua.LNumber(1000)) + + runtime.Gosched() + } + }() + + if ret := fn(123); ret != "123!" { + t.Fatalf("expected %#v, got %#v", "123!", ret) + } +} diff --git a/vendor/layeh.com/gopher-luar/slice.go b/vendor/layeh.com/gopher-luar/slice.go new file mode 100644 index 0000000..aff1fe2 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/slice.go @@ -0,0 +1,91 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +func sliceIndex(L *lua.LState) int { + ref, mt := check(L, 1) + key := L.CheckAny(2) + + switch converted := key.(type) { + case lua.LNumber: + index := int(converted) + if index < 1 || index > ref.Len() { + L.ArgError(2, "index out of range") + } + val := ref.Index(index - 1) + if (val.Kind() == reflect.Struct || val.Kind() == reflect.Array) && val.CanAddr() { + val = val.Addr() + } + L.Push(New(L, val.Interface())) + case lua.LString: + if fn := mt.method(string(converted)); fn != nil { + L.Push(fn) + return 1 + } + return 0 + default: + L.ArgError(2, "must be a number or string") + } + return 1 +} + +func sliceNewIndex(L *lua.LState) int { + ref, _ := check(L, 1) + index := L.CheckInt(2) + value := L.CheckAny(3) + + if index < 1 || index > ref.Len() { + L.ArgError(2, "index out of range") + } + val, err := lValueToReflect(L, value, ref.Type().Elem(), nil) + if err != nil { + L.ArgError(3, err.Error()) + } + ref.Index(index - 1).Set(val) + return 0 +} + +func sliceLen(L *lua.LState) int { + ref, _ := check(L, 1) + + L.Push(lua.LNumber(ref.Len())) + return 1 +} + +func sliceCall(L *lua.LState) int { + ref, _ := check(L, 1) + + i := 0 + fn := func(L *lua.LState) int { + if i >= ref.Len() { + return 0 + } + item := ref.Index(i).Interface() + L.Push(lua.LNumber(i + 1)) + L.Push(New(L, item)) + i++ + return 2 + } + + L.Push(L.NewFunction(fn)) + return 1 +} + +func sliceAdd(L *lua.LState) int { + ref, _ := check(L, 1) + item := L.CheckAny(2) + + hint := ref.Type().Elem() + value, err := lValueToReflect(L, item, hint, nil) + if err != nil { + L.ArgError(2, err.Error()) + } + + ref = reflect.Append(ref, value) + L.Push(New(L, ref.Interface())) + return 1 +} diff --git a/vendor/layeh.com/gopher-luar/slice_test.go b/vendor/layeh.com/gopher-luar/slice_test.go new file mode 100644 index 0000000..4265018 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/slice_test.go @@ -0,0 +1,62 @@ +package luar + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_slice(t *testing.T) { + L := lua.NewState() + defer L.Close() + + things := []string{ + "cake", + "wallet", + "calendar", + "phone", + "speaker", + } + + L.SetGlobal("things", New(L, things)) + + testReturn(t, L, `return #things, things[1], things[5]`, "5", "cake", "speaker") + if err := L.DoString(`things[1] = "cookie"`); err != nil { + t.Fatal(err) + } + if things[0] != "cookie" { + t.Fatalf(`expected things[0] = "cookie", got %s`, things[0]) + } +} + +func Test_slice_2(t *testing.T) { + L := lua.NewState() + defer L.Close() + + items := make([]string, 0, 10) + + L.SetGlobal("items", New(L, items)) + + testReturn(t, L, `return #items`, "0") + testReturn(t, L, `items = items + "hello" + "world"; return #items`, "2") + testReturn(t, L, `return items[1]`, "hello") + testReturn(t, L, `return items[2]`, "world") +} + +func Test_slice_iterator(t *testing.T) { + L := lua.NewState() + defer L.Close() + + s := []string{ + "hello", + "there", + } + + e := []string{} + + L.SetGlobal("s", New(L, s)) + L.SetGlobal("e", New(L, e)) + + testReturn(t, L, `local itr = s(); local a, b = itr(); local c, d = itr(); return a, b, c, d`, "1", "hello", "2", "there") + testReturn(t, L, `local itr = e(); local a, b = itr(); return a, b`, "nil", "nil") +} diff --git a/vendor/layeh.com/gopher-luar/struct.go b/vendor/layeh.com/gopher-luar/struct.go new file mode 100644 index 0000000..09f1966 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/struct.go @@ -0,0 +1,97 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +func structIndex(L *lua.LState) int { + ref, mt := check(L, 1) + key := L.CheckString(2) + + if fn := mt.method(key); fn != nil { + L.Push(fn) + return 1 + } + + ref = reflect.Indirect(ref) + index := mt.fieldIndex(key) + if index == nil { + return 0 + } + field := ref.FieldByIndex(index) + if !field.CanInterface() { + L.RaiseError("cannot interface field " + key) + } + + if (field.Kind() == reflect.Struct || field.Kind() == reflect.Array) && field.CanAddr() { + field = field.Addr() + } + L.Push(New(L, field.Interface())) + return 1 +} + +func structPtrIndex(L *lua.LState) int { + ref, mt := check(L, 1) + key := L.CheckString(2) + + if fn := mt.method(key); fn != nil { + L.Push(fn) + return 1 + } + + ref = ref.Elem() + mt = MT(L, ref.Interface()) + if fn := mt.method(key); fn != nil { + L.Push(fn) + return 1 + } + + index := mt.fieldIndex(key) + if index == nil { + return 0 + } + field := ref.FieldByIndex(index) + if !field.CanInterface() { + L.RaiseError("cannot interface field " + key) + } + + if (field.Kind() == reflect.Struct || field.Kind() == reflect.Array) && field.CanAddr() { + field = field.Addr() + } + L.Push(New(L, field.Interface())) + return 1 +} + +func structPtrNewIndex(L *lua.LState) int { + ref, mt := check(L, 1) + key := L.CheckString(2) + value := L.CheckAny(3) + + ref = ref.Elem() + mt = MT(L, ref.Interface()) + + index := mt.fieldIndex(key) + if index == nil { + L.RaiseError("unknown field " + key) + } + field := ref.FieldByIndex(index) + if !field.CanSet() { + L.RaiseError("cannot set field " + key) + } + val, err := lValueToReflect(L, value, field.Type(), nil) + if err != nil { + L.ArgError(2, err.Error()) + } + field.Set(val) + return 0 +} + +func structEq(L *lua.LState) int { + ref1, _ := check(L, 1) + ref2, _ := check(L, 2) + + L.Push(lua.LBool(ref1.Interface() == ref2.Interface())) + return 1 +} diff --git a/vendor/layeh.com/gopher-luar/struct_test.go b/vendor/layeh.com/gopher-luar/struct_test.go new file mode 100644 index 0000000..9d6baf0 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/struct_test.go @@ -0,0 +1,308 @@ +package luar + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_struct(t *testing.T) { + L := lua.NewState() + defer L.Close() + + tim := &StructTestPerson{ + Name: "Tim", + Age: 30, + } + + john := StructTestPerson{ + Name: "John", + Age: 40, + } + + L.SetGlobal("user1", New(L, tim)) + L.SetGlobal("user2", New(L, john)) + + testReturn(t, L, `return user1.Name`, "Tim") + testReturn(t, L, `return user1.Age`, "30") + testReturn(t, L, `return user1:Hello()`, "Hello, Tim") + + testReturn(t, L, `return user2.Name`, "John") + testReturn(t, L, `return user2.Age`, "40") + testReturn(t, L, `local hello = user2.Hello; return hello(user2)`, "Hello, John") +} + +func Test_struct_tostring(t *testing.T) { + L := lua.NewState() + defer L.Close() + + p1 := StructTestPerson{ + Name: "Tim", + Age: 99, + } + p2 := StructTestPerson{ + Name: "John", + Age: 2, + } + + L.SetGlobal("p1", New(L, &p1)) + L.SetGlobal("p2", New(L, &p2)) + + testReturn(t, L, `return tostring(p1)`, `Tim (99)`) + testReturn(t, L, `return tostring(p2)`, `John (2)`) +} + +func Test_struct_pointers(t *testing.T) { + L := lua.NewState() + defer L.Close() + + p1 := StructTestPerson{ + Name: "Tim", + } + p2 := StructTestPerson{ + Name: "John", + } + + L.SetGlobal("p1", New(L, &p1)) + L.SetGlobal("p1_alias", New(L, &p1)) + L.SetGlobal("p2", New(L, &p2)) + + testReturn(t, L, `return -p1 == -p1`, "true") + testReturn(t, L, `return -p1 == -p1_alias`, "true") + testReturn(t, L, `return p1 == p1`, "true") + testReturn(t, L, `return p1 == p1_alias`, "true") + testReturn(t, L, `return p1 == p2`, "false") +} + +func Test_struct_lstate(t *testing.T) { + L := lua.NewState() + defer L.Close() + + p := StructTestPerson{ + Name: "Tim", + } + + L.SetGlobal("p", New(L, &p)) + + testReturn(t, L, `return p:AddNumbers(1, 2, 3, 4, 5)`, "Tim counts: 15") +} + +type StructTestHidden struct { + Name string `luar:"name"` + Name2 string `luar:"Name"` + Str string + Hidden bool `luar:"-"` +} + +func Test_struct_hiddenfields(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := &StructTestHidden{ + Name: "tim", + Name2: "bob", + Str: "asd123", + Hidden: true, + } + + L.SetGlobal("a", New(L, a)) + + testReturn(t, L, `return a.name`, "tim") + testReturn(t, L, `return a.Name`, "bob") + testReturn(t, L, `return a.str`, "asd123") + testReturn(t, L, `return a.Str`, "asd123") + testReturn(t, L, `return a.Hidden`, "nil") + testReturn(t, L, `return a.hidden`, "nil") +} + +func Test_struct_method(t *testing.T) { + L := lua.NewState() + defer L.Close() + + p := StructTestPerson{ + Name: "Tim", + Age: 66, + } + + L.SetGlobal("p", New(L, &p)) + + testReturn(t, L, `return p:hello()`, "Hello, Tim") + testReturn(t, L, `return p.age`, "66") +} + +type NestedPointer struct { + B NestedPointerChild +} + +type NestedPointerChild struct { +} + +func (*NestedPointerChild) Test() string { + return "Pointer test" +} + +func Test_struct_nestedptrmethod(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := NestedPointer{} + L.SetGlobal("a", New(L, &a)) + + testReturn(t, L, `return a.b:Test()`, "Pointer test") +} + +type TestStructEmbeddedType struct { + TestStructEmbeddedTypeString +} + +type TestStructEmbeddedTypeString string + +func Test_struct_embeddedtype(t *testing.T) { + L := lua.NewState() + defer L.Close() + + a := TestStructEmbeddedType{ + TestStructEmbeddedTypeString: "hello", + } + + L.SetGlobal("a", New(L, &a)) + + testReturn(t, L, `a.TestStructEmbeddedTypeString = "world"`) + + if val := a.TestStructEmbeddedTypeString; val != "world" { + t.Fatalf("expecting %s, got %s", "world", val) + } +} + +type TestStructEmbedded struct { + StructTestPerson + P StructTestPerson + P2 StructTestPerson `luar:"other"` +} + +func Test_struct_embedded(t *testing.T) { + L := lua.NewState() + defer L.Close() + + e := &TestStructEmbedded{} + L.SetGlobal("e", New(L, e)) + + testReturn( + t, + L, + ` + e.StructTestPerson = { + Name = "Bill", + Age = 33 + } + e.P = { + Name = "Tim", + Age = 94, + Friend = { + Name = "Bob", + Age = 77 + } + } + e.other = { + Name = "Dale", + Age = 26 + } + `, + ) + + { + expected := StructTestPerson{ + Name: "Bill", + Age: 33, + } + if e.StructTestPerson != expected { + t.Fatalf("expected %#v, got %#v", expected, e.StructTestPerson) + } + } + + { + expected := StructTestPerson{ + Name: "Bob", + Age: 77, + } + if *(e.P.Friend) != expected { + t.Fatalf("expected %#v, got %#v", expected, *e.P.Friend) + } + } + + { + expected := StructTestPerson{ + Name: "Dale", + Age: 26, + } + if e.P2 != expected { + t.Fatalf("expected %#v, got %#v", expected, e.P2) + } + } +} + +type TestPointerReplaceHidden struct { + A string `luar:"q"` + B int `luar:"other"` + C int `luar:"-"` +} + +func Test_struct_pointerreplacehidden(t *testing.T) { + L := lua.NewState() + defer L.Close() + + e := &TestPointerReplaceHidden{} + L.SetGlobal("e", New(L, e)) + + testReturn( + t, + L, + ` + _ = e ^ { + q = "Cat", + other = 675 + } + `, + ) + + expected := TestPointerReplaceHidden{ + A: "Cat", + B: 675, + } + + if *e != expected { + t.Fatalf("expected %v, got %v", expected, *e) + } + + testError( + t, + L, + ` + _ = e ^ { + C = 333 + } + `, + `type luar.TestPointerReplaceHidden has no field C`, + ) +} + +func Test_struct_ptreq(t *testing.T) { + L := lua.NewState() + defer L.Close() + + p1 := StructTestPerson{ + Name: "Tim", + } + + p2 := StructTestPerson{ + Name: "Tim", + } + + L.SetGlobal("p1", New(L, &p1)) + L.SetGlobal("p2", New(L, &p2)) + + if &p1 == &p2 { + t.Fatal("expected structs to be unequal") + } + testReturn(t, L, `return p1 == p2`, "false") +} diff --git a/vendor/layeh.com/gopher-luar/type.go b/vendor/layeh.com/gopher-luar/type.go new file mode 100644 index 0000000..740f5a7 --- /dev/null +++ b/vendor/layeh.com/gopher-luar/type.go @@ -0,0 +1,59 @@ +package luar + +import ( + "reflect" + + "github.com/yuin/gopher-lua" +) + +func checkType(L *lua.LState, idx int) reflect.Type { + ud := L.CheckUserData(idx) + return ud.Value.(reflect.Type) +} + +func typeCall(L *lua.LState) int { + ref := checkType(L, 1) + + var value reflect.Value + switch ref.Kind() { + case reflect.Chan: + buffer := L.OptInt(2, 0) + + if buffer < 0 { + L.ArgError(2, "negative buffer size") + } + if ref.ChanDir() != reflect.BothDir { + L.RaiseError("unidirectional channel type") + } + + value = reflect.MakeChan(ref, buffer) + case reflect.Map: + value = reflect.MakeMap(ref) + case reflect.Slice: + length := L.OptInt(2, 0) + capacity := L.OptInt(3, length) + + if length < 0 { + L.ArgError(2, "negative length") + } + if capacity < 0 { + L.ArgError(3, "negative capacity") + } + if length > capacity { + L.RaiseError("length > capacity") + } + + value = reflect.MakeSlice(ref, length, capacity) + default: + value = reflect.New(ref) + } + L.Push(New(L, value.Interface())) + return 1 +} + +func typeEq(L *lua.LState) int { + type1 := checkType(L, 1) + type2 := checkType(L, 2) + L.Push(lua.LBool(type1 == type2)) + return 1 +} diff --git a/vendor/layeh.com/gopher-luar/type_test.go b/vendor/layeh.com/gopher-luar/type_test.go new file mode 100644 index 0000000..1dad33d --- /dev/null +++ b/vendor/layeh.com/gopher-luar/type_test.go @@ -0,0 +1,42 @@ +package luar + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_type_slice(t *testing.T) { + L := lua.NewState() + defer L.Close() + + type ints []int + + L.SetGlobal("newInts", NewType(L, ints{})) + + testReturn(t, L, `ints = newInts(1); return #ints`, "1") + testReturn(t, L, `ints = newInts(0, 10); return #ints`, "0") +} + +func Test_type(t *testing.T) { + L := lua.NewState() + defer L.Close() + + tim := &StructTestPerson{ + Name: "Tim", + } + + L.SetGlobal("user1", New(L, tim)) + L.SetGlobal("Person", NewType(L, StructTestPerson{})) + L.SetGlobal("People", NewType(L, map[string]*StructTestPerson{})) + + testReturn(t, L, `user2 = Person(); user2.Name = "John"; user2.Friend = user1`) + testReturn(t, L, `return user2.Name`, "John") + testReturn(t, L, `return user2.Friend.Name`, "Tim") + testReturn(t, L, `everyone = People(); everyone["tim"] = user1; everyone["john"] = user2`) + + everyone := L.GetGlobal("everyone").(*lua.LUserData).Value.(map[string]*StructTestPerson) + if len(everyone) != 2 { + t.Fatalf("expecting len(everyone) = 2, got %d", len(everyone)) + } +} diff --git a/vendor/layeh.com/gopher-luar/util.go b/vendor/layeh.com/gopher-luar/util.go new file mode 100644 index 0000000..92e6e2b --- /dev/null +++ b/vendor/layeh.com/gopher-luar/util.go @@ -0,0 +1,36 @@ +package luar + +import ( + "fmt" + "reflect" + "unicode" + "unicode/utf8" + + "github.com/yuin/gopher-lua" +) + +func check(L *lua.LState, idx int) (ref reflect.Value, mt *Metatable) { + ud := L.CheckUserData(idx) + ref = reflect.ValueOf(ud.Value) + mt = &Metatable{LTable: ud.Metatable.(*lua.LTable)} + return +} + +func tostring(L *lua.LState) int { + ud := L.CheckUserData(1) + value := ud.Value + if stringer, ok := value.(fmt.Stringer); ok { + L.Push(lua.LString(stringer.String())) + } else { + L.Push(lua.LString(ud.String())) + } + return 1 +} + +func getUnexportedName(name string) string { + first, n := utf8.DecodeRuneInString(name) + if n == 0 { + return name + } + return string(unicode.ToLower(first)) + name[n:] +}