mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51aa00e741 | ||
|
|
ffe4c490fd | ||
|
|
702c2371a4 | ||
|
|
776f416ed1 | ||
|
|
ce98ce8b37 | ||
|
|
de8c6cb9b5 | ||
|
|
6afdad6d50 | ||
|
|
d699a8d140 | ||
|
|
77148d0d31 | ||
|
|
c7e77c8f8b | ||
|
|
5e6148b193 | ||
|
|
7b12e893a5 | ||
|
|
a619b59329 | ||
|
|
ead23d8bf6 | ||
|
|
7e6abd2211 | ||
|
|
4052093d57 | ||
|
|
a676cb328a | ||
|
|
1be1904c4d | ||
|
|
ead6b48b74 | ||
|
|
5d8ee38cb4 | ||
|
|
cef27f6511 | ||
|
|
dd91251710 | ||
|
|
d13e3fc61e | ||
|
|
cfb292d6d6 |
No files matched your search
@@ -47,6 +47,15 @@ LDFLAGS = -ldflags "-s -w \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
# Debug builds can't be stripped (-s -w flags)
|
||||
LDFLAGS_DEBUG = -ldflags "-X $(VERSION_PKG).Version=$(VERSION) \
|
||||
-X \"$(VERSION_PKG).GoVersion=$(GO_VERSION)\" \
|
||||
-X $(VERSION_PKG).CompiledAt=$(COMPILED_AT) \
|
||||
-X $(VERSION_PKG).GithubReleasesURL=$(RELEASES_URL) \
|
||||
-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PKG).GitDirty=$(GIT_DIRTY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUB_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
STATIC_TARGET := linux
|
||||
@@ -146,6 +155,11 @@ pb:
|
||||
protoc -I protobuf/ protobuf/dnspb/dns.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=paths=source_relative:protobuf/ --go-grpc_out=protobuf/ --go-grpc_opt=paths=source_relative
|
||||
|
||||
.PHONY: debug
|
||||
debug: clean
|
||||
$(ENV) $(GO) build -mod=vendor $(TAGS),server $(LDFLAGS_DEBUG) -o sliver-server$(ARTIFACT_SUFFIX) ./server
|
||||
$(ENV) $(GO) build -mod=vendor $(TAGS),client $(LDFLAGS_DEBUG) -o sliver-client$(ARTIFACT_SUFFIX) ./client
|
||||
|
||||
validate-go-version:
|
||||
@if [ $(GO_MAJOR_VERSION) -gt $(MIN_SUPPORTED_GO_MAJOR_VERSION) ]; then \
|
||||
exit 0 ;\
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@ Anything in the `master` branch, without an open ticket.
|
||||
|
||||
### Reporting a Vulnerability
|
||||
|
||||
Please report high and/or critical vulnerabilities via email `jdemesy<[a]>bishopfox.com`, if no response within 14 days, file a ticket with a `security` tag. Medium to Low risk vulnerabilities can simply be filed as a normal bug/issue with the `security` tag.
|
||||
Please report high and/or critical vulnerabilities via GitHub's private [report a vulnerability feature](https://github.com/BishopFox/sliver/security/advisories).
|
||||
@@ -1965,6 +1965,7 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
f.Bool("4", "ip4", true, "display information about IPv4 sockets")
|
||||
f.Bool("6", "ip6", false, "display information about IPv6 sockets")
|
||||
f.Bool("l", "listen", false, "display information about listening sockets")
|
||||
f.Bool("n", "numeric", false, "display numeric addresses (disable hostname resolution)")
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
},
|
||||
HelpGroup: consts.SliverHelpGroup,
|
||||
|
||||
@@ -44,6 +44,7 @@ func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
ip6 := ctx.Flags.Bool("ip6")
|
||||
tcp := ctx.Flags.Bool("tcp")
|
||||
udp := ctx.Flags.Bool("udp")
|
||||
numeric := ctx.Flags.Bool("numeric")
|
||||
|
||||
implantPID := getPID(session, beacon)
|
||||
activeC2 := getActiveC2(session, beacon)
|
||||
@@ -67,25 +68,21 @@ func NetstatCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintNetstat(netstat, implantPID, activeC2, con)
|
||||
PrintNetstat(netstat, implantPID, activeC2, numeric, con)
|
||||
})
|
||||
con.PrintAsyncResponse(netstat.Response)
|
||||
} else {
|
||||
PrintNetstat(netstat, implantPID, activeC2, con)
|
||||
PrintNetstat(netstat, implantPID, activeC2, numeric, con)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string, con *console.SliverConsoleClient) {
|
||||
func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string, numeric bool, con *console.SliverConsoleClient) {
|
||||
lookup := func(skaddr *sliverpb.SockTabEntry_SockAddr) string {
|
||||
const IPv4Strlen = 17
|
||||
addr := skaddr.Ip
|
||||
names, err := net.LookupAddr(addr)
|
||||
if err == nil && len(names) > 0 {
|
||||
addr = names[0]
|
||||
}
|
||||
if len(addr) > IPv4Strlen {
|
||||
addr = addr[:IPv4Strlen]
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", addr, skaddr.Port)
|
||||
}
|
||||
|
||||
@@ -98,8 +95,12 @@ func PrintNetstat(netstat *sliverpb.Netstat, implantPID int32, activeC2 string,
|
||||
if entry.Process != nil {
|
||||
pid = fmt.Sprintf("%d/%s", entry.Process.Pid, entry.Process.Executable)
|
||||
}
|
||||
srcAddr := lookup(entry.LocalAddr)
|
||||
dstAddr := lookup(entry.RemoteAddr)
|
||||
srcAddr := fmt.Sprintf("%s:%d", entry.LocalAddr.Ip, entry.LocalAddr.Port)
|
||||
dstAddr := fmt.Sprintf("%s:%d", entry.RemoteAddr.Ip, entry.RemoteAddr.Port)
|
||||
if !numeric {
|
||||
srcAddr = lookup(entry.LocalAddr)
|
||||
dstAddr = lookup(entry.RemoteAddr)
|
||||
}
|
||||
if entry.Process != nil && entry.Process.Pid == implantPID {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(console.Green+"%s"+console.Normal, entry.Protocol),
|
||||
|
||||
@@ -30,8 +30,9 @@ import (
|
||||
|
||||
// TerminateCmd - Terminate a process on the remote system
|
||||
func TerminateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
if session == nil {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
con.PrintErrorf("No active session or beacon\n")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -432,7 +432,7 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
|
||||
con.PrintErrorf("Failed to fetch beacon: %s\n", err)
|
||||
return
|
||||
}
|
||||
network.PrintNetstat(netstat, beacon.PID, beacon.ActiveC2, con)
|
||||
network.PrintNetstat(netstat, beacon.PID, beacon.ActiveC2, false, con)
|
||||
|
||||
// ---------------------
|
||||
// Privilege commands
|
||||
|
||||
@@ -10,7 +10,7 @@ require (
|
||||
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
|
||||
github.com/Binject/universal v0.0.0-20210304094126-daefaa886313
|
||||
github.com/Ne0nd0g/go-clr v1.0.2
|
||||
github.com/Ne0nd0g/go-clr v1.0.3
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.0
|
||||
github.com/chromedp/cdproto v0.0.0-20220924210414-0e3390be1777
|
||||
@@ -20,11 +20,11 @@ require (
|
||||
github.com/desertbit/grumble v1.1.1
|
||||
github.com/desertbit/readline v1.5.1
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/gofrs/uuid v4.3.0+incompatible
|
||||
github.com/gofrs/uuid v4.3.1+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.2
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
github.com/lesnuages/snitch v0.6.0
|
||||
@@ -32,11 +32,12 @@ require (
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/moloch--/memmod v0.0.0-20211120144554-8b37cc654945
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.9
|
||||
github.com/shirou/gopsutil/v3 v3.22.10
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/things-go/go-socks5 v0.0.3-0.20210722055343-24af464efe43
|
||||
github.com/xlab/treeprint v1.1.0
|
||||
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
|
||||
@@ -48,7 +49,7 @@ require (
|
||||
google.golang.org/grpc v1.42.0-dev.0.20211020220737-f00baa6c3c84
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gorm.io/driver/mysql v1.4.3
|
||||
gorm.io/driver/mysql v1.4.4
|
||||
gorm.io/driver/postgres v1.4.5
|
||||
gorm.io/driver/sqlite v1.4.3
|
||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755
|
||||
@@ -109,7 +110,6 @@ require (
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 // indirect
|
||||
github.com/xlab/treeprint v1.1.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
|
||||
@@ -23,8 +23,8 @@ github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030I
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Ne0nd0g/go-clr v1.0.2 h1:jVD7iJyXaS3KhPiKetUnUAuLxgDlNSvYmPfMFK8E3VY=
|
||||
github.com/Ne0nd0g/go-clr v1.0.2/go.mod h1:TKYSQ/5xT25EvBUttAlUrzpR8yHuI0qTRK495I5xG/I=
|
||||
github.com/Ne0nd0g/go-clr v1.0.3 h1:xt92wwuqY23ZSC7RuHD3mKu3K22Bk5NNbxI803vojK4=
|
||||
github.com/Ne0nd0g/go-clr v1.0.3/go.mod h1:TKYSQ/5xT25EvBUttAlUrzpR8yHuI0qTRK495I5xG/I=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/Netflix/go-expect v0.0.0-20190729225929-0e00d9168667/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||
@@ -122,8 +122,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
||||
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
|
||||
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI=
|
||||
github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -228,8 +228,8 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.0 h1:YlI/2zYDrweA4MThiYMKtGRfT+2qZOO65ulej8GTcVI=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.0/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.2 h1:DcJNSNIb1E17Tvy9w9S7z+sExvWvvjNbFdyr6C+FUL0=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.2/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
@@ -332,8 +332,8 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shirou/gopsutil/v3 v3.22.9 h1:yibtJhIVEMcdw+tCTbOPiF1VcsuDeTE4utJ8Dm4c5eA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A=
|
||||
github.com/shirou/gopsutil/v3 v3.22.10 h1:4KMHdfBRYXGF9skjDWiL4RA2N+E8dRdodU/bOZpPoVg=
|
||||
github.com/shirou/gopsutil/v3 v3.22.10/go.mod h1:QNza6r4YQoydyCfo6rH0blGfKahgibh4dQmV5xdFkQk=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
@@ -604,8 +604,8 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
|
||||
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
||||
gorm.io/driver/mysql v1.4.4 h1:MX0K9Qvy0Na4o7qSC/YI7XxqUw5KDw01umqgID+svdQ=
|
||||
gorm.io/driver/mysql v1.4.4/go.mod h1:BCg8cKI+R0j/rZRQxeKis/forqRwRSYOR8OM3Wo6hOM=
|
||||
gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
|
||||
gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
|
||||
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
|
||||
|
||||
@@ -530,7 +530,16 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
execResp := &sliverpb.Execute{}
|
||||
cmd := exec.Command(execReq.Path, execReq.Args...)
|
||||
exePath, err := expandPath(execReq.Path)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(exePath, execReq.Args...)
|
||||
|
||||
if execReq.Output {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
@@ -929,3 +938,13 @@ func compressDir(path string, filter string, recurse bool, buf io.Writer) (int,
|
||||
}
|
||||
return readFiles, unreadableFiles, nil
|
||||
}
|
||||
|
||||
func expandPath(exePath string) (string, error) {
|
||||
if !strings.ContainsRune(exePath, os.PathSeparator) {
|
||||
_, err := exec.LookPath(exePath)
|
||||
if err != nil {
|
||||
return filepath.Abs(exePath)
|
||||
}
|
||||
}
|
||||
return exePath, nil
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"github.com/bishopfox/sliver/implant/sliver/registry"
|
||||
"github.com/bishopfox/sliver/implant/sliver/service"
|
||||
"github.com/bishopfox/sliver/implant/sliver/spoof"
|
||||
"github.com/bishopfox/sliver/implant/sliver/syscalls"
|
||||
"github.com/bishopfox/sliver/implant/sliver/taskrunner"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -120,6 +122,28 @@ func GetSystemHandlers() map[uint32]RPCHandler {
|
||||
return windowsHandlers
|
||||
}
|
||||
|
||||
func WrapperHandler(handler RPCHandler, data []byte, resp RPCResponse) {
|
||||
if priv.CurrentToken != 0 {
|
||||
err := syscalls.ImpersonateLoggedOnUser(priv.CurrentToken)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
}
|
||||
handler(data, resp)
|
||||
if priv.CurrentToken != 0 {
|
||||
err := priv.TRevertToSelf()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Windows Handlers ----------------
|
||||
|
||||
func impersonateHandler(data []byte, resp RPCResponse) {
|
||||
@@ -279,7 +303,16 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
|
||||
execResp := &sliverpb.Execute{}
|
||||
cmd := exec.Command(execReq.Path, execReq.Args...)
|
||||
exePath, err := expandPath(execReq.Path)
|
||||
if err != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
}
|
||||
proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(exePath, execReq.Args...)
|
||||
|
||||
// Execute with current token
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
|
||||
@@ -281,11 +281,11 @@ func buildEntries(proto string, s []netstat.SockTabEntry) []*sliverpb.SockTabEnt
|
||||
}
|
||||
entries = append(entries, &sliverpb.SockTabEntry{
|
||||
LocalAddr: &sliverpb.SockTabEntry_SockAddr{
|
||||
Ip: e.LocalAddr.String(),
|
||||
Ip: e.LocalAddr.IP.String(),
|
||||
Port: uint32(e.LocalAddr.Port),
|
||||
},
|
||||
RemoteAddr: &sliverpb.SockTabEntry_SockAddr{
|
||||
Ip: e.RemoteAddr.String(),
|
||||
Ip: e.RemoteAddr.IP.String(),
|
||||
Port: uint32(e.RemoteAddr.Port),
|
||||
},
|
||||
SkState: e.State.String(),
|
||||
|
||||
@@ -96,7 +96,23 @@ func SePrivEnable(s string) error {
|
||||
}
|
||||
|
||||
func RevertToSelf() error {
|
||||
err := windows.RevertToSelf()
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("RevertToSelf Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
err = windows.CloseHandle(windows.Handle(CurrentToken))
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("CloseHandle Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
CurrentToken = windows.Token(0)
|
||||
return err
|
||||
}
|
||||
|
||||
func TRevertToSelf() error {
|
||||
return windows.RevertToSelf()
|
||||
}
|
||||
|
||||
|
||||
+39
-18
@@ -42,17 +42,11 @@ import (
|
||||
"io/ioutil"
|
||||
// {{end}}
|
||||
|
||||
// {{if eq .Config.GOOS "windows"}}
|
||||
"github.com/bishopfox/sliver/implant/sliver/priv"
|
||||
"github.com/bishopfox/sliver/implant/sliver/syscalls"
|
||||
|
||||
// {{end}}
|
||||
|
||||
consts "github.com/bishopfox/sliver/implant/sliver/constants"
|
||||
"github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
"github.com/bishopfox/sliver/implant/sliver/hostuuid"
|
||||
"github.com/bishopfox/sliver/implant/sliver/locale"
|
||||
"github.com/bishopfox/sliver/implant/sliver/limits"
|
||||
"github.com/bishopfox/sliver/implant/sliver/locale"
|
||||
"github.com/bishopfox/sliver/implant/sliver/pivots"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports"
|
||||
"github.com/bishopfox/sliver/implant/sliver/version"
|
||||
@@ -127,6 +121,7 @@ func (serv *sliverService) Execute(args []string, r <-chan svc.ChangeRequest, ch
|
||||
var isRunning bool = false
|
||||
|
||||
// StartW - Export for shared lib build
|
||||
//
|
||||
//export StartW
|
||||
func StartW() {
|
||||
if !isRunning {
|
||||
@@ -139,21 +134,25 @@ func StartW() {
|
||||
//https://github.com/Ne0nd0g/merlin/blob/master/cmd/merlinagentdll/main.go#L65
|
||||
|
||||
// VoidFunc is an exported function used with PowerSploit's Invoke-ReflectivePEInjection.ps1
|
||||
//
|
||||
//export VoidFunc
|
||||
func VoidFunc() { main() }
|
||||
|
||||
// DllInstall is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /n /i sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb759846(v=vs.85).aspx
|
||||
//
|
||||
//export DllInstall
|
||||
func DllInstall() { main() }
|
||||
|
||||
// DllRegisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682162(v=vs.85).aspx
|
||||
//
|
||||
//export DllRegisterServer
|
||||
func DllRegisterServer() { main() }
|
||||
|
||||
// DllUnregisterServer - is used when executing the Sliver implant with regsvr32.exe (i.e. regsvr32.exe /s /u sliver.dll)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms691457(v=vs.85).aspx
|
||||
//
|
||||
//export DllUnregisterServer
|
||||
func DllUnregisterServer() { main() }
|
||||
|
||||
@@ -409,6 +408,23 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
|
||||
wg.Add(1)
|
||||
data := task.Data
|
||||
taskID := task.ID
|
||||
// {{if eq .Config.GOOS "windows" }}
|
||||
go handlers.WrapperHandler(handler, data, func(data []byte, err error) {
|
||||
resultsMutex.Lock()
|
||||
defer resultsMutex.Unlock()
|
||||
defer wg.Done()
|
||||
// {{if .Config.Debug}}
|
||||
if err != nil {
|
||||
log.Printf("[beacon] handler function returned an error: %s", err)
|
||||
}
|
||||
log.Printf("[beacon] task completed (id: %d)", taskID)
|
||||
// {{end}}
|
||||
results = append(results, &sliverpb.Envelope{
|
||||
ID: taskID,
|
||||
Data: data,
|
||||
})
|
||||
})
|
||||
// {{else}}
|
||||
go handler(data, func(data []byte, err error) {
|
||||
resultsMutex.Lock()
|
||||
defer resultsMutex.Unlock()
|
||||
@@ -424,6 +440,7 @@ func beaconMain(beacon *transports.Beacon, nextCheckin time.Time) error {
|
||||
Data: data,
|
||||
})
|
||||
})
|
||||
// {{end}}
|
||||
} else if task.Type == sliverpb.MsgOpenSession {
|
||||
go openSessionHandler(task.Data)
|
||||
resultsMutex.Lock()
|
||||
@@ -566,20 +583,23 @@ func sessionMainLoop(connection *transports.Connection) error {
|
||||
// only applies the token to the calling thread, we need to call it before every task.
|
||||
// It's fucking gross to do that here, but I could not come with a better solution.
|
||||
|
||||
// {{if eq .Config.GOOS "windows" }}
|
||||
if priv.CurrentToken != 0 {
|
||||
err := syscalls.ImpersonateLoggedOnUser(priv.CurrentToken)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("Error: %v\n", err)
|
||||
// {{end}}
|
||||
}
|
||||
}
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[recv] sysHandler %d", envelope.Type)
|
||||
// {{end}}
|
||||
|
||||
// {{if eq .Config.GOOS "windows" }}
|
||||
go handlers.WrapperHandler(handler, envelope.Data, func(data []byte, err error) {
|
||||
// {{if .Config.Debug}}
|
||||
if err != nil {
|
||||
log.Printf("[session] handler function returned an error: %s", err)
|
||||
}
|
||||
// {{end}}
|
||||
connection.Send <- &sliverpb.Envelope{
|
||||
ID: envelope.ID,
|
||||
Data: data,
|
||||
}
|
||||
})
|
||||
// {{else}}
|
||||
go handler(envelope.Data, func(data []byte, err error) {
|
||||
// {{if .Config.Debug}}
|
||||
if err != nil {
|
||||
@@ -591,6 +611,7 @@ func sessionMainLoop(connection *transports.Connection) error {
|
||||
Data: data,
|
||||
}
|
||||
})
|
||||
// {{end}}
|
||||
} else if handler, ok := tunHandlers[envelope.Type]; ok {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[recv] tunHandler %d", envelope.Type)
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/codenames"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/generate"
|
||||
@@ -77,6 +78,13 @@ func (rpc *Server) Backdoor(ctx context.Context, req *sliverpb.BackdoorReq) (*sl
|
||||
return nil, fmt.Errorf("please select a profile targeting a shellcode format")
|
||||
}
|
||||
|
||||
if p.Config.Name == "" {
|
||||
p.Config.Name, err = codenames.GetCodename()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
name, config := generate.ImplantConfigFromProtobuf(p.Config)
|
||||
otpSecret, _ := cryptography.TOTPServerSecret()
|
||||
err = generate.GenerateConfig(name, config, true)
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 1.0.3 2022-11-10
|
||||
|
||||
## Changed
|
||||
|
||||
- Merged [Pull 3](https://github.com/Ne0nd0g/go-clr/pull/3) from @mec07 to return errors instead of exiting the program
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 4](https://github.com/Ne0nd0g/go-clr/issues/4) - Application's Without A Console Will Not Return Output
|
||||
|
||||
## 1.0.2 2022-04-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Merged [Pull 2](https://github.com/Ne0nd0g/go-clr/pull/2) from @audibleblink that fixed an error when attempting to
|
||||
load correctly targeted assemblies through
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded supporting Go modules
|
||||
|
||||
## 1.0.1 2021-04-08
|
||||
|
||||
- Learned how to correctly use tags and updated import
|
||||
|
||||
## 1.0.0 2021-04-08
|
||||
|
||||
- Initial tagged version from forked
|
||||
|
||||
### Added
|
||||
|
||||
- Added buffer to collect redirected STDOUT/STDERR
|
||||
+21
-10
@@ -1,6 +1,7 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// go-clr is a PoC package that wraps Windows syscalls necessary to load and the CLR into the current process and
|
||||
// Package clr is a PoC package that wraps Windows syscalls necessary to load and the CLR into the current process and
|
||||
// execute a managed DLL from disk or a managed EXE from memory
|
||||
package clr
|
||||
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// GetInstallRuntimes is a wrapper function that returns an array of installed runtimes. Requires an existing ICLRMetaHost
|
||||
// GetInstalledRuntimes is a wrapper function that returns an array of installed runtimes. Requires an existing ICLRMetaHost
|
||||
func GetInstalledRuntimes(metahost *ICLRMetaHost) ([]string, error) {
|
||||
var runtimes []string
|
||||
enumICLRRuntimeInfo, err := metahost.EnumerateInstalledRuntimes()
|
||||
@@ -89,16 +90,26 @@ func ExecuteDLLFromDisk(targetRuntime, dllpath, typeName, methodName, argument s
|
||||
}
|
||||
|
||||
pDLLPath, err := syscall.UTF16PtrFromString(dllpath)
|
||||
must(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pTypeName, err := syscall.UTF16PtrFromString(typeName)
|
||||
must(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pMethodName, err := syscall.UTF16PtrFromString(methodName)
|
||||
must(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pArgument, err := syscall.UTF16PtrFromString(argument)
|
||||
must(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := runtimeHost.ExecuteInDefaultAppDomain(pDLLPath, pTypeName, pMethodName, pArgument)
|
||||
must(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if *ret != 0 {
|
||||
return int16(*ret), fmt.Errorf("the ICLRRuntimeHost::ExecuteInDefaultAppDomain method returned a non-zero return value: %d", *ret)
|
||||
}
|
||||
@@ -200,7 +211,7 @@ func ExecuteByteArray(targetRuntime string, rawBytes []byte, params []string) (r
|
||||
|
||||
// LoadCLR loads the target runtime into the current process and returns the runtimehost
|
||||
// The intended purpose is for the runtimehost to be reused for subsequent operations
|
||||
// throught the duration of the program. Commonly used with C2 frameworks
|
||||
// throughout the duration of the program. Commonly used with C2 frameworks
|
||||
func LoadCLR(targetRuntime string) (runtimeHost *ICORRuntimeHost, err error) {
|
||||
if targetRuntime == "" {
|
||||
targetRuntime = "v4"
|
||||
@@ -300,7 +311,7 @@ func ExecuteByteArrayDefaultDomain(runtimeHost *ICORRuntimeHost, rawBytes []byte
|
||||
|
||||
// LoadAssembly uses a previously instantiated runtimehost and loads an assembly into the default AppDomain
|
||||
// and returns the assembly's methodInfo structure. The intended purpose is for the assembly to be loaded
|
||||
// once but executed many times throught the duration of the program. Commonly used with C2 frameworks
|
||||
// once but executed many times throughout the duration of the program. Commonly used with C2 frameworks
|
||||
func LoadAssembly(runtimeHost *ICORRuntimeHost, rawBytes []byte) (methodInfo *MethodInfo, err error) {
|
||||
appDomain, err := GetAppDomain(runtimeHost)
|
||||
if err != nil {
|
||||
@@ -319,7 +330,7 @@ func LoadAssembly(runtimeHost *ICORRuntimeHost, rawBytes []byte) (methodInfo *Me
|
||||
}
|
||||
|
||||
// InvokeAssembly uses the MethodInfo structure of a previously loaded assembly and executes it.
|
||||
// The intended purpose is for the assembly to be executed many times throught the duration of the
|
||||
// The intended purpose is for the assembly to be executed many times throughout the duration of the
|
||||
// program. Commonly used with C2 frameworks
|
||||
func InvokeAssembly(methodInfo *MethodInfo, params []string) (stdout string, stderr string) {
|
||||
var paramSafeArray *SafeArray
|
||||
|
||||
+32
-2
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package clr
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -58,13 +60,41 @@ func RedirectStdoutStderr() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Createa new reader and writer for STDERR
|
||||
// Create a new reader and writer for STDERR
|
||||
rSTDERR, wSTDERR, err = os.Pipe()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("there was an error calling the os.Pipe() function to create a new STDERR:\n%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
getConsoleWindow := kernel32.NewProc("GetConsoleWindow")
|
||||
|
||||
// Ensure the process has a console because if it doesn't there will be no output to capture
|
||||
hConsole, _, _ := getConsoleWindow.Call()
|
||||
if hConsole == 0 {
|
||||
// https://learn.microsoft.com/en-us/windows/console/allocconsole
|
||||
allocConsole := kernel32.NewProc("AllocConsole")
|
||||
// BOOL WINAPI AllocConsole(void);
|
||||
ret, _, err := allocConsole.Call()
|
||||
// A process can be associated with only one console, so the AllocConsole function fails if the calling process
|
||||
// already has a console. So long as any console exists we are good to go and therefore don't care about errors
|
||||
if ret == 0 {
|
||||
return fmt.Errorf("there was an error calling kernel32!AllocConsole with return code %d: %s", ret, err)
|
||||
}
|
||||
|
||||
// Get a handle to the newly created/allocated console
|
||||
hConsole, _, _ = getConsoleWindow.Call()
|
||||
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
showWindow := user32.NewProc("ShowWindow")
|
||||
// Hide the console window
|
||||
ret, _, err = showWindow.Call(hConsole, windows.SW_HIDE)
|
||||
if err != syscall.Errno(0) {
|
||||
return fmt.Errorf("there was an error calling user32!ShowWindow with return %+v: %s", ret, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set STDOUT/STDERR to the new files from os.Pipe()
|
||||
// https://docs.microsoft.com/en-us/windows/console/setstdhandle
|
||||
if err = windows.SetStdHandle(windows.STD_OUTPUT_HANDLE, windows.Handle(wSTDOUT.Fd())); err != nil {
|
||||
@@ -132,7 +162,7 @@ func ReadStdoutStderr() (stdout string, stderr string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// CloseSTdoutStderr closes the Reader/Writer for the prviously redirected STDOUT/STDERR
|
||||
// CloseStdoutStderr closes the Reader/Writer for the previously redirected STDOUT/STDERR
|
||||
// that was changed to an *os.File
|
||||
func CloseStdoutStderr() (err error) {
|
||||
err = rSTDOUT.Close()
|
||||
|
||||
-8
@@ -5,7 +5,6 @@ package clr
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
@@ -25,13 +24,6 @@ func checkOK(hr uintptr, caller string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// must forces the program to exit if there is an error using the log.Fatal command
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func utf16Le(s string) []byte {
|
||||
enc := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
|
||||
var buf bytes.Buffer
|
||||
|
||||
+1
-1
@@ -285,7 +285,7 @@ func (g *Gen) NewV7() (UUID, error) {
|
||||
}
|
||||
|
||||
tn := g.epochFunc()
|
||||
ms := uint64(tn.UnixMilli())
|
||||
ms := uint64(tn.Unix())*1e3 + uint64(tn.Nanosecond())/1e6
|
||||
u[0] = byte(ms >> 40)
|
||||
u[1] = byte(ms >> 32)
|
||||
u[2] = byte(ms >> 24)
|
||||
|
||||
+9
-9
@@ -364,15 +364,16 @@ func (t *Table) renderRowsHeader(out *strings.Builder) {
|
||||
|
||||
func (t *Table) renderTitle(out *strings.Builder) {
|
||||
if t.title != "" {
|
||||
colors := t.style.Title.Colors
|
||||
rowLength := t.maxRowLength
|
||||
if t.allowedRowLength != 0 && t.allowedRowLength < rowLength {
|
||||
rowLength = t.allowedRowLength
|
||||
}
|
||||
if t.style.Options.DrawBorder {
|
||||
lenBorder := rowLength - text.RuneWidthWithoutEscSequences(t.style.Box.TopLeft+t.style.Box.TopRight)
|
||||
out.WriteString(t.style.Box.TopLeft)
|
||||
out.WriteString(text.RepeatAndTrim(t.style.Box.MiddleHorizontal, lenBorder))
|
||||
out.WriteString(t.style.Box.TopRight)
|
||||
out.WriteString(colors.Sprint(t.style.Box.TopLeft))
|
||||
out.WriteString(colors.Sprint(text.RepeatAndTrim(t.style.Box.MiddleHorizontal, lenBorder)))
|
||||
out.WriteString(colors.Sprint(t.style.Box.TopRight))
|
||||
}
|
||||
|
||||
lenText := rowLength - text.RuneWidthWithoutEscSequences(t.style.Box.PaddingLeft+t.style.Box.PaddingRight)
|
||||
@@ -381,26 +382,25 @@ func (t *Table) renderTitle(out *strings.Builder) {
|
||||
}
|
||||
titleText := text.WrapText(t.title, lenText)
|
||||
for _, titleLine := range strings.Split(titleText, "\n") {
|
||||
t.renderTitleLine(out, lenText, titleLine)
|
||||
t.renderTitleLine(out, lenText, titleLine, colors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) renderTitleLine(out *strings.Builder, lenText int, titleLine string) {
|
||||
func (t *Table) renderTitleLine(out *strings.Builder, lenText int, titleLine string, colors text.Colors) {
|
||||
titleLine = strings.TrimSpace(titleLine)
|
||||
titleLine = t.style.Title.Format.Apply(titleLine)
|
||||
titleLine = t.style.Title.Align.Apply(titleLine, lenText)
|
||||
titleLine = t.style.Box.PaddingLeft + titleLine + t.style.Box.PaddingRight
|
||||
titleLine = t.style.Title.Colors.Sprint(titleLine)
|
||||
|
||||
if out.Len() > 0 {
|
||||
out.WriteRune('\n')
|
||||
}
|
||||
if t.style.Options.DrawBorder {
|
||||
out.WriteString(t.style.Box.Left)
|
||||
out.WriteString(colors.Sprint(t.style.Box.Left))
|
||||
}
|
||||
out.WriteString(titleLine)
|
||||
out.WriteString(colors.Sprint(titleLine))
|
||||
if t.style.Options.DrawBorder {
|
||||
out.WriteString(t.style.Box.Right)
|
||||
out.WriteString(colors.Sprint(t.style.Box.Right))
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package table
|
||||
|
||||
// renderHint has hints for the Render*() logic
|
||||
type renderHint struct {
|
||||
isAutoIndexColumn bool // auto-index column?
|
||||
isAutoIndexRow bool // auto-index row?
|
||||
isBorderBottom bool // bottom-border?
|
||||
isBorderTop bool // top-border?
|
||||
isFirstRow bool // first-row of header/footer/regular-rows?
|
||||
isFooterRow bool // footer row?
|
||||
isHeaderRow bool // header row?
|
||||
isLastLineOfRow bool // last-line of the current row?
|
||||
isLastRow bool // last-row of header/footer/regular-rows?
|
||||
isSeparatorRow bool // separator row?
|
||||
rowLineNumber int // the line number for a multi-line row
|
||||
rowNumber int // the row number/index
|
||||
}
|
||||
|
||||
func (h *renderHint) isRegularRow() bool {
|
||||
return !h.isHeaderRow && !h.isFooterRow
|
||||
}
|
||||
|
||||
func (h *renderHint) isRegularNonSeparatorRow() bool {
|
||||
return !h.isHeaderRow && !h.isFooterRow && !h.isSeparatorRow
|
||||
}
|
||||
|
||||
func (h *renderHint) isHeaderOrFooterSeparator() bool {
|
||||
return h.isSeparatorRow && !h.isBorderBottom && !h.isBorderTop &&
|
||||
((h.isHeaderRow && !h.isLastRow) || (h.isFooterRow && (!h.isFirstRow || h.rowNumber > 0)))
|
||||
}
|
||||
|
||||
func (h *renderHint) isLastLineOfLastRow() bool {
|
||||
return h.isLastLineOfRow && h.isLastRow
|
||||
}
|
||||
+119
-80
@@ -371,12 +371,14 @@ func (t *Table) getAutoIndexColumnIDs() rowStr {
|
||||
}
|
||||
|
||||
func (t *Table) getBorderColors(hint renderHint) text.Colors {
|
||||
if hint.isFooterRow {
|
||||
if hint.isHeaderRow {
|
||||
return t.style.Color.Header
|
||||
} else if hint.isFooterRow {
|
||||
return t.style.Color.Footer
|
||||
} else if t.autoIndex {
|
||||
return t.style.Color.IndexColumn
|
||||
}
|
||||
return t.style.Color.Header
|
||||
return t.style.Color.Row
|
||||
}
|
||||
|
||||
func (t *Table) getBorderLeft(hint renderHint) string {
|
||||
@@ -557,6 +559,43 @@ func (t *Table) getMaxColumnLengthForMerging(colIdx int) int {
|
||||
return maxColumnLength
|
||||
}
|
||||
|
||||
// getMergedColumnIndices returns a map of colIdx values to all the other colIdx
|
||||
// values (that are being merged) and their lengths.
|
||||
func (t *Table) getMergedColumnIndices(row rowStr, hint renderHint) map[int]map[int]bool {
|
||||
if !t.getRowConfig(hint).AutoMerge {
|
||||
return nil
|
||||
}
|
||||
|
||||
rsp := make(map[int]map[int]bool)
|
||||
appendColumnsToResponse := func(colIdx, otherColIdx int) {
|
||||
if rsp[colIdx] == nil {
|
||||
rsp[colIdx] = make(map[int]bool)
|
||||
}
|
||||
if rsp[otherColIdx] == nil {
|
||||
rsp[otherColIdx] = make(map[int]bool)
|
||||
}
|
||||
rsp[colIdx][otherColIdx] = true
|
||||
rsp[otherColIdx][colIdx] = true
|
||||
}
|
||||
for colIdx := 0; colIdx < t.numColumns-1; colIdx++ {
|
||||
// look backward
|
||||
for otherColIdx := colIdx - 1; colIdx >= 0 && otherColIdx >= 0; otherColIdx-- {
|
||||
if row[colIdx] != row[otherColIdx] {
|
||||
break
|
||||
}
|
||||
appendColumnsToResponse(colIdx, otherColIdx)
|
||||
}
|
||||
// look forward
|
||||
for otherColIdx := colIdx + 1; colIdx < len(row) && otherColIdx < len(row); otherColIdx++ {
|
||||
if row[colIdx] != row[otherColIdx] {
|
||||
break
|
||||
}
|
||||
appendColumnsToResponse(colIdx, otherColIdx)
|
||||
}
|
||||
}
|
||||
return rsp
|
||||
}
|
||||
|
||||
func (t *Table) getRow(rowIdx int, hint renderHint) rowStr {
|
||||
switch {
|
||||
case hint.isHeaderRow:
|
||||
@@ -627,6 +666,39 @@ func (t *Table) hasHiddenColumns() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Table) hideColumns() map[int]int {
|
||||
colIdxMap := make(map[int]int)
|
||||
numColumns := 0
|
||||
hideColumnsInRows := func(rows []rowStr) []rowStr {
|
||||
var rsp []rowStr
|
||||
for _, row := range rows {
|
||||
var rowNew rowStr
|
||||
for colIdx, col := range row {
|
||||
cc := t.columnConfigMap[colIdx]
|
||||
if !cc.Hidden {
|
||||
rowNew = append(rowNew, col)
|
||||
colIdxMap[colIdx] = len(rowNew) - 1
|
||||
}
|
||||
}
|
||||
if len(rowNew) > numColumns {
|
||||
numColumns = len(rowNew)
|
||||
}
|
||||
rsp = append(rsp, rowNew)
|
||||
}
|
||||
return rsp
|
||||
}
|
||||
|
||||
// hide columns as directed
|
||||
t.rows = hideColumnsInRows(t.rows)
|
||||
t.rowsFooter = hideColumnsInRows(t.rowsFooter)
|
||||
t.rowsHeader = hideColumnsInRows(t.rowsHeader)
|
||||
|
||||
// reset numColumns to the new number of columns
|
||||
t.numColumns = numColumns
|
||||
|
||||
return colIdxMap
|
||||
}
|
||||
|
||||
func (t *Table) initForRender() {
|
||||
// pick a default style if none was set until now
|
||||
t.Style()
|
||||
@@ -668,16 +740,12 @@ func (t *Table) initForRenderColumnConfigs() {
|
||||
|
||||
func (t *Table) initForRenderColumnLengths() {
|
||||
t.maxColumnLengths = make([]int, t.numColumns)
|
||||
t.parseRowForMaxColumnLengths(t.rowsHeader)
|
||||
t.parseRowForMaxColumnLengths(t.rows)
|
||||
t.parseRowForMaxColumnLengths(t.rowsFooter)
|
||||
t.parseRowForMaxColumnLengths(t.rowsHeader, renderHint{isHeaderRow: true})
|
||||
t.parseRowForMaxColumnLengths(t.rows, renderHint{})
|
||||
t.parseRowForMaxColumnLengths(t.rowsFooter, renderHint{isFooterRow: true})
|
||||
|
||||
// restrict the column lengths if any are over or under the limits
|
||||
// increase the column lengths if any are under the limits
|
||||
for colIdx := range t.maxColumnLengths {
|
||||
maxWidth := t.getColumnWidthMax(colIdx)
|
||||
if maxWidth > 0 && t.maxColumnLengths[colIdx] > maxWidth {
|
||||
t.maxColumnLengths[colIdx] = maxWidth
|
||||
}
|
||||
minWidth := t.getColumnWidthMin(colIdx)
|
||||
if minWidth > 0 && t.maxColumnLengths[colIdx] < minWidth {
|
||||
t.maxColumnLengths[colIdx] = minWidth
|
||||
@@ -685,39 +753,6 @@ func (t *Table) initForRenderColumnLengths() {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) hideColumns() map[int]int {
|
||||
colIdxMap := make(map[int]int)
|
||||
numColumns := 0
|
||||
hideColumnsInRows := func(rows []rowStr) []rowStr {
|
||||
var rsp []rowStr
|
||||
for _, row := range rows {
|
||||
var rowNew rowStr
|
||||
for colIdx, col := range row {
|
||||
cc := t.columnConfigMap[colIdx]
|
||||
if !cc.Hidden {
|
||||
rowNew = append(rowNew, col)
|
||||
colIdxMap[colIdx] = len(rowNew) - 1
|
||||
}
|
||||
}
|
||||
if len(rowNew) > numColumns {
|
||||
numColumns = len(rowNew)
|
||||
}
|
||||
rsp = append(rsp, rowNew)
|
||||
}
|
||||
return rsp
|
||||
}
|
||||
|
||||
// hide columns as directed
|
||||
t.rows = hideColumnsInRows(t.rows)
|
||||
t.rowsFooter = hideColumnsInRows(t.rowsFooter)
|
||||
t.rowsHeader = hideColumnsInRows(t.rowsHeader)
|
||||
|
||||
// reset numColumns to the new number of columns
|
||||
t.numColumns = numColumns
|
||||
|
||||
return colIdxMap
|
||||
}
|
||||
|
||||
func (t *Table) initForRenderHideColumns() {
|
||||
if !t.hasHiddenColumns() {
|
||||
return
|
||||
@@ -850,11 +885,44 @@ func (t *Table) isIndexColumn(colIdx int, hint renderHint) bool {
|
||||
return t.indexColumn == colIdx+1 || hint.isAutoIndexColumn
|
||||
}
|
||||
|
||||
func (t *Table) parseRowForMaxColumnLengths(rows []rowStr) {
|
||||
for _, row := range rows {
|
||||
func (t *Table) parseRowForMaxColumnLengths(rows []rowStr, hint renderHint) {
|
||||
getMergedColumnsTotalLength := func(colIdx int, mergedColumns map[int]bool) int {
|
||||
mergedColLength := t.maxColumnLengths[colIdx]
|
||||
for otherColIdx := range mergedColumns {
|
||||
mergedColLength += t.maxColumnLengths[otherColIdx]
|
||||
}
|
||||
return mergedColLength
|
||||
}
|
||||
separatorLength := text.RuneWidthWithoutEscSequences(t.style.Box.MiddleSeparator)
|
||||
|
||||
for rowIdx, row := range rows {
|
||||
hint.rowNumber = rowIdx + 1
|
||||
mergedColumnsMap := t.getMergedColumnIndices(row, hint)
|
||||
for colIdx, colStr := range row {
|
||||
longestLineLen := text.LongestLineLen(colStr)
|
||||
if longestLineLen > t.maxColumnLengths[colIdx] {
|
||||
maxColWidth := t.getColumnWidthMax(colIdx)
|
||||
if maxColWidth > 0 && maxColWidth < longestLineLen {
|
||||
longestLineLen = maxColWidth
|
||||
}
|
||||
mergedColumnsForCol := mergedColumnsMap[colIdx]
|
||||
mergedColumnsLength := getMergedColumnsTotalLength(colIdx, mergedColumnsForCol)
|
||||
if longestLineLen > mergedColumnsLength {
|
||||
if mergedColumnsLength > 0 {
|
||||
numMergedColumns := len(mergedColumnsForCol) + 1
|
||||
longestLineLen -= (numMergedColumns - 1) * separatorLength
|
||||
maxLengthSplitAcrossColumns := longestLineLen / numMergedColumns
|
||||
if maxLengthSplitAcrossColumns > t.maxColumnLengths[colIdx] {
|
||||
t.maxColumnLengths[colIdx] = maxLengthSplitAcrossColumns
|
||||
}
|
||||
for otherColIdx := range mergedColumnsForCol {
|
||||
if maxLengthSplitAcrossColumns > t.maxColumnLengths[otherColIdx] {
|
||||
t.maxColumnLengths[otherColIdx] = maxLengthSplitAcrossColumns
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.maxColumnLengths[colIdx] = longestLineLen
|
||||
}
|
||||
} else if maxColWidth == 0 && longestLineLen > t.maxColumnLengths[colIdx] {
|
||||
t.maxColumnLengths[colIdx] = longestLineLen
|
||||
}
|
||||
}
|
||||
@@ -964,7 +1032,11 @@ func (t *Table) wrapRow(row rowStr) (int, rowStr) {
|
||||
rowWrapped := make(rowStr, len(row))
|
||||
for colIdx, colStr := range row {
|
||||
widthEnforcer := t.columnConfigMap[colIdx].getWidthMaxEnforcer()
|
||||
rowWrapped[colIdx] = widthEnforcer(colStr, t.maxColumnLengths[colIdx])
|
||||
maxWidth := t.getColumnWidthMax(colIdx)
|
||||
if maxWidth == 0 {
|
||||
maxWidth = t.maxColumnLengths[colIdx]
|
||||
}
|
||||
rowWrapped[colIdx] = widthEnforcer(colStr, maxWidth)
|
||||
colNumLines := strings.Count(rowWrapped[colIdx], "\n") + 1
|
||||
if colNumLines > colMaxLines {
|
||||
colMaxLines = colNumLines
|
||||
@@ -972,36 +1044,3 @@ func (t *Table) wrapRow(row rowStr) (int, rowStr) {
|
||||
}
|
||||
return colMaxLines, rowWrapped
|
||||
}
|
||||
|
||||
// renderHint has hints for the Render*() logic
|
||||
type renderHint struct {
|
||||
isAutoIndexColumn bool // auto-index column?
|
||||
isAutoIndexRow bool // auto-index row?
|
||||
isBorderBottom bool // bottom-border?
|
||||
isBorderTop bool // top-border?
|
||||
isFirstRow bool // first-row of header/footer/regular-rows?
|
||||
isFooterRow bool // footer row?
|
||||
isHeaderRow bool // header row?
|
||||
isLastLineOfRow bool // last-line of the current row?
|
||||
isLastRow bool // last-row of header/footer/regular-rows?
|
||||
isSeparatorRow bool // separator row?
|
||||
rowLineNumber int // the line number for a multi-line row
|
||||
rowNumber int // the row number/index
|
||||
}
|
||||
|
||||
func (h *renderHint) isRegularRow() bool {
|
||||
return !h.isHeaderRow && !h.isFooterRow
|
||||
}
|
||||
|
||||
func (h *renderHint) isRegularNonSeparatorRow() bool {
|
||||
return !h.isHeaderRow && !h.isFooterRow && !h.isSeparatorRow
|
||||
}
|
||||
|
||||
func (h *renderHint) isHeaderOrFooterSeparator() bool {
|
||||
return h.isSeparatorRow && !h.isBorderBottom && !h.isBorderTop &&
|
||||
((h.isHeaderRow && !h.isLastRow) || (h.isFooterRow && (!h.isFirstRow || h.rowNumber > 0)))
|
||||
}
|
||||
|
||||
func (h *renderHint) isLastLineOfLastRow() bool {
|
||||
return h.isLastLineOfRow && h.isLastRow
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# text
|
||||
|
||||
[](https://pkg.go.dev/github.com/jedib0t/go-pretty/v6/text)
|
||||
|
||||
Package with utility functions to manipulate strings/text.
|
||||
|
||||
Used heavily in the other packages in this repo ([list](../list),
|
||||
[progress](../progress), and [table](../table)).
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package common
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Warnings struct {
|
||||
List []error
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func (w *Warnings) Add(err error) {
|
||||
w.List = append(w.List, err)
|
||||
}
|
||||
|
||||
func (w *Warnings) Reference() error {
|
||||
if len(w.List) > 0 {
|
||||
return w
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Warnings) Error() string {
|
||||
if w.Verbose {
|
||||
str := ""
|
||||
for i, e := range w.List {
|
||||
str += fmt.Sprintf("\tError %d: %s\n", i, e.Error())
|
||||
}
|
||||
return str
|
||||
}
|
||||
return fmt.Sprintf("Number of warnings: %v", len(w.List))
|
||||
}
|
||||
+5
-7
@@ -183,7 +183,7 @@ func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
|
||||
}
|
||||
columnTypeSQL += "FROM information_schema.columns WHERE table_schema = ? AND table_name = ? ORDER BY ORDINAL_POSITION"
|
||||
|
||||
columns, rowErr := m.DB.Raw(columnTypeSQL, currentDatabase, table).Rows()
|
||||
columns, rowErr := m.DB.Table(table).Raw(columnTypeSQL, currentDatabase, table).Rows()
|
||||
if rowErr != nil {
|
||||
return rowErr
|
||||
}
|
||||
@@ -271,7 +271,7 @@ func (m Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
|
||||
|
||||
result := make([]*Index, 0)
|
||||
schema, table := m.CurrentSchema(stmt, stmt.Table)
|
||||
scanErr := m.DB.Raw(indexSql, schema, table).Scan(&result).Error
|
||||
scanErr := m.DB.Table(table).Raw(indexSql, schema, table).Scan(&result).Error
|
||||
if scanErr != nil {
|
||||
return scanErr
|
||||
}
|
||||
@@ -317,12 +317,10 @@ func groupByIndexName(indexList []*Index) map[string][]*Index {
|
||||
}
|
||||
|
||||
func (m Migrator) CurrentSchema(stmt *gorm.Statement, table string) (string, string) {
|
||||
if strings.Contains(table, ".") {
|
||||
if tables := strings.Split(table, `.`); len(tables) == 2 {
|
||||
return tables[0], tables[1]
|
||||
}
|
||||
if tables := strings.Split(table, `.`); len(tables) == 2 {
|
||||
return tables[0], tables[1]
|
||||
}
|
||||
|
||||
m.DB = m.DB.Table(table)
|
||||
return m.CurrentDatabase(), table
|
||||
}
|
||||
|
||||
|
||||
+40
-37
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/callbacks"
|
||||
"gorm.io/gorm/clause"
|
||||
@@ -76,22 +77,20 @@ func (dialector Dialector) NowFunc(n int) func() time.Time {
|
||||
}
|
||||
|
||||
func (dialector Dialector) Apply(config *gorm.Config) error {
|
||||
if config.NowFunc == nil {
|
||||
if dialector.DefaultDatetimePrecision == nil {
|
||||
dialector.DefaultDatetimePrecision = &defaultDatetimePrecision
|
||||
}
|
||||
|
||||
// while maintaining the readability of the code, separate the business logic from
|
||||
// the general part and leave it to the function to do it here.
|
||||
config.NowFunc = dialector.NowFunc(*dialector.DefaultDatetimePrecision)
|
||||
if config.NowFunc != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dialector.DefaultDatetimePrecision == nil {
|
||||
dialector.DefaultDatetimePrecision = &defaultDatetimePrecision
|
||||
}
|
||||
// while maintaining the readability of the code, separate the business logic from
|
||||
// the general part and leave it to the function to do it here.
|
||||
config.NowFunc = dialector.NowFunc(*dialector.DefaultDatetimePrecision)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
|
||||
ctx := context.Background()
|
||||
|
||||
if dialector.DriverName == "" {
|
||||
dialector.DriverName = "mysql"
|
||||
}
|
||||
@@ -111,7 +110,7 @@ func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
|
||||
|
||||
withReturning := false
|
||||
if !dialector.Config.SkipInitializeWithVersion {
|
||||
err = db.ConnPool.QueryRowContext(ctx, "SELECT VERSION()").Scan(&dialector.ServerVersion)
|
||||
err = db.ConnPool.QueryRowContext(context.Background(), "SELECT VERSION()").Scan(&dialector.ServerVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -121,9 +120,7 @@ func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
|
||||
dialector.Config.DontSupportRenameColumn = true
|
||||
dialector.Config.DontSupportForShareClause = true
|
||||
dialector.Config.DontSupportNullAsDefaultValue = true
|
||||
if checkVersion(dialector.ServerVersion, "10.5") {
|
||||
withReturning = true
|
||||
}
|
||||
withReturning = checkVersion(dialector.ServerVersion, "10.5")
|
||||
} else if strings.HasPrefix(dialector.ServerVersion, "5.6.") {
|
||||
dialector.Config.DontSupportRenameIndex = true
|
||||
dialector.Config.DontSupportRenameColumn = true
|
||||
@@ -176,7 +173,7 @@ const (
|
||||
ClauseOnConflict = "ON CONFLICT"
|
||||
// ClauseValues for clause.ClauseBuilder VALUES key
|
||||
ClauseValues = "VALUES"
|
||||
// ClauseValues for clause.ClauseBuilder FOR key
|
||||
// ClauseFor for clause.ClauseBuilder FOR key
|
||||
ClauseFor = "FOR"
|
||||
)
|
||||
|
||||
@@ -393,11 +390,11 @@ func (dialector Dialector) getSchemaStringType(field *schema.Field) string {
|
||||
}
|
||||
|
||||
func (dialector Dialector) getSchemaTimeType(field *schema.Field) string {
|
||||
precision := ""
|
||||
if !dialector.DisableDatetimePrecision && field.Precision == 0 {
|
||||
field.Precision = *dialector.DefaultDatetimePrecision
|
||||
}
|
||||
|
||||
var precision string
|
||||
if field.Precision > 0 {
|
||||
precision = fmt.Sprintf("(%d)", field.Precision)
|
||||
}
|
||||
@@ -421,27 +418,31 @@ func (dialector Dialector) getSchemaBytesType(field *schema.Field) string {
|
||||
}
|
||||
|
||||
func (dialector Dialector) getSchemaIntAndUnitType(field *schema.Field) string {
|
||||
sqlType := "bigint"
|
||||
constraint := func(sqlType string) string {
|
||||
if field.DataType == schema.Uint {
|
||||
sqlType += " unsigned"
|
||||
}
|
||||
if field.NotNull {
|
||||
sqlType += " NOT NULL"
|
||||
}
|
||||
if field.AutoIncrement {
|
||||
sqlType += " AUTO_INCREMENT"
|
||||
}
|
||||
return sqlType
|
||||
}
|
||||
|
||||
switch {
|
||||
case field.Size <= 8:
|
||||
sqlType = "tinyint"
|
||||
return constraint("tinyint")
|
||||
case field.Size <= 16:
|
||||
sqlType = "smallint"
|
||||
return constraint("smallint")
|
||||
case field.Size <= 24:
|
||||
sqlType = "mediumint"
|
||||
return constraint("mediumint")
|
||||
case field.Size <= 32:
|
||||
sqlType = "int"
|
||||
return constraint("int")
|
||||
default:
|
||||
return constraint("bigint")
|
||||
}
|
||||
|
||||
if field.DataType == schema.Uint {
|
||||
sqlType += " unsigned"
|
||||
}
|
||||
|
||||
if field.AutoIncrement {
|
||||
sqlType += " AUTO_INCREMENT"
|
||||
}
|
||||
|
||||
return sqlType
|
||||
}
|
||||
|
||||
func (dialector Dialector) getSchemaCustomType(field *schema.Field) string {
|
||||
@@ -462,23 +463,25 @@ func (dialector Dialector) RollbackTo(tx *gorm.DB, name string) error {
|
||||
return tx.Exec("ROLLBACK TO SAVEPOINT " + name).Error
|
||||
}
|
||||
|
||||
var versionTrimerRegexp = regexp.MustCompile(`^(\d+).*$`)
|
||||
|
||||
// checkVersion newer or equal returns true, old returns false
|
||||
func checkVersion(newVersion, oldVersion string) bool {
|
||||
if newVersion == oldVersion {
|
||||
return true
|
||||
}
|
||||
|
||||
newVersions := strings.Split(newVersion, ".")
|
||||
oldVersions := strings.Split(oldVersion, ".")
|
||||
var (
|
||||
versionTrimmerRegexp = regexp.MustCompile(`^(\d+).*$`)
|
||||
|
||||
newVersions = strings.Split(newVersion, ".")
|
||||
oldVersions = strings.Split(oldVersion, ".")
|
||||
)
|
||||
for idx, nv := range newVersions {
|
||||
if len(oldVersions) <= idx {
|
||||
return true
|
||||
}
|
||||
|
||||
nvi, _ := strconv.Atoi(versionTrimerRegexp.ReplaceAllString(nv, "$1"))
|
||||
ovi, _ := strconv.Atoi(versionTrimerRegexp.ReplaceAllString(oldVersions[idx], "$1"))
|
||||
nvi, _ := strconv.Atoi(versionTrimmerRegexp.ReplaceAllString(nv, "$1"))
|
||||
ovi, _ := strconv.Atoi(versionTrimmerRegexp.ReplaceAllString(oldVersions[idx], "$1"))
|
||||
if nvi == ovi {
|
||||
continue
|
||||
}
|
||||
|
||||
Vendored
+5
-5
@@ -30,7 +30,7 @@ github.com/BurntSushi/xgb/xproto
|
||||
# github.com/Microsoft/go-winio v0.5.1
|
||||
## explicit; go 1.12
|
||||
github.com/Microsoft/go-winio/pkg/guid
|
||||
# github.com/Ne0nd0g/go-clr v1.0.2
|
||||
# github.com/Ne0nd0g/go-clr v1.0.3
|
||||
## explicit; go 1.13
|
||||
github.com/Ne0nd0g/go-clr
|
||||
# github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab
|
||||
@@ -196,7 +196,7 @@ github.com/gobwas/pool/pbytes
|
||||
## explicit; go 1.15
|
||||
github.com/gobwas/ws
|
||||
github.com/gobwas/ws/wsutil
|
||||
# github.com/gofrs/uuid v4.3.0+incompatible
|
||||
# github.com/gofrs/uuid v4.3.1+incompatible
|
||||
## explicit
|
||||
github.com/gofrs/uuid
|
||||
# github.com/golang/protobuf v1.5.2
|
||||
@@ -269,7 +269,7 @@ github.com/jackc/pgx/v4/stdlib
|
||||
## explicit
|
||||
github.com/jcmturner/gofork/encoding/asn1
|
||||
github.com/jcmturner/gofork/x/crypto/pbkdf2
|
||||
# github.com/jedib0t/go-pretty/v6 v6.4.0
|
||||
# github.com/jedib0t/go-pretty/v6 v6.4.2
|
||||
## explicit; go 1.16
|
||||
github.com/jedib0t/go-pretty/v6/table
|
||||
github.com/jedib0t/go-pretty/v6/text
|
||||
@@ -348,7 +348,7 @@ github.com/remyoudompheng/bigfft
|
||||
# github.com/rivo/uniseg v0.2.0
|
||||
## explicit; go 1.12
|
||||
github.com/rivo/uniseg
|
||||
# github.com/shirou/gopsutil/v3 v3.22.9
|
||||
# github.com/shirou/gopsutil/v3 v3.22.10
|
||||
## explicit; go 1.15
|
||||
github.com/shirou/gopsutil/v3/internal/common
|
||||
github.com/shirou/gopsutil/v3/mem
|
||||
@@ -641,7 +641,7 @@ gopkg.in/jcmturner/rpc.v1/ndr
|
||||
# gopkg.in/yaml.v3 v3.0.1
|
||||
## explicit
|
||||
gopkg.in/yaml.v3
|
||||
# gorm.io/driver/mysql v1.4.3
|
||||
# gorm.io/driver/mysql v1.4.4
|
||||
## explicit; go 1.14
|
||||
gorm.io/driver/mysql
|
||||
# gorm.io/driver/postgres v1.4.5
|
||||
|
||||
Reference in new issue
Block a user