master v2.1.0.beta.11
兔子 3 months ago
parent 8b0a3483a0
commit 4fd9c37944

@ -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
}

@ -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

@ -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,11 +112,22 @@ var CmdGen = &cobra.Command{
starlog.Errorln("证书公钥不能为空")
os.Exit(1)
}
caKeyRaw, caCertRaw, err := LoadCA(caKey, caCert, caKeyPwd)
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 {
starlog.Errorln("加载证书请求错误", err)
@ -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解密密码")

@ -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{
*/
return &x509.CertificateRequest{
Version: 3,
SerialNumber: big.NewInt(time.Now().Unix()),
//SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: s2s(country),
Province: s2s(province),
@ -41,21 +42,25 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s
},
DNSNames: trueDNS,
IPAddresses: trueIp,
NotBefore: start,
NotAfter: end,
BasicConstraintsValid: true,
IsCA: isCa,
MaxPathLen: maxPathLen,
MaxPathLenZero: maxPathLenZero,
KeyUsage: ku,
ExtKeyUsage: []x509.ExtKeyUsage{eku},
//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
}

@ -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 {

@ -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
)

179
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=

@ -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")

@ -0,0 +1,10 @@
package httpserver
import (
"net/http"
"testing"
)
func TestHttpServer(t *testing.T) {
http.ListenAndServe(":89", http.FileServer(http.Dir(`./`)))
}

@ -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 = `<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>B612 Http Server %s</title>
<style>
* {
box-sizing: border-box;
}
body {
background-color: #f5f5f5;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.container {
max-width: 960px;
margin: 0 auto;
padding: 24px;
}
h1 {
text-align: center;
margin-bottom: 24px;
}
table {
width: 100%%;
border-collapse: collapse;
margin-top: 24px;
}
th,
td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
position: relative;
}
th[data-sort]:before {
content: "▼";
display: inline-block;
height: 20px;
width: 20px;
margin-right: 10px;
vertical-align: middle;
position: absolute;
right: 0;
top: 50%%;
transform: translateY(-50%%);
opacity: 0.3;
transition: all 0.2s ease-in-out;
}
th[data-sort].asc:before {
content: "▲";
opacity: 1;
}
th:hover:before {
opacity: 1;
}
.filename {
color: #007bff;
text-decoration: underline;
}
.filetype {
text-transform: uppercase;
}
@media screen and (max-width: 600px) {
table {
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>B612 Http Server - %s</h1>
<hr /><pre><h2> %s </h2></pre>%s
<table>
<thead>
<tr>
<th data-sort="name" class="asc">Name</th>
<th data-sort="modified">Modified</th>
<th data-sort="size">Size</th>
<th data-sort="type">Type</th>
</tr>
</thead>
<tbody>`
var htmlTail = ` </tbody>
</table>
<hr />
<pre>
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
</pre>
</div>
<script>
function sortTable(th, n) {
const table = document.querySelector('table');
const rows = table.rows;
let switching = true;
let shouldSwitch = false;
let direction = 'asc';
let switchcount = 0;
while (switching) {
switching = false;
let i;
for (i = 1; i < rows.length - 1; i++) {
shouldSwitch = false;
const x = rows[i].getElementsByTagName("td")[n];
const y = rows[i + 1].getElementsByTagName("td")[n];
let xValue, yValue;
if (n === 2) { // size sorting
if (x.innerText==="-") {
xValue=-1;
}else{
xValue = parseInt(x.innerText.split(' ')[0]);
}
if (y.innerText==="-") {
yValue=-1;
}else{
yValue = parseInt(y.innerText.split(' ')[0]);
}
} else {
xValue = x.innerText.toLowerCase();
yValue = y.innerText.toLowerCase();
}
if (direction === 'asc') {
if (xValue > yValue) {
shouldSwitch = true;
break;
}
} else if (direction === 'desc') {
if (xValue < yValue) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount++;
} else {
if (switchcount === 0 && direction === 'asc') {
direction = 'desc';
switching = true;
}
}
}
// update sort class
const ths = table.getElementsByTagName('th');
for (let i = 0; i < ths.length; i++) {
const currentTh = ths[i];
if (currentTh !== th) {
currentTh.classList.remove('asc');
} else {
currentTh.classList.toggle('asc');
}
}
// hide arrow on non-sorting columns
const sortableThs = table.querySelectorAll('thead th[data-sort]');
for (let i = 0; i < sortableThs.length; i++) {
const sortableTh = sortableThs[i];
if (sortableTh !== th) {
sortableTh.classList.remove('asc');
}
}
}
// add sorting event listener to thead
const ths = document.querySelectorAll('table th[data-sort]');
for (let i = 0; i < ths.length; i++) {
const th = ths[i];
th.addEventListener('click', () => {
const sortType = th.getAttribute('data-sort');
let columnIndex;
switch (sortType) {
case 'name':
columnIndex = 0;
break;
case 'modified':
columnIndex = 1;
break;
case 'size':
columnIndex = 2;
break;
case 'type':
columnIndex = 3;
break;
}
sortTable(th, columnIndex);
});
}
</script>
</body>
</html>
`
//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(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">403 Forbidden</h1><hr ></body></html>`))
w.Write([]byte(`
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>B612 HTTP SERVER</center>
</body>
</html>`))
}
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
var rangeStart, rangeEnd int64 = -1, -1
ranges := r.Header.Get("Range")
if ranges == "" {
return rangeStart, rangeEnd
}
if !strings.Contains(ranges, "bytes=") {
return rangeStart, rangeEnd
}
data := strings.Split(v[0], "-")
ranges = strings.TrimPrefix(ranges, "bytes=")
data := strings.Split(ranges, "-")
if len(data) == 0 {
break
return rangeStart, rangeEnd
}
rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
if len(data) > 1 {
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
}
//w.WriteHeader(206) //206 支持断点续传
break
}
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 = `<a href=/b612?upload=true>Upload Web Page Is Openned!</a>`
}
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(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
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(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
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(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
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: "文件夹",
})
}
}
}
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(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
jData, err := json.Marshal(fdatas)
if err != nil {
log.Errorf("Json Marshal Failed:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
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(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
w.Write([]byte(htmlTail))
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{
req, err := starnet.Curl(starnet.NewSimpleRequest(hook.Url,
"POST",
starnet.WithBytes(starnet.BuildPostForm(map[string]string{
"data": b64,
"ip": r.RemoteAddr,
}),
"POST",
})),
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)
}
}

@ -0,0 +1,478 @@
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>B612 Http Server {{ .IdxTitle }}</title>
<style>
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body {
background-color: #f5f5f5;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
height: 100vh;
position: relative; /* 为了使背景图片固定 */
}
.background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
{{ .Photo }} /* 这里的背景图片 URL 是动态插入的 */
background-size: cover;
opacity: 0.5; /* 调整透明度 */
}
@media screen and (max-width: 768px) {
.background {
{{ .MobilePhoto }}
background-size: cover;
}
}
.container {
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
width: 50%;
max-width: 1920px;
margin: 0 auto;
padding: 24px;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
background: rgba(245, 245, 245, 0.5); /* 添加一个半透明背景层 */
}
h1 {
text-align: center;
margin-bottom: 24px;
}
.table-container {
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 24px;
table-layout: fixed; /* 确保表格单元格宽度固定 */
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
white-space: normal; /* 允许换行 */
background: rgba(245, 245, 245, 0.5); /* 设置表格单元格背景为半透明 */
}
th[data-sort]:after {
content: "▲";
display: inline-block;
height: 20px;
width: 20px;
margin-left: 10px;
vertical-align: middle;
opacity: 0.3;
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-ms-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
th.asc:after {
content: "▲";
opacity: 1;
}
th.desc:after {
content: "▼";
opacity: 1;
}
th:hover:after {
opacity: 1;
}
.filename {
color: #007bff;
text-decoration: underline;
display: block;
max-width: 600px; /* 调整你想要的最大宽度 */
word-wrap: break-word; /* 确保文件名能够换行 */
}
.filename:hover {
color: #0056b3;
}
tr:hover {
background-color: rgba(241, 241, 241, 0.5); /* 设置鼠标悬停效果的背景为半透明 */
}
.filetype {
text-transform: uppercase;
}
@media screen and (max-width: 600px) {
table {
font-size: 14px;
}
}
@media (orientation: portrait) and (max-width: 1024px) {
.container {
width: 60%;
}
}
@media (max-width: 400px) {
.container {
width: 80%;
}
}
thead th {
position: -webkit-sticky;
position: -moz-sticky;
position: sticky;
top: 0;
z-index: 1;
background: rgba(245, 245, 245, 0.5); /* 设置表头背景为半透明 */
}
tbody td:first-child,
thead th:first-child {
position: -webkit-sticky;
position: -moz-sticky;
position: sticky;
left: 0;
z-index: 1;
background: rgba(245, 245, 245, 0.5); /* 设置第一列背景为半透明 */
}
hr {
width: 100%;
}
.search-container {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
-moz-justify-content: center;
-ms-justify-content: center;
justify-content: center;
margin-bottom: 20px;
}
.search-container input {
padding: 10px;
width: 80%;
max-width: 400px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="background"></div>
<div class="container">
<h1>B612 Http Server - {{ .Version }}</h1>
<hr />
<h2>{{ .Idx }}</h2>
{{ .Upload }}
<div class="search-container">
<input type="text" id="search-box" placeholder="Search for a file..." />
</div>
<div class="table-container" id="table-container">
<table>
<thead>
<tr>
<th data-sort="name">Name</th>
<th data-sort="modified">Modified</th>
<th data-sort="size">Size</th>
<th data-sort="type">Type</th>
</tr>
</thead>
<tbody id="table-content"><!-- table content is dynamically filled by JavaScript -->
</tbody>
</table>
</div>
<hr />
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
</div>
<!-- Context menu for copying file details -->
<div id="context-menu" style="display:none; position: absolute; z-index: 1000; background: white; border: 1px solid #ccc; padding: 5px;">
<ul style="list-style: none; margin: 0; padding: 0;">
<li id="copy-filename" style="padding: 5px; cursor: pointer;">复制文件名</li>
<li id="copy-link" style="padding: 5px; cursor: pointer;">复制文件链接地址</li>
<li id="copy-size-bytes" style="padding: 5px; cursor: pointer;">复制文件大小(按字节)</li>
<li id="copy-size-display" style="padding: 5px; cursor: pointer;">复制文件大小(按显示)</li>
</ul>
</div>
<script>
// 初始化内容
var dataRows = {{ .Data }};
function loadTableContent(dataRows) {
var tableContent = document.getElementById('table-content');
if (!tableContent) return;
var html = '';
dataRows.forEach(function(row) {
html += '<tr>';
html += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
html += '<td>' + row.modified + '</td>';
html += '<td title="' + row.size + ' bytes">' + formatSize(row.size) + '</td>';
html += '<td class="filetype">' + row.type + '</td>';
html += '</tr>';
});
tableContent.innerHTML = html;
}
function renderRows(rows) {
var tableContent = document.getElementById('table-content');
var fragment = document.createDocumentFragment();
rows.forEach(function(row) {
var tr = document.createElement('tr');
tr.innerHTML += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
tr.innerHTML += '<td>' + row.modified + '</td>';
var formattedSize = formatSize(row.size);
tr.innerHTML += '<td title="' + row.size + ' bytes">' + formattedSize + '</td>';
tr.innerHTML += '<td class="filetype">' + row.type + '</td>';
fragment.appendChild(tr);
});
tableContent.innerHTML = ''; // 清空现有内容
tableContent.appendChild(fragment);
}
function chunkedRenderRows() {
var chunkSize = 50;
var currentIndex = 0;
function renderChunk() {
var fragment = document.createDocumentFragment();
for (var i = currentIndex; i < currentIndex + chunkSize && i < dataRows.length; i++) {
var row = dataRows[i];
if (row.name === '..' && i !== 0) continue;
var tr = document.createElement('tr');
tr.innerHTML += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
tr.innerHTML += '<td>' + row.modified + '</td>';
var formattedSize = formatSize(row.size);
tr.innerHTML += '<td title="' + row.size + ' bytes">' + formattedSize + '</td>';
tr.innerHTML += '<td class="filetype">' + row.type + '</td>';
fragment.appendChild(tr);
}
document.getElementById('table-content').appendChild(fragment);
currentIndex += chunkSize;
if (currentIndex < dataRows.length) {
requestAnimationFrame(renderChunk);
}
}
// 清空现有内容
document.getElementById('table-content').innerHTML = '';
// 开始分块渲染
requestAnimationFrame(renderChunk);
}
function parseSize(size) {
var units = { 'KB': 1024, 'MB': 1024 * 1024, 'GB': 1024 * 1024 * 1024 };
var match = size.match(/(\d+\.?\d*)\s*(KB|MB|GB)/i);
if (match) {
return parseFloat(match[1]) * (units[match[2].toUpperCase()] || 1);
}
return parseInt(size, 10);
}
function formatSize(size) {
if (size < 0) return "-";
if (size < 1024) return size + ' B';
else if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB';
else if (size < 1024 * 1024 * 1024) return (size / (1024 * 1024)).toFixed(2) + ' MB';
else return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
}
function sortTable(th, n, initial) {
var direction = th.classList.contains('asc') && !initial ? 'desc' : 'asc';
dataRows.sort(function(a, b) {
// 检查 'name' 字段以确保 '..' 始终在第一位
if (a.name === '..') return -1;
if (b.name === '..') return 1;
var x = Object.values(a)[n];
var y = Object.values(b)[n];
if (n === 1) { // modified column
// 解析日期字符串
x = new Date(a.modified);
y = new Date(b.modified);
} else if (n === 2) { // size column
x = a.size;
y = b.size;
}
return direction === 'asc' ?
(x < y ? -1 : x > y ? 1 : 0) :
(x > y ? -1 : x < y ? 1 : 0);
});
th.classList.toggle('asc', direction === 'asc' && !initial);
th.classList.toggle('desc', direction === 'desc' && !initial);
updateSortIcons(th);
renderRows(dataRows);
}
function updateSortIcons(th) {
var ths = document.querySelectorAll('thead th[data-sort]');
ths.forEach(function(header) {
if (header !== th) {
header.classList.remove('asc');
header.classList.remove('desc');
}
});
}
// 初次加载时按 Name 列进行升序排序
function initialSort() {
var nameHeader = document.querySelector('th[data-sort="name"]');
if (nameHeader) {
nameHeader.classList.add('asc'); // 直接设置为升序状态
sortTable(nameHeader, 0, true); // 传递一个参数表示这是初始排序
}
}
function isIE() {
return window.navigator.userAgent.indexOf("MSIE ") > -1 || navigator.userAgent.indexOf("Trident/") > -1;
}
if (!isIE()) {
document.addEventListener('DOMContentLoaded', function(event) {
var ths = document.querySelectorAll('thead th[data-sort]');
ths.forEach(function(th, i) {
th.addEventListener('click', function() {
sortTable(th, i);
});
});
// 使用 chunkedRenderRows 进行初始渲染, 然后进行默认排序
renderRows(dataRows);
// 初次加载时按 Name 列进行升序排序
initialSort();
// 初始化右键菜单
initializeContextMenu();
});
// 处理搜索框输入事件
document.getElementById('search-box').addEventListener('input', function (e) {
var searchText = e.target.value.toLowerCase();
var filteredRows = dataRows.filter(function (row) {
return row.name.toLowerCase().includes(searchText);
});
renderRows(filteredRows);
});
}else{
loadTableContent(dataRows);
// 禁用搜索框
var searchBox = document.getElementById('search-box');
if (searchBox) {
searchBox.disabled = true;
searchBox.placeholder = "IE浏览器不受支持大部分功能受限";
}
}
function initializeContextMenu() {
var contextMenu = document.getElementById('context-menu');
document.addEventListener('contextmenu', function(e) {
if (e.target.classList.contains('filetype')) {
e.preventDefault();
var row = e.target.closest('tr');
var fileNameToCopy = row.querySelector('.filename').textContent;
var linkToCopy = row.querySelector('.filename').href;
var byteSizeToCopy = row.querySelector('td[title]') ? row.querySelector('td[title]').getAttribute('title') : '';
var displaySizeToCopy = row.querySelector('td[title]') ? row.querySelector('td[title]').textContent : '';
contextMenu.style.display = 'block';
contextMenu.style.top = e.pageY + 'px';
contextMenu.style.left = e.pageX + 'px';
contextMenu.setAttribute('data-filename', fileNameToCopy);
contextMenu.setAttribute('data-link', linkToCopy);
contextMenu.setAttribute('data-size-bytes', byteSizeToCopy);
contextMenu.setAttribute('data-size-display', displaySizeToCopy);
} else {
contextMenu.style.display = 'none';
}
});
document.addEventListener('click', function() {
contextMenu.style.display = 'none';
});
document.getElementById('copy-filename').addEventListener('click', function() {
var fileName = contextMenu.getAttribute('data-filename');
if (fileName) {
copyToClipboard(fileName);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-link').addEventListener('click', function() {
var fileLink = contextMenu.getAttribute('data-link');
if (fileLink) {
copyToClipboard(fileLink);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-size-bytes').addEventListener('click', function() {
var fileSizeBytes = contextMenu.getAttribute('data-size-bytes');
if (fileSizeBytes) {
copyToClipboard(fileSizeBytes);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-size-display').addEventListener('click', function() {
var fileSizeDisplay = contextMenu.getAttribute('data-size-display');
if (fileSizeDisplay) {
copyToClipboard(fileSizeDisplay);
}
contextMenu.style.display = 'none';
});
}
function copyToClipboard(text) {
var textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
</script>
</body>
</html>

@ -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)

@ -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() {

@ -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)
}
}

@ -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)
}
}
}
}

@ -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
}

@ -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())
}

@ -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))
}

@ -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<filename>[^;"]+)`)
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
}

@ -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
}

@ -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"
}

@ -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地址")

@ -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,17 +354,31 @@ func (c *NatThrough) HealthCheck() {
case <-time.After(time.Second * time.Duration(c.HealthCheckInterval)):
}
if c.Type == "udp" {
_, 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 {
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
starlog.Errorf("Failed to connect to remote, close connection retrying\n")
@ -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 {

@ -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()
}
}()
}

@ -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()
}

@ -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{

@ -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()

@ -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
}
}
}

Loading…
Cancel
Save