From 4fd9c37944330a530e0e55c7f9347f404e0571cd Mon Sep 17 00:00:00 2001
From: starainrt
Date: Sun, 15 Sep 2024 15:27:50 +0800
Subject: [PATCH] update
---
cert/ca.go | 12 +
cert/cert.go | 82 ++++--
cert/cmd.go | 71 +++--
cert/csr.go | 57 ++--
dns/dns.go | 1 +
go.mod | 67 +++--
go.sum | 179 ++++++++++--
httpserver/cmd.go | 63 +++++
httpserver/goserver_test.go | 10 +
httpserver/server.go | 465 ++++++++++++++-----------------
httpserver/template.html | 478 ++++++++++++++++++++++++++++++++
keygen/cmd.go | 7 +
main.go | 5 +-
mget/cmd.go | 130 +++++++++
mget/process.go | 99 +++++++
mget/range.go | 84 ++++++
mget/range_test.go | 30 ++
mget/redo.go | 138 ++++++++++
mget/util.go | 141 ++++++++++
mget/wget.go | 526 ++++++++++++++++++++++++++++++++++++
mget/wget_test.go | 35 +++
net/cmd.go | 1 +
net/natt.go | 167 ++++++++++--
net/sshjar.go | 2 +-
net/trace.go | 5 +-
netforward/cmd.go | 1 +
netforward/forward.go | 53 +++-
whois/cmd.go | 267 +++++++++++++++++-
28 files changed, 2767 insertions(+), 409 deletions(-)
create mode 100644 httpserver/goserver_test.go
create mode 100644 httpserver/template.html
create mode 100644 mget/cmd.go
create mode 100644 mget/process.go
create mode 100644 mget/range.go
create mode 100644 mget/range_test.go
create mode 100644 mget/redo.go
create mode 100644 mget/util.go
create mode 100644 mget/wget.go
create mode 100644 mget/wget_test.go
diff --git a/cert/ca.go b/cert/ca.go
index f07eeee..e9af752 100644
--- a/cert/ca.go
+++ b/cert/ca.go
@@ -50,3 +50,15 @@ func LoadCA(caKeyPath, caCertPath, KeyPwd string) (crypto.PrivateKey, *x509.Cert
}
return caKey, cert, nil
}
+
+func LoadPriv(caKeyPath, KeyPwd string) (crypto.PrivateKey, error) {
+ caKeyBytes, err := os.ReadFile(caKeyPath)
+ if err != nil {
+ return nil, err
+ }
+ caKey, err := starcrypto.DecodePrivateKey(caKeyBytes, KeyPwd)
+ if err != nil {
+ return nil, err
+ }
+ return caKey, nil
+}
diff --git a/cert/cert.go b/cert/cert.go
index e757eab..e46ce52 100644
--- a/cert/cert.go
+++ b/cert/cert.go
@@ -15,6 +15,7 @@ import (
"fmt"
"golang.org/x/crypto/ssh"
"os"
+ "reflect"
"software.sslmate.com/src/go-pkcs12"
"strings"
)
@@ -125,7 +126,7 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y:%d\n", n.Y)
- case *ed25519.PrivateKey:
+ case *ed25519.PrivateKey, ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
@@ -208,7 +209,7 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("公钥公钥Y:%d\n", n.Y)
case *ecdh.PublicKey:
starlog.Green("公钥算法为ECDH\n")
- case *ed25519.PublicKey:
+ case *ed25519.PublicKey, ed25519.PublicKey:
starlog.Green("公钥算法为ED25519\n")
default:
starlog.Green("未知公钥类型\n")
@@ -257,12 +258,18 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y:%d\n", n.Y)
+ case ed25519.PrivateKey:
+ starlog.Green("这是一个ED25519私钥\n")
+ sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
+ starlog.Green("公钥:%s\n", string(sshPub))
case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
- case *ecdh.PrivateKey:
+ sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
+ starlog.Green("公钥:%s\n", string(sshPub))
+ case ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
- starlog.Green("未知私钥类型\n")
+ starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n))
}
continue
@@ -392,12 +399,18 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y:%d\n", n.Y)
+ case ed25519.PrivateKey:
+ starlog.Green("这是一个ED25519私钥\n")
+ sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
+ starlog.Green("公钥:%s\n", string(sshPub))
case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
- case *ecdh.PrivateKey:
+ sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
+ starlog.Green("公钥:%s\n", string(sshPub))
+ case ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
- starlog.Green("未知私钥类型\n")
+ starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n))
}
continue
case "OPENSSH PUBLIC KEY":
@@ -410,7 +423,7 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("公钥算法:%s\n", pub.Type())
continue
default:
- starlog.Infof("未知证书文件类型\n")
+ starlog.Infof("不支持的证书文件类型:%v\n", reflect.TypeOf(block))
}
}
}
@@ -546,9 +559,9 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n")
- case *ed25519.PrivateKey:
+ case ed25519.PrivateKey, *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
- case *ecdh.PrivateKey:
+ case ecdh.PrivateKey, *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
@@ -633,9 +646,9 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n")
- case *ed25519.PrivateKey:
+ case ed25519.PrivateKey, *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
- case *ecdh.PrivateKey:
+ case ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
@@ -756,10 +769,10 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
case *dsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个DSA私钥\n")
- case *ed25519.PrivateKey:
+ case ed25519.PrivateKey, *ed25519.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ED25519私钥\n")
- case *ecdh.PrivateKey:
+ case ecdh.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ECDH私钥\n")
default:
@@ -777,28 +790,42 @@ func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) e
if err != nil {
return err
}
+ fmt.Println(len(keys))
for _, v := range keys {
if v == nil {
continue
}
switch n := v.(type) {
- case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
- data, err = x509.MarshalPKCS8PrivateKey(n)
+ case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, *ed25519.PrivateKey, ecdh.PrivateKey:
+ var key interface{} = n
+ if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
+ fmt.Println("1")
+ key = *(n.(*ed25519.PrivateKey))
+ }
+ fmt.Println("2")
+ data, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return err
}
+ fmt.Println("3")
var block *pem.Block
if newPwd != "" {
block, err = x509.EncryptPEMBlock(rand.Reader, "PRIVATE KEY", data, []byte(newPwd), x509.PEMCipherAES256)
+ if err != nil {
+ return err
+ }
} else {
block = &pem.Block{Type: "PRIVATE KEY", Bytes: data}
}
+ fmt.Println("4")
err = os.WriteFile(outpath+"/"+originName+".pkcs8", pem.EncodeToMemory(block), 0644)
if err != nil {
+ fmt.Println("5")
return err
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".pkcs8")
}
+ fmt.Println("6")
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
data, err = x509.MarshalPKIXPublicKey(n)
if err != nil {
@@ -810,6 +837,8 @@ func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) e
} else {
starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".pub.pkcs8")
}
+ default:
+ return fmt.Errorf("未知的密钥类型:%v", reflect.TypeOf(n))
}
}
return nil
@@ -865,8 +894,11 @@ func Pkcs12(keys []any, certs []x509.Certificate, enPwd string, originName strin
continue
}
switch n := v.(type) {
- case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
+ case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
priv = n
+ if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
+ priv = *(n.(*ed25519.PrivateKey))
+ }
break
}
}
@@ -927,7 +959,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
} else {
starlog.Green("已将公钥保存到%s\n", fmt.Sprintf("%s/%s_%v.tran.pub", outpath, originName, idx))
}
- case *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
+ case *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey:
data, err = x509.MarshalPKCS8PrivateKey(n)
if err != nil {
return err
@@ -938,7 +970,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".tran.key")
}
- case *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
+ case *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey:
data, err = x509.MarshalPKIXPublicKey(n)
if err != nil {
return err
@@ -966,11 +998,15 @@ func Openssh(data []byte, pwd, newPwd string, originName string, outpath string)
}
var block *pem.Block
switch n := v.(type) {
- case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
- if newPwd != "" {
- block, err = ssh.MarshalPrivateKey(n, "")
+ case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
+ var key interface{} = n
+ if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
+ key = *(n.(*ed25519.PrivateKey))
+ }
+ if newPwd == "" {
+ block, err = ssh.MarshalPrivateKey(key, "")
} else {
- block, err = ssh.MarshalPrivateKeyWithPassphrase(n, "", []byte(newPwd))
+ block, err = ssh.MarshalPrivateKeyWithPassphrase(key, "", []byte(newPwd))
}
if err != nil {
return err
@@ -981,7 +1017,7 @@ func Openssh(data []byte, pwd, newPwd string, originName string, outpath string)
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".openssh")
}
- case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
+ case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey:
sk, err := ssh.NewPublicKey(n)
if err != nil {
return err
diff --git a/cert/cmd.go b/cert/cmd.go
index de60390..84268d3 100644
--- a/cert/cmd.go
+++ b/cert/cmd.go
@@ -4,6 +4,7 @@ import (
"b612.me/starcrypto"
"b612.me/stario"
"b612.me/starlog"
+ "crypto"
"crypto/x509"
"fmt"
"github.com/spf13/cobra"
@@ -64,12 +65,6 @@ var CmdCsr = &cobra.Command{
if dnsName == nil {
dnsName = stario.MessageBox("请输入dns名称,用逗号分割:", "").MustSliceString(",")
}
- if startStr == "" {
- startStr = stario.MessageBox("请输入开始时间:", "").MustString()
- }
- if endStr == "" {
- endStr = stario.MessageBox("请输入结束时间:", "").MustString()
- }
}
start, err = time.Parse(time.RFC3339, startStr)
if err != nil {
@@ -81,7 +76,12 @@ var CmdCsr = &cobra.Command{
starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
os.Exit(1)
}
- csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName, start, end, isCa, maxPathLenZero, maxPathLen))
+ key, err := LoadPriv(caKey, caKeyPwd)
+ if err != nil {
+ starlog.Errorln("加载Key错误", err)
+ os.Exit(1)
+ }
+ csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName), key)
err = os.WriteFile(savefolder+"/"+name+".csr", csr, 0644)
if err != nil {
starlog.Errorln("保存csr文件错误", err)
@@ -112,10 +112,21 @@ var CmdGen = &cobra.Command{
starlog.Errorln("证书公钥不能为空")
os.Exit(1)
}
- caKeyRaw, caCertRaw, err := LoadCA(caKey, caCert, caKeyPwd)
- if err != nil {
- starlog.Errorln("加载CA错误", err)
- os.Exit(1)
+ var caKeyRaw crypto.PrivateKey
+ var caCertRaw *x509.Certificate
+ var err error
+ if !isCa {
+ caKeyRaw, caCertRaw, err = LoadCA(caKey, caCert, caKeyPwd)
+ if err != nil {
+ starlog.Errorln("加载CA错误", err)
+ os.Exit(1)
+ }
+ } else {
+ caKeyRaw, err = LoadPriv(caKey, caKeyPwd)
+ if err != nil {
+ starlog.Errorln("加载CA错误", err)
+ os.Exit(1)
+ }
}
csrRaw, err := LoadCsr(csr)
if err != nil {
@@ -132,7 +143,18 @@ var CmdGen = &cobra.Command{
starlog.Errorln("解析公钥错误", err)
os.Exit(1)
}
- cert, err := MakeCert(caKeyRaw, caCertRaw, csrRaw, pubKeyRaw)
+ certReq := &x509.Certificate{
+ Subject: csrRaw.Subject,
+ IsCA: isCa,
+ NotBefore: start,
+ NotAfter: end,
+ MaxPathLen: maxPathLen,
+ MaxPathLenZero: maxPathLenZero,
+ }
+ if isCa {
+ caCertRaw = certReq
+ }
+ cert, err := MakeCert(caKeyRaw, caCertRaw, certReq, pubKeyRaw)
if err != nil {
starlog.Errorln("生成证书错误", err)
os.Exit(1)
@@ -171,19 +193,21 @@ var CmdParse = &cobra.Command{
func init() {
Cmd.AddCommand(CmdCsr)
CmdCsr.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式")
- CmdCsr.Flags().StringVarP(&country, "country", "c", "", "国家")
- CmdCsr.Flags().StringVarP(&province, "province", "p", "", "省份")
- CmdCsr.Flags().StringVarP(&city, "city", "t", "", "城市")
+ CmdCsr.Flags().StringVarP(&country, "country", "c", "CN", "国家")
+ CmdCsr.Flags().StringVarP(&province, "province", "p", "B612", "省份")
+ CmdCsr.Flags().StringVarP(&city, "city", "t", "B612", "城市")
CmdCsr.Flags().StringVarP(&org, "org", "o", "", "组织")
CmdCsr.Flags().StringVarP(&orgUnit, "orgUnit", "u", "", "组织单位")
- CmdCsr.Flags().StringVarP(&name, "name", "n", "", "通用名称")
+ CmdCsr.Flags().StringVarP(&name, "name", "n", "Starainrt", "通用名称")
CmdCsr.Flags().StringSliceVarP(&dnsName, "dnsName", "d", nil, "dns名称")
- CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
- CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
CmdCsr.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
- CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
- CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
- CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
+ CmdCsr.Flags().StringVarP(&caKey, "secret-key", "k", "", "加密私钥")
+ CmdCsr.Flags().StringVarP(&caKeyPwd, "secret-key-passwd", "K", "", "加密私钥的密码")
+ //CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
+ //CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
+ //CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
+ //CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
+ //CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
CmdGen.Flags().StringVarP(&caKey, "caKey", "k", "", "CA私钥")
CmdGen.Flags().StringVarP(&caCert, "caCert", "C", "", "CA证书")
@@ -191,6 +215,11 @@ func init() {
CmdGen.Flags().StringVarP(&pubKey, "pubKey", "P", "", "证书公钥")
CmdGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
CmdGen.Flags().StringVarP(&caKeyPwd, "caKeyPwd", "p", "", "CA私钥密码")
+ CmdGen.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
+ CmdGen.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
+ CmdGen.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
+ CmdGen.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
+ CmdGen.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
Cmd.AddCommand(CmdGen)
CmdParse.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
diff --git a/cert/csr.go b/cert/csr.go
index 90c3a6c..0d41d1e 100644
--- a/cert/csr.go
+++ b/cert/csr.go
@@ -1,17 +1,16 @@
package cert
import (
+ "crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
- "math/big"
"net"
"os"
- "time"
)
-func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []string, start, end time.Time, isCa bool, maxPathLenZero bool, maxPathLen int) *x509.Certificate {
+func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []string) *x509.CertificateRequest {
var trueDNS []string
var trueIp []net.IP
for _, v := range dnsName {
@@ -22,15 +21,17 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s
}
trueIp = append(trueIp, ip)
}
- ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
- eku := x509.ExtKeyUsageServerAuth
- if isCa {
- ku = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature
- eku = x509.ExtKeyUsageAny
- }
- return &x509.Certificate{
- Version: 3,
- SerialNumber: big.NewInt(time.Now().Unix()),
+ /*
+ ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
+ eku := x509.ExtKeyUsageServerAuth
+ if isCa {
+ ku = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature
+ eku = x509.ExtKeyUsageAny
+ }
+ */
+ return &x509.CertificateRequest{
+ Version: 3,
+ //SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: s2s(country),
Province: s2s(province),
@@ -39,23 +40,27 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s
OrganizationalUnit: s2s(orgUnit),
CommonName: name,
},
- DNSNames: trueDNS,
- IPAddresses: trueIp,
- NotBefore: start,
- NotAfter: end,
- BasicConstraintsValid: true,
- IsCA: isCa,
- MaxPathLen: maxPathLen,
- MaxPathLenZero: maxPathLenZero,
- KeyUsage: ku,
- ExtKeyUsage: []x509.ExtKeyUsage{eku},
+ DNSNames: trueDNS,
+ IPAddresses: trueIp,
+ //NotBefore: start,
+ //NotAfter: end,
+ //BasicConstraintsValid: true,
+ //IsCA: isCa,
+ //MaxPathLen: maxPathLen,
+ //MaxPathLenZero: maxPathLenZero,
+ //KeyUsage: ku,
+ //ExtKeyUsage: []x509.ExtKeyUsage{eku},
}
}
-func outputCsr(csr *x509.Certificate) []byte {
+func outputCsr(csr *x509.CertificateRequest, priv interface{}) []byte {
+ csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csr, priv)
+ if err != nil {
+ return nil
+ }
return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE REQUEST",
- Bytes: csr.Raw,
+ Bytes: csrBytes,
})
}
@@ -66,7 +71,7 @@ func s2s(str string) []string {
return []string{str}
}
-func LoadCsr(csrPath string) (*x509.Certificate, error) {
+func LoadCsr(csrPath string) (*x509.CertificateRequest, error) {
csrBytes, err := os.ReadFile(csrPath)
if err != nil {
return nil, err
@@ -75,7 +80,7 @@ func LoadCsr(csrPath string) (*x509.Certificate, error) {
if block == nil || block.Type != "CERTIFICATE REQUEST" {
return nil, errors.New("Failed to decode PEM block containing the certificate")
}
- cert, err := x509.ParseCertificate(block.Bytes)
+ cert, err := x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return nil, err
}
diff --git a/dns/dns.go b/dns/dns.go
index 4899978..392d237 100644
--- a/dns/dns.go
+++ b/dns/dns.go
@@ -151,6 +151,7 @@ func (c *DoHClient) Exchange(req *dns.Msg, address string) (r *dns.Msg, rtt time
// No need to use hreq.URL.Query()
hreq, _ := http.NewRequest("GET", address+"?dns="+string(b64), nil)
+ hreq.Header.Set("User-Agent", "B612 DoH Client")
hreq.Header.Add("Accept", DoHMediaType)
resp, err := c.cli.Do(hreq)
if err != nil {
diff --git a/go.mod b/go.mod
index f5b23e3..a07de55 100644
--- a/go.mod
+++ b/go.mod
@@ -1,19 +1,21 @@
module b612.me/apps/b612
-go 1.21
+go 1.21.2
-toolchain go1.21.2
+toolchain go1.22.4
require (
- b612.me/notify v1.2.5
+ b612.me/notify v1.2.6
+ b612.me/sdk/whois v0.0.0-20240816133027-129514a15991
b612.me/starcrypto v0.0.5
- b612.me/stario v0.0.9
- b612.me/starlog v1.3.3
- b612.me/starnet v0.1.8
- b612.me/staros v1.1.7
+ b612.me/stario v0.0.10
+ b612.me/starlog v1.3.4
+ b612.me/starmap v1.2.4
+ b612.me/starnet v0.2.1
+ b612.me/staros v1.1.8
b612.me/starssh v0.0.2
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd
- b612.me/wincmd v0.0.3
+ b612.me/wincmd v0.0.4
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
github.com/emersion/go-smtp v0.20.2
@@ -21,32 +23,61 @@ require (
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
+ github.com/huin/goupnp v1.3.0
github.com/inconshreveable/mousetrap v1.1.0
- github.com/likexian/whois v1.15.1
github.com/miekg/dns v1.1.58
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/spf13/cobra v1.8.0
github.com/things-go/go-socks5 v0.0.5
- golang.org/x/crypto v0.21.0
- golang.org/x/net v0.21.0
+ github.com/vbauerster/mpb/v8 v8.8.3
+ golang.org/x/crypto v0.26.0
+ golang.org/x/net v0.28.0
+ golang.org/x/sys v0.24.0
software.sslmate.com/src/go-pkcs12 v0.4.0
-
)
require (
- b612.me/starmap v1.2.4 // indirect
b612.me/win32api v0.0.2 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
+ github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
+ github.com/VividCortex/ewma v1.2.0 // indirect
+ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
+ github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
+ github.com/cloudflare/cloudflare-go v0.86.0 // indirect
+ github.com/cpu/goacmedns v0.1.1 // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
+ github.com/google/go-querystring v1.1.0 // indirect
+ github.com/google/uuid v1.3.1 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/jlaffaye/ftp v0.1.0 // indirect
+ github.com/jmespath/go-jmespath v0.4.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/fs v0.1.0 // indirect
+ github.com/kylelemons/godebug v1.1.0 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/sftp v1.13.4 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
+ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
+ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
golang.org/x/image v0.6.0 // indirect
- golang.org/x/mod v0.14.0 // indirect
- golang.org/x/sys v0.18.0 // indirect
- golang.org/x/term v0.18.0 // indirect
- golang.org/x/text v0.14.0 // indirect
- golang.org/x/tools v0.17.0 // indirect
+ golang.org/x/mod v0.17.0 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/term v0.23.0 // indirect
+ golang.org/x/text v0.17.0 // indirect
+ golang.org/x/time v0.5.0 // indirect
+ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
)
diff --git a/go.sum b/go.sum
index 8bf2fa7..fae4fd6 100644
--- a/go.sum
+++ b/go.sum
@@ -1,31 +1,66 @@
-b612.me/notify v1.2.5 h1:fASpzi8YAo78g6jKnefzfbsQz0nGNYFbClB2Bylj+MA=
b612.me/notify v1.2.5/go.mod h1:GTnAdC6v9krGxtC8Gkn8TcyUsYnHSiHjRAXsONPiLpI=
+b612.me/notify v1.2.6 h1:fY+0ccP6cJCDvnfRilmPlDK+J8xTYBpYNwf7jaC2IIE=
+b612.me/notify v1.2.6/go.mod h1:awcFq3bvbkf3hdviUtOW16Io0IEJXkNPgno7IRe7B9g=
+b612.me/sdk/whois v0.0.0-20240816133027-129514a15991 h1:+eXeVqkoi4s9sNoY9eGt4ieSbr1+Deos8fC8wMfOCNI=
+b612.me/sdk/whois v0.0.0-20240816133027-129514a15991/go.mod h1:PB9QpUoQEip0MB3st8H5hmnDTcDsR0RGV0BfpUr5XDg=
b612.me/starcrypto v0.0.3/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
b612.me/starcrypto v0.0.5 h1:Aa4pRDO2lBH2Aw+vz8NuUtRb73J8z5aOa9SImBY5sq4=
b612.me/starcrypto v0.0.5/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
-b612.me/stario v0.0.9 h1:bFDlejUJMwZ12a09snZJspQsOlkqpDAl9qKPEYOGWCk=
b612.me/stario v0.0.9/go.mod h1:x4D/x8zA5SC0pj/uJAi4FyG5p4j5UZoMEZfvuRR6VNw=
-b612.me/starlog v1.3.3 h1:xYCHouOTpo6dsFg2A92TqTznxvRPPS/ovMWs7CJZ9WI=
-b612.me/starlog v1.3.3/go.mod h1:h928hRahvWqcXXxy0uKWZ+oFe3K7kFQDHKiBemedLyE=
+b612.me/stario v0.0.10 h1:+cIyiDCBCjUfodMJDp4FLs+2E1jo7YENkN+sMEe6550=
+b612.me/stario v0.0.10/go.mod h1:1Owmu9jzKWgs4VsmeI8YWlGwLrCwPNM/bYpxkyn+MMk=
+b612.me/starlog v1.3.4 h1:XuVYo6NCij8F4TGSgtEuMhs1WkZ7HZNnYUgQ3nLTt84=
+b612.me/starlog v1.3.4/go.mod h1:37GMgkWQMOAjzKs49Hf2i8bLwdXbd9QF4zKhUxFDoSk=
b612.me/starmap v1.2.4 h1:gfAyBtzW3KKCIyI14I2pEqGsR/u2E+3tkH0xRqtWb4E=
b612.me/starmap v1.2.4/go.mod h1:EhOUzkItc5IcyBmr1C7/vmZBbW3GgCWs63hGn7WhuMc=
-b612.me/starnet v0.1.8 h1:sTNytUFP38i2BFR9nha3lTSLYb7El3tvKpZplYCrhZk=
b612.me/starnet v0.1.8/go.mod h1:k862Kf8DiVWTqdX6PHTFb6NoT+3G3Y74n8NCyNhuP0Y=
-b612.me/staros v1.1.7 h1:GkQp5sBPRqo3pOh6nKyKffJydyYrjlfzpsPxNeVJ26g=
-b612.me/staros v1.1.7/go.mod h1:Yi/WfvIqRAPQEf/eiaaIwrL5LNcUbqzMIuFIyJJOU40=
+b612.me/starnet v0.2.1 h1:17n3wa2QgBYbO1rqDLAhyc2DfvbBc23GSp1v42Pvmiw=
+b612.me/starnet v0.2.1/go.mod h1:6q+AXhYeXsIiKV+hZZmqAMn8S48QcdonURJyH66rbzI=
+b612.me/staros v1.1.8 h1:5Bpuf9q2nH75S2ekmieJuH3Y8LTqg/voxXCOiMAC3kk=
+b612.me/staros v1.1.8/go.mod h1:4KmokjKXFW5h1hbA4aIv5O+2FptVzBubCo7IPirfqm8=
b612.me/starssh v0.0.2 h1:cYlrXjd7ZTesdZG+7XcoLsEEMROaeWMTYonScBLnvyY=
b612.me/starssh v0.0.2/go.mod h1:1gvG/GT5Y5EvOx9ZKnLFUa+wOX20HaqS1IuTnU7BOlk=
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd h1:EsmnczYZhOV8JTxD/m0N0qBjfZN8JuLNrTJ6z3S8YqA=
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd/go.mod h1:yKdeLQHZ3scqyjw1ZODCoL+hLmkOp2eu5riP4agraz8=
b612.me/win32api v0.0.2 h1:5PwvPR5fYs3a/v+LjYdtRif+5Q04zRGLTVxmCYNjCpA=
b612.me/win32api v0.0.2/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0=
-b612.me/wincmd v0.0.3 h1:GYrkYnNun39yfNcA2+u0h4VW/BYbTrJK39QW4W1LCYA=
-b612.me/wincmd v0.0.3/go.mod h1:nWdNREHO6F+2PngEUcyYN3Eo7DzYEVa/fO6czd9d/fo=
+b612.me/wincmd v0.0.4 h1:fv9p1V8mw2HdUjaoZBWZy0T41JftueyLxAuch1MgtdI=
+b612.me/wincmd v0.0.4/go.mod h1:o3yPoE+DpVPHGKl/q1WT1C8OaIVwHEnpeNgMFqzlwD8=
+github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
+github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
+github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
+github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
+github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI=
+github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
+github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
+github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
+github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
@@ -34,36 +69,88 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1X
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.20.2 h1:peX42Qnh5Q0q3vrAnRy43R/JwTnnv75AebxbkTL7Ia4=
github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss=
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 h1:JdOp2qR5PF4O75tzHeqrwnDDv8oHDptWyTbyYS4fD8E=
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
+github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
+github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
+github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
+github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
+github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
+github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jlaffaye/ftp v0.1.0 h1:DLGExl5nBoSFoNshAUHwXAezXwXBvFdx7/qwhucWNSE=
github.com/jlaffaye/ftp v0.1.0/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM=
-github.com/likexian/whois v1.15.1 h1:6vTMI8n9s1eJdmcO4R9h1x99aQWIZZX1CD3am68gApU=
-github.com/likexian/whois v1.15.1/go.mod h1:/nxmQ6YXvLz+qTxC/QFtEJNAt0zLuRxJrKiWpBJX8X0=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
+github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
@@ -71,55 +158,86 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ=
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
+github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ=
+github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
-golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
+golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -127,17 +245,32 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
-golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
diff --git a/httpserver/cmd.go b/httpserver/cmd.go
index 53e6414..88b413b 100644
--- a/httpserver/cmd.go
+++ b/httpserver/cmd.go
@@ -5,9 +5,12 @@ import (
"b612.me/staros"
"context"
"encoding/json"
+ "fmt"
"github.com/spf13/cobra"
"os"
"os/signal"
+ "regexp"
+ "strconv"
"strings"
)
@@ -15,6 +18,7 @@ var s HttpServer
var daemon bool
var hooks string
+var speedlimit string
func init() {
Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify")
@@ -34,8 +38,54 @@ func init() {
Cmd.Flags().StringVar(&s.page403, "403", "", "自定义403页面地址")
Cmd.Flags().StringVar(&s.page404, "404", "", "自定义404页面地址")
Cmd.Flags().BoolVarP(&s.httpDebug, "debug", "D", false, "开启调试模式")
+ Cmd.Flags().StringSliceVarP(&s.noListPath, "nolist", "N", []string{}, "禁止列出文件的路径,如/")
+ Cmd.Flags().StringToStringVarP(&s.listPwd, "listpwd", "L", map[string]string{}, "列出文件的路径的密码,如/=/password")
+ Cmd.Flags().BoolVarP(&s.listSameForFile, "list-same", "S", false, "如开启,文件的获取权限将与文件夹保持一致")
+ Cmd.Flags().StringVarP(&speedlimit, "speedlimit", "s", "", "限速,如1M,意思是1MB/s")
+
Cmd.Flags().Bool("daeapplied", false, "")
+ Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址")
+ Cmd.Flags().StringVar(&s.mobildBackground, "mbackground", "", "移动端背景图片地址")
+
Cmd.Flags().MarkHidden("daeapplied")
+
+}
+func parseSpeedString(speedString string) (uint64, error) {
+ // 定义单位及其对应的字节值
+ unitMultipliers := map[string]int{
+ "b": 1, "": 1,
+ "k": 1024, "kb": 1024, "kib": 1024,
+ "m": 1024 * 1024, "mb": 1024 * 1024, "mib": 1024 * 1024,
+ "g": 1024 * 1024 * 1024, "gb": 1024 * 1024 * 1024, "gib": 1024 * 1024 * 1024,
+ "t": 1024 * 1024 * 1024 * 1024, "tb": 1024 * 1024 * 1024 * 1024, "tib": 1024 * 1024 * 1024 * 1024,
+ }
+
+ // 正则表达式匹配速度的格式
+ re := regexp.MustCompile(`(?i)^\s*([\d.]+)\s*(b|k|m|g|t|kb|mb|gb|tb|kib|mib|gib|tib)?\s*/?\s*s?\s*$`)
+ matches := re.FindStringSubmatch(strings.ToLower(speedString))
+ if matches == nil {
+ return 0, fmt.Errorf("invalid speed string format")
+ }
+
+ // 解析数值部分
+ value, err := strconv.ParseFloat(matches[1], 64)
+ if err != nil {
+ return 0, fmt.Errorf("invalid numeric value")
+ }
+
+ // 获取单位部分
+ unit := matches[2]
+ if unit == "" {
+ unit = "b"
+ }
+
+ // 根据单位计算最终的字节每秒值
+ multiplier, ok := unitMultipliers[unit]
+ if !ok {
+ return 0, fmt.Errorf("invalid unit in speed string")
+ }
+
+ return uint64(value * float64(multiplier)), nil
}
var Cmd = &cobra.Command{
@@ -43,6 +93,19 @@ var Cmd = &cobra.Command{
Short: "HTTP文件服务器(HTTP File Browser Server)",
Long: `HTTP文件服务器(HTTP File Browser Server)`,
Run: func(cmd *cobra.Command, args []string) {
+ if s.logpath != "" && starlog.GetWriter() == nil {
+ starlog.SetLogFile(s.logpath, starlog.Std, true)
+ }
+ if speedlimit != "" {
+ speed, err := parseSpeedString(speedlimit)
+ if err != nil {
+ starlog.Criticalln("Speed Limit Error:", err)
+ os.Exit(1)
+ }
+ s.speedlimit = speed
+ starlog.Infoln("Speed Limit:(user in):\t", speedlimit)
+ starlog.Infoln("Speed Limit (bytes/s):\t", speed)
+ }
if hooks != "" {
if !staros.Exists(hooks) {
starlog.Criticalln("hook file not exists")
diff --git a/httpserver/goserver_test.go b/httpserver/goserver_test.go
new file mode 100644
index 0000000..eb77908
--- /dev/null
+++ b/httpserver/goserver_test.go
@@ -0,0 +1,10 @@
+package httpserver
+
+import (
+ "net/http"
+ "testing"
+)
+
+func TestHttpServer(t *testing.T) {
+ http.ListenAndServe(":89", http.FileServer(http.Dir(`./`)))
+}
diff --git a/httpserver/server.go b/httpserver/server.go
index 4e7d79e..a773e84 100644
--- a/httpserver/server.go
+++ b/httpserver/server.go
@@ -8,8 +8,10 @@ import (
"context"
_ "embed"
"encoding/base64"
+ "encoding/json"
"errors"
"fmt"
+ "html/template"
"io"
"io/ioutil"
"math"
@@ -22,7 +24,7 @@ import (
"time"
)
-var version = "2.1.0"
+var version = "2.1.0.b11"
type HttpServerCfgs func(cfg *HttpServerCfg)
@@ -45,6 +47,14 @@ type HttpServerCfg struct {
ctx context.Context
hooks []ServerHook
httpDebug bool
+ noListPath []string
+ listPwd map[string]string
+ listSameForFile bool
+ // speed limit means xx bytes/s
+ speedlimit uint64
+
+ background string
+ mobildBackground string
}
type ServerHook struct {
@@ -67,219 +77,8 @@ var jquery []byte
//go:embed upload.html
var uploadPage []byte
-var htmlTitle string = `
-
-
-
- B612 Http Server %s
-
-
-
-
-
B612 Http Server - %s
-
%s
%s
-
-
-
- Name |
- Modified |
- Size |
- Type |
-
-
- `
-
-var htmlTail = `
-
-
-
- B612.Me © Apache 2.0 License
-
-
-
-
-
-`
+//go:embed template.html
+var templateHtml []byte
func WithHooks(hooks []ServerHook) HttpServerCfgs {
return func(cfg *HttpServerCfg) {
@@ -333,7 +132,7 @@ func (h *HttpServer) Run(ctx context.Context) error {
server.Shutdown(ctx)
}
}()
- if h.logpath != "" {
+ if h.logpath != "" && starlog.GetWriter() == nil {
starlog.SetLogFile(h.logpath, starlog.Std, true)
}
netcards, err := net.Interfaces()
@@ -401,7 +200,14 @@ func (h *HttpServer) Page403(w http.ResponseWriter) {
return
}
}
- w.Write([]byte(`B612 Http Server403 Forbidden
`))
+ w.Write([]byte(`
+
+ 403 Forbidden
+
+
403 Forbidden
+
B612 HTTP SERVER
+
+ `))
}
func (h *HttpServer) Page401(w http.ResponseWriter) {
@@ -575,26 +381,25 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
}
func (h *HttpServer) CalcRange(r *http.Request) (int64, int64) {
- var rangeStart, rangeEnd int64
- rangeStart, rangeEnd = -1, -1
- for k, v := range r.Header {
- if strings.ToLower(k) == "range" {
- if strings.Contains(v[0], "bytes=") {
- v[0] = strings.Replace(v[0], "bytes=", "", -1)
- } else {
- continue
- }
- data := strings.Split(v[0], "-")
- if len(data) == 0 {
- break
- }
- rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
- if len(data) > 1 {
- rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
- }
- //w.WriteHeader(206) //206 支持断点续传
- break
- }
+ var rangeStart, rangeEnd int64 = -1, -1
+ ranges := r.Header.Get("Range")
+ if ranges == "" {
+ return rangeStart, rangeEnd
+ }
+ if !strings.Contains(ranges, "bytes=") {
+ return rangeStart, rangeEnd
+ }
+ ranges = strings.TrimPrefix(ranges, "bytes=")
+ data := strings.Split(ranges, "-")
+ if len(data) == 0 {
+ return rangeStart, rangeEnd
+ }
+ rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
+ if len(data) > 1 {
+ rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
+ }
+ if rangeEnd == 0 {
+ rangeEnd = -1
}
return rangeStart, rangeEnd
}
@@ -630,16 +435,19 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat
if _, ok := h.willHook(fullpath); ok {
return nil
}
- w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
start, end := h.CalcRange(r)
if start != -1 {
if end == -1 {
+ w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size()-start, 10))
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(finfo.Size()-1, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
//w.Header().Set("Content-Length", strconv.FormatInt(fpinfo.Size()-rangeStart, 10))
} else {
+ w.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10))
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
//w.Header().Set("Content-Length", strconv.FormatInt(1+rangeEnd-rangeStart, 10))
}
+ } else {
+ w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
}
}
}
@@ -666,11 +474,57 @@ func (h *HttpServer) willHook(fullpath string) (ServerHook, bool) {
func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if staros.IsFolder(fullpath) {
+ if len(h.listPwd) != 0 {
+ for k, v := range h.listPwd {
+ if strings.HasPrefix(r.URL.Path, k) {
+ if r.URL.Query().Get("list") == v {
+ return h.getFolder(log, w, r, fullpath)
+ }
+ }
+ }
+ }
+ if len(h.noListPath) != 0 {
+ for _, v := range h.noListPath {
+ if strings.HasPrefix(r.URL.Path, v) {
+ h.Page403(w)
+ return nil
+ }
+ }
+ }
return h.getFolder(log, w, r, fullpath)
}
+ if !h.listSameForFile {
+ return h.getFile(log, w, r, fullpath)
+ }
+
+ if len(h.listPwd) != 0 {
+ for k, v := range h.listPwd {
+ if strings.HasPrefix(r.URL.Path, k) {
+ if r.URL.Query().Get("list") == v {
+ return h.getFile(log, w, r, fullpath)
+ }
+ }
+ }
+ }
+ if len(h.noListPath) != 0 {
+ for _, v := range h.noListPath {
+ if strings.HasPrefix(r.URL.Path, v) {
+ h.Page403(w)
+ return nil
+ }
+ }
+ }
return h.getFile(log, w, r, fullpath)
}
+type FileData struct {
+ Attr string `json:"attr"`
+ Name string `json:"name"`
+ Modified string `json:"modified"`
+ Size int64 `json:"size"`
+ Type string `json:"type"`
+}
+
func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
dir, err := ioutil.ReadDir(fullpath)
if err != nil {
@@ -688,14 +542,24 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
if h.uploadFolder != "" {
upload = `Upload Web Page Is Openned!`
}
- w.Write([]byte(fmt.Sprintf(htmlTitle, r.URL.Path, version, "Index of "+r.URL.Path, upload)))
+ attr := r.URL.Query().Get("list")
+ if attr != "" {
+ attr = "/?list=" + attr
+ }
+ var fdatas = make([]FileData, 0, len(dir)+1)
+
if r.URL.Path != "/" {
p := r.URL.Path
if p[len(p)-1:] != "/" {
p += "/"
}
- w.Write([]byte(fmt.Sprintf(`%s | %s | %s | %s |
`,
- p+"..", "../", "-", "-", "上层文件夹")))
+ fdatas = append(fdatas, FileData{
+ Attr: p + ".." + attr,
+ Name: "..",
+ Modified: "-",
+ Size: -1,
+ Type: "上层文件夹",
+ })
}
if r.URL.Path == "/" {
r.URL.Path = ""
@@ -706,23 +570,104 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
for _, v := range dir {
if v.Name() != "." || v.Name() != ".." {
if !v.IsDir() {
- w.Write([]byte(fmt.Sprintf(`%s | %s | %s | %s |
`,
- r.URL.Path+"/"+v.Name(), v.Name(), v.ModTime().Format("2006-01-02 15:04:05"), fmt.Sprintf("%d (%s)", v.Size(), h.trimSize(v.Size())), h.FileType(v.Name()))))
+ fdatas = append(fdatas, FileData{
+ Name: v.Name(),
+ Attr: r.URL.Path + "/" + v.Name(),
+ Modified: v.ModTime().Format("2006-01-02 15:04:05"),
+ Size: v.Size(),
+ Type: h.FileType(v.Name()),
+ })
} else {
- w.Write([]byte(fmt.Sprintf(`%s | %s | %s | %s |
`,
- r.URL.Path+"/"+v.Name(), v.Name()+"/", v.ModTime().Format("2006-01-02 15:04:05"), "-", "文件夹")))
+ fdatas = append(fdatas, FileData{
+ Name: v.Name() + "/",
+ Attr: r.URL.Path + "/" + v.Name() + attr,
+ Modified: v.ModTime().Format("2006-01-02 15:04:05"),
+ Size: -1,
+ Type: "文件夹",
+ })
}
}
}
- w.Write([]byte(htmlTail))
+ tmpt, err := template.New("index").Parse(string(templateHtml))
+ if err != nil {
+ log.Errorf("Parse Template Error:%v\n", err)
+ w.WriteHeader(502)
+ w.Write([]byte(`B612 Http Server502 SERVER ERROR
`))
+ return err
+ }
+ jData, err := json.Marshal(fdatas)
+ if err != nil {
+ log.Errorf("Json Marshal Failed:%v\n", err)
+ w.WriteHeader(502)
+ w.Write([]byte(`B612 Http Server502 SERVER ERROR
`))
+ return err
+ }
+ if r.URL.Path == "" {
+ r.URL.Path = "/"
+ }
+ var bk, mbk string
+ if h.background != "" {
+ bk = `background: url('` + h.background + `') no-repeat center center fixed;`
+ }
+ if h.mobildBackground != "" {
+ mbk = `background: url('` + h.mobildBackground + `') no-repeat center center fixed;`
+ }
+ if h.mobildBackground == "" && h.background != "" {
+ mbk = bk
+ }
+ err = tmpt.Execute(w, map[string]interface{}{
+ "IdxTitle": r.URL.Path,
+ "Version": version,
+ "Idx": "Index of " + r.URL.Path,
+ "Upload": template.HTML(upload),
+ "Data": template.JS(jData),
+ "Photo": template.CSS(bk),
+ "MobilePhoto": template.CSS(mbk),
+ })
+ if err != nil {
+ log.Errorf("Template Execute Failed:%v\n", err)
+ w.WriteHeader(502)
+ w.Write([]byte(`B612 Http Server502 SERVER ERROR
`))
+ return err
+ }
return nil
}
+func (h *HttpServer) getSleepTime() time.Duration {
+ if h.speedlimit == 0 {
+ return 0
+ }
+ return time.Nanosecond * time.Duration(16384*1000*1000*1000/h.speedlimit) / 2
+
+}
+
func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if !staros.Exists(fullpath) {
h.Page404(w)
return errors.New("File Not Found! 404 ERROR")
}
+ var lastCount int64
+ var lastDate time.Time = time.Now()
+ var currentCount int64
+ speedControl := func(count int) {
+ if h.speedlimit == 0 {
+ return
+ }
+ currentCount += int64(count)
+ for {
+ if time.Since(lastDate) < time.Second {
+ if uint64(currentCount-lastCount) > h.speedlimit {
+ time.Sleep(h.getSleepTime())
+ } else {
+ break
+ }
+ } else {
+ lastDate = time.Now()
+ lastCount = currentCount
+ break
+ }
+ }
+ }
//starlog.Debugln(r.Header)
startRange, endRange := h.CalcRange(r)
fp, err := os.Open(fullpath)
@@ -756,10 +701,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
if !needCurl {
w.WriteHeader(200)
for {
- buf := make([]byte, 1048576)
+ buf := make([]byte, 16384)
n, err := fp.Read(buf)
if n != 0 {
- ns, err := w.Write(buf[0:n])
+ speedControl(n)
+ ns, err := w.Write(buf[:n])
transferData += ns
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
@@ -770,7 +716,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
if err == io.EOF {
break
}
- log.Errorln("Read File %s Failed:%v\n", fullpath, err)
+ log.Errorf("Read File %s Failed:%v\n", fullpath, err)
return err
}
}
@@ -784,13 +730,14 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
return err
}
b64 := base64.StdEncoding.EncodeToString(data)
- req, err := starnet.Curl(starnet.NewRequests(hook.Url, starnet.BuildPostForm(map[string]string{
- "data": b64,
- "ip": r.RemoteAddr,
- }),
+ req, err := starnet.Curl(starnet.NewSimpleRequest(hook.Url,
"POST",
+ starnet.WithBytes(starnet.BuildPostForm(map[string]string{
+ "data": b64,
+ "ip": r.RemoteAddr,
+ })),
starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond)))
- if err != nil || len(req.RecvData) == 0 {
+ if err != nil {
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.WriteHeader(200)
ns, err := w.Write(data)
@@ -801,9 +748,10 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
}
return nil
}
+ recvData := req.Body().Bytes()
w.WriteHeader(200)
- w.Header().Set("Content-Length", strconv.Itoa(len(req.RecvData)))
- ns, err := w.Write(req.RecvData)
+ w.Header().Set("Content-Length", strconv.Itoa(len(recvData)))
+ ns, err := w.Write(recvData)
transferData += ns
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
@@ -816,7 +764,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
fp.Seek(int64(startRange), 0)
count := startRange
for {
- buf := make([]byte, 1048576)
+ buf := make([]byte, 16384)
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
@@ -825,8 +773,9 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
log.Errorf("Read File %s Failed:%v\n", r.URL.Path, err)
return err
}
+ speedControl(n)
if endRange == -1 {
- ns, err := w.Write(buf[0:n])
+ ns, err := w.Write(buf[:n])
transferData += ns
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", r.URL.Path, err)
@@ -841,11 +790,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
writeNum = int(endRange - count + 1)
}
ns, err := w.Write(buf[0:writeNum])
- transferData += ns
if err != nil {
log.Errorln("Transfer Error:", err)
return err
}
+ transferData += ns
count += int64(n)
}
}
diff --git a/httpserver/template.html b/httpserver/template.html
new file mode 100644
index 0000000..f93be50
--- /dev/null
+++ b/httpserver/template.html
@@ -0,0 +1,478 @@
+
+
+
+
+ B612 Http Server {{ .IdxTitle }}
+
+
+
+
+
+
B612 Http Server - {{ .Version }}
+
+
{{ .Idx }}
+ {{ .Upload }}
+
+
+
+
+
+
+
+ Name |
+ Modified |
+ Size |
+ Type |
+
+
+
+
+
+
+
+
B612.Me © Apache 2.0 License
+
+
+
+
+
+
+
+
diff --git a/keygen/cmd.go b/keygen/cmd.go
index 503fd66..aca9ed8 100644
--- a/keygen/cmd.go
+++ b/keygen/cmd.go
@@ -5,6 +5,7 @@ import (
"b612.me/starlog"
"b612.me/staros"
"crypto/ecdsa"
+ "crypto/ed25519"
"crypto/rsa"
"github.com/spf13/cobra"
"os"
@@ -138,6 +139,12 @@ var CmdPub = &cobra.Command{
case *ecdsa.PrivateKey:
starlog.Infoln("found ecdsa private key")
pub = n.Public()
+ case ed25519.PrivateKey:
+ starlog.Infoln("found ed25519 private key")
+ pub = n.Public()
+ case *ed25519.PrivateKey:
+ starlog.Infoln("found ed25519 private key")
+ pub = n.Public()
default:
starlog.Errorln("unknown private key type")
os.Exit(1)
diff --git a/main.go b/main.go
index 79b9ca2..c984379 100644
--- a/main.go
+++ b/main.go
@@ -21,6 +21,7 @@ import (
"b612.me/apps/b612/image"
"b612.me/apps/b612/keygen"
"b612.me/apps/b612/merge"
+ "b612.me/apps/b612/mget"
"b612.me/apps/b612/net"
"b612.me/apps/b612/rmt"
"b612.me/apps/b612/search"
@@ -41,7 +42,7 @@ import (
var cmdRoot = &cobra.Command{
Use: "b612",
- Version: "2.1.0.beta.10",
+ Version: "2.1.0.beta.11",
}
func init() {
@@ -50,7 +51,7 @@ func init() {
base64.Cmd, base85.Cmd, base91.Cmd, attach.Cmd, detach.Cmd, df.Cmd, dfinder.Cmd,
ftp.Cmd, generate.Cmd, hash.Cmd, image.Cmd, merge.Cmd, search.Cmd, split.Cmd, vic.Cmd,
calc.Cmd, net.Cmd, rmt.Cmds, rmt.Cmdc, keygen.Cmd, dns.Cmd, whois.Cmd, socks5.Cmd, httproxy.Cmd, smtpserver.Cmd, smtpclient.Cmd,
- cert.Cmd, aes.Cmd, tls.Cmd)
+ cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd)
}
func main() {
diff --git a/mget/cmd.go b/mget/cmd.go
new file mode 100644
index 0000000..6e2288c
--- /dev/null
+++ b/mget/cmd.go
@@ -0,0 +1,130 @@
+package mget
+
+import (
+ "b612.me/stario"
+ "b612.me/starlog"
+ "fmt"
+ "github.com/spf13/cobra"
+ "os"
+ "os/signal"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var mg Mget
+
+var Cmd = &cobra.Command{
+ Use: "mget",
+ Short: "多线程下载工具",
+ Long: `多线程下载工具`,
+ Run: Run,
+}
+
+var headers []string
+var ua string
+var proxy string
+var skipVerify bool
+var speedcontrol string
+
+func init() {
+ Cmd.Flags().StringVarP(&mg.Tareget, "output", "o", "", "输出文件名")
+ Cmd.Flags().IntVarP(&mg.BufferSize, "buffer", "b", 8192, "缓冲区大小")
+ Cmd.Flags().IntVarP(&mg.Thread, "thread", "t", 8, "线程数")
+ Cmd.Flags().IntVarP(&mg.RedoRPO, "safe", "s", 1048576, "安全校验点")
+ Cmd.Flags().StringSliceVarP(&headers, "header", "H", []string{}, "自定义请求头,格式: key=value")
+ Cmd.Flags().StringVarP(&proxy, "proxy", "p", "", "代理地址")
+ Cmd.Flags().StringVarP(&ua, "user-agent", "U", "", "自定义User-Agent")
+ Cmd.Flags().BoolVarP(&skipVerify, "skip-verify", "k", false, "跳过SSL验证")
+ Cmd.Flags().StringVarP(&speedcontrol, "speed", "S", "", "限速,如1M,意思是1MB/s")
+}
+
+func parseSpeedString(speedString string) (uint64, error) {
+ // 定义单位及其对应的字节值
+ unitMultipliers := map[string]int{
+ "b": 1, "": 1,
+ "k": 1024, "kb": 1024, "kib": 1024,
+ "m": 1024 * 1024, "mb": 1024 * 1024, "mib": 1024 * 1024,
+ "g": 1024 * 1024 * 1024, "gb": 1024 * 1024 * 1024, "gib": 1024 * 1024 * 1024,
+ "t": 1024 * 1024 * 1024 * 1024, "tb": 1024 * 1024 * 1024 * 1024, "tib": 1024 * 1024 * 1024 * 1024,
+ }
+
+ // 正则表达式匹配速度的格式
+ re := regexp.MustCompile(`(?i)^\s*([\d.]+)\s*(b|k|m|g|t|kb|mb|gb|tb|kib|mib|gib|tib)?\s*/?\s*s?\s*$`)
+ matches := re.FindStringSubmatch(strings.ToLower(speedString))
+ if matches == nil {
+ return 0, fmt.Errorf("invalid speed string format")
+ }
+
+ // 解析数值部分
+ value, err := strconv.ParseFloat(matches[1], 64)
+ if err != nil {
+ return 0, fmt.Errorf("invalid numeric value")
+ }
+
+ // 获取单位部分
+ unit := matches[2]
+ if unit == "" {
+ unit = "b"
+ }
+
+ // 根据单位计算最终的字节每秒值
+ multiplier, ok := unitMultipliers[unit]
+ if !ok {
+ return 0, fmt.Errorf("invalid unit in speed string")
+ }
+
+ return uint64(value * float64(multiplier)), nil
+}
+
+func Run(cmd *cobra.Command, args []string) {
+ if args == nil || len(args) == 0 {
+ starlog.Errorln("缺少URL参数")
+ os.Exit(1)
+ }
+ if speedcontrol != "" {
+ speed, err := parseSpeedString(speedcontrol)
+ if err != nil {
+ starlog.Criticalln("Speed Limit Error:", err)
+ os.Exit(1)
+ }
+ mg.speedlimit = int64(speed)
+ fmt.Printf("Max Speed Limit:(user in):\t%v\n", speedcontrol)
+ fmt.Printf("Max Speed Limit (bytes/s):\t%v bytes/sec\n", speed)
+ }
+ for _, v := range headers {
+ kv := strings.SplitN(v, "=", 2)
+ if len(kv) != 2 {
+ continue
+ }
+ mg.Setting.AddHeader(strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]))
+ }
+ if ua != "" {
+ mg.Setting.SetUserAgent(ua)
+ }
+ if proxy != "" {
+ mg.Setting.SetProxy(proxy)
+ }
+ if skipVerify {
+ mg.Setting.SetSkipTLSVerify(true)
+ }
+ mg.OriginUri = args[0]
+ sig := make(chan os.Signal)
+ signal.Notify(sig, os.Interrupt)
+ select {
+ case err := <-stario.WaitUntilFinished(mg.Run):
+ if err != nil {
+ starlog.Errorln(err)
+ os.Exit(2)
+ }
+ time.Sleep(time.Second)
+ return
+ case <-sig:
+ starlog.Infoln("User Interrupted")
+ mg.fn()
+ time.Sleep(time.Second)
+ mg.Redo.Save()
+ os.Exit(3)
+ }
+}
diff --git a/mget/process.go b/mget/process.go
new file mode 100644
index 0000000..0e19c4c
--- /dev/null
+++ b/mget/process.go
@@ -0,0 +1,99 @@
+package mget
+
+import (
+ "fmt"
+ "github.com/vbauerster/mpb/v8"
+ "github.com/vbauerster/mpb/v8/decor"
+ "io"
+ "strings"
+ "time"
+)
+
+func (m *Mget) processMiddleware(base mpb.BarFiller) mpb.BarFiller {
+ fn := func(w io.Writer, st decor.Statistics) error {
+ var res string
+ count := 0
+ _, err := fmt.Fprintf(w, "\nFinished:%s Total Write:%d Speed:%v\n\n", m.Redo.FormatPercent(), m.Redo.Total(), m.Redo.FormatSpeed("MB"))
+ for k := range m.threads {
+ v := m.threads[len(m.threads)-1-k]
+ if v != nil {
+ count++
+ res = fmt.Sprintf("Thread %v: %s %s\t", len(m.threads)-k, v.FormatSpeed("MB"), v.FormatPercent()) + res
+ if count%3 == 0 {
+ res = strings.TrimRight(res, "\t")
+ fmt.Fprintf(w, "%s\n", res)
+ res = ""
+ }
+ }
+ }
+ if res != "" {
+ res = strings.TrimRight(res, "\t")
+ fmt.Fprintf(w, "%s\n", res)
+ }
+ return err
+ }
+ if base == nil {
+ return mpb.BarFillerFunc(fn)
+ }
+ return mpb.BarFillerFunc(func(w io.Writer, st decor.Statistics) error {
+ err := fn(w, st)
+ if err != nil {
+ return err
+ }
+ return base.Fill(w, st)
+ })
+}
+
+func (w *Mget) Process() {
+ w.processEnable = true
+ defer func() {
+ w.processEnable = false
+ }()
+ fmt.Println()
+ p := mpb.New()
+ var filler mpb.BarFiller
+ filler = w.processMiddleware(filler)
+ bar := p.New(int64(w.ContentLength),
+ mpb.BarStyle().Rbound("|"),
+ mpb.BarExtender(filler, true), // all bars share same extender filler
+ mpb.PrependDecorators(
+ decor.Counters(decor.SizeB1024(0), "% .2f / % .2f"),
+ ),
+ mpb.AppendDecorators(
+ decor.EwmaETA(decor.ET_STYLE_GO, 30),
+ decor.Name(" ] "),
+ decor.EwmaSpeed(decor.SizeB1024(0), "% .2f ", 30),
+ ),
+ )
+ defer p.Wait()
+ for {
+ last := w.Redo.Total()
+ lastTime := time.Now()
+ bar.SetCurrent(int64(w.Redo.Total()))
+ select {
+ case <-w.ctx.Done():
+ bar.SetCurrent(int64(w.Redo.Total()))
+ if w.dynLength {
+ bar.SetTotal(int64(w.Redo.ContentLength), true)
+ }
+ bar.Abort(false)
+ return
+ case <-time.After(time.Second):
+ if !w.writeEnable {
+ bar.SetCurrent(int64(w.Redo.Total()))
+ if w.dynLength {
+ bar.SetTotal(int64(w.Redo.ContentLength), true)
+ }
+ bar.Abort(true)
+ return
+ }
+ now := w.Redo.Total()
+ bar.EwmaIncrInt64(int64(now-last), time.Since(lastTime))
+ lastTime = time.Now()
+ last = now
+ if w.dynLength {
+ bar.SetTotal(int64(w.Redo.ContentLength), false)
+ }
+ }
+ }
+}
diff --git a/mget/range.go b/mget/range.go
new file mode 100644
index 0000000..98f09f8
--- /dev/null
+++ b/mget/range.go
@@ -0,0 +1,84 @@
+package mget
+
+import "sort"
+
+type Range struct {
+ Min uint64 `json:"min"`
+ Max uint64 `json:"max"`
+}
+type SortRange []Range
+
+func (s SortRange) Len() int { return len(s) }
+func (s SortRange) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s SortRange) Less(i, j int) bool { return s[i].Min < s[j].Min }
+
+func uniformRange(rg []Range) ([]Range, error) {
+ newRg := make([]Range, 0, len(rg))
+ sort.Sort(SortRange(rg))
+ var last *Range = nil
+ for _, v := range rg {
+ if last != nil && v.Min <= last.Max+1 {
+ if last.Max <= v.Max {
+ last.Max = v.Max
+ }
+ continue
+ }
+ newRg = append(newRg, v)
+ last = &newRg[len(newRg)-1]
+ }
+ return newRg, nil
+}
+
+func singleSubRange(origin []Range, v Range) []Range {
+ newRg := make([]Range, 0)
+ sort.Sort(SortRange(origin))
+ for i := 0; i < len(origin); i++ {
+ ori := origin[i]
+ res := make([]Range, 0)
+ shouldAdd := true
+ for j := 0; j < 1; j++ {
+ if v.Min <= ori.Min && v.Max >= ori.Max {
+ shouldAdd = false
+ break
+ }
+ if v.Max < ori.Min {
+ continue
+ }
+ if v.Min > ori.Max {
+ break
+ }
+ ur1 := Range{
+ Min: ori.Min,
+ Max: v.Min - 1,
+ }
+ if v.Min == 0 {
+ ur1.Min = 1
+ ur1.Max = 0
+ }
+ ur2 := Range{
+ Min: v.Max + 1,
+ Max: ori.Max,
+ }
+ if ur1.Max >= ur1.Min {
+ res = append(res, ur1)
+ }
+ if ur2.Max >= ur2.Min {
+ res = append(res, ur2)
+ }
+ }
+ if len(res) == 0 && shouldAdd {
+ res = append(res, ori)
+ }
+ newRg = append(newRg, res...)
+ }
+ return newRg
+}
+
+func subRange(origin, rg []Range) []Range {
+ sort.Sort(SortRange(rg))
+ sort.Sort(SortRange(origin))
+ for _, v := range rg {
+ origin = singleSubRange(origin, v)
+ }
+ return origin
+}
diff --git a/mget/range_test.go b/mget/range_test.go
new file mode 100644
index 0000000..81448b6
--- /dev/null
+++ b/mget/range_test.go
@@ -0,0 +1,30 @@
+package mget
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+)
+
+func TestRangePlus(t *testing.T) {
+ var r = Redo{
+ ContentLength: 100,
+ rangeUpdated: true,
+ Range: []Range{
+ {10, 12},
+ {13, 20},
+ {17, 19},
+ {30, 80},
+ {90, 97},
+ },
+ }
+ err := r.reform()
+ if err != nil {
+ t.Error(err)
+ }
+ if !reflect.DeepEqual(r.Range, []Range{{10, 20}, {30, 80}, {90, 97}}) {
+ t.Error("reform error")
+ }
+ fmt.Println(r.Range)
+ fmt.Println(r.ReverseRange())
+}
diff --git a/mget/redo.go b/mget/redo.go
new file mode 100644
index 0000000..dc60c92
--- /dev/null
+++ b/mget/redo.go
@@ -0,0 +1,138 @@
+package mget
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "strings"
+ "sync"
+ "time"
+)
+
+type Redo struct {
+ Is206 bool `json:"is_206"`
+ OriginUri string `json:"origin_uri"`
+ Date time.Time `json:"date"`
+ Filename string `json:"filename"`
+ ContentLength uint64 `json:"content_length"`
+ Range []Range `json:"range"`
+ rangeUpdated bool
+ lastUpdate time.Time
+ lastTotal uint64
+ speed float64
+ total uint64
+ isRedo bool
+ sync.RWMutex
+}
+
+func (r *Redo) CacheTotal() uint64 {
+ return r.total
+}
+
+func (r *Redo) Total() uint64 {
+ var total uint64
+ for {
+ r.RLock()
+ for _, v := range r.Range {
+ total += v.Max - v.Min + 1
+ }
+ r.total = total
+ r.RUnlock()
+ if r.total > r.ContentLength && r.ContentLength > 0 {
+ r.reform()
+ continue
+ }
+ break
+ }
+ return total
+}
+
+func (r *Redo) Update(start, end int) error {
+ if start < 0 || end < 0 || start > end {
+ return fmt.Errorf("invalid range: %d-%d", start, end)
+ }
+ r.Lock()
+ defer r.Unlock()
+ r.rangeUpdated = true
+ r.Range = append(r.Range, Range{uint64(start), uint64(end)})
+ now := time.Now()
+ if now.Sub(r.lastUpdate) >= time.Millisecond*500 {
+ var total uint64
+ for _, v := range r.Range {
+ total += v.Max - v.Min + 1
+ }
+ r.total = total
+ r.speed = float64(total-r.lastTotal) / (float64(now.Sub(r.lastUpdate).Milliseconds()) / 1000.00)
+ r.lastTotal = total
+ r.lastUpdate = now
+ }
+ return nil
+}
+
+func (r *Redo) Percent() float64 {
+ return float64(r.Total()) / float64(r.ContentLength)
+}
+
+func (r *Redo) FormatPercent() string {
+ return fmt.Sprintf("%.2f%%", r.Percent()*100)
+}
+
+func (r *Redo) FormatSpeed(unit string) string {
+ switch strings.ToLower(unit) {
+ case "kb":
+ return fmt.Sprintf("%.2f KB/s", r.speed/1024)
+ case "mb":
+ return fmt.Sprintf("%.2f MB/s", r.speed/1024/1024)
+ case "gb":
+ return fmt.Sprintf("%.2f GB/s", r.speed/1024/1024/1024)
+ default:
+ return fmt.Sprintf("%.2f B/s", r.speed)
+ }
+}
+
+func (r *Redo) Speed() float64 {
+ return r.speed
+}
+
+func (r *Redo) Save() error {
+ var err error
+ err = r.reform()
+ if err != nil {
+ return err
+ }
+ if r.Filename != "" {
+ data, err := json.Marshal(r)
+ if err != nil {
+ return err
+ }
+ r.Lock()
+ defer r.Unlock()
+ return os.WriteFile(r.Filename+".bgrd", data, 0644)
+ }
+ return nil
+}
+
+func (r *Redo) reform() error {
+ r.Lock()
+ defer r.Unlock()
+ if !r.rangeUpdated {
+ return nil
+ }
+ tmp, err := r.uniformRange(r.Range)
+ if err != nil {
+ return err
+ }
+ r.Range = tmp
+ return nil
+}
+
+func (r *Redo) uniformRange(rg []Range) ([]Range, error) {
+ return uniformRange(rg)
+}
+
+func (r *Redo) ReverseRange() ([]Range, error) {
+ r.reform()
+ r.RLock()
+ defer r.RUnlock()
+ return r.uniformRange(subRange([]Range{{0, r.ContentLength - 1}}, r.Range))
+}
diff --git a/mget/util.go b/mget/util.go
new file mode 100644
index 0000000..dd378bf
--- /dev/null
+++ b/mget/util.go
@@ -0,0 +1,141 @@
+package mget
+
+import (
+ "b612.me/staros"
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path"
+ "regexp"
+ "runtime"
+ "sync/atomic"
+ "time"
+)
+
+func parseContentRange(contentRange string) (start, end, total int64, err error) {
+ _, err = fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &total)
+ return
+}
+
+func GetFileName(resp *http.Response) string {
+ fname := getFileName(resp)
+ var idx = 0
+ for {
+ idx++
+ if staros.Exists(fname) {
+ if staros.Exists(fname + ".bgrd") {
+ return fname
+ }
+ fname = fmt.Sprintf("%s.%d", fname, idx)
+ } else {
+ break
+ }
+ }
+ return fname
+}
+
+func getFileName(resp *http.Response) string {
+ // 尝试从Content-Disposition头中提取文件名
+ contentDisposition := resp.Header.Get("Content-Disposition")
+ if contentDisposition != "" {
+ // 使用正则表达式提取文件名
+ re := regexp.MustCompile(`(?i)^attachment; filename="?(?P[^;"]+)`)
+ matches := re.FindStringSubmatch(contentDisposition)
+ if len(matches) > 1 {
+ // 提取命名的捕获组
+ for i, name := range re.SubexpNames() {
+ if name == "filename" {
+ return matches[i]
+ }
+ }
+ }
+ }
+ // 提取路径中的最后一个元素作为文件名
+ return path.Base(resp.Request.URL.Path)
+}
+
+func IOWriter(stopCtx context.Context, ch chan Buffer, state *uint32, di *downloadinfo, reader io.ReadCloser, bufSize int, start *int64, end *int64) error {
+ defer reader.Close()
+ for {
+ buf := make([]byte, bufSize)
+ select {
+ case <-stopCtx.Done():
+ return nil
+ default:
+ if atomic.LoadUint32(state) == 1 {
+ runtime.Gosched()
+ time.Sleep(time.Millisecond)
+ continue
+ }
+ n, err := reader.Read(buf)
+ if n > 0 {
+ ch <- Buffer{Data: buf[:n], Start: uint64(*start)}
+ *start += int64(n)
+ di.AddCurrent(int64(n))
+ }
+ if *start >= *end {
+ return nil
+ }
+ if err != nil {
+ if err == io.EOF {
+ return nil
+ }
+ return err
+ }
+ }
+ }
+}
+
+func createFileWithSize(filename string, size int64) (*os.File, error) {
+ file, err := os.Create(filename)
+ if err != nil {
+ return nil, err
+ }
+ if size == 0 {
+ return file, nil
+ }
+ // 调整文件指针到指定大小位置
+ if _, err = file.Seek(size-1, 0); err != nil {
+ return nil, err
+ }
+
+ // 写入一个空字节,以确保文件达到所需大小
+ if _, err = file.Write([]byte{0}); err != nil {
+ return nil, err
+ }
+
+ return file, nil
+}
+
+func CloneHeader(original http.Header) http.Header {
+ newHeader := make(http.Header)
+ for key, values := range original {
+ copiedValues := make([]string, len(values))
+ copy(copiedValues, values)
+ newHeader[key] = copiedValues
+ }
+ return newHeader
+}
+
+func CloneCookies(original []*http.Cookie) []*http.Cookie {
+ cloned := make([]*http.Cookie, len(original))
+ for i, cookie := range original {
+ cloned[i] = &http.Cookie{
+ Name: cookie.Name,
+ Value: cookie.Value,
+ Path: cookie.Path,
+ Domain: cookie.Domain,
+ Expires: cookie.Expires,
+ RawExpires: cookie.RawExpires,
+ MaxAge: cookie.MaxAge,
+ Secure: cookie.Secure,
+ HttpOnly: cookie.HttpOnly,
+ SameSite: cookie.SameSite,
+ Raw: cookie.Raw,
+ Unparsed: append([]string(nil), cookie.Unparsed...),
+ }
+ }
+ return cloned
+}
diff --git a/mget/wget.go b/mget/wget.go
new file mode 100644
index 0000000..3f12c12
--- /dev/null
+++ b/mget/wget.go
@@ -0,0 +1,526 @@
+package mget
+
+import (
+ "b612.me/stario"
+ "b612.me/starnet"
+ "b612.me/staros"
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+type Mget struct {
+ Setting starnet.Request
+ Redo
+ //本地文件地址
+ Tareget string
+ //本地文件大小
+ TargetSize int64
+ //redo文件最大丢数据量
+ RedoRPO int
+ //单个buffer大小
+ BufferSize int
+ //并发下载线程数
+ dynLength bool
+ Thread int `json:"thread"`
+ tf *os.File
+ ch chan Buffer
+ ctx context.Context
+ fn context.CancelFunc
+ wg sync.WaitGroup
+ threads []*downloader
+ lastUndoInfo []Range
+ writeError error
+ writeEnable bool
+ processEnable bool
+ speedlimit int64
+}
+
+type Buffer struct {
+ Data []byte
+ Start uint64
+}
+
+func (w *Mget) Clone() *starnet.Request {
+ req := starnet.NewSimpleRequest(w.Setting.Uri(), w.Setting.Method())
+ req.SetHeaders(CloneHeader(w.Setting.Headers()))
+ req.SetCookies(CloneCookies(w.Setting.Cookies()))
+ req.SetSkipTLSVerify(w.Setting.SkipTLSVerify())
+ req.SetProxy(w.Setting.Proxy())
+ return req
+}
+
+func (w *Mget) IsUrl206() (*starnet.Response, bool, error) {
+ req := w.Clone()
+ req.SetHeader("Range", "bytes=0-")
+ res, err := req.Do()
+ if err != nil {
+ return nil, false, err
+ }
+ if res.StatusCode == 206 {
+ return res, true, nil
+ }
+ return res, false, nil
+}
+
+func (w *Mget) prepareRun(res *starnet.Response, is206 bool) error {
+ var err error
+
+ length := res.Header.Get("Content-Length")
+ if length == "" {
+ length = "0"
+ w.dynLength = true
+ is206 = false
+ }
+ w.TargetSize, err = strconv.ParseInt(length, 10, 64)
+ if err != nil {
+ return fmt.Errorf("parse content length error: %w", err)
+ }
+ if w.Tareget == "" {
+ w.Tareget = GetFileName(res.Response)
+ }
+ fmt.Println("Will write to:", w.Tareget)
+ fmt.Println("Size:", w.TargetSize)
+ fmt.Println("Is206:", is206)
+ w.Redo = Redo{
+ Filename: w.Tareget,
+ ContentLength: uint64(w.TargetSize),
+ OriginUri: w.Setting.Uri(),
+ Date: time.Now(),
+ Is206: is206,
+ }
+ fmt.Println("Threads:", w.Thread)
+ if staros.Exists(w.Tareget + ".bgrd") {
+ fmt.Println("Found redo file, try to recover...")
+ var redo Redo
+ data, err := os.ReadFile(w.Tareget + ".bgrd")
+ if err != nil {
+ return fmt.Errorf("read redo file error: %w", err)
+ }
+ err = json.Unmarshal(data, &redo)
+ if err != nil {
+ return fmt.Errorf("unmarshal redo file error: %w", err)
+ }
+ redo.reform()
+ if redo.ContentLength != w.Redo.ContentLength {
+ fmt.Println("Content length not match, redo file may be invalid, ignore it")
+ return nil
+ }
+ if redo.OriginUri != w.Redo.OriginUri {
+ fmt.Println("Origin uri not match, redo file may be invalid, ignore it")
+ return nil
+ }
+ w.Redo = redo
+ w.Redo.isRedo = true
+ w.lastUndoInfo, err = w.Redo.ReverseRange()
+ if err != nil {
+ return fmt.Errorf("reverse redo range error: %w", err)
+ }
+ fmt.Println("Recover redo file success,process:", w.Redo.FormatPercent())
+ }
+ return nil
+}
+
+func (w *Mget) Run() error {
+ var err error
+ var res *starnet.Response
+ var is206 bool
+ w.ctx, w.fn = context.WithCancel(context.Background())
+ w.ch = make(chan Buffer)
+ defer w.fn()
+ w.threads = make([]*downloader, w.Thread)
+ if w.Setting.Uri() == "" {
+ w.Setting = *starnet.NewSimpleRequest(w.OriginUri, "GET")
+ }
+ for {
+ res, is206, err = w.IsUrl206()
+ if err != nil {
+ return fmt.Errorf("check 206 error: %w", err)
+ }
+ err = w.prepareRun(res, is206)
+ if err != nil {
+ return fmt.Errorf("prepare run error: %w", err)
+ }
+ if res.StatusCode != 206 && res.StatusCode != 200 {
+ return fmt.Errorf("Server return %d", res.StatusCode)
+ }
+ if !is206 {
+ var di = &downloader{
+ alive: true,
+ downloadinfo: &downloadinfo{
+ Start: 0,
+ End: w.TargetSize - 1,
+ Size: w.TargetSize,
+ },
+ }
+ w.threads[0] = di
+ state := uint32(0)
+ err = IOWriter(w.ctx, w.ch, &state, di.downloadinfo, res.Body().Reader(), w.BufferSize, &di.Start, &di.End)
+ di.alive = false
+ if err == nil {
+ return nil
+ }
+ continue
+ } else {
+ res.Body().Close()
+ }
+ break
+ }
+ go func() {
+ w.writeEnable = true
+ w.writeError = w.WriteServer()
+ w.writeEnable = false
+ }()
+ if w.TargetSize == 0 {
+ return nil
+ }
+ for i := 0; i < w.Thread; i++ {
+ w.wg.Add(1)
+ go w.dispatch(i)
+ }
+ go w.Process()
+ w.wg.Wait()
+ time.Sleep(2 * time.Microsecond)
+ for {
+ if w.writeEnable {
+ w.fn()
+ time.Sleep(time.Millisecond * 50)
+ continue
+ }
+ if w.writeError != nil {
+ err = w.Redo.Save()
+ return fmt.Errorf("write error: %w %v", w.writeError, err)
+ }
+ break
+ }
+ w.fn()
+ stario.WaitUntilTimeout(time.Second*2,
+ func(c chan struct{}) error {
+ for {
+ if w.processEnable {
+ time.Sleep(time.Millisecond * 50)
+ continue
+ }
+ return nil
+ }
+ })
+
+ r, err := w.ReverseRange()
+ if err != nil {
+ return err
+ }
+ if len(r) == 0 {
+ return os.Remove(w.Tareget + ".bgrd")
+ }
+ return w.Redo.Save()
+}
+
+func (w *Mget) dispatch(idx int) error {
+ defer w.wg.Done()
+ var start, end int64
+ if len(w.lastUndoInfo) == 0 {
+ count := w.TargetSize / int64(w.Thread)
+ start = count * int64(idx)
+ end = count*int64(idx+1) - 1
+ if idx == w.Thread-1 {
+ end = w.TargetSize - 1
+ }
+ } else {
+ w.Lock()
+ if len(w.lastUndoInfo) == 0 {
+ d := &downloader{}
+ w.threads[idx] = d
+ w.Unlock()
+ goto morejob
+ }
+ start = int64(w.lastUndoInfo[0].Min)
+ end = int64(w.lastUndoInfo[0].Max)
+ w.lastUndoInfo = w.lastUndoInfo[1:]
+ w.Unlock()
+ }
+ for {
+ req := w.Clone()
+ req.SetCookies(CloneCookies(w.Setting.Cookies()))
+ d := &downloader{
+ Request: req,
+ ch: w.ch,
+ ctx: w.ctx,
+ bufferSize: w.BufferSize,
+ downloadinfo: &downloadinfo{
+ Start: start,
+ End: end,
+ },
+ }
+ w.threads[idx] = d
+ if err := d.Run(); err != nil {
+ fmt.Printf("thread %d error: %v\n", idx, err)
+ if d.Start >= d.End {
+ break
+ }
+ start = d.Start
+ end = d.End
+ continue
+ }
+ break
+ }
+morejob:
+ for {
+ w.Lock()
+ if len(w.lastUndoInfo) > 0 {
+ w.threads[idx].Start = int64(w.lastUndoInfo[idx].Min)
+ w.threads[idx].End = int64(w.lastUndoInfo[idx].Max)
+ w.lastUndoInfo = w.lastUndoInfo[1:]
+ w.Unlock()
+ } else {
+ w.Unlock()
+ if !w.RequestNewTask(w.threads[idx]) {
+ break
+ }
+ }
+ for {
+ req := w.Clone()
+ req.SetCookies(CloneCookies(w.Setting.Cookies()))
+ d := &downloader{
+ Request: req,
+ ch: w.ch,
+ ctx: w.ctx,
+ bufferSize: w.BufferSize,
+ downloadinfo: &downloadinfo{
+ Start: w.threads[idx].Start,
+ End: w.threads[idx].End,
+ },
+ }
+ w.threads[idx] = d
+ if err := d.Run(); err != nil {
+ fmt.Printf("thread %d error: %v\n", idx, err)
+ if d.Start >= d.End {
+ break
+ }
+ start = d.Start
+ end = d.End
+ continue
+ }
+ break
+ }
+ }
+ return nil
+}
+
+func (w *Mget) getSleepTime() time.Duration {
+ if w.speedlimit == 0 {
+ return 0
+ }
+ return time.Nanosecond * time.Duration(16384*1000*1000*1000/w.speedlimit) / 2
+
+}
+func (w *Mget) WriteServer() error {
+ var err error
+ defer w.fn()
+ if !w.isRedo {
+ w.tf, err = createFileWithSize(w.Tareget, w.TargetSize)
+ } else {
+ w.tf, err = os.OpenFile(w.Tareget, os.O_RDWR, 0666)
+ }
+ if err != nil {
+ return err
+ }
+
+ lastUpdateRange := 0
+ currentRange := 0
+
+ currentCount := int64(0)
+ lastDate := time.Now()
+ lastCount := int64(0)
+ speedControl := func(count int) {
+ if w.speedlimit == 0 {
+ return
+ }
+ currentCount += int64(count)
+ for {
+ if time.Since(lastDate) < time.Second {
+ if currentCount-lastCount > w.speedlimit {
+ time.Sleep(w.getSleepTime())
+ } else {
+ break
+ }
+ } else {
+ lastDate = time.Now()
+ lastCount = currentCount
+ break
+ }
+ }
+ }
+ for {
+ select {
+ case <-w.ctx.Done():
+ return nil
+ case b := <-w.ch:
+ n, err := w.tf.WriteAt(b.Data, int64(b.Start))
+ if err != nil {
+ fmt.Println("write error:", err)
+ return err
+ }
+ speedControl(n)
+ if w.dynLength {
+ w.ContentLength += uint64(n)
+ }
+ currentRange += n
+ end := b.Start + uint64(n) - 1
+ err = w.Update(int(b.Start), int(end))
+ if err != nil {
+ return err
+ }
+ if currentRange-lastUpdateRange >= w.RedoRPO {
+ w.tf.Sync()
+ go w.Redo.Save()
+ lastUpdateRange = currentRange
+ }
+ }
+ }
+}
+
+type downloader struct {
+ *starnet.Request
+ alive bool
+ ch chan Buffer
+ ctx context.Context
+ state uint32
+ bufferSize int
+ *downloadinfo
+}
+
+func (d *downloader) Run() error {
+ d.alive = true
+ defer func() {
+ d.alive = false
+ }()
+ d.SetHeader("Range", fmt.Sprintf("bytes=%d-%d", d.Start, d.End))
+ res, err := d.Do()
+ if err != nil {
+ return err
+ }
+ if res.Header.Get("Content-Range") == "" {
+ return fmt.Errorf("server not support range")
+ }
+ start, end, _, err := parseContentRange(res.Header.Get("Content-Range"))
+ if d.Start != start {
+ return fmt.Errorf("server not support range")
+ }
+ d.End = end
+ d.downloadinfo = &downloadinfo{
+ Start: d.Start,
+ End: d.End,
+ Size: d.End - d.Start + 1,
+ }
+ reader := res.Body().Reader()
+ return IOWriter(d.ctx, d.ch, &d.state, d.downloadinfo, reader, d.bufferSize, &d.Start, &d.End)
+}
+
+func (w *Mget) RequestNewTask(task *downloader) bool {
+ //stop thhe world first
+ w.Lock()
+ defer w.Unlock()
+ defer func() {
+ for _, v := range w.threads {
+ if v != nil {
+ atomic.StoreUint32(&v.state, 0)
+ }
+ }
+ }()
+ var maxThread *downloader
+ for _, v := range w.threads {
+ if v != nil {
+ atomic.StoreUint32(&v.state, 1)
+ }
+ }
+ time.Sleep(time.Microsecond * 2)
+
+ for _, v := range w.threads {
+ if v == nil {
+ continue
+ }
+ if maxThread == nil {
+ maxThread = v
+ continue
+ }
+ if v.End-v.Start > maxThread.End-maxThread.Start {
+ maxThread = v
+ }
+ }
+ if maxThread == nil || maxThread.End <= maxThread.Start {
+ return false
+ }
+ if (maxThread.End-maxThread.Start)/2 < int64(w.BufferSize*2) || (maxThread.End-maxThread.Start)/2 < 100*1024 {
+ return false
+ }
+ task.End = maxThread.End
+ maxThread.End = maxThread.Start + (maxThread.End-maxThread.Start)/2
+ task.Start = maxThread.End + 1
+ //fmt.Printf("thread got new task %d-%d\n", task.Start, task.End)
+ return true
+}
+
+type downloadinfo struct {
+ Start int64
+ End int64
+ Size int64
+ current int64
+ lastCurrent int64
+ lastTime time.Time
+ speed float64
+}
+
+func (d *downloadinfo) Current() int64 {
+ return d.current
+}
+
+func (d *downloadinfo) Percent() float64 {
+ return float64(d.current) / float64(d.Size)
+}
+
+func (d *downloadinfo) FormatPercent() string {
+ return fmt.Sprintf("%.2f%%", d.Percent()*100)
+}
+
+func (d *downloadinfo) SetCurrent(info int64) {
+ d.current = info
+ now := time.Now()
+ if now.Sub(d.lastTime) >= time.Millisecond*500 {
+ d.speed = float64(d.current-d.lastCurrent) / (float64(now.Sub(d.lastTime).Milliseconds()) / 1000.00)
+ d.lastCurrent = d.current
+ d.lastTime = time.Now()
+ }
+}
+
+func (d *downloadinfo) AddCurrent(info int64) {
+ d.current += info
+ now := time.Now()
+ if now.Sub(d.lastTime) >= time.Millisecond*500 {
+ d.speed = float64(d.current-d.lastCurrent) / (float64(now.Sub(d.lastTime).Milliseconds()) / 1000.00)
+ d.lastCurrent = d.current
+ d.lastTime = time.Now()
+ }
+}
+
+func (d *downloadinfo) FormatSpeed(unit string) string {
+ switch strings.ToLower(unit) {
+ case "kb":
+ return fmt.Sprintf("%.2f KB/s", d.speed/1024)
+ case "mb":
+ return fmt.Sprintf("%.2f MB/s", d.speed/1024/1024)
+ case "gb":
+ return fmt.Sprintf("%.2f GB/s", d.speed/1024/1024/1024)
+ default:
+ return fmt.Sprintf("%.2f B/s", d.speed)
+ }
+}
+
+func (d *downloadinfo) Speed() float64 {
+ return d.speed
+}
diff --git a/mget/wget_test.go b/mget/wget_test.go
new file mode 100644
index 0000000..b8b5a6d
--- /dev/null
+++ b/mget/wget_test.go
@@ -0,0 +1,35 @@
+package mget
+
+import (
+ "b612.me/starnet"
+ "fmt"
+ "testing"
+)
+
+func TestWget(t *testing.T) {
+ r := starnet.NewSimpleRequest("http://192.168.2.33:88/DJI_0746.MP4", "GET")
+ w := Mget{
+ Setting: *r,
+ RedoRPO: 1048576,
+ BufferSize: 8192,
+ Thread: 8,
+ }
+ if err := w.Run(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestSM(t *testing.T) {
+ a := map[string]string{
+ "1": "1",
+ "2": "2",
+ }
+ modify(a)
+ fmt.Println(a)
+}
+
+func modify(a map[string]string) {
+ b := make(map[string]string)
+ b = a
+ b["1"] = "3"
+}
diff --git a/net/cmd.go b/net/cmd.go
index 2bd5b30..60356cd 100644
--- a/net/cmd.go
+++ b/net/cmd.go
@@ -81,6 +81,7 @@ func init() {
CmdNatThrough.Flags().BoolVarP(&natt.AutoUPnP, "auto-upnp", "u", true, "自动UPnP")
CmdNatThrough.Flags().IntVarP(&natt.WebPort, "web-port", "w", 8080, "web端口")
CmdNatThrough.Flags().IntVarP(&natt.HealthCheckInterval, "health-check-interval", "H", 30, "健康检查间隔")
+ CmdNatThrough.Flags().StringVarP(&natt.Type, "type", "t", "tcp", "穿透协议,tcp或udp")
Cmd.AddCommand(CmdNatThrough)
CmdScanIP.Flags().StringVarP(&scanip.Host, "ip", "i", "", "扫描IP地址")
diff --git a/net/natt.go b/net/natt.go
index f9a99ec..303f1ca 100644
--- a/net/natt.go
+++ b/net/natt.go
@@ -3,6 +3,7 @@ package net
import (
"b612.me/apps/b612/netforward"
"b612.me/starlog"
+ "b612.me/starmap"
"context"
"encoding/binary"
"encoding/json"
@@ -27,6 +28,7 @@ type NatThroughs struct {
STUN string
Remote string
HealthCheckInterval int
+ Type string
}
func (n *NatThroughs) Close() {
@@ -48,19 +50,23 @@ func (n *NatThroughs) Parse(reqs []string) error {
if n.STUN == "" {
n.STUN = "turn.b612.me:3478"
}
+ if n.Type == "" {
+ n.Type = "tcp"
+ }
for _, v := range reqs {
var req = NatThrough{
Forward: netforward.NetForward{
LocalAddr: "0.0.0.0",
DialTimeout: time.Second * 5,
- UDPTimeout: time.Second * 20,
+ UDPTimeout: time.Second * 30,
KeepAlivePeriod: n.KeepAlivePeriod,
KeepAliveIdel: n.KeepAliveIdel,
KeepAliveCount: n.KeepAliveCount,
UsingKeepAlive: true,
EnableTCP: true,
+ EnableUDP: true,
},
- Type: "tcp",
+ Type: n.Type,
STUN: n.STUN,
Remote: n.Remote,
KeepAlivePeriod: n.KeepAlivePeriod,
@@ -72,7 +78,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
strs := strings.Split(v, ",")
switch len(strs) {
case 1:
- req.Type = "tcp"
req.Forward.RemoteURI = strs[0]
case 2:
ipport := strings.Split(strs[0], ":")
@@ -90,7 +95,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
}
req.Forward.LocalPort = port
}
- req.Type = "tcp"
req.Forward.RemoteURI = strs[1]
case 3:
ipport := strings.Split(strs[1], ":")
@@ -108,7 +112,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
}
req.Forward.LocalPort = port
}
- req.Type = "tcp"
req.Forward.RemoteURI = strs[2]
req.Name = strs[0]
case 4:
@@ -144,7 +147,7 @@ func (n *NatThroughs) Run() error {
go func(v *NatThrough) {
defer wg.Done()
if err := v.Run(); err != nil {
- starlog.Errorf("Failed to run forward: %v\n", err)
+ starlog.Errorf("Failed to run natThrough: %v\n", err)
}
v.HealthCheck()
}(v)
@@ -262,6 +265,7 @@ func (c *NatThrough) Run() error {
c.Forward.EnableTCP = false
c.Forward.EnableUDP = true
}
+ starlog.Infof("NatThrough Type: %s\n", c.Type)
starlog.Infof("Local Port: %d\n", c.Forward.LocalPort)
starlog.Infof("Keepalive To: %s\n", c.Remote)
starlog.Infof("Forward To: %s\n", c.Forward.RemoteURI)
@@ -294,7 +298,7 @@ func (c *NatThrough) Run() error {
}
go func() {
if err := c.KeepAlive(c.Forward.LocalAddr, c.Forward.LocalPort); err != nil {
- starlog.Errorf("Failed to run forward: %v\n", err)
+ starlog.Errorf("Failed to run keepalive: %v\n", err)
c.Forward.Close()
c.stopFn()
}
@@ -319,6 +323,26 @@ func (c *NatThrough) Run() error {
}
func (c *NatThrough) HealthCheck() {
+ getIP := func(ip net.Addr) string {
+ switch ip.(type) {
+ case *net.TCPAddr:
+ return ip.(*net.TCPAddr).IP.String()
+ case *net.UDPAddr:
+ return ip.(*net.UDPAddr).IP.String()
+ default:
+ return ""
+ }
+ }
+ getPort := func(ip net.Addr) int {
+ switch ip.(type) {
+ case *net.TCPAddr:
+ return ip.(*net.TCPAddr).Port
+ case *net.UDPAddr:
+ return ip.(*net.UDPAddr).Port
+ default:
+ return 0
+ }
+ }
count := 0
if c.HealthCheckInterval == 0 {
c.HealthCheckInterval = 30
@@ -330,16 +354,30 @@ func (c *NatThrough) HealthCheck() {
case <-time.After(time.Second * time.Duration(c.HealthCheckInterval)):
}
if c.Type == "udp" {
- continue
- }
- conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2)
- if err != nil {
- starlog.Warningf("Health Check Fail: %v\n", err)
- count++
+ _, extIp, err := c.UdpKeppaliveSTUN(c.Forward.UdpListener(), c.STUN)
+ if err != nil {
+ count++
+ starlog.Errorf("Health Check Error: %v\n", err)
+ continue
+ }
+ extUrl := fmt.Sprintf("%s:%d", getIP(extIp), getPort(extIp))
+ if c.ExtUrl != extUrl {
+ count++
+ } else {
+ count = 0
+ }
+ starlog.Noticef("Health Check:Origin %s,Current %s\n", c.ExtUrl, extUrl)
} else {
- starlog.Infof("Health Check Ok\n")
- conn.(*net.TCPConn).SetLinger(0)
- conn.Close()
+ conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2)
+ if err != nil {
+ starlog.Warningf("Health Check Fail: %v\n", err)
+ count++
+ } else {
+ count = 0
+ starlog.Infof("Health Check Ok\n")
+ conn.(*net.TCPConn).SetLinger(0)
+ conn.Close()
+ }
}
if count >= 3 {
count = 0
@@ -412,20 +450,20 @@ func (c *NatThrough) KeepAlive(localAddr string, localPort int) error {
if err != nil {
return err
}
- conn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.ParseIP(localAddr), Port: localPort}, rmtUdpAddr)
- if err != nil {
- starlog.Errorf("Failed to dial remote: %v\n", err)
+ if c.Forward.UdpListener() == nil {
time.Sleep(time.Second * 5)
continue
}
- c.keepaliveConn = conn
+ c.keepaliveConn = c.Forward.UdpListener()
for {
- _, err = conn.Write([]byte("b612 tcp nat through"))
+ _, err = c.Forward.UdpListener().WriteTo([]byte("b612 udp nat through"), rmtUdpAddr)
if err != nil {
- conn.Close()
+ c.keepaliveConn.Close()
starlog.Warningf("Failed to keepalive remote: %v\n", err)
+ time.Sleep(time.Second * 30)
break
}
+ starlog.Infof("UDP Keepalive Ok! %v\n", rmtUdpAddr.String())
time.Sleep(time.Second * 30)
}
}
@@ -482,6 +520,7 @@ func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort
conn.(*net.TCPConn).SetLinger(0)
}
if netType == "udp" {
+
conn, err = net.DialUDP(netType, &net.UDPAddr{IP: net.ParseIP(localip), Port: localPort}, stunAddr)
if err != nil {
return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err)
@@ -551,6 +590,90 @@ func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort
return innerAddr, outerAddr, nil
}
+func (c *NatThrough) UdpKeppaliveSTUN(conn *net.UDPConn, stunServer string) (net.Addr, net.Addr, error) {
+ // 替换为你的 TURN 服务器地址
+ var target *starmap.StarStack
+ {
+ tmpConn, err := net.Dial("udp", stunServer)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err)
+ }
+ if c.Forward.UdpHooks == nil {
+ c.Forward.UdpHooks = make(map[string]*starmap.StarStack)
+ }
+ if c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] == nil {
+ c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] = starmap.NewStarStack(16)
+ }
+ target = c.Forward.UdpHooks[tmpConn.RemoteAddr().String()]
+ tmpConn.Close()
+ }
+ stunAddr, err := net.ResolveUDPAddr("udp", stunServer)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to resolve STUN server address: %v", err)
+ }
+ innerAddr := conn.LocalAddr()
+
+ // Create STUN request
+ transactionID := make([]byte, 12)
+ rand.Read(transactionID)
+ stunRequest := make([]byte, 20)
+ binary.BigEndian.PutUint16(stunRequest[0:], 0x0001) // Message Type: Binding Request
+ binary.BigEndian.PutUint16(stunRequest[2:], 0x0000) // Message Length
+ copy(stunRequest[4:], []byte{0x21, 0x12, 0xa4, 0x42}) // Magic Cookie
+ copy(stunRequest[8:], transactionID) // Transaction ID
+
+ _, err = conn.WriteToUDP(stunRequest, stunAddr)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to send STUN request: %v", err)
+ }
+
+ time.Sleep(time.Millisecond * 2500)
+ tmp, err := target.Pop()
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to receive STUN response: %v", err)
+ }
+ buf := tmp.([]byte)
+ n := len(buf)
+
+ // Parse STUN response
+ if n < 20 {
+ return nil, nil, fmt.Errorf("invalid STUN response")
+ }
+
+ payload := buf[20:n]
+ var ip uint32
+ var port uint16
+ for len(payload) > 0 {
+ attrType := binary.BigEndian.Uint16(payload[0:])
+ attrLen := binary.BigEndian.Uint16(payload[2:])
+ if len(payload) < int(4+attrLen) {
+ return nil, nil, fmt.Errorf("invalid STUN attribute length")
+ }
+
+ if attrType == 0x0001 || attrType == 0x0020 {
+ port = binary.BigEndian.Uint16(payload[6:])
+ ip = binary.BigEndian.Uint32(payload[8:])
+ if attrType == 0x0020 {
+ port ^= 0x2112
+ ip ^= 0x2112a442
+ }
+ break
+ }
+ payload = payload[4+attrLen:]
+ }
+
+ if ip == 0 || port == 0 {
+ return nil, nil, fmt.Errorf("invalid STUN response")
+ }
+
+ outerAddr := &net.UDPAddr{
+ IP: net.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)),
+ Port: int(port),
+ }
+
+ return innerAddr, outerAddr, nil
+}
+
func (c *NatThrough) GetMyOutIP() string {
tmp, err := net.Dial("udp", "8.8.8.8:53")
if err != nil {
diff --git a/net/sshjar.go b/net/sshjar.go
index 83e1e29..d71034f 100644
--- a/net/sshjar.go
+++ b/net/sshjar.go
@@ -79,7 +79,7 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
data[args[0]] = args[1]
}
}
- starnet.Curl(starnet.NewRequests(curlUrl, []byte(starnet.BuildQuery(data)), "POST"))
+ starnet.NewSimpleRequest(curlUrl, "POST").SetBodyDataBytes([]byte(starnet.BuildQuery(data))).Do()
}
}()
}
diff --git a/net/trace.go b/net/trace.go
index e0ee1f7..702ce6f 100644
--- a/net/trace.go
+++ b/net/trace.go
@@ -4,7 +4,6 @@ import (
"b612.me/starlog"
"b612.me/starnet"
"context"
- "encoding/json"
"fmt"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
@@ -214,12 +213,12 @@ func GetIPInfo(ip string, addr string) string {
return ""
}
uri := strings.ReplaceAll(addr, "{ip}", ip)
- res, err := starnet.Curl(starnet.NewRequests(uri, nil, "GET", starnet.WithTimeout(time.Second*2), starnet.WithDialTimeout(time.Second*3)))
+ res, err := starnet.Curl(starnet.NewSimpleRequest(uri, "GET", starnet.WithTimeout(time.Second*2), starnet.WithDialTimeout(time.Second*3)))
if err != nil {
return "获取IP信息失败:" + err.Error()
}
var ipinfo IPInfo
- err = json.Unmarshal(res.RecvData, &ipinfo)
+ err = res.Body().Unmarshal(&ipinfo)
if err != nil {
return "解析IP信息失败:" + err.Error()
}
diff --git a/netforward/cmd.go b/netforward/cmd.go
index 0203ce5..47b9714 100644
--- a/netforward/cmd.go
+++ b/netforward/cmd.go
@@ -29,6 +29,7 @@ func init() {
CmdNetforward.Flags().IntVarP(&f.KeepAliveCount, "keepalive-count", "C", 3, "keepalive count")
CmdNetforward.Flags().IntVarP(&f.UserTimeout, "user-timeout", "U", 0, "user timeout (milliseconds)")
CmdNetforward.Flags().BoolVarP(&f.IgnoreEof, "ignore-eof", "E", false, "ignore eof")
+ CmdNetforward.Flags().BoolVarP(&f.Verbose, "verbose", "v", false, "verbose mode")
}
var CmdNetforward = &cobra.Command{
diff --git a/netforward/forward.go b/netforward/forward.go
index 3698314..13ef3d4 100644
--- a/netforward/forward.go
+++ b/netforward/forward.go
@@ -3,6 +3,7 @@ package netforward
import (
"b612.me/stario"
"b612.me/starlog"
+ "b612.me/starmap"
"context"
"errors"
"fmt"
@@ -31,12 +32,19 @@ type NetForward struct {
stopCtx context.Context
stopFn context.CancelFunc
running int32
+ UdpHooks map[string]*starmap.StarStack
KeepAlivePeriod int
KeepAliveIdel int
KeepAliveCount int
UserTimeout int
UsingKeepAlive bool
+ Verbose bool
+ udpListener *net.UDPConn
+}
+
+func (n *NetForward) UdpListener() *net.UDPConn {
+ return n.udpListener
}
func (n *NetForward) Close() {
@@ -194,17 +202,17 @@ type UDPConn struct {
lastbeat int64
}
-func (u UDPConn) Write(p []byte) (n int, err error) {
+func (u *UDPConn) Write(p []byte) (n int, err error) {
u.lastbeat = time.Now().Unix()
return u.Conn.Write(p)
}
-func (u UDPConn) Read(p []byte) (n int, err error) {
+func (u *UDPConn) Read(p []byte) (n int, err error) {
u.lastbeat = time.Now().Unix()
return u.Conn.Read(p)
}
-func (u UDPConn) Work(delay int) {
+func (u *UDPConn) Work(delay int, verbose bool) {
buf := make([]byte, 8192)
for {
if delay > 0 {
@@ -216,7 +224,10 @@ func (u UDPConn) Work(delay int) {
u.lastbeat = 0
return
}
- _, err = u.listen.Write(buf[0:count])
+ if verbose {
+ fmt.Printf("U %v Recv Data %s ==> %s %X\n", time.Now().Format("2006-01-02 15:04:05"), u.Conn.RemoteAddr().String(), u.remoteAddr.String(), buf[0:count])
+ }
+ _, err = u.listen.WriteTo(buf[0:count], u.remoteAddr)
if err != nil {
u.lastbeat = 0
return
@@ -236,12 +247,13 @@ func (n *NetForward) runUDP() error {
if err != nil {
return err
}
+ n.udpListener = listen
starlog.Infof("Listening UDP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
go func() {
<-n.stopCtx.Done()
listen.Close()
}()
- udpMap := make(map[string]UDPConn)
+ udpMap := make(map[string]*UDPConn)
go func() {
for {
select {
@@ -270,6 +282,22 @@ func (n *NetForward) runUDP() error {
if err != nil || rmt.String() == n.RemoteURI {
continue
}
+ {
+ //hooks
+ if n.UdpHooks != nil {
+ if m, ok := n.UdpHooks[rmt.String()]; ok {
+ if m.Free() > 0 {
+ if n.Verbose {
+ starlog.Noticef("Hooked UDP Data %s ==> %s %X\n", rmt.String(), n.RemoteURI, buf[0:count])
+ } else {
+ starlog.Noticef("Hooked UDP Data %s ==> %s\n", rmt.String(), n.RemoteURI)
+ }
+ m.Push(buf[0:count])
+ continue
+ }
+ }
+ }
+ }
go func(data []byte, rmt *net.UDPAddr) {
log := starlog.Std.NewFlag()
mu.Lock()
@@ -282,20 +310,23 @@ func (n *NetForward) runUDP() error {
mu.Unlock()
return
}
- addr = UDPConn{
+ addr = &UDPConn{
Conn: conn,
remoteAddr: rmt,
listen: listen,
lastbeat: time.Now().Unix(),
}
udpMap[rmt.String()] = addr
- go addr.Work(n.DelayMilSec)
+ go addr.Work(n.DelayMilSec, n.Verbose)
log.Infof("UDP Connect %s <==> %s\n", rmt.String(), n.RemoteURI)
}
mu.Unlock()
if n.DelayMilSec > 0 || (n.DelayToward == 0 || n.DelayToward == 1) {
time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec))
}
+ if n.Verbose {
+ fmt.Printf("T %v Recv Data %s ==> %s %X\n", time.Now().Format("2006-01-02 15:04:05"), rmt.String(), n.RemoteURI, data)
+ }
_, err := addr.Write(data)
if err != nil {
mu.Lock()
@@ -308,6 +339,12 @@ func (n *NetForward) runUDP() error {
}
}
+func (n *NetForward) showVerbose(toward, src, dst string, data []byte) {
+ if n.Verbose {
+ fmt.Printf("%s %v Recv Data %s ==> %s %X\n", toward, time.Now().Format("2006-01-02 15:04:05"), src, dst, data)
+ }
+}
+
func (n *NetForward) copy(dst, src net.Conn) {
var wg sync.WaitGroup
wg.Add(2)
@@ -324,6 +361,7 @@ func (n *NetForward) copy(dst, src net.Conn) {
src.Close()
return
}
+ n.showVerbose("T", src.RemoteAddr().String(), dst.RemoteAddr().String(), bufsize[:count])
_, err = dst.Write(bufsize[:count])
if err != nil {
src.Close()
@@ -348,6 +386,7 @@ func (n *NetForward) copy(dst, src net.Conn) {
dst.Close()
return
}
+ n.showVerbose("U", dst.RemoteAddr().String(), src.RemoteAddr().String(), bufsize[:count])
_, err = src.Write(bufsize[:count])
if err != nil {
src.Close()
diff --git a/whois/cmd.go b/whois/cmd.go
index 7075660..5cf1724 100644
--- a/whois/cmd.go
+++ b/whois/cmd.go
@@ -1,13 +1,18 @@
package whois
import (
+ "b612.me/sdk/whois"
+ "b612.me/stario"
"b612.me/starlog"
"b612.me/staros"
- "github.com/likexian/whois"
+ "bufio"
+ "fmt"
"github.com/spf13/cobra"
"golang.org/x/net/proxy"
+ "io"
"os"
"strings"
+ "sync"
"time"
)
@@ -16,13 +21,37 @@ var output string
var whoisServer []string
var socks5 string
var socks5Auth string
+var showFull bool
+var hideFormat bool
+var useDict bool
+var hideExists bool
+var hideNoExists bool
+var thread int
+var suffix string
+var retries int
func init() {
Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
Cmd.Flags().StringVarP(&output, "output", "o", "", "输出文件夹")
Cmd.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器")
Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理,示例:127.0.0.1:1080")
- Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证,示例:username:password")
+ Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "a", "", "socks5代理认证,示例:username:password")
+ Cmd.Flags().BoolVarP(&showFull, "full", "f", false, "显示完整信息")
+ Cmd.Flags().BoolVarP(&hideFormat, "hide-format", "g", false, "隐藏格式化信息")
+
+ CmdExists.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
+ CmdExists.Flags().StringVarP(&output, "output", "o", "", "输出文件")
+ CmdExists.Flags().BoolVarP(&useDict, "use-dict", "d", false, "使用字典查询")
+ CmdExists.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器")
+ CmdExists.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理,示例:127.0.0.1:1080")
+ CmdExists.Flags().StringVarP(&socks5Auth, "socks5-auth", "a", "", "socks5代理认证,示例:username:password")
+ CmdExists.Flags().BoolVarP(&hideExists, "hide-exists", "e", false, "隐藏存在的域名")
+ CmdExists.Flags().BoolVarP(&hideNoExists, "hide-no-exists", "n", false, "隐藏不存在的域名")
+ CmdExists.Flags().IntVarP(&thread, "thread", "m", 10, "并发查询数")
+ CmdExists.Flags().StringVarP(&suffix, "suffix", "x", "", "域名后缀")
+ CmdExists.Flags().IntVarP(&retries, "retries", "r", 3, "重试次数")
+
+ Cmd.AddCommand(CmdExists)
}
var Cmd = &cobra.Command{
@@ -35,7 +64,6 @@ var Cmd = &cobra.Command{
return
}
if !staros.Exists(output) {
- cmd.Println("输出文件夹不存在,将使用标准输出")
output = ""
}
c := whois.NewClient()
@@ -70,9 +98,238 @@ var Cmd = &cobra.Command{
cmd.Println("-----------------------------------------------------")
continue
}
- cmd.Println(data)
+ if !hideFormat && !data.Exists() {
+ fmt.Printf("域名:\t%s 不存在\n", data.Domain())
+ }
+ if !hideFormat && data.Exists() {
+ fmt.Printf("域名名称:\t%s\n", data.Domain())
+ fmt.Printf("注册商:\t%s\n", data.Registar())
+ if data.HasRegisterDate() {
+ fmt.Printf("注册时间:\t%s\n", data.RegisterDate())
+ }
+ if data.HasExpireDate() {
+ fmt.Printf("到期时间:\t%s\n", data.ExpireDate())
+ }
+ if data.HasUpdateDate() {
+ fmt.Printf("更新时间:\t%s\n", data.UpdateDate())
+ }
+ for _, v := range data.Status() {
+ fmt.Printf("域名状态:\t%s\n", v)
+ }
+ if data.IanaID() != "" {
+ fmt.Printf("IANA ID:\t%s\n", data.IanaID())
+ }
+ if data.Dnssec() != "" {
+ fmt.Printf("DNSSEC:\t%s\n", data.Dnssec())
+ }
+ isShowContact := false
+ if data.RegisterInfo().Name != "" {
+ fmt.Printf("注册者:\t%s\n", data.RegisterInfo().Name)
+ isShowContact = true
+ }
+ if data.RegisterInfo().State != "" {
+ fmt.Printf("注册省份:\t%s\n", data.RegisterInfo().State)
+ isShowContact = true
+ }
+ if data.RegisterInfo().Country != "" {
+ fmt.Printf("注册国家:\t%s\n", data.RegisterInfo().Country)
+ isShowContact = true
+ }
+ if data.RegisterInfo().Email != "" {
+ fmt.Printf("注册邮箱:\t%s\n", data.RegisterInfo().Email)
+ isShowContact = true
+ }
+ if !isShowContact {
+ if data.AdminInfo().Name != "" {
+ fmt.Printf("注册者:\t%s\n", data.RegisterInfo().Name)
+ isShowContact = true
+ }
+ if data.AdminInfo().State != "" {
+ fmt.Printf("注册省份:\t%s\n", data.RegisterInfo().State)
+ isShowContact = true
+ }
+ if data.AdminInfo().Country != "" {
+ fmt.Printf("注册国家:\t%s\n", data.RegisterInfo().Country)
+ isShowContact = true
+ }
+ if data.AdminInfo().Email != "" {
+ fmt.Printf("注册邮箱:\t%s\n", data.RegisterInfo().Email)
+ isShowContact = true
+ }
+ }
+ for _, v := range data.NsServers() {
+ fmt.Printf("NS服务器:\t%s\n", v)
+ }
+ for _, v := range data.NsIps() {
+ fmt.Printf("NS IP:\t%s\n", v)
+ }
+ }
+ if showFull {
+ fmt.Println("")
+ fmt.Println(data.RawData())
+ }
cmd.Println("-----------------------------------------------------")
- os.WriteFile(output+"/"+v+".txt", []byte(data), 0644)
+ os.WriteFile(output+"/"+v+".txt", []byte(data.RawData()), 0644)
}
},
}
+
+var CmdExists = &cobra.Command{
+ Use: "exists",
+ Short: "域名是否存在查询",
+ Long: "域名是否存在查询,可使用字典",
+ Run: func(cmd *cobra.Command, args []string) {
+ if len(args) == 0 {
+ cmd.Help()
+ return
+ }
+ var of *os.File
+ var err error
+ if output != "" {
+ of, err = os.Create(output)
+ if err != nil {
+ starlog.Errorln("创建输出文件失败", err)
+ return
+ }
+ defer of.Close()
+ }
+ c := whois.NewClient()
+ if socks5 != "" {
+ var auth *proxy.Auth
+ if socks5Auth != "" {
+ up := strings.SplitN(socks5Auth, ":", 2)
+ if len(up) == 2 {
+ auth = &proxy.Auth{
+ User: up[0],
+ Password: up[1],
+ }
+ } else {
+ starlog.Errorln("socks5认证格式错误")
+ return
+ }
+ }
+ s5Dial, err := proxy.SOCKS5("tcp", socks5, auth, proxy.Direct)
+ if err == nil {
+ c.SetDialer(s5Dial)
+ } else {
+ starlog.Errorln("socks5代理错误:", err)
+ return
+ }
+ }
+ c.SetTimeout(time.Second * time.Duration(timeout))
+ var d domainList
+ if !useDict {
+ err = d.New("stdin", args)
+ } else {
+ err = d.New("file", args)
+ }
+ if err != nil {
+ starlog.Errorln("初始化域名列表失败", err)
+ return
+ }
+ if thread == 0 {
+ thread = 1
+ }
+ var wg = stario.NewWaitGroup(thread)
+ fin := false
+ for !fin {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ domain, err := d.Next()
+ if err != nil {
+ if err == io.EOF {
+ fin = true
+ }
+ return
+ }
+ if suffix != "" {
+ domain = domain + "." + suffix
+ }
+ var data whois.Result
+ for i := 0; i < retries; i++ {
+ data, err = c.Whois(domain, whoisServer...)
+ if err != nil {
+ continue
+ }
+ break
+ }
+ if err != nil {
+ starlog.Errorln("查询失败:", domain, err)
+ return
+ }
+ if !hideExists && data.Exists() {
+ fmt.Println(domain, "已注册")
+ if of != nil {
+ of.WriteString(domain + " 已注册\n")
+ }
+ }
+ if !hideNoExists && !data.Exists() {
+ fmt.Println(domain, "不存在")
+ if of != nil {
+ of.WriteString(domain + " 不存在\n")
+ }
+ }
+ }()
+ }
+ wg.Wait()
+ },
+}
+
+type domainList struct {
+ typed string
+ args []string
+ idx int
+ mu sync.Mutex
+ f *os.File
+ scanner *bufio.Reader
+}
+
+func (d *domainList) New(types string, args []string) error {
+ var err error
+ d.typed = types
+ d.args = args
+ switch d.typed {
+ case "file":
+ d.f, err = os.OpenFile(d.args[0], os.O_RDONLY, 0644)
+ if err != nil {
+ return err
+ }
+ d.scanner = bufio.NewReader(d.f)
+ case "stdin":
+
+ }
+ return err
+}
+
+func (d *domainList) Next() (string, error) {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ for {
+ switch d.typed {
+ case "file":
+ l, _, err := d.scanner.ReadLine()
+ if err == io.EOF {
+ if d.idx+1 >= len(d.args) {
+ d.f.Close()
+ return "", err
+ }
+ d.idx++
+ d.f.Close()
+ d.f, err = os.OpenFile(d.args[d.idx], os.O_RDONLY, 0644)
+ if err != nil {
+ return "", err
+ }
+ d.scanner = bufio.NewReader(d.f)
+ continue
+ }
+ return strings.TrimSpace(string(l)), nil
+ case "stdin":
+ if d.idx >= len(d.args) {
+ return "", io.EOF
+ }
+ d.idx++
+ return d.args[d.idx-1], nil
+ }
+ }
+}