|
|
|
|
|
package utils |
|
|
|
|
|
import ( |
|
|
"archive/tar" |
|
|
"archive/zip" |
|
|
"bufio" |
|
|
"bytes" |
|
|
"compress/gzip" |
|
|
"context" |
|
|
"encoding/base64" |
|
|
"fmt" |
|
|
"io" |
|
|
"net" |
|
|
"os" |
|
|
"os/exec" |
|
|
"path/filepath" |
|
|
"runtime" |
|
|
"strconv" |
|
|
"strings" |
|
|
"time" |
|
|
|
|
|
"github.com/Tencent/AI-Infra-Guard/internal/gologger" |
|
|
|
|
|
"github.com/spaolacci/murmur3" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
func Duration2String(t time.Duration) string { |
|
|
sceond := t.Seconds() |
|
|
if sceond >= 60 { |
|
|
return fmt.Sprintf("%.2f min", t.Minutes()) |
|
|
} else { |
|
|
return fmt.Sprintf("%.2f s", sceond) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func InsertInto(s string, interval int, sep rune) string { |
|
|
var buffer bytes.Buffer |
|
|
before := interval - 1 |
|
|
last := len(s) - 1 |
|
|
for i, char := range s { |
|
|
buffer.WriteRune(char) |
|
|
if i%interval == before && i != last { |
|
|
buffer.WriteRune(sep) |
|
|
} |
|
|
} |
|
|
buffer.WriteRune(sep) |
|
|
return buffer.String() |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func FaviconHash(data []byte) int32 { |
|
|
stdBase64 := base64.StdEncoding.EncodeToString(data) |
|
|
stdBase64 = InsertInto(stdBase64, 76, '\n') |
|
|
hasher := murmur3.New32WithSeed(0) |
|
|
hasher.Write([]byte(stdBase64)) |
|
|
return int32(hasher.Sum32()) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func ScanDir(path string) ([]string, error) { |
|
|
files := make([]string, 0) |
|
|
dir, err := os.ReadDir(path) |
|
|
if err != nil { |
|
|
return nil, err |
|
|
} |
|
|
for _, fi := range dir { |
|
|
if fi.IsDir() { |
|
|
newDir, err := ScanDir(filepath.Join(path, fi.Name())) |
|
|
if err != nil { |
|
|
return files, err |
|
|
} |
|
|
files = append(files, newDir...) |
|
|
} else { |
|
|
files = append(files, filepath.Join(path, fi.Name())) |
|
|
} |
|
|
} |
|
|
return files, nil |
|
|
} |
|
|
|
|
|
|
|
|
func IsCIDR(cidr string) bool { |
|
|
_, _, err := net.ParseCIDR(cidr) |
|
|
return err == nil |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func IsFileExists(path string) bool { |
|
|
_, err := os.Stat(path) |
|
|
if err != nil { |
|
|
if os.IsExist(err) { |
|
|
return true |
|
|
} |
|
|
return false |
|
|
} |
|
|
return true |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func IsDir(path string) bool { |
|
|
s, err := os.Stat(path) |
|
|
if err != nil { |
|
|
return false |
|
|
} |
|
|
return s.IsDir() |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TrimProtocol(targetURL string) string { |
|
|
URL := strings.TrimSpace(targetURL) |
|
|
if strings.HasPrefix(strings.ToLower(URL), "http://") || strings.HasPrefix(strings.ToLower(URL), "https://") { |
|
|
URL = URL[strings.Index(URL, "//")+2:] |
|
|
} |
|
|
URL = strings.TrimRight(URL, "/") |
|
|
return URL |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func CompareVersions(version1, version2 string) int { |
|
|
v1Parts := strings.Split(version1, ".") |
|
|
v2Parts := strings.Split(version2, ".") |
|
|
|
|
|
|
|
|
maxLen := len(v1Parts) |
|
|
if len(v2Parts) > maxLen { |
|
|
maxLen = len(v2Parts) |
|
|
} |
|
|
|
|
|
for i := 0; i < maxLen; i++ { |
|
|
var num1, num2 int |
|
|
|
|
|
if i < len(v1Parts) { |
|
|
num1, _ = strconv.Atoi(v1Parts[i]) |
|
|
} |
|
|
|
|
|
if i < len(v2Parts) { |
|
|
num2, _ = strconv.Atoi(v2Parts[i]) |
|
|
} |
|
|
|
|
|
if num1 > num2 { |
|
|
return 1 |
|
|
} else if num1 < num2 { |
|
|
return -1 |
|
|
} |
|
|
} |
|
|
|
|
|
return 0 |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func GetMiddleText(left, right, html string) string { |
|
|
start := strings.Index(html, left) |
|
|
if start == -1 { |
|
|
return "" |
|
|
} |
|
|
start += len(left) |
|
|
|
|
|
end := strings.Index(html[start:], right) |
|
|
if end == -1 { |
|
|
return "" |
|
|
} |
|
|
end += start |
|
|
|
|
|
return html[start:end] |
|
|
} |
|
|
|
|
|
|
|
|
type PortInfo struct { |
|
|
Port int |
|
|
Address string |
|
|
} |
|
|
|
|
|
|
|
|
func GetLocalOpenPorts() ([]PortInfo, error) { |
|
|
var portInfos []PortInfo |
|
|
switch runtime.GOOS { |
|
|
case "windows": |
|
|
cmd := exec.Command("netstat", "-an") |
|
|
output, err := cmd.Output() |
|
|
if err != nil { |
|
|
return nil, fmt.Errorf("执行netstat命令失败: %v", err) |
|
|
} |
|
|
|
|
|
scanner := bufio.NewScanner(strings.NewReader(string(output))) |
|
|
for scanner.Scan() { |
|
|
line := scanner.Text() |
|
|
if strings.Contains(line, "LISTENING") { |
|
|
parts := strings.Fields(line) |
|
|
if len(parts) >= 2 { |
|
|
addrPort := strings.Split(parts[1], ":") |
|
|
if len(addrPort) == 2 { |
|
|
port, err := strconv.Atoi(addrPort[1]) |
|
|
if err == nil { |
|
|
addr := addrPort[0] |
|
|
portInfos = append(portInfos, PortInfo{ |
|
|
Port: port, |
|
|
Address: addr, |
|
|
}) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
case "darwin", "linux": |
|
|
cmd := exec.Command("lsof", "-i", "-P", "-n") |
|
|
output, err := cmd.Output() |
|
|
if err != nil { |
|
|
return nil, fmt.Errorf("执行lsof命令失败: %v", err) |
|
|
} |
|
|
|
|
|
scanner := bufio.NewScanner(strings.NewReader(string(output))) |
|
|
for scanner.Scan() { |
|
|
line := scanner.Text() |
|
|
if strings.Contains(line, "LISTEN") { |
|
|
parts := strings.Fields(line) |
|
|
for _, part := range parts { |
|
|
if strings.Contains(part, ":") { |
|
|
addrPort := strings.Split(part, ":") |
|
|
if len(addrPort) == 2 { |
|
|
port, err := strconv.Atoi(addrPort[1]) |
|
|
if err == nil { |
|
|
addr := addrPort[0] |
|
|
if addr == "*" || addr == "0.0.0.0" { |
|
|
addr = "0.0.0.0" |
|
|
} else if addr == "127.0.0.1" || addr == "localhost" { |
|
|
addr = "127.0.0.1" |
|
|
} |
|
|
portInfos = append(portInfos, PortInfo{ |
|
|
Port: port, |
|
|
Address: addr, |
|
|
}) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
default: |
|
|
return nil, fmt.Errorf("不支持的操作系统: %s", runtime.GOOS) |
|
|
} |
|
|
|
|
|
|
|
|
seen := make(map[string]bool) |
|
|
var result []PortInfo |
|
|
for _, info := range portInfos { |
|
|
key := fmt.Sprintf("%s:%d", info.Address, info.Port) |
|
|
if !seen[key] { |
|
|
seen[key] = true |
|
|
result = append(result, info) |
|
|
} |
|
|
} |
|
|
|
|
|
return result, nil |
|
|
} |
|
|
|
|
|
|
|
|
func ExtractZipFile(zipFile string, destPath string) error { |
|
|
|
|
|
reader, err := zip.OpenReader(zipFile) |
|
|
if err != nil { |
|
|
return fmt.Errorf("打开ZIP文件失败: %v", err) |
|
|
} |
|
|
defer reader.Close() |
|
|
|
|
|
|
|
|
if err := os.MkdirAll(destPath, 0755); err != nil { |
|
|
return fmt.Errorf("创建目标目录失败: %v", err) |
|
|
} |
|
|
|
|
|
|
|
|
for _, file := range reader.File { |
|
|
|
|
|
filePath := filepath.Join(destPath, file.Name) |
|
|
if !strings.HasPrefix(filePath, filepath.Clean(destPath)+string(os.PathSeparator)) { |
|
|
gologger.Errorln(fmt.Sprintf("不安全的路径: %s", file.Name)) |
|
|
continue |
|
|
} |
|
|
|
|
|
|
|
|
if file.FileInfo().IsDir() { |
|
|
if err := os.MkdirAll(filePath, 0755); err != nil { |
|
|
return fmt.Errorf("创建目录失败: %v", err) |
|
|
} |
|
|
continue |
|
|
} |
|
|
|
|
|
|
|
|
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { |
|
|
return fmt.Errorf("创建父目录失败: %v", err) |
|
|
} |
|
|
|
|
|
|
|
|
outFile, err := os.Create(filePath) |
|
|
if err != nil { |
|
|
return fmt.Errorf("创建文件失败: %v", err) |
|
|
} |
|
|
defer outFile.Close() |
|
|
|
|
|
|
|
|
rc, err := file.Open() |
|
|
if err != nil { |
|
|
return fmt.Errorf("打开压缩文件内容失败: %v", err) |
|
|
} |
|
|
defer rc.Close() |
|
|
|
|
|
|
|
|
if _, err := io.Copy(outFile, rc); err != nil { |
|
|
return fmt.Errorf("复制文件内容失败: %v", err) |
|
|
} |
|
|
} |
|
|
|
|
|
return nil |
|
|
} |
|
|
|
|
|
|
|
|
func ExtractTGZ(src, dest string) error { |
|
|
|
|
|
file, err := os.Open(src) |
|
|
if err != nil { |
|
|
return err |
|
|
} |
|
|
defer file.Close() |
|
|
|
|
|
|
|
|
gzr, err := gzip.NewReader(file) |
|
|
if err != nil { |
|
|
return err |
|
|
} |
|
|
defer gzr.Close() |
|
|
|
|
|
|
|
|
tr := tar.NewReader(gzr) |
|
|
|
|
|
|
|
|
for { |
|
|
header, err := tr.Next() |
|
|
if err == io.EOF { |
|
|
break |
|
|
} |
|
|
if err != nil { |
|
|
return err |
|
|
} |
|
|
|
|
|
|
|
|
targetPath, err := safePath(dest, header.Name) |
|
|
if err != nil { |
|
|
return err |
|
|
} |
|
|
|
|
|
|
|
|
switch header.Typeflag { |
|
|
case tar.TypeDir: |
|
|
if err := os.MkdirAll(targetPath, 0755); err != nil { |
|
|
return err |
|
|
} |
|
|
case tar.TypeReg: |
|
|
if err := writeFile(targetPath, tr, header.Mode); err != nil { |
|
|
return err |
|
|
} |
|
|
|
|
|
default: |
|
|
fmt.Printf("未处理类型: %v in %s\n", header.Typeflag, header.Name) |
|
|
} |
|
|
} |
|
|
return nil |
|
|
} |
|
|
|
|
|
|
|
|
func safePath(dest, name string) (string, error) { |
|
|
targetPath := filepath.Join(dest, name) |
|
|
cleanedPath := filepath.Clean(targetPath) |
|
|
dest = filepath.Clean(dest) |
|
|
|
|
|
|
|
|
if !strings.HasPrefix(cleanedPath, dest+string(os.PathSeparator)) && cleanedPath != dest { |
|
|
return "", fmt.Errorf("非法路径: %s", name) |
|
|
} |
|
|
return targetPath, nil |
|
|
} |
|
|
|
|
|
|
|
|
func writeFile(path string, r io.Reader, mode int64) error { |
|
|
|
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { |
|
|
return err |
|
|
} |
|
|
|
|
|
|
|
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(mode)) |
|
|
if err != nil { |
|
|
return err |
|
|
} |
|
|
defer file.Close() |
|
|
|
|
|
|
|
|
if _, err := io.Copy(file, r); err != nil { |
|
|
return err |
|
|
} |
|
|
return nil |
|
|
} |
|
|
|
|
|
|
|
|
func GitClone(repoURL, targetDir string, timeout time.Duration) error { |
|
|
var err error |
|
|
for i := 0; i < 3; i++ { |
|
|
err = func() error { |
|
|
ctx := context.Background() |
|
|
ctx, cancel := context.WithTimeout(ctx, timeout) |
|
|
defer cancel() |
|
|
cmd := exec.CommandContext(ctx, "git", "clone", "--", repoURL, targetDir) |
|
|
done := make(chan error) |
|
|
go func() { |
|
|
_, err := cmd.CombinedOutput() |
|
|
done <- err |
|
|
}() |
|
|
|
|
|
select { |
|
|
case <-ctx.Done(): |
|
|
_ = cmd.Process.Kill() |
|
|
return fmt.Errorf("操作超时") |
|
|
case err = <-done: |
|
|
return err |
|
|
} |
|
|
}() |
|
|
if err == nil { |
|
|
return nil |
|
|
} |
|
|
} |
|
|
return err |
|
|
} |
|
|
|
|
|
func RunCmd(dir, name string, arg []string, callback func(line string)) error { |
|
|
|
|
|
cmd := exec.Command(name, arg...) |
|
|
cmd.Dir = dir |
|
|
cmd.Env = os.Environ() |
|
|
|
|
|
cmdStr := name + " " + strings.Join(arg, " ") |
|
|
gologger.Infof("开始执行命令: %s", cmdStr) |
|
|
|
|
|
stdout, err := cmd.StdoutPipe() |
|
|
if err != nil { |
|
|
return err |
|
|
} |
|
|
cmd.Stderr = cmd.Stdout |
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(stdout) |
|
|
|
|
|
|
|
|
const maxCapacity = 1024 * 1024 * 10 |
|
|
buf := make([]byte, 0, 64*1024) |
|
|
scanner.Buffer(buf, maxCapacity) |
|
|
|
|
|
done := make(chan error) |
|
|
go func() { |
|
|
defer close(done) |
|
|
for scanner.Scan() { |
|
|
line := scanner.Text() |
|
|
callback(line) |
|
|
} |
|
|
|
|
|
if err := scanner.Err(); err != nil { |
|
|
|
|
|
if strings.Contains(err.Error(), "file already closed") || |
|
|
strings.Contains(err.Error(), "broken pipe") { |
|
|
done <- nil |
|
|
return |
|
|
} |
|
|
done <- fmt.Errorf("读取输出时发生错误: %v", err) |
|
|
return |
|
|
} |
|
|
done <- nil |
|
|
}() |
|
|
|
|
|
|
|
|
if err = cmd.Start(); err != nil { |
|
|
return err |
|
|
} |
|
|
|
|
|
|
|
|
cmdErr := cmd.Wait() |
|
|
|
|
|
|
|
|
readErr := <-done |
|
|
|
|
|
|
|
|
if readErr != nil { |
|
|
return readErr |
|
|
} |
|
|
if cmdErr != nil { |
|
|
return cmdErr |
|
|
} |
|
|
|
|
|
return nil |
|
|
} |
|
|
|
|
|
func IsHostname(hostname string) bool { |
|
|
ips := strings.Split(hostname, ":") |
|
|
if len(ips) != 2 { |
|
|
return false |
|
|
} |
|
|
p := net.ParseIP(strings.TrimSpace(ips[0])) |
|
|
if p == nil { |
|
|
return false |
|
|
} |
|
|
return true |
|
|
} |
|
|
|
|
|
|
|
|
func StrInSlice(str string, list []string) bool { |
|
|
for _, v := range list { |
|
|
if v == str { |
|
|
return true |
|
|
} |
|
|
} |
|
|
return false |
|
|
} |
|
|
|