str.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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. // NormalizeBillingPreference clamps the billing preference to valid values.
  94. func NormalizeBillingPreference(pref string) string {
  95. switch strings.TrimSpace(pref) {
  96. case "subscription_first", "wallet_first", "subscription_only", "wallet_only":
  97. return strings.TrimSpace(pref)
  98. default:
  99. return "subscription_first"
  100. }
  101. }
  102. // MaskEmail masks a user email to prevent PII leakage in logs
  103. // Returns "***masked***" if email is empty, otherwise shows only the domain part
  104. func MaskEmail(email string) string {
  105. if email == "" {
  106. return "***masked***"
  107. }
  108. // Find the @ symbol
  109. atIndex := strings.Index(email, "@")
  110. if atIndex == -1 {
  111. // No @ symbol found, return masked
  112. return "***masked***"
  113. }
  114. // Return only the domain part with @ symbol
  115. return "***@" + email[atIndex+1:]
  116. }
  117. // maskHostTail returns the tail parts of a domain/host that should be preserved.
  118. // It keeps 2 parts for likely country-code TLDs (e.g., co.uk, com.cn), otherwise keeps only the TLD.
  119. func maskHostTail(parts []string) []string {
  120. if len(parts) < 2 {
  121. return parts
  122. }
  123. lastPart := parts[len(parts)-1]
  124. secondLastPart := parts[len(parts)-2]
  125. if len(lastPart) == 2 && len(secondLastPart) <= 3 {
  126. // Likely country code TLD like co.uk, com.cn
  127. return []string{secondLastPart, lastPart}
  128. }
  129. return []string{lastPart}
  130. }
  131. // maskHostForURL collapses subdomains and keeps only masked prefix + preserved tail.
  132. // Example: api.openai.com -> ***.com, sub.domain.co.uk -> ***.co.uk
  133. func maskHostForURL(host string) string {
  134. parts := strings.Split(host, ".")
  135. if len(parts) < 2 {
  136. return "***"
  137. }
  138. tail := maskHostTail(parts)
  139. return "***." + strings.Join(tail, ".")
  140. }
  141. // maskHostForPlainDomain masks a plain domain and reflects subdomain depth with multiple ***.
  142. // Example: openai.com -> ***.com, api.openai.com -> ***.***.com, sub.domain.co.uk -> ***.***.co.uk
  143. func maskHostForPlainDomain(domain string) string {
  144. parts := strings.Split(domain, ".")
  145. if len(parts) < 2 {
  146. return domain
  147. }
  148. tail := maskHostTail(parts)
  149. numStars := len(parts) - len(tail)
  150. if numStars < 1 {
  151. numStars = 1
  152. }
  153. stars := strings.TrimSuffix(strings.Repeat("***.", numStars), ".")
  154. return stars + "." + strings.Join(tail, ".")
  155. }
  156. // MaskSensitiveInfo masks sensitive information like URLs, IPs, and domain names in a string
  157. // Example:
  158. // http://example.com -> http://***.com
  159. // https://api.test.org/v1/users/123?key=secret -> https://***.org/***/***/?key=***
  160. // https://sub.domain.co.uk/path/to/resource -> https://***.co.uk/***/***
  161. // 192.168.1.1 -> ***.***.***.***
  162. // openai.com -> ***.com
  163. // www.openai.com -> ***.***.com
  164. // api.openai.com -> ***.***.com
  165. func MaskSensitiveInfo(str string) string {
  166. // Mask URLs
  167. str = maskURLPattern.ReplaceAllStringFunc(str, func(urlStr string) string {
  168. u, err := url.Parse(urlStr)
  169. if err != nil {
  170. return urlStr
  171. }
  172. host := u.Host
  173. if host == "" {
  174. return urlStr
  175. }
  176. // Mask host with unified logic
  177. maskedHost := maskHostForURL(host)
  178. result := u.Scheme + "://" + maskedHost
  179. // Mask path
  180. if u.Path != "" && u.Path != "/" {
  181. pathParts := strings.Split(strings.Trim(u.Path, "/"), "/")
  182. maskedPathParts := make([]string, len(pathParts))
  183. for i := range pathParts {
  184. if pathParts[i] != "" {
  185. maskedPathParts[i] = "***"
  186. }
  187. }
  188. if len(maskedPathParts) > 0 {
  189. result += "/" + strings.Join(maskedPathParts, "/")
  190. }
  191. } else if u.Path == "/" {
  192. result += "/"
  193. }
  194. // Mask query parameters
  195. if u.RawQuery != "" {
  196. values, err := url.ParseQuery(u.RawQuery)
  197. if err != nil {
  198. // If can't parse query, just mask the whole query string
  199. result += "?***"
  200. } else {
  201. maskedParams := make([]string, 0, len(values))
  202. for key := range values {
  203. maskedParams = append(maskedParams, key+"=***")
  204. }
  205. if len(maskedParams) > 0 {
  206. result += "?" + strings.Join(maskedParams, "&")
  207. }
  208. }
  209. }
  210. return result
  211. })
  212. // Mask domain names without protocol (like openai.com, www.openai.com)
  213. str = maskDomainPattern.ReplaceAllStringFunc(str, func(domain string) string {
  214. return maskHostForPlainDomain(domain)
  215. })
  216. // Mask IP addresses
  217. str = maskIPPattern.ReplaceAllString(str, "***.***.***.***")
  218. // Mask API keys (e.g., "api_key:AIzaSyAAAaUooTUni8AdaOkSRMda30n_Q4vrV70" -> "api_key:***")
  219. str = maskApiKeyPattern.ReplaceAllString(str, "${1}api_key:***${3}")
  220. return str
  221. }