You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

273 lines
6.9 KiB

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