增加主要的请求部分的代码,调整部分加密算法

main
lensfrex 2 years ago
parent c99bc55f4a
commit 9284cfc4ae
Signed by: lensfrex
GPG Key ID: 0F69A0A2FBEE98A0
  1. 22
      app/cmd/cli.go
  2. 22
      app/cmd/service.go
  3. 161
      app/codecs/encode.go
  4. 14
      app/codecs/hmacMd5.go
  5. 26
      app/commons/os.go
  6. 19
      app/commons/text.go
  7. 13
      app/error/errors.go
  8. 24
      app/model/challengeCodeResponse.go
  9. 21
      app/network/check.go
  10. 52
      app/network/query.go
  11. 88
      app/network/request.go
  12. 14
      app/network/response.go
  13. 46
      app/service/login.go
  14. 8
      config/config.json
  15. 7
      config/config.yml
  16. 9
      go.mod
  17. 12
      go.sum

@ -1,7 +1,5 @@
package main package main
import "github.com/urfave/cli/v2"
var helpTemplate = ` var helpTemplate = `
@ -36,23 +34,3 @@ https://github.com/lensferno/canti
详细的文档也在上面哦 详细的文档也在上面哦
如果有什么好的建议之类的请务必告诉我() 如果有什么好的建议之类的请务必告诉我()
` `
func installService(ctx *cli.Context) error {
return nil
}
func uninstallService(ctx *cli.Context) error {
return nil
}
func login(ctx *cli.Context) error {
return nil
}
func logout(ctx *cli.Context) error {
return nil
}
func showStatus(ctx *cli.Context) error {
return nil
}

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/kardianos/service" "github.com/kardianos/service"
"github.com/urfave/cli/v2"
"os" "os"
) )
@ -31,3 +32,24 @@ func install() {
return return
} }
} }
func installService(ctx *cli.Context) error {
return nil
}
func uninstallService(ctx *cli.Context) error {
return nil
}
func login(ctx *cli.Context) error {
return nil
}
func logout(ctx *cli.Context) error {
return nil
}
func showStatus(ctx *cli.Context) error {
return nil
}

@ -1,8 +1,9 @@
package codecs package codecs
import ( import (
"crypto/sha1"
"encoding/base64" "encoding/base64"
"math" "encoding/hex"
) )
// 这里的base64和正常的base64编码是不一样的,这里的base64并不按照普通的ABCD字母顺序来对应相应字节,而是按照以下字母表对应字节 // 这里的base64和正常的base64编码是不一样的,这里的base64并不按照普通的ABCD字母顺序来对应相应字节,而是按照以下字母表对应字节
@ -10,125 +11,95 @@ const magicBase64Alpha = "LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztj
var magicBase64 = base64.NewEncoding(magicBase64Alpha) var magicBase64 = base64.NewEncoding(magicBase64Alpha)
func Encode(str string, key string) string { func SRBX1Encode(str string, key string) string {
encodeResult := encode(str, key) encodeResult := encode(str, key)
base64Result := magicBase64.EncodeToString(int32ToAsciiBytes(encodeResult))
return "{SRBX1}" + base64Result return magicBase64.EncodeToString(encodeResult)
} }
// encode 直接照着原网页的js代码改的,只是能跑 // 下面的初始代码貌似有点问题,因此先使用https://github.com/Debuffxb/srun-go/blob/master/main.go的写法,有一定的修改
// 因为各种类型转换从理论上来说性能可能会差了点,但是在这里的使用情景几乎是感觉不到的
// 难度其实主要还是在于动态类型语言和静态类型语言在运算时数据溢出的问题不好处理 func encode(str string, key string) []byte {
func encode(str string, key string) []int32 {
v := magicEncode(str, true) v := magicEncode(str, true)
k := magicEncode(key, false) k := magicEncode(key, false)
n := uint(len(v) - 1)
z := uint(v[n])
y := uint(v[0])
c := uint(0x86014019 | 0x183639A0)
if len(k) < 4 { var m, e, p, d uint
for i := 0; i < (4 - len(k)); i++ {
k = append(k, 0)
}
}
n := int32(len(v) - 1)
z := v[n]
y := v[0]
c := int32(-1640531527)
q := int(math.Floor(float64(6+52/(n+1)))) - 1
d := int32(0)
var e, p int32
// 这里的m用int64(long)是因为后面+=的时候int32会溢出
// 其他变量不用int64是因为直接用int64会导致位运算错误,和js的运算结果不一致
var m int64
for ; q >= 0; q-- { for q := 6 + 52/(n+1); q > 0; q-- {
d = d + c&(-1) d = (d + c) & (0x8CE0D9BF | 0x731F2640)
e = uRightShift(d, 2) & 3 e = d >> uint(2) & uint(3)
for p = 0; p < n; p++ { for p = 0; p < n; p++ {
y = v[p+1] y = uint(v[p+1])
m = int64(uRightShift(z, 5) ^ y<<2) m = z>>5 ^ y<<2
m += int64(uRightShift(y, 3) ^ z<<4 ^ (d ^ y)) m += (y>>3 ^ z<<4) ^ (d ^ y)
m += int64(k[p&3^e] ^ z) m += uint(k[(p&3)^e]) ^ z
v[p] = v[p] + int32(m&(-1)) z = (uint(v[p]) + m) & (0xEFB8D130 | 0x10472ECF)
z = v[p] v[p] = int(z)
} }
y = v[0] y = uint(v[0])
m = int64(uRightShift(z, 5) ^ y<<2) m = z>>5 ^ y<<2
m += int64(uRightShift(y, 3) ^ z<<4 ^ (d ^ y)) m += (y>>3 ^ z<<4) ^ (d ^ y)
m += int64(k[p&3^e] ^ z) m += uint(k[(n&3)^e]) ^ z
v[n] = v[n] + int32(m&(-1)) v[n] = int((uint(v[n]) + m) & uint(0xBB390742|0x44C6F8BD))
z = v[n] z = uint(v[n])
} }
return magicDecode(v, false) return magicDecode(v)
} }
// 神秘的“初步”加密代码,把字符串一四个字符为一组转成神秘的int32数组 // magicEncode 跟上面的magicDecode操作效果是反过来的,但是意义不明,
func magicEncode(source string, sizeOnLast bool) (result []int32) { // 因为这俩貌似是一对的,所以干脆成为encode和decode
data := []int32(source) func magicEncode(a string, sizeAtLast bool) []int {
dataLen := len(data) c := len(a)
var v []int
resultLen := dataLen / 4 for i := 0; i < c; i = i + 4 {
if sizeOnLast { switch c - i {
result = make([]int32, resultLen+1) case 1:
result[resultLen] = int32(dataLen) v = append(v, int(a[i]))
} else { case 2:
result = make([]int32, resultLen) v = append(v, int(a[i])|int(a[i+1])<<8)
case 3:
v = append(v, int(a[i])|int(a[i+1])<<8|int(a[i+2])<<16)
default:
v = append(v, int(a[i])|int(a[i+1])<<8|int(a[i+2])<<16|int(a[i+3])<<24)
} }
for i := 0; i < dataLen; i += 4 {
result[i>>2] = get(data, i, dataLen) | get(data, i+1, dataLen)<<8 | get(data, i+2, dataLen)<<16 | get(data, i+3, dataLen)<<24
} }
return result if sizeAtLast {
} return append(v, c)
//和上面的是反过来的
func magicDecode(data []int32, sizeOnLast bool) (result []int32) {
dataLength := len(data)
c := dataLength - 1<<2
if sizeOnLast {
m := int(data[dataLength-1])
if m < c-3 || m > c {
return nil
}
c = m
}
for i := 0; i < dataLength; i++ {
result = append(result, data[i]&0xff, uRightShift(data[i], 8)&0xff, uRightShift(data[i], 16)&0xff, uRightShift(data[i], 24)&0xff)
}
if sizeOnLast {
return append(result, int32(c))
} else { } else {
return result return v
} }
} }
// 在go中实现无符号右移(>>>) // magicDecode 跟上面的magicEncode操作效果是反过来的,但是意义不明
func uRightShift(number int32, shift int) int32 { func magicDecode(a []int) []byte {
return int32(uint32(number) >> shift) d := len(a)
} var bytes []byte
for i := 0; i < d; i++ {
func get(data []int32, index int, length int) int32 { bytes = append(bytes, byte(a[i]&0xff), byte(a[i]>>8&0xff), byte(a[i]>>16&0xff), byte(a[i]>>24&0xff))
if index >= length {
return 0
} else {
return data[index]
} }
return bytes
} }
func int32ToAsciiBytes(data []int32) []byte { func Checksum(challengeCode, username, hashedMd5, ip, info string) string {
result := make([]byte, len(data)) str := challengeCode + username +
for i, number := range data { challengeCode + hashedMd5 +
result[i] = byte(number) challengeCode + "7" +
} challengeCode + ip +
challengeCode + "200" +
challengeCode + "1" +
challengeCode + info
sha := sha1.New()
sha.Write([]byte(str))
return result return hex.EncodeToString(sha.Sum(nil))
} }

@ -0,0 +1,14 @@
package codecs
import (
"crypto/hmac"
"crypto/md5"
"encoding/hex"
)
func HmacMd5(key, data string) string {
h := hmac.New(md5.New, []byte(key))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}

@ -1,7 +1,31 @@
package commons package commons
import "runtime" import (
"runtime"
"strconv"
"time"
)
func IsWindows() bool { func IsWindows() bool {
return runtime.GOOS == "windows" return runtime.GOOS == "windows"
} }
func CurrentMilliSecond() string {
return strconv.FormatInt(time.Now().UnixMilli(), 10)
}
func CurrentSecond() string {
return strconv.FormatInt(time.Now().Unix(), 10)
}
func GetOsName() string {
if IsWindows() {
return GetOsType() + " NT"
} else {
return GetOsType()
}
}
func GetOsType() string {
return runtime.GOOS
}

@ -0,0 +1,19 @@
package commons
import (
"fmt"
"regexp"
)
var jqueryJsonRegexp = regexp.MustCompile("\\d+\\((?P<json>.*?)\\)$")
func FilterJQueryPrefix(source string) string {
result := jqueryJsonRegexp.FindStringSubmatch(source)
if len(result) < 2 {
fmt.Errorf("jquery字段提纯失败,原字符串:", source)
return ""
} else {
return result[1]
}
}

@ -0,0 +1,13 @@
package error
type NotNeedLogin struct{}
func (receiver *NotNeedLogin) Error() string {
return "Network is available, needn't login."
}
type PasswordWrongError struct{}
func (receiver PasswordWrongError) Error() string {
return "The password is wrong."
}

@ -0,0 +1,24 @@
package model
type ChallengeCodeResponse struct {
Challenge string `json:"challenge"`
Error string `json:"error"`
ErrorMsg string `json:"error_msg"`
Res string `json:"res"`
SrunVer string `json:"srun_ver"`
St int `json:"st"`
}
//{
// "challenge": "3c6d08d667d0ee0ccad77c55b19d3e4ab2552f7163ec40a9389095a18f86c398",
// "client_ip": "10.16.1.9",
// "ecode": 0,
// "error": "ok",
// "error_msg": "",
// "expire": "52",
// "online_ip": "10.16.1.9",
// "res": "ok",
// "srun_ver": "SRunCGIAuthIntfSvr V1.18 B20211105",
// "st": 1668219964
//}

@ -0,0 +1,21 @@
package network
import (
"canti/app/api"
"github.com/go-resty/resty/v2"
)
const (
TestBaidu = "http://baidu.com"
TestTencent = "http://qq.com"
TestTaobao = "http://taobao.com"
TestSchoolNetwork = api.Host
)
// CheckNetwork 检查网络是否可通
func CheckNetwork(testUrl string) bool {
client := resty.New()
resp, err := client.R().SetHeaders(defaultHeaders).Get(testUrl)
return err != nil && resp.Body() != nil
}

@ -0,0 +1,52 @@
package network
import (
"canti/app/commons"
"strconv"
"time"
)
type QueryParam map[string]string
func (param QueryParam) Add(key string, value string) {
param[key] = value
}
// generateCallback callback参数生成,虽然说实际上callback参数随意设置也行,但是还是生成一个类似的比较好
func generateCallback() string {
return "jQuery_1124005588867363182781_" + strconv.FormatInt(time.Now().UnixMilli(), 10)
}
func userInfoQuery() QueryParam {
return QueryParam{
"callback": generateCallback(),
}
}
func challengeCodeQuery(username string, ip string) QueryParam {
return QueryParam{
"callback": generateCallback(),
"username": username,
"ip": ip,
"_": commons.CurrentMilliSecond(),
}
}
func authQuery(username, password, checkSum, info, ip string) QueryParam {
return QueryParam{
"callback": generateCallback(),
"action": "login",
"username": username,
"password": password,
"os": commons.GetOsType(),
"name": commons.GetOsName(),
"double_stack": "0",
"chksum": checkSum,
"info": info,
"ac_id": "7",
"ip": ip,
"n": "200",
"type": "1",
"_": commons.CurrentMilliSecond(),
}
}

@ -0,0 +1,88 @@
package network
import (
"canti/app/api"
"canti/app/codecs"
"canti/app/commons"
"canti/app/model"
"encoding/json"
"github.com/go-resty/resty/v2"
)
var defaultHeaders = map[string]string{
"Accept": "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Host": "59.68.177.183",
"Pragma": "",
"Referer": "http://59.68.177.183/srun_portal_pc?ac_id=7&theme=pro",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.42",
"X-Requested-With": "XMLHttpRequest",
}
func sendGetRequest(url string, param QueryParam) (string, error) {
client := resty.New()
resp, err := client.R().
SetQueryParams(param).
SetHeaders(defaultHeaders).
Get(url)
if err != nil {
return "", err
}
return commons.FilterJQueryPrefix(resp.String()), nil
}
func RequestAuth(username, password, ip, challengeCode string) (string, error) {
md5Password := codecs.HmacMd5(challengeCode, password)
jsonMap := infoBody{
Username: username,
Password: password,
Ip: ip,
Acid: "7",
EncVer: "srun_bx1",
}
infoData, _ := json.Marshal(jsonMap)
info := string(infoData)
encodedInfo := `{SRBX1}` + codecs.SRBX1Encode(info, challengeCode)
checksum := codecs.Checksum(challengeCode, username, md5Password, ip, encodedInfo)
return sendGetRequest(api.AuthApi, authQuery(username, `{md5}`+md5Password, checksum, encodedInfo, ip))
}
type infoBody struct {
Username string `json:"username"`
Password string `json:"password"`
Ip string `json:"ip"`
Acid string `json:"acid"`
EncVer string `json:"enc_ver"`
}
func RequestChallengeCode(username, ip string) (string, error) {
return sendGetRequest(api.ChallengeCodeApi, challengeCodeQuery(username, ip))
}
func RequestUserInfo() (string, error) {
return sendGetRequest(api.UserInfoApi, userInfoQuery())
}
func GetClientIp() (ip string, errs error) {
userInfoJson, err := RequestUserInfo()
if err != nil {
return "", err
}
var userInfo model.UserInfo
err2 := json.Unmarshal([]byte(userInfoJson), &userInfo)
if err2 != nil {
return "", err2
}
return userInfo.OnlineIp, nil
}

@ -0,0 +1,14 @@
package network
type ChallengeCodeResponse struct {
Challenge string `json:"challenge"`
ClientIp string `json:"client_ip"`
Ecode int `json:"ecode"`
Error string `json:"error"`
ErrorMsg string `json:"error_msg"`
Expire string `json:"expire"`
OnlineIp string `json:"online_ip"`
Res string `json:"res"`
SrunVer string `json:"srun_ver"`
St int `json:"st"`
}

@ -0,0 +1,46 @@
package service
import (
"canti/app/model"
"canti/app/network"
"encoding/json"
"errors"
"fmt"
)
var NoNeedLogin = errors.New("no need to login")
var UserinfoError = errors.New("error happens in requesting userinfo")
var ChallengeCodeError = errors.New("error happens in requesting challenge code")
func WebLogin(username string, password string) error {
// 能直接上网的话就不用登陆了
if network.CheckNetwork(network.TestBaidu) {
return NoNeedLogin
}
ip, err := network.GetClientIp()
if err != nil || ip == "" || ip == "<nil>" {
return UserinfoError
}
fmt.Print("Got ip:")
fmt.Println(ip)
challengeCodeJson, err2 := network.RequestChallengeCode(username, ip)
if err2 != nil || challengeCodeJson == "" {
return err
}
var challengeCodeResponse model.ChallengeCodeResponse
jsonError := json.Unmarshal([]byte(challengeCodeJson), &challengeCodeResponse)
if jsonError != nil {
return jsonError
} else if challengeCodeResponse.Challenge == "" {
_ = fmt.Errorf(challengeCodeJson)
return ChallengeCodeError
}
fmt.Print(challengeCodeResponse)
return nil
}

@ -0,0 +1,8 @@
{
"username": "202100000000",
"password": "password12450",
"method": "web",
"max-retry-times": 3,
"keep-alive": false,
"reconnect": true
}

@ -0,0 +1,7 @@
canti:
username: "202100000000"
password: "password12450"
method: "web"
max-retry-times: 3
keep-alive: false
reconnect: true

@ -2,11 +2,16 @@ module canti
go 1.18 go 1.18
require (
github.com/go-resty/resty/v2 v2.7.0
github.com/kardianos/service v1.2.2
github.com/urfave/cli/v2 v2.23.5
)
require ( require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/kardianos/service v1.2.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli/v2 v2.23.5 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
golang.org/x/sys v0.2.0 // indirect golang.org/x/sys v0.2.0 // indirect
) )

@ -1,5 +1,7 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@ -8,9 +10,13 @@ github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw=
github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

Loading…
Cancel
Save