str.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package common
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "net/url"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. "unsafe"
  10. "github.com/samber/lo"
  11. )
  12. var (
  13. maskURLPattern = regexp.MustCompile(`(http|https)://[^\s/$.?#].[^\s]*`)
  14. maskDomainPattern = regexp.MustCompile(`\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b`)
  15. maskIPPattern = regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`)
  16. )
  17. func GetStringIfEmpty(str string, defaultValue string) string {
  18. if str == "" {
  19. return defaultValue
  20. }
  21. return str
  22. }
  23. func GetRandomString(length int) string {
  24. if length <= 0 {
  25. return ""
  26. }
  27. return lo.RandomString(length, lo.AlphanumericCharset)
  28. }
  29. func MapToJsonStr(m map[string]interface{}) string {
  30. bytes, err := json.Marshal(m)
  31. if err != nil {
  32. return ""
  33. }
  34. return string(bytes)
  35. }
  36. func StrToMap(str string) (map[string]interface{}, error) {
  37. m := make(map[string]interface{})
  38. err := Unmarshal([]byte(str), &m)
  39. if err != nil {
  40. return nil, err
  41. }
  42. return m, nil
  43. }
  44. func StrToJsonArray(str string) ([]interface{}, error) {
  45. var js []interface{}
  46. err := json.Unmarshal([]byte(str), &js)
  47. if err != nil {
  48. return nil, err
  49. }
  50. return js, nil
  51. }
  52. func IsJsonArray(str string) bool {
  53. var js []interface{}
  54. return json.Unmarshal([]byte(str), &js) == nil
  55. }
  56. func IsJsonObject(str string) bool {
  57. var js map[string]interface{}
  58. return json.Unmarshal([]byte(str), &js) == nil
  59. }
  60. func String2Int(str string) int {
  61. num, err := strconv.Atoi(str)
  62. if err != nil {
  63. return 0
  64. }
  65. return num
  66. }
  67. func StringsContains(strs []string, str string) bool {
  68. for _, s := range strs {
  69. if s == str {
  70. return true
  71. }
  72. }
  73. return false
  74. }
  75. // StringToByteSlice []byte only read, panic on append
  76. func StringToByteSlice(s string) []byte {
  77. tmp1 := (*[2]uintptr)(unsafe.Pointer(&s))
  78. tmp2 := [3]uintptr{tmp1[0], tmp1[1], tmp1[1]}
  79. return *(*[]byte)(unsafe.Pointer(&tmp2))
  80. }
  81. func EncodeBase64(str string) string {
  82. return base64.StdEncoding.EncodeToString([]byte(str))
  83. }
  84. func GetJsonString(data any) string {
  85. if data == nil {
  86. return ""
  87. }
  88. b, _ := json.Marshal(data)
  89. return string(b)
  90. }
  91. // MaskEmail masks a user email to prevent PII leakage in logs
  92. // Returns "***masked***" if email is empty, otherwise shows only the domain part
  93. func MaskEmail(email string) string {
  94. if email == "" {
  95. return "***masked***"
  96. }
  97. // Find the @ symbol
  98. atIndex := strings.Index(email, "@")
  99. if atIndex == -1 {
  100. // No @ symbol found, return masked
  101. return "***masked***"
  102. }
  103. // Return only the domain part with @ symbol
  104. return "***@" + email[atIndex+1:]
  105. }
  106. // maskHostTail returns the tail parts of a domain/host that should be preserved.
  107. // It keeps 2 parts for likely country-code TLDs (e.g., co.uk, com.cn), otherwise keeps only the TLD.
  108. func maskHostTail(parts []string) []string {
  109. if len(parts) < 2 {
  110. return parts
  111. }
  112. lastPart := parts[len(parts)-1]
  113. secondLastPart := parts[len(parts)-2]
  114. if len(lastPart) == 2 && len(secondLastPart) <= 3 {
  115. // Likely country code TLD like co.uk, com.cn
  116. return []string{secondLastPart, lastPart}
  117. }
  118. return []string{lastPart}
  119. }
  120. // maskHostForURL collapses subdomains and keeps only masked prefix + preserved tail.
  121. // Example: api.openai.com -> ***.com, sub.domain.co.uk -> ***.co.uk
  122. func maskHostForURL(host string) string {
  123. parts := strings.Split(host, ".")
  124. if len(parts) < 2 {
  125. return "***"
  126. }
  127. tail := maskHostTail(parts)
  128. return "***." + strings.Join(tail, ".")
  129. }
  130. // maskHostForPlainDomain masks a plain domain and reflects subdomain depth with multiple ***.
  131. // Example: openai.com -> ***.com, api.openai.com -> ***.***.com, sub.domain.co.uk -> ***.***.co.uk
  132. func maskHostForPlainDomain(domain string) string {
  133. parts := strings.Split(domain, ".")
  134. if len(parts) < 2 {
  135. return domain
  136. }
  137. tail := maskHostTail(parts)
  138. numStars := len(parts) - len(tail)
  139. if numStars < 1 {
  140. numStars = 1
  141. }
  142. stars := strings.TrimSuffix(strings.Repeat("***.", numStars), ".")
  143. return stars + "." + strings.Join(tail, ".")
  144. }
  145. // MaskSensitiveInfo masks sensitive information like URLs, IPs, and domain names in a string
  146. // Example:
  147. // http://example.com -> http://***.com
  148. // https://api.test.org/v1/users/123?key=secret -> https://***.org/***/***/?key=***
  149. // https://sub.domain.co.uk/path/to/resource -> https://***.co.uk/***/***
  150. // 192.168.1.1 -> ***.***.***.***
  151. // openai.com -> ***.com
  152. // www.openai.com -> ***.***.com
  153. // api.openai.com -> ***.***.com
  154. func MaskSensitiveInfo(str string) string {
  155. // Mask URLs
  156. str = maskURLPattern.ReplaceAllStringFunc(str, func(urlStr string) string {
  157. u, err := url.Parse(urlStr)
  158. if err != nil {
  159. return urlStr
  160. }
  161. host := u.Host
  162. if host == "" {
  163. return urlStr
  164. }
  165. // Mask host with unified logic
  166. maskedHost := maskHostForURL(host)
  167. result := u.Scheme + "://" + maskedHost
  168. // Mask path
  169. if u.Path != "" && u.Path != "/" {
  170. pathParts := strings.Split(strings.Trim(u.Path, "/"), "/")
  171. maskedPathParts := make([]string, len(pathParts))
  172. for i := range pathParts {
  173. if pathParts[i] != "" {
  174. maskedPathParts[i] = "***"
  175. }
  176. }
  177. if len(maskedPathParts) > 0 {
  178. result += "/" + strings.Join(maskedPathParts, "/")
  179. }
  180. } else if u.Path == "/" {
  181. result += "/"
  182. }
  183. // Mask query parameters
  184. if u.RawQuery != "" {
  185. values, err := url.ParseQuery(u.RawQuery)
  186. if err != nil {
  187. // If can't parse query, just mask the whole query string
  188. result += "?***"
  189. } else {
  190. maskedParams := make([]string, 0, len(values))
  191. for key := range values {
  192. maskedParams = append(maskedParams, key+"=***")
  193. }
  194. if len(maskedParams) > 0 {
  195. result += "?" + strings.Join(maskedParams, "&")
  196. }
  197. }
  198. }
  199. return result
  200. })
  201. // Mask domain names without protocol (like openai.com, www.openai.com)
  202. str = maskDomainPattern.ReplaceAllStringFunc(str, func(domain string) string {
  203. return maskHostForPlainDomain(domain)
  204. })
  205. // Mask IP addresses
  206. str = maskIPPattern.ReplaceAllString(str, "***.***.***.***")
  207. return str
  208. }