diff --git a/.travis.yml b/.travis.yml index b132b6aa..1382f953 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ env: # - docker go: - - 1.12.x + - 1.22.x git: depth: 1 diff --git a/README.md b/README.md index 08fdd9ad..15fdd6bd 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,7 @@ List file is set in yaml format. When selecting a host, you can filter by keywords. Can execute commands concurrently to multiple hosts. -lssh also has a shell (parallel shell) that connects to multiple hosts at the same time and pipes the execution results of local commands and remote hosts. -In addition, lsftp also has a shell that can be connected in parallel. +lsftp shells can be connected in parallel. Supported multiple ssh proxy, http/socks5 proxy, x11 forward, and port forwarding. @@ -25,7 +24,7 @@ Supported multiple ssh proxy, http/socks5 proxy, x11 forward, and port forwardin * There is a shell function that connects to multiple hosts in parallel for interactive operation and connects with local commands via pipes. * Supported multiple proxy, **ssh**, **http**, and **socks5** proxy. It's supported multi-stage proxy. * Supported **ssh-agent**. -* Supported **Local** and **Remote Port forward**, **Dynamic Forward(SOCKS5, http)**, **Reverse Dynamic Forward(SOCKS5)** and **x11 forward**. +* Supported **Local** and **Remote Port forward**, **Dynamic Forward(SOCKS5, http)**, **Reverse Dynamic Forward(SOCKS5, http)** and **x11 forward**. * Can use bashrc of local machine at ssh connection destination. * It supports various authentication methods. Password, Public key, Certificate and PKCS11(Yubikey etc.). * Can read the OpenSSH config (~/.ssh/config) and use it as it is. @@ -140,11 +139,6 @@ option(lssh) # run command parallel in selected server over ssh. lssh -p command... - # run command parallel in selected server over ssh, do it in interactively shell. - lssh -s - - - ### lscp run command. @@ -300,7 +294,7 @@ There are other parameters corresponding to ClientAliveInterval and ClientAliveC -### 2. [lssh] run command (parallel) +### 2. [lssh] run command (with parallel)
It is possible to execute by specifying command in argument.\ @@ -336,27 +330,7 @@ Can be piped to send Stdin.
-### 3. [lssh] Execute commands interactively (parallel shell) -
- -You can send commands to multiple servers interactively. - -

- -

- - # parallel shell connect - lssh -s - - -You can also combine remote and local commands. - - remote_command | !local_command - - -
- -### 4. [lscp] scp (local=>remote(multi), remote(multi)=>local, remote=>remote(multi)) +### 3. [lscp] scp (local=>remote(multi), remote(multi)=>local, remote=>remote(multi))
You can do scp by selecting a list with the command lscp.\ @@ -386,7 +360,7 @@ You can select multiple connection destinations. This program use sftp protocol.
-### 5. [lsftp] sftp (local=>remote(multi), remote(multi)=>local) +### 4. [lsftp] sftp (local=>remote(multi), remote(multi)=>local)
You can do sftp by selecting a list with the command lstp.\ @@ -402,7 +376,7 @@ You can select multiple connection destinations.
-### 6. include ~/.ssh/config file. +### 5. include ~/.ssh/config file.
Load and use `~/.ssh/config` by default.\ @@ -417,7 +391,7 @@ Alternatively, you can specify and read the path as follows: In addition to the
-### 7. include other ServerConfig file. +### 6. include other ServerConfig file.
You can include server settings in another file.\ @@ -459,7 +433,7 @@ The priority of setting values ​​is as follows.
-### 8. Supported Proxy +### 7. Supported Proxy
Supports multiple proxy. @@ -530,7 +504,7 @@ Besides this, you can also specify ProxyCommand like OpenSSH.
-### 9. Available authentication method +### 8. Available authentication method
* Password auth @@ -612,7 +586,7 @@ Besides this, you can also specify ProxyCommand like OpenSSH.
-### 10. Port forwarding +### 9. Port forwarding
Supported Local/Remote/Dynamic port forwarding.\ @@ -665,6 +639,10 @@ If OpenSsh config is loaded, it will be loaded as it is.
+## Related projects + +- [go-sshlib](github.com/blacknon/go-sshlib) +- [lsshell](github.com/blacknon/lsshell) ## Licence diff --git a/cmd/lscp/args.go b/cmd/lscp/args.go index 4a881674..e4f21aeb 100644 --- a/cmd/lscp/args.go +++ b/cmd/lscp/args.go @@ -58,7 +58,7 @@ USAGE: app.Name = "lscp" app.Usage = "TUI list select and parallel scp client command." app.Copyright = "blacknon(blacknon@orebibou.com)" - app.Version = "0.6.8" + app.Version = "0.6.9" // options // TODO(blacknon): オプションの追加(0.7.0) diff --git a/cmd/lsftp/args.go b/cmd/lsftp/args.go index c76abe49..8eb6bf6e 100644 --- a/cmd/lsftp/args.go +++ b/cmd/lsftp/args.go @@ -50,7 +50,7 @@ USAGE: app.Name = "lsftp" app.Usage = "TUI list select and parallel sftp client command." app.Copyright = "blacknon(blacknon@orebibou.com)" - app.Version = "0.6.8" + app.Version = "0.6.9" app.Flags = []cli.Flag{ cli.StringFlag{Name: "file,F", Value: defConf, Usage: "config file path"}, diff --git a/cmd/lssh/args.go b/cmd/lssh/args.go index b2c13ef6..28c5e6fa 100644 --- a/cmd/lssh/args.go +++ b/cmd/lssh/args.go @@ -51,9 +51,6 @@ USAGE: # run command parallel in selected server over ssh. {{.Name}} -p command... - - # run command parallel in selected server over ssh, do it in interactively shell. - {{.Name}} -s ` // Create app @@ -62,7 +59,7 @@ USAGE: app.Name = "lssh" app.Usage = "TUI list select and parallel ssh client command." app.Copyright = "blacknon(blacknon@orebibou.com)" - app.Version = "0.6.8" + app.Version = "0.6.9" // TODO(blacknon): オプションの追加 // -f ... バックグラウンドでの接続(X11接続やport forwardingをバックグラウンドで実行する場合など)。 @@ -85,7 +82,7 @@ USAGE: cli.StringSliceFlag{Name: "R", Usage: "Remote port forward mode.Specify a `[bind_address:]port:remote_address:port`. If only one port is specified, it will operate as Reverse Dynamic Forward. Only single connection works."}, cli.StringFlag{Name: "D", Usage: "Dynamic port forward mode(Socks5). Specify a `port`. Only single connection works."}, cli.StringFlag{Name: "d", Usage: "HTTP Dynamic port forward mode. Specify a `port`. Only single connection works."}, - // cli.StringFlag{Name: "r", Usage: "HTTP Reverse Dynamic port forward mode. Specify a `port`. Only single connection works."}, + cli.StringFlag{Name: "r", Usage: "HTTP Reverse Dynamic port forward mode. Specify a `port`. Only single connection works."}, // Other bool cli.BoolFlag{Name: "w", Usage: "Displays the server header when in command execution mode."}, @@ -97,7 +94,6 @@ USAGE: cli.BoolFlag{Name: "parallel,p", Usage: "run command parallel node(tail -F etc...)."}, cli.BoolFlag{Name: "localrc", Usage: "use local bashrc shell."}, cli.BoolFlag{Name: "not-localrc", Usage: "not use local bashrc shell."}, - cli.BoolFlag{Name: "pshell,s", Usage: "use parallel-shell(pshell) (alpha)."}, cli.BoolFlag{Name: "list,l", Usage: "print server list from config."}, cli.BoolFlag{Name: "help,h", Usage: "print this help"}, } @@ -249,6 +245,9 @@ USAGE: // HTTP Dynamic port forwarding port r.HTTPDynamicPortForward = c.String("d") + // HTTP Reverse Dynamic port forwarding port + r.HTTPReverseDynamicPortForward = c.String("r") + r.Start() return nil } diff --git a/cmd/s/.gitkeep b/cmd/s/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/conf/conf_struct_server.go b/conf/conf_struct_server.go index 6437a5ce..6d86a1ef 100644 --- a/conf/conf_struct_server.go +++ b/conf/conf_struct_server.go @@ -89,6 +89,10 @@ type ServerConfig struct { // ex.) "11080" HTTPDynamicPortForward string `toml:"http_dynamic_port_forward"` + // HTTP Reverse Dynamic Port Forward setting + // ex.) "11080" + HTTPReverseDynamicPortForward string `toml:"http_reverse_dynamic_port_forward"` + // x11 forwarding setting X11 bool `toml:"x11"` diff --git a/go.mod b/go.mod index fa7d27cf..8576e0b2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/blacknon/lssh -go 1.22.2 +go 1.22.4 + +toolchain go1.22.5 // require require ( @@ -11,7 +13,7 @@ require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 // indirect - github.com/blacknon/go-sshlib v0.1.11 + github.com/blacknon/go-sshlib v0.1.12 github.com/blacknon/go-x11auth v0.1.0 // indirect github.com/blacknon/textcol v0.0.1 github.com/c-bata/go-prompt v0.2.6 @@ -19,12 +21,10 @@ require ( github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect github.com/disiqueira/gotree v1.0.0 github.com/dustin/go-humanize v1.0.0 - github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8 github.com/kr/fs v0.1.0 // indirect - github.com/kr/pretty v0.1.0 // indirect github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/mattn/go-colorable v0.1.7 // indirect github.com/mattn/go-isatty v0.0.12 // indirect @@ -48,7 +48,6 @@ require ( golang.org/x/sys v0.19.0 golang.org/x/term v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - mvdan.cc/sh v2.6.3+incompatible ) // replace diff --git a/go.sum b/go.sum index 1987c6e7..f6f60219 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/blacknon/crypto11 v1.2.6 h1:Mv+Boto0qVR1O2k5lmoCYcyXfiQYY7mQ1P/TcK5Tw github.com/blacknon/crypto11 v1.2.6/go.mod h1:HThRIRjHpJIJwcExGgNuPCyf26HqcFVTTAnipaXWz7M= github.com/blacknon/go-prompt v0.2.7 h1:dVdTqVplKvpT/k4bB9BlbcBYl/k6amYX5tvjYBmuKkI= github.com/blacknon/go-prompt v0.2.7/go.mod h1:zNBmC/BPAyr+3ey1oRhPxuXJS9zz1lEmJpwaoQroe3w= -github.com/blacknon/go-sshlib v0.1.11 h1:cUaAb9Cv0PI9OyQ1xJTz5hvJRki8Jqgf90n4F0MYxOc= -github.com/blacknon/go-sshlib v0.1.11/go.mod h1:GvW6dUsuVG4dcmyqYv40H773YUsUK8MGHdsoQxbtr6M= +github.com/blacknon/go-sshlib v0.1.12 h1:K66DzwzF5RhlKQjcda0R7/FbvslXOayVd75UVzCKfZQ= +github.com/blacknon/go-sshlib v0.1.12/go.mod h1:EqEKtFd4DInkF3Ryx+YXnj70u7pJenVr4H3uEGEgoHI= github.com/blacknon/go-x11auth v0.1.0 h1:SnljCPWcvglWeGAlKc1RAPMHnOfMpM9+GrTGEUQ1lqQ= github.com/blacknon/go-x11auth v0.1.0/go.mod h1:SKOCa19LluXHyB+OaLYobquzceE0SWxVW7e/qU5xGBM= github.com/blacknon/textcol v0.0.1 h1:x9h7yLPGyr8Pdz12XJ30h7Iz5mJlKd0CzfGYxhrmnk8= @@ -29,10 +29,6 @@ github.com/disiqueira/gotree v1.0.0 h1:en5wk87n7/Jyk6gVME3cx3xN9KmUCstJ1IjHr4Se4 github.com/disiqueira/gotree v1.0.0/go.mod h1:7CwL+VWsWAU95DovkdRZAtA7YbtHwGk+tLV/kNi8niU= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M= -github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= @@ -43,11 +39,6 @@ github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8 h1:AUkD9wwFc github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -80,7 +71,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk= github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -137,5 +127,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -mvdan.cc/sh v2.6.3+incompatible h1:uXnnFNSBQbKUwwh2iBSkVjG+GbwoMuI+UmBVPnNiWhA= -mvdan.cc/sh v2.6.3+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8= diff --git a/sftp/shell.go b/sftp/shell.go index 54af7826..65d1e2c1 100644 --- a/sftp/shell.go +++ b/sftp/shell.go @@ -690,7 +690,12 @@ func (r *RunSftp) exitChecker(in string, breakline bool) bool { } if len(r.Client) == 0 { + // error messages fmt.Printf("Error: No valid connections\n") + + // TODO: 再接続が発生する場合はexitせずに返す? + os.Exit(1) + return true } diff --git a/ssh/cmd.go b/ssh/cmd.go index 6d92665d..102a2783 100644 --- a/ssh/cmd.go +++ b/ssh/cmd.go @@ -97,11 +97,16 @@ func (r *Run) cmd() (err error) { config.ReverseDynamicPortForward = r.ReverseDynamicPortForward } - // OverWrite dynamic port forwarding + // OverWrite http dynamic port forwarding if r.HTTPDynamicPortForward != "" { config.HTTPDynamicPortForward = r.HTTPDynamicPortForward } + // OverWrite reverse http dynamic port forwarding + if r.HTTPReverseDynamicPortForward != "" { + config.HTTPReverseDynamicPortForward = r.HTTPReverseDynamicPortForward + } + // OverWrite local bashrc use if r.IsBashrc { config.LocalRcUse = "yes" @@ -150,6 +155,12 @@ func (r *Run) cmd() (err error) { go c.HTTPDynamicForward("localhost", config.HTTPDynamicPortForward) } + // HTTP Reverse Dynamic Port Forwarding + if config.HTTPReverseDynamicPortForward != "" { + r.printHTTPReverseDynamicPortForward(config.HTTPReverseDynamicPortForward) + go c.HTTPReverseDynamicForward("localhost", config.HTTPReverseDynamicPortForward) + } + // if tty if r.IsTerm { c.Stdin = os.Stdin @@ -172,7 +183,7 @@ func (r *Run) cmd() (err error) { go output.PushInput(exitInput, writers, os.Stdin) case !r.IsParallel && len(r.ServerList) > 1: - if r.isStdinPipe { + if r.IsStdinPipe { stdinData, _ = ioutil.ReadAll(os.Stdin) } } diff --git a/ssh/pshell.go b/ssh/pshell.go deleted file mode 100644 index b9a26ee7..00000000 --- a/ssh/pshell.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2022 Blacknon. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -package ssh - -import ( - "fmt" - "log" - "os" - "os/signal" - "strconv" - "strings" - "syscall" - "time" - - "github.com/blacknon/go-sshlib" - "github.com/blacknon/lssh/output" - "github.com/c-bata/go-prompt" -) - -// TODO(blacknon): 接続が切れた場合の再接続処理、および再接続ができなかった場合のsliceからの削除対応の追加(v0.7.0) -// TODO(blacknon): pShellのログ(実行コマンド及び出力結果)をログとしてファイルに記録する機能の追加(v0.7.0) => 任意のファイルを指定するように -// TODO(blacknon): グループ化(`()`で囲んだりする)や三項演算子への対応(v0.7.0) -// TODO(blacknon): `サーバ名:command...` で、指定したサーバでのみコマンドを実行させる機能の追加(v0.6.8) -// TODO(blacknon): petをうまいこと利用できるような仕組みを作る(v0.7.0) -// TODO(blacknon): parallel shellでkeybindや関数が使えるような仕組みを作る(どうやってやるかは不明だが…)(v0.7.0) - -// TODO(blacknon): -// 出力をvim diffに食わせてdiffを得られるようにしたい => 変数かプロセス置換か、なにかしらの方法でローカルコマンド実行時にssh経由で得られた出力を食わせる方法を実装する? -// => 多分、プロセス置換が良いんだと思う(プロセス置換時にssh先でコマンドを実行できるように、かつ実行したデータを個別にファイルとして扱えるようにしたい) -// ```bash -// !vimdiff <(cat /etc/passwd) -// => !vimdiff host1:/etc/passwd host2:/etc/passwd .... -// ``` -// やるなら普通に一時ファイルに書き出すのが良さそう(/tmp 配下とか。一応、ちゃんと権限周り気をつけないといかんね、というのと消さないといかんね、というお気持ち) - -// Pshell is Parallel-Shell struct -type pShell struct { - Signal chan os.Signal - Count int - ServerList []string - Connects []*psConnect - PROMPT string - History map[int]map[string]*pShellHistory - HistoryFile string - latestCommand string - CmdComplete []prompt.Suggest - PathComplete []prompt.Suggest - Options pShellOption -} - -// pShellOption is optitons pshell. -// TODO(blacknon): つくる。 -type pShellOption struct { - // trueの場合、リモートマシンでパイプライン処理をする際にパイプ経由でもOPROMPTを付与して出力する - // RemoteHeaderWithPipe bool - - // trueの場合、リモートマシンにキーインプットを送信しない - // hogehoge - - // trueの場合、コマンドの補完処理を無効にする - // DisableCommandComplete bool - - // trueの場合、PATHの補完処理を無効にする - // DisableCommandComplete bool - - // local command実行時の結果をHistoryResultに記録しない(os.Stdoutに直接出す) - LocalCommandNotRecordResult bool -} - -// psConnect is pShell connect struct. -type psConnect struct { - Name string - Output *output.Output - *sshlib.Connect -} - -// variable -var ( - // Default PROMPT - defaultPrompt = "[${COUNT}] <<< " - - // Default OPROMPT - defaultOPrompt = "[${SERVER}][${COUNT}] > " - - // Default Parallel shell history file - defaultHistoryFile = "~/.lssh_history" -) - -func (r *Run) pshell() (err error) { - // print header - fmt.Println("Start parallel-shell...") - r.PrintSelectServer() - - // read shell config - config := r.Conf.Shell - - // overwrite default value config.Prompt - if config.Prompt == "" { - config.Prompt = defaultPrompt - } - - // overwrite default value config.OPrompt - if config.OPrompt == "" { - config.OPrompt = defaultOPrompt - } - - // overwrite default parallel shell history file - if config.HistoryFile == "" { - config.HistoryFile = defaultHistoryFile - } - - // run pre cmd - execLocalCommand(config.PreCmd) - defer execLocalCommand(config.PostCmd) - - // Connect - var cons []*psConnect - for _, server := range r.ServerList { - // Create *sshlib.Connect - con, err := r.CreateSshConnect(server) - if err != nil { - log.Println(err) - continue - } - - // TTY enable - con.TTY = true - - // Create Output - o := &output.Output{ - Templete: config.OPrompt, - ServerList: r.ServerList, - Conf: r.Conf.Server[server], - AutoColor: true, - } - - // Create output prompt - o.Create(server) - - psCon := &psConnect{ - Name: server, - Output: o, - Connect: con, - } - cons = append(cons, psCon) - } - - // count sshlib.Connect. - if len(cons) == 0 { - return - } - - // create new shell struct - ps := &pShell{ - Signal: make(chan os.Signal), - ServerList: r.ServerList, - Connects: cons, - PROMPT: config.Prompt, - History: map[int]map[string]*pShellHistory{}, - HistoryFile: config.HistoryFile, - Options: pShellOption{ - LocalCommandNotRecordResult: true, // debug - }, - } - - // set signal - // TODO: Windows対応 - // - 参考: https://cad-san.hatenablog.com/entry/2017/01/09/170213 - signal.Notify(ps.Signal, syscall.SIGTERM, syscall.SIGINT, os.Interrupt) - - // old history list - var historyCommand []string - oldHistory, err := ps.GetHistoryFromFile() - if err == nil { - for _, h := range oldHistory { - historyCommand = append(historyCommand, h.Command) - } - } - - // check keepalive - go func() { - for { - ps.checkKeepalive() - time.Sleep(3 * time.Second) - } - }() - - // create complete data - // TODO(blacknon): 定期的に裏で取得するよう処理を加える(v0.6.1) - ps.GetCommandComplete() - - // create go-prompt - p := prompt.New( - ps.Executor, - ps.Completer, - prompt.OptionHistory(historyCommand), - prompt.OptionLivePrefix(ps.CreatePrompt), - prompt.OptionInputTextColor(prompt.Green), - prompt.OptionPrefixTextColor(prompt.Blue), - prompt.OptionCompletionWordSeparator("/: \\"), // test - // Keybind - // Alt+Backspace - prompt.OptionAddASCIICodeBind(prompt.ASCIICodeBind{ - ASCIICode: []byte{0x1b, 0x7f}, - Fn: prompt.DeleteWord, - }), - // Opt+LeftArrow - prompt.OptionAddASCIICodeBind(prompt.ASCIICodeBind{ - ASCIICode: []byte{0x1b, 0x62}, - Fn: prompt.GoLeftWord, - }), - // Opt+RightArrow - prompt.OptionAddASCIICodeBind(prompt.ASCIICodeBind{ - ASCIICode: []byte{0x1b, 0x66}, - Fn: prompt.GoRightWord, - }), - // Alt+LeftArrow - prompt.OptionAddASCIICodeBind(prompt.ASCIICodeBind{ - ASCIICode: []byte{0x1b, 0x1b, 0x5B, 0x44}, - Fn: prompt.GoLeftWord, - }), - // Alt+RightArrow - prompt.OptionAddASCIICodeBind(prompt.ASCIICodeBind{ - ASCIICode: []byte{0x1b, 0x1b, 0x5B, 0x43}, - Fn: prompt.GoRightWord, - }), - prompt.OptionSetExitCheckerOnInput(ps.exitChecker), - ) - - // start go-prompt - p.Run() - - return -} - -// CreatePrompt is create shell prompt. -// default value is `[${COUNT}] <<< ` -func (ps *pShell) CreatePrompt() (p string, result bool) { - // set prompt templete (from conf) - p = ps.PROMPT - if p == "" { - p = defaultPrompt - } - - // Get env - hostname, _ := os.Hostname() - username := os.Getenv("USER") - pwd := os.Getenv("PWD") - - // replace variable value - p = strings.Replace(p, "${COUNT}", strconv.Itoa(ps.Count), -1) - p = strings.Replace(p, "${HOSTNAME}", hostname, -1) - p = strings.Replace(p, "${USER}", username, -1) - p = strings.Replace(p, "${PWD}", pwd, -1) - - return p, true -} - -func (ps *pShell) exitChecker(in string, breakline bool) bool { - if breakline { - ps.checkKeepalive() - } - - if len(ps.Connects) == 0 { - fmt.Printf("Error: No valid connections\n") - return true - } - - return false -} diff --git a/ssh/pshell_cmd.go b/ssh/pshell_cmd.go deleted file mode 100644 index 7dda9062..00000000 --- a/ssh/pshell_cmd.go +++ /dev/null @@ -1,482 +0,0 @@ -// Copyright (c) 2022 Blacknon. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -package ssh - -import ( - "bufio" - "fmt" - "io" - "os" - "os/exec" - "reflect" - "regexp" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/blacknon/go-sshlib" - "github.com/blacknon/lssh/output" - "golang.org/x/crypto/ssh" -) - -var ( - pShellHelptext = `{{.Name}} - {{.Usage}} - - {{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{end}} - {{range .VisibleFlags}} {{.}} - {{end}} - ` -) - -// TODO(blacknon): 以下のBuild-in Commandを追加する -// - %cd ... リモートのディレクトリを変更する(事前のチェックにsftpを使用か?) -// - %lcd ... ローカルのディレクトリを変更する -// - %save ... 指定したnumの履歴をPATHに記録する (v0.6.11) -// - %set ... 指定されたオプションを設定する(Optionsにて管理) (v0.6.11) -// - %diff ... 指定されたnumの履歴をdiffする(multi diff)。できるかどうか要検討。 (v0.7.0以降) -// できれば、vimdiffのように横に差分表示させるようにしたいものだけど…? -// - %get remote local ... sftpプロトコルを利用して、ファイルやディレクトリを取得する (v0.6.11) -// - %put local remote ... sftpプロトコルを利用して、ファイルやディレクトリを配置する (v0.6.11) - -// TODO(blacknon): 任意のBuild-in Commandを追加できるようにする -// - configにて、環境変数に過去のoutの出力をつけて任意のスクリプトを実行できるようにしてやることで、任意のスクリプト実行が可能に出来たら良くないか?というネタ -// - もしくは、Goのモジュールとして機能追加できるようにするって方法もありかも?? - -// checkBuildInCommand return true if cmd is build-in command. -func checkBuildInCommand(cmd string) (isBuildInCmd bool) { - // check build-in command - switch cmd { - case "exit", "quit", "clear": // build-in command - isBuildInCmd = true - - case - "%history", - "%out", "%outlist", "%outexec", - "%save", - "%set": // parsent build-in command. - isBuildInCmd = true - } - - return -} - -// checkLocalCommand return bool, check is pshell build-in command or -// local machine command(%%command). -func checkLocalCommand(cmd string) (isLocalCmd bool) { - // check local command regex - regex := regexp.MustCompile(`^?.*`) - - // local command - switch { - case regex.MatchString(cmd): - isLocalCmd = true - } - - return -} - -// check local or build-in command -func checkLocalBuildInCommand(cmd string) (result bool) { - // check build-in command - result = checkBuildInCommand(cmd) - if result { - return result - } - - // check local command - result = checkLocalCommand(cmd) - - return result -} - -// runBuildInCommand is run buildin or local machine command. -func (ps *pShell) run(pline pipeLine, in *io.PipeReader, out *io.PipeWriter, ch chan<- bool, kill chan bool) (err error) { - // get 1st element - command := pline.Args[0] - - // check and exec build-in command - switch command { - // exit or quit - case "exit", "quit": - os.Exit(0) - - // clear - case "clear": - fmt.Printf("\033[H\033[2J") - return - - // %history - case "%history": - ps.buildin_history(out, ch) - return - - // %outlist - case "%outlist": - ps.buildin_outlist(out, ch) - return - - // %out [num] - case "%out": - num := ps.Count - 1 - if len(pline.Args) > 1 { - num, err = strconv.Atoi(pline.Args[1]) - if err != nil { - return - } - } - - ps.buildin_out(num, out, ch) - return - - // %outexec [num] - case "%outexec": - ps.buildin_outexec(pline, in, out, ch, kill) - return - } - - // check and exec local command - buildinRegex := regexp.MustCompile(`^!.*`) - switch { - case buildinRegex.MatchString(command): - // exec local machine - ps.executeLocalPipeLine(pline, in, out, ch, kill, os.Environ()) - default: - // exec remote machine - ps.executeRemotePipeLine(pline, in, out, ch, kill) - } - - return -} - -// localCmd_set is set pshll option. -// TODO(blacknon): Optionsの値などについて、あとから変更できるようにする。 -// func (ps *pShell) buildin_set(args []string, out *io.PipeWriter, ch chan<- bool) { -// } - -// localCmd_save is save HistoryResult results as a file local. -// %save num PATH(独自の環境変数を利用して個別のファイルに保存できるようにする) -// TODO(blacknon): Optionsの値などについて、あとから変更できるようにする。 -// func (ps *pShell) buildin_save(args []string, out *io.PipeWriter, ch chan<- bool) { -// } - -// localCmd_history is printout history (shell history) -func (ps *pShell) buildin_history(out *io.PipeWriter, ch chan<- bool) { - stdout := setOutput(out) - - // read history file - data, err := ps.GetHistoryFromFile() - if err != nil { - return - } - - // print out history - for _, h := range data { - fmt.Fprintf(stdout, "%s: %s\n", h.Timestamp, h.Command) - } - - // close out - switch stdout.(type) { - case *io.PipeWriter: - out.CloseWithError(io.ErrClosedPipe) - } - - // send exit - ch <- true -} - -// localcmd_outlist is print exec history list. -func (ps *pShell) buildin_outlist(out *io.PipeWriter, ch chan<- bool) { - stdout := setOutput(out) - - for i := 0; i < len(ps.History); i++ { - h := ps.History[i] - for _, hh := range h { - fmt.Fprintf(stdout, "%3d : %s\n", i, hh.Command) - break - } - } - - // close out - switch stdout.(type) { - case *io.PipeWriter: - out.CloseWithError(io.ErrClosedPipe) - } - - // send exit - ch <- true -} - -// localCmd_out is print exec history at number -// example: -// - %out -// - %out -func (ps *pShell) buildin_out(num int, out *io.PipeWriter, ch chan<- bool) { - stdout := setOutput(out) - histories := ps.History[num] - - // get key - keys := []string{} - for k := range histories { - keys = append(keys, k) - } - sort.Strings(keys) - - i := 0 - for _, k := range keys { - h := histories[k] - - // if first, print out command - if i == 0 { - fmt.Fprintf(os.Stderr, "[History:%s ]\n", h.Command) - } - i += 1 - - // print out result - if len(histories) > 1 && stdout == os.Stdout && h.Output != nil { - // set Output.Count - bc := h.Output.Count - h.Output.Count = num - op := h.Output.GetPrompt() - - // TODO(blacknon): Outputを利用させてOPROMPTを生成 - sc := bufio.NewScanner(strings.NewReader(h.Result)) - for sc.Scan() { - fmt.Fprintf(stdout, "%s %s\n", op, sc.Text()) - } - - // reset Output.Count - h.Output.Count = bc - } else { - fmt.Fprintf(stdout, h.Result) - } - } - - // close out - switch stdout.(type) { - case *io.PipeWriter: - out.CloseWithError(io.ErrClosedPipe) - } - - // send exit - ch <- true -} - -// executePipeLineRemote is exec command in remote machine. -// Didn't know how to send data from Writer to Channel, so switch the function if * io.PipeWriter is Nil. -func (ps *pShell) executeRemotePipeLine(pline pipeLine, in *io.PipeReader, out *io.PipeWriter, ch chan<- bool, kill chan bool) { - // join command - command := strings.Join(pline.Args, " ") - - // set stdin/stdout - stdin := setInput(in) - stdout := setOutput(out) - - // create channels - exit := make(chan bool) - exitInput := make(chan bool) // Input finish channel - exitOutput := make(chan bool) - - // create []io.WriteCloser - var writers []io.WriteCloser - - // create []ssh.Session - var sessions []*ssh.Session - - // create session and writers - m := new(sync.Mutex) - for _, c := range ps.Connects { - // create session - s, err := c.CreateSession() - if err != nil { - continue - } - - // Request tty (Only when input is os.Stdin and output is os.Stdout). - if stdin == os.Stdin && stdout == os.Stdout { - sshlib.RequestTty(s) - } - - // set stdout - var ow io.Writer - ow = stdout - if ow == os.Stdout { - // create Output Writer - c.Output.Count = ps.Count - w := c.Output.NewWriter() - defer w.CloseWithError(io.ErrClosedPipe) - - // create pShellHistory Writer - hw := ps.NewHistoryWriter(c.Output.Server, c.Output, m) - defer hw.CloseWithError(io.ErrClosedPipe) - - ow = io.MultiWriter(w, hw) - } - s.Stdout = ow - - // get and append stdin writer - w, _ := s.StdinPipe() - writers = append(writers, w) - - // append sessions - sessions = append(sessions, s) - } - - // multi input-writer - go output.PushInput(exitInput, writers, stdin) - - // run command - for _, s := range sessions { - session := s - go func() { - session.Run(command) - session.Close() - exit <- true - if stdout == os.Stdout { - exitOutput <- true - } - }() - } - - // kill - go func() { - select { - case <-kill: - for _, s := range sessions { - s.Signal(ssh.SIGINT) - s.Close() - } - } - }() - - // wait - ps.wait(len(sessions), exit) - - // wait time (0.050 sec) - time.Sleep(500 * time.Millisecond) - - // send exit - ch <- true - - // exit input. - if stdin == os.Stdin { - exitInput <- true - } - - // close out - switch stdout.(type) { - case *io.PipeWriter: - out.CloseWithError(io.ErrClosedPipe) - } - - // wait time (0.050 sec) - time.Sleep(500 * time.Millisecond) - - return -} - -// executePipeLineLocal is exec command in local machine. -// TODO(blacknon): 利用中のShellでの実行+functionや環境変数、aliasの引き継ぎを行えるように実装 -func (ps *pShell) executeLocalPipeLine(pline pipeLine, in *io.PipeReader, out *io.PipeWriter, ch chan<- bool, kill chan bool, envrionment []string) (err error) { - // set stdin/stdout - stdin := setInput(in) - stdout := setOutput(out) - - // set HistoryResult - var stdoutw io.Writer - stdoutw = stdout - m := new(sync.Mutex) - if stdout == os.Stdout { - pw := ps.NewHistoryWriter("localhost", nil, m) - defer pw.CloseWithError(io.ErrClosedPipe) - stdoutw = io.MultiWriter(pw, stdout) - } else { - stdoutw = stdout - } - - // delete command prefix(`!`) - rep := regexp.MustCompile(`^!`) - pline.Args[0] = rep.ReplaceAllString(pline.Args[0], "") - - // join command - command := strings.Join(pline.Args, " ") - - // execute command - var cmd *exec.Cmd - if runtime.GOOS == "windows" { - cmd = exec.Command("powershell.exe", "-c", command) - } else { - cmd = exec.Command("sh", "-c", command) - } - - // set stdin, stdout, stderr - cmd.Stdin = stdin - if ps.Options.LocalCommandNotRecordResult { - cmd.Stdout = stdout - } else { // default - cmd.Stdout = stdoutw - } - cmd.Stderr = os.Stderr - - // set envrionment - cmd.Env = envrionment - - // run command - err = cmd.Start() - - // get signal and kill - p := cmd.Process - go func() { - select { - case <-kill: - p.Kill() - } - }() - - // wait command - cmd.Wait() - - // close out, or write pShellHistory - switch stdout.(type) { - case *io.PipeWriter: - out.CloseWithError(io.ErrClosedPipe) - } - - // send exit - ch <- true - - return -} - -// ps.wait -func (ps *pShell) wait(num int, ch <-chan bool) { - for i := 0; i < num; i++ { - <-ch - } -} - -// setInput -func setInput(in io.ReadCloser) (stdin io.ReadCloser) { - if reflect.ValueOf(in).IsNil() { - stdin = os.Stdin - } else { - stdin = in - } - - return -} - -// setOutput -func setOutput(out io.WriteCloser) (stdout io.WriteCloser) { - if reflect.ValueOf(out).IsNil() { - stdout = os.Stdout - } else { - stdout = out - } - - return -} diff --git a/ssh/pshell_cmd_outexec.go b/ssh/pshell_cmd_outexec.go deleted file mode 100644 index ddd36bd8..00000000 --- a/ssh/pshell_cmd_outexec.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2024 Blacknon. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -package ssh - -import ( - "crypto/md5" - "fmt" - "io" - "os" - "sort" - "strconv" - - "github.com/urfave/cli" -) - -// outExecEnvrionment is struct for outexec environment -type outExecEnvrionment struct { - Environment string - Value string -} - -// localcmd_outexec -// example: -// - %outexec -n [num] regist command... -func (ps *pShell) buildin_outexec(pline pipeLine, in *io.PipeReader, out *io.PipeWriter, ch chan<- bool, kill chan bool) (err error) { - // set help text template - pShellHelptext = `{{.Name}} - {{.Usage}} - - {{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{end}} - {{range .VisibleFlags}} {{.}} - {{end}} - - Note: - This command is a built-in command for lssh. - Saves the output of the selected history in the \${LSSH_PSHELL_OUT_{md5(SERVER_NAME)_VALUE}} environment variable and executes the registration command passed in the argument. - and \${LSSH_PSHELL_OUT_{md5(SERVER_NAME)_NAME}} in server name. - - example: - outexec -n [num] regist command... - ` - - // create app - app := cli.NewApp() - - // set help message - app.CustomAppHelpTemplate = pShellHelptext - - // default number - num := ps.Count - 1 - - // set parameter - app.Flags = []cli.Flag{ - cli.StringFlag{Name: "n", Usage: "set history number.", Value: strconv.Itoa(num)}, - } - - app.Name = "outexec" - app.Usage = "lssh build-in command: outexec [-n num] regist command..." - app.ArgsUsage = "perm path..." - app.HideHelp = true - app.HideVersion = true - app.EnableBashCompletion = true - - // action - app.Action = func(c *cli.Context) (aerr error) { - // show help messages - if c.Bool("help") { - cli.ShowAppHelp(c) - return nil - } - - // check count args - if len(c.Args()) < 1 { - fmt.Fprintln(os.Stderr, "Too few arguments.") - cli.ShowAppHelp(c) - return nil - } - - hnum, aerr := strconv.Atoi(c.String("n")) - - histories := ps.History[hnum] - - // get key - keys := []string{} - for k := range histories { - keys = append(keys, k) - } - sort.Strings(keys) - - // get environment set - appendEnv := []outExecEnvrionment{} - for _, k := range keys { - h := histories[k] - - key := md5.Sum([]byte(k)) - - en := outExecEnvrionment{ - Environment: fmt.Sprintf("LSSH_PSHELL_OUT_%s_NAME", fmt.Sprintf("%x", key)), - Value: k, - } - appendEnv = append(appendEnv, en) - - ev := outExecEnvrionment{ - Environment: fmt.Sprintf("LSSH_PSHELL_OUT_%s_VALUE", fmt.Sprintf("%x", key)), - Value: h.Result, - } - appendEnv = append(appendEnv, ev) - } - childEnvrionment := genOutExecChildEnv(appendEnv) - - // create pline - ppline := pipeLine{ - Args: c.Args(), - } - - // run local command - err = ps.executeLocalPipeLine(ppline, in, out, ch, kill, childEnvrionment) - - return aerr - } - - app.Run(pline.Args) - - return -} - -// genChildEnv generate child environment. -func genOutExecChildEnv(env []outExecEnvrionment) (result []string) { - // If an environment variable is already set, retrieve the old value so you can rollback. - rollbackEnvMap := map[string]string{} - - for _, e := range env { - // get old env value - oldValue := os.Getenv(e.Environment) - rollbackEnvMap[e.Environment] = oldValue - - // set value - os.Setenv(e.Environment, e.Value) - } - - // get result - result = os.Environ() - - // rollback - for k, v := range rollbackEnvMap { - if v == "" { - _ = os.Unsetenv(k) - } else { - _ = os.Setenv(k, v) - } - } - - return result -} diff --git a/ssh/pshell_complete.go b/ssh/pshell_complete.go deleted file mode 100644 index eb8ac089..00000000 --- a/ssh/pshell_complete.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright (c) 2022 Blacknon. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -package ssh - -import ( - "bufio" - "bytes" - "os/exec" - libpath "path" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "sync" - - "github.com/c-bata/go-prompt" -) - -// TODO(blacknon): `!!`や"`:$`についても実装を行う -// TODO(blacknon): `!command`だとまとめてパイプ経由でデータを渡すことになっているが、`!!command`で個別のローカルコマンドにデータを渡すように実装する - -// Completer parallel-shell complete function -func (ps *pShell) Completer(t prompt.Document) []prompt.Suggest { - // if currente line data is none. - if len(t.CurrentLine()) == 0 { - return prompt.FilterHasPrefix(nil, t.GetWordBeforeCursor(), false) - } - - // Get cursor left - left := t.CurrentLineBeforeCursor() - pslice, err := parsePipeLine(left) - if err != nil { - return prompt.FilterHasPrefix(nil, t.GetWordBeforeCursor(), false) - } - - // Get cursor char(string) - char := "" - if len(left) > 0 { - char = string(left[len(left)-1]) - } - - sl := len(pslice) // pline slice count - ll := 0 - num := 0 - if sl >= 1 { - ll = len(pslice[sl-1]) // pline count - num = len(pslice[sl-1][ll-1].Args) // pline args count - } - - if sl >= 1 && ll >= 1 { - c := pslice[sl-1][ll-1].Args[0] - - // switch suggest - switch { - case num <= 1 && !contains([]string{" ", "|"}, char): // if command - var c []prompt.Suggest - - // build-in command suggest - buildin := []prompt.Suggest{ - {Text: "exit", Description: "exit lssh shell"}, - {Text: "quit", Description: "exit lssh shell"}, - {Text: "clear", Description: "clear screen"}, - {Text: "%history", Description: "show history"}, - {Text: "%out", Description: "%out [num], show history result."}, - {Text: "%outlist", Description: "%outlist, show history result list."}, - {Text: "%outexec", Description: "%outexec <-n num> command..., exec local command with output result. result is in env variable."}, - // outの出力でdiffをするためのローカルコマンド。すべての出力と比較するのはあまりに辛いと思われるため、最初の出力との比較、といった方式で対応するのが良いか?? - // {Text: "%diff", Description: "%diff [num], show history result list."}, - } - c = append(c, buildin...) - - // get remote and local command complete data - c = append(c, ps.CmdComplete...) - - // return - return prompt.FilterHasPrefix(c, t.GetWordBeforeCursor(), false) - - case checkBuildInCommand(c): // if build-in command. - var suggest []prompt.Suggest - switch c { - // %out - case "%out": - for i := 0; i < len(ps.History); i++ { - var cmd string - for _, h := range ps.History[i] { - cmd = h.Command - } - - s := prompt.Suggest{ - Text: strconv.Itoa(i), - Description: cmd, - } - suggest = append(suggest, s) - } - - // %outexec - case "%outexec": - // switch options or path - switch { - case contains([]string{"-"}, char): - suggest = []prompt.Suggest{ - {Text: "--help", Description: "help message"}, - {Text: "-h", Description: "help message"}, - {Text: "-n", Description: "set history number"}, - } - - case "-n " == t.GetWordBeforeCursorWithSpace(): - for i := 0; i < len(ps.History); i++ { - var cmd string - for _, h := range ps.History[i] { - cmd = h.Command - } - - s := prompt.Suggest{ - Text: strconv.Itoa(i), - Description: cmd, - } - suggest = append(suggest, s) - } - - default: - suggest = ps.GetLocalhostCommandComplete() - } - - } - - return prompt.FilterHasPrefix(suggest, t.GetWordBeforeCursor(), false) - - default: - switch { - case contains([]string{"/"}, char): // char is slach or - ps.PathComplete = ps.GetPathComplete(!checkLocalCommand(c), t.GetWordBeforeCursor()) - case contains([]string{" "}, char) && strings.Count(t.CurrentLineBeforeCursor(), " ") == 1: - ps.PathComplete = ps.GetPathComplete(!checkLocalCommand(c), t.GetWordBeforeCursor()) - } - - // get last slash place - word := t.GetWordBeforeCursor() - sp := strings.LastIndex(word, "/") - if len(word) > 0 { - word = word[sp+1:] - } - - return prompt.FilterHasPrefix(ps.PathComplete, word, false) - } - } - - return prompt.FilterHasPrefix(nil, t.GetWordBeforeCursor(), false) -} - -// GetLocalhostCommandComplete -func (ps *pShell) GetLocalhostCommandComplete() (suggest []prompt.Suggest) { - // bash complete command. use `compgen`. - compCmd := []string{"compgen", "-c"} - command := strings.Join(compCmd, " ") - - // get local machine command complete - local, _ := exec.Command("bash", "-c", command).Output() - rd := strings.NewReader(string(local)) - sc := bufio.NewScanner(rd) - for sc.Scan() { - s := prompt.Suggest{ - Text: sc.Text(), - Description: "Command. from:localhost", - } - suggest = append(suggest, s) - } - - return suggest -} - -// GetCommandComplete get command list remote machine. -// mode ... command/path -// data ... Value being entered -func (ps *pShell) GetCommandComplete() { - // bash complete command. use `compgen`. - compCmd := []string{"compgen", "-c"} - command := strings.Join(compCmd, " ") - - // get local machine command complete - local, _ := exec.Command("bash", "-c", command).Output() - rd := strings.NewReader(string(local)) - sc := bufio.NewScanner(rd) - for sc.Scan() { - suggest := prompt.Suggest{ - Text: "!" + sc.Text(), - Description: "Command. from:localhost", - } - ps.CmdComplete = append(ps.CmdComplete, suggest) - } - - // get remote machine command complete - // command map - cmdMap := map[string][]string{} - - // append command to cmdMap - for _, c := range ps.Connects { - // Create buffer - buf := new(bytes.Buffer) - - // Create session, and output to buffer - session, _ := c.CreateSession() - session.Stdout = buf - - // Run get complete command - session.Run(command) - - // Scan and put completed command to map. - sc := bufio.NewScanner(buf) - for sc.Scan() { - cmdMap[sc.Text()] = append(cmdMap[sc.Text()], c.Name) - } - } - - // cmdMap to suggest - for cmd, hosts := range cmdMap { - // join hosts - sort.Strings(hosts) - h := strings.Join(hosts, ",") - - // create suggest - suggest := prompt.Suggest{ - Text: cmd, - Description: "Command. from:" + h, - } - - // append ps.Complete - ps.CmdComplete = append(ps.CmdComplete, suggest) - } - - sort.SliceStable(ps.CmdComplete, func(i, j int) bool { return ps.CmdComplete[i].Text < ps.CmdComplete[j].Text }) -} - -// GetPathComplete return complete path from local or remote machine. -// TODO(blacknon): 複数のノードにあるPATHだけ補完リストに出てる状態なので、単一ノードにしか無いファイルも出力されるよう修正する -func (ps *pShell) GetPathComplete(remote bool, word string) (p []prompt.Suggest) { - compCmd := []string{"compgen", "-f", word} - command := strings.Join(compCmd, " ") - - switch { - case remote: // is remote machine - // create map - m := map[string][]string{} - - exit := make(chan bool) - - // create sync mutex - sm := new(sync.Mutex) - - // append path to m - for _, c := range ps.Connects { - con := c - go func() { - // Create buffer - buf := new(bytes.Buffer) - - // Create session, and output to buffer - session, _ := c.CreateSession() - session.Stdout = buf - - // Run get complete command - session.Run(command) - - // Scan and put completed command to map. - sc := bufio.NewScanner(buf) - for sc.Scan() { - sm.Lock() - - path := libpath.Base(sc.Text()) - - m[path] = append(m[path], con.Name) - sm.Unlock() - } - - exit <- true - }() - } - - for i := 0; i < len(ps.Connects); i++ { - <-exit - } - - // m to suggest - for path, hosts := range m { - // join hosts - h := strings.Join(hosts, ",") - - // create suggest - suggest := prompt.Suggest{ - Text: path, - Description: "remote path. from:" + h, - } - - // append ps.Complete - p = append(p, suggest) - } - - case !remote: // is local machine - if runtime.GOOS != "windows" { - sgt, _ := exec.Command("bash", "-c", command).Output() - rd := strings.NewReader(string(sgt)) - sc := bufio.NewScanner(rd) - for sc.Scan() { - suggest := prompt.Suggest{ - Text: filepath.Base(sc.Text()), - // Text: sc.Text(), - Description: "local path.", - } - p = append(p, suggest) - } - } - } - - sort.SliceStable(p, func(i, j int) bool { return p[i].Text < p[j].Text }) - return -} - -func contains(s []string, e string) bool { - for _, v := range s { - if e == v { - return true - } - } - return false -} diff --git a/ssh/pshell_executor.go b/ssh/pshell_executor.go deleted file mode 100644 index 06cc760c..00000000 --- a/ssh/pshell_executor.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) 2022 Blacknon. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -package ssh - -import ( - "fmt" - "io" - "os" - "strings" -) - -// PipeSet is pipe in/out set struct. -type PipeSet struct { - in *io.PipeReader - out *io.PipeWriter -} - -// Executor run ssh command in parallel-shell. -func (ps *pShell) Executor(command string) { - // trim space - command = strings.TrimSpace(command) - - // parse command - pslice, _ := parsePipeLine(command) - if len(pslice) == 0 { - return - } - - // set latest command - ps.latestCommand = command - - // regist history - ps.PutHistoryFile(command) - - // exec pipeline - ps.parseExecuter(pslice) - - return -} - -// parseExecuter assemble and execute the parsed command line. -// TODO(blacknon): 現状はパイプにしか対応していないので、`&&`や`||`にも対応できるよう変更する(v0.6.1) -// TODO(blacknon): !commandで1プロセス、!!commandでssh接続ごとにプロセスを生成してローカルのコマンドを実行するように変更(v0.6.1) -func (ps *pShell) parseExecuter(pslice [][]pipeLine) { - // Create History - ps.History[ps.Count] = map[string]*pShellHistory{} - - // for pslice - for _, pline := range pslice { - // count pipe num - pnum := countPipeSet(pline, "|") - - // create pipe set - pipes := createPipeSet(pnum) - - // join pipe set - pline = joinPipeLine(pline) - - // printout run command - fmt.Printf("[Command:%s ]\n", joinPipeLineSlice(pline)) - - // pipe counter - var n int - - // create channel - ch := make(chan bool) - defer close(ch) - - kill := make(chan bool) - defer close(kill) - - for i, p := range pline { - // declare nextPipeLine - var bp pipeLine - - // declare in,out - var in *io.PipeReader - var out *io.PipeWriter - - // get next pipe line - if i > 0 { - bp = pline[i-1] - } - - // set stdin - // If the before delimiter is a pipe, set the stdin before io.PipeReader. - if bp.Oprator == "|" { - in = pipes[n-1].in - } - - // set stdout - // If the delimiter is a pipe, set the stdout output a io.PipeWriter. - if p.Oprator == "|" { - out = pipes[n].out - - // add pipe num - n++ - } - - // exec pipeline - go ps.run(p, in, out, ch, kill) - } - - // get and send kill - killExit := make(chan bool) - defer close(killExit) - go func(sig chan os.Signal) { - select { - case <-sig: - for i := 0; i < len(pline); i++ { - kill <- true - } - case <-killExit: - return - } - }(ps.Signal) - - // wait channel - ps.wait(len(pline), ch) - } - - // add ps.Count - // (Does not count if only the built-in command is executed) - isBuildInOnly := true - for _, pline := range pslice { - if len(pline) > 1 { - isBuildInOnly = false - break - } - - if !checkBuildInCommand(pline[0].Args[0]) { - isBuildInOnly = false - break - } - } - - if !isBuildInOnly { - ps.Count++ - } -} - -// countPipeSet count delimiter in pslice. -func countPipeSet(pline []pipeLine, del string) (count int) { - for _, p := range pline { - if p.Oprator == del { - count++ - } - } - - return count -} - -// createPipeSet return Returns []*PipeSet used by the process. -func createPipeSet(count int) (pipes []*PipeSet) { - for i := 0; i < count; i++ { - in, out := io.Pipe() - pipe := &PipeSet{ - in: in, - out: out, - } - - pipes = append(pipes, pipe) - } - - return -} diff --git a/ssh/pshell_history.go b/ssh/pshell_history.go deleted file mode 100644 index d0b85bdd..00000000 --- a/ssh/pshell_history.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2022 Blacknon. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -package ssh - -import ( - "bufio" - "fmt" - "io" - "os" - "os/user" - "strings" - "sync" - "time" - - "github.com/blacknon/lssh/output" -) - -type pShellHistory struct { - Timestamp string - Command string - Result string - Output *output.Output -} - -// -func (ps *pShell) NewHistoryWriter(server string, output *output.Output, m *sync.Mutex) *io.PipeWriter { - // craete pShellHistory struct - psh := &pShellHistory{ - Command: ps.latestCommand, - Timestamp: time.Now().Format("2006/01/02_15:04:05 "), // "yyyy/mm/dd_HH:MM:SS " - Output: output, - } - - // create io.PipeReader, io.PipeWriter - r, w := io.Pipe() - - // output Struct - go ps.pShellHistoryPrint(psh, server, r, m) - - // return io.PipeWriter - return w -} - -func (ps *pShell) pShellHistoryPrint(psh *pShellHistory, server string, r *io.PipeReader, m *sync.Mutex) { - count := ps.Count - - var result string - sc := bufio.NewScanner(r) -loop: - for { - for sc.Scan() { - text := sc.Text() - result = result + text + "\n" - } - - if sc.Err() == io.ErrClosedPipe { - break loop - } - - select { - case <-time.After(50 * time.Millisecond): - continue - } - } - - // Add Result - psh.Result = result - - // Add History - m.Lock() - ps.History[count][server] = psh - m.Unlock() -} - -// GetHistoryFromFile return []History from historyfile -func (ps *pShell) GetHistoryFromFile() (data []pShellHistory, err error) { - // user path - usr, _ := user.Current() - histfile := strings.Replace(ps.HistoryFile, "~", usr.HomeDir, 1) - - // Open history file - file, err := os.OpenFile(histfile, os.O_RDONLY, 0600) - if err != nil { - return - } - defer file.Close() - - sc := bufio.NewScanner(file) - for sc.Scan() { - line := sc.Text() - text := strings.SplitN(line, " ", 2) - - if len(text) < 2 { - continue - } - - d := pShellHistory{ - Timestamp: text[0], - Command: text[1], - Result: "", - } - - data = append(data, d) - } - return -} - -// PutHistoryFile put history text to s.HistoryFile -// ex.) write history(history file format) -// YYYY-mm-dd_HH:MM:SS command... -// YYYY-mm-dd_HH:MM:SS command... -// ... -func (ps *pShell) PutHistoryFile(cmd string) (err error) { - // user path - usr, _ := user.Current() - histfile := strings.Replace(ps.HistoryFile, "~", usr.HomeDir, 1) - - // Open history file - file, err := os.OpenFile(histfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) - if err != nil { - return - } - defer file.Close() - - // Get Time - timestamp := time.Now().Format("2006/01/02_15:04:05 ") // "yyyy/mm/dd_HH:MM:SS " - - fmt.Fprintln(file, timestamp+cmd) - - return -} diff --git a/ssh/pshell_keepalive.go b/ssh/pshell_keepalive.go deleted file mode 100644 index 18875159..00000000 --- a/ssh/pshell_keepalive.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2024 Blacknon. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -package ssh - -import ( - "fmt" - "os" - "sync" -) - -func (ps *pShell) checkKeepalive() { - result := []*psConnect{} - ch := make(chan bool) - m := new(sync.Mutex) - clients := ps.Connects - - for _, client := range clients { - go func(client *psConnect) { - // keepalive - err := client.Connect.CheckClientAlive() - - if err != nil { - // error - fmt.Fprintf(os.Stderr, "Exit Connect %s, Error: %s\n", client.Name, err) - - // close sftp client - client.Client.Close() - } else { - // delete client from map - m.Lock() - result = append(result, client) - m.Unlock() - } - - ch <- true - }(client) - } - - // wait - for i := 0; i < len(clients); i++ { - <-ch - } - - ps.Connects = result - - return -} diff --git a/ssh/pshell_parse.go b/ssh/pshell_parse.go deleted file mode 100644 index 84ad3130..00000000 --- a/ssh/pshell_parse.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) 2022 Blacknon. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -package ssh - -import ( - "bytes" - "strings" - - "mvdan.cc/sh/syntax" -) - -type pipeLine struct { - Args []string - Oprator string -} - -// pipeLine return string of join -func (p *pipeLine) String() string { - result := strings.Join(p.Args, " ") - result = result + " " + p.Oprator - - return result -} - -// joinPipeLineSlice -func joinPipeLineSlice(pslice []pipeLine) string { - var result string - for _, pline := range pslice { - result = result + pline.String() - } - - return result -} - -// joinPipeLine is concatenates a pipe without a built-in command or -// local command as a command to be executed on a remote machine as a string. -func joinPipeLine(pslice []pipeLine) []pipeLine { - beforeLocal := false - var bpline pipeLine // before pipeLine - result := []pipeLine{} - - for _, pline := range pslice { - // get command - cmd := pline.Args[0] - - // check in local or build-in command - isLocal := checkLocalBuildInCommand(cmd) - switch { - case isLocal: - if len(bpline.Args) > 0 { - result = append(result, bpline) - } - bpline = pline - beforeLocal = true - case !isLocal && beforeLocal: // RemoteCommand で前がLocalの場合 - if len(bpline.Args) > 0 { - result = append(result, bpline) - } - bpline = pline - beforeLocal = false - case !isLocal && !beforeLocal: // RemoteCommandで前がRemoteの場合 - // append bpline - bpline.Args = append(bpline.Args, bpline.Oprator) - bpline.Args = append(bpline.Args, pline.Args...) - bpline.Oprator = pline.Oprator - beforeLocal = false - } - } - - result = append(result, bpline) - return result -} - -// parseCmdPipeLine return [][]pipeLine. -func parsePipeLine(command string) (pslice [][]pipeLine, err error) { - // Create result pipeLineSlice - pslice = [][]pipeLine{} - - // Create parser - in := strings.NewReader(command) - f, err := syntax.NewParser().Parse(in, " ") - if err != nil { - return - } - - // parse stmt - for _, stmt := range f.Stmts { - // create slice - var cmdLine []pipeLine - - // create stmtCmd, stmtRedirs - var stmtCmd syntax.Command - var stmtRedirs []*syntax.Redirect - stmtCmd = stmt.Cmd - stmtRedirs = stmt.Redirs - - // parse stmt loop - stmtCmdLoop: - for { - switch c := stmtCmd.(type) { - case *syntax.CallExpr: - args := parseCallExpr(c) - - args = append(args, parseRedirect(stmtRedirs)...) - pLine := pipeLine{ - Args: args, - } - cmdLine = append(cmdLine, pLine) - - break stmtCmdLoop - case *syntax.BinaryCmd: - switch c.X.Cmd.(type) { - case *syntax.CallExpr: - cx := c.X.Cmd.(*syntax.CallExpr) - cxr := c.X.Redirs - - args := parseCallExpr(cx) - args = append(args, parseRedirect(cxr)...) - - pLine := pipeLine{ - Args: args, - Oprator: c.Op.String(), - } - cmdLine = append(cmdLine, pLine) - stmtCmd = c.Y.Cmd - stmtRedirs = c.Y.Redirs - - case *syntax.BinaryCmd: // TODO(blacknon): &&や||に対応させる(対処方法がわからん…) - stmtCmd = c.X.Cmd - stmtRedirs = c.X.Redirs - } - } - } - - pslice = append(pslice, cmdLine) - } - - return -} - -// parseCallExpr return pipeline element ([]string). -func parseCallExpr(cmd *syntax.CallExpr) (pLine []string) { - printer := syntax.NewPrinter() - - for _, arg := range cmd.Args { - for _, part := range arg.Parts { - buf := new(bytes.Buffer) - printer.Print(buf, part) - pLine = append(pLine, buf.String()) - - } - } - return -} - -// parseRedirect return pipeline redirect element ([]string) -func parseRedirect(redir []*syntax.Redirect) (rs []string) { - printer := syntax.NewPrinter() - - for _, r := range redir { - var rr string - if r.N != nil { - rr = rr + r.N.Value - } - rr = rr + r.Op.String() - for _, part := range r.Word.Parts { - buf := new(bytes.Buffer) - printer.Print(buf, part) - rr = rr + buf.String() - } - - rs = append(rs, rr) - } - - return -} diff --git a/ssh/run.go b/ssh/run.go index 52c13c36..0e3c0958 100644 --- a/ssh/run.go +++ b/ssh/run.go @@ -22,6 +22,7 @@ import ( // TODO(blacknon): 自動再接続機能の追加(v1.0.0) // autosshのように、接続が切れた際に自動的に再接続を試みる動作をさせたい // パラメータでの有効・無効指定が必要になる。 +// → go-sshlib側で処理させる // TODO(blacknon): リバースでのsshfsの追加(v1.0.0以降?) // lsshfs実装後になるか?ssh接続時に、指定したフォルダにローカルの内容をマウントさせて読み取らせる。 @@ -32,6 +33,8 @@ import ( // - https://github.com/rom1v/rsshfs // - https://github.com/hanwen/go-fuse // - https://gitlab.com/dns2utf8/revfs/ +// +// → go-sshlib側で処理させる(sshfsもreverse sshfsも共に) // Run type Run struct { @@ -41,7 +44,6 @@ type Run struct { // Mode value in // - shell // - cmd - // - pshell Mode string // tty use (-t option) @@ -89,6 +91,10 @@ type Run struct { // set remotehost port num (ex. 11080). ReverseDynamicPortForward string + // HTTP Reverse Dynamic Port Forwarding + // set remotehost port num (ex. 11080). + HTTPReverseDynamicPortForward string + // Exec command ExecCmd []string @@ -101,7 +107,7 @@ type Run struct { agent interface{} // StdinData from pipe flag - isStdinPipe bool + IsStdinPipe bool // AuthMethodMap is // map of AuthMethod summarized in Run overall @@ -162,7 +168,7 @@ func (r *Run) Start() { if runtime.GOOS != "windows" { stdin := 0 if !terminal.IsTerminal(stdin) { - r.isStdinPipe = true + r.IsStdinPipe = true } } @@ -179,10 +185,6 @@ func (r *Run) Start() { // connect remote shell err = r.shell() - case r.Mode == "pshell": - // start lsshshell - err = r.pshell() - default: return } @@ -252,6 +254,15 @@ func (r *Run) printHTTPDynamicPortForward(port string) { } } +// printHTTPReverseDynamicPortForward is printout port forwarding. +// use ssh command run header. only use shell(). +func (r *Run) printHTTPReverseDynamicPortForward(port string) { + if port != "" { + fmt.Fprintf(os.Stderr, "HTTPReverseDynamicForward:%s\n", port) + fmt.Fprintf(os.Stderr, " %s\n", "connect http.") + } +} + // printProxy is printout proxy route. // use ssh command run header. only use shell(). func (r *Run) printProxy(server string) { diff --git a/ssh/shell.go b/ssh/shell.go index ddf754f2..6a961e6e 100644 --- a/ssh/shell.go +++ b/ssh/shell.go @@ -2,8 +2,6 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -// TODO(blacknon): proxyを経由してPKCS11を使う際、Panicが起こることがあるので対応を考える(多分Proxy周りの処理に問題がある) - package ssh import ( @@ -52,6 +50,11 @@ func (r *Run) shell() (err error) { config.HTTPDynamicPortForward = r.HTTPDynamicPortForward } + // OverWrite http reverse dynamic port forwarding + if r.HTTPReverseDynamicPortForward != "" { + config.HTTPReverseDynamicPortForward = r.HTTPReverseDynamicPortForward + } + // OverWrite local bashrc use if r.IsBashrc { config.LocalRcUse = "yes" @@ -123,11 +126,16 @@ func (r *Run) shell() (err error) { go connect.HTTPDynamicForward("localhost", config.HTTPDynamicPortForward) } + // HTTP Reverse Dynamic Port Forwarding + if config.HTTPReverseDynamicPortForward != "" { + go connect.HTTPReverseDynamicForward("localhost", config.HTTPReverseDynamicPortForward) + } + // switch check Not-execute flag // TODO(blacknon): Backgroundフラグを実装したら追加 switch { case r.IsNone: - r.noneExecute() + r.noneExecute(connect) default: // run pre local command @@ -205,14 +213,28 @@ func (r *Run) getLogDirPath(server string) (dir string, err error) { } // noneExecute is not execute command and shell. -func (r *Run) noneExecute() (err error) { +func (r *Run) noneExecute(con *sshlib.Connect) (err error) { loop: for { select { case <-time.After(500 * time.Millisecond): + // 接続状況チェック + err = con.CheckClientAlive() + if err != nil { + // error + fmt.Fprintf(os.Stderr, "Exit Connect, Error: %s\n", err) + + // close sftp client + con.Client.Close() + + break loop + } + continue loop } } + + return } // localRcShell connect to remote shell using local bashrc diff --git a/vendor/github.com/blacknon/go-sshlib/auth.go b/vendor/github.com/blacknon/go-sshlib/auth.go index 0f82fb08..56f6b7e5 100644 --- a/vendor/github.com/blacknon/go-sshlib/auth.go +++ b/vendor/github.com/blacknon/go-sshlib/auth.go @@ -13,7 +13,7 @@ package sshlib import ( "fmt" - "io/ioutil" + "os" "regexp" "strings" @@ -46,7 +46,7 @@ func CreateSignerPublicKey(key, password string) (signer ssh.Signer, err error) key = getAbsPath(key) // Read PrivateKey file - keyData, err := ioutil.ReadFile(key) + keyData, err := os.ReadFile(key) if err != nil { return } @@ -86,7 +86,7 @@ func CreateSignerPublicKeyPrompt(key, password string) (signer ssh.Signer, err e key = getAbsPath(key) // Read PrivateKey file - keyData, err := ioutil.ReadFile(key) + keyData, err := os.ReadFile(key) if err != nil { return } @@ -138,7 +138,7 @@ func CreateSignerCertificate(cert string, keySigner ssh.Signer) (certSigner ssh. cert = getAbsPath(cert) // Read Cert file - certData, err := ioutil.ReadFile(cert) + certData, err := os.ReadFile(cert) if err != nil { return } diff --git a/vendor/github.com/blacknon/go-sshlib/connect.go b/vendor/github.com/blacknon/go-sshlib/connect.go index 2752acec..2c4631d2 100644 --- a/vendor/github.com/blacknon/go-sshlib/connect.go +++ b/vendor/github.com/blacknon/go-sshlib/connect.go @@ -95,7 +95,7 @@ type Connect struct { // Set it before CraeteClient. ForwardX11Trusted bool - // + // Dynamic forward related logger DynamicForwardLogger *log.Logger // shell terminal log flag diff --git a/vendor/github.com/blacknon/go-sshlib/forward.go b/vendor/github.com/blacknon/go-sshlib/forward.go index ff7b1903..3f3ad1b4 100644 --- a/vendor/github.com/blacknon/go-sshlib/forward.go +++ b/vendor/github.com/blacknon/go-sshlib/forward.go @@ -19,7 +19,6 @@ import ( "github.com/armon/go-socks5" xauth "github.com/blacknon/go-x11auth" - "github.com/elazarl/goproxy" "golang.org/x/crypto/ssh" ) @@ -158,8 +157,8 @@ func getX11DisplayNumber(display string) int { // // example) "127.0.0.1:22", "abc.com:9977" func (c *Connect) TCPLocalForward(localAddr, remoteAddr string) (err error) { - // create listner - listner, err := net.Listen("tcp", localAddr) + // create listener + listener, err := net.Listen("tcp", localAddr) if err != nil { return } @@ -168,7 +167,7 @@ func (c *Connect) TCPLocalForward(localAddr, remoteAddr string) (err error) { go func() { for { // local (type net.Conn) - local, err := listner.Accept() + local, err := listener.Accept() if err != nil { return } @@ -189,8 +188,8 @@ func (c *Connect) TCPLocalForward(localAddr, remoteAddr string) (err error) { // // example) "127.0.0.1:22", "abc.com:9977" func (c *Connect) TCPRemoteForward(localAddr, remoteAddr string) (err error) { - // create listner - listner, err := c.Client.Listen("tcp", remoteAddr) + // create listener + listener, err := c.Client.Listen("tcp", remoteAddr) if err != nil { return } @@ -205,7 +204,7 @@ func (c *Connect) TCPRemoteForward(localAddr, remoteAddr string) (err error) { } // remote (type net.Conn) - remote, err := listner.Accept() + remote, err := listener.Accept() if err != nil { return } @@ -291,8 +290,8 @@ func (c *Connect) TCPReverseDynamicForward(address, port string) (err error) { Logger: c.getDynamicForwardLogger(), } - // create listner - listner, err := c.Client.Listen("tcp", net.JoinHostPort(address, port)) + // create listener + listener, err := c.Client.Listen("tcp", net.JoinHostPort(address, port)) if err != nil { return } @@ -304,25 +303,66 @@ func (c *Connect) TCPReverseDynamicForward(address, port string) (err error) { } // Listen - err = s.Serve(listner) + err = s.Serve(listener) return } // HTTPDynamicForward forwarding http data. // Like Dynamic forward (`ssh -D `). but use http proxy. func (c *Connect) HTTPDynamicForward(address, port string) (err error) { - // create http proxy. use goproxy - httpProxy := goproxy.NewProxyHttpServer() + // create dial + dial := c.Client.Dial - // set dial - httpProxy.ConnectDial = func(n, addr string) (net.Conn, error) { - return c.Client.Dial(n, addr) + // create listener + listener, err := net.Listen("tcp", net.JoinHostPort(address, port)) + if err != nil { + return + } + defer listener.Close() + + // create proxy server. + server := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodConnect { + handleHTTPSProxy(dial, w, r) + } else { + handleHTTPProxy(dial, w, r) + } + }), + ErrorLog: c.getDynamicForwardLogger(), } - // set logger - httpProxy.Logger = c.getDynamicForwardLogger() + // listen + err = server.Serve(listener) + return +} + +// HTTPReverseDynamicForward reverse forwarding http data. +// Like Reverse Dynamic forward (`ssh -R `). but use http proxy. +func (c *Connect) HTTPReverseDynamicForward(address, port string) (err error) { + // create dial + dial := net.Dial + + // create listener + listener, err := c.Client.Listen("tcp", net.JoinHostPort(address, port)) + if err != nil { + return + } + defer listener.Close() + + // create proxy server. + server := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodConnect { + handleHTTPSProxy(dial, w, r) + } else { + handleHTTPProxy(dial, w, r) + } + }), + ErrorLog: c.getDynamicForwardLogger(), + } // listen - err = http.ListenAndServe(net.JoinHostPort(address, port), httpProxy) + err = server.Serve(listener) return } diff --git a/vendor/github.com/blacknon/go-sshlib/http_proxy.go b/vendor/github.com/blacknon/go-sshlib/http_proxy.go new file mode 100644 index 00000000..82c49611 --- /dev/null +++ b/vendor/github.com/blacknon/go-sshlib/http_proxy.go @@ -0,0 +1,79 @@ +// Copyright (c) 2024 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +package sshlib + +import ( + "io" + "net" + "net/http" + "time" +) + +// httpTransfer copies data between src and dst +func httpTransfer(dst io.WriteCloser, src io.ReadCloser) { + defer dst.Close() + defer src.Close() + io.Copy(dst, src) +} + +// handleHTTPSProxy handles CONNECT method for HTTPS requests +func handleHTTPSProxy(dial func(network, addr string) (net.Conn, error), w http.ResponseWriter, r *http.Request) { + destConn, err := dial("tcp", r.Host) + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + + // Write 200 OK response to the client + w.WriteHeader(http.StatusOK) + + // Get underlying connection from ResponseWriter + clientConn, buf, err := w.(http.Hijacker).Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + destConn.Close() + return + } + + // Make sure to set read/write deadlines for both connections + clientConn.SetDeadline(time.Time{}) + destConn.SetDeadline(time.Time{}) + + go httpTransfer(destConn, clientConn) + go httpTransfer(clientConn, destConn) + + // Ensure any buffered data from the client is written to the destination + if buf.Reader.Buffered() > 0 { + io.Copy(destConn, buf) + } +} + +// handleHTTPProxy handles HTTP requests +func handleHTTPProxy(dial func(network, addr string) (net.Conn, error), w http.ResponseWriter, r *http.Request) { + r.RequestURI = "" + r.URL.Scheme = "http" + if r.URL.Host == "" { + r.URL.Host = r.Host + } + + transport := &http.Transport{ + Dial: dial, + } + + resp, err := transport.RoundTrip(r) + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + defer resp.Body.Close() + + for key, value := range resp.Header { + for _, v := range value { + w.Header().Add(key, v) + } + } + w.WriteHeader(resp.StatusCode) + io.Copy(w, resp.Body) +} diff --git a/vendor/github.com/elazarl/goproxy/.gitignore b/vendor/github.com/elazarl/goproxy/.gitignore deleted file mode 100644 index 1005f6f1..00000000 --- a/vendor/github.com/elazarl/goproxy/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bin -*.swp diff --git a/vendor/github.com/elazarl/goproxy/LICENSE b/vendor/github.com/elazarl/goproxy/LICENSE deleted file mode 100644 index 2067e567..00000000 --- a/vendor/github.com/elazarl/goproxy/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 Elazar Leibovich. 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 Elazar Leibovich. 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/elazarl/goproxy/README.md b/vendor/github.com/elazarl/goproxy/README.md deleted file mode 100644 index 495afc2d..00000000 --- a/vendor/github.com/elazarl/goproxy/README.md +++ /dev/null @@ -1,169 +0,0 @@ -# Introduction - -[![GoDoc](https://godoc.org/github.com/elazarl/goproxy?status.svg)](https://godoc.org/github.com/elazarl/goproxy) -[![Join the chat at https://gitter.im/elazarl/goproxy](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/elazarl/goproxy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Status](https://github.com/elazarl/goproxy/workflows/Go/badge.svg) - -Package goproxy provides a customizable HTTP proxy library for Go (golang), - -It supports regular HTTP proxy, HTTPS through CONNECT, and "hijacking" HTTPS -connection using "Man in the Middle" style attack. - -The intent of the proxy is to be usable with reasonable amount of traffic, -yet customizable and programmable. - -The proxy itself is simply a `net/http` handler. - -In order to use goproxy, one should set their browser to use goproxy as an HTTP -proxy. Here is how you do that [in Chrome](https://support.google.com/chrome/answer/96815?hl=en) -and [in Firefox](http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox). - -For example, the URL you should use as proxy when running `./bin/basic` is -`localhost:8080`, as this is the default binding for the basic proxy. - -## Mailing List - -New features will be discussed on the [mailing list](https://groups.google.com/forum/#!forum/goproxy-dev) -before their development. - -## Latest Stable Release - -Get the latest goproxy from `gopkg.in/elazarl/goproxy.v1`. - -# Why not Fiddler2? - -Fiddler is an excellent software with similar intent. However, Fiddler is not -as customizable as goproxy intends to be. The main difference is, Fiddler is not -intended to be used as a real proxy. - -A possible use case that suits goproxy but -not Fiddler, is gathering statistics on page load times for a certain website over a week. -With goproxy you could ask all your users to set their proxy to a dedicated machine running a -goproxy server. Fiddler is a GUI app not designed to be run like a server for multiple users. - -# A taste of goproxy - -To get a taste of `goproxy`, a basic HTTP/HTTPS transparent proxy - -```go -package main - -import ( - "github.com/elazarl/goproxy" - "log" - "net/http" -) - -func main() { - proxy := goproxy.NewProxyHttpServer() - proxy.Verbose = true - log.Fatal(http.ListenAndServe(":8080", proxy)) -} -``` - -This line will add `X-GoProxy: yxorPoG-X` header to all requests sent through the proxy - -```go -proxy.OnRequest().DoFunc( - func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { - r.Header.Set("X-GoProxy","yxorPoG-X") - return r,nil - }) -``` - -`DoFunc` will process all incoming requests to the proxy. It will add a header to the request -and return it. The proxy will send the modified request. - -Note that we returned nil value as the response. Had we returned a response, goproxy would -have discarded the request and sent the new response to the client. - -In order to refuse connections to reddit at work time - -```go -proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc( - func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { - if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 { - return r,goproxy.NewResponse(r, - goproxy.ContentTypeText,http.StatusForbidden, - "Don't waste your time!") - } - return r,nil -}) -``` - -`DstHostIs` returns a `ReqCondition`, that is a function receiving a `Request` and returning a boolean. -We will only process requests that match the condition. `DstHostIs("www.reddit.com")` will return -a `ReqCondition` accepting only requests directed to "www.reddit.com". - -`DoFunc` will receive a function that will preprocess the request. We can change the request, or -return a response. If the time is between 8:00am and 17:00pm, we will reject the request, and -return a pre-canned text response saying "do not waste your time". - -See additional examples in the examples directory. - - -# Type of handlers for manipulating connect/req/resp behavior - -There are 3 kinds of useful handlers to manipulate the behavior, as follows: - -```go -// handler called after receiving HTTP CONNECT from the client, and before proxy establish connection -// with destination host -httpsHandlers []HttpsHandler - -// handler called before proxy send HTTP request to destination host -reqHandlers []ReqHandler - -// handler called after proxy receives HTTP Response from destination host, and before proxy forward -// the Response to the client. -respHandlers []RespHandler -``` - -Depending on what you want to manipulate, the ways to add handlers to each handler list are: - -```go -// Add handlers to httpsHandlers -proxy.OnRequest(Some ReqConditions).HandleConnect(YourHandlerFunc()) - -// Add handlers to reqHandlers -proxy.OnRequest(Some ReqConditions).Do(YourReqHandlerFunc()) - -// Add handlers to respHandlers -proxy.OnResponse(Some RespConditions).Do(YourRespHandlerFunc()) -``` - -For example: - -```go -// This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase -proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("reddit.*:443$"))).HandleConnect(goproxy.AlwaysReject) - -// This will NOT reject the HTTPS request with URL ending with gif, due to the fact that proxy -// only got the URL.Hostname and URL.Port during the HTTP CONNECT phase if the scheme is HTTPS, which is -// quiet common these days. -proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.AlwaysReject) - -// The correct way to manipulate the HTTP request using URL.Path as condition is: -proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc()) -``` - -# What's New - -1. Ability to `Hijack` CONNECT requests. See -[the eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-eavesdropper/main.go#L27) -2. Transparent proxy support for http/https including MITM certificate generation for TLS. See the [transparent example.](https://github.com/elazarl/goproxy/tree/master/examples/goproxy-transparent) - -# License - -I put the software temporarily under the Go-compatible BSD license. -If this prevents someone from using the software, do let me know and I'll consider changing it. - -At any rate, user feedback is very important for me, so I'll be delighted to know if you're using this package. - -# Beta Software - -I've received positive feedback from a few people who use goproxy in production settings. -I believe it is good enough for usage. - -I'll try to keep reasonable backwards compatibility. In case of a major API change, -I'll change the import path. diff --git a/vendor/github.com/elazarl/goproxy/actions.go b/vendor/github.com/elazarl/goproxy/actions.go deleted file mode 100644 index e1a3e7ff..00000000 --- a/vendor/github.com/elazarl/goproxy/actions.go +++ /dev/null @@ -1,57 +0,0 @@ -package goproxy - -import "net/http" - -// ReqHandler will "tamper" with the request coming to the proxy server -// If Handle returns req,nil the proxy will send the returned request -// to the destination server. If it returns nil,resp the proxy will -// skip sending any requests, and will simply return the response `resp` -// to the client. -type ReqHandler interface { - Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) -} - -// A wrapper that would convert a function to a ReqHandler interface type -type FuncReqHandler func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) - -// FuncReqHandler.Handle(req,ctx) <=> FuncReqHandler(req,ctx) -func (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { - return f(req, ctx) -} - -// after the proxy have sent the request to the destination server, it will -// "filter" the response through the RespHandlers it has. -// The proxy server will send to the client the response returned by the RespHandler. -// In case of error, resp will be nil, and ctx.RoundTrip.Error will contain the error -type RespHandler interface { - Handle(resp *http.Response, ctx *ProxyCtx) *http.Response -} - -// A wrapper that would convert a function to a RespHandler interface type -type FuncRespHandler func(resp *http.Response, ctx *ProxyCtx) *http.Response - -// FuncRespHandler.Handle(req,ctx) <=> FuncRespHandler(req,ctx) -func (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *http.Response { - return f(resp, ctx) -} - -// When a client send a CONNECT request to a host, the request is filtered through -// all the HttpsHandlers the proxy has, and if one returns true, the connection is -// sniffed using Man in the Middle attack. -// That is, the proxy will create a TLS connection with the client, another TLS -// connection with the destination the client wished to connect to, and would -// send back and forth all messages from the server to the client and vice versa. -// The request and responses sent in this Man In the Middle channel are filtered -// through the usual flow (request and response filtered through the ReqHandlers -// and RespHandlers) -type HttpsHandler interface { - HandleConnect(req string, ctx *ProxyCtx) (*ConnectAction, string) -} - -// A wrapper that would convert a function to a HttpsHandler interface type -type FuncHttpsHandler func(host string, ctx *ProxyCtx) (*ConnectAction, string) - -// FuncHttpsHandler should implement the RespHandler interface -func (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*ConnectAction, string) { - return f(host, ctx) -} diff --git a/vendor/github.com/elazarl/goproxy/all.bash b/vendor/github.com/elazarl/goproxy/all.bash deleted file mode 100644 index 6503e73d..00000000 --- a/vendor/github.com/elazarl/goproxy/all.bash +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -go test || exit -for action in $@; do go $action; done - -mkdir -p bin -find regretable examples/* ext/* -maxdepth 0 -type d | while read d; do - (cd $d - go build -o ../../bin/$(basename $d) - find *_test.go -maxdepth 0 2>/dev/null|while read f;do - for action in $@; do go $action; done - go test - break - done) -done diff --git a/vendor/github.com/elazarl/goproxy/ca.pem b/vendor/github.com/elazarl/goproxy/ca.pem deleted file mode 100644 index 62653dae..00000000 --- a/vendor/github.com/elazarl/goproxy/ca.pem +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD -VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM -B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0 -aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0 -MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE -CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV -BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI -hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9 -3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP -sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9 -V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh -hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr -lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq -j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo -WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD -fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj -YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh -WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj -UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4 -uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB -CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F -AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0 -C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3 -Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin -o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye -i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr -bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY -VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft -8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86 -NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV -BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA== ------END CERTIFICATE----- diff --git a/vendor/github.com/elazarl/goproxy/certs.go b/vendor/github.com/elazarl/goproxy/certs.go deleted file mode 100644 index 4731971e..00000000 --- a/vendor/github.com/elazarl/goproxy/certs.go +++ /dev/null @@ -1,111 +0,0 @@ -package goproxy - -import ( - "crypto/tls" - "crypto/x509" -) - -func init() { - if goproxyCaErr != nil { - panic("Error parsing builtin CA " + goproxyCaErr.Error()) - } - var err error - if GoproxyCa.Leaf, err = x509.ParseCertificate(GoproxyCa.Certificate[0]); err != nil { - panic("Error parsing builtin CA " + err.Error()) - } -} - -var tlsClientSkipVerify = &tls.Config{InsecureSkipVerify: true} - -var defaultTLSConfig = &tls.Config{ - InsecureSkipVerify: true, -} - -var CA_CERT = []byte(`-----BEGIN CERTIFICATE----- -MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD -VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM -B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0 -aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0 -MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE -CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV -BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI -hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9 -3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP -sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9 -V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh -hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr -lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq -j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo -WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD -fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj -YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh -WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj -UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4 -uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB -CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F -AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0 -C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3 -Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin -o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye -i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr -bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY -VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft -8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86 -NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV -BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA== ------END CERTIFICATE-----`) - -var CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF -0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw -HM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf -m31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+ -qiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ -0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I -yeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq -AmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU -BIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK -0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic -geFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA -AQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR -kPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3 -lVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt -zCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7 -+68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ -3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf -pjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U -C440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4 -Ood3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3 -4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm -V6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9 -jZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag -/1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6 -eDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw -+LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ -ZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt -FQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC -06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7 -OSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9 -7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf -KR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt -sjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB -N/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa -QbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv -5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W -t3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF -540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru -sIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi -L0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um -YJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi -9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe -yKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ -QBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c -ue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH -759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh -pmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1 -cj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88 -4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w= ------END RSA PRIVATE KEY-----`) - -var GoproxyCa, goproxyCaErr = tls.X509KeyPair(CA_CERT, CA_KEY) diff --git a/vendor/github.com/elazarl/goproxy/chunked.go b/vendor/github.com/elazarl/goproxy/chunked.go deleted file mode 100644 index 83654f65..00000000 --- a/vendor/github.com/elazarl/goproxy/chunked.go +++ /dev/null @@ -1,59 +0,0 @@ -// Taken from $GOROOT/src/pkg/net/http/chunked -// needed to write https responses to client. -package goproxy - -import ( - "io" - "strconv" -) - -// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP -// "chunked" format before writing them to w. Closing the returned chunkedWriter -// sends the final 0-length chunk that marks the end of the stream. -// -// newChunkedWriter is not needed by normal applications. The http -// package adds chunking automatically if handlers don't set a -// Content-Length header. Using newChunkedWriter inside a handler -// would result in double chunking or chunking with a Content-Length -// length, both of which are wrong. -func newChunkedWriter(w io.Writer) io.WriteCloser { - return &chunkedWriter{w} -} - -// Writing to chunkedWriter translates to writing in HTTP chunked Transfer -// Encoding wire format to the underlying Wire chunkedWriter. -type chunkedWriter struct { - Wire io.Writer -} - -// Write the contents of data as one chunk to Wire. -// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has -// a bug since it does not check for success of io.WriteString -func (cw *chunkedWriter) Write(data []byte) (n int, err error) { - - // Don't send 0-length data. It looks like EOF for chunked encoding. - if len(data) == 0 { - return 0, nil - } - - head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" - - if _, err = io.WriteString(cw.Wire, head); err != nil { - return 0, err - } - if n, err = cw.Wire.Write(data); err != nil { - return - } - if n != len(data) { - err = io.ErrShortWrite - return - } - _, err = io.WriteString(cw.Wire, "\r\n") - - return -} - -func (cw *chunkedWriter) Close() error { - _, err := io.WriteString(cw.Wire, "0\r\n") - return err -} diff --git a/vendor/github.com/elazarl/goproxy/counterecryptor.go b/vendor/github.com/elazarl/goproxy/counterecryptor.go deleted file mode 100644 index d1c39d23..00000000 --- a/vendor/github.com/elazarl/goproxy/counterecryptor.go +++ /dev/null @@ -1,73 +0,0 @@ -package goproxy - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/ecdsa" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "errors" -) - -type CounterEncryptorRand struct { - cipher cipher.Block - counter []byte - rand []byte - ix int -} - -func NewCounterEncryptorRandFromKey(key interface{}, seed []byte) (r CounterEncryptorRand, err error) { - var keyBytes []byte - switch key := key.(type) { - case *rsa.PrivateKey: - keyBytes = x509.MarshalPKCS1PrivateKey(key) - case *ecdsa.PrivateKey: - if keyBytes, err = x509.MarshalECPrivateKey(key); err != nil { - return - } - default: - err = errors.New("only RSA and ECDSA keys supported") - return - } - h := sha256.New() - if r.cipher, err = aes.NewCipher(h.Sum(keyBytes)[:aes.BlockSize]); err != nil { - return - } - r.counter = make([]byte, r.cipher.BlockSize()) - if seed != nil { - copy(r.counter, h.Sum(seed)[:r.cipher.BlockSize()]) - } - r.rand = make([]byte, r.cipher.BlockSize()) - r.ix = len(r.rand) - return -} - -func (c *CounterEncryptorRand) Seed(b []byte) { - if len(b) != len(c.counter) { - panic("SetCounter: wrong counter size") - } - copy(c.counter, b) -} - -func (c *CounterEncryptorRand) refill() { - c.cipher.Encrypt(c.rand, c.counter) - for i := 0; i < len(c.counter); i++ { - if c.counter[i]++; c.counter[i] != 0 { - break - } - } - c.ix = 0 -} - -func (c *CounterEncryptorRand) Read(b []byte) (n int, err error) { - if c.ix == len(c.rand) { - c.refill() - } - if n = len(c.rand) - c.ix; n > len(b) { - n = len(b) - } - copy(b, c.rand[c.ix:c.ix+n]) - c.ix += n - return -} diff --git a/vendor/github.com/elazarl/goproxy/ctx.go b/vendor/github.com/elazarl/goproxy/ctx.go deleted file mode 100644 index b372f7d4..00000000 --- a/vendor/github.com/elazarl/goproxy/ctx.go +++ /dev/null @@ -1,93 +0,0 @@ -package goproxy - -import ( - "crypto/tls" - "net/http" - "regexp" -) - -// ProxyCtx is the Proxy context, contains useful information about every request. It is passed to -// every user function. Also used as a logger. -type ProxyCtx struct { - // Will contain the client request from the proxy - Req *http.Request - // Will contain the remote server's response (if available. nil if the request wasn't send yet) - Resp *http.Response - RoundTripper RoundTripper - // will contain the recent error that occurred while trying to send receive or parse traffic - Error error - // A handle for the user to keep data in the context, from the call of ReqHandler to the - // call of RespHandler - UserData interface{} - // Will connect a request to a response - Session int64 - certStore CertStorage - Proxy *ProxyHttpServer -} - -type RoundTripper interface { - RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) -} - -type CertStorage interface { - Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) -} - -type RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error) - -func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) { - return f(req, ctx) -} - -func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error) { - if ctx.RoundTripper != nil { - return ctx.RoundTripper.RoundTrip(req, ctx) - } - return ctx.Proxy.Tr.RoundTrip(req) -} - -func (ctx *ProxyCtx) printf(msg string, argv ...interface{}) { - ctx.Proxy.Logger.Printf("[%03d] "+msg+"\n", append([]interface{}{ctx.Session & 0xFF}, argv...)...) -} - -// Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter -// This message will be printed only if the Verbose field of the ProxyHttpServer is set to true -// -// proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ -// nr := atomic.AddInt32(&counter,1) -// ctx.Printf("So far %d requests",nr) -// return r, nil -// }) -func (ctx *ProxyCtx) Logf(msg string, argv ...interface{}) { - if ctx.Proxy.Verbose { - ctx.printf("INFO: "+msg, argv...) - } -} - -// Warnf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter -// This message will always be printed. -// -// proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ -// f,err := os.OpenFile(cachedContent) -// if err != nil { -// ctx.Warnf("error open file %v: %v",cachedContent,err) -// return r, nil -// } -// return r, nil -// }) -func (ctx *ProxyCtx) Warnf(msg string, argv ...interface{}) { - ctx.printf("WARN: "+msg, argv...) -} - -var charsetFinder = regexp.MustCompile("charset=([^ ;]*)") - -// Will try to infer the character set of the request from the headers. -// Returns the empty string if we don't know which character set it used. -// Currently it will look for charset= in the Content-Type header of the request. -func (ctx *ProxyCtx) Charset() string { - charsets := charsetFinder.FindStringSubmatch(ctx.Resp.Header.Get("Content-Type")) - if charsets == nil { - return "" - } - return charsets[1] -} diff --git a/vendor/github.com/elazarl/goproxy/dispatcher.go b/vendor/github.com/elazarl/goproxy/dispatcher.go deleted file mode 100644 index 25c949c0..00000000 --- a/vendor/github.com/elazarl/goproxy/dispatcher.go +++ /dev/null @@ -1,341 +0,0 @@ -package goproxy - -import ( - "bytes" - "io/ioutil" - "net" - "net/http" - "regexp" - "strings" -) - -// ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request -// before sending it to the remote server -type ReqCondition interface { - RespCondition - HandleReq(req *http.Request, ctx *ProxyCtx) bool -} - -// RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response -// before sending it to the proxy client. Note that resp might be nil, in case there was an -// error sending the request. -type RespCondition interface { - HandleResp(resp *http.Response, ctx *ProxyCtx) bool -} - -// ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx) -type ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool - -// RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx) -type RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool - -func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool { - return c(req, ctx) -} - -// ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that -// to be usable as RespCondition. -func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { - return c(ctx.Req, ctx) -} - -func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { - return c(resp, ctx) -} - -// UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested -// has the given prefix, with or without the host. -// For example UrlHasPrefix("host/x") will match requests of the form 'GET host/x', and will match -// requests to url 'http://host/x' -func UrlHasPrefix(prefix string) ReqConditionFunc { - return func(req *http.Request, ctx *ProxyCtx) bool { - return strings.HasPrefix(req.URL.Path, prefix) || - strings.HasPrefix(req.URL.Host+req.URL.Path, prefix) || - strings.HasPrefix(req.URL.Scheme+req.URL.Host+req.URL.Path, prefix) - } -} - -// UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings -// with or without the host prefix. -// UrlIs("google.com/","foo") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to -// any host, and requests of the form 'GET foo'. -func UrlIs(urls ...string) ReqConditionFunc { - urlSet := make(map[string]bool) - for _, u := range urls { - urlSet[u] = true - } - return func(req *http.Request, ctx *ProxyCtx) bool { - _, pathOk := urlSet[req.URL.Path] - _, hostAndOk := urlSet[req.URL.Host+req.URL.Path] - return pathOk || hostAndOk - } -} - -// ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches -// any of the given regular expressions. -func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc { - return func(req *http.Request, ctx *ProxyCtx) bool { - for _, re := range regexps { - if re.MatchString(req.Host) { - return true - } - } - return false - } -} - -// ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal -// to one of the given strings -func ReqHostIs(hosts ...string) ReqConditionFunc { - hostSet := make(map[string]bool) - for _, h := range hosts { - hostSet[h] = true - } - return func(req *http.Request, ctx *ProxyCtx) bool { - _, ok := hostSet[req.URL.Host] - return ok - } -} - -var localHostIpv4 = regexp.MustCompile(`127\.0\.0\.\d+`) - -// IsLocalHost checks whether the destination host is explicitly local host -// (buggy, there can be IPv6 addresses it doesn't catch) -var IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool { - return req.URL.Host == "::1" || - req.URL.Host == "0:0:0:0:0:0:0:1" || - localHostIpv4.MatchString(req.URL.Host) || - req.URL.Host == "localhost" -} - -// UrlMatches returns a ReqCondition testing whether the destination URL -// of the request matches the given regexp, with or without prefix -func UrlMatches(re *regexp.Regexp) ReqConditionFunc { - return func(req *http.Request, ctx *ProxyCtx) bool { - return re.MatchString(req.URL.Path) || - re.MatchString(req.URL.Host+req.URL.Path) - } -} - -// DstHostIs returns a ReqCondition testing wether the host in the request url is the given string -func DstHostIs(host string) ReqConditionFunc { - return func(req *http.Request, ctx *ProxyCtx) bool { - return req.URL.Host == host - } -} - -// SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings -func SrcIpIs(ips ...string) ReqCondition { - return ReqConditionFunc(func(req *http.Request, ctx *ProxyCtx) bool { - for _, ip := range ips { - if strings.HasPrefix(req.RemoteAddr, ip+":") { - return true - } - } - return false - }) -} - -// Not returns a ReqCondition negating the given ReqCondition -func Not(r ReqCondition) ReqConditionFunc { - return func(req *http.Request, ctx *ProxyCtx) bool { - return !r.HandleReq(req, ctx) - } -} - -// ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal -// to one of the given strings. -func ContentTypeIs(typ string, types ...string) RespCondition { - types = append(types, typ) - return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool { - if resp == nil { - return false - } - contentType := resp.Header.Get("Content-Type") - for _, typ := range types { - if contentType == typ || strings.HasPrefix(contentType, typ+";") { - return true - } - } - return false - }) -} - -// StatusCodeIs returns a RespCondition, testing whether or not the HTTP status -// code is one of the given ints -func StatusCodeIs(codes ...int) RespCondition { - codeSet := make(map[int]bool) - for _, c := range codes { - codeSet[c] = true - } - return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool { - if resp == nil { - return false - } - _, codeMatch := codeSet[resp.StatusCode] - return codeMatch - }) -} - -// ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions. -// You will use the ReqProxyConds struct to register a ReqHandler, that would filter -// the request, only if all the given ReqCondition matched. -// Typical usage: -// proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...) -func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds { - return &ReqProxyConds{proxy, conds} -} - -// ReqProxyConds aggregate ReqConditions for a ProxyHttpServer. Upon calling Do, it will register a ReqHandler that would -// handle the request if all conditions on the HTTP request are met. -type ReqProxyConds struct { - proxy *ProxyHttpServer - reqConds []ReqCondition -} - -// DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)) -func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) { - pcond.Do(FuncReqHandler(f)) -} - -// ReqProxyConds.Do will register the ReqHandler on the proxy, -// the ReqHandler will handle the HTTP request if all the conditions -// aggregated in the ReqProxyConds are met. Typical usage: -// proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy -// proxy.OnRequest(cond1,cond2).Do(handler) -// // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true -// // if they are, will call handler.Handle(req,ctx) -func (pcond *ReqProxyConds) Do(h ReqHandler) { - pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers, - FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { - for _, cond := range pcond.reqConds { - if !cond.HandleReq(r, ctx) { - return r, nil - } - } - return h.Handle(r, ctx) - })) -} - -// HandleConnect is used when proxy receives an HTTP CONNECT request, -// it'll then use the HttpsHandler to determine what should it -// do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction -// struct returned will determine what to do with this request. ConnectAccept will simply accept the request -// forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the -// client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man -// in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped -// connection. -// The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy -// will use the default tls configuration. -// proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests -func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) { - pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, - FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { - for _, cond := range pcond.reqConds { - if !cond.HandleReq(ctx.Req, ctx) { - return nil, "" - } - } - return h.HandleConnect(host, ctx) - })) -} - -// HandleConnectFunc is equivalent to HandleConnect, -// for example, accepting CONNECT request if they contain a password in header -// io.WriteString(h,password) -// passHash := h.Sum(nil) -// proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { -// c := sha1.New() -// io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth")) -// if c.Sum(nil) == passHash { -// return OkConnect, host -// } -// return RejectConnect, host -// }) -func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) { - pcond.HandleConnect(FuncHttpsHandler(f)) -} - -func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) { - pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, - FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { - for _, cond := range pcond.reqConds { - if !cond.HandleReq(ctx.Req, ctx) { - return nil, "" - } - } - return &ConnectAction{Action: ConnectHijack, Hijack: f}, host - })) -} - -// ProxyConds is used to aggregate RespConditions for a ProxyHttpServer. -// Upon calling ProxyConds.Do, it will register a RespHandler that would -// handle the HTTP response from remote server if all conditions on the HTTP response are met. -type ProxyConds struct { - proxy *ProxyHttpServer - reqConds []ReqCondition - respCond []RespCondition -} - -// ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)) -func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) { - pcond.Do(FuncRespHandler(f)) -} - -// ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every -// request that matches the conditions aggregated in pcond. -func (pcond *ProxyConds) Do(h RespHandler) { - pcond.proxy.respHandlers = append(pcond.proxy.respHandlers, - FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { - for _, cond := range pcond.reqConds { - if !cond.HandleReq(ctx.Req, ctx) { - return resp - } - } - for _, cond := range pcond.respCond { - if !cond.HandleResp(resp, ctx) { - return resp - } - } - return h.Handle(resp, ctx) - })) -} - -// OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is -// proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used -// // if cond1.HandleResp(resp) && cond2.HandleResp(resp) -func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds { - return &ProxyConds{proxy, make([]ReqCondition, 0), conds} -} - -// AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to -// eavesdrop all https connections to www.google.com, we can use -// proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm) -var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { - return MitmConnect, host -} - -// AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow -// connections to hosts on any other port than 443 -// proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))). -// HandleConnect(goproxy.AlwaysReject) -var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { - return RejectConnect, host -} - -// HandleBytes will return a RespHandler that read the entire body of the request -// to a byte array in memory, would run the user supplied f function on the byte arra, -// and will replace the body of the original response with the resulting byte array. -func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler { - return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - ctx.Warnf("Cannot read response %s", err) - return resp - } - resp.Body.Close() - - resp.Body = ioutil.NopCloser(bytes.NewBuffer(f(b, ctx))) - return resp - }) -} diff --git a/vendor/github.com/elazarl/goproxy/doc.go b/vendor/github.com/elazarl/goproxy/doc.go deleted file mode 100644 index 6f44317b..00000000 --- a/vendor/github.com/elazarl/goproxy/doc.go +++ /dev/null @@ -1,100 +0,0 @@ -/* -Package goproxy provides a customizable HTTP proxy, -supporting hijacking HTTPS connection. - -The intent of the proxy, is to be usable with reasonable amount of traffic -yet, customizable and programmable. - -The proxy itself is simply an `net/http` handler. - -Typical usage is - - proxy := goproxy.NewProxyHttpServer() - proxy.OnRequest(..conditions..).Do(..requesthandler..) - proxy.OnRequest(..conditions..).DoFunc(..requesthandlerFunction..) - proxy.OnResponse(..conditions..).Do(..responesHandler..) - proxy.OnResponse(..conditions..).DoFunc(..responesHandlerFunction..) - http.ListenAndServe(":8080", proxy) - -Adding a header to each request - - proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ - r.Header.Set("X-GoProxy","1") - return r, nil - }) - -Note that the function is called before the proxy sends the request to the server - -For printing the content type of all incoming responses - - proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{ - println(ctx.Req.Host,"->",r.Header.Get("Content-Type")) - return r - }) - -note that we used the ProxyCtx context variable here. It contains the request -and the response (Req and Resp, Resp is nil if unavailable) of this specific client -interaction with the proxy. - -To print the content type of all responses from a certain url, we'll add a -ReqCondition to the OnResponse function: - - proxy.OnResponse(goproxy.UrlIs("golang.org/pkg")).DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{ - println(ctx.Req.Host,"->",r.Header.Get("Content-Type")) - return r - }) - -We can write the condition ourselves, conditions can be set on request and on response - - var random = ReqConditionFunc(func(r *http.Request) bool { - return rand.Intn(1) == 0 - }) - var hasGoProxyHeader = RespConditionFunc(func(resp *http.Response,req *http.Request)bool { - return resp.Header.Get("X-GoProxy") != "" - }) - -Caution! If you give a RespCondition to the OnRequest function, you'll get a run time panic! It doesn't -make sense to read the response, if you still haven't got it! - -Finally, we have convenience function to throw a quick response - - proxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response { - r.Body.Close() - return goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, "Can't see response with X-GoProxy header!") - }) - -we close the body of the original response, and return a new 403 response with a short message. - -Example use cases: - -1. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-avgsize - -To measure the average size of an Html served in your site. One can ask -all the QA team to access the website by a proxy, and the proxy will -measure the average size of all text/html responses from your host. - -2. [not yet implemented] - -All requests to your web servers should be directed through the proxy, -when the proxy will detect html pieces sent as a response to AJAX -request, it'll send a warning email. - -3. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-httpdump/ - -Generate a real traffic to your website by real users using through -proxy. Record the traffic, and try it again for more real load testing. - -4. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-no-reddit-at-worktime - -Will allow browsing to reddit.com between 8:00am and 17:00pm - -5. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-jquery-version - -Will warn if multiple versions of jquery are used in the same domain. - -6. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-upside-down-ternet/ - -Modifies image files in an HTTP response via goproxy's image extension found in ext/. - -*/ -package goproxy diff --git a/vendor/github.com/elazarl/goproxy/https.go b/vendor/github.com/elazarl/goproxy/https.go deleted file mode 100644 index 608863fa..00000000 --- a/vendor/github.com/elazarl/goproxy/https.go +++ /dev/null @@ -1,493 +0,0 @@ -package goproxy - -import ( - "bufio" - "crypto/tls" - "errors" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/url" - "os" - "regexp" - "strconv" - "strings" - "sync" - "sync/atomic" -) - -type ConnectActionLiteral int - -const ( - ConnectAccept = iota - ConnectReject - ConnectMitm - ConnectHijack - ConnectHTTPMitm - ConnectProxyAuthHijack -) - -var ( - OkConnect = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)} - MitmConnect = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)} - HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)} - RejectConnect = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)} - httpsRegexp = regexp.MustCompile(`^https:\/\/`) -) - -// ConnectAction enables the caller to override the standard connect flow. -// When Action is ConnectHijack, it is up to the implementer to send the -// HTTP 200, or any other valid http response back to the client from within the -// Hijack func -type ConnectAction struct { - Action ConnectActionLiteral - Hijack func(req *http.Request, client net.Conn, ctx *ProxyCtx) - TLSConfig func(host string, ctx *ProxyCtx) (*tls.Config, error) -} - -func stripPort(s string) string { - var ix int - if strings.Contains(s, "[") && strings.Contains(s, "]") { - //ipv6 : for example : [2606:4700:4700::1111]:443 - - //strip '[' and ']' - s = strings.ReplaceAll(s, "[", "") - s = strings.ReplaceAll(s, "]", "") - - ix = strings.LastIndexAny(s, ":") - if ix == -1 { - return s - } - } else { - //ipv4 - ix = strings.IndexRune(s, ':') - if ix == -1 { - return s - } - - } - return s[:ix] -} - -func (proxy *ProxyHttpServer) dial(network, addr string) (c net.Conn, err error) { - if proxy.Tr.Dial != nil { - return proxy.Tr.Dial(network, addr) - } - return net.Dial(network, addr) -} - -func (proxy *ProxyHttpServer) connectDial(ctx *ProxyCtx, network, addr string) (c net.Conn, err error) { - if proxy.ConnectDialWithReq == nil && proxy.ConnectDial == nil { - return proxy.dial(network, addr) - } - - if proxy.ConnectDialWithReq != nil { - return proxy.ConnectDialWithReq(ctx.Req, network, addr) - } - - return proxy.ConnectDial(network, addr) -} - -type halfClosable interface { - net.Conn - CloseWrite() error - CloseRead() error -} - -var _ halfClosable = (*net.TCPConn)(nil) - -func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) { - ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, certStore: proxy.CertStore} - - hij, ok := w.(http.Hijacker) - if !ok { - panic("httpserver does not support hijacking") - } - - proxyClient, _, e := hij.Hijack() - if e != nil { - panic("Cannot hijack connection " + e.Error()) - } - - ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers)) - todo, host := OkConnect, r.URL.Host - for i, h := range proxy.httpsHandlers { - newtodo, newhost := h.HandleConnect(host, ctx) - - // If found a result, break the loop immediately - if newtodo != nil { - todo, host = newtodo, newhost - ctx.Logf("on %dth handler: %v %s", i, todo, host) - break - } - } - switch todo.Action { - case ConnectAccept: - if !hasPort.MatchString(host) { - host += ":80" - } - targetSiteCon, err := proxy.connectDial(ctx, "tcp", host) - if err != nil { - ctx.Warnf("Error dialing to %s: %s", host, err.Error()) - httpError(proxyClient, ctx, err) - return - } - ctx.Logf("Accepting CONNECT to %s", host) - proxyClient.Write([]byte("HTTP/1.0 200 Connection established\r\n\r\n")) - - targetTCP, targetOK := targetSiteCon.(halfClosable) - proxyClientTCP, clientOK := proxyClient.(halfClosable) - if targetOK && clientOK { - go copyAndClose(ctx, targetTCP, proxyClientTCP) - go copyAndClose(ctx, proxyClientTCP, targetTCP) - } else { - go func() { - var wg sync.WaitGroup - wg.Add(2) - go copyOrWarn(ctx, targetSiteCon, proxyClient, &wg) - go copyOrWarn(ctx, proxyClient, targetSiteCon, &wg) - wg.Wait() - proxyClient.Close() - targetSiteCon.Close() - - }() - } - - case ConnectHijack: - todo.Hijack(r, proxyClient, ctx) - case ConnectHTTPMitm: - proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) - ctx.Logf("Assuming CONNECT is plain HTTP tunneling, mitm proxying it") - targetSiteCon, err := proxy.connectDial(ctx, "tcp", host) - if err != nil { - ctx.Warnf("Error dialing to %s: %s", host, err.Error()) - return - } - for { - client := bufio.NewReader(proxyClient) - remote := bufio.NewReader(targetSiteCon) - req, err := http.ReadRequest(client) - if err != nil && err != io.EOF { - ctx.Warnf("cannot read request of MITM HTTP client: %+#v", err) - } - if err != nil { - return - } - req, resp := proxy.filterRequest(req, ctx) - if resp == nil { - if err := req.Write(targetSiteCon); err != nil { - httpError(proxyClient, ctx, err) - return - } - resp, err = http.ReadResponse(remote, req) - if err != nil { - httpError(proxyClient, ctx, err) - return - } - defer resp.Body.Close() - } - resp = proxy.filterResponse(resp, ctx) - if err := resp.Write(proxyClient); err != nil { - httpError(proxyClient, ctx, err) - return - } - } - case ConnectMitm: - proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) - ctx.Logf("Assuming CONNECT is TLS, mitm proxying it") - // this goes in a separate goroutine, so that the net/http server won't think we're - // still handling the request even after hijacking the connection. Those HTTP CONNECT - // request can take forever, and the server will be stuck when "closed". - // TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible - tlsConfig := defaultTLSConfig - if todo.TLSConfig != nil { - var err error - tlsConfig, err = todo.TLSConfig(host, ctx) - if err != nil { - httpError(proxyClient, ctx, err) - return - } - } - go func() { - //TODO: cache connections to the remote website - rawClientTls := tls.Server(proxyClient, tlsConfig) - if err := rawClientTls.Handshake(); err != nil { - ctx.Warnf("Cannot handshake client %v %v", r.Host, err) - return - } - defer rawClientTls.Close() - clientTlsReader := bufio.NewReader(rawClientTls) - for !isEof(clientTlsReader) { - req, err := http.ReadRequest(clientTlsReader) - var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, UserData: ctx.UserData} - if err != nil && err != io.EOF { - return - } - if err != nil { - ctx.Warnf("Cannot read TLS request from mitm'd client %v %v", r.Host, err) - return - } - req.RemoteAddr = r.RemoteAddr // since we're converting the request, need to carry over the original connecting IP as well - ctx.Logf("req %v", r.Host) - - if !httpsRegexp.MatchString(req.URL.String()) { - req.URL, err = url.Parse("https://" + r.Host + req.URL.String()) - } - - // Bug fix which goproxy fails to provide request - // information URL in the context when does HTTPS MITM - ctx.Req = req - - req, resp := proxy.filterRequest(req, ctx) - if resp == nil { - if isWebSocketRequest(req) { - ctx.Logf("Request looks like websocket upgrade.") - proxy.serveWebsocketTLS(ctx, w, req, tlsConfig, rawClientTls) - return - } - if err != nil { - if req.URL != nil { - ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path) - } else { - ctx.Warnf("Illegal URL %s", "https://"+r.Host) - } - return - } - removeProxyHeaders(ctx, req) - resp, err = func() (*http.Response, error) { - // explicitly discard request body to avoid data races in certain RoundTripper implementations - // see https://github.com/golang/go/issues/61596#issuecomment-1652345131 - defer req.Body.Close() - return ctx.RoundTrip(req) - }() - if err != nil { - ctx.Warnf("Cannot read TLS response from mitm'd server %v", err) - return - } - ctx.Logf("resp %v", resp.Status) - } - resp = proxy.filterResponse(resp, ctx) - defer resp.Body.Close() - - text := resp.Status - statusCode := strconv.Itoa(resp.StatusCode) + " " - if strings.HasPrefix(text, statusCode) { - text = text[len(statusCode):] - } - // always use 1.1 to support chunked encoding - if _, err := io.WriteString(rawClientTls, "HTTP/1.1"+" "+statusCode+text+"\r\n"); err != nil { - ctx.Warnf("Cannot write TLS response HTTP status from mitm'd client: %v", err) - return - } - - if resp.Request.Method == "HEAD" { - // don't change Content-Length for HEAD request - } else { - // Since we don't know the length of resp, return chunked encoded response - // TODO: use a more reasonable scheme - resp.Header.Del("Content-Length") - resp.Header.Set("Transfer-Encoding", "chunked") - } - // Force connection close otherwise chrome will keep CONNECT tunnel open forever - resp.Header.Set("Connection", "close") - if err := resp.Header.Write(rawClientTls); err != nil { - ctx.Warnf("Cannot write TLS response header from mitm'd client: %v", err) - return - } - if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { - ctx.Warnf("Cannot write TLS response header end from mitm'd client: %v", err) - return - } - - if resp.Request.Method == "HEAD" { - // Don't write out a response body for HEAD request - } else { - chunked := newChunkedWriter(rawClientTls) - if _, err := io.Copy(chunked, resp.Body); err != nil { - ctx.Warnf("Cannot write TLS response body from mitm'd client: %v", err) - return - } - if err := chunked.Close(); err != nil { - ctx.Warnf("Cannot write TLS chunked EOF from mitm'd client: %v", err) - return - } - if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { - ctx.Warnf("Cannot write TLS response chunked trailer from mitm'd client: %v", err) - return - } - } - } - ctx.Logf("Exiting on EOF") - }() - case ConnectProxyAuthHijack: - proxyClient.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n")) - todo.Hijack(r, proxyClient, ctx) - case ConnectReject: - if ctx.Resp != nil { - if err := ctx.Resp.Write(proxyClient); err != nil { - ctx.Warnf("Cannot write response that reject http CONNECT: %v", err) - } - } - proxyClient.Close() - } -} - -func httpError(w io.WriteCloser, ctx *ProxyCtx, err error) { - errStr := fmt.Sprintf("HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(err.Error()), err.Error()) - if _, err := io.WriteString(w, errStr); err != nil { - ctx.Warnf("Error responding to client: %s", err) - } - if err := w.Close(); err != nil { - ctx.Warnf("Error closing client connection: %s", err) - } -} - -func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup) { - if _, err := io.Copy(dst, src); err != nil { - ctx.Warnf("Error copying to client: %s", err) - } - wg.Done() -} - -func copyAndClose(ctx *ProxyCtx, dst, src halfClosable) { - if _, err := io.Copy(dst, src); err != nil { - ctx.Warnf("Error copying to client: %s", err) - } - - dst.CloseWrite() - src.CloseRead() -} - -func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn, error) { - https_proxy := os.Getenv("HTTPS_PROXY") - if https_proxy == "" { - https_proxy = os.Getenv("https_proxy") - } - if https_proxy == "" { - return nil - } - return proxy.NewConnectDialToProxy(https_proxy) -} - -func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) { - return proxy.NewConnectDialToProxyWithHandler(https_proxy, nil) -} - -func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(https_proxy string, connectReqHandler func(req *http.Request)) func(network, addr string) (net.Conn, error) { - u, err := url.Parse(https_proxy) - if err != nil { - return nil - } - if u.Scheme == "" || u.Scheme == "http" { - if !strings.ContainsRune(u.Host, ':') { - u.Host += ":80" - } - return func(network, addr string) (net.Conn, error) { - connectReq := &http.Request{ - Method: "CONNECT", - URL: &url.URL{Opaque: addr}, - Host: addr, - Header: make(http.Header), - } - if connectReqHandler != nil { - connectReqHandler(connectReq) - } - c, err := proxy.dial(network, u.Host) - if err != nil { - return nil, err - } - connectReq.Write(c) - // Read response. - // Okay to use and discard buffered reader here, because - // TLS server will not speak until spoken to. - br := bufio.NewReader(c) - resp, err := http.ReadResponse(br, connectReq) - if err != nil { - c.Close() - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - resp, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - c.Close() - return nil, errors.New("proxy refused connection" + string(resp)) - } - return c, nil - } - } - if u.Scheme == "https" || u.Scheme == "wss" { - if !strings.ContainsRune(u.Host, ':') { - u.Host += ":443" - } - return func(network, addr string) (net.Conn, error) { - c, err := proxy.dial(network, u.Host) - if err != nil { - return nil, err - } - c = tls.Client(c, proxy.Tr.TLSClientConfig) - connectReq := &http.Request{ - Method: "CONNECT", - URL: &url.URL{Opaque: addr}, - Host: addr, - Header: make(http.Header), - } - if connectReqHandler != nil { - connectReqHandler(connectReq) - } - connectReq.Write(c) - // Read response. - // Okay to use and discard buffered reader here, because - // TLS server will not speak until spoken to. - br := bufio.NewReader(c) - resp, err := http.ReadResponse(br, connectReq) - if err != nil { - c.Close() - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 500)) - if err != nil { - return nil, err - } - c.Close() - return nil, errors.New("proxy refused connection" + string(body)) - } - return c, nil - } - } - return nil -} - -func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) { - return func(host string, ctx *ProxyCtx) (*tls.Config, error) { - var err error - var cert *tls.Certificate - - hostname := stripPort(host) - config := defaultTLSConfig.Clone() - ctx.Logf("signing for %s", stripPort(host)) - - genCert := func() (*tls.Certificate, error) { - return signHost(*ca, []string{hostname}) - } - if ctx.certStore != nil { - cert, err = ctx.certStore.Fetch(hostname, genCert) - } else { - cert, err = genCert() - } - - if err != nil { - ctx.Warnf("Cannot sign host certificate with provided CA: %s", err) - return nil, err - } - - config.Certificates = append(config.Certificates, *cert) - return config, nil - } -} diff --git a/vendor/github.com/elazarl/goproxy/key.pem b/vendor/github.com/elazarl/goproxy/key.pem deleted file mode 100644 index 2ea1dca4..00000000 --- a/vendor/github.com/elazarl/goproxy/key.pem +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF -0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw -HM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf -m31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+ -qiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ -0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I -yeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq -AmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU -BIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK -0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic -geFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA -AQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR -kPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3 -lVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt -zCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7 -+68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ -3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf -pjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U -C440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4 -Ood3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3 -4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm -V6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9 -jZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag -/1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6 -eDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw -+LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ -ZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt -FQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC -06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7 -OSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9 -7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf -KR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt -sjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB -N/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa -QbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv -5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W -t3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF -540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru -sIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi -L0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um -YJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi -9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe -yKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ -QBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c -ue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH -759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh -pmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1 -cj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88 -4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w= ------END RSA PRIVATE KEY----- diff --git a/vendor/github.com/elazarl/goproxy/logger.go b/vendor/github.com/elazarl/goproxy/logger.go deleted file mode 100644 index 939cf69e..00000000 --- a/vendor/github.com/elazarl/goproxy/logger.go +++ /dev/null @@ -1,5 +0,0 @@ -package goproxy - -type Logger interface { - Printf(format string, v ...interface{}) -} diff --git a/vendor/github.com/elazarl/goproxy/proxy.go b/vendor/github.com/elazarl/goproxy/proxy.go deleted file mode 100644 index fa5494c6..00000000 --- a/vendor/github.com/elazarl/goproxy/proxy.go +++ /dev/null @@ -1,225 +0,0 @@ -package goproxy - -import ( - "bufio" - "io" - "log" - "net" - "net/http" - "os" - "regexp" - "sync/atomic" -) - -// The basic proxy type. Implements http.Handler. -type ProxyHttpServer struct { - // session variable must be aligned in i386 - // see http://golang.org/src/pkg/sync/atomic/doc.go#L41 - sess int64 - // KeepDestinationHeaders indicates the proxy should retain any headers present in the http.Response before proxying - KeepDestinationHeaders bool - // setting Verbose to true will log information on each request sent to the proxy - Verbose bool - Logger Logger - NonproxyHandler http.Handler - reqHandlers []ReqHandler - respHandlers []RespHandler - httpsHandlers []HttpsHandler - Tr *http.Transport - // ConnectDial will be used to create TCP connections for CONNECT requests - // if nil Tr.Dial will be used - ConnectDial func(network string, addr string) (net.Conn, error) - ConnectDialWithReq func(req *http.Request, network string, addr string) (net.Conn, error) - CertStore CertStorage - KeepHeader bool -} - -var hasPort = regexp.MustCompile(`:\d+$`) - -func copyHeaders(dst, src http.Header, keepDestHeaders bool) { - if !keepDestHeaders { - for k := range dst { - dst.Del(k) - } - } - for k, vs := range src { - for _, v := range vs { - dst.Add(k, v) - } - } -} - -func isEof(r *bufio.Reader) bool { - _, err := r.Peek(1) - if err == io.EOF { - return true - } - return false -} - -func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) { - req = r - for _, h := range proxy.reqHandlers { - req, resp = h.Handle(r, ctx) - // non-nil resp means the handler decided to skip sending the request - // and return canned response instead. - if resp != nil { - break - } - } - return -} -func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) { - resp = respOrig - for _, h := range proxy.respHandlers { - ctx.Resp = resp - resp = h.Handle(resp, ctx) - } - return -} - -func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) { - r.RequestURI = "" // this must be reset when serving a request with the client - ctx.Logf("Sending request %v %v", r.Method, r.URL.String()) - // If no Accept-Encoding header exists, Transport will add the headers it can accept - // and would wrap the response body with the relevant reader. - r.Header.Del("Accept-Encoding") - // curl can add that, see - // https://jdebp.eu./FGA/web-proxy-connection-header.html - r.Header.Del("Proxy-Connection") - r.Header.Del("Proxy-Authenticate") - r.Header.Del("Proxy-Authorization") - // Connection, Authenticate and Authorization are single hop Header: - // http://www.w3.org/Protocols/rfc2616/rfc2616.txt - // 14.10 Connection - // The Connection general-header field allows the sender to specify - // options that are desired for that particular connection and MUST NOT - // be communicated by proxies over further connections. - - // When server reads http request it sets req.Close to true if - // "Connection" header contains "close". - // https://github.com/golang/go/blob/master/src/net/http/request.go#L1080 - // Later, transfer.go adds "Connection: close" back when req.Close is true - // https://github.com/golang/go/blob/master/src/net/http/transfer.go#L275 - // That's why tests that checks "Connection: close" removal fail - if r.Header.Get("Connection") == "close" { - r.Close = false - } - r.Header.Del("Connection") -} - -type flushWriter struct { - w io.Writer -} - -func (fw flushWriter) Write(p []byte) (int, error) { - n, err := fw.w.Write(p) - if f, ok := fw.w.(http.Flusher); ok { - // only flush if the Writer implements the Flusher interface. - f.Flush() - } - - return n, err -} - -// Standard net/http function. Shouldn't be used directly, http.Serve will use it. -func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - //r.Header["X-Forwarded-For"] = w.RemoteAddr() - if r.Method == "CONNECT" { - proxy.handleHttps(w, r) - } else { - ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy} - - var err error - ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String()) - if !r.URL.IsAbs() { - proxy.NonproxyHandler.ServeHTTP(w, r) - return - } - r, resp := proxy.filterRequest(r, ctx) - - if resp == nil { - if isWebSocketRequest(r) { - ctx.Logf("Request looks like websocket upgrade.") - proxy.serveWebsocket(ctx, w, r) - } - - if !proxy.KeepHeader { - removeProxyHeaders(ctx, r) - } - resp, err = ctx.RoundTrip(r) - if err != nil { - ctx.Error = err - resp = proxy.filterResponse(nil, ctx) - - } - if resp != nil { - ctx.Logf("Received response %v", resp.Status) - } - } - - var origBody io.ReadCloser - - if resp != nil { - origBody = resp.Body - defer origBody.Close() - } - - resp = proxy.filterResponse(resp, ctx) - - if resp == nil { - var errorString string - if ctx.Error != nil { - errorString = "error read response " + r.URL.Host + " : " + ctx.Error.Error() - ctx.Logf(errorString) - http.Error(w, ctx.Error.Error(), 500) - } else { - errorString = "error read response " + r.URL.Host - ctx.Logf(errorString) - http.Error(w, errorString, 500) - } - return - } - ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode) - // http.ResponseWriter will take care of filling the correct response length - // Setting it now, might impose wrong value, contradicting the actual new - // body the user returned. - // We keep the original body to remove the header only if things changed. - // This will prevent problems with HEAD requests where there's no body, yet, - // the Content-Length header should be set. - if origBody != resp.Body { - resp.Header.Del("Content-Length") - } - copyHeaders(w.Header(), resp.Header, proxy.KeepDestinationHeaders) - w.WriteHeader(resp.StatusCode) - var copyWriter io.Writer = w - if w.Header().Get("content-type") == "text/event-stream" { - // server-side events, flush the buffered data to the client. - copyWriter = &flushWriter{w: w} - } - - nr, err := io.Copy(copyWriter, resp.Body) - if err := resp.Body.Close(); err != nil { - ctx.Warnf("Can't close response body %v", err) - } - ctx.Logf("Copied %v bytes to client error=%v", nr, err) - } -} - -// NewProxyHttpServer creates and returns a proxy server, logging to stderr by default -func NewProxyHttpServer() *ProxyHttpServer { - proxy := ProxyHttpServer{ - Logger: log.New(os.Stderr, "", log.LstdFlags), - reqHandlers: []ReqHandler{}, - respHandlers: []RespHandler{}, - httpsHandlers: []HttpsHandler{}, - NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", 500) - }), - Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify, Proxy: http.ProxyFromEnvironment}, - } - - proxy.ConnectDial = dialerFromEnv(&proxy) - - return &proxy -} diff --git a/vendor/github.com/elazarl/goproxy/responses.go b/vendor/github.com/elazarl/goproxy/responses.go deleted file mode 100644 index e1bf28fc..00000000 --- a/vendor/github.com/elazarl/goproxy/responses.go +++ /dev/null @@ -1,39 +0,0 @@ -package goproxy - -import ( - "bytes" - "io/ioutil" - "net/http" -) - -// Will generate a valid http response to the given request the response will have -// the given contentType, and http status. -// Typical usage, refuse to process requests to local addresses: -// -// proxy.OnRequest(IsLocalHost()).DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request,*http.Response) { -// return nil,NewResponse(r,goproxy.ContentTypeHtml,http.StatusUnauthorized, -// `Can't use proxy for local addresses`) -// }) -func NewResponse(r *http.Request, contentType string, status int, body string) *http.Response { - resp := &http.Response{} - resp.Request = r - resp.TransferEncoding = r.TransferEncoding - resp.Header = make(http.Header) - resp.Header.Add("Content-Type", contentType) - resp.StatusCode = status - resp.Status = http.StatusText(status) - buf := bytes.NewBufferString(body) - resp.ContentLength = int64(buf.Len()) - resp.Body = ioutil.NopCloser(buf) - return resp -} - -const ( - ContentTypeText = "text/plain" - ContentTypeHtml = "text/html" -) - -// Alias for NewResponse(r,ContentTypeText,http.StatusAccepted,text) -func TextResponse(r *http.Request, text string) *http.Response { - return NewResponse(r, ContentTypeText, http.StatusAccepted, text) -} diff --git a/vendor/github.com/elazarl/goproxy/signer.go b/vendor/github.com/elazarl/goproxy/signer.go deleted file mode 100644 index aa511ca9..00000000 --- a/vendor/github.com/elazarl/goproxy/signer.go +++ /dev/null @@ -1,108 +0,0 @@ -package goproxy - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rsa" - "crypto/sha1" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "fmt" - "math/big" - "math/rand" - "net" - "runtime" - "sort" - "time" -) - -func hashSorted(lst []string) []byte { - c := make([]string, len(lst)) - copy(c, lst) - sort.Strings(c) - h := sha1.New() - for _, s := range c { - h.Write([]byte(s + ",")) - } - return h.Sum(nil) -} - -func hashSortedBigInt(lst []string) *big.Int { - rv := new(big.Int) - rv.SetBytes(hashSorted(lst)) - return rv -} - -var goproxySignerVersion = ":goroxy1" - -func signHost(ca tls.Certificate, hosts []string) (cert *tls.Certificate, err error) { - var x509ca *x509.Certificate - - // Use the provided ca and not the global GoproxyCa for certificate generation. - if x509ca, err = x509.ParseCertificate(ca.Certificate[0]); err != nil { - return - } - - start := time.Unix(time.Now().Unix()-2592000, 0) // 2592000 = 30 day - end := time.Unix(time.Now().Unix()+31536000, 0) // 31536000 = 365 day - - serial := big.NewInt(rand.Int63()) - template := x509.Certificate{ - // TODO(elazar): instead of this ugly hack, just encode the certificate and hash the binary form. - SerialNumber: serial, - Issuer: x509ca.Subject, - Subject: pkix.Name{ - Organization: []string{"GoProxy untrusted MITM proxy Inc"}, - }, - NotBefore: start, - NotAfter: end, - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } - for _, h := range hosts { - if ip := net.ParseIP(h); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, h) - template.Subject.CommonName = h - } - } - - hash := hashSorted(append(hosts, goproxySignerVersion, ":"+runtime.Version())) - var csprng CounterEncryptorRand - if csprng, err = NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil { - return - } - - var certpriv crypto.Signer - switch ca.PrivateKey.(type) { - case *rsa.PrivateKey: - if certpriv, err = rsa.GenerateKey(&csprng, 2048); err != nil { - return - } - case *ecdsa.PrivateKey: - if certpriv, err = ecdsa.GenerateKey(elliptic.P256(), &csprng); err != nil { - return - } - default: - err = fmt.Errorf("unsupported key type %T", ca.PrivateKey) - } - - var derBytes []byte - if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, certpriv.Public(), ca.PrivateKey); err != nil { - return - } - return &tls.Certificate{ - Certificate: [][]byte{derBytes, ca.Certificate[0]}, - PrivateKey: certpriv, - }, nil -} - -func init() { - // Avoid deterministic random numbers - rand.Seed(time.Now().UnixNano()) -} diff --git a/vendor/github.com/elazarl/goproxy/websocket.go b/vendor/github.com/elazarl/goproxy/websocket.go deleted file mode 100644 index 522b88e3..00000000 --- a/vendor/github.com/elazarl/goproxy/websocket.go +++ /dev/null @@ -1,121 +0,0 @@ -package goproxy - -import ( - "bufio" - "crypto/tls" - "io" - "net/http" - "net/url" - "strings" -) - -func headerContains(header http.Header, name string, value string) bool { - for _, v := range header[name] { - for _, s := range strings.Split(v, ",") { - if strings.EqualFold(value, strings.TrimSpace(s)) { - return true - } - } - } - return false -} - -func isWebSocketRequest(r *http.Request) bool { - return headerContains(r.Header, "Connection", "upgrade") && - headerContains(r.Header, "Upgrade", "websocket") -} - -func (proxy *ProxyHttpServer) serveWebsocketTLS(ctx *ProxyCtx, w http.ResponseWriter, req *http.Request, tlsConfig *tls.Config, clientConn *tls.Conn) { - targetURL := url.URL{Scheme: "wss", Host: req.URL.Host, Path: req.URL.Path} - - // Connect to upstream - targetConn, err := tls.Dial("tcp", targetURL.Host, tlsConfig) - if err != nil { - ctx.Warnf("Error dialing target site: %v", err) - return - } - defer targetConn.Close() - - // Perform handshake - if err := proxy.websocketHandshake(ctx, req, targetConn, clientConn); err != nil { - ctx.Warnf("Websocket handshake error: %v", err) - return - } - - // Proxy wss connection - proxy.proxyWebsocket(ctx, targetConn, clientConn) -} - -func (proxy *ProxyHttpServer) serveWebsocket(ctx *ProxyCtx, w http.ResponseWriter, req *http.Request) { - targetURL := url.URL{Scheme: "ws", Host: req.URL.Host, Path: req.URL.Path} - - targetConn, err := proxy.connectDial(ctx, "tcp", targetURL.Host) - if err != nil { - ctx.Warnf("Error dialing target site: %v", err) - return - } - defer targetConn.Close() - - // Connect to Client - hj, ok := w.(http.Hijacker) - if !ok { - panic("httpserver does not support hijacking") - } - clientConn, _, err := hj.Hijack() - if err != nil { - ctx.Warnf("Hijack error: %v", err) - return - } - - // Perform handshake - if err := proxy.websocketHandshake(ctx, req, targetConn, clientConn); err != nil { - ctx.Warnf("Websocket handshake error: %v", err) - return - } - - // Proxy ws connection - proxy.proxyWebsocket(ctx, targetConn, clientConn) -} - -func (proxy *ProxyHttpServer) websocketHandshake(ctx *ProxyCtx, req *http.Request, targetSiteConn io.ReadWriter, clientConn io.ReadWriter) error { - // write handshake request to target - err := req.Write(targetSiteConn) - if err != nil { - ctx.Warnf("Error writing upgrade request: %v", err) - return err - } - - targetTLSReader := bufio.NewReader(targetSiteConn) - - // Read handshake response from target - resp, err := http.ReadResponse(targetTLSReader, req) - if err != nil { - ctx.Warnf("Error reading handhsake response %v", err) - return err - } - - // Run response through handlers - resp = proxy.filterResponse(resp, ctx) - - // Proxy handshake back to client - err = resp.Write(clientConn) - if err != nil { - ctx.Warnf("Error writing handshake response: %v", err) - return err - } - return nil -} - -func (proxy *ProxyHttpServer) proxyWebsocket(ctx *ProxyCtx, dest io.ReadWriter, source io.ReadWriter) { - errChan := make(chan error, 2) - cp := func(dst io.Writer, src io.Reader) { - _, err := io.Copy(dst, src) - ctx.Warnf("Websocket error: %v", err) - errChan <- err - } - - // Start proxying websocket data - go cp(dest, source) - go cp(source, dest) - <-errChan -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7f14ad42..decf5e8d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -20,8 +20,8 @@ github.com/acarl005/stripansi # github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 ## explicit github.com/armon/go-socks5 -# github.com/blacknon/go-sshlib v0.1.11 -## explicit; go 1.22.2 +# github.com/blacknon/go-sshlib v0.1.12 +## explicit; go 1.22.4 github.com/blacknon/go-sshlib # github.com/blacknon/go-x11auth v0.1.0 ## explicit; go 1.22.2 @@ -48,9 +48,6 @@ github.com/disiqueira/gotree # github.com/dustin/go-humanize v1.0.0 ## explicit github.com/dustin/go-humanize -# github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 -## explicit -github.com/elazarl/goproxy # github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 ## explicit github.com/kardianos/osext @@ -63,8 +60,6 @@ github.com/kevinburke/ssh_config # github.com/kr/fs v0.1.0 ## explicit github.com/kr/fs -# github.com/kr/pretty v0.1.0 -## explicit # github.com/lunixbochs/vtclean v1.0.0 ## explicit github.com/lunixbochs/vtclean @@ -154,6 +149,3 @@ golang.org/x/term # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# mvdan.cc/sh v2.6.3+incompatible -## explicit -mvdan.cc/sh/syntax diff --git a/vendor/mvdan.cc/sh/LICENSE b/vendor/mvdan.cc/sh/LICENSE deleted file mode 100644 index 2a5268e5..00000000 --- a/vendor/mvdan.cc/sh/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2016, Daniel Martí. 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 the copyright holder 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/mvdan.cc/sh/syntax/canonical.sh b/vendor/mvdan.cc/sh/syntax/canonical.sh deleted file mode 100644 index 012f48dd..00000000 --- a/vendor/mvdan.cc/sh/syntax/canonical.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -# separate comment - -! foo bar >a & - -foo() { bar; } - -{ - var1="some long value" # var1 comment - var2=short # var2 comment -} - -if foo; then bar; fi - -for foo in a b c; do - bar -done - -case $foo in -a) A ;; -b) - B - ;; -esac - -foo | bar -foo && - $(bar) && - (more) - -foo 2>&1 -foo <<-EOF - bar -EOF - -$((3 + 4)) diff --git a/vendor/mvdan.cc/sh/syntax/doc.go b/vendor/mvdan.cc/sh/syntax/doc.go deleted file mode 100644 index eff8c2fe..00000000 --- a/vendor/mvdan.cc/sh/syntax/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) 2016, Daniel Martí -// See LICENSE for licensing information - -// Package syntax implements parsing and formatting of shell programs. -// It supports both POSIX Shell and Bash. -package syntax diff --git a/vendor/mvdan.cc/sh/syntax/expand.go b/vendor/mvdan.cc/sh/syntax/expand.go deleted file mode 100644 index 9316dce9..00000000 --- a/vendor/mvdan.cc/sh/syntax/expand.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) 2018, Daniel Martí -// See LICENSE for licensing information - -package syntax - -import "strconv" - -// TODO(v3): Consider making these special syntax nodes. -// Among other things, we can make use of Word.Lit. - -type brace struct { - seq bool // {x..y[..incr]} instead of {x,y[,...]} - chars bool // sequence is of chars, not numbers - elems []*braceWord -} - -// braceWord is like Word, but with braceWordPart. -type braceWord struct { - parts []braceWordPart -} - -// braceWordPart contains any WordPart or a brace. -type braceWordPart interface{} - -var ( - litLeftBrace = &Lit{Value: "{"} - litComma = &Lit{Value: ","} - litDots = &Lit{Value: ".."} - litRightBrace = &Lit{Value: "}"} -) - -func splitBraces(word *Word) (*braceWord, bool) { - any := false - top := &braceWord{} - acc := top - var cur *brace - open := []*brace{} - - pop := func() *brace { - old := cur - open = open[:len(open)-1] - if len(open) == 0 { - cur = nil - acc = top - } else { - cur = open[len(open)-1] - acc = cur.elems[len(cur.elems)-1] - } - return old - } - addLit := func(lit *Lit) { - acc.parts = append(acc.parts, lit) - } - addParts := func(parts ...braceWordPart) { - acc.parts = append(acc.parts, parts...) - } - - for _, wp := range word.Parts { - lit, ok := wp.(*Lit) - if !ok { - addParts(wp) - continue - } - last := 0 - for j := 0; j < len(lit.Value); j++ { - addlitidx := func() { - if last == j { - return // empty lit - } - l2 := *lit - l2.Value = l2.Value[last:j] - addLit(&l2) - } - switch lit.Value[j] { - case '{': - addlitidx() - acc = &braceWord{} - cur = &brace{elems: []*braceWord{acc}} - open = append(open, cur) - case ',': - if cur == nil { - continue - } - addlitidx() - acc = &braceWord{} - cur.elems = append(cur.elems, acc) - case '.': - if cur == nil { - continue - } - if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' { - continue - } - addlitidx() - cur.seq = true - acc = &braceWord{} - cur.elems = append(cur.elems, acc) - j++ - case '}': - if cur == nil { - continue - } - any = true - addlitidx() - br := pop() - if len(br.elems) == 1 { - // return {x} to a non-brace - addLit(litLeftBrace) - addParts(br.elems[0].parts...) - addLit(litRightBrace) - break - } - if !br.seq { - addParts(br) - break - } - var chars [2]bool - broken := false - for i, elem := range br.elems[:2] { - val := braceWordLit(elem) - if _, err := strconv.Atoi(val); err == nil { - } else if len(val) == 1 && - 'a' <= val[0] && val[0] <= 'z' { - chars[i] = true - } else { - broken = true - } - } - if len(br.elems) == 3 { - // increment must be a number - val := braceWordLit(br.elems[2]) - if _, err := strconv.Atoi(val); err != nil { - broken = true - } - } - // are start and end both chars or - // non-chars? - if chars[0] != chars[1] { - broken = true - } - if !broken { - br.chars = chars[0] - addParts(br) - break - } - // return broken {x..y[..incr]} to a non-brace - addLit(litLeftBrace) - for i, elem := range br.elems { - if i > 0 { - addLit(litDots) - } - addParts(elem.parts...) - } - addLit(litRightBrace) - default: - continue - } - last = j + 1 - } - if last == 0 { - addLit(lit) - } else { - left := *lit - left.Value = left.Value[last:] - addLit(&left) - } - } - // open braces that were never closed fall back to non-braces - for acc != top { - br := pop() - addLit(litLeftBrace) - for i, elem := range br.elems { - if i > 0 { - if br.seq { - addLit(litDots) - } else { - addLit(litComma) - } - } - addParts(elem.parts...) - } - } - return top, any -} - -func braceWordLit(v interface{}) string { - word, _ := v.(*braceWord) - if word == nil || len(word.parts) != 1 { - return "" - } - lit, ok := word.parts[0].(*Lit) - if !ok { - return "" - } - return lit.Value -} - -func expandRec(bw *braceWord) []*Word { - var all []*Word - var left []WordPart - for i, wp := range bw.parts { - br, ok := wp.(*brace) - if !ok { - left = append(left, wp.(WordPart)) - continue - } - if br.seq { - var from, to int - if br.chars { - from = int(braceWordLit(br.elems[0])[0]) - to = int(braceWordLit(br.elems[1])[0]) - } else { - from, _ = strconv.Atoi(braceWordLit(br.elems[0])) - to, _ = strconv.Atoi(braceWordLit(br.elems[1])) - } - upward := from <= to - incr := 1 - if !upward { - incr = -1 - } - if len(br.elems) > 2 { - val := braceWordLit(br.elems[2]) - n, _ := strconv.Atoi(val) - if n != 0 && n > 0 == upward { - incr = n - } - } - n := from - for { - if upward && n > to { - break - } - if !upward && n < to { - break - } - next := *bw - next.parts = next.parts[i+1:] - lit := &Lit{} - if br.chars { - lit.Value = string(n) - } else { - lit.Value = strconv.Itoa(n) - } - next.parts = append([]braceWordPart{lit}, next.parts...) - exp := expandRec(&next) - for _, w := range exp { - w.Parts = append(left, w.Parts...) - } - all = append(all, exp...) - n += incr - } - return all - } - for _, elem := range br.elems { - next := *bw - next.parts = next.parts[i+1:] - next.parts = append(elem.parts, next.parts...) - exp := expandRec(&next) - for _, w := range exp { - w.Parts = append(left, w.Parts...) - } - all = append(all, exp...) - } - return all - } - return []*Word{{Parts: left}} -} - -// TODO(v3): remove - -// ExpandBraces performs Bash brace expansion on a word. For example, -// passing it a single-literal word "foo{bar,baz}" will return two -// single-literal words, "foobar" and "foobaz". -// -// Deprecated: use mvdan.cc/sh/expand.Braces instead. -func ExpandBraces(word *Word) []*Word { - topBrace, any := splitBraces(word) - if !any { - return []*Word{word} - } - return expandRec(topBrace) -} diff --git a/vendor/mvdan.cc/sh/syntax/lexer.go b/vendor/mvdan.cc/sh/syntax/lexer.go deleted file mode 100644 index d2c1990c..00000000 --- a/vendor/mvdan.cc/sh/syntax/lexer.go +++ /dev/null @@ -1,1100 +0,0 @@ -// Copyright (c) 2016, Daniel Martí -// See LICENSE for licensing information - -package syntax - -import ( - "bytes" - "io" - "unicode/utf8" -) - -// bytes that form or start a token -func regOps(r rune) bool { - switch r { - case ';', '"', '\'', '(', ')', '$', '|', '&', '>', '<', '`': - return true - } - return false -} - -// tokenize these inside parameter expansions -func paramOps(r rune) bool { - switch r { - case '}', '#', '!', ':', '-', '+', '=', '?', '%', '[', ']', '/', '^', - ',', '@', '*': - return true - } - return false -} - -// these start a parameter expansion name -func paramNameOp(r rune) bool { - switch r { - case '}', ':', '+', '=', '%', '[', ']', '/', '^', ',': - return false - } - return true -} - -// tokenize these inside arithmetic expansions -func arithmOps(r rune) bool { - switch r { - case '+', '-', '!', '*', '/', '%', '(', ')', '^', '<', '>', ':', '=', - ',', '?', '|', '&', '[', ']', '#': - return true - } - return false -} - -func bquoteEscaped(b byte) bool { - switch b { - case '$', '`', '\\': - return true - } - return false -} - -func (p *Parser) rune() rune { - if p.r == '\n' { - // p.r instead of b so that newline - // character positions don't have col 0. - p.npos.line++ - p.npos.col = 0 - } - p.npos.col += p.w - bquotes := 0 -retry: - if p.bsp < len(p.bs) { - if b := p.bs[p.bsp]; b < utf8.RuneSelf { - p.bsp++ - if b == '\\' && p.openBquotes > 0 { - // don't do it for newlines, as we want - // the newlines to be eaten in p.next - if bquotes < p.openBquotes && p.bsp < len(p.bs) && - bquoteEscaped(p.bs[p.bsp]) { - bquotes++ - goto retry - } - } - if b == '`' { - p.lastBquoteEsc = bquotes - } - if p.litBs != nil { - p.litBs = append(p.litBs, b) - } - p.w, p.r = 1, rune(b) - return p.r - } - if !utf8.FullRune(p.bs[p.bsp:]) { - // we need more bytes to read a full non-ascii rune - p.fill() - } - var w int - p.r, w = utf8.DecodeRune(p.bs[p.bsp:]) - if p.litBs != nil { - p.litBs = append(p.litBs, p.bs[p.bsp:p.bsp+w]...) - } - p.bsp += w - if p.r == utf8.RuneError && w == 1 { - p.posErr(p.npos, "invalid UTF-8 encoding") - } - p.w = uint16(w) - } else { - if p.r == utf8.RuneSelf { - } else if p.fill(); p.bs == nil { - p.bsp++ - p.r = utf8.RuneSelf - p.w = 1 - } else { - goto retry - } - } - return p.r -} - -// fill reads more bytes from the input src into readBuf. Any bytes that -// had not yet been used at the end of the buffer are slid into the -// beginning of the buffer. -func (p *Parser) fill() { - p.offs += p.bsp - left := len(p.bs) - p.bsp - copy(p.readBuf[:left], p.readBuf[p.bsp:]) -readAgain: - n, err := 0, p.readErr - if err == nil { - n, err = p.src.Read(p.readBuf[left:]) - p.readErr = err - } - if n == 0 { - if err == nil { - goto readAgain - } - // don't use p.errPass as we don't want to overwrite p.tok - if err != io.EOF { - p.err = err - } - if left > 0 { - p.bs = p.readBuf[:left] - } else { - p.bs = nil - } - } else { - p.bs = p.readBuf[:left+n] - } - p.bsp = 0 -} - -func (p *Parser) nextKeepSpaces() { - r := p.r - p.pos = p.getPos() - switch p.quote { - case paramExpRepl: - switch r { - case '}', '/': - p.tok = p.paramToken(r) - case '`', '"', '$': - p.tok = p.regToken(r) - default: - p.advanceLitOther(r) - } - case dblQuotes: - switch r { - case '`', '"', '$': - p.tok = p.dqToken(r) - default: - p.advanceLitDquote(r) - } - case hdocBody, hdocBodyTabs: - switch { - case r == '`' || r == '$': - p.tok = p.dqToken(r) - case p.hdocStop == nil: - p.tok = _Newl - default: - p.advanceLitHdoc(r) - } - default: // paramExpExp: - switch r { - case '}': - p.tok = p.paramToken(r) - case '`', '"', '$', '\'': - p.tok = p.regToken(r) - default: - p.advanceLitOther(r) - } - } - if p.err != nil && p.tok != _EOF { - p.tok = _EOF - } -} - -func (p *Parser) next() { - if p.r == utf8.RuneSelf { - p.tok = _EOF - return - } - p.spaced = false - if p.quote&allKeepSpaces != 0 { - p.nextKeepSpaces() - return - } - r := p.r -skipSpace: - for { - switch r { - case utf8.RuneSelf: - p.tok = _EOF - return - case ' ', '\t', '\r': - p.spaced = true - r = p.rune() - case '\n': - if p.tok == _Newl { - // merge consecutive newline tokens - r = p.rune() - continue - } - p.spaced = true - p.tok = _Newl - if p.quote != hdocWord && len(p.heredocs) > p.buriedHdocs { - p.doHeredocs() - } - return - case '\\': - if !p.peekByte('\n') { - break skipSpace - } - p.rune() - r = p.rune() - default: - break skipSpace - } - } - if p.stopAt != nil && (p.spaced || p.tok == illegalTok || stopToken(p.tok)) { - w := utf8.RuneLen(r) - if bytes.HasPrefix(p.bs[p.bsp-w:], p.stopAt) { - p.r = utf8.RuneSelf - p.w = 1 - p.tok = _EOF - return - } - } -changedState: - p.pos = p.getPos() - switch { - case p.quote&allRegTokens != 0: - switch r { - case ';', '"', '\'', '(', ')', '$', '|', '&', '>', '<', '`': - p.tok = p.regToken(r) - case '#': - r = p.rune() - p.newLit(r) - for r != utf8.RuneSelf && r != '\n' { - r = p.rune() - } - if p.keepComments { - *p.curComs = append(*p.curComs, Comment{ - Hash: p.pos, - Text: p.endLit(), - }) - } else { - p.litBs = nil - } - p.next() - case '[', '=': - if p.quote == arrayElems { - p.tok = p.paramToken(r) - } else { - p.advanceLitNone(r) - } - case '?', '*', '+', '@', '!': - if p.peekByte('(') { - switch r { - case '?': - p.tok = globQuest - case '*': - p.tok = globStar - case '+': - p.tok = globPlus - case '@': - p.tok = globAt - default: // '!' - p.tok = globExcl - } - p.rune() - p.rune() - } else { - p.advanceLitNone(r) - } - default: - p.advanceLitNone(r) - } - case p.quote&allArithmExpr != 0 && arithmOps(r): - p.tok = p.arithmToken(r) - case p.quote&allParamExp != 0 && paramOps(r): - p.tok = p.paramToken(r) - case p.quote == testRegexp: - if !p.rxFirstPart && p.spaced { - p.quote = noState - goto changedState - } - p.rxFirstPart = false - switch r { - case ';', '"', '\'', '$', '&', '>', '<', '`': - p.tok = p.regToken(r) - case ')': - if p.rxOpenParens > 0 { - // continuation of open paren - p.advanceLitRe(r) - } else { - p.tok = rightParen - p.quote = noState - } - default: // including '(', '|' - p.advanceLitRe(r) - } - case regOps(r): - p.tok = p.regToken(r) - default: - p.advanceLitOther(r) - } - if p.err != nil && p.tok != _EOF { - p.tok = _EOF - } -} - -func (p *Parser) peekByte(b byte) bool { - if p.bsp == len(p.bs) { - p.fill() - } - return p.bsp < len(p.bs) && p.bs[p.bsp] == b -} - -func (p *Parser) regToken(r rune) token { - switch r { - case '\'': - if p.openBquotes > 0 { - // bury openBquotes - p.buriedBquotes = p.openBquotes - p.openBquotes = 0 - } - p.rune() - return sglQuote - case '"': - p.rune() - return dblQuote - case '`': - // Don't call p.rune, as we need to work out p.openBquotes to - // properly handle backslashes in the lexer. - return bckQuote - case '&': - switch p.rune() { - case '&': - p.rune() - return andAnd - case '>': - if p.lang == LangPOSIX { - break - } - if p.rune() == '>' { - p.rune() - return appAll - } - return rdrAll - } - return and - case '|': - switch p.rune() { - case '|': - p.rune() - return orOr - case '&': - if p.lang == LangPOSIX { - break - } - p.rune() - return orAnd - } - return or - case '$': - switch p.rune() { - case '\'': - if p.lang == LangPOSIX { - break - } - p.rune() - return dollSglQuote - case '"': - if p.lang == LangPOSIX { - break - } - p.rune() - return dollDblQuote - case '{': - p.rune() - return dollBrace - case '[': - if p.lang != LangBash || p.quote == paramExpName { - // latter to not tokenise ${$[@]} as $[ - break - } - p.rune() - return dollBrack - case '(': - if p.rune() == '(' { - p.rune() - return dollDblParen - } - return dollParen - } - return dollar - case '(': - if p.rune() == '(' && p.lang != LangPOSIX { - p.rune() - return dblLeftParen - } - return leftParen - case ')': - p.rune() - return rightParen - case ';': - switch p.rune() { - case ';': - if p.rune() == '&' && p.lang == LangBash { - p.rune() - return dblSemiAnd - } - return dblSemicolon - case '&': - if p.lang == LangPOSIX { - break - } - p.rune() - return semiAnd - case '|': - if p.lang != LangMirBSDKorn { - break - } - p.rune() - return semiOr - } - return semicolon - case '<': - switch p.rune() { - case '<': - if r = p.rune(); r == '-' { - p.rune() - return dashHdoc - } else if r == '<' && p.lang != LangPOSIX { - p.rune() - return wordHdoc - } - return hdoc - case '>': - p.rune() - return rdrInOut - case '&': - p.rune() - return dplIn - case '(': - if p.lang != LangBash { - break - } - p.rune() - return cmdIn - } - return rdrIn - default: // '>' - switch p.rune() { - case '>': - p.rune() - return appOut - case '&': - p.rune() - return dplOut - case '|': - p.rune() - return clbOut - case '(': - if p.lang != LangBash { - break - } - p.rune() - return cmdOut - } - return rdrOut - } -} - -func (p *Parser) dqToken(r rune) token { - switch r { - case '"': - p.rune() - return dblQuote - case '`': - // Don't call p.rune, as we need to work out p.openBquotes to - // properly handle backslashes in the lexer. - return bckQuote - default: // '$' - switch p.rune() { - case '{': - p.rune() - return dollBrace - case '[': - if p.lang != LangBash { - break - } - p.rune() - return dollBrack - case '(': - if p.rune() == '(' { - p.rune() - return dollDblParen - } - return dollParen - } - return dollar - } -} - -func (p *Parser) paramToken(r rune) token { - switch r { - case '}': - p.rune() - return rightBrace - case ':': - switch p.rune() { - case '+': - p.rune() - return colPlus - case '-': - p.rune() - return colMinus - case '?': - p.rune() - return colQuest - case '=': - p.rune() - return colAssgn - } - return colon - case '+': - p.rune() - return plus - case '-': - p.rune() - return minus - case '?': - p.rune() - return quest - case '=': - p.rune() - return assgn - case '%': - if p.rune() == '%' { - p.rune() - return dblPerc - } - return perc - case '#': - if p.rune() == '#' { - p.rune() - return dblHash - } - return hash - case '!': - p.rune() - return exclMark - case '[': - p.rune() - return leftBrack - case ']': - p.rune() - return rightBrack - case '/': - if p.rune() == '/' && p.quote != paramExpRepl { - p.rune() - return dblSlash - } - return slash - case '^': - if p.rune() == '^' { - p.rune() - return dblCaret - } - return caret - case ',': - if p.rune() == ',' { - p.rune() - return dblComma - } - return comma - case '@': - p.rune() - return at - default: // '*' - p.rune() - return star - } -} - -func (p *Parser) arithmToken(r rune) token { - switch r { - case '!': - if p.rune() == '=' { - p.rune() - return nequal - } - return exclMark - case '=': - if p.rune() == '=' { - p.rune() - return equal - } - return assgn - case '(': - p.rune() - return leftParen - case ')': - p.rune() - return rightParen - case '&': - switch p.rune() { - case '&': - p.rune() - return andAnd - case '=': - p.rune() - return andAssgn - } - return and - case '|': - switch p.rune() { - case '|': - p.rune() - return orOr - case '=': - p.rune() - return orAssgn - } - return or - case '<': - switch p.rune() { - case '<': - if p.rune() == '=' { - p.rune() - return shlAssgn - } - return hdoc - case '=': - p.rune() - return lequal - } - return rdrIn - case '>': - switch p.rune() { - case '>': - if p.rune() == '=' { - p.rune() - return shrAssgn - } - return appOut - case '=': - p.rune() - return gequal - } - return rdrOut - case '+': - switch p.rune() { - case '+': - p.rune() - return addAdd - case '=': - p.rune() - return addAssgn - } - return plus - case '-': - switch p.rune() { - case '-': - p.rune() - return subSub - case '=': - p.rune() - return subAssgn - } - return minus - case '%': - if p.rune() == '=' { - p.rune() - return remAssgn - } - return perc - case '*': - switch p.rune() { - case '*': - p.rune() - return power - case '=': - p.rune() - return mulAssgn - } - return star - case '/': - if p.rune() == '=' { - p.rune() - return quoAssgn - } - return slash - case '^': - if p.rune() == '=' { - p.rune() - return xorAssgn - } - return caret - case '[': - p.rune() - return leftBrack - case ']': - p.rune() - return rightBrack - case ',': - p.rune() - return comma - case '?': - p.rune() - return quest - case ':': - p.rune() - return colon - default: // '#' - p.rune() - return hash - } -} - -func (p *Parser) newLit(r rune) { - switch { - case r < utf8.RuneSelf: - p.litBs = p.litBuf[:1] - p.litBs[0] = byte(r) - case r > utf8.RuneSelf: - w := utf8.RuneLen(r) - p.litBs = append(p.litBuf[:0], p.bs[p.bsp-w:p.bsp]...) - default: - // don't let r == utf8.RuneSelf go to the second case as RuneLen - // would return -1 - p.litBs = p.litBuf[:0] - } -} - -func (p *Parser) discardLit(n int) { p.litBs = p.litBs[:len(p.litBs)-n] } - -func (p *Parser) endLit() (s string) { - if p.r == utf8.RuneSelf { - s = string(p.litBs) - } else { - s = string(p.litBs[:len(p.litBs)-int(p.w)]) - } - p.litBs = nil - return -} - -func (p *Parser) isLitRedir() bool { - lit := p.litBs[:len(p.litBs)-1] - if lit[0] == '{' && lit[len(lit)-1] == '}' { - return ValidName(string(lit[1 : len(lit)-1])) - } - for _, b := range lit { - switch b { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - default: - return false - } - } - return true -} - -func (p *Parser) advanceNameCont(r rune) { - // we know that r is a letter or underscore -loop: - for p.newLit(r); r != utf8.RuneSelf; r = p.rune() { - switch { - case r == '\\': - if p.peekByte('\n') { - p.rune() - p.discardLit(2) - } else { - break loop - } - case 'a' <= r && r <= 'z': - case 'A' <= r && r <= 'Z': - case r == '_': - case '0' <= r && r <= '9': - default: - break loop - } - } - p.tok, p.val = _LitWord, p.endLit() -} - -func (p *Parser) advanceLitOther(r rune) { - tok := _LitWord -loop: - for p.newLit(r); r != utf8.RuneSelf; r = p.rune() { - switch r { - case '\\': // escaped byte follows - if r = p.rune(); r == '\n' { - p.discardLit(2) - } - case '"', '`', '$': - tok = _Lit - break loop - case '}': - if p.quote&allParamExp != 0 { - break loop - } - case '/': - if p.quote != paramExpExp { - break loop - } - case ':', '=', '%', '^', ',', '?', '!', '*': - if p.quote&allArithmExpr != 0 || p.quote == paramExpName { - break loop - } - case '[', ']': - if p.lang != LangPOSIX && p.quote&allArithmExpr != 0 { - break loop - } - fallthrough - case '#', '@': - if p.quote&allParamReg != 0 { - break loop - } - case '\'', '+', '-', ' ', '\t', ';', '&', '>', '<', '|', '(', ')', '\n', '\r': - if p.quote&allKeepSpaces == 0 { - break loop - } - } - } - p.tok, p.val = tok, p.endLit() -} - -func (p *Parser) advanceLitNone(r rune) { - p.eqlOffs = 0 - tok := _LitWord -loop: - for p.newLit(r); r != utf8.RuneSelf; r = p.rune() { - switch r { - case ' ', '\t', '\n', '\r', '&', '|', ';', '(', ')': - break loop - case '\\': // escaped byte follows - if r = p.rune(); r == '\n' { - p.discardLit(2) - } - case '>', '<': - if p.peekByte('(') || !p.isLitRedir() { - tok = _Lit - } else { - tok = _LitRedir - } - break loop - case '`': - if p.quote != subCmdBckquo { - tok = _Lit - } - break loop - case '"', '\'', '$': - tok = _Lit - break loop - case '?', '*', '+', '@', '!': - if p.peekByte('(') { - tok = _Lit - break loop - } - case '=': - if p.eqlOffs == 0 { - p.eqlOffs = len(p.litBs) - 1 - } - case '[': - if p.lang != LangPOSIX && len(p.litBs) > 1 && p.litBs[0] != '[' { - tok = _Lit - break loop - } - } - } - p.tok, p.val = tok, p.endLit() -} - -func (p *Parser) advanceLitDquote(r rune) { - tok := _LitWord -loop: - for p.newLit(r); r != utf8.RuneSelf; r = p.rune() { - switch r { - case '"': - break loop - case '\\': // escaped byte follows - p.rune() - case '`', '$': - tok = _Lit - break loop - } - } - p.tok, p.val = tok, p.endLit() -} - -func (p *Parser) advanceLitHdoc(r rune) { - p.tok = _Lit - p.newLit(r) - if p.quote == hdocBodyTabs { - for r == '\t' { - r = p.rune() - } - } - lStart := len(p.litBs) - 1 - if lStart < 0 { - return - } - for ; ; r = p.rune() { - switch r { - case '`', '$': - p.val = p.endLit() - return - case '\\': // escaped byte follows - p.rune() - case '\n', utf8.RuneSelf: - if p.parsingDoc { - if r == utf8.RuneSelf { - p.val = p.endLit() - return - } - } else if bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) { - p.val = p.endLit()[:lStart] - if p.val == "" { - p.tok = _Newl - } - p.hdocStop = nil - return - } - if r == utf8.RuneSelf { - return - } - if p.quote == hdocBodyTabs { - for p.peekByte('\t') { - p.rune() - } - } - lStart = len(p.litBs) - } - } -} - -func (p *Parser) quotedHdocWord() *Word { - r := p.r - p.newLit(r) - pos := p.getPos() - for ; ; r = p.rune() { - if r == utf8.RuneSelf { - return nil - } - if p.quote == hdocBodyTabs { - for r == '\t' { - r = p.rune() - } - } - lStart := len(p.litBs) - 1 - if lStart < 0 { - return nil - } - for r != utf8.RuneSelf && r != '\n' { - r = p.rune() - } - if bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) { - p.hdocStop = nil - val := p.endLit()[:lStart] - if val == "" { - return nil - } - return p.word(p.wps(p.lit(pos, val))) - } - } -} - -func (p *Parser) advanceLitRe(r rune) { - for p.newLit(r); ; r = p.rune() { - switch r { - case '\\': - p.rune() - case '(': - p.rxOpenParens++ - case ')': - if p.rxOpenParens--; p.rxOpenParens < 0 { - p.tok, p.val = _LitWord, p.endLit() - p.quote = noState - return - } - case ' ', '\t', '\r', '\n': - if p.rxOpenParens <= 0 { - p.tok, p.val = _LitWord, p.endLit() - p.quote = noState - return - } - case '"', '\'', '$', '`': - p.tok, p.val = _Lit, p.endLit() - return - case utf8.RuneSelf, ';', '&', '>', '<': - p.tok, p.val = _LitWord, p.endLit() - p.quote = noState - return - } - } -} - -func testUnaryOp(val string) UnTestOperator { - switch val { - case "!": - return TsNot - case "-e", "-a": - return TsExists - case "-f": - return TsRegFile - case "-d": - return TsDirect - case "-c": - return TsCharSp - case "-b": - return TsBlckSp - case "-p": - return TsNmPipe - case "-S": - return TsSocket - case "-L", "-h": - return TsSmbLink - case "-k": - return TsSticky - case "-g": - return TsGIDSet - case "-u": - return TsUIDSet - case "-G": - return TsGrpOwn - case "-O": - return TsUsrOwn - case "-N": - return TsModif - case "-r": - return TsRead - case "-w": - return TsWrite - case "-x": - return TsExec - case "-s": - return TsNoEmpty - case "-t": - return TsFdTerm - case "-z": - return TsEmpStr - case "-n": - return TsNempStr - case "-o": - return TsOptSet - case "-v": - return TsVarSet - case "-R": - return TsRefVar - default: - return 0 - } -} - -func testBinaryOp(val string) BinTestOperator { - switch val { - case "==", "=": - return TsMatch - case "!=": - return TsNoMatch - case "=~": - return TsReMatch - case "-nt": - return TsNewer - case "-ot": - return TsOlder - case "-ef": - return TsDevIno - case "-eq": - return TsEql - case "-ne": - return TsNeq - case "-le": - return TsLeq - case "-ge": - return TsGeq - case "-lt": - return TsLss - case "-gt": - return TsGtr - default: - return 0 - } -} diff --git a/vendor/mvdan.cc/sh/syntax/nodes.go b/vendor/mvdan.cc/sh/syntax/nodes.go deleted file mode 100644 index 3daf82fb..00000000 --- a/vendor/mvdan.cc/sh/syntax/nodes.go +++ /dev/null @@ -1,869 +0,0 @@ -// Copyright (c) 2016, Daniel Martí -// See LICENSE for licensing information - -package syntax - -import ( - "fmt" - "strings" -) - -// Node represents a syntax tree node. -type Node interface { - // Pos returns the position of the first character of the node. Comments - // are ignored, except if the node is a *File. - Pos() Pos - // End returns the position of the character immediately after the node. - // If the character is a newline, the line number won't cross into the - // next line. Comments are ignored, except if the node is a *File. - End() Pos -} - -// File represents a shell source file. -type File struct { - Name string - - StmtList -} - -// StmtList is a list of statements with any number of trailing comments. Both -// lists can be empty. -type StmtList struct { - Stmts []*Stmt - Last []Comment -} - -func (s StmtList) pos() Pos { - if len(s.Stmts) > 0 { - s := s.Stmts[0] - sPos := s.Pos() - if len(s.Comments) > 0 { - if cPos := s.Comments[0].Pos(); sPos.After(cPos) { - return cPos - } - } - return sPos - } - if len(s.Last) > 0 { - return s.Last[0].Pos() - } - return Pos{} -} - -func (s StmtList) end() Pos { - if len(s.Last) > 0 { - return s.Last[len(s.Last)-1].End() - } - if len(s.Stmts) > 0 { - s := s.Stmts[len(s.Stmts)-1] - sEnd := s.End() - if len(s.Comments) > 0 { - if cEnd := s.Comments[0].End(); cEnd.After(sEnd) { - return cEnd - } - } - return sEnd - } - return Pos{} -} - -func (s StmtList) empty() bool { - return len(s.Stmts) == 0 && len(s.Last) == 0 -} - -// Pos is a position within a shell source file. -type Pos struct { - offs uint32 - line, col uint16 -} - -// Offset returns the byte offset of the position in the original source file. -// Byte offsets start at 0. -func (p Pos) Offset() uint { return uint(p.offs) } - -// Line returns the line number of the position, starting at 1. -func (p Pos) Line() uint { return uint(p.line) } - -// Col returns the column number of the position, starting at 1. It counts in -// bytes. -func (p Pos) Col() uint { return uint(p.col) } - -func (p Pos) String() string { - return fmt.Sprintf("%d:%d", p.Line(), p.Col()) -} - -// IsValid reports whether the position is valid. All positions in nodes -// returned by Parse are valid. -func (p Pos) IsValid() bool { return p.line > 0 } - -// After reports whether the position p is after p2. It is a more expressive -// version of p.Offset() > p2.Offset(). -func (p Pos) After(p2 Pos) bool { return p.offs > p2.offs } - -func (f *File) Pos() Pos { return f.StmtList.pos() } -func (f *File) End() Pos { return f.StmtList.end() } - -func posAddCol(p Pos, n int) Pos { - p.col += uint16(n) - p.offs += uint32(n) - return p -} - -func posMax(p1, p2 Pos) Pos { - if p2.After(p1) { - return p2 - } - return p1 -} - -// Comment represents a single comment on a single line. -type Comment struct { - Hash Pos - Text string -} - -func (c *Comment) Pos() Pos { return c.Hash } -func (c *Comment) End() Pos { return posAddCol(c.Hash, 1+len(c.Text)) } - -// Stmt represents a statement, also known as a "complete command". It is -// compromised of a command and other components that may come before or after -// it. -type Stmt struct { - Comments []Comment - Cmd Command - Position Pos - Semicolon Pos // position of ';', '&', or '|&', if any - Negated bool // ! stmt - Background bool // stmt & - Coprocess bool // mksh's |& - - Redirs []*Redirect // stmt >a 0 { - end = posMax(end, s.Redirs[len(s.Redirs)-1].End()) - } - return end -} - -// Command represents all nodes that are simple or compound commands, including -// function declarations. -// -// These are *CallExpr, *IfClause, *WhileClause, *ForClause, *CaseClause, -// *Block, *Subshell, *BinaryCmd, *FuncDecl, *ArithmCmd, *TestClause, -// *DeclClause, *LetClause, *TimeClause, and *CoprocClause. -type Command interface { - Node - commandNode() -} - -func (*CallExpr) commandNode() {} -func (*IfClause) commandNode() {} -func (*WhileClause) commandNode() {} -func (*ForClause) commandNode() {} -func (*CaseClause) commandNode() {} -func (*Block) commandNode() {} -func (*Subshell) commandNode() {} -func (*BinaryCmd) commandNode() {} -func (*FuncDecl) commandNode() {} -func (*ArithmCmd) commandNode() {} -func (*TestClause) commandNode() {} -func (*DeclClause) commandNode() {} -func (*LetClause) commandNode() {} -func (*TimeClause) commandNode() {} -func (*CoprocClause) commandNode() {} - -// Assign represents an assignment to a variable. -// -// Here and elsewhere, Index can mean either an index expression into an indexed -// array, or a string key into an associative array. -// -// If Index is non-nil, the value will be a word and not an array as nested -// arrays are not allowed. -// -// If Naked is true and Name is nil, the assignment is part of a DeclClause and -// the assignment expression (in the Value field) will be evaluated at run-time. -type Assign struct { - Append bool // += - Naked bool // without '=' - Name *Lit - Index ArithmExpr // [i], ["k"] - Value *Word // =val - Array *ArrayExpr // =(arr) -} - -func (a *Assign) Pos() Pos { - if a.Name == nil { - return a.Value.Pos() - } - return a.Name.Pos() -} - -func (a *Assign) End() Pos { - if a.Value != nil { - return a.Value.End() - } - if a.Array != nil { - return a.Array.End() - } - if a.Index != nil { - return posAddCol(a.Index.End(), 2) - } - if a.Naked { - return a.Name.End() - } - return posAddCol(a.Name.End(), 1) -} - -// Redirect represents an input/output redirection. -type Redirect struct { - OpPos Pos - Op RedirOperator - N *Lit // fd>, or {varname}> in Bash - Word *Word // >word - Hdoc *Word // here-document body -} - -func (r *Redirect) Pos() Pos { - if r.N != nil { - return r.N.Pos() - } - return r.OpPos -} -func (r *Redirect) End() Pos { - if r.Hdoc != nil { - return r.Hdoc.End() - } - return r.Word.End() -} - -// CallExpr represents a command execution or function call, otherwise known as -// a "simple command". -// -// If Args is empty, Assigns apply to the shell environment. Otherwise, they are -// variables that cannot be arrays and which only apply to the call. -type CallExpr struct { - Assigns []*Assign // a=x b=y args - Args []*Word -} - -func (c *CallExpr) Pos() Pos { - if len(c.Assigns) > 0 { - return c.Assigns[0].Pos() - } - return c.Args[0].Pos() -} - -func (c *CallExpr) End() Pos { - if len(c.Args) == 0 { - return c.Assigns[len(c.Assigns)-1].End() - } - return c.Args[len(c.Args)-1].End() -} - -// Subshell represents a series of commands that should be executed in a nested -// shell environment. -type Subshell struct { - Lparen, Rparen Pos - StmtList -} - -func (s *Subshell) Pos() Pos { return s.Lparen } -func (s *Subshell) End() Pos { return posAddCol(s.Rparen, 1) } - -// Block represents a series of commands that should be executed in a nested -// scope. -type Block struct { - Lbrace, Rbrace Pos - StmtList -} - -func (b *Block) Pos() Pos { return b.Lbrace } -func (b *Block) End() Pos { return posAddCol(b.Rbrace, 1) } - -// TODO(v3): Refactor and simplify elif/else. For example, we could likely make -// Else an *IfClause, remove ElsePos, make IfPos also do opening "else" -// positions, and join the comment slices as Last []Comment. - -// IfClause represents an if statement. -type IfClause struct { - Elif bool // whether this IfClause begins with "elif" - IfPos Pos // position of the starting "if" or "elif" token - ThenPos Pos - ElsePos Pos // position of a following "else" or "elif", if any - FiPos Pos // position of "fi", empty if Elif == true - - Cond StmtList - Then StmtList - Else StmtList - - ElseComments []Comment // comments on the "else" - FiComments []Comment // comments on the "fi" -} - -func (c *IfClause) Pos() Pos { return c.IfPos } -func (c *IfClause) End() Pos { - if !c.FiPos.IsValid() { - return posAddCol(c.ElsePos, 4) - } - return posAddCol(c.FiPos, 2) -} - -// FollowedByElif reports whether this IfClause is followed by an "elif" -// IfClause in its Else branch. This is true if Else.Stmts has exactly one -// statement with an IfClause whose Elif field is true. -func (c *IfClause) FollowedByElif() bool { - if len(c.Else.Stmts) != 1 { - return false - } - ic, _ := c.Else.Stmts[0].Cmd.(*IfClause) - return ic != nil && ic.Elif -} - -func (c *IfClause) bodyEndPos() Pos { - if c.ElsePos.IsValid() { - return c.ElsePos - } - return c.FiPos -} - -// WhileClause represents a while or an until clause. -type WhileClause struct { - WhilePos, DoPos, DonePos Pos - Until bool - Cond StmtList - Do StmtList -} - -func (w *WhileClause) Pos() Pos { return w.WhilePos } -func (w *WhileClause) End() Pos { return posAddCol(w.DonePos, 4) } - -// ForClause represents a for or a select clause. The latter is only present in -// Bash. -type ForClause struct { - ForPos, DoPos, DonePos Pos - Select bool - Loop Loop - Do StmtList -} - -func (f *ForClause) Pos() Pos { return f.ForPos } -func (f *ForClause) End() Pos { return posAddCol(f.DonePos, 4) } - -// Loop holds either *WordIter or *CStyleLoop. -type Loop interface { - Node - loopNode() -} - -func (*WordIter) loopNode() {} -func (*CStyleLoop) loopNode() {} - -// WordIter represents the iteration of a variable over a series of words in a -// for clause. -type WordIter struct { - Name *Lit - Items []*Word -} - -func (w *WordIter) Pos() Pos { return w.Name.Pos() } -func (w *WordIter) End() Pos { return posMax(w.Name.End(), wordLastEnd(w.Items)) } - -// CStyleLoop represents the behaviour of a for clause similar to the C -// language. -// -// This node will only appear with LangBash. -type CStyleLoop struct { - Lparen, Rparen Pos - Init, Cond, Post ArithmExpr -} - -func (c *CStyleLoop) Pos() Pos { return c.Lparen } -func (c *CStyleLoop) End() Pos { return posAddCol(c.Rparen, 2) } - -// BinaryCmd represents a binary expression between two statements. -type BinaryCmd struct { - OpPos Pos - Op BinCmdOperator - X, Y *Stmt -} - -func (b *BinaryCmd) Pos() Pos { return b.X.Pos() } -func (b *BinaryCmd) End() Pos { return b.Y.End() } - -// FuncDecl represents the declaration of a function. -type FuncDecl struct { - Position Pos - RsrvWord bool // non-posix "function f()" style - Name *Lit - Body *Stmt -} - -func (f *FuncDecl) Pos() Pos { return f.Position } -func (f *FuncDecl) End() Pos { return f.Body.End() } - -// Word represents a shell word, containing one or more word parts contiguous to -// each other. The word is delimeted by word boundaries, such as spaces, -// newlines, semicolons, or parentheses. -type Word struct { - Parts []WordPart -} - -func (w *Word) Pos() Pos { return w.Parts[0].Pos() } -func (w *Word) End() Pos { return w.Parts[len(w.Parts)-1].End() } - -// Lit returns the word as a literal value, if the word consists of *syntax.Lit -// nodes only. An empty string is returned otherwise. Words with multiple -// literals, which can appear in some edge cases, are handled properly. -// -// For example, the word "foo" will return "foo", but the word "foo${bar}" will -// return "". -func (w *Word) Lit() string { - // In the usual case, we'll have either a single part that's a literal, - // or one of the parts being a non-literal. Using strings.Join instead - // of a strings.Builder avoids extra work in these cases, since a single - // part is a shortcut, and many parts don't incur string copies. - lits := make([]string, 0, 1) - for _, part := range w.Parts { - lit, ok := part.(*Lit) - if !ok { - return "" - } - lits = append(lits, lit.Value) - } - return strings.Join(lits, "") -} - -// WordPart represents all nodes that can form part of a word. -// -// These are *Lit, *SglQuoted, *DblQuoted, *ParamExp, *CmdSubst, *ArithmExp, -// *ProcSubst, and *ExtGlob. -type WordPart interface { - Node - wordPartNode() -} - -func (*Lit) wordPartNode() {} -func (*SglQuoted) wordPartNode() {} -func (*DblQuoted) wordPartNode() {} -func (*ParamExp) wordPartNode() {} -func (*CmdSubst) wordPartNode() {} -func (*ArithmExp) wordPartNode() {} -func (*ProcSubst) wordPartNode() {} -func (*ExtGlob) wordPartNode() {} - -// Lit represents a string literal. -// -// Note that a parsed string literal may not appear as-is in the original source -// code, as it is possible to split literals by escaping newlines. The splitting -// is lost, but the end position is not. -type Lit struct { - ValuePos, ValueEnd Pos - Value string -} - -func (l *Lit) Pos() Pos { return l.ValuePos } -func (l *Lit) End() Pos { return l.ValueEnd } - -// SglQuoted represents a string within single quotes. -type SglQuoted struct { - Left, Right Pos - Dollar bool // $'' - Value string -} - -func (q *SglQuoted) Pos() Pos { return q.Left } -func (q *SglQuoted) End() Pos { return posAddCol(q.Right, 1) } - -// DblQuoted represents a list of nodes within double quotes. -type DblQuoted struct { - Position Pos - Dollar bool // $"" - Parts []WordPart -} - -func (q *DblQuoted) Pos() Pos { return q.Position } -func (q *DblQuoted) End() Pos { - if len(q.Parts) == 0 { - if q.Dollar { - return posAddCol(q.Position, 3) - } - return posAddCol(q.Position, 2) - } - return posAddCol(q.Parts[len(q.Parts)-1].End(), 1) -} - -// CmdSubst represents a command substitution. -type CmdSubst struct { - Left, Right Pos - StmtList - - TempFile bool // mksh's ${ foo;} - ReplyVar bool // mksh's ${|foo;} -} - -func (c *CmdSubst) Pos() Pos { return c.Left } -func (c *CmdSubst) End() Pos { return posAddCol(c.Right, 1) } - -// ParamExp represents a parameter expansion. -type ParamExp struct { - Dollar, Rbrace Pos - Short bool // $a instead of ${a} - Excl bool // ${!a} - Length bool // ${#a} - Width bool // ${%a} - Param *Lit - Index ArithmExpr // ${a[i]}, ${a["k"]} - Slice *Slice // ${a:x:y} - Repl *Replace // ${a/x/y} - Names ParNamesOperator // ${!prefix*} or ${!prefix@} - Exp *Expansion // ${a:-b}, ${a#b}, etc -} - -func (p *ParamExp) Pos() Pos { return p.Dollar } -func (p *ParamExp) End() Pos { - if !p.Short { - return posAddCol(p.Rbrace, 1) - } - if p.Index != nil { - return posAddCol(p.Index.End(), 1) - } - return p.Param.End() -} - -func (p *ParamExp) nakedIndex() bool { - return p.Short && p.Index != nil -} - -// Slice represents a character slicing expression inside a ParamExp. -// -// This node will only appear in LangBash and LangMirBSDKorn. -type Slice struct { - Offset, Length ArithmExpr -} - -// Replace represents a search and replace expression inside a ParamExp. -type Replace struct { - All bool - Orig, With *Word -} - -// Expansion represents string manipulation in a ParamExp other than those -// covered by Replace. -type Expansion struct { - Op ParExpOperator - Word *Word -} - -// ArithmExp represents an arithmetic expansion. -type ArithmExp struct { - Left, Right Pos - Bracket bool // deprecated $[expr] form - Unsigned bool // mksh's $((# expr)) - X ArithmExpr -} - -func (a *ArithmExp) Pos() Pos { return a.Left } -func (a *ArithmExp) End() Pos { - if a.Bracket { - return posAddCol(a.Right, 1) - } - return posAddCol(a.Right, 2) -} - -// ArithmCmd represents an arithmetic command. -// -// This node will only appear in LangBash and LangMirBSDKorn. -type ArithmCmd struct { - Left, Right Pos - Unsigned bool // mksh's ((# expr)) - X ArithmExpr -} - -func (a *ArithmCmd) Pos() Pos { return a.Left } -func (a *ArithmCmd) End() Pos { return posAddCol(a.Right, 2) } - -// ArithmExpr represents all nodes that form arithmetic expressions. -// -// These are *BinaryArithm, *UnaryArithm, *ParenArithm, and *Word. -type ArithmExpr interface { - Node - arithmExprNode() -} - -func (*BinaryArithm) arithmExprNode() {} -func (*UnaryArithm) arithmExprNode() {} -func (*ParenArithm) arithmExprNode() {} -func (*Word) arithmExprNode() {} - -// BinaryArithm represents a binary arithmetic expression. -// -// If Op is any assign operator, X will be a word with a single *Lit whose value -// is a valid name. -// -// Ternary operators like "a ? b : c" are fit into this structure. Thus, if -// Op==Quest, Y will be a *BinaryArithm with Op==Colon. Op can only be Colon in -// that scenario. -type BinaryArithm struct { - OpPos Pos - Op BinAritOperator - X, Y ArithmExpr -} - -func (b *BinaryArithm) Pos() Pos { return b.X.Pos() } -func (b *BinaryArithm) End() Pos { return b.Y.End() } - -// UnaryArithm represents an unary arithmetic expression. The unary opearator -// may come before or after the sub-expression. -// -// If Op is Inc or Dec, X will be a word with a single *Lit whose value is a -// valid name. -type UnaryArithm struct { - OpPos Pos - Op UnAritOperator - Post bool - X ArithmExpr -} - -func (u *UnaryArithm) Pos() Pos { - if u.Post { - return u.X.Pos() - } - return u.OpPos -} - -func (u *UnaryArithm) End() Pos { - if u.Post { - return posAddCol(u.OpPos, 2) - } - return u.X.End() -} - -// ParenArithm represents an arithmetic expression within parentheses. -type ParenArithm struct { - Lparen, Rparen Pos - X ArithmExpr -} - -func (p *ParenArithm) Pos() Pos { return p.Lparen } -func (p *ParenArithm) End() Pos { return posAddCol(p.Rparen, 1) } - -// CaseClause represents a case (switch) clause. -type CaseClause struct { - Case, Esac Pos - Word *Word - Items []*CaseItem - Last []Comment -} - -func (c *CaseClause) Pos() Pos { return c.Case } -func (c *CaseClause) End() Pos { return posAddCol(c.Esac, 4) } - -// CaseItem represents a pattern list (case) within a CaseClause. -type CaseItem struct { - Op CaseOperator - OpPos Pos // unset if it was finished by "esac" - Comments []Comment - Patterns []*Word - StmtList -} - -func (c *CaseItem) Pos() Pos { return c.Patterns[0].Pos() } -func (c *CaseItem) End() Pos { - if c.OpPos.IsValid() { - return posAddCol(c.OpPos, len(c.Op.String())) - } - return c.StmtList.end() -} - -// TestClause represents a Bash extended test clause. -// -// This node will only appear in LangBash and LangMirBSDKorn. -type TestClause struct { - Left, Right Pos - X TestExpr -} - -func (t *TestClause) Pos() Pos { return t.Left } -func (t *TestClause) End() Pos { return posAddCol(t.Right, 2) } - -// TestExpr represents all nodes that form test expressions. -// -// These are *BinaryTest, *UnaryTest, *ParenTest, and *Word. -type TestExpr interface { - Node - testExprNode() -} - -func (*BinaryTest) testExprNode() {} -func (*UnaryTest) testExprNode() {} -func (*ParenTest) testExprNode() {} -func (*Word) testExprNode() {} - -// BinaryTest represents a binary test expression. -type BinaryTest struct { - OpPos Pos - Op BinTestOperator - X, Y TestExpr -} - -func (b *BinaryTest) Pos() Pos { return b.X.Pos() } -func (b *BinaryTest) End() Pos { return b.Y.End() } - -// UnaryTest represents a unary test expression. The unary opearator may come -// before or after the sub-expression. -type UnaryTest struct { - OpPos Pos - Op UnTestOperator - X TestExpr -} - -func (u *UnaryTest) Pos() Pos { return u.OpPos } -func (u *UnaryTest) End() Pos { return u.X.End() } - -// ParenTest represents a test expression within parentheses. -type ParenTest struct { - Lparen, Rparen Pos - X TestExpr -} - -func (p *ParenTest) Pos() Pos { return p.Lparen } -func (p *ParenTest) End() Pos { return posAddCol(p.Rparen, 1) } - -// DeclClause represents a Bash declare clause. -// -// This node will only appear with LangBash. -type DeclClause struct { - // Variant is one of "declare", "local", "export", "readonly", - // "typeset", or "nameref". - Variant *Lit - Opts []*Word - Assigns []*Assign -} - -func (d *DeclClause) Pos() Pos { return d.Variant.Pos() } -func (d *DeclClause) End() Pos { - if len(d.Assigns) > 0 { - return d.Assigns[len(d.Assigns)-1].End() - } - if len(d.Opts) > 0 { - return wordLastEnd(d.Opts) - } - return d.Variant.End() -} - -// ArrayExpr represents a Bash array expression. -// -// This node will only appear with LangBash. -type ArrayExpr struct { - Lparen, Rparen Pos - Elems []*ArrayElem - Last []Comment -} - -func (a *ArrayExpr) Pos() Pos { return a.Lparen } -func (a *ArrayExpr) End() Pos { return posAddCol(a.Rparen, 1) } - -// ArrayElem represents a Bash array element. -type ArrayElem struct { - Index ArithmExpr // [i]=, ["k"]= - Value *Word - Comments []Comment -} - -func (a *ArrayElem) Pos() Pos { - if a.Index != nil { - return a.Index.Pos() - } - return a.Value.Pos() -} -func (a *ArrayElem) End() Pos { return a.Value.End() } - -// ExtGlob represents a Bash extended globbing expression. Note that these are -// parsed independently of whether shopt has been called or not. -// -// This node will only appear in LangBash and LangMirBSDKorn. -type ExtGlob struct { - OpPos Pos - Op GlobOperator - Pattern *Lit -} - -func (e *ExtGlob) Pos() Pos { return e.OpPos } -func (e *ExtGlob) End() Pos { return posAddCol(e.Pattern.End(), 1) } - -// ProcSubst represents a Bash process substitution. -// -// This node will only appear with LangBash. -type ProcSubst struct { - OpPos, Rparen Pos - Op ProcOperator - StmtList -} - -func (s *ProcSubst) Pos() Pos { return s.OpPos } -func (s *ProcSubst) End() Pos { return posAddCol(s.Rparen, 1) } - -// TimeClause represents a Bash time clause. PosixFormat corresponds to the -p -// flag. -// -// This node will only appear in LangBash and LangMirBSDKorn. -type TimeClause struct { - Time Pos - PosixFormat bool - Stmt *Stmt -} - -func (c *TimeClause) Pos() Pos { return c.Time } -func (c *TimeClause) End() Pos { - if c.Stmt == nil { - return posAddCol(c.Time, 4) - } - return c.Stmt.End() -} - -// CoprocClause represents a Bash coproc clause. -// -// This node will only appear with LangBash. -type CoprocClause struct { - Coproc Pos - Name *Lit - Stmt *Stmt -} - -func (c *CoprocClause) Pos() Pos { return c.Coproc } -func (c *CoprocClause) End() Pos { return c.Stmt.End() } - -// LetClause represents a Bash let clause. -// -// This node will only appear in LangBash and LangMirBSDKorn. -type LetClause struct { - Let Pos - Exprs []ArithmExpr -} - -func (l *LetClause) Pos() Pos { return l.Let } -func (l *LetClause) End() Pos { return l.Exprs[len(l.Exprs)-1].End() } - -func wordLastEnd(ws []*Word) Pos { - if len(ws) == 0 { - return Pos{} - } - return ws[len(ws)-1].End() -} diff --git a/vendor/mvdan.cc/sh/syntax/parser.go b/vendor/mvdan.cc/sh/syntax/parser.go deleted file mode 100644 index bf54d786..00000000 --- a/vendor/mvdan.cc/sh/syntax/parser.go +++ /dev/null @@ -1,2436 +0,0 @@ -// Copyright (c) 2016, Daniel Martí -// See LICENSE for licensing information - -package syntax - -import ( - "bytes" - "fmt" - "io" - "strconv" - "strings" - "unicode/utf8" -) - -// KeepComments makes the parser parse comments and attach them to -// nodes, as opposed to discarding them. -func KeepComments(p *Parser) { p.keepComments = true } - -type LangVariant int - -const ( - LangBash LangVariant = iota - LangPOSIX - LangMirBSDKorn -) - -// Variant changes the shell language variant that the parser will -// accept. -func Variant(l LangVariant) func(*Parser) { - return func(p *Parser) { p.lang = l } -} - -func (l LangVariant) String() string { - switch l { - case LangBash: - return "bash" - case LangPOSIX: - return "posix" - case LangMirBSDKorn: - return "mksh" - } - return "unknown shell language variant" -} - -// StopAt configures the lexer to stop at an arbitrary word, treating it -// as if it were the end of the input. It can contain any characters -// except whitespace, and cannot be over four bytes in size. -// -// This can be useful to embed shell code within another language, as -// one can use a special word to mark the delimiters between the two. -// -// As a word, it will only apply when following whitespace or a -// separating token. For example, StopAt("$$") will act on the inputs -// "foo $$" and "foo;$$", but not on "foo '$$'". -// -// The match is done by prefix, so the example above will also act on -// "foo $$bar". -func StopAt(word string) func(*Parser) { - if len(word) > 4 { - panic("stop word can't be over four bytes in size") - } - if strings.ContainsAny(word, " \t\n\r") { - panic("stop word can't contain whitespace characters") - } - return func(p *Parser) { p.stopAt = []byte(word) } -} - -// NewParser allocates a new Parser and applies any number of options. -func NewParser(options ...func(*Parser)) *Parser { - p := &Parser{helperBuf: new(bytes.Buffer)} - for _, opt := range options { - opt(p) - } - return p -} - -// Parse reads and parses a shell program with an optional name. It -// returns the parsed program if no issues were encountered. Otherwise, -// an error is returned. Reads from r are buffered. -// -// Parse can be called more than once, but not concurrently. That is, a -// Parser can be reused once it is done working. -func (p *Parser) Parse(r io.Reader, name string) (*File, error) { - p.reset() - p.f = &File{Name: name} - p.src = r - p.rune() - p.next() - p.f.StmtList = p.stmtList() - if p.err == nil { - // EOF immediately after heredoc word so no newline to - // trigger it - p.doHeredocs() - } - return p.f, p.err -} - -// Stmts reads and parses statements one at a time, calling a function -// each time one is parsed. If the function returns false, parsing is -// stopped and the function is not called again. -func (p *Parser) Stmts(r io.Reader, fn func(*Stmt) bool) error { - p.reset() - p.f = &File{} - p.src = r - p.rune() - p.next() - p.stmts(fn) - if p.err == nil { - // EOF immediately after heredoc word so no newline to - // trigger it - p.doHeredocs() - } - return p.err -} - -type wrappedReader struct { - *Parser - io.Reader - - lastLine uint16 - accumulated []*Stmt - fn func([]*Stmt) bool -} - -func (w *wrappedReader) Read(p []byte) (n int, err error) { - // If we lexed a newline for the first time, we just finished a line, so - // we may need to give a callback for the edge cases below not covered - // by Parser.Stmts. - if w.r == '\n' && w.npos.line > w.lastLine { - if w.Incomplete() { - // Incomplete statement; call back to print "> ". - if !w.fn(w.accumulated) { - return 0, io.EOF - } - } else if len(w.accumulated) == 0 { - // Nothing was parsed; call back to print another "$ ". - if !w.fn(nil) { - return 0, io.EOF - } - } - w.lastLine = w.npos.line - } - return w.Reader.Read(p) -} - -// Interactive implements what is necessary to parse statements in an -// interactive shell. The parser will call the given function under two -// circumstances outlined below. -// -// If a line containing any number of statements is parsed, the function will be -// called with said statements. -// -// If a line ending in an incomplete statement is parsed, the function will be -// called with any fully parsed statents, and Parser.Incomplete will return -// true. -// -// One can imagine a simple interactive shell implementation as follows: -// -// fmt.Fprintf(os.Stdout, "$ ") -// parser.Interactive(os.Stdin, func(stmts []*syntax.Stmt) bool { -// if parser.Incomplete() { -// fmt.Fprintf(os.Stdout, "> ") -// return true -// } -// run(stmts) -// fmt.Fprintf(os.Stdout, "$ ") -// return true -// } -// -// If the callback function returns false, parsing is stopped and the function -// is not called again. -func (p *Parser) Interactive(r io.Reader, fn func([]*Stmt) bool) error { - w := wrappedReader{Parser: p, Reader: r, fn: fn} - return p.Stmts(&w, func(stmt *Stmt) bool { - w.accumulated = append(w.accumulated, stmt) - // We finished parsing a statement and we're at a newline token, - // so we finished fully parsing a number of statements. Call - // back to run the statements and print "$ ". - if p.tok == _Newl { - if !fn(w.accumulated) { - return false - } - w.accumulated = w.accumulated[:0] - // The callback above would already print "$ ", so we - // don't want the subsequent wrappedReader.Read to cause - // another "$ " print thinking that nothing was parsed. - w.lastLine = w.npos.line + 1 - } - return true - }) -} - -// Words reads and parses words one at a time, calling a function each time one -// is parsed. If the function returns false, parsing is stopped and the function -// is not called again. -// -// Newlines are skipped, meaning that multi-line input will work fine. If the -// parser encounters a token that isn't a word, such as a semicolon, an error -// will be returned. -// -// Note that the lexer doesn't currently tokenize spaces, so it may need to read -// a non-space byte such as a newline or a letter before finishing the parsing -// of a word. This will be fixed in the future. -func (p *Parser) Words(r io.Reader, fn func(*Word) bool) error { - p.reset() - p.f = &File{} - p.src = r - p.rune() - p.next() - for { - p.got(_Newl) - w := p.getWord() - if w == nil { - if p.tok != _EOF { - p.curErr("%s is not a valid word", p.tok) - } - return p.err - } - if !fn(w) { - return nil - } - } -} - -// Document parses a single here-document word. That is, it parses the input as -// if they were lines following a < 0 || p.litBs != nil -} - -const bufSize = 1 << 10 - -func (p *Parser) reset() { - p.tok, p.val = illegalTok, "" - p.eqlOffs = 0 - p.bs, p.bsp = nil, 0 - p.offs = 0 - p.npos = Pos{line: 1, col: 1} - p.r, p.w = 0, 0 - p.err, p.readErr = nil, nil - p.quote, p.forbidNested = noState, false - p.openStmts = 0 - p.heredocs, p.buriedHdocs = p.heredocs[:0], 0 - p.parsingDoc = false - p.openBquotes, p.buriedBquotes = 0, 0 - p.accComs, p.curComs = nil, &p.accComs -} - -func (p *Parser) getPos() Pos { - p.npos.offs = uint32(p.offs + p.bsp - int(p.w)) - return p.npos -} - -func (p *Parser) lit(pos Pos, val string) *Lit { - if len(p.litBatch) == 0 { - p.litBatch = make([]Lit, 128) - } - l := &p.litBatch[0] - p.litBatch = p.litBatch[1:] - l.ValuePos = pos - l.ValueEnd = p.getPos() - l.Value = val - return l -} - -func (p *Parser) word(parts []WordPart) *Word { - if len(p.wordBatch) == 0 { - p.wordBatch = make([]Word, 64) - } - w := &p.wordBatch[0] - p.wordBatch = p.wordBatch[1:] - w.Parts = parts - return w -} - -func (p *Parser) wps(wp WordPart) []WordPart { - if len(p.wpsBatch) == 0 { - p.wpsBatch = make([]WordPart, 64) - } - wps := p.wpsBatch[:1:1] - p.wpsBatch = p.wpsBatch[1:] - wps[0] = wp - return wps -} - -func (p *Parser) stmt(pos Pos) *Stmt { - if len(p.stmtBatch) == 0 { - p.stmtBatch = make([]Stmt, 64) - } - s := &p.stmtBatch[0] - p.stmtBatch = p.stmtBatch[1:] - s.Position = pos - return s -} - -func (p *Parser) stList() []*Stmt { - if len(p.stListBatch) == 0 { - p.stListBatch = make([]*Stmt, 256) - } - stmts := p.stListBatch[:0:4] - p.stListBatch = p.stListBatch[4:] - return stmts -} - -type callAlloc struct { - ce CallExpr - ws [4]*Word -} - -func (p *Parser) call(w *Word) *CallExpr { - if len(p.callBatch) == 0 { - p.callBatch = make([]callAlloc, 32) - } - alloc := &p.callBatch[0] - p.callBatch = p.callBatch[1:] - ce := &alloc.ce - ce.Args = alloc.ws[:1] - ce.Args[0] = w - return ce -} - -//go:generate stringer -type=quoteState - -type quoteState uint32 - -const ( - noState quoteState = 1 << iota - subCmd - subCmdBckquo - dblQuotes - hdocWord - hdocBody - hdocBodyTabs - arithmExpr - arithmExprLet - arithmExprCmd - arithmExprBrack - testRegexp - switchCase - paramExpName - paramExpSlice - paramExpRepl - paramExpExp - arrayElems - - allKeepSpaces = paramExpRepl | dblQuotes | hdocBody | - hdocBodyTabs | paramExpExp - allRegTokens = noState | subCmd | subCmdBckquo | hdocWord | - switchCase | arrayElems - allArithmExpr = arithmExpr | arithmExprLet | arithmExprCmd | - arithmExprBrack | paramExpSlice - allParamReg = paramExpName | paramExpSlice - allParamExp = allParamReg | paramExpRepl | paramExpExp | arithmExprBrack -) - -type saveState struct { - quote quoteState - buriedHdocs int -} - -func (p *Parser) preNested(quote quoteState) (s saveState) { - s.quote, s.buriedHdocs = p.quote, p.buriedHdocs - p.buriedHdocs, p.quote = len(p.heredocs), quote - return -} - -func (p *Parser) postNested(s saveState) { - p.quote, p.buriedHdocs = s.quote, s.buriedHdocs -} - -func (p *Parser) unquotedWordBytes(w *Word) ([]byte, bool) { - p.helperBuf.Reset() - didUnquote := false - for _, wp := range w.Parts { - if p.unquotedWordPart(p.helperBuf, wp, false) { - didUnquote = true - } - } - return p.helperBuf.Bytes(), didUnquote -} - -func (p *Parser) unquotedWordPart(buf *bytes.Buffer, wp WordPart, quotes bool) (quoted bool) { - switch x := wp.(type) { - case *Lit: - for i := 0; i < len(x.Value); i++ { - if b := x.Value[i]; b == '\\' && !quotes { - if i++; i < len(x.Value) { - buf.WriteByte(x.Value[i]) - } - quoted = true - } else { - buf.WriteByte(b) - } - } - case *SglQuoted: - buf.WriteString(x.Value) - quoted = true - case *DblQuoted: - for _, wp2 := range x.Parts { - p.unquotedWordPart(buf, wp2, true) - } - quoted = true - } - return -} - -func (p *Parser) doHeredocs() { - p.rune() // consume '\n', since we know p.tok == _Newl - old := p.quote - hdocs := p.heredocs[p.buriedHdocs:] - p.heredocs = p.heredocs[:p.buriedHdocs] - for i, r := range hdocs { - if p.err != nil { - break - } - p.quote = hdocBody - if r.Op == DashHdoc { - p.quote = hdocBodyTabs - } - var quoted bool - p.hdocStop, quoted = p.unquotedWordBytes(r.Word) - if i > 0 && p.r == '\n' { - p.rune() - } - if quoted { - r.Hdoc = p.quotedHdocWord() - } else { - p.next() - r.Hdoc = p.getWord() - } - if p.hdocStop != nil { - p.posErr(r.Pos(), "unclosed here-document '%s'", - string(p.hdocStop)) - } - } - p.quote = old -} - -func (p *Parser) got(tok token) bool { - if p.tok == tok { - p.next() - return true - } - return false -} - -func (p *Parser) gotRsrv(val string) (Pos, bool) { - pos := p.pos - if p.tok == _LitWord && p.val == val { - p.next() - return pos, true - } - return pos, false -} - -func readableStr(s string) string { - // don't quote tokens like & or } - if s != "" && s[0] >= 'a' && s[0] <= 'z' { - return strconv.Quote(s) - } - return s -} - -func (p *Parser) followErr(pos Pos, left, right string) { - leftStr := readableStr(left) - p.posErr(pos, "%s must be followed by %s", leftStr, right) -} - -func (p *Parser) followErrExp(pos Pos, left string) { - p.followErr(pos, left, "an expression") -} - -func (p *Parser) follow(lpos Pos, left string, tok token) { - if !p.got(tok) { - p.followErr(lpos, left, tok.String()) - } -} - -func (p *Parser) followRsrv(lpos Pos, left, val string) Pos { - pos, ok := p.gotRsrv(val) - if !ok { - p.followErr(lpos, left, fmt.Sprintf("%q", val)) - } - return pos -} - -func (p *Parser) followStmts(left string, lpos Pos, stops ...string) StmtList { - if p.got(semicolon) { - return StmtList{} - } - newLine := p.got(_Newl) - sl := p.stmtList(stops...) - if len(sl.Stmts) < 1 && !newLine { - p.followErr(lpos, left, "a statement list") - } - return sl -} - -func (p *Parser) followWordTok(tok token, pos Pos) *Word { - w := p.getWord() - if w == nil { - p.followErr(pos, tok.String(), "a word") - } - return w -} - -func (p *Parser) followWord(s string, pos Pos) *Word { - w := p.getWord() - if w == nil { - p.followErr(pos, s, "a word") - } - return w -} - -func (p *Parser) stmtEnd(n Node, start, end string) Pos { - pos, ok := p.gotRsrv(end) - if !ok { - p.posErr(n.Pos(), "%s statement must end with %q", start, end) - } - return pos -} - -func (p *Parser) quoteErr(lpos Pos, quote token) { - p.posErr(lpos, "reached %s without closing quote %s", - p.tok.String(), quote) -} - -func (p *Parser) matchingErr(lpos Pos, left, right interface{}) { - p.posErr(lpos, "reached %s without matching %s with %s", - p.tok.String(), left, right) -} - -func (p *Parser) matched(lpos Pos, left, right token) Pos { - pos := p.pos - if !p.got(right) { - p.matchingErr(lpos, left, right) - } - return pos -} - -func (p *Parser) errPass(err error) { - if p.err == nil { - p.err = err - p.bsp = len(p.bs) + 1 - p.r = utf8.RuneSelf - p.w = 1 - p.tok = _EOF - } -} - -// ParseError represents an error found when parsing a source file, from which -// the parser cannot recover. -type ParseError struct { - Filename string - Pos - Text string -} - -func (e ParseError) Error() string { - if e.Filename == "" { - return fmt.Sprintf("%s: %s", e.Pos.String(), e.Text) - } - return fmt.Sprintf("%s:%s: %s", e.Filename, e.Pos.String(), e.Text) -} - -// LangError is returned when the parser encounters code that is only valid in -// other shell language variants. The error includes what feature is not present -// in the current language variant, and what languages support it. -type LangError struct { - Filename string - Pos - Feature string - Langs []LangVariant -} - -func (e LangError) Error() string { - var buf bytes.Buffer - if e.Filename != "" { - buf.WriteString(e.Filename + ":") - } - buf.WriteString(e.Pos.String() + ": ") - buf.WriteString(e.Feature) - if strings.HasSuffix(e.Feature, "s") { - buf.WriteString(" are a ") - } else { - buf.WriteString(" is a ") - } - for i, lang := range e.Langs { - if i > 0 { - buf.WriteString("/") - } - buf.WriteString(lang.String()) - } - buf.WriteString(" feature") - return buf.String() -} - -func (p *Parser) posErr(pos Pos, format string, a ...interface{}) { - p.errPass(ParseError{ - Filename: p.f.Name, - Pos: pos, - Text: fmt.Sprintf(format, a...), - }) -} - -func (p *Parser) curErr(format string, a ...interface{}) { - p.posErr(p.pos, format, a...) -} - -func (p *Parser) langErr(pos Pos, feature string, langs ...LangVariant) { - p.errPass(LangError{ - Filename: p.f.Name, - Pos: pos, - Feature: feature, - Langs: langs, - }) -} - -func (p *Parser) stmts(fn func(*Stmt) bool, stops ...string) { - gotEnd := true -loop: - for p.tok != _EOF { - newLine := p.got(_Newl) - switch p.tok { - case _LitWord: - for _, stop := range stops { - if p.val == stop { - break loop - } - } - case rightParen: - if p.quote == subCmd { - break loop - } - case bckQuote: - if p.backquoteEnd() { - break loop - } - case dblSemicolon, semiAnd, dblSemiAnd, semiOr: - if p.quote == switchCase { - break loop - } - p.curErr("%s can only be used in a case clause", p.tok) - } - if !newLine && !gotEnd { - p.curErr("statements must be separated by &, ; or a newline") - } - if p.tok == _EOF { - break - } - p.openStmts++ - s := p.getStmt(true, false, false) - p.openStmts-- - if s == nil { - p.invalidStmtStart() - break - } - gotEnd = s.Semicolon.IsValid() - if !fn(s) { - break - } - } -} - -func (p *Parser) stmtList(stops ...string) (sl StmtList) { - fn := func(s *Stmt) bool { - if sl.Stmts == nil { - sl.Stmts = p.stList() - } - sl.Stmts = append(sl.Stmts, s) - return true - } - p.stmts(fn, stops...) - split := len(p.accComs) - if p.tok == _LitWord && (p.val == "elif" || p.val == "else" || p.val == "fi") { - // Split the comments, so that any aligned with an opening token - // get attached to it. For example: - // - // if foo; then - // # inside the body - // # document the else - // else - // fi - // TODO(mvdan): look into deduplicating this with similar logic - // in caseItems. - for i := len(p.accComs) - 1; i >= 0; i-- { - c := p.accComs[i] - if c.Pos().Col() != p.pos.Col() { - break - } - split = i - } - } - sl.Last = p.accComs[:split] - p.accComs = p.accComs[split:] - return -} - -func (p *Parser) invalidStmtStart() { - switch p.tok { - case semicolon, and, or, andAnd, orOr: - p.curErr("%s can only immediately follow a statement", p.tok) - case rightParen: - p.curErr("%s can only be used to close a subshell", p.tok) - default: - p.curErr("%s is not a valid start for a statement", p.tok) - } -} - -func (p *Parser) getWord() *Word { - if parts := p.wordParts(); len(parts) > 0 { - return p.word(parts) - } - return nil -} - -func (p *Parser) getLit() *Lit { - switch p.tok { - case _Lit, _LitWord, _LitRedir: - l := p.lit(p.pos, p.val) - p.next() - return l - } - return nil -} - -func (p *Parser) wordParts() (wps []WordPart) { - for { - n := p.wordPart() - if n == nil { - return - } - if wps == nil { - wps = p.wps(n) - } else { - wps = append(wps, n) - } - if p.spaced { - return - } - } -} - -func (p *Parser) ensureNoNested() { - if p.forbidNested { - p.curErr("expansions not allowed in heredoc words") - } -} - -func (p *Parser) wordPart() WordPart { - switch p.tok { - case _Lit, _LitWord: - l := p.lit(p.pos, p.val) - p.next() - return l - case dollBrace: - p.ensureNoNested() - switch p.r { - case '|': - if p.lang != LangMirBSDKorn { - p.curErr(`"${|stmts;}" is a mksh feature`) - } - fallthrough - case ' ', '\t', '\n': - if p.lang != LangMirBSDKorn { - p.curErr(`"${ stmts;}" is a mksh feature`) - } - cs := &CmdSubst{ - Left: p.pos, - TempFile: p.r != '|', - ReplyVar: p.r == '|', - } - old := p.preNested(subCmd) - p.rune() // don't tokenize '|' - p.next() - cs.StmtList = p.stmtList("}") - p.postNested(old) - pos, ok := p.gotRsrv("}") - if !ok { - p.matchingErr(cs.Left, "${", "}") - } - cs.Right = pos - return cs - default: - return p.paramExp() - } - case dollDblParen, dollBrack: - p.ensureNoNested() - left := p.tok - ar := &ArithmExp{Left: p.pos, Bracket: left == dollBrack} - var old saveState - if ar.Bracket { - old = p.preNested(arithmExprBrack) - } else { - old = p.preNested(arithmExpr) - } - p.next() - if p.got(hash) { - if p.lang != LangMirBSDKorn { - p.langErr(ar.Pos(), "unsigned expressions", LangMirBSDKorn) - } - ar.Unsigned = true - } - ar.X = p.followArithm(left, ar.Left) - if ar.Bracket { - if p.tok != rightBrack { - p.matchingErr(ar.Left, dollBrack, rightBrack) - } - p.postNested(old) - ar.Right = p.pos - p.next() - } else { - ar.Right = p.arithmEnd(dollDblParen, ar.Left, old) - } - return ar - case dollParen: - p.ensureNoNested() - cs := &CmdSubst{Left: p.pos} - old := p.preNested(subCmd) - p.next() - cs.StmtList = p.stmtList() - p.postNested(old) - cs.Right = p.matched(cs.Left, leftParen, rightParen) - return cs - case dollar: - r := p.r - switch { - case singleRuneParam(r): - p.tok, p.val = _LitWord, string(r) - p.rune() - case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', - '0' <= r && r <= '9', r == '_', r == '\\': - p.advanceNameCont(r) - default: - l := p.lit(p.pos, "$") - p.next() - return l - } - p.ensureNoNested() - pe := &ParamExp{Dollar: p.pos, Short: true} - p.pos = posAddCol(p.pos, 1) - pe.Param = p.getLit() - if pe.Param != nil && pe.Param.Value == "" { - l := p.lit(pe.Dollar, "$") - if p.val == "" { - // e.g. "$\\\n" followed by a closing double - // quote, so we need the next token. - p.next() - } else { - // e.g. "$\\\"" within double quotes, so we must - // keep the rest of the literal characters. - l.ValueEnd = posAddCol(l.ValuePos, 1) - } - return l - } - return pe - case cmdIn, cmdOut: - p.ensureNoNested() - ps := &ProcSubst{Op: ProcOperator(p.tok), OpPos: p.pos} - old := p.preNested(subCmd) - p.next() - ps.StmtList = p.stmtList() - p.postNested(old) - ps.Rparen = p.matched(ps.OpPos, token(ps.Op), rightParen) - return ps - case sglQuote, dollSglQuote: - sq := &SglQuoted{Left: p.pos, Dollar: p.tok == dollSglQuote} - r := p.r - for p.newLit(r); ; r = p.rune() { - switch r { - case '\\': - if sq.Dollar { - p.rune() - } - case '\'': - sq.Right = p.getPos() - sq.Value = p.endLit() - - // restore openBquotes - p.openBquotes = p.buriedBquotes - p.buriedBquotes = 0 - - p.rune() - p.next() - return sq - case utf8.RuneSelf: - p.posErr(sq.Pos(), "reached EOF without closing quote %s", sglQuote) - return nil - } - } - case dblQuote, dollDblQuote: - if p.quote == dblQuotes { - // p.tok == dblQuote, as "foo$" puts $ in the lit - return nil - } - return p.dblQuoted() - case bckQuote: - if p.backquoteEnd() { - return nil - } - p.ensureNoNested() - cs := &CmdSubst{Left: p.pos} - old := p.preNested(subCmdBckquo) - p.openBquotes++ - - // The lexer didn't call p.rune for us, so that it could have - // the right p.openBquotes to properly handle backslashes. - p.rune() - - p.next() - cs.StmtList = p.stmtList() - p.postNested(old) - p.openBquotes-- - cs.Right = p.pos - - // Like above, the lexer didn't call p.rune for us. - p.rune() - if !p.got(bckQuote) { - p.quoteErr(cs.Pos(), bckQuote) - } - return cs - case globQuest, globStar, globPlus, globAt, globExcl: - if p.lang == LangPOSIX { - p.langErr(p.pos, "extended globs", LangBash, LangMirBSDKorn) - } - eg := &ExtGlob{Op: GlobOperator(p.tok), OpPos: p.pos} - lparens := 1 - r := p.r - globLoop: - for p.newLit(r); ; r = p.rune() { - switch r { - case utf8.RuneSelf: - break globLoop - case '(': - lparens++ - case ')': - if lparens--; lparens == 0 { - break globLoop - } - } - } - eg.Pattern = p.lit(posAddCol(eg.OpPos, 2), p.endLit()) - p.rune() - p.next() - if lparens != 0 { - p.matchingErr(eg.OpPos, eg.Op, rightParen) - } - return eg - default: - return nil - } -} - -func (p *Parser) dblQuoted() *DblQuoted { - q := &DblQuoted{Position: p.pos, Dollar: p.tok == dollDblQuote} - old := p.quote - p.quote = dblQuotes - p.next() - q.Parts = p.wordParts() - p.quote = old - if !p.got(dblQuote) { - p.quoteErr(q.Pos(), dblQuote) - } - return q -} - -func arithmOpLevel(op BinAritOperator) int { - switch op { - case Comma: - return 0 - case AddAssgn, SubAssgn, MulAssgn, QuoAssgn, RemAssgn, AndAssgn, - OrAssgn, XorAssgn, ShlAssgn, ShrAssgn: - return 1 - case Assgn: - return 2 - case Quest, Colon: - return 3 - case AndArit, OrArit: - return 4 - case And, Or, Xor: - return 5 - case Eql, Neq: - return 6 - case Lss, Gtr, Leq, Geq: - return 7 - case Shl, Shr: - return 8 - case Add, Sub: - return 9 - case Mul, Quo, Rem: - return 10 - case Pow: - return 11 - } - return -1 -} - -func (p *Parser) followArithm(ftok token, fpos Pos) ArithmExpr { - x := p.arithmExpr(0, false, false) - if x == nil { - p.followErrExp(fpos, ftok.String()) - } - return x -} - -func (p *Parser) arithmExpr(level int, compact, tern bool) ArithmExpr { - if p.tok == _EOF || p.peekArithmEnd() { - return nil - } - var left ArithmExpr - if level > 11 { - left = p.arithmExprBase(compact) - } else { - left = p.arithmExpr(level+1, compact, false) - } - if compact && p.spaced { - return left - } - p.got(_Newl) - newLevel := arithmOpLevel(BinAritOperator(p.tok)) - if !tern && p.tok == colon && p.quote == paramExpSlice { - newLevel = -1 - } - if newLevel < 0 { - switch p.tok { - case _Lit, _LitWord: - p.curErr("not a valid arithmetic operator: %s", p.val) - return nil - case leftBrack: - p.curErr("[ must follow a name") - return nil - case rightParen, _EOF: - default: - if p.quote == arithmExpr { - p.curErr("not a valid arithmetic operator: %v", p.tok) - return nil - } - } - } - if newLevel < level { - return left - } - if left == nil { - p.curErr("%s must follow an expression", p.tok.String()) - return nil - } - b := &BinaryArithm{ - OpPos: p.pos, - Op: BinAritOperator(p.tok), - X: left, - } - switch b.Op { - case Colon: - if !tern { - p.posErr(b.Pos(), "ternary operator missing ? before :") - } - case AddAssgn, SubAssgn, MulAssgn, QuoAssgn, RemAssgn, AndAssgn, - OrAssgn, XorAssgn, ShlAssgn, ShrAssgn, Assgn: - if !isArithName(b.X) { - p.posErr(b.OpPos, "%s must follow a name", b.Op.String()) - } - } - if p.next(); compact && p.spaced { - p.followErrExp(b.OpPos, b.Op.String()) - } - b.Y = p.arithmExpr(newLevel, compact, b.Op == Quest) - if b.Y == nil { - p.followErrExp(b.OpPos, b.Op.String()) - } - if b.Op == Quest { - if b2, ok := b.Y.(*BinaryArithm); !ok || b2.Op != Colon { - p.posErr(b.Pos(), "ternary operator missing : after ?") - } - } - return b -} - -func isArithName(left ArithmExpr) bool { - w, ok := left.(*Word) - if !ok || len(w.Parts) != 1 { - return false - } - switch x := w.Parts[0].(type) { - case *Lit: - return ValidName(x.Value) - case *ParamExp: - return x.nakedIndex() - default: - return false - } -} - -func (p *Parser) arithmExprBase(compact bool) ArithmExpr { - p.got(_Newl) - var x ArithmExpr - switch p.tok { - case exclMark: - ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)} - p.next() - if ue.X = p.arithmExprBase(compact); ue.X == nil { - p.followErrExp(ue.OpPos, ue.Op.String()) - } - return ue - case addAdd, subSub: - ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)} - p.next() - if p.tok != _LitWord { - p.followErr(ue.OpPos, token(ue.Op).String(), "a literal") - } - ue.X = p.arithmExprBase(compact) - return ue - case leftParen: - pe := &ParenArithm{Lparen: p.pos} - p.next() - pe.X = p.followArithm(leftParen, pe.Lparen) - pe.Rparen = p.matched(pe.Lparen, leftParen, rightParen) - x = pe - case plus, minus: - ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)} - if p.next(); compact && p.spaced { - p.followErrExp(ue.OpPos, ue.Op.String()) - } - ue.X = p.arithmExprBase(compact) - if ue.X == nil { - p.followErrExp(ue.OpPos, ue.Op.String()) - } - x = ue - case _LitWord: - l := p.getLit() - if p.tok != leftBrack { - x = p.word(p.wps(l)) - break - } - pe := &ParamExp{Dollar: l.ValuePos, Short: true, Param: l} - pe.Index = p.eitherIndex() - x = p.word(p.wps(pe)) - case bckQuote: - if p.quote == arithmExprLet && p.openBquotes > 0 { - return nil - } - fallthrough - default: - if w := p.getWord(); w != nil { - // we want real nil, not (*Word)(nil) as that - // sets the type to non-nil and then x != nil - x = w - } - } - if compact && p.spaced { - return x - } - if p.tok == addAdd || p.tok == subSub { - if !isArithName(x) { - p.curErr("%s must follow a name", p.tok.String()) - } - u := &UnaryArithm{ - Post: true, - OpPos: p.pos, - Op: UnAritOperator(p.tok), - X: x, - } - p.next() - return u - } - return x -} - -func singleRuneParam(r rune) bool { - switch r { - case '@', '*', '#', '$', '?', '!', '-', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - return true - } - return false -} - -func (p *Parser) paramExp() *ParamExp { - pe := &ParamExp{Dollar: p.pos} - old := p.quote - p.quote = paramExpName - if p.r == '#' { - p.tok = hash - p.pos = p.getPos() - p.rune() - } else { - p.next() - } - switch p.tok { - case hash: - if paramNameOp(p.r) { - pe.Length = true - p.next() - } - case perc: - if p.lang != LangMirBSDKorn { - p.posErr(pe.Pos(), `"${%%foo}" is a mksh feature`) - } - if paramNameOp(p.r) { - pe.Width = true - p.next() - } - case exclMark: - if paramNameOp(p.r) { - if p.lang == LangPOSIX { - p.langErr(p.pos, "${!foo}", LangBash, LangMirBSDKorn) - } - pe.Excl = true - p.next() - } - } - op := p.tok - switch p.tok { - case _Lit, _LitWord: - if !numberLiteral(p.val) && !ValidName(p.val) { - p.curErr("invalid parameter name") - } - pe.Param = p.lit(p.pos, p.val) - p.next() - case quest, minus: - if pe.Length && p.r != '}' { - // actually ${#-default}, not ${#-}; error below - pe.Length = false - pe.Param = p.lit(p.pos, "#") - break - } - fallthrough - case at, star, hash, exclMark, dollar: - pe.Param = p.lit(p.pos, p.tok.String()) - p.next() - default: - p.curErr("parameter expansion requires a literal") - } - switch p.tok { - case _Lit, _LitWord: - p.curErr("%s cannot be followed by a word", op) - case rightBrace: - pe.Rbrace = p.pos - p.quote = old - p.next() - return pe - case leftBrack: - if p.lang == LangPOSIX { - p.langErr(p.pos, "arrays", LangBash, LangMirBSDKorn) - } - if !ValidName(pe.Param.Value) { - p.curErr("cannot index a special parameter name") - } - pe.Index = p.eitherIndex() - } - if p.tok == rightBrace { - pe.Rbrace = p.pos - p.quote = old - p.next() - return pe - } - if p.tok != _EOF && (pe.Length || pe.Width) { - p.curErr("cannot combine multiple parameter expansion operators") - } - switch p.tok { - case slash, dblSlash: - // pattern search and replace - if p.lang == LangPOSIX { - p.langErr(p.pos, "search and replace", LangBash, LangMirBSDKorn) - } - pe.Repl = &Replace{All: p.tok == dblSlash} - p.quote = paramExpRepl - p.next() - pe.Repl.Orig = p.getWord() - p.quote = paramExpExp - if p.got(slash) { - pe.Repl.With = p.getWord() - } - case colon: - // slicing - if p.lang == LangPOSIX { - p.langErr(p.pos, "slicing", LangBash, LangMirBSDKorn) - } - pe.Slice = &Slice{} - colonPos := p.pos - p.quote = paramExpSlice - if p.next(); p.tok != colon { - pe.Slice.Offset = p.followArithm(colon, colonPos) - } - colonPos = p.pos - if p.got(colon) { - pe.Slice.Length = p.followArithm(colon, colonPos) - } - case caret, dblCaret, comma, dblComma: - // upper/lower case - if p.lang != LangBash { - p.langErr(p.pos, "this expansion operator", LangBash) - } - pe.Exp = p.paramExpExp() - case at, star: - switch { - case p.tok == at && p.lang == LangPOSIX: - p.langErr(p.pos, "this expansion operator", LangBash, LangMirBSDKorn) - case p.tok == star && !pe.Excl: - p.curErr("not a valid parameter expansion operator: %v", p.tok) - case pe.Excl: - pe.Names = ParNamesOperator(p.tok) - p.next() - default: - pe.Exp = p.paramExpExp() - } - case plus, colPlus, minus, colMinus, quest, colQuest, assgn, colAssgn, - perc, dblPerc, hash, dblHash: - pe.Exp = p.paramExpExp() - case _EOF: - default: - p.curErr("not a valid parameter expansion operator: %v", p.tok) - } - p.quote = old - pe.Rbrace = p.pos - p.matched(pe.Dollar, dollBrace, rightBrace) - return pe -} - -func (p *Parser) paramExpExp() *Expansion { - op := ParExpOperator(p.tok) - p.quote = paramExpExp - p.next() - if op == OtherParamOps { - switch p.tok { - case _Lit, _LitWord: - default: - p.curErr("@ expansion operator requires a literal") - } - switch p.val { - case "Q", "E", "P", "A", "a": - default: - p.curErr("invalid @ expansion operator") - } - } - return &Expansion{Op: op, Word: p.getWord()} -} - -func (p *Parser) eitherIndex() ArithmExpr { - old := p.quote - lpos := p.pos - p.quote = arithmExprBrack - p.next() - if p.tok == star || p.tok == at { - p.tok, p.val = _LitWord, p.tok.String() - } - expr := p.followArithm(leftBrack, lpos) - p.quote = old - p.matched(lpos, leftBrack, rightBrack) - return expr -} - -func (p *Parser) peekArithmEnd() bool { - return p.tok == rightParen && p.r == ')' -} - -func (p *Parser) arithmEnd(ltok token, lpos Pos, old saveState) Pos { - if !p.peekArithmEnd() { - p.matchingErr(lpos, ltok, dblRightParen) - } - p.rune() - p.postNested(old) - pos := p.pos - p.next() - return pos -} - -func stopToken(tok token) bool { - switch tok { - case _EOF, _Newl, semicolon, and, or, andAnd, orOr, orAnd, dblSemicolon, - semiAnd, dblSemiAnd, semiOr, rightParen: - return true - } - return false -} - -func (p *Parser) backquoteEnd() bool { - return p.quote == subCmdBckquo && p.lastBquoteEsc < p.openBquotes -} - -// ValidName returns whether val is a valid name as per the POSIX spec. -func ValidName(val string) bool { - if val == "" { - return false - } - for i, r := range val { - switch { - case 'a' <= r && r <= 'z': - case 'A' <= r && r <= 'Z': - case r == '_': - case i > 0 && '0' <= r && r <= '9': - default: - return false - } - } - return true -} - -func numberLiteral(val string) bool { - for _, r := range val { - if '0' > r || r > '9' { - return false - } - } - return true -} - -func (p *Parser) hasValidIdent() bool { - if p.tok != _Lit && p.tok != _LitWord { - return false - } - if end := p.eqlOffs; end > 0 { - if p.val[end-1] == '+' && p.lang != LangPOSIX { - end-- - } - if ValidName(p.val[:end]) { - return true - } - } - return p.r == '[' -} - -func (p *Parser) getAssign(needEqual bool) *Assign { - as := &Assign{} - if p.eqlOffs > 0 { // foo=bar - nameEnd := p.eqlOffs - if p.lang != LangPOSIX && p.val[p.eqlOffs-1] == '+' { - // a+=b - as.Append = true - nameEnd-- - } - as.Name = p.lit(p.pos, p.val[:nameEnd]) - // since we're not using the entire p.val - as.Name.ValueEnd = posAddCol(as.Name.ValuePos, nameEnd) - left := p.lit(posAddCol(p.pos, 1), p.val[p.eqlOffs+1:]) - if left.Value != "" { - left.ValuePos = posAddCol(left.ValuePos, p.eqlOffs) - as.Value = p.word(p.wps(left)) - } - p.next() - } else { // foo[x]=bar - as.Name = p.lit(p.pos, p.val) - // hasValidIdent already checks p.r is '[' - p.rune() - p.pos = posAddCol(p.pos, 1) - as.Index = p.eitherIndex() - if !needEqual && (p.spaced || stopToken(p.tok)) { - as.Naked = true - return as - } - if len(p.val) > 0 && p.val[0] == '+' { - as.Append = true - p.val = p.val[1:] - p.pos = posAddCol(p.pos, 1) - } - if len(p.val) < 1 || p.val[0] != '=' { - if as.Append { - p.followErr(as.Pos(), "a[b]+", "=") - } else { - p.followErr(as.Pos(), "a[b]", "=") - } - return nil - } - p.pos = posAddCol(p.pos, 1) - p.val = p.val[1:] - if p.val == "" { - p.next() - } - } - if p.spaced || stopToken(p.tok) { - return as - } - if as.Value == nil && p.tok == leftParen { - if p.lang == LangPOSIX { - p.langErr(p.pos, "arrays", LangBash, LangMirBSDKorn) - } - if as.Index != nil { - p.curErr("arrays cannot be nested") - } - as.Array = &ArrayExpr{Lparen: p.pos} - newQuote := p.quote - if p.lang == LangBash { - newQuote = arrayElems - } - old := p.preNested(newQuote) - p.next() - p.got(_Newl) - for p.tok != _EOF && p.tok != rightParen { - ae := &ArrayElem{} - ae.Comments, p.accComs = p.accComs, nil - if p.tok == leftBrack { - left := p.pos - ae.Index = p.eitherIndex() - p.follow(left, `"[x]"`, assgn) - } - if ae.Value = p.getWord(); ae.Value == nil { - if p.tok == leftParen { - p.curErr("arrays cannot be nested") - } - p.curErr("array element values must be words") - break - } - if len(p.accComs) > 0 { - c := p.accComs[0] - if c.Pos().Line() == ae.End().Line() { - ae.Comments = append(ae.Comments, c) - p.accComs = p.accComs[1:] - } - } - as.Array.Elems = append(as.Array.Elems, ae) - p.got(_Newl) - } - as.Array.Last, p.accComs = p.accComs, nil - p.postNested(old) - as.Array.Rparen = p.matched(as.Array.Lparen, leftParen, rightParen) - } else if w := p.getWord(); w != nil { - if as.Value == nil { - as.Value = w - } else { - as.Value.Parts = append(as.Value.Parts, w.Parts...) - } - } - return as -} - -func (p *Parser) peekRedir() bool { - switch p.tok { - case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut, - hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir: - return true - } - return false -} - -func (p *Parser) doRedirect(s *Stmt) { - var r *Redirect - if s.Redirs == nil { - var alloc struct { - redirs [4]*Redirect - redir Redirect - } - s.Redirs = alloc.redirs[:0] - r = &alloc.redir - s.Redirs = append(s.Redirs, r) - } else { - r = &Redirect{} - s.Redirs = append(s.Redirs, r) - } - r.N = p.getLit() - if p.lang != LangBash && r.N != nil && r.N.Value[0] == '{' { - p.langErr(r.N.Pos(), "{varname} redirects", LangBash) - } - r.Op, r.OpPos = RedirOperator(p.tok), p.pos - p.next() - switch r.Op { - case Hdoc, DashHdoc: - old := p.quote - p.quote, p.forbidNested = hdocWord, true - p.heredocs = append(p.heredocs, r) - r.Word = p.followWordTok(token(r.Op), r.OpPos) - p.quote, p.forbidNested = old, false - if p.tok == _Newl { - if len(p.accComs) > 0 { - c := p.accComs[0] - if c.Pos().Line() == s.End().Line() { - s.Comments = append(s.Comments, c) - p.accComs = p.accComs[1:] - } - } - p.doHeredocs() - } - default: - r.Word = p.followWordTok(token(r.Op), r.OpPos) - } -} - -func (p *Parser) getStmt(readEnd, binCmd, fnBody bool) *Stmt { - pos, ok := p.gotRsrv("!") - s := p.stmt(pos) - if ok { - s.Negated = true - if stopToken(p.tok) { - p.posErr(s.Pos(), `"!" cannot form a statement alone`) - } - if _, ok := p.gotRsrv("!"); ok { - p.posErr(s.Pos(), `cannot negate a command multiple times`) - } - } - if s = p.gotStmtPipe(s); s == nil || p.err != nil { - return nil - } - // instead of using recursion, iterate manually - for p.tok == andAnd || p.tok == orOr { - // left associativity: in a list of BinaryCmds, the - // right recursion should only read a single element - if binCmd { - return s - } - b := &BinaryCmd{ - OpPos: p.pos, - Op: BinCmdOperator(p.tok), - X: s, - } - p.next() - p.got(_Newl) - b.Y = p.getStmt(false, true, false) - if b.Y == nil || p.err != nil { - p.followErr(b.OpPos, b.Op.String(), "a statement") - return nil - } - s = p.stmt(s.Position) - s.Cmd = b - s.Comments, b.X.Comments = b.X.Comments, nil - } - if readEnd { - switch p.tok { - case semicolon: - s.Semicolon = p.pos - p.next() - case and: - s.Semicolon = p.pos - p.next() - s.Background = true - case orAnd: - s.Semicolon = p.pos - p.next() - s.Coprocess = true - } - } - if len(p.accComs) > 0 && !binCmd && !fnBody { - c := p.accComs[0] - if c.Pos().Line() == s.End().Line() { - s.Comments = append(s.Comments, c) - p.accComs = p.accComs[1:] - } - } - return s -} - -func (p *Parser) gotStmtPipe(s *Stmt) *Stmt { - s.Comments, p.accComs = p.accComs, nil - switch p.tok { - case _LitWord: - switch p.val { - case "{": - p.block(s) - case "if": - p.ifClause(s) - case "while", "until": - p.whileClause(s, p.val == "until") - case "for": - p.forClause(s) - case "case": - p.caseClause(s) - case "}": - p.curErr(`%q can only be used to close a block`, p.val) - case "then": - p.curErr(`%q can only be used in an if`, p.val) - case "elif": - p.curErr(`%q can only be used in an if`, p.val) - case "fi": - p.curErr(`%q can only be used to end an if`, p.val) - case "do": - p.curErr(`%q can only be used in a loop`, p.val) - case "done": - p.curErr(`%q can only be used to end a loop`, p.val) - case "esac": - p.curErr(`%q can only be used to end a case`, p.val) - case "!": - if !s.Negated { - p.curErr(`"!" can only be used in full statements`) - break - } - case "[[": - if p.lang != LangPOSIX { - p.testClause(s) - } - case "]]": - if p.lang != LangPOSIX { - p.curErr(`%q can only be used to close a test`, - p.val) - } - case "let": - if p.lang != LangPOSIX { - p.letClause(s) - } - case "function": - if p.lang != LangPOSIX { - p.bashFuncDecl(s) - } - case "declare": - if p.lang == LangBash { - p.declClause(s) - } - case "local", "export", "readonly", "typeset", "nameref": - if p.lang != LangPOSIX { - p.declClause(s) - } - case "time": - if p.lang != LangPOSIX { - p.timeClause(s) - } - case "coproc": - if p.lang == LangBash { - p.coprocClause(s) - } - case "select": - if p.lang != LangPOSIX { - p.selectClause(s) - } - } - if s.Cmd != nil { - break - } - if p.hasValidIdent() { - p.callExpr(s, nil, true) - break - } - name := p.lit(p.pos, p.val) - if p.next(); p.got(leftParen) { - p.follow(name.ValuePos, "foo(", rightParen) - if p.lang == LangPOSIX && !ValidName(name.Value) { - p.posErr(name.Pos(), "invalid func name") - } - p.funcDecl(s, name, name.ValuePos) - } else { - p.callExpr(s, p.word(p.wps(name)), false) - } - case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut, - hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir: - p.doRedirect(s) - p.callExpr(s, nil, false) - case bckQuote: - if p.backquoteEnd() { - return nil - } - fallthrough - case _Lit, dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut, - sglQuote, dollSglQuote, dblQuote, dollDblQuote, dollBrack, - globQuest, globStar, globPlus, globAt, globExcl: - if p.hasValidIdent() { - p.callExpr(s, nil, true) - break - } - w := p.word(p.wordParts()) - if p.got(leftParen) && p.err == nil { - p.posErr(w.Pos(), "invalid func name") - } - p.callExpr(s, w, false) - case leftParen: - p.subshell(s) - case dblLeftParen: - p.arithmExpCmd(s) - default: - if len(s.Redirs) == 0 { - return nil - } - } - for p.peekRedir() { - p.doRedirect(s) - } - switch p.tok { - case orAnd: - if p.lang == LangMirBSDKorn { - break - } - fallthrough - case or: - b := &BinaryCmd{OpPos: p.pos, Op: BinCmdOperator(p.tok), X: s} - p.next() - p.got(_Newl) - if b.Y = p.gotStmtPipe(p.stmt(p.pos)); b.Y == nil || p.err != nil { - p.followErr(b.OpPos, b.Op.String(), "a statement") - break - } - s = p.stmt(s.Position) - s.Cmd = b - s.Comments, b.X.Comments = b.X.Comments, nil - // in "! x | y", the bang applies to the entire pipeline - s.Negated = b.X.Negated - b.X.Negated = false - } - return s -} - -func (p *Parser) subshell(s *Stmt) { - sub := &Subshell{Lparen: p.pos} - old := p.preNested(subCmd) - p.next() - sub.StmtList = p.stmtList() - p.postNested(old) - sub.Rparen = p.matched(sub.Lparen, leftParen, rightParen) - s.Cmd = sub -} - -func (p *Parser) arithmExpCmd(s *Stmt) { - ar := &ArithmCmd{Left: p.pos} - old := p.preNested(arithmExprCmd) - p.next() - if p.got(hash) { - if p.lang != LangMirBSDKorn { - p.langErr(ar.Pos(), "unsigned expressions", LangMirBSDKorn) - } - ar.Unsigned = true - } - ar.X = p.followArithm(dblLeftParen, ar.Left) - ar.Right = p.arithmEnd(dblLeftParen, ar.Left, old) - s.Cmd = ar -} - -func (p *Parser) block(s *Stmt) { - b := &Block{Lbrace: p.pos} - p.next() - b.StmtList = p.stmtList("}") - pos, ok := p.gotRsrv("}") - b.Rbrace = pos - if !ok { - p.matchingErr(b.Lbrace, "{", "}") - } - s.Cmd = b -} - -func (p *Parser) ifClause(s *Stmt) { - rif := &IfClause{IfPos: p.pos} - p.next() - rif.Cond = p.followStmts("if", rif.IfPos, "then") - rif.ThenPos = p.followRsrv(rif.IfPos, "if ", "then") - rif.Then = p.followStmts("then", rif.ThenPos, "fi", "elif", "else") - curIf := rif - for p.tok == _LitWord && p.val == "elif" { - elf := &IfClause{IfPos: p.pos, Elif: true} - s := p.stmt(elf.IfPos) - s.Cmd = elf - s.Comments = p.accComs - p.accComs = nil - p.next() - elf.Cond = p.followStmts("elif", elf.IfPos, "then") - elf.ThenPos = p.followRsrv(elf.IfPos, "elif ", "then") - elf.Then = p.followStmts("then", elf.ThenPos, "fi", "elif", "else") - curIf.ElsePos = elf.IfPos - curIf.Else.Stmts = []*Stmt{s} - curIf = elf - } - if elsePos, ok := p.gotRsrv("else"); ok { - curIf.ElseComments = p.accComs - p.accComs = nil - curIf.ElsePos = elsePos - curIf.Else = p.followStmts("else", curIf.ElsePos, "fi") - } - curIf.FiComments = p.accComs - p.accComs = nil - rif.FiPos = p.stmtEnd(rif, "if", "fi") - curIf.FiPos = rif.FiPos - s.Cmd = rif -} - -func (p *Parser) whileClause(s *Stmt, until bool) { - wc := &WhileClause{WhilePos: p.pos, Until: until} - rsrv := "while" - rsrvCond := "while " - if wc.Until { - rsrv = "until" - rsrvCond = "until " - } - p.next() - wc.Cond = p.followStmts(rsrv, wc.WhilePos, "do") - wc.DoPos = p.followRsrv(wc.WhilePos, rsrvCond, "do") - wc.Do = p.followStmts("do", wc.DoPos, "done") - wc.DonePos = p.stmtEnd(wc, rsrv, "done") - s.Cmd = wc -} - -func (p *Parser) forClause(s *Stmt) { - fc := &ForClause{ForPos: p.pos} - p.next() - fc.Loop = p.loop(fc.ForPos) - fc.DoPos = p.followRsrv(fc.ForPos, "for foo [in words]", "do") - - s.Comments = append(s.Comments, p.accComs...) - p.accComs = nil - fc.Do = p.followStmts("do", fc.DoPos, "done") - fc.DonePos = p.stmtEnd(fc, "for", "done") - s.Cmd = fc -} - -func (p *Parser) loop(fpos Pos) Loop { - if p.lang != LangBash { - switch p.tok { - case leftParen, dblLeftParen: - p.langErr(p.pos, "c-style fors", LangBash) - } - } - if p.tok == dblLeftParen { - cl := &CStyleLoop{Lparen: p.pos} - old := p.preNested(arithmExprCmd) - p.next() - cl.Init = p.arithmExpr(0, false, false) - if !p.got(dblSemicolon) { - p.follow(p.pos, "expr", semicolon) - cl.Cond = p.arithmExpr(0, false, false) - p.follow(p.pos, "expr", semicolon) - } - cl.Post = p.arithmExpr(0, false, false) - cl.Rparen = p.arithmEnd(dblLeftParen, cl.Lparen, old) - p.got(semicolon) - p.got(_Newl) - return cl - } - return p.wordIter("for", fpos) -} - -func (p *Parser) wordIter(ftok string, fpos Pos) *WordIter { - wi := &WordIter{} - if wi.Name = p.getLit(); wi.Name == nil { - p.followErr(fpos, ftok, "a literal") - } - if p.got(semicolon) { - p.got(_Newl) - return wi - } - p.got(_Newl) - if _, ok := p.gotRsrv("in"); ok { - for !stopToken(p.tok) { - if w := p.getWord(); w == nil { - p.curErr("word list can only contain words") - } else { - wi.Items = append(wi.Items, w) - } - } - p.got(semicolon) - p.got(_Newl) - } else if p.tok == _LitWord && p.val == "do" { - } else { - p.followErr(fpos, ftok+" foo", `"in", "do", ;, or a newline`) - } - return wi -} - -func (p *Parser) selectClause(s *Stmt) { - fc := &ForClause{ForPos: p.pos, Select: true} - p.next() - fc.Loop = p.wordIter("select", fc.ForPos) - fc.DoPos = p.followRsrv(fc.ForPos, "select foo [in words]", "do") - fc.Do = p.followStmts("do", fc.DoPos, "done") - fc.DonePos = p.stmtEnd(fc, "select", "done") - s.Cmd = fc -} - -func (p *Parser) caseClause(s *Stmt) { - cc := &CaseClause{Case: p.pos} - p.next() - cc.Word = p.followWord("case", cc.Case) - end := "esac" - p.got(_Newl) - if _, ok := p.gotRsrv("{"); ok { - if p.lang != LangMirBSDKorn { - p.posErr(cc.Pos(), `"case i {" is a mksh feature`) - } - end = "}" - } else { - p.followRsrv(cc.Case, "case x", "in") - } - cc.Items = p.caseItems(end) - cc.Last, p.accComs = p.accComs, nil - cc.Esac = p.stmtEnd(cc, "case", end) - s.Cmd = cc -} - -func (p *Parser) caseItems(stop string) (items []*CaseItem) { - p.got(_Newl) - for p.tok != _EOF && !(p.tok == _LitWord && p.val == stop) { - ci := &CaseItem{} - ci.Comments, p.accComs = p.accComs, nil - p.got(leftParen) - for p.tok != _EOF { - if w := p.getWord(); w == nil { - p.curErr("case patterns must consist of words") - } else { - ci.Patterns = append(ci.Patterns, w) - } - if p.tok == rightParen { - break - } - if !p.got(or) { - p.curErr("case patterns must be separated with |") - } - } - old := p.preNested(switchCase) - p.next() - ci.StmtList = p.stmtList(stop) - p.postNested(old) - switch p.tok { - case dblSemicolon, semiAnd, dblSemiAnd, semiOr: - default: - ci.Op = Break - items = append(items, ci) - return - } - ci.Last = append(ci.Last, p.accComs...) - p.accComs = nil - ci.OpPos = p.pos - ci.Op = CaseOperator(p.tok) - p.next() - p.got(_Newl) - split := len(p.accComs) - if p.tok == _LitWord && p.val != stop { - for i := len(p.accComs) - 1; i >= 0; i-- { - c := p.accComs[i] - if c.Pos().Col() != p.pos.Col() { - break - } - split = i - } - } - ci.Comments = append(ci.Comments, p.accComs[:split]...) - p.accComs = p.accComs[split:] - items = append(items, ci) - } - return -} - -func (p *Parser) testClause(s *Stmt) { - tc := &TestClause{Left: p.pos} - p.next() - if _, ok := p.gotRsrv("]]"); ok || p.tok == _EOF { - p.posErr(tc.Left, "test clause requires at least one expression") - } - tc.X = p.testExpr(dblLeftBrack, tc.Left, false) - tc.Right = p.pos - if _, ok := p.gotRsrv("]]"); !ok { - p.matchingErr(tc.Left, "[[", "]]") - } - s.Cmd = tc -} - -func (p *Parser) testExpr(ftok token, fpos Pos, pastAndOr bool) TestExpr { - p.got(_Newl) - var left TestExpr - if pastAndOr { - left = p.testExprBase(ftok, fpos) - } else { - left = p.testExpr(ftok, fpos, true) - } - if left == nil { - return left - } - p.got(_Newl) - switch p.tok { - case andAnd, orOr: - case _LitWord: - if p.val == "]]" { - return left - } - case rdrIn, rdrOut: - case _EOF, rightParen: - return left - case _Lit: - p.curErr("test operator words must consist of a single literal") - default: - p.curErr("not a valid test operator: %v", p.tok) - } - if p.tok == _LitWord { - if p.tok = token(testBinaryOp(p.val)); p.tok == illegalTok { - p.curErr("not a valid test operator: %s", p.val) - } - } - b := &BinaryTest{ - OpPos: p.pos, - Op: BinTestOperator(p.tok), - X: left, - } - // Save the previous quoteState, since we change it in TsReMatch. - oldQuote := p.quote - - switch b.Op { - case AndTest, OrTest: - p.next() - if b.Y = p.testExpr(token(b.Op), b.OpPos, false); b.Y == nil { - p.followErrExp(b.OpPos, b.Op.String()) - } - case TsReMatch: - if p.lang != LangBash { - p.langErr(p.pos, "regex tests", LangBash) - } - p.rxOpenParens = 0 - p.rxFirstPart = true - // TODO(mvdan): Using nested states within a regex will break in - // all sorts of ways. The better fix is likely to use a stop - // token, like we do with heredocs. - p.quote = testRegexp - fallthrough - default: - if _, ok := b.X.(*Word); !ok { - p.posErr(b.OpPos, "expected %s, %s or %s after complex expr", - AndTest, OrTest, "]]") - } - p.next() - b.Y = p.followWordTok(token(b.Op), b.OpPos) - } - p.quote = oldQuote - return b -} - -func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr { - switch p.tok { - case _EOF, rightParen: - return nil - case _LitWord: - op := token(testUnaryOp(p.val)) - switch op { - case illegalTok: - case tsRefVar, tsModif: // not available in mksh - if p.lang == LangBash { - p.tok = op - } - default: - p.tok = op - } - } - switch p.tok { - case exclMark: - u := &UnaryTest{OpPos: p.pos, Op: TsNot} - p.next() - if u.X = p.testExpr(token(u.Op), u.OpPos, false); u.X == nil { - p.followErrExp(u.OpPos, u.Op.String()) - } - return u - case tsExists, tsRegFile, tsDirect, tsCharSp, tsBlckSp, tsNmPipe, - tsSocket, tsSmbLink, tsSticky, tsGIDSet, tsUIDSet, tsGrpOwn, - tsUsrOwn, tsModif, tsRead, tsWrite, tsExec, tsNoEmpty, - tsFdTerm, tsEmpStr, tsNempStr, tsOptSet, tsVarSet, tsRefVar: - u := &UnaryTest{OpPos: p.pos, Op: UnTestOperator(p.tok)} - p.next() - u.X = p.followWordTok(token(u.Op), u.OpPos) - return u - case leftParen: - pe := &ParenTest{Lparen: p.pos} - p.next() - if pe.X = p.testExpr(leftParen, pe.Lparen, false); pe.X == nil { - p.followErrExp(pe.Lparen, "(") - } - pe.Rparen = p.matched(pe.Lparen, leftParen, rightParen) - return pe - default: - return p.followWordTok(ftok, fpos) - } -} - -func (p *Parser) declClause(s *Stmt) { - ds := &DeclClause{Variant: p.lit(p.pos, p.val)} - p.next() - for (p.tok == _LitWord || p.tok == _Lit) && - (p.val[0] == '-' || p.val[0] == '+') { - ds.Opts = append(ds.Opts, p.getWord()) - } - for !stopToken(p.tok) && !p.peekRedir() { - if p.hasValidIdent() { - ds.Assigns = append(ds.Assigns, p.getAssign(false)) - } else if p.eqlOffs > 0 { - p.curErr("invalid var name") - } else if p.tok == _LitWord { - ds.Assigns = append(ds.Assigns, &Assign{ - Naked: true, - Name: p.getLit(), - }) - } else if w := p.getWord(); w != nil { - ds.Assigns = append(ds.Assigns, &Assign{ - Naked: true, - Value: w, - }) - } else { - p.followErr(p.pos, ds.Variant.Value, "names or assignments") - } - } - s.Cmd = ds -} - -func isBashCompoundCommand(tok token, val string) bool { - switch tok { - case leftParen, dblLeftParen: - return true - case _LitWord: - switch val { - case "{", "if", "while", "until", "for", "case", "[[", - "coproc", "let", "function", "declare", "local", - "export", "readonly", "typeset", "nameref": - return true - } - } - return false -} - -func (p *Parser) timeClause(s *Stmt) { - tc := &TimeClause{Time: p.pos} - p.next() - if _, ok := p.gotRsrv("-p"); ok { - tc.PosixFormat = true - } - tc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) - s.Cmd = tc -} - -func (p *Parser) coprocClause(s *Stmt) { - cc := &CoprocClause{Coproc: p.pos} - if p.next(); isBashCompoundCommand(p.tok, p.val) { - // has no name - cc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) - s.Cmd = cc - return - } - cc.Name = p.getLit() - cc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) - if cc.Stmt == nil { - if cc.Name == nil { - p.posErr(cc.Coproc, "coproc clause requires a command") - return - } - // name was in fact the stmt - cc.Stmt = p.stmt(cc.Name.ValuePos) - cc.Stmt.Cmd = p.call(p.word(p.wps(cc.Name))) - cc.Name = nil - } else if cc.Name != nil { - if call, ok := cc.Stmt.Cmd.(*CallExpr); ok { - // name was in fact the start of a call - call.Args = append([]*Word{p.word(p.wps(cc.Name))}, - call.Args...) - cc.Name = nil - } - } - s.Cmd = cc -} - -func (p *Parser) letClause(s *Stmt) { - lc := &LetClause{Let: p.pos} - old := p.preNested(arithmExprLet) - p.next() - for !stopToken(p.tok) && !p.peekRedir() { - x := p.arithmExpr(0, true, false) - if x == nil { - break - } - lc.Exprs = append(lc.Exprs, x) - } - if len(lc.Exprs) == 0 { - p.followErrExp(lc.Let, "let") - } - p.postNested(old) - s.Cmd = lc -} - -func (p *Parser) bashFuncDecl(s *Stmt) { - fpos := p.pos - if p.next(); p.tok != _LitWord { - if w := p.followWord("function", fpos); p.err == nil { - p.posErr(w.Pos(), "invalid func name") - } - } - name := p.lit(p.pos, p.val) - if p.next(); p.got(leftParen) { - p.follow(name.ValuePos, "foo(", rightParen) - } - p.funcDecl(s, name, fpos) -} - -func (p *Parser) callExpr(s *Stmt, w *Word, assign bool) { - ce := p.call(w) - if w == nil { - ce.Args = ce.Args[:0] - } - if assign { - ce.Assigns = append(ce.Assigns, p.getAssign(true)) - } -loop: - for { - switch p.tok { - case _EOF, _Newl, semicolon, and, or, andAnd, orOr, orAnd, - dblSemicolon, semiAnd, dblSemiAnd, semiOr: - break loop - case _LitWord: - if len(ce.Args) == 0 && p.hasValidIdent() { - ce.Assigns = append(ce.Assigns, p.getAssign(true)) - break - } - ce.Args = append(ce.Args, p.word( - p.wps(p.lit(p.pos, p.val)), - )) - p.next() - case _Lit: - if len(ce.Args) == 0 && p.hasValidIdent() { - ce.Assigns = append(ce.Assigns, p.getAssign(true)) - break - } - ce.Args = append(ce.Args, p.word(p.wordParts())) - case bckQuote: - if p.backquoteEnd() { - break loop - } - fallthrough - case dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut, - sglQuote, dollSglQuote, dblQuote, dollDblQuote, dollBrack, - globQuest, globStar, globPlus, globAt, globExcl: - ce.Args = append(ce.Args, p.word(p.wordParts())) - case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut, - hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir: - p.doRedirect(s) - case dblLeftParen: - p.curErr("%s can only be used to open an arithmetic cmd", p.tok) - case rightParen: - if p.quote == subCmd { - break loop - } - fallthrough - default: - p.curErr("a command can only contain words and redirects") - } - } - if len(ce.Assigns) == 0 && len(ce.Args) == 0 { - return - } - if len(ce.Args) == 0 { - ce.Args = nil - } else { - for _, asgn := range ce.Assigns { - if asgn.Index != nil || asgn.Array != nil { - p.posErr(asgn.Pos(), "inline variables cannot be arrays") - } - } - } - s.Cmd = ce -} - -func (p *Parser) funcDecl(s *Stmt, name *Lit, pos Pos) { - fd := &FuncDecl{ - Position: pos, - RsrvWord: pos != name.ValuePos, - Name: name, - } - p.got(_Newl) - if fd.Body = p.getStmt(false, false, true); fd.Body == nil { - p.followErr(fd.Pos(), "foo()", "a statement") - } - s.Cmd = fd -} diff --git a/vendor/mvdan.cc/sh/syntax/pattern.go b/vendor/mvdan.cc/sh/syntax/pattern.go deleted file mode 100644 index 4fb0844a..00000000 --- a/vendor/mvdan.cc/sh/syntax/pattern.go +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) 2017, Daniel Martí -// See LICENSE for licensing information - -package syntax - -import ( - "bytes" - "fmt" - "regexp" - "strings" -) - -func charClass(s string) (string, error) { - if strings.HasPrefix(s, "[[.") || strings.HasPrefix(s, "[[=") { - return "", fmt.Errorf("collating features not available") - } - if !strings.HasPrefix(s, "[[:") { - return "", nil - } - name := s[3:] - end := strings.Index(name, ":]]") - if end < 0 { - return "", fmt.Errorf("[[: was not matched with a closing :]]") - } - name = name[:end] - switch name { - case "alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph", - "lower", "print", "punct", "space", "upper", "word", "xdigit": - default: - return "", fmt.Errorf("invalid character class: %q", name) - } - return s[:len(name)+6], nil -} - -// TranslatePattern turns a shell wildcard pattern into a regular expression -// that can be used with regexp.Compile. It will return an error if the input -// pattern was incorrect. Otherwise, the returned expression can be passed to -// regexp.MustCompile. -// -// For example, TranslatePattern(`foo*bar?`, true) returns `foo.*bar.`. -// -// Note that this function (and QuotePattern) should not be directly used with -// file paths if Windows is supported, as the path separator on that platform is -// the same character as the escaping character for shell patterns. -func TranslatePattern(pattern string, greedy bool) (string, error) { - any := false -loop: - for _, r := range pattern { - switch r { - // including those that need escaping since they are - // special chars in regexes - case '*', '?', '[', '\\', '.', '+', '(', ')', '|', - ']', '{', '}', '^', '$': - any = true - break loop - } - } - if !any { // short-cut without a string copy - return pattern, nil - } - var buf bytes.Buffer - for i := 0; i < len(pattern); i++ { - switch c := pattern[i]; c { - case '*': - buf.WriteString(".*") - if !greedy { - buf.WriteByte('?') - } - case '?': - buf.WriteString(".") - case '\\': - if i++; i >= len(pattern) { - return "", fmt.Errorf(`\ at end of pattern`) - } - buf.WriteString(regexp.QuoteMeta(string(pattern[i]))) - case '[': - name, err := charClass(pattern[i:]) - if err != nil { - return "", err - } - if name != "" { - buf.WriteString(name) - i += len(name) - 1 - break - } - buf.WriteByte(c) - if i++; i >= len(pattern) { - return "", fmt.Errorf("[ was not matched with a closing ]") - } - switch c = pattern[i]; c { - case '!', '^': - buf.WriteByte('^') - i++ - c = pattern[i] - } - buf.WriteByte(c) - last := c - rangeStart := byte(0) - for { - if i++; i >= len(pattern) { - return "", fmt.Errorf("[ was not matched with a closing ]") - } - last, c = c, pattern[i] - buf.WriteByte(c) - if c == ']' { - break - } - if rangeStart != 0 && rangeStart > c { - return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c) - } - if c == '-' { - rangeStart = last - } else { - rangeStart = 0 - } - } - default: - buf.WriteString(regexp.QuoteMeta(string(c))) - } - } - return buf.String(), nil -} - -// HasPattern returns whether a string contains any unescaped wildcard -// characters: '*', '?', or '['. When the function returns false, the given -// pattern can only match at most one string. -// -// For example, HasPattern(`foo\*bar`) returns false, but HasPattern(`foo*bar`) -// returns true. -// -// This can be useful to avoid extra work, like TranslatePattern. Note that this -// function cannot be used to avoid QuotePattern, as backslashes are quoted by -// that function but ignored here. -func HasPattern(pattern string) bool { - for i := 0; i < len(pattern); i++ { - switch pattern[i] { - case '\\': - i++ - case '*', '?', '[': - return true - } - } - return false -} - -// QuotePattern returns a string that quotes all special characters in the given -// wildcard pattern. The returned string is a pattern that matches the literal -// string. -// -// For example, QuotePattern(`foo*bar?`) returns `foo\*bar\?`. -func QuotePattern(pattern string) string { - any := false -loop: - for _, r := range pattern { - switch r { - case '*', '?', '[', '\\': - any = true - break loop - } - } - if !any { // short-cut without a string copy - return pattern - } - var buf bytes.Buffer - for _, r := range pattern { - switch r { - case '*', '?', '[', '\\': - buf.WriteByte('\\') - } - buf.WriteRune(r) - } - return buf.String() -} diff --git a/vendor/mvdan.cc/sh/syntax/printer.go b/vendor/mvdan.cc/sh/syntax/printer.go deleted file mode 100644 index b7da1606..00000000 --- a/vendor/mvdan.cc/sh/syntax/printer.go +++ /dev/null @@ -1,1312 +0,0 @@ -// Copyright (c) 2016, Daniel Martí -// See LICENSE for licensing information - -package syntax - -import ( - "bufio" - "bytes" - "fmt" - "io" - "strings" - "unicode" -) - -// Indent sets the number of spaces used for indentation. If set to 0, -// tabs will be used instead. -func Indent(spaces uint) func(*Printer) { - return func(p *Printer) { p.indentSpaces = spaces } -} - -// BinaryNextLine will make binary operators appear on the next line -// when a binary command, such as a pipe, spans multiple lines. A -// backslash will be used. -func BinaryNextLine(p *Printer) { p.binNextLine = true } - -// SwitchCaseIndent will make switch cases be indented. As such, switch -// case bodies will be two levels deeper than the switch itself. -func SwitchCaseIndent(p *Printer) { p.swtCaseIndent = true } - -// SpaceRedirects will put a space after most redirection operators. The -// exceptions are '>&', '<&', '>(', and '<('. -func SpaceRedirects(p *Printer) { p.spaceRedirects = true } - -// KeepPadding will keep most nodes and tokens in the same column that -// they were in the original source. This allows the user to decide how -// to align and pad their code with spaces. -// -// Note that this feature is best-effort and will only keep the -// alignment stable, so it may need some human help the first time it is -// run. -func KeepPadding(p *Printer) { - p.keepPadding = true - p.cols.Writer = p.bufWriter.(*bufio.Writer) - p.bufWriter = &p.cols -} - -// Minify will print programs in a way to save the most bytes possible. -// For example, indentation and comments are skipped, and extra -// whitespace is avoided when possible. -func Minify(p *Printer) { p.minify = true } - -// NewPrinter allocates a new Printer and applies any number of options. -func NewPrinter(options ...func(*Printer)) *Printer { - p := &Printer{ - bufWriter: bufio.NewWriter(nil), - lenPrinter: new(Printer), - tabsPrinter: new(Printer), - } - for _, opt := range options { - opt(p) - } - return p -} - -// Print "pretty-prints" the given syntax tree node to the given writer. Writes -// to w are buffered. -// -// The node types supported at the moment are *File, *Stmt, *Word, any Command -// node, and any WordPart node. A trailing newline will only be printed when a -// *File is used. -func (p *Printer) Print(w io.Writer, node Node) error { - p.reset() - p.bufWriter.Reset(w) - switch x := node.(type) { - case *File: - p.stmtList(x.StmtList) - p.newline(x.End()) - case *Stmt: - p.stmtList(StmtList{Stmts: []*Stmt{x}}) - case Command: - p.line = x.Pos().Line() - p.command(x, nil) - case *Word: - p.word(x) - case WordPart: - p.wordPart(x, nil) - default: - return fmt.Errorf("unsupported node type: %T", x) - } - p.flushHeredocs() - p.flushComments() - return p.bufWriter.Flush() -} - -type bufWriter interface { - Write([]byte) (int, error) - WriteString(string) (int, error) - WriteByte(byte) error - Reset(io.Writer) - Flush() error -} - -type colCounter struct { - *bufio.Writer - column int - lineStart bool -} - -func (c *colCounter) WriteByte(b byte) error { - switch b { - case '\n': - c.column = 0 - c.lineStart = true - case '\t', ' ': - default: - c.lineStart = false - } - c.column++ - return c.Writer.WriteByte(b) -} - -func (c *colCounter) WriteString(s string) (int, error) { - c.lineStart = false - for _, r := range s { - if r == '\n' { - c.column = 0 - } - c.column++ - } - return c.Writer.WriteString(s) -} - -func (c *colCounter) Reset(w io.Writer) { - c.column = 1 - c.lineStart = true - c.Writer.Reset(w) -} - -// Printer holds the internal state of the printing mechanism of a -// program. -type Printer struct { - bufWriter - cols colCounter - - indentSpaces uint - binNextLine bool - swtCaseIndent bool - spaceRedirects bool - keepPadding bool - minify bool - - wantSpace bool - wantNewline bool - wroteSemi bool - - commentPadding uint - - // pendingComments are any comments in the current line or statement - // that we have yet to print. This is useful because that way, we can - // ensure that all comments are written immediately before a newline. - // Otherwise, in some edge cases we might wrongly place words after a - // comment in the same line, breaking programs. - pendingComments []Comment - - // firstLine means we are still writing the first line - firstLine bool - // line is the current line number - line uint - - // lastLevel is the last level of indentation that was used. - lastLevel uint - // level is the current level of indentation. - level uint - // levelIncs records which indentation level increments actually - // took place, to revert them once their section ends. - levelIncs []bool - - nestedBinary bool - - // pendingHdocs is the list of pending heredocs to write. - pendingHdocs []*Redirect - - // used in stmtCols to align comments - lenPrinter *Printer - lenCounter byteCounter - - // used when printing <<- heredocs with tab indentation - tabsPrinter *Printer -} - -func (p *Printer) reset() { - p.wantSpace, p.wantNewline = false, false - p.commentPadding = 0 - p.pendingComments = p.pendingComments[:0] - - // minification uses its own newline logic - p.firstLine = !p.minify - p.line = 0 - - p.lastLevel, p.level = 0, 0 - p.levelIncs = p.levelIncs[:0] - p.nestedBinary = false - p.pendingHdocs = p.pendingHdocs[:0] -} - -func (p *Printer) spaces(n uint) { - for i := uint(0); i < n; i++ { - p.WriteByte(' ') - } -} - -func (p *Printer) space() { - p.WriteByte(' ') - p.wantSpace = false -} - -func (p *Printer) spacePad(pos Pos) { - if p.wantSpace { - p.WriteByte(' ') - p.wantSpace = false - } - if p.cols.lineStart { - // Never add padding at the start of a line, since this may - // result in broken indentation or mixing of spaces and tabs. - return - } - for !p.cols.lineStart && p.cols.column > 0 && p.cols.column < int(pos.col) { - p.WriteByte(' ') - } -} - -func (p *Printer) bslashNewl() { - if p.wantSpace { - p.space() - } - p.WriteString("\\\n") - p.line++ - p.indent() -} - -func (p *Printer) spacedString(s string, pos Pos) { - p.spacePad(pos) - p.WriteString(s) - p.wantSpace = true -} - -func (p *Printer) spacedToken(s string, pos Pos) { - if p.minify { - p.WriteString(s) - p.wantSpace = false - return - } - p.spacePad(pos) - p.WriteString(s) - p.wantSpace = true -} - -func (p *Printer) semiOrNewl(s string, pos Pos) { - if p.wantNewline { - p.newline(pos) - p.indent() - } else { - if !p.wroteSemi { - p.WriteByte(';') - } - if !p.minify { - p.space() - } - p.line = pos.Line() - } - p.WriteString(s) - p.wantSpace = true -} - -func (p *Printer) incLevel() { - inc := false - if p.level <= p.lastLevel || len(p.levelIncs) == 0 { - p.level++ - inc = true - } else if last := &p.levelIncs[len(p.levelIncs)-1]; *last { - *last = false - inc = true - } - p.levelIncs = append(p.levelIncs, inc) -} - -func (p *Printer) decLevel() { - if p.levelIncs[len(p.levelIncs)-1] { - p.level-- - } - p.levelIncs = p.levelIncs[:len(p.levelIncs)-1] -} - -func (p *Printer) indent() { - if p.minify { - return - } - p.lastLevel = p.level - switch { - case p.level == 0: - case p.indentSpaces == 0: - for i := uint(0); i < p.level; i++ { - p.WriteByte('\t') - } - default: - p.spaces(p.indentSpaces * p.level) - } -} - -func (p *Printer) newline(pos Pos) { - p.flushHeredocs() - p.flushComments() - p.WriteByte('\n') - p.wantNewline, p.wantSpace = false, false - if p.line < pos.Line() { - p.line++ - } -} - -func (p *Printer) flushHeredocs() { - if len(p.pendingHdocs) == 0 { - return - } - hdocs := p.pendingHdocs - p.pendingHdocs = p.pendingHdocs[:0] - coms := p.pendingComments - p.pendingComments = nil - if len(coms) > 0 { - c := coms[0] - if c.Pos().Line() == p.line { - p.pendingComments = append(p.pendingComments, c) - p.flushComments() - coms = coms[1:] - } - } - - // Reuse the last indentation level, as - // indentation levels are usually changed before - // newlines are printed along with their - // subsequent indentation characters. - newLevel := p.level - p.level = p.lastLevel - - for _, r := range hdocs { - p.line++ - p.WriteByte('\n') - p.wantNewline, p.wantSpace = false, false - if r.Op == DashHdoc && p.indentSpaces == 0 && - !p.minify && p.tabsPrinter != nil { - if r.Hdoc != nil { - extra := extraIndenter{ - bufWriter: p.bufWriter, - baseIndent: int(p.level + 1), - firstIndent: -1, - } - *p.tabsPrinter = Printer{ - bufWriter: &extra, - } - p.tabsPrinter.line = r.Hdoc.Pos().Line() - p.tabsPrinter.word(r.Hdoc) - p.indent() - p.line = r.Hdoc.End().Line() - } else { - p.indent() - } - } else if r.Hdoc != nil { - p.word(r.Hdoc) - p.line = r.Hdoc.End().Line() - } - p.unquotedWord(r.Word) - p.wantSpace = false - } - p.level = newLevel - p.pendingComments = coms -} - -func (p *Printer) newlines(pos Pos) { - if p.firstLine && len(p.pendingComments) == 0 { - p.firstLine = false - return // no empty lines at the top - } - if !p.wantNewline && pos.Line() <= p.line { - return - } - p.newline(pos) - if pos.Line() > p.line { - if !p.minify { - // preserve single empty lines - p.WriteByte('\n') - } - p.line++ - } - p.indent() -} - -func (p *Printer) rightParen(pos Pos) { - if !p.minify { - p.newlines(pos) - } - p.WriteByte(')') - p.wantSpace = true -} - -func (p *Printer) semiRsrv(s string, pos Pos) { - if p.wantNewline || pos.Line() > p.line { - p.newlines(pos) - } else { - if !p.wroteSemi { - p.WriteByte(';') - } - if !p.minify { - p.spacePad(pos) - } - } - p.WriteString(s) - p.wantSpace = true -} - -func (p *Printer) flushComments() { - for i, c := range p.pendingComments { - p.firstLine = false - // We can't call any of the newline methods, as they call this - // function and we'd recurse forever. - cline := c.Hash.Line() - switch { - case i > 0, cline > p.line && p.line > 0: - p.WriteByte('\n') - if cline > p.line+1 { - p.WriteByte('\n') - } - p.indent() - case p.wantSpace: - if p.keepPadding { - p.spacePad(c.Pos()) - } else { - p.spaces(p.commentPadding + 1) - } - } - // don't go back one line, which may happen in some edge cases - if p.line < cline { - p.line = cline - } - p.WriteByte('#') - p.WriteString(strings.TrimRightFunc(c.Text, unicode.IsSpace)) - p.wantNewline = true - } - p.pendingComments = nil -} - -func (p *Printer) comments(comments ...Comment) { - if p.minify { - return - } - p.pendingComments = append(p.pendingComments, comments...) -} - -func (p *Printer) wordParts(wps []WordPart) { - for i, n := range wps { - var next WordPart - if i+1 < len(wps) { - next = wps[i+1] - } - p.wordPart(n, next) - } -} - -func (p *Printer) wordPart(wp, next WordPart) { - switch x := wp.(type) { - case *Lit: - p.WriteString(x.Value) - case *SglQuoted: - if x.Dollar { - p.WriteByte('$') - } - p.WriteByte('\'') - p.WriteString(x.Value) - p.WriteByte('\'') - p.line = x.End().Line() - case *DblQuoted: - p.dblQuoted(x) - case *CmdSubst: - p.line = x.Pos().Line() - switch { - case x.TempFile: - p.WriteString("${") - p.wantSpace = true - p.nestedStmts(x.StmtList, x.Right) - p.wantSpace = false - p.semiRsrv("}", x.Right) - case x.ReplyVar: - p.WriteString("${|") - p.nestedStmts(x.StmtList, x.Right) - p.wantSpace = false - p.semiRsrv("}", x.Right) - default: - p.WriteString("$(") - p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0]) - p.nestedStmts(x.StmtList, x.Right) - p.rightParen(x.Right) - } - case *ParamExp: - litCont := ";" - if nextLit, ok := next.(*Lit); ok && nextLit.Value != "" { - litCont = nextLit.Value[:1] - } - name := x.Param.Value - switch { - case !p.minify: - case x.Excl, x.Length, x.Width: - case x.Index != nil, x.Slice != nil: - case x.Repl != nil, x.Exp != nil: - case len(name) > 1 && !ValidName(name): // ${10} - case ValidName(name + litCont): // ${var}cont - default: - x2 := *x - x2.Short = true - p.paramExp(&x2) - return - } - p.paramExp(x) - case *ArithmExp: - p.WriteString("$((") - if x.Unsigned { - p.WriteString("# ") - } - p.arithmExpr(x.X, false, false) - p.WriteString("))") - case *ExtGlob: - p.WriteString(x.Op.String()) - p.WriteString(x.Pattern.Value) - p.WriteByte(')') - case *ProcSubst: - // avoid conflict with << and others - if p.wantSpace { - p.space() - } - p.WriteString(x.Op.String()) - p.nestedStmts(x.StmtList, x.Rparen) - p.rightParen(x.Rparen) - } -} - -func (p *Printer) dblQuoted(dq *DblQuoted) { - if dq.Dollar { - p.WriteByte('$') - } - p.WriteByte('"') - if len(dq.Parts) > 0 { - p.wordParts(dq.Parts) - p.line = dq.Parts[len(dq.Parts)-1].End().Line() - } - p.WriteByte('"') -} - -func (p *Printer) wroteIndex(index ArithmExpr) bool { - if index == nil { - return false - } - p.WriteByte('[') - p.arithmExpr(index, false, false) - p.WriteByte(']') - return true -} - -func (p *Printer) paramExp(pe *ParamExp) { - if pe.nakedIndex() { // arr[x] - p.WriteString(pe.Param.Value) - p.wroteIndex(pe.Index) - return - } - if pe.Short { // $var - p.WriteByte('$') - p.WriteString(pe.Param.Value) - return - } - // ${var...} - p.WriteString("${") - switch { - case pe.Length: - p.WriteByte('#') - case pe.Width: - p.WriteByte('%') - case pe.Excl: - p.WriteByte('!') - } - p.WriteString(pe.Param.Value) - p.wroteIndex(pe.Index) - switch { - case pe.Slice != nil: - p.WriteByte(':') - p.arithmExpr(pe.Slice.Offset, true, true) - if pe.Slice.Length != nil { - p.WriteByte(':') - p.arithmExpr(pe.Slice.Length, true, false) - } - case pe.Repl != nil: - if pe.Repl.All { - p.WriteByte('/') - } - p.WriteByte('/') - if pe.Repl.Orig != nil { - p.word(pe.Repl.Orig) - } - p.WriteByte('/') - if pe.Repl.With != nil { - p.word(pe.Repl.With) - } - case pe.Names != 0: - p.WriteString(pe.Names.String()) - case pe.Exp != nil: - p.WriteString(pe.Exp.Op.String()) - if pe.Exp.Word != nil { - p.word(pe.Exp.Word) - } - } - p.WriteByte('}') -} - -func (p *Printer) loop(loop Loop) { - switch x := loop.(type) { - case *WordIter: - p.WriteString(x.Name.Value) - if len(x.Items) > 0 { - p.spacedString(" in", Pos{}) - p.wordJoin(x.Items) - } - case *CStyleLoop: - p.WriteString("((") - if x.Init == nil { - p.space() - } - p.arithmExpr(x.Init, false, false) - p.WriteString("; ") - p.arithmExpr(x.Cond, false, false) - p.WriteString("; ") - p.arithmExpr(x.Post, false, false) - p.WriteString("))") - } -} - -func (p *Printer) arithmExpr(expr ArithmExpr, compact, spacePlusMinus bool) { - if p.minify { - compact = true - } - switch x := expr.(type) { - case *Word: - p.word(x) - case *BinaryArithm: - if compact { - p.arithmExpr(x.X, compact, spacePlusMinus) - p.WriteString(x.Op.String()) - p.arithmExpr(x.Y, compact, false) - } else { - p.arithmExpr(x.X, compact, spacePlusMinus) - if x.Op != Comma { - p.space() - } - p.WriteString(x.Op.String()) - p.space() - p.arithmExpr(x.Y, compact, false) - } - case *UnaryArithm: - if x.Post { - p.arithmExpr(x.X, compact, spacePlusMinus) - p.WriteString(x.Op.String()) - } else { - if spacePlusMinus { - switch x.Op { - case Plus, Minus: - p.space() - } - } - p.WriteString(x.Op.String()) - p.arithmExpr(x.X, compact, false) - } - case *ParenArithm: - p.WriteByte('(') - p.arithmExpr(x.X, false, false) - p.WriteByte(')') - } -} - -func (p *Printer) testExpr(expr TestExpr) { - switch x := expr.(type) { - case *Word: - p.word(x) - case *BinaryTest: - p.testExpr(x.X) - p.space() - p.WriteString(x.Op.String()) - p.space() - p.testExpr(x.Y) - case *UnaryTest: - p.WriteString(x.Op.String()) - p.space() - p.testExpr(x.X) - case *ParenTest: - p.WriteByte('(') - p.testExpr(x.X) - p.WriteByte(')') - } -} - -func (p *Printer) word(w *Word) { - p.wordParts(w.Parts) - p.wantSpace = true -} - -func (p *Printer) unquotedWord(w *Word) { - for _, wp := range w.Parts { - switch x := wp.(type) { - case *SglQuoted: - p.WriteString(x.Value) - case *DblQuoted: - p.wordParts(x.Parts) - case *Lit: - for i := 0; i < len(x.Value); i++ { - if b := x.Value[i]; b == '\\' { - if i++; i < len(x.Value) { - p.WriteByte(x.Value[i]) - } - } else { - p.WriteByte(b) - } - } - } - } -} - -func (p *Printer) wordJoin(ws []*Word) { - anyNewline := false - for _, w := range ws { - if pos := w.Pos(); pos.Line() > p.line { - if !anyNewline { - p.incLevel() - anyNewline = true - } - p.bslashNewl() - } else { - p.spacePad(w.Pos()) - } - p.word(w) - } - if anyNewline { - p.decLevel() - } -} - -func (p *Printer) casePatternJoin(pats []*Word) { - anyNewline := false - for i, w := range pats { - if i > 0 { - p.spacedToken("|", Pos{}) - } - if pos := w.Pos(); pos.Line() > p.line { - if !anyNewline { - p.incLevel() - anyNewline = true - } - p.bslashNewl() - } else { - p.spacePad(w.Pos()) - } - p.word(w) - } - if anyNewline { - p.decLevel() - } -} - -func (p *Printer) elemJoin(elems []*ArrayElem, last []Comment) { - p.incLevel() - for _, el := range elems { - var left []Comment - for _, c := range el.Comments { - if c.Pos().After(el.Pos()) { - left = append(left, c) - break - } - p.comments(c) - } - if el.Pos().Line() > p.line { - p.newline(el.Pos()) - p.indent() - } else if p.wantSpace { - p.space() - } - if p.wroteIndex(el.Index) { - p.WriteByte('=') - } - p.word(el.Value) - p.comments(left...) - } - if len(last) > 0 { - p.comments(last...) - p.flushComments() - } - p.decLevel() -} - -func (p *Printer) stmt(s *Stmt) { - p.wroteSemi = false - if s.Negated { - p.spacedString("!", s.Pos()) - } - var startRedirs int - if s.Cmd != nil { - startRedirs = p.command(s.Cmd, s.Redirs) - } - p.incLevel() - for _, r := range s.Redirs[startRedirs:] { - if r.OpPos.Line() > p.line { - p.bslashNewl() - } - if p.wantSpace { - p.spacePad(r.Pos()) - } - if r.N != nil { - p.WriteString(r.N.Value) - } - p.WriteString(r.Op.String()) - if p.spaceRedirects && (r.Op != DplIn && r.Op != DplOut) { - p.space() - } else { - p.wantSpace = true - } - p.word(r.Word) - if r.Op == Hdoc || r.Op == DashHdoc { - p.pendingHdocs = append(p.pendingHdocs, r) - } - } - switch { - case s.Semicolon.IsValid() && s.Semicolon.Line() > p.line: - p.bslashNewl() - p.WriteByte(';') - p.wroteSemi = true - case s.Background: - if !p.minify { - p.space() - } - p.WriteString("&") - case s.Coprocess: - if !p.minify { - p.space() - } - p.WriteString("|&") - } - p.decLevel() -} - -func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { - p.spacePad(cmd.Pos()) - switch x := cmd.(type) { - case *CallExpr: - p.assigns(x.Assigns) - if len(x.Args) <= 1 { - p.wordJoin(x.Args) - return 0 - } - p.wordJoin(x.Args[:1]) - for _, r := range redirs { - if r.Pos().After(x.Args[1].Pos()) || r.Op == Hdoc || r.Op == DashHdoc { - break - } - if p.wantSpace { - p.spacePad(r.Pos()) - } - if r.N != nil { - p.WriteString(r.N.Value) - } - p.WriteString(r.Op.String()) - if p.spaceRedirects && (r.Op != DplIn && r.Op != DplOut) { - p.space() - } else { - p.wantSpace = true - } - p.word(r.Word) - startRedirs++ - } - p.wordJoin(x.Args[1:]) - case *Block: - p.WriteByte('{') - p.wantSpace = true - p.nestedStmts(x.StmtList, x.Rbrace) - p.semiRsrv("}", x.Rbrace) - case *IfClause: - p.ifClause(x, false) - case *Subshell: - p.WriteByte('(') - p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0]) - p.spacePad(x.StmtList.pos()) - p.nestedStmts(x.StmtList, x.Rparen) - p.wantSpace = false - p.spacePad(x.Rparen) - p.rightParen(x.Rparen) - case *WhileClause: - if x.Until { - p.spacedString("until", x.Pos()) - } else { - p.spacedString("while", x.Pos()) - } - p.nestedStmts(x.Cond, Pos{}) - p.semiOrNewl("do", x.DoPos) - p.nestedStmts(x.Do, x.DonePos) - p.semiRsrv("done", x.DonePos) - case *ForClause: - if x.Select { - p.WriteString("select ") - } else { - p.WriteString("for ") - } - p.loop(x.Loop) - p.semiOrNewl("do", x.DoPos) - p.nestedStmts(x.Do, x.DonePos) - p.semiRsrv("done", x.DonePos) - case *BinaryCmd: - p.stmt(x.X) - if p.minify || x.Y.Pos().Line() <= p.line { - // leave p.nestedBinary untouched - p.spacedToken(x.Op.String(), x.OpPos) - p.line = x.Y.Pos().Line() - p.stmt(x.Y) - break - } - indent := !p.nestedBinary - if indent { - p.incLevel() - } - if p.binNextLine { - if len(p.pendingHdocs) == 0 { - p.bslashNewl() - } - p.spacedToken(x.Op.String(), x.OpPos) - if len(x.Y.Comments) > 0 { - p.wantSpace = false - p.newline(Pos{}) - p.indent() - p.comments(x.Y.Comments...) - p.newline(Pos{}) - p.indent() - } - } else { - p.spacedToken(x.Op.String(), x.OpPos) - p.line = x.OpPos.Line() - p.comments(x.Y.Comments...) - p.newline(Pos{}) - p.indent() - } - p.line = x.Y.Pos().Line() - _, p.nestedBinary = x.Y.Cmd.(*BinaryCmd) - p.stmt(x.Y) - if indent { - p.decLevel() - } - p.nestedBinary = false - case *FuncDecl: - if x.RsrvWord { - p.WriteString("function ") - } - p.WriteString(x.Name.Value) - p.WriteString("()") - if !p.minify { - p.space() - } - p.line = x.Body.Pos().Line() - p.comments(x.Body.Comments...) - p.stmt(x.Body) - case *CaseClause: - p.WriteString("case ") - p.word(x.Word) - p.WriteString(" in") - if p.swtCaseIndent { - p.incLevel() - } - for i, ci := range x.Items { - var last []Comment - for i, c := range ci.Comments { - if c.Pos().After(ci.Pos()) { - last = ci.Comments[i:] - break - } - p.comments(c) - } - p.newlines(ci.Pos()) - p.casePatternJoin(ci.Patterns) - p.WriteByte(')') - p.wantSpace = !p.minify - sep := len(ci.Stmts) > 1 || ci.StmtList.pos().Line() > p.line || - (!ci.StmtList.empty() && ci.OpPos.Line() > ci.StmtList.end().Line()) - p.nestedStmts(ci.StmtList, ci.OpPos) - p.level++ - if !p.minify || i != len(x.Items)-1 { - if sep { - p.newlines(ci.OpPos) - p.wantNewline = true - } - p.spacedToken(ci.Op.String(), ci.OpPos) - // avoid ; directly after tokens like ;; - p.wroteSemi = true - } - p.comments(last...) - p.flushComments() - p.level-- - } - p.comments(x.Last...) - if p.swtCaseIndent { - p.flushComments() - p.decLevel() - } - p.semiRsrv("esac", x.Esac) - case *ArithmCmd: - p.WriteString("((") - if x.Unsigned { - p.WriteString("# ") - } - p.arithmExpr(x.X, false, false) - p.WriteString("))") - case *TestClause: - p.WriteString("[[ ") - p.testExpr(x.X) - p.spacedString("]]", x.Right) - case *DeclClause: - p.spacedString(x.Variant.Value, x.Pos()) - for _, w := range x.Opts { - p.space() - p.word(w) - } - p.assigns(x.Assigns) - case *TimeClause: - p.spacedString("time", x.Pos()) - if x.PosixFormat { - p.spacedString("-p", x.Pos()) - } - if x.Stmt != nil { - p.stmt(x.Stmt) - } - case *CoprocClause: - p.spacedString("coproc", x.Pos()) - if x.Name != nil { - p.space() - p.WriteString(x.Name.Value) - } - p.space() - p.stmt(x.Stmt) - case *LetClause: - p.spacedString("let", x.Pos()) - for _, n := range x.Exprs { - p.space() - p.arithmExpr(n, true, false) - } - } - return startRedirs -} - -func (p *Printer) ifClause(ic *IfClause, elif bool) { - if !elif { - p.spacedString("if", ic.Pos()) - } - p.nestedStmts(ic.Cond, Pos{}) - p.semiOrNewl("then", ic.ThenPos) - p.nestedStmts(ic.Then, ic.bodyEndPos()) - - var left []Comment - for _, c := range ic.ElseComments { - if c.Pos().After(ic.ElsePos) { - left = append(left, c) - break - } - p.comments(c) - } - if ic.FollowedByElif() { - s := ic.Else.Stmts[0] - p.comments(s.Comments...) - p.semiRsrv("elif", ic.ElsePos) - p.ifClause(s.Cmd.(*IfClause), true) - return - } - if !ic.Else.empty() { - p.semiRsrv("else", ic.ElsePos) - p.comments(left...) - p.nestedStmts(ic.Else, ic.FiPos) - } else if ic.ElsePos.IsValid() { - p.line = ic.ElsePos.Line() - } - p.comments(ic.FiComments...) - p.semiRsrv("fi", ic.FiPos) -} - -func startsWithLparen(s *Stmt) bool { - switch x := s.Cmd.(type) { - case *Subshell: - return true - case *BinaryCmd: - return startsWithLparen(x.X) - } - return false -} - -func (p *Printer) hasInline(s *Stmt) bool { - for _, c := range s.Comments { - if c.Pos().Line() == s.End().Line() { - return true - } - } - return false -} - -func (p *Printer) stmtList(sl StmtList) { - sep := p.wantNewline || - (len(sl.Stmts) > 0 && sl.Stmts[0].Pos().Line() > p.line) - inlineIndent := 0 - lastIndentedLine := uint(0) - for i, s := range sl.Stmts { - pos := s.Pos() - var midComs, endComs []Comment - for _, c := range s.Comments { - if c.End().After(s.End()) { - endComs = append(endComs, c) - break - } - if c.Pos().After(s.Pos()) { - midComs = append(midComs, c) - continue - } - p.comments(c) - } - if !p.minify || p.wantSpace { - p.newlines(pos) - } - p.line = pos.Line() - if !p.hasInline(s) { - inlineIndent = 0 - p.commentPadding = 0 - p.comments(midComs...) - p.stmt(s) - p.wantNewline = true - continue - } - p.comments(midComs...) - p.stmt(s) - if s.Pos().Line() > lastIndentedLine+1 { - inlineIndent = 0 - } - if inlineIndent == 0 { - for _, s2 := range sl.Stmts[i:] { - if !p.hasInline(s2) { - break - } - if l := p.stmtCols(s2); l > inlineIndent { - inlineIndent = l - } - } - } - if inlineIndent > 0 { - if l := p.stmtCols(s); l > 0 { - p.commentPadding = uint(inlineIndent - l) - } - lastIndentedLine = p.line - } - p.comments(endComs...) - p.wantNewline = true - } - if len(sl.Stmts) == 1 && !sep { - p.wantNewline = false - } - p.comments(sl.Last...) -} - -type byteCounter int - -func (c *byteCounter) WriteByte(b byte) error { - switch { - case *c < 0: - case b == '\n': - *c = -1 - default: - *c++ - } - return nil -} -func (c *byteCounter) Write(p []byte) (int, error) { - return c.WriteString(string(p)) -} -func (c *byteCounter) WriteString(s string) (int, error) { - switch { - case *c < 0: - case strings.Contains(s, "\n"): - *c = -1 - default: - *c += byteCounter(len(s)) - } - return 0, nil -} -func (c *byteCounter) Reset(io.Writer) { *c = 0 } -func (c *byteCounter) Flush() error { return nil } - -// extraIndenter ensures that all lines in a '<<-' heredoc body have at least -// baseIndent leading tabs. Those that had more tab indentation than the first -// heredoc line will keep that relative indentation. -type extraIndenter struct { - bufWriter - baseIndent int - - firstIndent int - firstChange int - curLine []byte -} - -func (e *extraIndenter) WriteByte(b byte) error { - e.curLine = append(e.curLine, b) - if b != '\n' { - return nil - } - trimmed := bytes.TrimLeft(e.curLine, "\t") - lineIndent := len(e.curLine) - len(trimmed) - if e.firstIndent < 0 { - e.firstIndent = lineIndent - e.firstChange = e.baseIndent - lineIndent - lineIndent = e.baseIndent - } else { - if lineIndent < e.firstIndent { - lineIndent = e.firstIndent - } else { - lineIndent += e.firstChange - } - } - for i := 0; i < lineIndent; i++ { - e.bufWriter.WriteByte('\t') - } - e.bufWriter.Write(trimmed) - e.curLine = e.curLine[:0] - return nil -} - -func (e *extraIndenter) WriteString(s string) (int, error) { - for i := 0; i < len(s); i++ { - e.WriteByte(s[i]) - } - return len(s), nil -} - -// stmtCols reports the length that s will take when formatted in a -// single line. If it will span multiple lines, stmtCols will return -1. -func (p *Printer) stmtCols(s *Stmt) int { - if p.lenPrinter == nil { - return -1 // stmtCols call within stmtCols, bail - } - *p.lenPrinter = Printer{ - bufWriter: &p.lenCounter, - line: s.Pos().Line(), - } - p.lenPrinter.bufWriter.Reset(nil) - p.lenPrinter.stmt(s) - return int(p.lenCounter) -} - -func (p *Printer) nestedStmts(sl StmtList, closing Pos) { - p.incLevel() - switch { - case len(sl.Stmts) > 1: - // Force a newline if we find: - // { stmt; stmt; } - p.wantNewline = true - case closing.Line() > p.line && len(sl.Stmts) > 0 && - sl.end().Line() < closing.Line(): - // Force a newline if we find: - // { stmt - // } - p.wantNewline = true - case len(p.pendingComments) > 0 && len(sl.Stmts) > 0: - // Force a newline if we find: - // for i in a b # stmt - // do foo; done - p.wantNewline = true - } - p.stmtList(sl) - if closing.IsValid() { - p.flushComments() - } - p.decLevel() -} - -func (p *Printer) assigns(assigns []*Assign) { - p.incLevel() - for _, a := range assigns { - if a.Pos().Line() > p.line { - p.bslashNewl() - } else { - p.spacePad(a.Pos()) - } - if a.Name != nil { - p.WriteString(a.Name.Value) - p.wroteIndex(a.Index) - if a.Append { - p.WriteByte('+') - } - if !a.Naked { - p.WriteByte('=') - } - } - if a.Value != nil { - p.word(a.Value) - } else if a.Array != nil { - p.wantSpace = false - p.WriteByte('(') - p.elemJoin(a.Array.Elems, a.Array.Last) - p.rightParen(a.Array.Rparen) - } - p.wantSpace = true - } - p.decLevel() -} diff --git a/vendor/mvdan.cc/sh/syntax/quotestate_string.go b/vendor/mvdan.cc/sh/syntax/quotestate_string.go deleted file mode 100644 index bf17419e..00000000 --- a/vendor/mvdan.cc/sh/syntax/quotestate_string.go +++ /dev/null @@ -1,35 +0,0 @@ -// Code generated by "stringer -type=quoteState"; DO NOT EDIT. - -package syntax - -import "strconv" - -const _quoteState_name = "noStatesubCmdsubCmdBckquodblQuoteshdocWordhdocBodyhdocBodyTabsarithmExprarithmExprLetarithmExprCmdarithmExprBracktestRegexpswitchCaseparamExpNameparamExpSliceparamExpReplparamExpExparrayElems" - -var _quoteState_map = map[quoteState]string{ - 1: _quoteState_name[0:7], - 2: _quoteState_name[7:13], - 4: _quoteState_name[13:25], - 8: _quoteState_name[25:34], - 16: _quoteState_name[34:42], - 32: _quoteState_name[42:50], - 64: _quoteState_name[50:62], - 128: _quoteState_name[62:72], - 256: _quoteState_name[72:85], - 512: _quoteState_name[85:98], - 1024: _quoteState_name[98:113], - 2048: _quoteState_name[113:123], - 4096: _quoteState_name[123:133], - 8192: _quoteState_name[133:145], - 16384: _quoteState_name[145:158], - 32768: _quoteState_name[158:170], - 65536: _quoteState_name[170:181], - 131072: _quoteState_name[181:191], -} - -func (i quoteState) String() string { - if str, ok := _quoteState_map[i]; ok { - return str - } - return "quoteState(" + strconv.FormatInt(int64(i), 10) + ")" -} diff --git a/vendor/mvdan.cc/sh/syntax/simplify.go b/vendor/mvdan.cc/sh/syntax/simplify.go deleted file mode 100644 index a7644716..00000000 --- a/vendor/mvdan.cc/sh/syntax/simplify.go +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2017, Daniel Martí -// See LICENSE for licensing information - -package syntax - -import "bytes" - -// Simplify simplifies a given program and returns whether any changes -// were made. -// -// The changes currently applied are: -// -// Remove clearly useless parentheses $(( (expr) )) -// Remove dollars from vars in exprs (($var)) -// Remove duplicate subshells $( (stmts) ) -// Remove redundant quotes [[ "$var" == str ]] -// Merge negations with unary operators [[ ! -n $var ]] -// Use single quotes to shorten literals "\$foo" -func Simplify(n Node) bool { - s := simplifier{} - Walk(n, s.visit) - return s.modified -} - -type simplifier struct { - modified bool -} - -func (s *simplifier) visit(node Node) bool { - switch x := node.(type) { - case *Assign: - x.Index = s.removeParensArithm(x.Index) - // Don't inline params, as x[i] and x[$i] mean - // different things when x is an associative - // array; the first means "i", the second "$i". - case *ParamExp: - x.Index = s.removeParensArithm(x.Index) - // don't inline params - same as above. - - if x.Slice == nil { - break - } - x.Slice.Offset = s.removeParensArithm(x.Slice.Offset) - x.Slice.Offset = s.inlineSimpleParams(x.Slice.Offset) - x.Slice.Length = s.removeParensArithm(x.Slice.Length) - x.Slice.Length = s.inlineSimpleParams(x.Slice.Length) - case *ArithmExp: - x.X = s.removeParensArithm(x.X) - x.X = s.inlineSimpleParams(x.X) - case *ArithmCmd: - x.X = s.removeParensArithm(x.X) - x.X = s.inlineSimpleParams(x.X) - case *ParenArithm: - x.X = s.removeParensArithm(x.X) - x.X = s.inlineSimpleParams(x.X) - case *BinaryArithm: - x.X = s.inlineSimpleParams(x.X) - x.Y = s.inlineSimpleParams(x.Y) - case *CmdSubst: - x.Stmts = s.inlineSubshell(x.Stmts) - case *Subshell: - x.Stmts = s.inlineSubshell(x.Stmts) - case *Word: - x.Parts = s.simplifyWord(x.Parts) - case *TestClause: - x.X = s.removeParensTest(x.X) - x.X = s.removeNegateTest(x.X) - case *ParenTest: - x.X = s.removeParensTest(x.X) - x.X = s.removeNegateTest(x.X) - case *BinaryTest: - x.X = s.unquoteParams(x.X) - x.X = s.removeNegateTest(x.X) - switch x.Op { - case TsMatch, TsNoMatch: - // unquoting enables globbing - default: - x.Y = s.unquoteParams(x.Y) - } - x.Y = s.removeNegateTest(x.Y) - case *UnaryTest: - x.X = s.unquoteParams(x.X) - } - return true -} - -func (s *simplifier) simplifyWord(wps []WordPart) []WordPart { -parts: - for i, wp := range wps { - dq, _ := wp.(*DblQuoted) - if dq == nil || len(dq.Parts) != 1 { - break - } - lit, _ := dq.Parts[0].(*Lit) - if lit == nil { - break - } - var buf bytes.Buffer - escaped := false - for _, r := range lit.Value { - switch r { - case '\\': - escaped = !escaped - if escaped { - continue - } - case '\'': - continue parts - case '$', '"', '`': - escaped = false - default: - if escaped { - continue parts - } - escaped = false - } - buf.WriteRune(r) - } - newVal := buf.String() - if newVal == lit.Value { - break - } - s.modified = true - wps[i] = &SglQuoted{ - Left: dq.Pos(), - Right: dq.End(), - Dollar: dq.Dollar, - Value: newVal, - } - } - return wps -} - -func (s *simplifier) removeParensArithm(x ArithmExpr) ArithmExpr { - for { - par, _ := x.(*ParenArithm) - if par == nil { - return x - } - s.modified = true - x = par.X - } -} - -func (s *simplifier) inlineSimpleParams(x ArithmExpr) ArithmExpr { - w, _ := x.(*Word) - if w == nil || len(w.Parts) != 1 { - return x - } - pe, _ := w.Parts[0].(*ParamExp) - if pe == nil || !ValidName(pe.Param.Value) { - return x - } - if pe.Excl || pe.Length || pe.Width || pe.Slice != nil || - pe.Repl != nil || pe.Exp != nil { - return x - } - if pe.Index != nil { - s.modified = true - pe.Short = true - return w - } - s.modified = true - return &Word{Parts: []WordPart{pe.Param}} -} - -func (s *simplifier) inlineSubshell(stmts []*Stmt) []*Stmt { - for len(stmts) == 1 { - st := stmts[0] - if st.Negated || st.Background || st.Coprocess || - len(st.Redirs) > 0 { - break - } - sub, _ := st.Cmd.(*Subshell) - if sub == nil { - break - } - s.modified = true - stmts = sub.Stmts - } - return stmts -} - -func (s *simplifier) unquoteParams(x TestExpr) TestExpr { - w, _ := x.(*Word) - if w == nil || len(w.Parts) != 1 { - return x - } - dq, _ := w.Parts[0].(*DblQuoted) - if dq == nil || len(dq.Parts) != 1 { - return x - } - if _, ok := dq.Parts[0].(*ParamExp); !ok { - return x - } - s.modified = true - w.Parts = dq.Parts - return w -} - -func (s *simplifier) removeParensTest(x TestExpr) TestExpr { - for { - par, _ := x.(*ParenTest) - if par == nil { - return x - } - s.modified = true - x = par.X - } -} - -func (s *simplifier) removeNegateTest(x TestExpr) TestExpr { - u, _ := x.(*UnaryTest) - if u == nil || u.Op != TsNot { - return x - } - switch y := u.X.(type) { - case *UnaryTest: - switch y.Op { - case TsEmpStr: - y.Op = TsNempStr - s.modified = true - return y - case TsNempStr: - y.Op = TsEmpStr - s.modified = true - return y - case TsNot: - s.modified = true - return y.X - } - case *BinaryTest: - switch y.Op { - case TsMatch: - y.Op = TsNoMatch - s.modified = true - return y - case TsNoMatch: - y.Op = TsMatch - s.modified = true - return y - } - } - return x -} diff --git a/vendor/mvdan.cc/sh/syntax/token_string.go b/vendor/mvdan.cc/sh/syntax/token_string.go deleted file mode 100644 index 0327dbae..00000000 --- a/vendor/mvdan.cc/sh/syntax/token_string.go +++ /dev/null @@ -1,16 +0,0 @@ -// Code generated by "stringer -type token -linecomment -trimprefix _"; DO NOT EDIT. - -package syntax - -import "strconv" - -const _token_name = "illegalTokEOFNewlLitLitWordLitRedir'\"`&&&||||&$$'$\"${$[$($(([[[(((}])));;;;&;;&;|!++--***==!=<=>=+=-=*=/=%=&=|=^=<<=>>=>>><<><&>&>|<<<<-<<<&>&>><(>(+:+-:-?:?=:=%%%###^^^,,,@///:-e-f-d-c-b-p-S-L-k-g-u-G-O-N-r-w-x-s-t-z-n-o-v-R=~-nt-ot-ef-eq-ne-le-ge-lt-gt?(*(+(@(!(" - -var _token_index = [...]uint16{0, 10, 13, 17, 20, 27, 35, 36, 37, 38, 39, 41, 43, 44, 46, 47, 49, 51, 53, 55, 57, 60, 61, 63, 64, 66, 67, 68, 69, 71, 72, 74, 76, 79, 81, 82, 84, 86, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 116, 119, 120, 122, 123, 125, 127, 129, 131, 133, 136, 139, 141, 144, 146, 148, 149, 151, 152, 154, 155, 157, 158, 160, 161, 163, 164, 166, 167, 169, 170, 172, 173, 174, 176, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 256, 258, 260, 262, 264} - -func (i token) String() string { - if i >= token(len(_token_index)-1) { - return "token(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _token_name[_token_index[i]:_token_index[i+1]] -} diff --git a/vendor/mvdan.cc/sh/syntax/tokens.go b/vendor/mvdan.cc/sh/syntax/tokens.go deleted file mode 100644 index aff34c48..00000000 --- a/vendor/mvdan.cc/sh/syntax/tokens.go +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright (c) 2016, Daniel Martí -// See LICENSE for licensing information - -package syntax - -//go:generate stringer -type token -linecomment -trimprefix _ - -type token uint32 - -// The list of all possible tokens. -const ( - illegalTok token = iota - - _EOF - _Newl - _Lit - _LitWord - _LitRedir - - sglQuote // ' - dblQuote // " - bckQuote // ` - - and // & - andAnd // && - orOr // || - or // | - orAnd // |& - - dollar // $ - dollSglQuote // $' - dollDblQuote // $" - dollBrace // ${ - dollBrack // $[ - dollParen // $( - dollDblParen // $(( - leftBrack // [ - dblLeftBrack // [[ - leftParen // ( - dblLeftParen // (( - - rightBrace // } - rightBrack // ] - rightParen // ) - dblRightParen // )) - semicolon // ; - - dblSemicolon // ;; - semiAnd // ;& - dblSemiAnd // ;;& - semiOr // ;| - - exclMark // ! - addAdd // ++ - subSub // -- - star // * - power // ** - equal // == - nequal // != - lequal // <= - gequal // >= - - addAssgn // += - subAssgn // -= - mulAssgn // *= - quoAssgn // /= - remAssgn // %= - andAssgn // &= - orAssgn // |= - xorAssgn // ^= - shlAssgn // <<= - shrAssgn // >>= - - rdrOut // > - appOut // >> - rdrIn // < - rdrInOut // <> - dplIn // <& - dplOut // >& - clbOut // >| - hdoc // << - dashHdoc // <<- - wordHdoc // <<< - rdrAll // &> - appAll // &>> - - cmdIn // <( - cmdOut // >( - - plus // + - colPlus // :+ - minus // - - colMinus // :- - quest // ? - colQuest // :? - assgn // = - colAssgn // := - perc // % - dblPerc // %% - hash // # - dblHash // ## - caret // ^ - dblCaret // ^^ - comma // , - dblComma // ,, - at // @ - slash // / - dblSlash // // - colon // : - - tsExists // -e - tsRegFile // -f - tsDirect // -d - tsCharSp // -c - tsBlckSp // -b - tsNmPipe // -p - tsSocket // -S - tsSmbLink // -L - tsSticky // -k - tsGIDSet // -g - tsUIDSet // -u - tsGrpOwn // -G - tsUsrOwn // -O - tsModif // -N - tsRead // -r - tsWrite // -w - tsExec // -x - tsNoEmpty // -s - tsFdTerm // -t - tsEmpStr // -z - tsNempStr // -n - tsOptSet // -o - tsVarSet // -v - tsRefVar // -R - - tsReMatch // =~ - tsNewer // -nt - tsOlder // -ot - tsDevIno // -ef - tsEql // -eq - tsNeq // -ne - tsLeq // -le - tsGeq // -ge - tsLss // -lt - tsGtr // -gt - - globQuest // ?( - globStar // *( - globPlus // +( - globAt // @( - globExcl // !( -) - -type RedirOperator token - -const ( - RdrOut = RedirOperator(rdrOut) + iota - AppOut - RdrIn - RdrInOut - DplIn - DplOut - ClbOut - Hdoc - DashHdoc - WordHdoc - RdrAll - AppAll -) - -type ProcOperator token - -const ( - CmdIn = ProcOperator(cmdIn) + iota - CmdOut -) - -type GlobOperator token - -const ( - GlobQuest = GlobOperator(globQuest) + iota - GlobStar - GlobPlus - GlobAt - GlobExcl -) - -type BinCmdOperator token - -const ( - AndStmt = BinCmdOperator(andAnd) + iota - OrStmt - Pipe - PipeAll -) - -type CaseOperator token - -const ( - Break = CaseOperator(dblSemicolon) + iota - Fallthrough - Resume - ResumeKorn -) - -type ParNamesOperator token - -const ( - NamesPrefix = ParNamesOperator(star) - NamesPrefixWords = ParNamesOperator(at) -) - -type ParExpOperator token - -const ( - SubstPlus = ParExpOperator(plus) + iota - SubstColPlus - SubstMinus - SubstColMinus - SubstQuest - SubstColQuest - SubstAssgn - SubstColAssgn - RemSmallSuffix - RemLargeSuffix - RemSmallPrefix - RemLargePrefix - UpperFirst - UpperAll - LowerFirst - LowerAll - OtherParamOps -) - -type UnAritOperator token - -const ( - Not = UnAritOperator(exclMark) + iota - Inc - Dec - Plus = UnAritOperator(plus) - Minus = UnAritOperator(minus) -) - -type BinAritOperator token - -const ( - Add = BinAritOperator(plus) - Sub = BinAritOperator(minus) - Mul = BinAritOperator(star) - Quo = BinAritOperator(slash) - Rem = BinAritOperator(perc) - Pow = BinAritOperator(power) - Eql = BinAritOperator(equal) - Gtr = BinAritOperator(rdrOut) - Lss = BinAritOperator(rdrIn) - Neq = BinAritOperator(nequal) - Leq = BinAritOperator(lequal) - Geq = BinAritOperator(gequal) - And = BinAritOperator(and) - Or = BinAritOperator(or) - Xor = BinAritOperator(caret) - Shr = BinAritOperator(appOut) - Shl = BinAritOperator(hdoc) - - AndArit = BinAritOperator(andAnd) - OrArit = BinAritOperator(orOr) - Comma = BinAritOperator(comma) - Quest = BinAritOperator(quest) - Colon = BinAritOperator(colon) - - Assgn = BinAritOperator(assgn) - AddAssgn = BinAritOperator(addAssgn) - SubAssgn = BinAritOperator(subAssgn) - MulAssgn = BinAritOperator(mulAssgn) - QuoAssgn = BinAritOperator(quoAssgn) - RemAssgn = BinAritOperator(remAssgn) - AndAssgn = BinAritOperator(andAssgn) - OrAssgn = BinAritOperator(orAssgn) - XorAssgn = BinAritOperator(xorAssgn) - ShlAssgn = BinAritOperator(shlAssgn) - ShrAssgn = BinAritOperator(shrAssgn) -) - -type UnTestOperator token - -const ( - TsExists = UnTestOperator(tsExists) + iota - TsRegFile - TsDirect - TsCharSp - TsBlckSp - TsNmPipe - TsSocket - TsSmbLink - TsSticky - TsGIDSet - TsUIDSet - TsGrpOwn - TsUsrOwn - TsModif - TsRead - TsWrite - TsExec - TsNoEmpty - TsFdTerm - TsEmpStr - TsNempStr - TsOptSet - TsVarSet - TsRefVar - TsNot = UnTestOperator(exclMark) -) - -type BinTestOperator token - -const ( - TsReMatch = BinTestOperator(tsReMatch) + iota - TsNewer - TsOlder - TsDevIno - TsEql - TsNeq - TsLeq - TsGeq - TsLss - TsGtr - AndTest = BinTestOperator(andAnd) - OrTest = BinTestOperator(orOr) - TsMatch = BinTestOperator(equal) - TsNoMatch = BinTestOperator(nequal) - TsBefore = BinTestOperator(rdrIn) - TsAfter = BinTestOperator(rdrOut) -) - -func (o RedirOperator) String() string { return token(o).String() } -func (o ProcOperator) String() string { return token(o).String() } -func (o GlobOperator) String() string { return token(o).String() } -func (o BinCmdOperator) String() string { return token(o).String() } -func (o CaseOperator) String() string { return token(o).String() } -func (o ParNamesOperator) String() string { return token(o).String() } -func (o ParExpOperator) String() string { return token(o).String() } -func (o UnAritOperator) String() string { return token(o).String() } -func (o BinAritOperator) String() string { return token(o).String() } -func (o UnTestOperator) String() string { return token(o).String() } -func (o BinTestOperator) String() string { return token(o).String() } diff --git a/vendor/mvdan.cc/sh/syntax/walk.go b/vendor/mvdan.cc/sh/syntax/walk.go deleted file mode 100644 index 1192d575..00000000 --- a/vendor/mvdan.cc/sh/syntax/walk.go +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright (c) 2016, Daniel Martí -// See LICENSE for licensing information - -package syntax - -import ( - "fmt" - "io" - "reflect" -) - -func walkStmts(sl StmtList, f func(Node) bool) { - for _, s := range sl.Stmts { - Walk(s, f) - } - for _, c := range sl.Last { - Walk(&c, f) - } -} - -func walkWords(words []*Word, f func(Node) bool) { - for _, w := range words { - Walk(w, f) - } -} - -// Walk traverses a syntax tree in depth-first order: It starts by calling -// f(node); node must not be nil. If f returns true, Walk invokes f -// recursively for each of the non-nil children of node, followed by -// f(nil). -func Walk(node Node, f func(Node) bool) { - if !f(node) { - return - } - - switch x := node.(type) { - case *File: - walkStmts(x.StmtList, f) - case *Comment: - case *Stmt: - for _, c := range x.Comments { - if !x.End().After(c.Pos()) { - defer Walk(&c, f) - break - } - Walk(&c, f) - } - if x.Cmd != nil { - Walk(x.Cmd, f) - } - for _, r := range x.Redirs { - Walk(r, f) - } - case *Assign: - if x.Name != nil { - Walk(x.Name, f) - } - if x.Value != nil { - Walk(x.Value, f) - } - if x.Index != nil { - Walk(x.Index, f) - } - if x.Array != nil { - Walk(x.Array, f) - } - case *Redirect: - if x.N != nil { - Walk(x.N, f) - } - Walk(x.Word, f) - if x.Hdoc != nil { - Walk(x.Hdoc, f) - } - case *CallExpr: - for _, a := range x.Assigns { - Walk(a, f) - } - walkWords(x.Args, f) - case *Subshell: - walkStmts(x.StmtList, f) - case *Block: - walkStmts(x.StmtList, f) - case *IfClause: - walkStmts(x.Cond, f) - walkStmts(x.Then, f) - walkStmts(x.Else, f) - case *WhileClause: - walkStmts(x.Cond, f) - walkStmts(x.Do, f) - case *ForClause: - Walk(x.Loop, f) - walkStmts(x.Do, f) - case *WordIter: - Walk(x.Name, f) - walkWords(x.Items, f) - case *CStyleLoop: - if x.Init != nil { - Walk(x.Init, f) - } - if x.Cond != nil { - Walk(x.Cond, f) - } - if x.Post != nil { - Walk(x.Post, f) - } - case *BinaryCmd: - Walk(x.X, f) - Walk(x.Y, f) - case *FuncDecl: - Walk(x.Name, f) - Walk(x.Body, f) - case *Word: - for _, wp := range x.Parts { - Walk(wp, f) - } - case *Lit: - case *SglQuoted: - case *DblQuoted: - for _, wp := range x.Parts { - Walk(wp, f) - } - case *CmdSubst: - walkStmts(x.StmtList, f) - case *ParamExp: - Walk(x.Param, f) - if x.Index != nil { - Walk(x.Index, f) - } - if x.Repl != nil { - if x.Repl.Orig != nil { - Walk(x.Repl.Orig, f) - } - if x.Repl.With != nil { - Walk(x.Repl.With, f) - } - } - if x.Exp != nil && x.Exp.Word != nil { - Walk(x.Exp.Word, f) - } - case *ArithmExp: - Walk(x.X, f) - case *ArithmCmd: - Walk(x.X, f) - case *BinaryArithm: - Walk(x.X, f) - Walk(x.Y, f) - case *BinaryTest: - Walk(x.X, f) - Walk(x.Y, f) - case *UnaryArithm: - Walk(x.X, f) - case *UnaryTest: - Walk(x.X, f) - case *ParenArithm: - Walk(x.X, f) - case *ParenTest: - Walk(x.X, f) - case *CaseClause: - Walk(x.Word, f) - for _, ci := range x.Items { - Walk(ci, f) - } - for _, c := range x.Last { - Walk(&c, f) - } - case *CaseItem: - for _, c := range x.Comments { - if c.Pos().After(x.Pos()) { - defer Walk(&c, f) - break - } - Walk(&c, f) - } - walkWords(x.Patterns, f) - walkStmts(x.StmtList, f) - case *TestClause: - Walk(x.X, f) - case *DeclClause: - walkWords(x.Opts, f) - for _, a := range x.Assigns { - Walk(a, f) - } - case *ArrayExpr: - for _, el := range x.Elems { - Walk(el, f) - } - for _, c := range x.Last { - Walk(&c, f) - } - case *ArrayElem: - for _, c := range x.Comments { - if c.Pos().After(x.Pos()) { - defer Walk(&c, f) - break - } - Walk(&c, f) - } - if x.Index != nil { - Walk(x.Index, f) - } - Walk(x.Value, f) - case *ExtGlob: - Walk(x.Pattern, f) - case *ProcSubst: - walkStmts(x.StmtList, f) - case *TimeClause: - if x.Stmt != nil { - Walk(x.Stmt, f) - } - case *CoprocClause: - if x.Name != nil { - Walk(x.Name, f) - } - Walk(x.Stmt, f) - case *LetClause: - for _, expr := range x.Exprs { - Walk(expr, f) - } - default: - panic(fmt.Sprintf("syntax.Walk: unexpected node type %T", x)) - } - - f(nil) -} - -// DebugPrint prints the provided syntax tree, spanning multiple lines and with -// indentation. Can be useful to investigate the content of a syntax tree. -func DebugPrint(w io.Writer, node Node) error { - p := debugPrinter{out: w} - p.print(reflect.ValueOf(node)) - return p.err -} - -type debugPrinter struct { - out io.Writer - level int - err error -} - -func (p *debugPrinter) printf(format string, args ...interface{}) { - _, err := fmt.Fprintf(p.out, format, args...) - if err != nil && p.err == nil { - p.err = err - } -} - -func (p *debugPrinter) newline() { - p.printf("\n") - for i := 0; i < p.level; i++ { - p.printf(". ") - } -} - -func (p *debugPrinter) print(x reflect.Value) { - switch x.Kind() { - case reflect.Interface: - if x.IsNil() { - p.printf("nil") - return - } - p.print(x.Elem()) - case reflect.Ptr: - if x.IsNil() { - p.printf("nil") - return - } - p.printf("*") - p.print(x.Elem()) - case reflect.Slice: - p.printf("%s (len = %d) {", x.Type(), x.Len()) - if x.Len() > 0 { - p.level++ - p.newline() - for i := 0; i < x.Len(); i++ { - p.printf("%d: ", i) - p.print(x.Index(i)) - if i == x.Len()-1 { - p.level-- - } - p.newline() - } - } - p.printf("}") - - case reflect.Struct: - if v, ok := x.Interface().(Pos); ok { - p.printf("%v:%v", v.Line(), v.Col()) - return - } - t := x.Type() - p.printf("%s {", t) - p.level++ - p.newline() - for i := 0; i < t.NumField(); i++ { - p.printf("%s: ", t.Field(i).Name) - p.print(x.Field(i)) - if i == x.NumField()-1 { - p.level-- - } - p.newline() - } - p.printf("}") - default: - p.printf("%#v", x.Interface()) - } -}