str.go 6.1 KB

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