1.新增自动颁发证书
2.更新Go到1.23
This commit is contained in:
parent
db851fbcb1
commit
f77fc6dddf
@ -16,14 +16,16 @@ go build -o .\bin\b612_x86_64 -ldflags "-w -s" .
|
||||
upx -9 .\bin\b612_x86_64
|
||||
set GOARCH=386
|
||||
go build -o .\bin\b612_x86 -ldflags "-w -s" .
|
||||
#upx -9 .\bin\b612_x86
|
||||
upx -9 .\bin\b612_x86
|
||||
set GOARCH=arm64
|
||||
go build -o .\bin\b612_aarch64 -ldflags "-w -s" .
|
||||
upx -9 .\bin\b612_aarch64
|
||||
set GOARCH=mips
|
||||
go build -o .\bin\b612_mips -ldflags "-w -s" .
|
||||
upx -9 .\bin\b612_mips
|
||||
set GOARCH=mipsle
|
||||
go build -o .\bin\b612_mipsle -ldflags "-w -s" .
|
||||
upx -9 .\bin\b612_mipsle
|
||||
set GOARCH=mips64
|
||||
go build -o .\bin\b612_mips64 -ldflags "-w -s" .
|
||||
set GOARCH=mips64le
|
||||
|
100
cert/cmd.go
100
cert/cmd.go
@ -1,13 +1,13 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"b612.me/starcrypto"
|
||||
"b612.me/stario"
|
||||
"b612.me/starlog"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@ -26,10 +26,11 @@ var maxPathLen int
|
||||
var caKey string
|
||||
var caCert string
|
||||
var csr string
|
||||
var pubKey string
|
||||
var caKeyPwd string
|
||||
var passwd string
|
||||
var enPasswd string
|
||||
var keyUsage int
|
||||
var extKeyUsage []int
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "cert",
|
||||
@ -66,16 +67,6 @@ var CmdCsr = &cobra.Command{
|
||||
dnsName = stario.MessageBox("请输入dns名称,用逗号分割:", "").MustSliceString(",")
|
||||
}
|
||||
}
|
||||
start, err = time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
starlog.Errorln("开始时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
end, err = time.Parse(time.RFC3339, endStr)
|
||||
if err != nil {
|
||||
starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
key, err := LoadPriv(caKey, caKeyPwd)
|
||||
if err != nil {
|
||||
starlog.Errorln("加载Key错误", err)
|
||||
@ -108,10 +99,6 @@ var CmdGen = &cobra.Command{
|
||||
starlog.Errorln("证书请求不能为空")
|
||||
os.Exit(1)
|
||||
}
|
||||
if pubKey == "" {
|
||||
starlog.Errorln("证书公钥不能为空")
|
||||
os.Exit(1)
|
||||
}
|
||||
var caKeyRaw crypto.PrivateKey
|
||||
var caCertRaw *x509.Certificate
|
||||
var err error
|
||||
@ -133,24 +120,68 @@ var CmdGen = &cobra.Command{
|
||||
starlog.Errorln("加载证书请求错误", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pubKeyByte, err := os.ReadFile(pubKey)
|
||||
start, err = time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
starlog.Errorln("加载公钥错误", err)
|
||||
starlog.Errorln("开始时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pubKeyRaw, err := starcrypto.DecodePublicKey(pubKeyByte)
|
||||
end, err = time.Parse(time.RFC3339, endStr)
|
||||
if err != nil {
|
||||
starlog.Errorln("解析公钥错误", err)
|
||||
starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pubKeyRaw := csrRaw.PublicKey
|
||||
certReq := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(time.Now().UnixNano()),
|
||||
Subject: csrRaw.Subject,
|
||||
IsCA: isCa,
|
||||
NotBefore: start,
|
||||
NotAfter: end,
|
||||
MaxPathLen: maxPathLen,
|
||||
MaxPathLenZero: maxPathLenZero,
|
||||
DNSNames: csrRaw.DNSNames,
|
||||
IPAddresses: csrRaw.IPAddresses,
|
||||
}
|
||||
if !isCa {
|
||||
if keyUsage == 0 {
|
||||
certReq.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
|
||||
}
|
||||
if len(extKeyUsage) == 0 {
|
||||
certReq.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||
}
|
||||
} else {
|
||||
if len(extKeyUsage) == 0 {
|
||||
certReq.ExtKeyUsage = []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageAny,
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
x509.ExtKeyUsageCodeSigning,
|
||||
x509.ExtKeyUsageEmailProtection,
|
||||
x509.ExtKeyUsageIPSECEndSystem,
|
||||
x509.ExtKeyUsageIPSECTunnel,
|
||||
x509.ExtKeyUsageIPSECUser,
|
||||
x509.ExtKeyUsageTimeStamping,
|
||||
x509.ExtKeyUsageOCSPSigning,
|
||||
x509.ExtKeyUsageMicrosoftServerGatedCrypto,
|
||||
x509.ExtKeyUsageNetscapeServerGatedCrypto,
|
||||
x509.ExtKeyUsageMicrosoftCommercialCodeSigning,
|
||||
x509.ExtKeyUsageMicrosoftKernelCodeSigning,
|
||||
}
|
||||
}
|
||||
if keyUsage == 0 {
|
||||
certReq.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature
|
||||
}
|
||||
}
|
||||
if keyUsage != 0 {
|
||||
certReq.KeyUsage = x509.KeyUsage(keyUsage)
|
||||
}
|
||||
if len(extKeyUsage) > 0 {
|
||||
certReq.ExtKeyUsage = make([]x509.ExtKeyUsage, len(extKeyUsage))
|
||||
for i, v := range extKeyUsage {
|
||||
certReq.ExtKeyUsage[i] = x509.ExtKeyUsage(v)
|
||||
}
|
||||
}
|
||||
certReq.Subject.SerialNumber = fmt.Sprint(time.Now().UnixNano())
|
||||
if isCa {
|
||||
caCertRaw = certReq
|
||||
}
|
||||
@ -169,6 +200,15 @@ var CmdGen = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var CmdFastGen = &cobra.Command{
|
||||
Use: "fastgen",
|
||||
Short: "快速生成证书",
|
||||
Long: "快速生成证书",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
var CmdParse = &cobra.Command{
|
||||
Use: "parse",
|
||||
Short: "解析证书",
|
||||
@ -208,11 +248,11 @@ func init() {
|
||||
//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().IntVarP(&keyUsage, "keyUsage", "u", 0, "证书使用类型,默认数字0,0表示数字签名和密钥加密,1表示证书签名,2表示CRL签名,4表示密钥协商,8表示数据加密")
|
||||
CmdGen.Flags().IntSliceVarP(&extKeyUsage, "extKeyUsage", "e", nil, "扩展证书使用类型,默认数字0,0表示服务器认证,1表示客户端认证,2表示代码签名,3表示电子邮件保护,4表示IPSEC终端系统,5表示IPSEC隧道,6表示IPSEC用户,7表示时间戳,8表示OCSP签名,9表示Microsoft服务器网关加密,10表示Netscape服务器网关加密,11表示Microsoft商业代码签名,12表示Microsoft内核代码签名")
|
||||
CmdGen.Flags().StringVarP(&caKey, "caKey", "k", "", "CA私钥")
|
||||
CmdGen.Flags().StringVarP(&caCert, "caCert", "C", "", "CA证书")
|
||||
CmdGen.Flags().StringVarP(&csr, "csr", "r", "", "证书请求")
|
||||
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")
|
||||
@ -248,6 +288,22 @@ func init() {
|
||||
CmdOpenssh.Flags().StringVarP(&enPasswd, "en-passwd", "P", "", "加密密码")
|
||||
Cmd.AddCommand(CmdOpenssh)
|
||||
|
||||
CmdFastGen.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式")
|
||||
CmdFastGen.Flags().StringVarP(&country, "country", "c", "CN", "国家")
|
||||
CmdFastGen.Flags().StringVarP(&province, "province", "p", "B612", "省份")
|
||||
CmdFastGen.Flags().StringVarP(&city, "city", "t", "B612", "城市")
|
||||
CmdFastGen.Flags().StringVarP(&org, "org", "o", "", "组织")
|
||||
CmdFastGen.Flags().StringVarP(&orgUnit, "orgUnit", "u", "", "组织单位")
|
||||
CmdFastGen.Flags().StringVarP(&name, "name", "n", "Starainrt", "通用名称")
|
||||
CmdFastGen.Flags().StringSliceVarP(&dnsName, "dnsName", "d", nil, "dns名称")
|
||||
CmdFastGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
|
||||
CmdFastGen.Flags().IntVarP(&keyUsage, "keyUsage", "U", 0, "证书使用类型,默认数字0,0表示数字签名和密钥加密,1表示证书签名,2表示CRL签名,4表示密钥协商,8表示数据加密")
|
||||
CmdFastGen.Flags().IntSliceVarP(&extKeyUsage, "extKeyUsage", "e", nil, "扩展证书使用类型,默认数字0,0表示服务器认证,1表示客户端认证,2表示代码签名,3表示电子邮件保护,4表示IPSEC终端系统,5表示IPSEC隧道,6表示IPSEC用户,7表示时间戳,8表示OCSP签名,9表示Microsoft服务器网关加密,10表示Netscape服务器网关加密,11表示Microsoft商业代码签名,12表示Microsoft内核代码签名")
|
||||
CmdFastGen.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
|
||||
CmdFastGen.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
|
||||
CmdFastGen.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
|
||||
CmdFastGen.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
|
||||
CmdFastGen.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
|
||||
}
|
||||
|
||||
var CmdPkcs8 = &cobra.Command{
|
||||
|
204
filedate/cmd.go
Normal file
204
filedate/cmd.go
Normal file
@ -0,0 +1,204 @@
|
||||
package filedate
|
||||
|
||||
import (
|
||||
"b612.me/staros"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var aTime, mTime, cTime, allTime string // "2006-01-02 15:04:05" format
|
||||
var fileRegexp string // 正则表达式匹配文件名
|
||||
var recursive bool // 是否递归子目录
|
||||
var dateFromName bool // 是否从文件名中提取日期
|
||||
var dateFromNameReg string // 从文件名提取日期的正则表达式
|
||||
var dateFromNameReplace string // 从文件名提取日期正则替代
|
||||
var dateFromNameFormat string
|
||||
var isTimestamp bool // 文件名是否是时间戳
|
||||
var onlyModifyAccess bool // 只修改访问时间
|
||||
var onlyModifyCreate bool // 只修改创建时间
|
||||
var onlyModifyModify bool // 只修改修改时间
|
||||
var excludeFolder bool // 是否排除目录本身
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "filedate",
|
||||
Short: "修改文件的创建/修改/访问时间",
|
||||
Long: `修改文件的创建/修改/访问时间。
|
||||
可以通过 --access, --modify, --create, --all 来设置不同的时间。
|
||||
例如:
|
||||
b612 filedate file.txt --access "2023-10-01 12:00:00" --modify "2023-10-01 12:00:00"
|
||||
b612 filedate dir/ --all "2023-10-01 12:00:00" -r
|
||||
也可以通过 --regexp 来匹配文件名,或者通过 --date-from-name 来从文件名中提取日期。
|
||||
比如,文件名为IMG_20160711_161625.jpg,,可以通过如下命令来设置时间:
|
||||
b612 filedate -D -y '$1$2' -x 20060102150405 -z '.*?(\d+)_(\d+).+' .\IMG_20160711_161625.jpg
|
||||
`,
|
||||
Run: run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().StringVarP(&aTime, "access", "a", "", "设置访问时间,格式: 2006-01-02 15:04:05")
|
||||
Cmd.Flags().StringVarP(&mTime, "modify", "m", "", "设置修改时间,格式: 2006-01-02 15:04:05")
|
||||
Cmd.Flags().StringVarP(&cTime, "create", "c", "", "设置创建时间,格式: 2006-01-02 15:04:05")
|
||||
Cmd.Flags().StringVarP(&allTime, "all", "l", "", "设置所有时间,格式: 2006-01-02 15:04:05")
|
||||
Cmd.Flags().StringVarP(&fileRegexp, "regexp", "R", "", "正则表达式匹配文件名")
|
||||
Cmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "递归子目录")
|
||||
Cmd.Flags().BoolVarP(&dateFromName, "date-from-name", "D", false, "从文件名中提取日期")
|
||||
Cmd.Flags().StringVarP(&dateFromNameReg, "date-from-name-regex", "z", "", "从文件名提取日期的正则表达式")
|
||||
Cmd.Flags().StringVarP(&dateFromNameReplace, "date-from-name-replace", "y", "", "从文件名提取日期的替换表达式")
|
||||
Cmd.Flags().StringVarP(&dateFromNameFormat, "date-from-name-format", "x", "2006-01-02 15:04:05", "从文件名提取日期的格式")
|
||||
Cmd.Flags().BoolVarP(&isTimestamp, "timestamp", "t", false, "文件名是否是时间戳")
|
||||
Cmd.Flags().BoolVarP(&onlyModifyAccess, "only-modify-access", "A", false, "只修改访问时间")
|
||||
Cmd.Flags().BoolVarP(&onlyModifyCreate, "only-modify-create", "C", false, "只修改创建时间")
|
||||
Cmd.Flags().BoolVarP(&onlyModifyModify, "only-modify-modify", "M", false, "只修改修改时间")
|
||||
Cmd.Flags().BoolVarP(&excludeFolder, "exclude-folder", "e", false, "排除目录本身,不修改目录的时间")
|
||||
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) {
|
||||
var aDate, mDate, cDate, allDate time.Time
|
||||
var err error
|
||||
if aTime != "" && (onlyModifyAccess || !(onlyModifyCreate || onlyModifyModify)) {
|
||||
aDate, err = time.ParseInLocation("2006-01-02 15:04:05", aTime, time.Local)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("解析访问时间失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if mTime != "" && (onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify)) {
|
||||
mDate, err = time.ParseInLocation("2006-01-02 15:04:05", mTime, time.Local)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("解析修改时间失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if cTime != "" && (onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify)) {
|
||||
cDate, err = time.ParseInLocation("2006-01-02 15:04:05", cTime, time.Local)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("解析创建时间失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if allTime != "" {
|
||||
allDate, err = time.ParseInLocation("2006-01-02 15:04:05", allTime, time.Local)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("解析所有时间失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
if mDate.IsZero() && (onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify)) {
|
||||
mDate = allDate
|
||||
}
|
||||
if cDate.IsZero() && (onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify)) {
|
||||
cDate = allDate
|
||||
}
|
||||
if aDate.IsZero() && (onlyModifyAccess || !(onlyModifyCreate || onlyModifyModify)) {
|
||||
aDate = allDate
|
||||
}
|
||||
}
|
||||
var reg, namereg *regexp.Regexp
|
||||
if dateFromName {
|
||||
if dateFromNameReg == "" {
|
||||
cmd.PrintErrf("未指定从文件名提取日期的正则表达式\n")
|
||||
return
|
||||
}
|
||||
if dateFromNameReplace == "" {
|
||||
cmd.PrintErrf("未指定从文件名提取日期的替换表达式\n")
|
||||
return
|
||||
}
|
||||
if dateFromNameFormat == "" {
|
||||
cmd.PrintErrf("未指定从文件名提取日期的格式\n")
|
||||
return
|
||||
}
|
||||
namereg, err = regexp.Compile(dateFromNameReg)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("解析正则表达式失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if fileRegexp != "" {
|
||||
reg, err = regexp.Compile(fileRegexp)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("解析正则表达式失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, path := range args {
|
||||
SetFileTimes(path, reg, namereg, cDate, aDate, mDate)
|
||||
}
|
||||
}
|
||||
|
||||
func SetFileTimes(path string, reg, namereg *regexp.Regexp, ctime, atime, mtime time.Time) error {
|
||||
setFile := func(path string, info os.FileInfo) error {
|
||||
var err error
|
||||
if reg != nil && !reg.MatchString(info.Name()) {
|
||||
return nil // 跳过不匹配的文件
|
||||
}
|
||||
if dateFromName && namereg != nil {
|
||||
date := namereg.ReplaceAllString(filepath.Base(path), dateFromNameReplace)
|
||||
if date == "" {
|
||||
fmt.Printf("从文件名提取日期失败: %s\n", path)
|
||||
return nil // 跳过无法提取日期的文件
|
||||
}
|
||||
var realDate time.Time
|
||||
if !isTimestamp {
|
||||
realDate, err = time.ParseInLocation(dateFromNameFormat, date, time.Local)
|
||||
if err != nil {
|
||||
fmt.Printf("解析文件名日期失败: %s, 错误: %v\n", path, err)
|
||||
return nil // 跳过无法解析日期的文件
|
||||
}
|
||||
} else {
|
||||
tmp, err := strconv.ParseInt(date, 10, 64)
|
||||
if err != nil {
|
||||
fmt.Printf("解析时间戳失败: %s, 错误: %v\n", path, err)
|
||||
return nil // 跳过无法解析时间戳的文件
|
||||
}
|
||||
realDate = time.Unix(tmp, 0)
|
||||
}
|
||||
if onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify) {
|
||||
mtime = realDate
|
||||
}
|
||||
if onlyModifyCreate || !(onlyModifyAccess || onlyModifyModify) {
|
||||
ctime = realDate
|
||||
}
|
||||
if onlyModifyAccess || !(onlyModifyCreate || onlyModifyModify) {
|
||||
atime = realDate
|
||||
}
|
||||
}
|
||||
if err := SetFileTime(path, ctime, atime, mtime); err != nil {
|
||||
return fmt.Errorf("设置文件时间失败: %s, 错误: %v", path, err)
|
||||
}
|
||||
fmt.Printf("已设置文件时间: %s\n", path)
|
||||
return nil
|
||||
}
|
||||
if !staros.Exists(path) {
|
||||
return fmt.Errorf("文件不存在: %s", path)
|
||||
}
|
||||
if staros.IsFile(path) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取文件信息失败: %s, 错误: %v", path, err)
|
||||
}
|
||||
return setFile(path, info)
|
||||
}
|
||||
return filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||
if staros.IsFile(path) {
|
||||
return setFile(path, info)
|
||||
}
|
||||
if staros.IsFolder(path) {
|
||||
if recursive {
|
||||
err = SetFileTimes(path, reg, namereg, ctime, atime, mtime)
|
||||
if err != nil {
|
||||
return fmt.Errorf("递归设置文件时间失败: %s, 错误: %v", path, err)
|
||||
}
|
||||
}
|
||||
if excludeFolder {
|
||||
return nil // 如果排除目录本身,则不处理目录的时间
|
||||
}
|
||||
return setFile(path, info) // 处理目录本身
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
38
filedate/filedate_darwin.go
Normal file
38
filedate/filedate_darwin.go
Normal file
@ -0,0 +1,38 @@
|
||||
//go:build darwin
|
||||
|
||||
package filedate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetFileTime(path string, ctime, atime, mtime time.Time) error {
|
||||
var err error
|
||||
originalAtime := atime
|
||||
originalMtime := mtime
|
||||
|
||||
if atime.IsZero() || mtime.IsZero() {
|
||||
var fi os.FileInfo
|
||||
fi, err = os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
|
||||
// 获取原始时间
|
||||
if atime.IsZero() {
|
||||
originalAtime = fi.ModTime() // macOS 没有访问时间,使用修改时间代替
|
||||
}
|
||||
if mtime.IsZero() {
|
||||
originalMtime = fi.ModTime()
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Chtimes(path, originalAtime, originalMtime)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Chtimes failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
32
filedate/filedate_other.go
Normal file
32
filedate/filedate_other.go
Normal file
@ -0,0 +1,32 @@
|
||||
//go:build !windows && !darwin
|
||||
|
||||
package filedate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/sys/unix"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetFileTime(path string, ctime, atime, mtime time.Time) error {
|
||||
var ts [2]unix.Timespec
|
||||
|
||||
if atime.IsZero() {
|
||||
ts[0].Nsec = unix.UTIME_OMIT
|
||||
} else {
|
||||
ts[0] = unix.NsecToTimespec(atime.UnixNano())
|
||||
}
|
||||
|
||||
if mtime.IsZero() {
|
||||
ts[1].Nsec = unix.UTIME_OMIT
|
||||
} else {
|
||||
ts[1] = unix.NsecToTimespec(mtime.UnixNano())
|
||||
}
|
||||
|
||||
// 使用 AT_FDCWD 表示相对当前工作目录
|
||||
err := unix.UtimesNanoAt(unix.AT_FDCWD, path, ts[:], unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
return fmt.Errorf("utimensat failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
54
filedate/filedate_windows.go
Normal file
54
filedate/filedate_windows.go
Normal file
@ -0,0 +1,54 @@
|
||||
//go:build windows
|
||||
|
||||
package filedate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/sys/windows"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetFileTime(path string, ctime, atime, mtime time.Time) error {
|
||||
path16, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handle, err := windows.CreateFile(
|
||||
path16,
|
||||
windows.FILE_WRITE_ATTRIBUTES,
|
||||
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
||||
nil,
|
||||
windows.OPEN_EXISTING,
|
||||
windows.FILE_FLAG_BACKUP_SEMANTICS,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CreateFile failed: %w", err)
|
||||
}
|
||||
defer windows.CloseHandle(handle)
|
||||
|
||||
var (
|
||||
ctimePtr *windows.Filetime
|
||||
atimePtr *windows.Filetime
|
||||
mtimePtr *windows.Filetime
|
||||
)
|
||||
|
||||
if !ctime.IsZero() {
|
||||
ctimeFt := windows.NsecToFiletime(ctime.UnixNano())
|
||||
ctimePtr = &ctimeFt
|
||||
}
|
||||
if !atime.IsZero() {
|
||||
atimeFt := windows.NsecToFiletime(atime.UnixNano())
|
||||
atimePtr = &atimeFt
|
||||
}
|
||||
if !mtime.IsZero() {
|
||||
mtimeFt := windows.NsecToFiletime(mtime.UnixNano())
|
||||
mtimePtr = &mtimeFt
|
||||
}
|
||||
|
||||
if err := windows.SetFileTime(handle, ctimePtr, atimePtr, mtimePtr); err != nil {
|
||||
return fmt.Errorf("SetFileTime failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
208
ftp/filedriver.go
Normal file
208
ftp/filedriver.go
Normal file
@ -0,0 +1,208 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"goftp.io/server/v2"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
RootPath string
|
||||
}
|
||||
|
||||
// NewDriver implements Driver
|
||||
func NewDriver(rootPath string) (server.Driver, error) {
|
||||
var err error
|
||||
rootPath, err = filepath.Abs(rootPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fi, err := os.Lstat(rootPath); err == nil {
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
realPath, err := filepath.EvalSymlinks(rootPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootPath = realPath
|
||||
fmt.Println("Real path:", rootPath)
|
||||
}
|
||||
}
|
||||
return &Driver{rootPath}, nil
|
||||
}
|
||||
|
||||
func (driver *Driver) realPath(path string) string {
|
||||
paths := strings.Split(path, "/")
|
||||
realPath, _ := filepath.Abs(filepath.Join(append([]string{driver.RootPath}, paths...)...))
|
||||
if strings.HasPrefix(realPath, driver.RootPath) {
|
||||
return realPath
|
||||
}
|
||||
return filepath.Join(driver.RootPath)
|
||||
}
|
||||
|
||||
// Stat implements Driver
|
||||
func (driver *Driver) Stat(ctx *server.Context, path string) (os.FileInfo, error) {
|
||||
basepath := driver.realPath(path)
|
||||
rPath, err := filepath.Abs(basepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Lstat(rPath)
|
||||
}
|
||||
|
||||
// ListDir implements Driver
|
||||
func (driver *Driver) ListDir(ctx *server.Context, path string, callback func(os.FileInfo) error) error {
|
||||
basepath := driver.realPath(path)
|
||||
return filepath.Walk(basepath, func(f string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rPath, _ := filepath.Rel(basepath, f)
|
||||
if rPath == info.Name() {
|
||||
err = callback(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteDir implements Driver
|
||||
func (driver *Driver) DeleteDir(ctx *server.Context, path string) error {
|
||||
rPath := driver.realPath(path)
|
||||
f, err := os.Lstat(rPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.IsDir() {
|
||||
return os.RemoveAll(rPath)
|
||||
}
|
||||
return errors.New("Not a directory")
|
||||
}
|
||||
|
||||
// DeleteFile implements Driver
|
||||
func (driver *Driver) DeleteFile(ctx *server.Context, path string) error {
|
||||
rPath := driver.realPath(path)
|
||||
f, err := os.Lstat(rPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !f.IsDir() {
|
||||
return os.Remove(rPath)
|
||||
}
|
||||
return errors.New("Not a file")
|
||||
}
|
||||
|
||||
// Rename implements Driver
|
||||
func (driver *Driver) Rename(ctx *server.Context, fromPath string, toPath string) error {
|
||||
oldPath := driver.realPath(fromPath)
|
||||
newPath := driver.realPath(toPath)
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
// MakeDir implements Driver
|
||||
func (driver *Driver) MakeDir(ctx *server.Context, path string) error {
|
||||
rPath := driver.realPath(path)
|
||||
return os.MkdirAll(rPath, os.ModePerm)
|
||||
}
|
||||
|
||||
// GetFile implements Driver
|
||||
func (driver *Driver) GetFile(ctx *server.Context, path string, offset int64) (int64, io.ReadCloser, error) {
|
||||
rPath := driver.realPath(path)
|
||||
f, err := os.Open(rPath)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil && f != nil {
|
||||
f.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
_, err = f.Seek(offset, io.SeekStart)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return info.Size() - offset, f, nil
|
||||
}
|
||||
|
||||
// PutFile implements Driver
|
||||
func (driver *Driver) PutFile(ctx *server.Context, destPath string, data io.Reader, offset int64) (int64, error) {
|
||||
rPath := driver.realPath(destPath)
|
||||
var isExist bool
|
||||
f, err := os.Lstat(rPath)
|
||||
if err == nil {
|
||||
isExist = true
|
||||
if f.IsDir() {
|
||||
return 0, errors.New("A dir has the same name")
|
||||
}
|
||||
} else {
|
||||
if os.IsNotExist(err) {
|
||||
isExist = false
|
||||
} else {
|
||||
return 0, errors.New(fmt.Sprintln("Put File error:", err))
|
||||
}
|
||||
}
|
||||
|
||||
if offset > -1 && !isExist {
|
||||
offset = -1
|
||||
}
|
||||
|
||||
if offset == -1 {
|
||||
if isExist {
|
||||
err = os.Remove(rPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
f, err := os.Create(rPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
bytes, err := io.Copy(f, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
of, err := os.OpenFile(rPath, os.O_APPEND|os.O_RDWR, 0660)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer of.Close()
|
||||
|
||||
info, err := of.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if offset > info.Size() {
|
||||
return 0, fmt.Errorf("Offset %d is beyond file size %d", offset, info.Size())
|
||||
}
|
||||
|
||||
_, err = of.Seek(offset, os.SEEK_END)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
bytes, err := io.Copy(of, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
96
ftp/ftp.go
96
ftp/ftp.go
@ -2,16 +2,34 @@ package ftp
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
filedriver "github.com/goftp/file-driver"
|
||||
"github.com/goftp/server"
|
||||
"github.com/spf13/cobra"
|
||||
"goftp.io/server/v2"
|
||||
)
|
||||
|
||||
var ports int
|
||||
var username, pwd string
|
||||
var path, ip string
|
||||
var authuser, authpwd string
|
||||
var path, ip, publicIp string
|
||||
var username, groupname, passiveports string
|
||||
var cert, key string
|
||||
var forceTls bool
|
||||
|
||||
type Auth struct {
|
||||
}
|
||||
|
||||
func (a Auth) CheckPasswd(ctx *server.Context, user string, name string) (bool, error) {
|
||||
if authuser == "" && authpwd == "" {
|
||||
return true, nil
|
||||
}
|
||||
if authuser == user && authpwd == name {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ftpCmd represents the ftp command
|
||||
var Cmd = &cobra.Command{
|
||||
@ -19,21 +37,56 @@ var Cmd = &cobra.Command{
|
||||
Short: `FTP文件服务器`,
|
||||
Long: `FTP文件服务器`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
path, _ = filepath.Abs(path)
|
||||
factory := &filedriver.FileDriverFactory{
|
||||
RootPath: path,
|
||||
Perm: server.NewSimplePerm("user", "group"),
|
||||
if username == "" || groupname == "" {
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
username = u.Username
|
||||
groupname = u.Username
|
||||
} else {
|
||||
username = "root"
|
||||
groupname = "root"
|
||||
}
|
||||
}
|
||||
opts := &server.ServerOpts{
|
||||
Factory: factory,
|
||||
Port: ports,
|
||||
Hostname: ip,
|
||||
Auth: &server.SimpleAuth{Name: username, Password: pwd},
|
||||
if publicIp == "" {
|
||||
if tmp, err := net.Dial("udp", "139.199.163.65:443"); err == nil {
|
||||
publicIp = strings.Split(tmp.LocalAddr().String(), ":")[0]
|
||||
tmp.Close()
|
||||
}
|
||||
}
|
||||
path, _ = filepath.Abs(path)
|
||||
driver, err := NewDriver(path)
|
||||
if err != nil {
|
||||
log.Fatal("Error starting server:", err)
|
||||
}
|
||||
opts := &server.Options{
|
||||
Auth: Auth{},
|
||||
Driver: driver,
|
||||
Perm: server.NewSimplePerm(username, groupname),
|
||||
Name: "B612 FTP Server",
|
||||
Hostname: ip,
|
||||
PublicIP: publicIp,
|
||||
PassivePorts: passiveports,
|
||||
Port: ports,
|
||||
WelcomeMessage: "B612 FTP Server",
|
||||
Logger: nil,
|
||||
RateLimit: 0,
|
||||
TLS: key != "" && cert != "",
|
||||
ForceTLS: forceTls,
|
||||
CertFile: cert,
|
||||
KeyFile: key,
|
||||
}
|
||||
log.Printf("Starting ftp server on %v:%v", opts.Hostname, opts.Port)
|
||||
log.Printf("Username %v, Password %v", username, pwd)
|
||||
server := server.NewServer(opts)
|
||||
err := server.ListenAndServe()
|
||||
log.Printf("AuthUser %v, Password %v", authuser, authpwd)
|
||||
log.Printf("Public IP %v", publicIp)
|
||||
log.Printf("Path %v", path)
|
||||
log.Printf("Passive Ports %v", passiveports)
|
||||
log.Printf("TLS %v", opts.TLS)
|
||||
log.Printf("Username %v, Groupname %v", username, groupname)
|
||||
server, err := server.NewServer(opts)
|
||||
if err != nil {
|
||||
log.Fatal("Error starting server:", err)
|
||||
}
|
||||
err = server.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Fatal("Error starting server:", err)
|
||||
}
|
||||
@ -41,9 +94,16 @@ var Cmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().StringVarP(&publicIp, "public-ip", "I", "", "公网IP")
|
||||
Cmd.Flags().StringVarP(&username, "username", "U", "", "FTP用户名")
|
||||
Cmd.Flags().StringVarP(&groupname, "groupname", "G", "", "FTP组名")
|
||||
Cmd.Flags().StringVarP(&passiveports, "passiveports", "P", "50000-60000", "被动模式端口范围,格式:50000-60000")
|
||||
Cmd.Flags().IntVarP(&ports, "port", "p", 21, "监听端口")
|
||||
Cmd.Flags().StringVarP(&ip, "ip", "i", "0.0.0.0", "监听地址")
|
||||
Cmd.Flags().StringVarP(&username, "user", "u", "1", "用户名,默认为1")
|
||||
Cmd.Flags().StringVarP(&pwd, "pwd", "k", "1", "密码,默认为1")
|
||||
Cmd.Flags().StringVarP(&authuser, "auth-user", "u", "", "用户名,默认为任意")
|
||||
Cmd.Flags().StringVarP(&authpwd, "auth-passwd", "k", "", "密码,默认为任意")
|
||||
Cmd.Flags().StringVarP(&path, "folder", "f", "./", "本地文件地址")
|
||||
Cmd.Flags().BoolVarP(&forceTls, "force-tls", "t", false, "强制使用TLS加密传输")
|
||||
Cmd.Flags().StringVarP(&cert, "cert", "C", "", "TLS证书文件")
|
||||
Cmd.Flags().StringVarP(&key, "key", "K", "", "TLS密钥文件")
|
||||
}
|
||||
|
36
go.mod
36
go.mod
@ -1,32 +1,36 @@
|
||||
module b612.me/apps/b612
|
||||
|
||||
go 1.22.4
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.10
|
||||
|
||||
require (
|
||||
b612.me/astro v0.0.4
|
||||
b612.me/bcap v0.0.4
|
||||
b612.me/exif v0.0.0-20250609055701-2bc6bd257a4f
|
||||
b612.me/notify v0.0.0-20240818092352-85803f75dfa0
|
||||
b612.me/sdk/whois v0.0.0-20240816133027-129514a15991
|
||||
b612.me/starcrypto v0.0.5
|
||||
b612.me/stario v0.0.10
|
||||
b612.me/starlog v1.3.4
|
||||
b612.me/starmap v0.0.0-20240818092703-ae61140c5062
|
||||
b612.me/starnet v0.2.1
|
||||
b612.me/starnet v0.0.0-20250612085047-7a1767214960
|
||||
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.4
|
||||
github.com/anthonynsimon/bild v0.14.0
|
||||
github.com/dgraph-io/badger/v3 v3.2103.2
|
||||
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
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
|
||||
github.com/emersion/go-smtp v0.22.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.9
|
||||
github.com/gdamore/tcell v1.4.0
|
||||
github.com/gdamore/tcell/v2 v2.7.1
|
||||
github.com/go-acme/lego/v4 v4.16.1
|
||||
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/google/gopacket v1.1.19
|
||||
github.com/h2non/filetype v1.1.3
|
||||
@ -48,11 +52,12 @@ require (
|
||||
github.com/tobert/pcstat v0.0.2
|
||||
github.com/ulikunitz/xz v0.5.12
|
||||
github.com/vbauerster/mpb/v8 v8.8.3
|
||||
golang.org/x/crypto v0.32.0
|
||||
goftp.io/server/v2 v2.0.1
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3
|
||||
golang.org/x/net v0.32.0
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/term v0.28.0
|
||||
golang.org/x/net v0.39.0
|
||||
golang.org/x/sys v0.32.0
|
||||
golang.org/x/term v0.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0
|
||||
)
|
||||
@ -75,15 +80,18 @@ require (
|
||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||
github.com/gdamore/encoding v1.0.1 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
@ -94,7 +102,6 @@ require (
|
||||
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/josharian/native v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
@ -120,12 +127,13 @@ require (
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/image v0.6.0 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
115
go.sum
115
go.sum
@ -2,6 +2,8 @@ b612.me/astro v0.0.4 h1:i60vJ3Dq+9U16ryMqmnjE0uXB28poRBx8boIMbP8VH8=
|
||||
b612.me/astro v0.0.4/go.mod h1:yC1arIa79Q5ztQgWphj5mX5UvM/gddwTP+jGneojPt4=
|
||||
b612.me/bcap v0.0.4 h1:iY2Oz+uyG/mue6a/dJiU82ci5Xwkj4xHhre/q0O8G60=
|
||||
b612.me/bcap v0.0.4/go.mod h1:tpus+4iMpsnxb98Pck70s87Zt4sIWuRZjK23MmmzmoY=
|
||||
b612.me/exif v0.0.0-20250609055701-2bc6bd257a4f h1:lzmq/A9fvE5xMqEKIsVJQo3RzOUawTd4Ny6pZpWNL5A=
|
||||
b612.me/exif v0.0.0-20250609055701-2bc6bd257a4f/go.mod h1:ig9W4FPsYf3ZfAfehinro0qNLfV/RNyKO6xF92ku5N0=
|
||||
b612.me/notify v0.0.0-20240818092352-85803f75dfa0 h1:PaLuNLMM0HBM7qPdk4igtNvqT2CDgdm52sL+KA9I3so=
|
||||
b612.me/notify v0.0.0-20240818092352-85803f75dfa0/go.mod h1:YyaYF4jj5ygIC/QbCMyhBWnsmmTL8kVs6UlHMGVKiY8=
|
||||
b612.me/sdk/whois v0.0.0-20240816133027-129514a15991 h1:+eXeVqkoi4s9sNoY9eGt4ieSbr1+Deos8fC8wMfOCNI=
|
||||
@ -15,8 +17,8 @@ b612.me/starlog v1.3.4 h1:XuVYo6NCij8F4TGSgtEuMhs1WkZ7HZNnYUgQ3nLTt84=
|
||||
b612.me/starlog v1.3.4/go.mod h1:37GMgkWQMOAjzKs49Hf2i8bLwdXbd9QF4zKhUxFDoSk=
|
||||
b612.me/starmap v0.0.0-20240818092703-ae61140c5062 h1:ImKEWAxzBYsS/YbqdVOPdUdv6b+i/lSGpipUGueXk7w=
|
||||
b612.me/starmap v0.0.0-20240818092703-ae61140c5062/go.mod h1:PhtO9wFrwPIHpry2CEdnVNZkrNOgfv77xrE0ZKQDkLM=
|
||||
b612.me/starnet v0.2.1 h1:17n3wa2QgBYbO1rqDLAhyc2DfvbBc23GSp1v42Pvmiw=
|
||||
b612.me/starnet v0.2.1/go.mod h1:6q+AXhYeXsIiKV+hZZmqAMn8S48QcdonURJyH66rbzI=
|
||||
b612.me/starnet v0.0.0-20250612085047-7a1767214960 h1:LRB59HvHW6D4sAKd/P2GDgmnGyx5DM8aSGnJoEifeI0=
|
||||
b612.me/starnet v0.0.0-20250612085047-7a1767214960/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=
|
||||
@ -54,6 +56,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
|
||||
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/anthonynsimon/bild v0.14.0 h1:IFRkmKdNdqmexXHfEU7rPlAmdUZ8BDZEGtGHDnGWync=
|
||||
github.com/anthonynsimon/bild v0.14.0/go.mod h1:hcvEAyBjTW69qkKJTfpcDQ83sSZHxwOunsseDfeQhUs=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
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=
|
||||
@ -84,6 +88,21 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
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/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0=
|
||||
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
@ -92,10 +111,10 @@ github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9P
|
||||
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=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
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/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.22.0 h1:/d3HWxkZZ4riB+0kzfoODh9X+xyCrLEezMnAAa1LEMU=
|
||||
github.com/emersion/go-smtp v0.22.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -105,6 +124,8 @@ github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4Nij
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0 h1:NTCxS9b0GSbHkWv1a7oOvZn679fsyDkaSkRvOYpQ9Oo=
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0/go.mod h1:M2tBLIj62QpwqjwV0qfcjqGOqP3qiTuXr2uSRBXH9Qk=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
@ -114,16 +135,17 @@ github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQb
|
||||
github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||
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-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
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/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
@ -131,6 +153,10 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
|
||||
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/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -175,17 +201,14 @@ github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
@ -194,8 +217,9 @@ github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFck
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
|
||||
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=
|
||||
@ -206,10 +230,12 @@ github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2C
|
||||
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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@ -243,6 +269,8 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
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/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -280,8 +308,11 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM=
|
||||
github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
@ -296,6 +327,7 @@ 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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
@ -333,8 +365,12 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE=
|
||||
goftp.io/server/v2 v2.0.1/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
@ -345,13 +381,13 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
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/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
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/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@ -370,20 +406,27 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
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.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.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -398,19 +441,23 @@ 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/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/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=
|
||||
@ -418,7 +465,9 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -429,8 +478,8 @@ 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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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=
|
||||
@ -441,27 +490,28 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
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/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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.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/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
@ -506,11 +556,14 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/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=
|
||||
|
@ -3,6 +3,8 @@ addr=0.0.0.0
|
||||
port=9999
|
||||
key=C:\\tech\b612.me.key
|
||||
cert=C:\\tech\b612.me.cer
|
||||
tlsallowhttp=false
|
||||
autogencert=false
|
||||
enablessl=true
|
||||
reverse=/::https://www.b612.me
|
||||
replace=www.b612.me::127.0.0.1:9999
|
||||
|
@ -17,30 +17,32 @@ import (
|
||||
)
|
||||
|
||||
type ReverseConfig struct {
|
||||
Name string
|
||||
Addr string
|
||||
ReverseURL map[string]*url.URL
|
||||
Port int
|
||||
UsingSSL bool
|
||||
Key string
|
||||
Cert string
|
||||
Host string
|
||||
SkipSSLVerify bool
|
||||
InHeader [][2]string
|
||||
OutHeader [][2]string
|
||||
Cookie [][3]string //[3]string should contains path::key::value
|
||||
ReplaceList [][2]string
|
||||
ReplaceOnce bool
|
||||
proxy map[string]*rp.ReverseProxy
|
||||
IPFilterMode int //0=off 1=useremote 2=add 3=filter
|
||||
FilterXForward bool
|
||||
FilterRemoteAddr bool
|
||||
FilterMustKey string
|
||||
FilterSetKey string
|
||||
FilterFile string
|
||||
httpmux http.ServeMux
|
||||
httpserver http.Server
|
||||
CIDR []*net.IPNet
|
||||
Name string
|
||||
Addr string
|
||||
ReverseURL map[string]*url.URL
|
||||
Port int
|
||||
UsingSSL bool
|
||||
AllowHTTPWithHttps bool
|
||||
AutoGenerateCert bool
|
||||
Key string
|
||||
Cert string
|
||||
Host string
|
||||
SkipSSLVerify bool
|
||||
InHeader [][2]string
|
||||
OutHeader [][2]string
|
||||
Cookie [][3]string //[3]string should contains path::key::value
|
||||
ReplaceList [][2]string
|
||||
ReplaceOnce bool
|
||||
proxy map[string]*rp.ReverseProxy
|
||||
IPFilterMode int //0=off 1=useremote 2=add 3=filter
|
||||
FilterXForward bool
|
||||
FilterRemoteAddr bool
|
||||
FilterMustKey string
|
||||
FilterSetKey string
|
||||
FilterFile string
|
||||
httpmux http.ServeMux
|
||||
httpserver http.Server
|
||||
CIDR []*net.IPNet
|
||||
|
||||
basicAuthUser string
|
||||
basicAuthPwd string
|
||||
@ -64,23 +66,25 @@ func Parse(path string) (HttpReverseServer, error) {
|
||||
}
|
||||
for _, v := range ini.Data {
|
||||
var ins = ReverseConfig{
|
||||
Name: v.Name,
|
||||
Host: v.Get("host"),
|
||||
Addr: v.Get("addr"),
|
||||
Port: v.Int("port"),
|
||||
UsingSSL: v.Bool("enablessl"),
|
||||
Key: v.Get("key"),
|
||||
Cert: v.Get("cert"),
|
||||
ReplaceOnce: v.Bool("replaceonce"),
|
||||
IPFilterMode: v.Int("ipfiltermode"),
|
||||
FilterXForward: v.Bool("filterxforward"),
|
||||
FilterRemoteAddr: v.Bool("filterremoteaddr"),
|
||||
FilterMustKey: v.Get("filtermustkey"),
|
||||
FilterSetKey: v.Get("filtersetkey"),
|
||||
FilterFile: v.Get("filterfile"),
|
||||
basicAuthUser: v.Get("authuser"),
|
||||
basicAuthPwd: v.Get("authpasswd"),
|
||||
warningpage: v.Get("warnpage"),
|
||||
Name: v.Name,
|
||||
Host: v.Get("host"),
|
||||
Addr: v.Get("addr"),
|
||||
Port: v.Int("port"),
|
||||
UsingSSL: v.Bool("enablessl"),
|
||||
AllowHTTPWithHttps: v.Bool("tlsallowhttp"),
|
||||
AutoGenerateCert: v.Bool("autogencert"),
|
||||
Key: v.Get("key"),
|
||||
Cert: v.Get("cert"),
|
||||
ReplaceOnce: v.Bool("replaceonce"),
|
||||
IPFilterMode: v.Int("ipfiltermode"),
|
||||
FilterXForward: v.Bool("filterxforward"),
|
||||
FilterRemoteAddr: v.Bool("filterremoteaddr"),
|
||||
FilterMustKey: v.Get("filtermustkey"),
|
||||
FilterSetKey: v.Get("filtersetkey"),
|
||||
FilterFile: v.Get("filterfile"),
|
||||
basicAuthUser: v.Get("authuser"),
|
||||
basicAuthPwd: v.Get("authpasswd"),
|
||||
warningpage: v.Get("warnpage"),
|
||||
}
|
||||
if ins.IPFilterMode == 3 && ins.FilterFile != "" {
|
||||
starlog.Infoln("IP Filter Mode 3, Load IP Filter File", ins.FilterFile)
|
||||
|
@ -2,10 +2,13 @@ package httpreverse
|
||||
|
||||
import (
|
||||
"b612.me/apps/b612/httpreverse/rp"
|
||||
"b612.me/apps/b612/utils"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starnet"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@ -58,18 +61,75 @@ func (h *ReverseConfig) Run() error {
|
||||
Handler: &h.httpmux,
|
||||
}
|
||||
starlog.Infoln(h.Name + " Listening on " + h.Addr + ":" + strconv.Itoa(h.Port))
|
||||
if !h.UsingSSL {
|
||||
if !h.UsingSSL && !h.AutoGenerateCert {
|
||||
if err := h.httpserver.ListenAndServe(); err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if h.httpserver.ListenAndServeTLS(h.Cert, h.Key); err != http.ErrServerClosed {
|
||||
|
||||
if !h.AllowHTTPWithHttps && !h.AutoGenerateCert {
|
||||
if h.httpserver.ListenAndServeTLS(h.Cert, h.Key); err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var lis net.Listener
|
||||
if !h.AutoGenerateCert {
|
||||
lis, err = starnet.ListenTLS("tcp", fmt.Sprintf("%s:%d", h.Addr, h.Port), h.Cert, h.Key, h.AllowHTTPWithHttps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
lis, err = starnet.ListenTLSWithConfig("tcp", fmt.Sprintf("%s:%d", h.Addr, h.Port), &tls.Config{}, autoGenCert, h.AllowHTTPWithHttps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer lis.Close()
|
||||
if err := h.httpserver.Serve(lis); err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var certCache = make(map[string]tls.Certificate)
|
||||
var toolCa *x509.Certificate
|
||||
var toolCaKey any
|
||||
|
||||
func autoGenCert(hostname string) *tls.Config {
|
||||
if cert, ok := certCache[hostname]; ok {
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
if toolCa == nil {
|
||||
toolCa, toolCaKey = utils.ToolCert()
|
||||
}
|
||||
cert, err := utils.GenerateTlsCert(utils.GenerateCertParams{
|
||||
Country: "CN",
|
||||
Organization: "B612 HTTP SERVER",
|
||||
OrganizationUnit: "cert@b612.me",
|
||||
CommonName: hostname,
|
||||
Dns: []string{hostname},
|
||||
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign),
|
||||
ExtendedKeyUsage: []int{
|
||||
int(x509.ExtKeyUsageServerAuth),
|
||||
int(x509.ExtKeyUsageClientAuth),
|
||||
},
|
||||
IsCA: false,
|
||||
StartDate: time.Now().Add(-24 * time.Hour),
|
||||
EndDate: time.Now().AddDate(1, 0, 0),
|
||||
Type: "RSA",
|
||||
Bits: 2048,
|
||||
CA: toolCa,
|
||||
CAPriv: toolCaKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
certCache[hostname] = cert
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
func (h *ReverseConfig) Close() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
@ -42,6 +42,8 @@ func init() {
|
||||
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().BoolVarP(&s.allowHttpWithHttps, "allow-http-with-https", "A", false, "同一个端口允许在HTTPS下使用HTTP协议")
|
||||
Cmd.Flags().BoolVarP(&s.autoGenCert, "autogen-cert", "G", false, "自动生成证书,此时使用--ssl-cert和--ssl-key参数无效")
|
||||
|
||||
Cmd.Flags().Bool("daeapplied", false, "")
|
||||
Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址")
|
||||
|
@ -1,6 +1,9 @@
|
||||
package httpserver
|
||||
|
||||
import "path/filepath"
|
||||
import (
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (h *HttpServer) FileType(name string) string {
|
||||
ext := filepath.Ext(name)
|
||||
@ -224,8 +227,15 @@ func (h *HttpServer) GetExt(fullpath string) string {
|
||||
}
|
||||
return ext[1:]
|
||||
}
|
||||
|
||||
func (h *HttpServer) MIME(fullpath string) string {
|
||||
mime, err := mimetype.DetectFile(fullpath)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return mime.String()
|
||||
}
|
||||
|
||||
func (h *HttpServer) OldMIME(fullpath string) string {
|
||||
ext := filepath.Ext(filepath.Base(fullpath))
|
||||
if len(ext) == 0 || ext == "." {
|
||||
return ""
|
||||
|
@ -1,12 +1,15 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"b612.me/apps/b612/utils"
|
||||
"b612.me/apps/b612/version"
|
||||
"b612.me/starcrypto"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starnet"
|
||||
"b612.me/staros"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@ -30,27 +33,29 @@ var ver = version.Version
|
||||
type HttpServerCfgs func(cfg *HttpServerCfg)
|
||||
|
||||
type HttpServerCfg struct {
|
||||
basicAuthUser string
|
||||
basicAuthPwd string
|
||||
envPath string
|
||||
uploadFolder string
|
||||
logpath string
|
||||
indexFile string
|
||||
cert string
|
||||
key string
|
||||
addr string
|
||||
port string
|
||||
page404 string
|
||||
page403 string
|
||||
page401 string
|
||||
protectAuthPage []string
|
||||
disableMIME bool
|
||||
ctx context.Context
|
||||
hooks []ServerHook
|
||||
httpDebug bool
|
||||
noListPath []string
|
||||
listPwd map[string]string
|
||||
listSameForFile bool
|
||||
basicAuthUser string
|
||||
basicAuthPwd string
|
||||
envPath string
|
||||
uploadFolder string
|
||||
logpath string
|
||||
indexFile string
|
||||
cert string
|
||||
key string
|
||||
allowHttpWithHttps bool
|
||||
autoGenCert bool
|
||||
addr string
|
||||
port string
|
||||
page404 string
|
||||
page403 string
|
||||
page401 string
|
||||
protectAuthPage []string
|
||||
disableMIME bool
|
||||
ctx context.Context
|
||||
hooks []ServerHook
|
||||
httpDebug bool
|
||||
noListPath []string
|
||||
listPwd map[string]string
|
||||
listSameForFile bool
|
||||
// speed limit means xx bytes/s
|
||||
speedlimit uint64
|
||||
|
||||
@ -157,25 +162,81 @@ func (h *HttpServer) Run(ctx context.Context) error {
|
||||
uconn, err := net.Dial("udp", "106.55.44.79:80")
|
||||
if err == nil {
|
||||
schema := "http://"
|
||||
if h.cert != "" {
|
||||
if h.cert != "" || h.autoGenCert {
|
||||
schema = "https://"
|
||||
}
|
||||
starlog.Infof("Visit: %s%s:%s\n", schema, uconn.LocalAddr().(*net.UDPAddr).IP.String(), h.port)
|
||||
uconn.Close()
|
||||
}
|
||||
starlog.Infoln("Listening on " + h.addr + ":" + h.port)
|
||||
if h.cert == "" {
|
||||
if h.cert == "" && !h.autoGenCert {
|
||||
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := server.ListenAndServeTLS(h.cert, h.key); err != http.ErrServerClosed {
|
||||
if !h.allowHttpWithHttps && !h.autoGenCert {
|
||||
if err := server.ListenAndServeTLS(h.cert, h.key); err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var lis net.Listener
|
||||
if !h.autoGenCert {
|
||||
lis, err = starnet.ListenTLS("tcp", h.addr+":"+h.port, h.cert, h.key, h.allowHttpWithHttps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
lis, err = starnet.ListenTLSWithConfig("tcp", h.addr+":"+h.port, &tls.Config{}, autoGenCert, h.allowHttpWithHttps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer lis.Close()
|
||||
if err := server.Serve(lis); err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var certCache = make(map[string]tls.Certificate)
|
||||
var toolCa *x509.Certificate
|
||||
var toolCaKey any
|
||||
|
||||
func autoGenCert(hostname string) *tls.Config {
|
||||
if cert, ok := certCache[hostname]; ok {
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
if toolCa == nil {
|
||||
toolCa, toolCaKey = utils.ToolCert()
|
||||
}
|
||||
cert, err := utils.GenerateTlsCert(utils.GenerateCertParams{
|
||||
Country: "CN",
|
||||
Organization: "B612 HTTP SERVER",
|
||||
OrganizationUnit: "cert@b612.me",
|
||||
CommonName: hostname,
|
||||
Dns: []string{hostname},
|
||||
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign),
|
||||
ExtendedKeyUsage: []int{
|
||||
int(x509.ExtKeyUsageServerAuth),
|
||||
int(x509.ExtKeyUsageClientAuth),
|
||||
},
|
||||
IsCA: false,
|
||||
StartDate: time.Now().Add(-24 * time.Hour),
|
||||
EndDate: time.Now().AddDate(1, 0, 0),
|
||||
Type: "RSA",
|
||||
Bits: 2048,
|
||||
CA: toolCa,
|
||||
CAPriv: toolCaKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
certCache[hostname] = cert
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
func (h *HttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.Listen(w, r)
|
||||
}
|
||||
|
134
image/bild/adjust.go
Normal file
134
image/bild/adjust.go
Normal file
@ -0,0 +1,134 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/adjust"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func brightness() *cobra.Command {
|
||||
var change float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "brightness",
|
||||
Short: "调整图像的相对亮度",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "brightness --change 0.5 input.jpg output.jpg",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return adjust.Brightness(img, change), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&change, "change", "c", 0, "adjust change")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func contrast() *cobra.Command {
|
||||
var change float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "contrast",
|
||||
Short: "调整图像的相对对比度",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "contrast --change 0.5 input.jpg output.jpg",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return adjust.Contrast(img, change), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&change, "change", "c", 0, "adjust change")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func gamma() *cobra.Command {
|
||||
var change float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "gamma",
|
||||
Short: "adjust the gamma of an image",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "gamma --gamma 1.1 input.jpg output.jpg",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return adjust.Gamma(img, change), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&change, "gamma", "g", 0, "set the gamma of the image")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func hue() *cobra.Command {
|
||||
var change int
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "hue",
|
||||
Short: "adjust the hue of an image",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "hue --change 170 input.jpg output.jpg",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return adjust.Hue(img, change), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().IntVarP(&change, "change", "c", 0, "adjust change")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func saturation() *cobra.Command {
|
||||
var change float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "saturation",
|
||||
Short: "adjust the saturation of an image",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "saturation --change 170 input.jpg output.jpg",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return adjust.Saturation(img, change), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&change, "change", "c", 0, "adjust change")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createAdjust() *cobra.Command {
|
||||
adjustCmd := &cobra.Command{
|
||||
Use: "adjust",
|
||||
Short: "adjust basic image features like brightness or contrast",
|
||||
}
|
||||
|
||||
adjustCmd.AddCommand(brightness())
|
||||
adjustCmd.AddCommand(contrast())
|
||||
adjustCmd.AddCommand(gamma())
|
||||
adjustCmd.AddCommand(hue())
|
||||
adjustCmd.AddCommand(saturation())
|
||||
|
||||
return adjustCmd
|
||||
}
|
95
image/bild/blend.go
Normal file
95
image/bild/blend.go
Normal file
@ -0,0 +1,95 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/blend"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func buildBlendModeCommand(name string) *cobra.Command {
|
||||
var strength float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: name,
|
||||
Args: cobra.ExactArgs(3),
|
||||
Example: fmt.Sprintf("%s --strength 0.5 input1.jpg input2.jpg output.jpg", name),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin1 := args[0]
|
||||
fin2 := args[1]
|
||||
fout := args[2]
|
||||
|
||||
apply2(fin1, fin2, fout, func(img1, img2 image.Image) (image.Image, error) {
|
||||
switch name {
|
||||
case "add":
|
||||
return blend.Opacity(img1, blend.Add(img1, img2), strength), nil
|
||||
case "colorburn":
|
||||
return blend.Opacity(img1, blend.ColorBurn(img1, img2), strength), nil
|
||||
case "colordodge":
|
||||
return blend.Opacity(img1, blend.ColorDodge(img1, img2), strength), nil
|
||||
case "darken":
|
||||
return blend.Opacity(img1, blend.Darken(img1, img2), strength), nil
|
||||
case "difference":
|
||||
return blend.Opacity(img1, blend.Difference(img1, img2), strength), nil
|
||||
case "divide":
|
||||
return blend.Opacity(img1, blend.Divide(img1, img2), strength), nil
|
||||
case "exclusion":
|
||||
return blend.Opacity(img1, blend.Exclusion(img1, img2), strength), nil
|
||||
case "lighten":
|
||||
return blend.Opacity(img1, blend.Lighten(img1, img2), strength), nil
|
||||
case "linearburn":
|
||||
return blend.Opacity(img1, blend.LinearBurn(img1, img2), strength), nil
|
||||
case "linearLight":
|
||||
return blend.Opacity(img1, blend.LinearLight(img1, img2), strength), nil
|
||||
case "multiply":
|
||||
return blend.Opacity(img1, blend.Multiply(img1, img2), strength), nil
|
||||
case "normal":
|
||||
return blend.Opacity(img1, blend.Normal(img1, img2), strength), nil
|
||||
case "opacity":
|
||||
return blend.Opacity(img1, img2, strength), nil
|
||||
case "overlay":
|
||||
return blend.Opacity(img1, blend.Overlay(img1, img2), strength), nil
|
||||
case "screen":
|
||||
return blend.Opacity(img1, blend.Screen(img1, img2), strength), nil
|
||||
case "softlight":
|
||||
return blend.Opacity(img1, blend.SoftLight(img1, img2), strength), nil
|
||||
case "subtract":
|
||||
return blend.Opacity(img1, blend.Subtract(img1, img2), strength), nil
|
||||
}
|
||||
return blend.Opacity(img1, blend.Add(img1, img2), strength), nil
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Float64VarP(&strength, "strength", "s", 1.0, "blend mode strength")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createBlend() *cobra.Command {
|
||||
blendCmd := &cobra.Command{
|
||||
Use: "blend",
|
||||
Short: "blend two images together",
|
||||
}
|
||||
|
||||
blendCmd.AddCommand(buildBlendModeCommand("add"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("colorburn"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("colordodge"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("darken"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("difference"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("divide"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("exclusion"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("lighten"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("linearburn"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("linearLight"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("multiply"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("normal"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("opacity"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("overlay"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("screen"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("softlight"))
|
||||
blendCmd.AddCommand(buildBlendModeCommand("subtract"))
|
||||
|
||||
return blendCmd
|
||||
}
|
64
image/bild/blur.go
Normal file
64
image/bild/blur.go
Normal file
@ -0,0 +1,64 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/blur"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func box() *cobra.Command {
|
||||
var radius float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "box",
|
||||
Short: "apply box blur to an input image",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "box --radius 0.5 input.jpg output.jpg",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return blur.Box(img, radius), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&radius, "radius", "r", 0, "the blur's radius")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func gaussian() *cobra.Command {
|
||||
var radius float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "gaussian",
|
||||
Short: "apply gaussian blur to an input image",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "gaussian --radius 0.5 input.jpg output.jpg",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return blur.Gaussian(img, radius), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&radius, "radius", "r", 0, "the blur's radius")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createBlur() *cobra.Command {
|
||||
var blurCmd = &cobra.Command{
|
||||
Use: "blur",
|
||||
Short: "blur an image using the specified method",
|
||||
}
|
||||
|
||||
blurCmd.AddCommand(box())
|
||||
blurCmd.AddCommand(gaussian())
|
||||
|
||||
return blurCmd
|
||||
}
|
60
image/bild/channel.go
Normal file
60
image/bild/channel.go
Normal file
@ -0,0 +1,60 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/channel"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func extractChannel() *cobra.Command {
|
||||
var channels string
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "extract",
|
||||
Short: "extracts RGBA channels from an input image",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "extract --channels rba input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
onlyChannels := []channel.Channel{}
|
||||
for _, c := range channels {
|
||||
switch c {
|
||||
case 'r':
|
||||
onlyChannels = append(onlyChannels, channel.Red)
|
||||
case 'g':
|
||||
onlyChannels = append(onlyChannels, channel.Green)
|
||||
case 'b':
|
||||
onlyChannels = append(onlyChannels, channel.Blue)
|
||||
case 'a':
|
||||
onlyChannels = append(onlyChannels, channel.Alpha)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown channel alias '%c'", c)
|
||||
}
|
||||
}
|
||||
|
||||
result := channel.ExtractMultiple(img, onlyChannels...)
|
||||
return result, nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().StringVarP(&channels, "channels", "c", "rgba", "the channels to include in the histogram")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createChannel() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "channel",
|
||||
Short: "channel operations on images",
|
||||
}
|
||||
|
||||
cmd.AddCommand(extractChannel())
|
||||
|
||||
return cmd
|
||||
}
|
257
image/bild/effect.go
Normal file
257
image/bild/effect.go
Normal file
@ -0,0 +1,257 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/effect"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func grayscale() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "grayscale",
|
||||
Aliases: []string{"gray"},
|
||||
Short: "applies the grayscale effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "grayscale input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Grayscale(img), nil
|
||||
})
|
||||
}}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func sepia() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "sepia",
|
||||
Short: "applies the sepia effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "sepia input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Sepia(img), nil
|
||||
})
|
||||
}}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func sharpen() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "sharpen",
|
||||
Aliases: []string{"sharp"},
|
||||
Short: "applies the sharpen effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "sharpen input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Sharpen(img), nil
|
||||
})
|
||||
}}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func sobel() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "sobel",
|
||||
Short: "applies the sobel effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "sobel input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Sobel(img), nil
|
||||
})
|
||||
}}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func invert() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "invert",
|
||||
Short: "applies the invert effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "invert input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Invert(img), nil
|
||||
})
|
||||
}}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func median() *cobra.Command {
|
||||
var radius float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "median",
|
||||
Short: "applies the median effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "median --radius 2.5 input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Median(img, radius), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&radius, "radius", "r", 3, "the effect's radius")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func erode() *cobra.Command {
|
||||
var radius float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "erode",
|
||||
Short: "applies the erode effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "erode --radius 0.5 input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Erode(img, radius), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&radius, "radius", "r", 0.5, "the effect's radius")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func dilate() *cobra.Command {
|
||||
var radius float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "dilate",
|
||||
Short: "applies the dilate effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "dilate --radius 0.5 input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Dilate(img, radius), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&radius, "radius", "r", 0.5, "the effect's radius")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func edgedetection() *cobra.Command {
|
||||
var radius float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "edgedetection",
|
||||
Short: "applies the edgedetection effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "edgedetection --radius 0.5 input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.EdgeDetection(img, radius), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&radius, "radius", "r", 0.5, "the effect's radius")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func emboss() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "emboss",
|
||||
Short: "applies the emboss effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "emboss input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.Emboss(img), nil
|
||||
})
|
||||
}}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func unsharpmask() *cobra.Command {
|
||||
var radius float64
|
||||
var amount float64
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "unsharpmask",
|
||||
Short: "applies the unsharpmask effect",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "unsharpmask input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return effect.UnsharpMask(img, radius, amount), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Float64VarP(&radius, "radius", "r", 25, "the effect's radius")
|
||||
cmd.Flags().Float64VarP(&radius, "amount", "a", 1, "the effect's amount")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createEffect() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "effect",
|
||||
Short: "apply effects on images",
|
||||
}
|
||||
|
||||
cmd.AddCommand(grayscale())
|
||||
cmd.AddCommand(sepia())
|
||||
cmd.AddCommand(sharpen())
|
||||
cmd.AddCommand(sobel())
|
||||
cmd.AddCommand(invert())
|
||||
cmd.AddCommand(median())
|
||||
cmd.AddCommand(erode())
|
||||
cmd.AddCommand(dilate())
|
||||
cmd.AddCommand(edgedetection())
|
||||
cmd.AddCommand(emboss())
|
||||
cmd.AddCommand(unsharpmask())
|
||||
|
||||
return cmd
|
||||
}
|
106
image/bild/helpers.go
Normal file
106
image/bild/helpers.go
Normal file
@ -0,0 +1,106 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/anthonynsimon/bild/imgio"
|
||||
)
|
||||
|
||||
var jpgExtensions = []string{".jpg", ".jpeg"}
|
||||
var pngExtensions = []string{".png"}
|
||||
var bmpExtensions = []string{".bmp"}
|
||||
|
||||
var (
|
||||
// ErrWrongSize is thrown when the provided size string does not match the expected form.
|
||||
errWrongSize = errors.New("size must be of form [width]x[height], i.e. 400x200")
|
||||
)
|
||||
|
||||
type size struct {
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
func resolveEncoder(outputfile string, defaultEncoding imgio.Encoder) imgio.Encoder {
|
||||
lower := strings.ToLower(outputfile)
|
||||
|
||||
for _, ext := range jpgExtensions {
|
||||
if strings.HasSuffix(lower, ext) {
|
||||
return imgio.JPEGEncoder(100)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ext := range pngExtensions {
|
||||
if strings.HasSuffix(lower, ext) {
|
||||
return imgio.PNGEncoder()
|
||||
}
|
||||
}
|
||||
|
||||
for _, ext := range bmpExtensions {
|
||||
if strings.HasSuffix(lower, ext) {
|
||||
return imgio.BMPEncoder()
|
||||
}
|
||||
}
|
||||
|
||||
return defaultEncoding
|
||||
}
|
||||
|
||||
func apply(fin, fout string, process func(image.Image) (image.Image, error)) {
|
||||
in, err := imgio.Open(fin)
|
||||
exitIfNotNil(err)
|
||||
|
||||
result, err := process(in)
|
||||
exitIfNotNil(err)
|
||||
|
||||
encoder := resolveEncoder(fout, imgio.PNGEncoder())
|
||||
err = imgio.Save(fout, result, encoder)
|
||||
exitIfNotNil(err)
|
||||
}
|
||||
|
||||
func apply2(fin1, fin2, fout string, process func(image.Image, image.Image) (image.Image, error)) {
|
||||
in1, err := imgio.Open(fin1)
|
||||
exitIfNotNil(err)
|
||||
|
||||
in2, err := imgio.Open(fin2)
|
||||
exitIfNotNil(err)
|
||||
|
||||
result, err := process(in1, in2)
|
||||
exitIfNotNil(err)
|
||||
|
||||
encoder := resolveEncoder(fout, imgio.PNGEncoder())
|
||||
err = imgio.Save(fout, result, encoder)
|
||||
exitIfNotNil(err)
|
||||
}
|
||||
|
||||
func exitIfNotNil(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func parseSizeStr(sizestr string) (*size, error) {
|
||||
parts := strings.Split(sizestr, "x")
|
||||
if len(parts) != 2 {
|
||||
return nil, errWrongSize
|
||||
}
|
||||
|
||||
w, err := strconv.Atoi(parts[0])
|
||||
if err != nil || w < 0 {
|
||||
return nil, errWrongSize
|
||||
}
|
||||
|
||||
h, err := strconv.Atoi(parts[1])
|
||||
if err != nil || h < 0 {
|
||||
return nil, errWrongSize
|
||||
}
|
||||
|
||||
return &size{
|
||||
Width: w,
|
||||
Height: h,
|
||||
}, nil
|
||||
}
|
60
image/bild/histogram.go
Normal file
60
image/bild/histogram.go
Normal file
@ -0,0 +1,60 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/channel"
|
||||
"github.com/anthonynsimon/bild/histogram"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newHisto() *cobra.Command {
|
||||
var channels string
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "new",
|
||||
Short: "creates a RBG histogram from an input image",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "new --channels rgb input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
onlyChannels := []channel.Channel{channel.Alpha}
|
||||
for _, c := range channels {
|
||||
switch c {
|
||||
case 'r':
|
||||
onlyChannels = append(onlyChannels, channel.Red)
|
||||
case 'g':
|
||||
onlyChannels = append(onlyChannels, channel.Green)
|
||||
case 'b':
|
||||
onlyChannels = append(onlyChannels, channel.Blue)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown channel alias '%c'", c)
|
||||
}
|
||||
}
|
||||
|
||||
hist := histogram.NewRGBAHistogram(img)
|
||||
result := channel.ExtractMultiple(hist.Image(), onlyChannels...)
|
||||
return result, nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().StringVarP(&channels, "channels", "c", "rgb", "the channels to include in the histogram")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createHistogram() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "histogram",
|
||||
Short: "histogram operations on images",
|
||||
}
|
||||
|
||||
cmd.AddCommand(newHisto())
|
||||
|
||||
return cmd
|
||||
}
|
36
image/bild/imgio.go
Normal file
36
image/bild/imgio.go
Normal file
@ -0,0 +1,36 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func encode() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "encode",
|
||||
Short: "encodes the input image using the desired encoding set by the destination file extension",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "encode input.jpg output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
// Apply takes care of resolving the destination encoder
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return img, nil
|
||||
})
|
||||
}}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createImgio() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "imgio",
|
||||
Short: "i/o operations on images",
|
||||
}
|
||||
|
||||
cmd.AddCommand(encode())
|
||||
|
||||
return cmd
|
||||
}
|
48
image/bild/noise.go
Normal file
48
image/bild/noise.go
Normal file
@ -0,0 +1,48 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"github.com/anthonynsimon/bild/imgio"
|
||||
"github.com/anthonynsimon/bild/noise"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func generateNoise() *cobra.Command {
|
||||
size := ""
|
||||
mono := false
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "new",
|
||||
Short: "generates an image filled with noise",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: "new -s 100x100 output.png",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fout := args[0]
|
||||
|
||||
size, err := parseSizeStr(size)
|
||||
exitIfNotNil(err)
|
||||
|
||||
result := noise.Generate(size.Width, size.Height, &noise.Options{
|
||||
Monochrome: mono,
|
||||
})
|
||||
|
||||
encoder := resolveEncoder(fout, imgio.PNGEncoder())
|
||||
err = imgio.Save(fout, result, encoder)
|
||||
exitIfNotNil(err)
|
||||
}}
|
||||
|
||||
cmd.Flags().StringVarP(&size, "size", "s", "512x512", "the width and height of the output image")
|
||||
cmd.Flags().BoolVarP(&mono, "monochrome", "m", false, "output monochrome noise")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createNoise() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "noise",
|
||||
Short: "noise generators",
|
||||
}
|
||||
|
||||
cmd.AddCommand(generateNoise())
|
||||
|
||||
return cmd
|
||||
}
|
32
image/bild/root.go
Normal file
32
image/bild/root.go
Normal file
@ -0,0 +1,32 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Version of bild's CLI, set by the compiler on release
|
||||
var Version string
|
||||
|
||||
var CmdBild = &cobra.Command{
|
||||
Use: "bild",
|
||||
Short: "A collection of parallel image processing algorithms in pure Go",
|
||||
Version: Version,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CmdBild.AddCommand(createAdjust())
|
||||
CmdBild.AddCommand(createBlend())
|
||||
CmdBild.AddCommand(createBlur())
|
||||
CmdBild.AddCommand(createImgio())
|
||||
CmdBild.AddCommand(createNoise())
|
||||
CmdBild.AddCommand(createSegment())
|
||||
CmdBild.AddCommand(createHistogram())
|
||||
CmdBild.AddCommand(createChannel())
|
||||
CmdBild.AddCommand(createEffect())
|
||||
}
|
||||
|
||||
// Execute starts the cli's root command
|
||||
func Execute() {
|
||||
err := CmdBild.Execute()
|
||||
exitIfNotNil(err)
|
||||
}
|
41
image/bild/segment.go
Normal file
41
image/bild/segment.go
Normal file
@ -0,0 +1,41 @@
|
||||
package bilds
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/segment"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func threshold() *cobra.Command {
|
||||
var level uint8
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
Use: "threshold",
|
||||
Short: "segment an image by a threshold",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "threshold --level 200 input.jpg output.jpg",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fin := args[0]
|
||||
fout := args[1]
|
||||
|
||||
apply(fin, fout, func(img image.Image) (image.Image, error) {
|
||||
return segment.Threshold(img, level), nil
|
||||
})
|
||||
}}
|
||||
|
||||
cmd.Flags().Uint8VarP(&level, "level", "l", 128, "the level at which the segmenting threshold will be crossed")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func createSegment() *cobra.Command {
|
||||
var blurCmd = &cobra.Command{
|
||||
Use: "segment",
|
||||
Short: "segment an image using the specified method",
|
||||
}
|
||||
|
||||
blurCmd.AddCommand(threshold())
|
||||
|
||||
return blurCmd
|
||||
}
|
231
image/exif.go
Normal file
231
image/exif.go
Normal file
@ -0,0 +1,231 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"b612.me/exif"
|
||||
exifcommon "b612.me/exif/common"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Exif(path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("打开文件失败 %s", path)
|
||||
}
|
||||
rawExif, err := exif.SearchAndExtractExifWithReader(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询 EXIF 信息失败 %s: %w", path, err)
|
||||
}
|
||||
tags, _, err := exif.GetFlatExifDataUniversalSearch(rawExif, nil, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 EXIF 信息失败 %s: %w", path, err)
|
||||
}
|
||||
ifd0Map := map[string]string{
|
||||
// 根 IFD (IFD0) 标签
|
||||
"IFD.Software": "生成软件名",
|
||||
"IFD.ImageWidth": "图像宽度",
|
||||
"IFD.ImageLength": "图像高度",
|
||||
"IFD.Make": "相机制造商",
|
||||
"IFD.Model": "相机型号",
|
||||
"IFD.Orientation": "图像方向",
|
||||
"IFD.YResolution": "Y轴分辨率",
|
||||
"IFD.XResolution": "X轴分辨率",
|
||||
"IFD.ResolutionUnit": "分辨率单位",
|
||||
"IFD.DateTime": "最后修改时间",
|
||||
"IFD.ImageDescription": "图像描述",
|
||||
"IFD.Artist": "图像作者",
|
||||
"IFD.Copyright": "版权信息",
|
||||
"IFD.HostComputer": "创建图像的计算机",
|
||||
"IFD.BitsPerSample": "每个颜色分量的位数",
|
||||
"IFD.Compression": "压缩方案",
|
||||
"IFD.PhotometricInterpretation": "像素组成方式",
|
||||
"IFD.StripOffsets": "图像数据条带偏移量",
|
||||
"IFD.SamplesPerPixel": "每个像素的分量数",
|
||||
"IFD.RowsPerStrip": "每个条带的行数",
|
||||
"IFD.StripByteCounts": "每个条带的字节数",
|
||||
"IFD.PlanarConfiguration": "数据排列方式",
|
||||
|
||||
// Exif 子 IFD 标签
|
||||
"IFD/Exif.DateTimeOriginal": "原始拍摄时间",
|
||||
"IFD/Exif.DateTimeDigitized": "数字化时间",
|
||||
"IFD/Exif.OffsetTimeOriginal": "原始拍摄时区偏移",
|
||||
"IFD/Exif.ExposureTime": "曝光时间",
|
||||
"IFD/Exif.FNumber": "光圈F值",
|
||||
"IFD/Exif.ExposureProgram": "曝光程序",
|
||||
"IFD/Exif.ISOSpeedRatings": "ISO感光度",
|
||||
"IFD/Exif.ShutterSpeedValue": "快门速度(APEX值)",
|
||||
"IFD/Exif.ApertureValue": "光圈值(APEX值)",
|
||||
"IFD/Exif.BrightnessValue": "亮度值(APEX值)",
|
||||
"IFD/Exif.ExposureBiasValue": "曝光补偿值(EV)",
|
||||
"IFD/Exif.MeteringMode": "测光模式",
|
||||
"IFD/Exif.Flash": "闪光灯状态",
|
||||
"IFD/Exif.FocalLength": "焦距(毫米)",
|
||||
"IFD/Exif.SubjectDistance": "拍摄主体距离(米)",
|
||||
"IFD/Exif.FlashEnergy": "闪光灯强度",
|
||||
"IFD/Exif.SpatialFrequencyResponse": "空间频率响应",
|
||||
"IFD/Exif.FocalPlaneXResolution": "焦平面X分辨率",
|
||||
"IFD/Exif.FocalPlaneYResolution": "焦平面Y分辨率",
|
||||
"IFD/Exif.FocalPlaneResolutionUnit": "焦平面分辨率单位",
|
||||
"IFD/Exif.FileSource": "文件来源",
|
||||
"IFD/Exif.SceneType": "场景类型",
|
||||
"IFD/Exif.CFAPattern": "CFA(彩色滤镜阵列)模式",
|
||||
"IFD/Exif.CustomRendered": "自定义图像处理",
|
||||
"IFD/Exif.ExposureMode": "曝光模式",
|
||||
"IFD/Exif.WhiteBalance": "白平衡",
|
||||
"IFD/Exif.DigitalZoomRatio": "数码变焦比率",
|
||||
"IFD/Exif.SceneCaptureType": "场景拍摄类型",
|
||||
"IFD/Exif.GainControl": "增益控制",
|
||||
"IFD/Exif.Contrast": "对比度",
|
||||
"IFD/Exif.Saturation": "饱和度",
|
||||
"IFD/Exif.Sharpness": "锐度",
|
||||
"IFD/Exif.DeviceSettingDescription": "设备设置描述",
|
||||
"IFD/Exif.SubjectDistanceRange": "主体距离范围",
|
||||
|
||||
// GPS 子 IFD 标签 (可选添加)
|
||||
"IFD/GPSInfo.GPSLatitude": "纬度",
|
||||
"IFD/GPSInfo.GPSLongitude": "经度",
|
||||
"IFD/GPSInfo.GPSAltitude": "海拔高度",
|
||||
"IFD/GPSInfo.GPSTimeStamp": "GPS时间戳",
|
||||
"IFD/GPSInfo.GPSDateStamp": "GPS日期戳",
|
||||
"IFD/GPSInfo.GPSDOP": "定位精度",
|
||||
}
|
||||
for _, tag := range tags {
|
||||
sample := "%s.%v {%#04x} [%v] :%-10v\n"
|
||||
if name, ok := ifd0Map[tag.IfdPath+"."+tag.TagName]; ok {
|
||||
sample = "%s.%v {%#04x} [%v] (" + name + ") :%-10v\n"
|
||||
}
|
||||
switch tag.IfdPath + "." + tag.TagName {
|
||||
case "IFD.ResolutionUnit":
|
||||
switch tag.Value.([]uint16)[0] {
|
||||
case 1:
|
||||
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) : 没有单位\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
|
||||
case 2:
|
||||
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) : 英寸\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
|
||||
case 3:
|
||||
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) : 像素\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName)
|
||||
default:
|
||||
fmt.Printf("%s.%v {%#04x} [%v] (分辨率单位) : 未知单位:%v\n", tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, tag.Value)
|
||||
}
|
||||
case "IFD/GPSInfo.GPSLatitude", "IFD/GPSInfo.GPSLongitude":
|
||||
switch val := tag.Value.(type) {
|
||||
case []exifcommon.Rational:
|
||||
var value string
|
||||
for i, v := range val {
|
||||
switch i {
|
||||
case 0:
|
||||
value += fmt.Sprintf("%.0f°", float64(v.Numerator)/float64(v.Denominator))
|
||||
case 1:
|
||||
value += fmt.Sprintf("%.0f′", float64(v.Numerator)/float64(v.Denominator))
|
||||
case 2:
|
||||
value += fmt.Sprintf("%.3f″", float64(v.Numerator)/float64(v.Denominator))
|
||||
}
|
||||
}
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, value)
|
||||
default:
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
|
||||
}
|
||||
case "IFD/GPSInfo.GPSTimeStamp":
|
||||
switch val := tag.Value.(type) {
|
||||
case []exifcommon.Rational:
|
||||
var value string
|
||||
for _, v := range val {
|
||||
value += fmt.Sprintf("%.0f:", float64(v.Numerator)/float64(v.Denominator))
|
||||
}
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, value[:len(value)-1]) // 去掉最后一个冒号
|
||||
default:
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
|
||||
}
|
||||
default:
|
||||
switch val := tag.Value.(type) {
|
||||
case []exifcommon.Rational:
|
||||
switch tag.IfdPath + "." + tag.TagName {
|
||||
case "IFD/Exif.ExposureTime":
|
||||
sample += "秒"
|
||||
}
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, float64(val[0].Numerator)/float64(val[0].Denominator))
|
||||
case []uint16:
|
||||
if len(val) == 1 {
|
||||
switch tag.IfdPath + "." + tag.TagName {
|
||||
case "IFD/Exif.ExposureProgram":
|
||||
valStr := ""
|
||||
switch val[0] {
|
||||
case 0:
|
||||
valStr = "未知"
|
||||
case 1:
|
||||
valStr = "手动"
|
||||
case 2:
|
||||
valStr = "自动"
|
||||
case 3:
|
||||
valStr = "光圈优先"
|
||||
case 4:
|
||||
valStr = "快门优先"
|
||||
case 5:
|
||||
valStr = "创意(慢速)"
|
||||
case 6:
|
||||
valStr = "动作(高速)"
|
||||
case 7:
|
||||
valStr = "肖像"
|
||||
case 8:
|
||||
valStr = "景观"
|
||||
case 9:
|
||||
valStr = "Bulb"
|
||||
}
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, valStr)
|
||||
continue
|
||||
case "IFD/Exif.MeteringMode":
|
||||
/*
|
||||
0 = Unknown
|
||||
1 = Average
|
||||
2 = Center-weighted average
|
||||
3 = Spot
|
||||
4 = Multi-spot
|
||||
5 = Multi-segment
|
||||
6 = Partial
|
||||
255 = Other
|
||||
*/
|
||||
valStr := ""
|
||||
switch val[0] {
|
||||
case 0:
|
||||
valStr = "未知"
|
||||
case 1:
|
||||
valStr = "平均"
|
||||
case 2:
|
||||
valStr = "中央重点平均"
|
||||
case 3:
|
||||
valStr = "点测光"
|
||||
case 4:
|
||||
valStr = "多点测光"
|
||||
case 5:
|
||||
valStr = "多段测光"
|
||||
case 6:
|
||||
valStr = "局部测光"
|
||||
case 255:
|
||||
valStr = "其他"
|
||||
}
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, valStr)
|
||||
continue
|
||||
}
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val[0])
|
||||
} else {
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
|
||||
}
|
||||
case []uint32:
|
||||
if len(val) == 1 {
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val[0])
|
||||
} else {
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
|
||||
}
|
||||
case []uint8:
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, hex.EncodeToString(val))
|
||||
case string:
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
|
||||
default:
|
||||
fmt.Printf(sample, tag.IfdPath, tag.TagName, tag.TagId, tag.TagTypeName, val)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
@ -19,7 +19,7 @@ var fromRgb string
|
||||
var toRgb string
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(imgMirrorCmd, imgRgbCountCmd, imgReplaceCmd, imgReverseCmd)
|
||||
Cmd.AddCommand(imgMirrorCmd, imgRgbCountCmd, imgReplaceCmd, imgReverseCmd, CmdExifRead)
|
||||
imgRgbCountCmd.Flags().BoolVarP(&useHex, "hex", "x", false, "使用十六进制表示")
|
||||
imgRgbCountCmd.Flags().BoolVarP(&useAlpha, "alpha", "a", false, "统计alpha通道")
|
||||
imgRgbCountCmd.Flags().IntVarP(&useCount, "count", "c", 10, "显示数量")
|
||||
@ -268,3 +268,23 @@ var imgReverseCmd = &cobra.Command{
|
||||
fmt.Println("任务完成!")
|
||||
},
|
||||
}
|
||||
|
||||
var CmdExifRead = &cobra.Command{
|
||||
Use: "exifread",
|
||||
Short: "读取图像Exif信息",
|
||||
Long: "读取图像Exif信息,输出为表格格式",
|
||||
Run: func(this *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
starlog.Errorln("请指定需要读取Exif信息的图像!")
|
||||
return
|
||||
}
|
||||
for _, v := range args {
|
||||
if err := Exif(v); err != nil {
|
||||
starlog.Errorln(err, v)
|
||||
continue
|
||||
}
|
||||
fmt.Println(v, "Exif信息读取完成!")
|
||||
}
|
||||
fmt.Println("任务完成!")
|
||||
},
|
||||
}
|
||||
|
6
main.go
6
main.go
@ -14,6 +14,7 @@ import (
|
||||
"b612.me/apps/b612/df"
|
||||
"b612.me/apps/b612/dfinder"
|
||||
"b612.me/apps/b612/dns"
|
||||
"b612.me/apps/b612/filedate"
|
||||
"b612.me/apps/b612/ftp"
|
||||
"b612.me/apps/b612/gdu/cmd/gdu"
|
||||
"b612.me/apps/b612/generate"
|
||||
@ -23,9 +24,11 @@ import (
|
||||
"b612.me/apps/b612/httproxy"
|
||||
"b612.me/apps/b612/httpserver"
|
||||
"b612.me/apps/b612/image"
|
||||
bilds "b612.me/apps/b612/image/bild"
|
||||
"b612.me/apps/b612/keygen"
|
||||
"b612.me/apps/b612/merge"
|
||||
"b612.me/apps/b612/mget"
|
||||
"b612.me/apps/b612/mime"
|
||||
"b612.me/apps/b612/net"
|
||||
"b612.me/apps/b612/nmon"
|
||||
"b612.me/apps/b612/rmt"
|
||||
@ -59,7 +62,8 @@ 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, mget.Cmd, tcpkill.Cmd, tcm.Cmd, astro.CmdCal, astro.Cmd, nmon.Cmd, hcache.Cmd, gdu.Cmd, bed.Cmd)
|
||||
cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd, tcpkill.Cmd, tcm.Cmd, astro.CmdCal, astro.Cmd, nmon.Cmd, hcache.Cmd, gdu.Cmd, bed.Cmd, mime.Cmd,
|
||||
filedate.Cmd, bilds.CmdBild)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"b612.me/stario"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starnet"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
@ -41,7 +42,7 @@ func init() {
|
||||
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")
|
||||
Cmd.Flags().StringVarP(&speedcontrol, "speed", "S", "", "限速,如1M,意思是1MiB/s")
|
||||
Cmd.Flags().IntVarP(&dialTimeout, "dial-timeout", "d", 5, "连接网络超时时间,单位:秒")
|
||||
Cmd.Flags().IntVarP(&timeout, "timeout", "T", 0, "下载超时时间,单位:秒")
|
||||
Cmd.Flags().StringVarP(&user, "user", "u", "", "http basic认证用户")
|
||||
@ -126,7 +127,9 @@ func Run(cmd *cobra.Command, args []string) {
|
||||
mg.Setting.SetProxy(proxy)
|
||||
}
|
||||
if skipVerify {
|
||||
mg.Setting.SetSkipTLSVerify(true)
|
||||
mg.Setting.SetTlsConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
}
|
||||
sig := make(chan os.Signal)
|
||||
signal.Notify(sig, os.Interrupt)
|
||||
|
20
mget/util.go
20
mget/util.go
@ -88,7 +88,7 @@ func IOWriter(stopCtx context.Context, ch chan Buffer, state *uint32, di *downlo
|
||||
}
|
||||
}
|
||||
|
||||
func createFileWithSize(filename string, size int64) (*os.File, error) {
|
||||
func createFileWithSizeOld(filename string, size int64) (*os.File, error) {
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -109,6 +109,24 @@ func createFileWithSize(filename string, size int64) (*os.File, error) {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// 最佳实践
|
||||
func createFileWithSize(filename string, size int64) (*os.File, error) {
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if size > 0 {
|
||||
if err := file.Truncate(size); err != nil {
|
||||
file.Close()
|
||||
os.Remove(filename)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func CloneHeader(original http.Header) http.Header {
|
||||
newHeader := make(http.Header)
|
||||
for key, values := range original {
|
||||
|
11
mget/wget.go
11
mget/wget.go
@ -112,7 +112,7 @@ func (w *Mget) prepareRun(res *starnet.Response, is206 bool) error {
|
||||
Is206: is206,
|
||||
}
|
||||
fmt.Println("Threads:", w.Thread)
|
||||
if staros.Exists(w.Tareget + ".bgrd") {
|
||||
if !w.NoWriteRedo && staros.Exists(w.Tareget+".bgrd") {
|
||||
fmt.Println("Found redo file, try to recover...")
|
||||
var redo Redo
|
||||
data, err := os.ReadFile(w.Tareget + ".bgrd")
|
||||
@ -323,8 +323,8 @@ 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.threads[idx].Start = int64(w.lastUndoInfo[0].Min)
|
||||
w.threads[idx].End = int64(w.lastUndoInfo[0].Max)
|
||||
w.lastUndoInfo = w.lastUndoInfo[1:]
|
||||
w.Unlock()
|
||||
} else {
|
||||
@ -408,7 +408,7 @@ func (w *Mget) WriteServer() error {
|
||||
}
|
||||
if w.RedoRPO == 0 {
|
||||
w.RedoAutoRpo = true
|
||||
w.RedoRPO = 1024 * 1024 * 1024
|
||||
w.RedoRPO = 1024 * 1024 * 10
|
||||
}
|
||||
for {
|
||||
select {
|
||||
@ -435,6 +435,9 @@ func (w *Mget) WriteServer() error {
|
||||
go w.Redo.Save()
|
||||
if w.RedoAutoRpo {
|
||||
w.RedoRPO = int(w.Redo.Speed()) * 2
|
||||
if w.RedoRPO > 100*1024*1024 {
|
||||
w.RedoRPO = 1024 * 1024 * 100
|
||||
}
|
||||
}
|
||||
lastUpdateRange = currentRange
|
||||
}
|
||||
|
154
mime/mime.go
Normal file
154
mime/mime.go
Normal file
@ -0,0 +1,154 @@
|
||||
package mime
|
||||
|
||||
import (
|
||||
"b612.me/starlog"
|
||||
"b612.me/staros"
|
||||
"encoding/csv"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var recursive, verbose, repair, appendOldName, onlyShowNotMatch, secondExt bool
|
||||
var outputCsvPath string
|
||||
var fromSize int
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "递归检测文件夹中的所有文件")
|
||||
Cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "详细模式,输出更多日志")
|
||||
Cmd.Flags().BoolVarP(&repair, "repair", "R", false, "修复文件类型,尝试将文件类型修复为正确的类型")
|
||||
Cmd.Flags().BoolVarP(&appendOldName, "append-mode", "a", false, "在修复文件名时,追加旧文件名后缀")
|
||||
Cmd.Flags().StringVarP(&outputCsvPath, "output-csv", "o", "", "输出CSV文件路径,若不指定则不输出CSV")
|
||||
Cmd.Flags().IntVarP(&fromSize, "from-size", "s", 0, "需要检测的地址偏移量,单位为字节,默认从0开始检测,负数表示从文件末尾开始计算偏移量")
|
||||
Cmd.Flags().BoolVarP(&onlyShowNotMatch, "only-show-not-match", "m", false, "只显示文件类型不匹配的文件")
|
||||
Cmd.Flags().BoolVarP(&secondExt, "second-ext", "S", false, "使用第二个扩展名进行匹配,适用于某些文件类型(如 .tar.gz .7z.001)")
|
||||
}
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "mime",
|
||||
Short: "文件类型检测",
|
||||
Long: `mime - 文件类型检测`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var c *csv.Writer
|
||||
if len(args) == 0 {
|
||||
starlog.Errorf("请指定文件路径\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if outputCsvPath != "" {
|
||||
file, err := os.Create(outputCsvPath)
|
||||
if err != nil {
|
||||
starlog.Errorf("创建CSV文件 %s 失败: %v\n", outputCsvPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
c = csv.NewWriter(file)
|
||||
c.Write([]string{"File Path", "Extension", "Detect File Type", "Detect File Extension", "Match"})
|
||||
defer c.Flush()
|
||||
}
|
||||
for _, path := range args {
|
||||
if staros.IsFolder(path) {
|
||||
for _, file := range getFiles(path, recursive) {
|
||||
Detect(c, file, fromSize, onlyShowNotMatch)
|
||||
}
|
||||
} else {
|
||||
Detect(c, path, fromSize, onlyShowNotMatch)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func getFiles(folder string, recurring bool) []string {
|
||||
files := make([]string, 0)
|
||||
err := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
starlog.Errorf("walk folder %s error: %v", folder, err)
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if recurring {
|
||||
return nil // 继续递归
|
||||
}
|
||||
return filepath.SkipDir // 不递归子目录
|
||||
}
|
||||
files = append(files, path)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
starlog.Errorf("walk folder %s error: %v", folder, err)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func Detect(c *csv.Writer, path string, fromSize int, onlyShowNotMatch bool) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
starlog.Errorf("open file %s error: %v\n", path, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if fromSize > 0 {
|
||||
f.Seek(int64(fromSize), io.SeekStart)
|
||||
} else if fromSize < 0 {
|
||||
f.Seek(int64(fromSize), io.SeekEnd)
|
||||
}
|
||||
tp, err := mimetype.DetectReader(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
starlog.Errorf("detect file type error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
ext2 := strings.ToLower(filepath.Ext(strings.TrimRight(path, ext)))
|
||||
if verbose {
|
||||
starlog.Infof("file path: %s\n", path)
|
||||
starlog.Infof("file type: %s\n", tp.String())
|
||||
starlog.Infof("file ext: %s\n", tp.Extension())
|
||||
if ext2 == tp.Extension() && secondExt {
|
||||
if !onlyShowNotMatch {
|
||||
starlog.Infof("file type %s match extension %s\n", tp.String(), ext2)
|
||||
}
|
||||
} else if ext != tp.Extension() && tp.Extension() != "" {
|
||||
starlog.Warningf("file type %s not match extension %s\n", tp.String(), ext)
|
||||
} else if !onlyShowNotMatch {
|
||||
starlog.Infof("file type %s match extension %s\n", tp.String(), ext)
|
||||
}
|
||||
} else {
|
||||
if ext2 == tp.Extension() && secondExt {
|
||||
if !onlyShowNotMatch {
|
||||
starlog.Infof("%s: %s (%s) matched %s\n", path, tp.String(), tp.Extension(), ext2)
|
||||
}
|
||||
} else if ext != tp.Extension() && tp.Extension() != "" {
|
||||
starlog.Warningf("%s: %s (%s) not match %s\n", path, tp.String(), tp.Extension(), ext)
|
||||
} else if !onlyShowNotMatch {
|
||||
starlog.Infof("%s: %s (%s) matched %s\n", path, tp.String(), tp.Extension(), ext)
|
||||
}
|
||||
}
|
||||
if c != nil {
|
||||
match := "false"
|
||||
if ext == tp.Extension() {
|
||||
match = "true"
|
||||
}
|
||||
err = c.Write([]string{path, ext, tp.String(), tp.Extension(), match})
|
||||
if err != nil {
|
||||
starlog.Errorf("write csv file %s error: %v\n", outputCsvPath, err)
|
||||
}
|
||||
}
|
||||
if repair && ext != tp.Extension() && tp.Extension() != "" {
|
||||
newPath := path[:len(path)-len(ext)] + tp.Extension()
|
||||
if appendOldName {
|
||||
newPath = path + tp.Extension()
|
||||
}
|
||||
err = os.Rename(path, newPath)
|
||||
if err != nil {
|
||||
starlog.Errorf("rename file %s to %s error: %v", path, newPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if verbose {
|
||||
starlog.Infof("file %s renamed to %s\n", path, newPath)
|
||||
} else {
|
||||
starlog.Infof("renamed: %s -> %s\n", path, newPath)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
package smtpserver
|
||||
|
||||
import (
|
||||
"b612.me/apps/b612/utils"
|
||||
"b612.me/starlog"
|
||||
"b612.me/startext"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/emersion/go-sasl"
|
||||
"github.com/spf13/cobra"
|
||||
"html"
|
||||
"io"
|
||||
@ -22,10 +25,11 @@ import (
|
||||
|
||||
var addr string
|
||||
var user, pass string
|
||||
var allowAnyuser bool
|
||||
var allowAnyuser, startls bool
|
||||
var output string
|
||||
var domain string
|
||||
var cert, key string
|
||||
var allowInsecureAuth, autoGenKey bool
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "smtps",
|
||||
@ -33,7 +37,6 @@ var Cmd = &cobra.Command{
|
||||
Long: "smtp server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
run()
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
@ -46,6 +49,9 @@ func init() {
|
||||
Cmd.Flags().StringVarP(&domain, "domain", "d", "localhost", "smtp server domain")
|
||||
Cmd.Flags().StringVarP(&cert, "cert", "c", "", "smtp server cert(TLS)")
|
||||
Cmd.Flags().StringVarP(&key, "key", "k", "", "smtp server key(TLS)")
|
||||
Cmd.Flags().BoolVarP(&autoGenKey, "auto-gen-key", "g", false, "auto generate key and cert")
|
||||
Cmd.Flags().BoolVarP(&startls, "startls", "s", false, "enable starttls")
|
||||
Cmd.Flags().BoolVarP(&allowInsecureAuth, "allow-insecure-auth", "i", false, "allow insecure auth")
|
||||
}
|
||||
|
||||
type backend struct{}
|
||||
@ -58,32 +64,50 @@ type session struct {
|
||||
username string
|
||||
password string
|
||||
to string
|
||||
auth bool
|
||||
}
|
||||
|
||||
func (s *session) AuthPlain(username, password string) error {
|
||||
s.username = username
|
||||
s.password = password
|
||||
starlog.Printf("username:%s,password:%s\n", username, password)
|
||||
if allowAnyuser {
|
||||
return nil
|
||||
} else {
|
||||
if username != user || password != pass {
|
||||
return smtp.ErrAuthFailed
|
||||
func (s *session) AuthMechanisms() []string {
|
||||
return []string{sasl.Plain}
|
||||
}
|
||||
|
||||
func (s *session) Auth(mech string) (sasl.Server, error) {
|
||||
return sasl.NewPlainServer(func(identity, username, password string) error {
|
||||
s.username = username
|
||||
s.password = password
|
||||
starlog.Red("username:%s,password:%s\n", username, password)
|
||||
if allowAnyuser {
|
||||
s.auth = true
|
||||
return nil
|
||||
} else {
|
||||
if username != user || password != pass {
|
||||
return smtp.ErrAuthFailed
|
||||
}
|
||||
}
|
||||
s.auth = true
|
||||
return nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *session) Mail(from string, opts *smtp.MailOptions) error {
|
||||
if !s.auth {
|
||||
return smtp.ErrAuthRequired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) Mail(from string, opts *smtp.MailOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) Rcpt(to string, opts *smtp.RcptOptions) error {
|
||||
if !s.auth {
|
||||
return smtp.ErrAuthRequired
|
||||
}
|
||||
s.to += to + ";"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) Data(r io.Reader) error {
|
||||
if !s.auth {
|
||||
return smtp.ErrAuthRequired
|
||||
}
|
||||
mailData, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -169,25 +193,61 @@ func (s *session) Logout() error {
|
||||
func run() {
|
||||
var err error
|
||||
s := smtp.NewServer(&backend{})
|
||||
if cert != "" && key != "" {
|
||||
var config tls.Config
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
starlog.Errorln("failed to load cert:", err)
|
||||
return
|
||||
}
|
||||
s.TLSConfig = &config
|
||||
s.Addr = addr
|
||||
s.Domain = domain
|
||||
s.AllowInsecureAuth = true
|
||||
s.Debug = os.Stdout
|
||||
starlog.Infoln("Starting TLS-SMTP server at", addr)
|
||||
starlog.Errorln(s.ListenAndServeTLS())
|
||||
return
|
||||
}
|
||||
s.Addr = addr
|
||||
s.Domain = domain
|
||||
s.EnableSMTPUTF8 = true
|
||||
s.EnableDSN = true
|
||||
s.EnableBINARYMIME = true
|
||||
s.EnableRRVS = true
|
||||
if autoGenKey || (cert != "" && key != "") {
|
||||
var config tls.Config
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
if !autoGenKey {
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
starlog.Errorln("failed to load cert:", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ca, cakey := utils.ToolCert()
|
||||
genCert, err := utils.GenerateTlsCert(utils.GenerateCertParams{
|
||||
Country: "CN",
|
||||
Organization: "B612 SMTP SERVER",
|
||||
OrganizationUnit: "CA@B612.ME",
|
||||
CommonName: s.Domain,
|
||||
Dns: []string{s.Domain},
|
||||
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign),
|
||||
ExtendedKeyUsage: []int{
|
||||
int(x509.ExtKeyUsageServerAuth),
|
||||
int(x509.ExtKeyUsageClientAuth),
|
||||
},
|
||||
IsCA: false,
|
||||
StartDate: time.Now().Add(-24 * time.Hour),
|
||||
EndDate: time.Now().AddDate(1, 0, 0),
|
||||
Type: "RSA",
|
||||
Bits: 2048,
|
||||
CA: ca,
|
||||
CAPriv: cakey,
|
||||
})
|
||||
if err != nil {
|
||||
starlog.Errorln("failed to generate cert:", err)
|
||||
return
|
||||
}
|
||||
config.Certificates[0] = genCert
|
||||
}
|
||||
s.TLSConfig = &config
|
||||
s.AllowInsecureAuth = allowInsecureAuth
|
||||
s.EnableREQUIRETLS = true
|
||||
s.Debug = os.Stdout
|
||||
if !startls {
|
||||
starlog.Infoln("Starting TLS-SMTP server at", addr)
|
||||
starlog.Errorln(s.ListenAndServeTLS())
|
||||
return
|
||||
}
|
||||
starlog.Infoln("Starting StartTls-SMTP server at", addr)
|
||||
starlog.Errorln(s.ListenAndServe())
|
||||
return
|
||||
}
|
||||
s.AllowInsecureAuth = true
|
||||
s.Debug = os.Stdout
|
||||
|
||||
|
302
utils/cert.go
Normal file
302
utils/cert.go
Normal file
@ -0,0 +1,302 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"b612.me/starcrypto"
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GenerateCertParams struct {
|
||||
Country string `json:"country"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
Organization string `json:"organization"`
|
||||
OrganizationUnit string `json:"organization_unit"`
|
||||
CommonName string `json:"common_name"`
|
||||
Dns []string `json:"dns"`
|
||||
KeyUsage int `json:"key_usage"`
|
||||
ExtendedKeyUsage []int `json:"extended_key_usage"`
|
||||
IsCA bool `json:"is_ca"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
MaxPathLength int `json:"max_path_length"`
|
||||
MaxPathLengthZero bool `json:"max_path_length_zero"`
|
||||
Type string `json:"type"` // e.g., "x509", "ssh"
|
||||
Bits int `json:"bits"` // e.g., 2048, 4096
|
||||
CA *x509.Certificate
|
||||
CAPriv any // private key for CA, if nil, use the generated key
|
||||
}
|
||||
|
||||
func GenerateKeys(types string, bits int) (crypto.PublicKey, any, error) {
|
||||
|
||||
var priv any
|
||||
var pub crypto.PublicKey
|
||||
var err error
|
||||
switch strings.ToLower(types) {
|
||||
case "rsa":
|
||||
priv, pub, err = starcrypto.GenerateRsaKey(bits)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case "ecdsa":
|
||||
var cr elliptic.Curve
|
||||
switch bits {
|
||||
case 224:
|
||||
cr = elliptic.P224()
|
||||
case 256:
|
||||
cr = elliptic.P256()
|
||||
case 384:
|
||||
cr = elliptic.P384()
|
||||
case 521:
|
||||
cr = elliptic.P521()
|
||||
default:
|
||||
return nil, nil, errors.New("invalid bits,should be 224,256,384,521")
|
||||
}
|
||||
priv, pub, err = starcrypto.GenerateEcdsaKey(cr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case "ed25519":
|
||||
pub, priv, err = ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case "x25519":
|
||||
priv, err = ecdh.X25519().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub = priv.(*ecdh.PrivateKey).Public()
|
||||
case "ecdh":
|
||||
switch bits {
|
||||
case 256:
|
||||
priv, err = ecdh.P256().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub = priv.(*ecdh.PrivateKey).Public()
|
||||
case 384:
|
||||
priv, err = ecdh.P384().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub = priv.(*ecdh.PrivateKey).Public()
|
||||
case 521:
|
||||
priv, err = ecdh.P521().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub = priv.(*ecdh.PrivateKey).Public()
|
||||
}
|
||||
default:
|
||||
return nil, nil, errors.New("invalid key type,only support rsa,ecdsa")
|
||||
}
|
||||
return pub, priv, nil
|
||||
}
|
||||
|
||||
func getSlice(val string) []string {
|
||||
if val == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{val}
|
||||
}
|
||||
|
||||
func GenerateCert(params GenerateCertParams) ([]byte, []byte, error) {
|
||||
var needAppendCa bool = true
|
||||
pub, priv, err := GenerateKeys(params.Type, params.Bits)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var extKeyUsage []x509.ExtKeyUsage = nil
|
||||
for _, usage := range params.ExtendedKeyUsage {
|
||||
if extKeyUsage == nil {
|
||||
extKeyUsage = make([]x509.ExtKeyUsage, 0, len(params.ExtendedKeyUsage))
|
||||
}
|
||||
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsage(usage))
|
||||
}
|
||||
var trueDNS []string
|
||||
var trueIp []net.IP
|
||||
for _, v := range params.Dns {
|
||||
ip := net.ParseIP(v)
|
||||
if ip == nil {
|
||||
trueDNS = append(trueDNS, v)
|
||||
continue
|
||||
}
|
||||
trueIp = append(trueIp, ip)
|
||||
}
|
||||
cert := &x509.Certificate{
|
||||
Version: 3,
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: pkix.Name{
|
||||
Country: getSlice(params.Country),
|
||||
Province: getSlice(params.Province),
|
||||
Locality: getSlice(params.City),
|
||||
Organization: getSlice(params.Organization),
|
||||
OrganizationalUnit: getSlice(params.OrganizationUnit),
|
||||
CommonName: params.CommonName,
|
||||
},
|
||||
NotBefore: params.StartDate,
|
||||
NotAfter: params.EndDate,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: params.IsCA,
|
||||
MaxPathLenZero: params.MaxPathLengthZero,
|
||||
ExtKeyUsage: extKeyUsage,
|
||||
KeyUsage: x509.KeyUsage(params.KeyUsage),
|
||||
MaxPathLen: params.MaxPathLength,
|
||||
DNSNames: trueDNS,
|
||||
IPAddresses: trueIp,
|
||||
}
|
||||
if params.CA == nil {
|
||||
params.CA = cert
|
||||
needAppendCa = false
|
||||
}
|
||||
if params.CAPriv == nil {
|
||||
params.CAPriv = priv
|
||||
}
|
||||
certs, err := x509.CreateCertificate(rand.Reader, cert, params.CA, pub, params.CAPriv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
certBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certs,
|
||||
}
|
||||
pemData := pem.EncodeToMemory(certBlock)
|
||||
privData, err := starcrypto.EncodePrivateKey(priv, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if needAppendCa {
|
||||
pemData = append(pemData, []byte("\n")...)
|
||||
certBlock = &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: params.CA.Raw,
|
||||
}
|
||||
pemData = append(pemData, pem.EncodeToMemory(certBlock)...)
|
||||
}
|
||||
return pemData, privData, nil
|
||||
}
|
||||
|
||||
func GenerateTlsCert(params GenerateCertParams) (tls.Certificate, error) {
|
||||
certPem, privPem, err := GenerateCert(params)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
return tls.X509KeyPair(certPem, privPem)
|
||||
}
|
||||
|
||||
func ToolCert() (*x509.Certificate, any) {
|
||||
certPem := `-----BEGIN CERTIFICATE-----
|
||||
MIIGFjCCA/6gAwIBAgIEaEqE/DANBgkqhkiG9w0BAQsFADCBnzELMAkGA1UEBhMC
|
||||
Q04xFjAUBgNVBAcTDUFzdGVyb2lkIEI2MTIxGjALBgNVBBETBEI2MTIwCwYDVQQR
|
||||
EwRTdGFyMRAwDgYDVQQKEwdCNjEyLk1FMRMwEQYDVQQLEwpDQS5CNjEyLk1FMRsw
|
||||
GQYDVQQDExJCNjEyIFRvb2xzIFJvb3QgQ0ExGDAWBgNVBAUTD0I2MTJUT09MU1JP
|
||||
T1RDQTAgFw0wMDAxMDEwODAwMDBaGA8yMDc3MTIzMTIzNTk1OVowaTELMAkGA1UE
|
||||
BhMCQ04xFjAUBgNVBAcTDUFzdGVyb2lkIEI2MTIxEDAOBgNVBAoTB0I2MTIuTUUx
|
||||
EzARBgNVBAsTCkNBLkI2MTIuTUUxGzAZBgNVBAMTEkI2MTIgSW50ZXIgVG9vbCBD
|
||||
QTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANZp4NnlAmIGrXaP3/6R
|
||||
LEER7XrO6NyrXyKzqjg01aSw/XOvwbiwYb6UKZonhEJyFzGxHWopzUdTESZTD1WQ
|
||||
3RGtoXzjOF8xzPnB+kwnFy3nrboHrjyYy3MIkgeWzG9FdJlxTj90dwPypwIIKlhV
|
||||
KxVijilwaCqesg+K6/E9zsq3a2vVx+Od+Ho3CN28SxWgeletNsaHmwHV+OjSkVOK
|
||||
+Ck43xCl06cMMrx/sBD2FJ+DGQc994Fzy2US69jp2+PkCZEv+VCYRBKYG7C0OOQq
|
||||
C3utF/lnKfC66Nq4Ql/HFa3DXko+drb2gnbXuuvKDQGd4cGkteqnOxRfU68ahGiJ
|
||||
0iZ76M1xm52yTDInXMTZRiwMtVT8JxYlLSwnTu3y3jhcaZMrwl6kGmEfOfEKpq86
|
||||
F5dQ5umT9REK9er1HxxvEg2w7TsgHbXI61BrcfdvTaK+gzSX2XldUbPfMv6W77Z+
|
||||
BSc9rLEROh3FjPAXXC7bvcCJblRhC5zIKNdY3THUTsQNGc7tma2bQgODhYiZ6Trq
|
||||
x1rdDluoiEEmnyjUkJ1DuZn9ODQJJZJkyoNbQ5Zy+nMyM+MaG+05zLdOLLpC8GCK
|
||||
Xn9ldp2wMybGWtm4YOZm2YPWKBj2pMyrGhwkJHDKTWCDdiLz9mur4iZmpsfI1yvV
|
||||
EZH0/cVNUYrKnCR++Xn811CXAgMBAAGjgYwwgYkwDgYDVR0PAQH/BAQDAgGuMCMG
|
||||
A1UdJQQcMBoGBFUdJQAGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAG
|
||||
AQH/AgEAMB0GA1UdDgQWBBTMkml2uqysQSs4QoZH0AoANqIZPzAfBgNVHSMEGDAW
|
||||
gBSpZ9jSMtZoS9+yELlalGFs53t9ETANBgkqhkiG9w0BAQsFAAOCAgEAoKVTl1cR
|
||||
45raGhdNhujTrHJ7n8kEi38tJ3Ppo1IeomDDFS6NbHXloUnhhFuTNy+TtySCy94L
|
||||
4xr13vfyL51Yqf1lgyQKHX0vZskWGp8XlnIiMRsqDR40pp2cDPwFPUM6V7ttLvq4
|
||||
DwEUnOjTJkq3fmgakS5eBFiH6aHENhO0RbV9KaUEgio2IYGoJMR5MshdAiGYJ2zF
|
||||
wF5pUJxm9ZwDPv/p+ah/GujKCRFOVXWy/UkYywG+eClwwRy4iJiv9ohTVB0j5su0
|
||||
jpYvXsDbPRBqcY8lW/jtOLT1AzDVR+asY7ZBOcJc/WZqR1lYAiAldYIMw1bxB+IN
|
||||
eEjAFEk9SMF9MDTq8GZJFSxhcBa93vOy+g5GkHC1RtNYpi5WFQWXm32vLeIPX+1L
|
||||
4ZgAtCTwVXxvC84BDa7hTnK7bKF4gwnl1ii23/ouU1UO/ko1i+j3Q29ot0k+DUZ2
|
||||
BD2F2TAHjkDh3fbgQx8riy2fUnqo22+S7eaACIT84dmS2SqQ/2m/rGuymT3d4ClI
|
||||
2F9aLDZEQelpjUX82QSizNJFESFPTf37yxsvcrMcCKpr1sApShfixWBQ9bnAJyO7
|
||||
Lw8AiI/h+jf1UBg/+Qlws4GIFRCC3zEo2Cu33CYTA7UDXDEOu0gzuDkNS9/lWAL3
|
||||
EdAtgNNfLagVhHl7yF8TGiLUPgszinPBDd0=
|
||||
-----END CERTIFICATE-----`
|
||||
privPem := `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKQIBAAKCAgEA1mng2eUCYgatdo/f/pEsQRHtes7o3KtfIrOqODTVpLD9c6/B
|
||||
uLBhvpQpmieEQnIXMbEdainNR1MRJlMPVZDdEa2hfOM4XzHM+cH6TCcXLeetugeu
|
||||
PJjLcwiSB5bMb0V0mXFOP3R3A/KnAggqWFUrFWKOKXBoKp6yD4rr8T3Oyrdra9XH
|
||||
4534ejcI3bxLFaB6V602xoebAdX46NKRU4r4KTjfEKXTpwwyvH+wEPYUn4MZBz33
|
||||
gXPLZRLr2Onb4+QJkS/5UJhEEpgbsLQ45CoLe60X+Wcp8Lro2rhCX8cVrcNeSj52
|
||||
tvaCdte668oNAZ3hwaS16qc7FF9TrxqEaInSJnvozXGbnbJMMidcxNlGLAy1VPwn
|
||||
FiUtLCdO7fLeOFxpkyvCXqQaYR858QqmrzoXl1Dm6ZP1EQr16vUfHG8SDbDtOyAd
|
||||
tcjrUGtx929Nor6DNJfZeV1Rs98y/pbvtn4FJz2ssRE6HcWM8BdcLtu9wIluVGEL
|
||||
nMgo11jdMdROxA0Zzu2ZrZtCA4OFiJnpOurHWt0OW6iIQSafKNSQnUO5mf04NAkl
|
||||
kmTKg1tDlnL6czIz4xob7TnMt04sukLwYIpef2V2nbAzJsZa2bhg5mbZg9YoGPak
|
||||
zKsaHCQkcMpNYIN2IvP2a6viJmamx8jXK9URkfT9xU1RisqcJH75efzXUJcCAwEA
|
||||
AQKCAgAv50PXKrUXlYpXvNk8lM6gUxMNpwCbnKYKqL1VBWjd/LUDjbboPm/4Fj4d
|
||||
NMr11WudLIb32xMD5mkkeNYqNc1OT86Oim1jx0qWWmJDdVBWbzZ/I4wn+bMqpjWK
|
||||
AIT4LzpXtdrXjnuwpBvv9kcPqSeRBY3hcD21l/MMSetg4IA9BjG3y/F2xypmk7kl
|
||||
YRYdZNcwk4BzZoSZKmcMDU9urNO40f30SDg7UBxdxOFfSLtez/ldhtivUWGV2V5b
|
||||
/hOknKQOjftTqFE9HkLBfeJgB4y9OvTqQcQ7BmdTxmr93wrW4ZlFWSNIyVZomtYm
|
||||
K+QwAkgX7Wa/YFFwwcN+kZwXhwMuMTvvjpCwstq9ex5U8xuASqz9+wp1Tuj7n6cQ
|
||||
IirecQYy22TDdfFSZ3wUwlY+y4U9bx/Z7QfymhOlEPTzig/OlUrC1CqJqrLMmGna
|
||||
108mXkFkbeQypbzXG0cTgrjULX4WrhETqiLi+AjQj7Gr2MSyYZffw4fzjUWECWUZ
|
||||
EDYX2lza6S0deaTPG0Zbz17V5V39M4LEucHhGQZpP1+MNTI4XxH3/eVKH1XO3uqg
|
||||
I5dUGJmQDxPJQCfjIuPOaGqued6qF3kZDBwbV7hDs1StONDCILdcG/elpBdKmoxH
|
||||
SlZ1/hR2xr8BMe8QE2og/IHbJx5SiPCU7f83V3q7YdMLcw9GIQKCAQEA7Bn3CV7g
|
||||
2QL5ehOnKEVPdWekY11ZUtHjamQVeZRgz1z6XYEJyxeo5ZG+aUp8WEoUWH6IWuzN
|
||||
rXAhQkT2ReNZu0JofRGVuaZVj9RnrtxQo/+LzYeFfCK2fSfkevFsrpMQyEMXqi8+
|
||||
WVUKCFLYbDERmKjGoPVNFUYSUtaomuSzUrie+B2Kmy5EdfUI1nix5t48sPshaqb2
|
||||
d1DWAPJ1Xq2OR5vwtndgYnGwaPNpAtaQbLtk2woyGqxIaD1KCaN/91e/nw34NVNu
|
||||
CbvCwXm4RPyv/VgZySs/4xLzuGXrWH5vyelP7fz5CQpY9GCqlM0F4pch419j9vjo
|
||||
iQJaYI44aQs2DwKCAQEA6Hv8GAQ+ECkUfNLIjew0xuhFfVSfvU0CBZGTh9FMB+pL
|
||||
o37/99+kCN5J8lQIDOrnJaI7uMWw0H4lGB5uxYAY0qQSZvLYqyXw1fpPNzGdZl0l
|
||||
0CYX7sKTFBjCumRYCWH/3d2pkknCu6JdkfPFPcfdKbrdYfVjOXVm8aeFLhl2lOG1
|
||||
AuCNmqpOEfrRR0Hdq0TdGwvYW3y+e4/iMuQ0ML69QPyLx2GCq1Al+42z2dKOUzyL
|
||||
8U2pAlz5mi2olpNbXzgN9fqyvyVjI28WwwEL+j6L23GshnjtZvnJuwk3LPX+Y0a7
|
||||
0YbBDX2Vzdnql/aSeHMQy1BMl1lYQIP0Wa6d9fCE+QKCAQEAuJ/VPc6jpQ9eZsfX
|
||||
fvY0HGrfcZQdtVXLr/ZzlI8i5QSgA15UeiwWNu6xJ0TH14KWRl0r424pp+Z3G4sx
|
||||
yZTvJi/X5XVKz9HyNnayXVqK9LNwb1f6WggLC/OWB02i3yDBjthoOPyYlOKa5cdi
|
||||
1bfJOsdAC73GeUxCJ+UUE4ujbpxQM1VmfdLAVj02m//lndNLQloe13eYY9Uig7sV
|
||||
bOPqzrRylzzichjVCjzNcRq39U7UnzRp2dG/DURgEQl5l8FWZtpVrd1/vrzEnua/
|
||||
4bJ3LHUoNNdNLhQz4Y5RavH0GMAJcODRHPCqfu7YdWOdpoLoTRTa5tXdgMYGRlrw
|
||||
YbQSoQKCAQEA1HWaKIiX/0TLiFdJGQooIS7bcnIHmYPquRQU8yX+ia1AeqXxXqFu
|
||||
0vvyMBdDVCrIGshsM6vWrnLZi3UkXjF2fembN6HvCFmgAqzB//rDkWzGxbZKYNRI
|
||||
fTEzpAtXuRtqLWQJN7tYzwjO4jcYpiEkqKIw9vi+OSBld6pUN5DloaGzPnHgdtv0
|
||||
hNHmt2wmHALO3YyxqMoTefBAE6ohV/q4Ec+6Hfeq5sxUKdOR7RpTHxZR/a+vKI23
|
||||
PYNEcncwJZCgkY8OE0kjlJpM/uDSBVtrjJwRwsJ4kobsKJV/awNT+34E3rJ7csy2
|
||||
Pm1Lypx3tsPRMTytAhOQZ0Uv5VWC3eN0YQKCAQAge52QXJe1xYFudWQ0zwWqSpz+
|
||||
HUD8MStNFgsPiJ47ufa/wJOHJaIh0qw9iEWsreEjt0jEBwI3NdzJqtElH9Itp8YG
|
||||
F6K1/AtM2roEuck7KiGu7ouDaA4kSupG60ZK2wn2tmmOqcYYbDtiCcDjhzXQIadQ
|
||||
CxCyahQ0Pg9JDM2TLeTgnvn5pCjke+0tQfMYJKs4tQ2yO4NC4+6dhXAzObsrgxpd
|
||||
yw0Ybep9fUUvuhwWJkMWnvSV8FC+pr/uhcYS1zZcFY82vkFZwMJKFPJ2aghgER+/
|
||||
HWarSznekvG0leX8t4XefN9WV+Gj9G/isKgF7bYaI3/S3wK5IgQ+AYPx8TSb
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
block, _ := pem.Decode([]byte(certPem))
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
return nil, nil
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
priv, err := starcrypto.DecodePrivateKey([]byte(privPem), "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, nil
|
||||
}
|
||||
return cert, priv
|
||||
}
|
142
utils/cert_test.go
Normal file
142
utils/cert_test.go
Normal file
@ -0,0 +1,142 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"b612.me/starcrypto"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGenerateRootCA(t *testing.T) {
|
||||
hexStr := "B61220050612B612"
|
||||
data, _ := hex.DecodeString(hexStr)
|
||||
num := new(big.Int).SetBytes(data)
|
||||
var rootCsr = &x509.Certificate{
|
||||
Version: 3,
|
||||
SerialNumber: num,
|
||||
Subject: pkix.Name{
|
||||
Country: []string{"CN"},
|
||||
Locality: []string{"Asteroid B612"},
|
||||
Organization: []string{"B612.ME"},
|
||||
OrganizationalUnit: []string{"CA.B612.ME"},
|
||||
PostalCode: []string{"B612", "Star"},
|
||||
CommonName: "B612 Tools Root CA",
|
||||
SerialNumber: "B612TOOLSROOTCA",
|
||||
},
|
||||
NotBefore: time.Date(2000, 01, 01, 00, 00, 00, 00, time.UTC),
|
||||
NotAfter: time.Date(2100, 01, 01, 00, 00, 00, 00, time.UTC),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
MaxPathLenZero: false,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageAny,
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
x509.ExtKeyUsageCodeSigning,
|
||||
x509.ExtKeyUsageEmailProtection,
|
||||
x509.ExtKeyUsageIPSECEndSystem,
|
||||
x509.ExtKeyUsageIPSECTunnel,
|
||||
x509.ExtKeyUsageIPSECUser,
|
||||
x509.ExtKeyUsageTimeStamping,
|
||||
x509.ExtKeyUsageOCSPSigning,
|
||||
x509.ExtKeyUsageMicrosoftServerGatedCrypto,
|
||||
x509.ExtKeyUsageNetscapeServerGatedCrypto,
|
||||
x509.ExtKeyUsageMicrosoftCommercialCodeSigning,
|
||||
x509.ExtKeyUsageMicrosoftKernelCodeSigning,
|
||||
},
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
key, _, err := starcrypto.GenerateRsaKey(4096)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert, err := MakeCert(key, rootCsr, rootCsr, key.Public())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
priv, _ := starcrypto.EncodePrivateKey(key, "")
|
||||
fmt.Println(os.WriteFile("../bin/b612toolca.key", priv, 0644))
|
||||
fmt.Println(os.WriteFile("../bin/b612toolca.crt", cert, 0644))
|
||||
}
|
||||
|
||||
func TestGenerateMiddleCA(t *testing.T) {
|
||||
var interCsr = &x509.Certificate{
|
||||
Version: 3,
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: pkix.Name{
|
||||
Country: []string{"CN"},
|
||||
Locality: []string{"Asteroid B612"},
|
||||
Organization: []string{"B612.ME"},
|
||||
OrganizationalUnit: []string{"CA.B612.ME"},
|
||||
CommonName: "B612 Inter Tool CA",
|
||||
},
|
||||
NotBefore: time.Date(2000, 01, 01, 8, 00, 00, 00, time.UTC),
|
||||
NotAfter: time.Date(2077, 12, 31, 23, 59, 59, 00, time.UTC),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny, x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageClientAuth},
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
rsa, _, err := starcrypto.GenerateRsaKey(4096)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
caKey, caCrt, err := LoadB612CA()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cert, err := MakeCert(caKey, caCrt, interCsr, rsa.Public())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
priv, _ := starcrypto.EncodePrivateKey(rsa, "")
|
||||
os.WriteFile("../bin/toolinter.key", priv, 0644)
|
||||
os.WriteFile("../bin/toolinter.crt", cert, 0644)
|
||||
}
|
||||
|
||||
func MakeCert(caKey any, caCrt *x509.Certificate, csr *x509.Certificate, pub any) ([]byte, error) {
|
||||
der, err := x509.CreateCertificate(rand.Reader, csr, caCrt, pub, caKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certBlock := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
pemData := pem.EncodeToMemory(certBlock)
|
||||
return pemData, nil
|
||||
}
|
||||
|
||||
func LoadB612CA() (crypto.PrivateKey, *x509.Certificate, error) {
|
||||
caRootK, _ := os.ReadFile("../bin/b612toolca.key")
|
||||
caRootC, _ := os.ReadFile("../bin/b612toolca.crt")
|
||||
caKey, err := starcrypto.DecodePrivateKey(caRootK, "")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
block, _ := pem.Decode(caRootC)
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
return nil, nil, errors.New("Failed to decode PEM block containing the certificate")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return caKey, cert, nil
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
package version
|
||||
|
||||
var Version = "2.1.0.beta.18"
|
||||
var Version = "2.1.0.beta.19"
|
||||
|
Loading…
x
Reference in New Issue
Block a user