Files
SamWaf/utils/common.go
2025-05-22 16:03:12 +08:00

427 lines
10 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package utils
import (
"SamWaf/common/zlog"
"SamWaf/global"
"SamWaf/model"
"crypto/tls"
"errors"
"fmt"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"github.com/oschwald/geoip2-golang"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
func GetExternalIp() string {
resp, err := http.Get("http://myexternalip.com/raw")
if err != nil {
return ""
}
defer resp.Body.Close()
//io.Copy(os.Stdout, resp.Body)
body, _ := ioutil.ReadAll(resp.Body)
clientIP := fmt.Sprintf("%s", string(body))
return clientIP
}
func GetCurrentDir() string {
// 检测环境变量是否存在
envVar := "SamWafIDE"
if _, exists := os.LookupEnv(envVar); exists {
return "."
}
exePath, err := os.Executable()
if err != nil {
fmt.Println(fmt.Sprintf("Failed to get executable path:%v", err))
return ""
}
exeDir := filepath.Dir(exePath)
return exeDir
}
// CheckDebugEnvInfo 检测是否打印debug信息
func CheckDebugEnvInfo() bool {
// 检测环境变量是否存在
envVar := "SamWafIDEDebugLog"
if _, exists := os.LookupEnv(envVar); exists {
return true
}
return false
}
func GetServerByHosts(hosts model.Hosts) string {
if hosts.Ssl == 1 {
return "https"
} else {
return "http"
}
}
/*
*
计算两个自然天数的间隔数
*/
func DiffNatureDays(t1, t2 int64) int {
var SecondsOfDay int64 = 86400
if t1 == t2 {
return -1
}
if t1 > t2 {
t1, t2 = t2, t1
}
diffDays := 0
secDiff := t2 - t1
if secDiff > SecondsOfDay {
tmpDays := int(secDiff / SecondsOfDay)
t1 += int64(tmpDays) * SecondsOfDay
diffDays += tmpDays
}
st := time.Unix(t1, 0)
et := time.Unix(t2, 0)
dateFormatTpl := "20060102"
if st.Format(dateFormatTpl) != et.Format(dateFormatTpl) {
diffDays += 1
}
return diffDays
}
/**字符串->时间对象*/
func Str2Time(formatTimeStr string) time.Time {
timeLayout := "20060102"
loc, _ := time.LoadLocation("Local")
theTime, _ := time.ParseInLocation(timeLayout, formatTimeStr, loc) //使用模板在对应时区转化为time.time类型
return theTime
}
/*
*
时间转int天
*/
func TimeToDayInt(t time.Time) int {
day, _ := strconv.Atoi(t.Format("20060102"))
return day
}
func GetPublicIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return "localhost"
// log.Fatal(err)
}
defer conn.Close()
localAddr := conn.LocalAddr().String()
idx := strings.LastIndex(localAddr, ":")
return localAddr[0:idx]
}
func GetCountry(ip string) []string {
if IsValidIPv6(ip) {
a := "ipv6|ipv6|ipv6|ipv6|ipv6"
if global.GCACHE_IPV6_SEARCHER == nil {
db, err := geoip2.FromBytes(global.GCACHE_IP_V6_COUNTRY_CBUFF)
if err != nil {
zlog.Error("Failed to open GeoLite2-Country.mmdb:", err)
return strings.Split(a, "|")
}
global.GCACHE_IPV6_SEARCHER = db
}
ipv6 := net.ParseIP(ip)
record, err := global.GCACHE_IPV6_SEARCHER.Country(ipv6)
if err != nil {
zlog.Error("Failed to Search GeoLite2-Country.mmdb:", err)
return strings.Split(a, "|")
}
if record.Country.Names == nil {
a = "内网" + "||||"
} else {
a = record.Country.Names["zh-CN"] + "||||"
}
return strings.Split(a, "|")
}
//IPV4得查询逻辑
if global.GCACHE_IPV4_SEARCHER == nil {
// 2、用全局的 cBuff 创建完全基于内存的查询对象。
searcher, err := xdb.NewWithBuffer(global.GCACHE_IP_CBUFF)
if err != nil {
fmt.Printf("failed to create searcher with content: %s\n", err)
}
global.GCACHE_IPV4_SEARCHER = searcher
}
// do the search
var tStart = time.Now()
// 备注:并发使用,每个 goroutine 需要创建一个独立的 searcher 对象。
region, err := global.GCACHE_IPV4_SEARCHER.SearchByStr(ip)
if err != nil {
fmt.Printf("failed to SearchIP(%s): %s\n", ip, err)
return []string{"无", "无"}
}
zlog.Debug("{region: %s, took: %s}\n", region, time.Since(tStart))
regions := strings.Split(region, "|")
//如果是内网IP情况下显示内网的内容
if regions[4] == "内网IP" {
regions[0] = "内网"
regions[1] = "内网"
regions[2] = "内网"
}
return regions
}
// CloseIPDatabase 关闭IP数据库
func CloseIPDatabase() {
if global.GCACHE_IPV4_SEARCHER != nil {
global.GCACHE_IPV4_SEARCHER.Close()
}
if global.GCACHE_IPV6_SEARCHER != nil {
err := global.GCACHE_IPV6_SEARCHER.Close()
if err != nil {
return
}
}
}
/*
*
通过ip段CIDR来查询是否在很多CIDR段内
*/
func CheckIPInRanges(ip string, ipRanges []string) bool {
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return false // 如果IP地址解析失败则返回false
}
for _, ipRange := range ipRanges {
_, ipNet, err := net.ParseCIDR(ipRange)
if err != nil {
return false // 如果CIDR格式解析失败则返回false
}
if ipNet.Contains(parsedIP) {
return true // 如果IP地址在指定的CIDR范围内则返回true
}
}
return false // 如果IP地址不在任何一个CIDR范围内则返回false
}
/*
*
通过ip段CIDR来查询是否在这个段内
*/
func CheckIPInCIDR(ip string, ipRange string) bool {
parts := strings.Split(ipRange, "/")
if len(parts) != 2 {
// 如果是普通ip
if ip == ipRange {
return true
} else {
return false
}
} else {
//如果是网段IP
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return false // 如果IP地址解析失败则返回false
}
_, ipNet, err := net.ParseCIDR(ipRange)
if err != nil {
return false // 如果CIDR格式解析失败则返回false
}
if ipNet.Contains(parsedIP) {
return true // 如果IP地址在指定的CIDR范围内则返回true
}
return false // 如果IP地址不在任何一个CIDR范围内则返回false
}
}
// IsValidIPOrNetwork 检查给定的字符串是否为有效的 IP 地址或 IP 段CIDR
func IsValidIPOrNetwork(input string) (bool, string) {
// 尝试解析为 IP 地址
if ip := net.ParseIP(input); ip != nil {
return true, "valid IP address"
}
// 尝试解析为 IP 段CIDR
_, _, err := net.ParseCIDR(input)
if err == nil {
return true, "valid IP network (CIDR)"
}
return false, "not a valid IP or IP network (CIDR)"
}
// 检查 JSON 字符串是否为有效的 UTF-8 编码
func CheckJSONValidity(jsonStr []byte) bool {
for i := 0; i < len(jsonStr); {
r, size := rune(jsonStr[i]), 1
switch {
case r >= 0xF0:
r, size = rune(jsonStr[i+3]), 4
case r >= 0xE0:
r, size = rune(jsonStr[i+2]), 3
case r >= 0xC0:
r, size = rune(jsonStr[i+1]), 2
default:
}
if r > 0xFFFF {
return false
}
i += size
}
return true
}
/*
*
删除老旧数据
*/
func DeleteOldFiles(dir string, duration time.Duration) error {
// 遍历目录下的文件
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 如果是文件并且文件创建时间超过指定的时间
if !info.IsDir() {
// 获取文件的修改时间(可以近似认为是创建时间)
fileModTime := info.ModTime()
// 判断文件是否超过指定的时间比如30分钟
if time.Since(fileModTime) > duration {
// 删除文件
zlog.Info("删除陈旧文件:", path)
err := os.Remove(path)
if err != nil {
return err
}
}
}
return nil
})
}
// 检测是否属于ipv4
func IsValidIPv4(ip string) bool {
parsedIP := net.ParseIP(ip)
return parsedIP != nil && parsedIP.To4() != nil
}
// 验证IPv6地址是否有效
func IsValidIPv6(ip string) bool {
parsedIP := net.ParseIP(ip)
// 如果解析出的 IP 类型是 IPv6 且不是 IPv4 映射地址IPv4-mapped IPv6 addresses
return parsedIP != nil && strings.Contains(ip, ":") && parsedIP.To4() == nil
}
// 获取纯域名
func GetPureDomain(host string) string {
// 检查是否包含端口号
if strings.Contains(host, ":") {
host, _, _ = net.SplitHostPort(host)
}
return host
}
// UpdateFileIsHasNewInfo 检查文件内容并更新
func UpdateFileIsHasNewInfo(filePath, newContent string) error {
// 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) {
// 文件不存在,写入新内容
if err := ioutil.WriteFile(filePath, []byte(newContent), 0644); err != nil {
return fmt.Errorf("failed to write file %s: %v", filePath, err)
}
} else {
// 文件存在,读取当前内容
currentContent, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("failed to read file %s: %v", filePath, err)
}
// 比较内容是否一致
if string(currentContent) != newContent {
// 内容不一致,更新文件
if err := ioutil.WriteFile(filePath, []byte(newContent), 0644); err != nil {
return fmt.Errorf("failed to update file %s: %v", filePath, err)
}
}
}
return nil
}
// IsValidChallengeFile 检查challenge文件是否合法
func IsValidChallengeFile(challengeFile string) bool {
// 定义一个正则表达式:允许字母、数字、连字符、下划线,且长度在 1 到 255 之间
// 这里假设合法的文件名不含有特殊字符和空格
re := regexp.MustCompile(`^[a-zA-Z0-9_-]{1,255}$`)
// 判断文件名是否符合正则表达式
return re.MatchString(challengeFile)
}
// CheckPathAndCreate 检测路径如果不存在则创建
func CheckPathAndCreate(path string) error {
// 检测目录是否存在
_, err := os.Stat(path)
if os.IsNotExist(err) {
// 目录不存在,创建目录
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return errors.New("创建目录失败" + err.Error())
}
return nil
} else if err != nil {
// 发生其他错误
return errors.New("检查目录失败" + err.Error())
} else {
return nil
}
}
// CheckSSLCertificateExpiry 检查指定主机的 SSL 证书到期情况
// host: 需要检查的主机地址,格式为 "ssl.samwaf.com:443"
func CheckSSLCertificateExpiry(host string) (time.Time, error) {
conn, err := tls.Dial("tcp", host, nil)
if err != nil {
return time.Time{}, fmt.Errorf("无法连接到主机 %s: %v", host, err)
}
defer conn.Close()
// 获取 SSL 证书的到期时间
cert := conn.ConnectionState().PeerCertificates[0]
expiryDate := cert.NotAfter
// 返回到期时间
return expiryDate, nil
}
// GetSourceClientIP 获取原始IP
func GetSourceClientIP(ipAndPort string) string {
ip, _, err := net.SplitHostPort(ipAndPort)
if err != nil {
return ""
} else {
return ip
}
}