| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- package common
- import (
- "encoding/base64"
- "encoding/json"
- "net/url"
- "regexp"
- "strconv"
- "strings"
- "unsafe"
- "github.com/samber/lo"
- )
- var (
- maskURLPattern = regexp.MustCompile(`(http|https)://[^\s/$.?#].[^\s]*`)
- maskDomainPattern = regexp.MustCompile(`\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b`)
- maskIPPattern = regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`)
- )
- func GetStringIfEmpty(str string, defaultValue string) string {
- if str == "" {
- return defaultValue
- }
- return str
- }
- func GetRandomString(length int) string {
- if length <= 0 {
- return ""
- }
- return lo.RandomString(length, lo.AlphanumericCharset)
- }
- func MapToJsonStr(m map[string]interface{}) string {
- bytes, err := json.Marshal(m)
- if err != nil {
- return ""
- }
- return string(bytes)
- }
- func StrToMap(str string) (map[string]interface{}, error) {
- m := make(map[string]interface{})
- err := Unmarshal([]byte(str), &m)
- if err != nil {
- return nil, err
- }
- return m, nil
- }
- func StrToJsonArray(str string) ([]interface{}, error) {
- var js []interface{}
- err := json.Unmarshal([]byte(str), &js)
- if err != nil {
- return nil, err
- }
- return js, nil
- }
- func IsJsonArray(str string) bool {
- var js []interface{}
- return json.Unmarshal([]byte(str), &js) == nil
- }
- func IsJsonObject(str string) bool {
- var js map[string]interface{}
- return json.Unmarshal([]byte(str), &js) == nil
- }
- func String2Int(str string) int {
- num, err := strconv.Atoi(str)
- if err != nil {
- return 0
- }
- return num
- }
- func StringsContains(strs []string, str string) bool {
- for _, s := range strs {
- if s == str {
- return true
- }
- }
- return false
- }
- // StringToByteSlice []byte only read, panic on append
- func StringToByteSlice(s string) []byte {
- tmp1 := (*[2]uintptr)(unsafe.Pointer(&s))
- tmp2 := [3]uintptr{tmp1[0], tmp1[1], tmp1[1]}
- return *(*[]byte)(unsafe.Pointer(&tmp2))
- }
- func EncodeBase64(str string) string {
- return base64.StdEncoding.EncodeToString([]byte(str))
- }
- func GetJsonString(data any) string {
- if data == nil {
- return ""
- }
- b, _ := json.Marshal(data)
- return string(b)
- }
- // MaskEmail masks a user email to prevent PII leakage in logs
- // Returns "***masked***" if email is empty, otherwise shows only the domain part
- func MaskEmail(email string) string {
- if email == "" {
- return "***masked***"
- }
- // Find the @ symbol
- atIndex := strings.Index(email, "@")
- if atIndex == -1 {
- // No @ symbol found, return masked
- return "***masked***"
- }
- // Return only the domain part with @ symbol
- return "***@" + email[atIndex+1:]
- }
- // maskHostTail returns the tail parts of a domain/host that should be preserved.
- // It keeps 2 parts for likely country-code TLDs (e.g., co.uk, com.cn), otherwise keeps only the TLD.
- func maskHostTail(parts []string) []string {
- if len(parts) < 2 {
- return parts
- }
- lastPart := parts[len(parts)-1]
- secondLastPart := parts[len(parts)-2]
- if len(lastPart) == 2 && len(secondLastPart) <= 3 {
- // Likely country code TLD like co.uk, com.cn
- return []string{secondLastPart, lastPart}
- }
- return []string{lastPart}
- }
- // maskHostForURL collapses subdomains and keeps only masked prefix + preserved tail.
- // Example: api.openai.com -> ***.com, sub.domain.co.uk -> ***.co.uk
- func maskHostForURL(host string) string {
- parts := strings.Split(host, ".")
- if len(parts) < 2 {
- return "***"
- }
- tail := maskHostTail(parts)
- return "***." + strings.Join(tail, ".")
- }
- // maskHostForPlainDomain masks a plain domain and reflects subdomain depth with multiple ***.
- // Example: openai.com -> ***.com, api.openai.com -> ***.***.com, sub.domain.co.uk -> ***.***.co.uk
- func maskHostForPlainDomain(domain string) string {
- parts := strings.Split(domain, ".")
- if len(parts) < 2 {
- return domain
- }
- tail := maskHostTail(parts)
- numStars := len(parts) - len(tail)
- if numStars < 1 {
- numStars = 1
- }
- stars := strings.TrimSuffix(strings.Repeat("***.", numStars), ".")
- return stars + "." + strings.Join(tail, ".")
- }
- // MaskSensitiveInfo masks sensitive information like URLs, IPs, and domain names in a string
- // Example:
- // http://example.com -> http://***.com
- // https://api.test.org/v1/users/123?key=secret -> https://***.org/***/***/?key=***
- // https://sub.domain.co.uk/path/to/resource -> https://***.co.uk/***/***
- // 192.168.1.1 -> ***.***.***.***
- // openai.com -> ***.com
- // www.openai.com -> ***.***.com
- // api.openai.com -> ***.***.com
- func MaskSensitiveInfo(str string) string {
- // Mask URLs
- str = maskURLPattern.ReplaceAllStringFunc(str, func(urlStr string) string {
- u, err := url.Parse(urlStr)
- if err != nil {
- return urlStr
- }
- host := u.Host
- if host == "" {
- return urlStr
- }
- // Mask host with unified logic
- maskedHost := maskHostForURL(host)
- result := u.Scheme + "://" + maskedHost
- // Mask path
- if u.Path != "" && u.Path != "/" {
- pathParts := strings.Split(strings.Trim(u.Path, "/"), "/")
- maskedPathParts := make([]string, len(pathParts))
- for i := range pathParts {
- if pathParts[i] != "" {
- maskedPathParts[i] = "***"
- }
- }
- if len(maskedPathParts) > 0 {
- result += "/" + strings.Join(maskedPathParts, "/")
- }
- } else if u.Path == "/" {
- result += "/"
- }
- // Mask query parameters
- if u.RawQuery != "" {
- values, err := url.ParseQuery(u.RawQuery)
- if err != nil {
- // If can't parse query, just mask the whole query string
- result += "?***"
- } else {
- maskedParams := make([]string, 0, len(values))
- for key := range values {
- maskedParams = append(maskedParams, key+"=***")
- }
- if len(maskedParams) > 0 {
- result += "?" + strings.Join(maskedParams, "&")
- }
- }
- }
- return result
- })
- // Mask domain names without protocol (like openai.com, www.openai.com)
- str = maskDomainPattern.ReplaceAllStringFunc(str, func(domain string) string {
- return maskHostForPlainDomain(domain)
- })
- // Mask IP addresses
- str = maskIPPattern.ReplaceAllString(str, "***.***.***.***")
- return str
- }
|