helpers.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. package exprhelpers
  2. import (
  3. "bufio"
  4. "encoding/base64"
  5. "fmt"
  6. "math"
  7. "net"
  8. "net/url"
  9. "os"
  10. "path/filepath"
  11. "reflect"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/antonmedv/expr"
  17. "github.com/bluele/gcache"
  18. "github.com/c-robinson/iplib"
  19. "github.com/cespare/xxhash/v2"
  20. "github.com/davecgh/go-spew/spew"
  21. "github.com/prometheus/client_golang/prometheus"
  22. log "github.com/sirupsen/logrus"
  23. "github.com/umahmood/haversine"
  24. "github.com/wasilibs/go-re2"
  25. "github.com/crowdsecurity/go-cs-lib/ptr"
  26. "github.com/crowdsecurity/crowdsec/pkg/cache"
  27. "github.com/crowdsecurity/crowdsec/pkg/database"
  28. "github.com/crowdsecurity/crowdsec/pkg/fflag"
  29. "github.com/crowdsecurity/crowdsec/pkg/types"
  30. )
  31. var dataFile map[string][]string
  32. var dataFileRegex map[string][]*regexp.Regexp
  33. var dataFileRe2 map[string][]*re2.Regexp
  34. // This is used to (optionally) cache regexp results for RegexpInFile operations
  35. var dataFileRegexCache map[string]gcache.Cache = make(map[string]gcache.Cache)
  36. /*prometheus*/
  37. var RegexpCacheMetrics = prometheus.NewGaugeVec(
  38. prometheus.GaugeOpts{
  39. Name: "cs_regexp_cache_size",
  40. Help: "Entries per regexp cache.",
  41. },
  42. []string{"name"},
  43. )
  44. var dbClient *database.Client
  45. var exprFunctionOptions []expr.Option
  46. var keyValuePattern = regexp.MustCompile(`(?P<key>[^=\s]+)=(?:"(?P<quoted_value>[^"\\]*(?:\\.[^"\\]*)*)"|(?P<value>[^=\s]+)|\s*)`)
  47. func GetExprOptions(ctx map[string]interface{}) []expr.Option {
  48. if len(exprFunctionOptions) == 0 {
  49. exprFunctionOptions = []expr.Option{}
  50. for _, function := range exprFuncs {
  51. exprFunctionOptions = append(exprFunctionOptions,
  52. expr.Function(function.name,
  53. function.function,
  54. function.signature...,
  55. ))
  56. }
  57. }
  58. ret := []expr.Option{}
  59. ret = append(ret, exprFunctionOptions...)
  60. ret = append(ret, expr.Env(ctx))
  61. return ret
  62. }
  63. func Init(databaseClient *database.Client) error {
  64. dataFile = make(map[string][]string)
  65. dataFileRegex = make(map[string][]*regexp.Regexp)
  66. dataFileRe2 = make(map[string][]*re2.Regexp)
  67. dbClient = databaseClient
  68. return nil
  69. }
  70. func RegexpCacheInit(filename string, CacheCfg types.DataSource) error {
  71. //cache is explicitly disabled
  72. if CacheCfg.Cache != nil && !*CacheCfg.Cache {
  73. return nil
  74. }
  75. //cache is implicitly disabled if no cache config is provided
  76. if CacheCfg.Strategy == nil && CacheCfg.TTL == nil && CacheCfg.Size == nil {
  77. return nil
  78. }
  79. //cache is enabled
  80. if CacheCfg.Size == nil {
  81. CacheCfg.Size = ptr.Of(50)
  82. }
  83. gc := gcache.New(*CacheCfg.Size)
  84. if CacheCfg.Strategy == nil {
  85. CacheCfg.Strategy = ptr.Of("LRU")
  86. }
  87. switch *CacheCfg.Strategy {
  88. case "LRU":
  89. gc = gc.LRU()
  90. case "LFU":
  91. gc = gc.LFU()
  92. case "ARC":
  93. gc = gc.ARC()
  94. default:
  95. return fmt.Errorf("unknown cache strategy '%s'", *CacheCfg.Strategy)
  96. }
  97. if CacheCfg.TTL != nil {
  98. gc.Expiration(*CacheCfg.TTL)
  99. }
  100. cache := gc.Build()
  101. dataFileRegexCache[filename] = cache
  102. return nil
  103. }
  104. // UpdateCacheMetrics is called directly by the prom handler
  105. func UpdateRegexpCacheMetrics() {
  106. RegexpCacheMetrics.Reset()
  107. for name := range dataFileRegexCache {
  108. RegexpCacheMetrics.With(prometheus.Labels{"name": name}).Set(float64(dataFileRegexCache[name].Len(true)))
  109. }
  110. }
  111. func FileInit(fileFolder string, filename string, fileType string) error {
  112. log.Debugf("init (folder:%s) (file:%s) (type:%s)", fileFolder, filename, fileType)
  113. if fileType == "" {
  114. log.Debugf("ignored file %s%s because no type specified", fileFolder, filename)
  115. return nil
  116. }
  117. ok, err := existsInFileMaps(filename, fileType)
  118. if ok {
  119. log.Debugf("ignored file %s%s because already loaded", fileFolder, filename)
  120. return nil
  121. }
  122. if err != nil {
  123. return err
  124. }
  125. filepath := filepath.Join(fileFolder, filename)
  126. file, err := os.Open(filepath)
  127. if err != nil {
  128. return err
  129. }
  130. defer file.Close()
  131. scanner := bufio.NewScanner(file)
  132. for scanner.Scan() {
  133. if strings.HasPrefix(scanner.Text(), "#") { // allow comments
  134. continue
  135. }
  136. if len(scanner.Text()) == 0 { //skip empty lines
  137. continue
  138. }
  139. switch fileType {
  140. case "regex", "regexp":
  141. if fflag.Re2RegexpInfileSupport.IsEnabled() {
  142. dataFileRe2[filename] = append(dataFileRe2[filename], re2.MustCompile(scanner.Text()))
  143. continue
  144. }
  145. dataFileRegex[filename] = append(dataFileRegex[filename], regexp.MustCompile(scanner.Text()))
  146. case "string":
  147. dataFile[filename] = append(dataFile[filename], scanner.Text())
  148. }
  149. }
  150. if err := scanner.Err(); err != nil {
  151. return err
  152. }
  153. return nil
  154. }
  155. // Expr helpers
  156. func Distinct(params ...any) (any, error) {
  157. if rt := reflect.TypeOf(params[0]).Kind(); rt != reflect.Slice && rt != reflect.Array {
  158. return nil, nil
  159. }
  160. array := params[0].([]interface{})
  161. if array == nil {
  162. return []interface{}{}, nil
  163. }
  164. var exists map[any]bool = make(map[any]bool)
  165. var ret []interface{} = make([]interface{}, 0)
  166. for _, val := range array {
  167. if _, ok := exists[val]; !ok {
  168. exists[val] = true
  169. ret = append(ret, val)
  170. }
  171. }
  172. return ret, nil
  173. }
  174. func FlattenDistinct(params ...any) (any, error) {
  175. return Distinct(flatten(nil, reflect.ValueOf(params))) //nolint:asasalint
  176. }
  177. func Flatten(params ...any) (any, error) {
  178. return flatten(nil, reflect.ValueOf(params)), nil
  179. }
  180. func flatten(args []interface{}, v reflect.Value) []interface{} {
  181. if v.Kind() == reflect.Interface {
  182. v = v.Elem()
  183. }
  184. if v.Kind() == reflect.Array || v.Kind() == reflect.Slice {
  185. for i := 0; i < v.Len(); i++ {
  186. args = flatten(args, v.Index(i))
  187. }
  188. } else {
  189. args = append(args, v.Interface())
  190. }
  191. return args
  192. }
  193. func existsInFileMaps(filename string, ftype string) (bool, error) {
  194. ok := false
  195. var err error
  196. switch ftype {
  197. case "regex", "regexp":
  198. if fflag.Re2RegexpInfileSupport.IsEnabled() {
  199. _, ok = dataFileRe2[filename]
  200. } else {
  201. _, ok = dataFileRegex[filename]
  202. }
  203. case "string":
  204. _, ok = dataFile[filename]
  205. default:
  206. err = fmt.Errorf("unknown data type '%s' for : '%s'", ftype, filename)
  207. }
  208. return ok, err
  209. }
  210. //Expr helpers
  211. // func Get(arr []string, index int) string {
  212. func Get(params ...any) (any, error) {
  213. arr := params[0].([]string)
  214. index := params[1].(int)
  215. if index >= len(arr) {
  216. return "", nil
  217. }
  218. return arr[index], nil
  219. }
  220. // func Atof(x string) float64 {
  221. func Atof(params ...any) (any, error) {
  222. x := params[0].(string)
  223. log.Debugf("debug atof %s", x)
  224. ret, err := strconv.ParseFloat(x, 64)
  225. if err != nil {
  226. log.Warningf("Atof : can't convert float '%s' : %v", x, err)
  227. }
  228. return ret, nil
  229. }
  230. // func Upper(s string) string {
  231. func Upper(params ...any) (any, error) {
  232. s := params[0].(string)
  233. return strings.ToUpper(s), nil
  234. }
  235. // func Lower(s string) string {
  236. func Lower(params ...any) (any, error) {
  237. s := params[0].(string)
  238. return strings.ToLower(s), nil
  239. }
  240. // func Distance(lat1 string, long1 string, lat2 string, long2 string) (float64, error) {
  241. func Distance(params ...any) (any, error) {
  242. lat1 := params[0].(string)
  243. long1 := params[1].(string)
  244. lat2 := params[2].(string)
  245. long2 := params[3].(string)
  246. lat1f, err := strconv.ParseFloat(lat1, 64)
  247. if err != nil {
  248. log.Warningf("lat1 is not a float : %v", err)
  249. return 0.0, fmt.Errorf("lat1 is not a float : %v", err)
  250. }
  251. long1f, err := strconv.ParseFloat(long1, 64)
  252. if err != nil {
  253. log.Warningf("long1 is not a float : %v", err)
  254. return 0.0, fmt.Errorf("long1 is not a float : %v", err)
  255. }
  256. lat2f, err := strconv.ParseFloat(lat2, 64)
  257. if err != nil {
  258. log.Warningf("lat2 is not a float : %v", err)
  259. return 0.0, fmt.Errorf("lat2 is not a float : %v", err)
  260. }
  261. long2f, err := strconv.ParseFloat(long2, 64)
  262. if err != nil {
  263. log.Warningf("long2 is not a float : %v", err)
  264. return 0.0, fmt.Errorf("long2 is not a float : %v", err)
  265. }
  266. //either set of coordinates is 0,0, return 0 to avoid FPs
  267. if (lat1f == 0.0 && long1f == 0.0) || (lat2f == 0.0 && long2f == 0.0) {
  268. log.Warningf("one of the coordinates is 0,0, returning 0")
  269. return 0.0, nil
  270. }
  271. first := haversine.Coord{Lat: lat1f, Lon: long1f}
  272. second := haversine.Coord{Lat: lat2f, Lon: long2f}
  273. _, km := haversine.Distance(first, second)
  274. return km, nil
  275. }
  276. // func QueryEscape(s string) string {
  277. func QueryEscape(params ...any) (any, error) {
  278. s := params[0].(string)
  279. return url.QueryEscape(s), nil
  280. }
  281. // func PathEscape(s string) string {
  282. func PathEscape(params ...any) (any, error) {
  283. s := params[0].(string)
  284. return url.PathEscape(s), nil
  285. }
  286. // func PathUnescape(s string) string {
  287. func PathUnescape(params ...any) (any, error) {
  288. s := params[0].(string)
  289. ret, err := url.PathUnescape(s)
  290. if err != nil {
  291. log.Debugf("unable to PathUnescape '%s': %+v", s, err)
  292. return s, nil
  293. }
  294. return ret, nil
  295. }
  296. // func QueryUnescape(s string) string {
  297. func QueryUnescape(params ...any) (any, error) {
  298. s := params[0].(string)
  299. ret, err := url.QueryUnescape(s)
  300. if err != nil {
  301. log.Debugf("unable to QueryUnescape '%s': %+v", s, err)
  302. return s, nil
  303. }
  304. return ret, nil
  305. }
  306. // func File(filename string) []string {
  307. func File(params ...any) (any, error) {
  308. filename := params[0].(string)
  309. if _, ok := dataFile[filename]; ok {
  310. return dataFile[filename], nil
  311. }
  312. log.Errorf("file '%s' (type:string) not found in expr library", filename)
  313. log.Errorf("expr library : %s", spew.Sdump(dataFile))
  314. return []string{}, nil
  315. }
  316. // func RegexpInFile(data string, filename string) bool {
  317. func RegexpInFile(params ...any) (any, error) {
  318. data := params[0].(string)
  319. filename := params[1].(string)
  320. var hash uint64
  321. hasCache := false
  322. matched := false
  323. if _, ok := dataFileRegexCache[filename]; ok {
  324. hasCache = true
  325. hash = xxhash.Sum64String(data)
  326. if val, err := dataFileRegexCache[filename].Get(hash); err == nil {
  327. return val.(bool), nil
  328. }
  329. }
  330. switch fflag.Re2RegexpInfileSupport.IsEnabled() {
  331. case true:
  332. if _, ok := dataFileRe2[filename]; ok {
  333. for _, re := range dataFileRe2[filename] {
  334. if re.MatchString(data) {
  335. matched = true
  336. break
  337. }
  338. }
  339. } else {
  340. log.Errorf("file '%s' (type:regexp) not found in expr library", filename)
  341. log.Errorf("expr library : %s", spew.Sdump(dataFileRe2))
  342. }
  343. case false:
  344. if _, ok := dataFileRegex[filename]; ok {
  345. for _, re := range dataFileRegex[filename] {
  346. if re.MatchString(data) {
  347. matched = true
  348. break
  349. }
  350. }
  351. } else {
  352. log.Errorf("file '%s' (type:regexp) not found in expr library", filename)
  353. log.Errorf("expr library : %s", spew.Sdump(dataFileRegex))
  354. }
  355. }
  356. if hasCache {
  357. dataFileRegexCache[filename].Set(hash, matched)
  358. }
  359. return matched, nil
  360. }
  361. // func IpInRange(ip string, ipRange string) bool {
  362. func IpInRange(params ...any) (any, error) {
  363. var err error
  364. var ipParsed net.IP
  365. var ipRangeParsed *net.IPNet
  366. ip := params[0].(string)
  367. ipRange := params[1].(string)
  368. ipParsed = net.ParseIP(ip)
  369. if ipParsed == nil {
  370. log.Debugf("'%s' is not a valid IP", ip)
  371. return false, nil
  372. }
  373. if _, ipRangeParsed, err = net.ParseCIDR(ipRange); err != nil {
  374. log.Debugf("'%s' is not a valid IP Range", ipRange)
  375. return false, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility
  376. }
  377. if ipRangeParsed.Contains(ipParsed) {
  378. return true, nil
  379. }
  380. return false, nil
  381. }
  382. // func IsIPV6(ip string) bool {
  383. func IsIPV6(params ...any) (any, error) {
  384. ip := params[0].(string)
  385. ipParsed := net.ParseIP(ip)
  386. if ipParsed == nil {
  387. log.Debugf("'%s' is not a valid IP", ip)
  388. return false, nil
  389. }
  390. // If it's a valid IP and can't be converted to IPv4 then it is an IPv6
  391. return ipParsed.To4() == nil, nil
  392. }
  393. // func IsIPV4(ip string) bool {
  394. func IsIPV4(params ...any) (any, error) {
  395. ip := params[0].(string)
  396. ipParsed := net.ParseIP(ip)
  397. if ipParsed == nil {
  398. log.Debugf("'%s' is not a valid IP", ip)
  399. return false, nil
  400. }
  401. return ipParsed.To4() != nil, nil
  402. }
  403. // func IsIP(ip string) bool {
  404. func IsIP(params ...any) (any, error) {
  405. ip := params[0].(string)
  406. ipParsed := net.ParseIP(ip)
  407. if ipParsed == nil {
  408. log.Debugf("'%s' is not a valid IP", ip)
  409. return false, nil
  410. }
  411. return true, nil
  412. }
  413. // func IpToRange(ip string, cidr string) string {
  414. func IpToRange(params ...any) (any, error) {
  415. ip := params[0].(string)
  416. cidr := params[1].(string)
  417. cidr = strings.TrimPrefix(cidr, "/")
  418. mask, err := strconv.Atoi(cidr)
  419. if err != nil {
  420. log.Errorf("bad cidr '%s': %s", cidr, err)
  421. return "", nil
  422. }
  423. ipAddr := net.ParseIP(ip)
  424. if ipAddr == nil {
  425. log.Errorf("can't parse IP address '%s'", ip)
  426. return "", nil
  427. }
  428. ipRange := iplib.NewNet(ipAddr, mask)
  429. if ipRange.IP() == nil {
  430. log.Errorf("can't get cidr '%s' of '%s'", cidr, ip)
  431. return "", nil
  432. }
  433. return ipRange.String(), nil
  434. }
  435. // func TimeNow() string {
  436. func TimeNow(params ...any) (any, error) {
  437. return time.Now().UTC().Format(time.RFC3339), nil
  438. }
  439. // func ParseUri(uri string) map[string][]string {
  440. func ParseUri(params ...any) (any, error) {
  441. uri := params[0].(string)
  442. ret := make(map[string][]string)
  443. u, err := url.Parse(uri)
  444. if err != nil {
  445. log.Errorf("Could not parse URI: %s", err)
  446. return ret, nil
  447. }
  448. parsed, err := url.ParseQuery(u.RawQuery)
  449. if err != nil {
  450. log.Errorf("Could not parse query uri : %s", err)
  451. return ret, nil
  452. }
  453. for k, v := range parsed {
  454. ret[k] = v
  455. }
  456. return ret, nil
  457. }
  458. // func KeyExists(key string, dict map[string]interface{}) bool {
  459. func KeyExists(params ...any) (any, error) {
  460. key := params[0].(string)
  461. dict := params[1].(map[string]interface{})
  462. _, ok := dict[key]
  463. return ok, nil
  464. }
  465. // func GetDecisionsCount(value string) int {
  466. func GetDecisionsCount(params ...any) (any, error) {
  467. value := params[0].(string)
  468. if dbClient == nil {
  469. log.Error("No database config to call GetDecisionsCount()")
  470. return 0, nil
  471. }
  472. count, err := dbClient.CountDecisionsByValue(value)
  473. if err != nil {
  474. log.Errorf("Failed to get decisions count from value '%s'", value)
  475. return 0, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility
  476. }
  477. return count, nil
  478. }
  479. // func GetDecisionsSinceCount(value string, since string) int {
  480. func GetDecisionsSinceCount(params ...any) (any, error) {
  481. value := params[0].(string)
  482. since := params[1].(string)
  483. if dbClient == nil {
  484. log.Error("No database config to call GetDecisionsCount()")
  485. return 0, nil
  486. }
  487. sinceDuration, err := time.ParseDuration(since)
  488. if err != nil {
  489. log.Errorf("Failed to parse since parameter '%s' : %s", since, err)
  490. return 0, nil
  491. }
  492. sinceTime := time.Now().UTC().Add(-sinceDuration)
  493. count, err := dbClient.CountDecisionsSinceByValue(value, sinceTime)
  494. if err != nil {
  495. log.Errorf("Failed to get decisions count from value '%s'", value)
  496. return 0, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility
  497. }
  498. return count, nil
  499. }
  500. // func LookupHost(value string) []string {
  501. func LookupHost(params ...any) (any, error) {
  502. value := params[0].(string)
  503. addresses, err := net.LookupHost(value)
  504. if err != nil {
  505. log.Errorf("Failed to lookup host '%s' : %s", value, err)
  506. return []string{}, nil
  507. }
  508. return addresses, nil
  509. }
  510. // func ParseUnixTime(value string) (time.Time, error) {
  511. func ParseUnixTime(params ...any) (any, error) {
  512. value := params[0].(string)
  513. //Splitting string here as some unix timestamp may have milliseconds and break ParseInt
  514. i, err := strconv.ParseInt(strings.Split(value, ".")[0], 10, 64)
  515. if err != nil || i <= 0 {
  516. return time.Time{}, fmt.Errorf("unable to parse %s as unix timestamp", value)
  517. }
  518. return time.Unix(i, 0), nil
  519. }
  520. // func ParseUnix(value string) string {
  521. func ParseUnix(params ...any) (any, error) {
  522. value := params[0].(string)
  523. t, err := ParseUnixTime(value)
  524. if err != nil {
  525. log.Error(err)
  526. return "", nil
  527. }
  528. return t.(time.Time).Format(time.RFC3339), nil
  529. }
  530. // func ToString(value interface{}) string {
  531. func ToString(params ...any) (any, error) {
  532. value := params[0]
  533. s, ok := value.(string)
  534. if !ok {
  535. return "", nil
  536. }
  537. return s, nil
  538. }
  539. // func GetFromStash(cacheName string, key string) (string, error) {
  540. func GetFromStash(params ...any) (any, error) {
  541. cacheName := params[0].(string)
  542. key := params[1].(string)
  543. return cache.GetKey(cacheName, key)
  544. }
  545. // func SetInStash(cacheName string, key string, value string, expiration *time.Duration) any {
  546. func SetInStash(params ...any) (any, error) {
  547. cacheName := params[0].(string)
  548. key := params[1].(string)
  549. value := params[2].(string)
  550. expiration := params[3].(*time.Duration)
  551. return cache.SetKey(cacheName, key, value, expiration), nil
  552. }
  553. func Sprintf(params ...any) (any, error) {
  554. format := params[0].(string)
  555. return fmt.Sprintf(format, params[1:]...), nil
  556. }
  557. // func Match(pattern, name string) bool {
  558. func Match(params ...any) (any, error) {
  559. var matched bool
  560. pattern := params[0].(string)
  561. name := params[1].(string)
  562. if pattern == "" {
  563. return name == "", nil
  564. }
  565. if name == "" {
  566. if pattern == "*" || pattern == "" {
  567. return true, nil
  568. }
  569. return false, nil
  570. }
  571. if pattern[0] == '*' {
  572. for i := 0; i <= len(name); i++ {
  573. matched, _ := Match(pattern[1:], name[i:])
  574. if matched.(bool) {
  575. return matched, nil
  576. }
  577. }
  578. return matched, nil
  579. }
  580. if pattern[0] == '?' || pattern[0] == name[0] {
  581. return Match(pattern[1:], name[1:])
  582. }
  583. return matched, nil
  584. }
  585. func FloatApproxEqual(params ...any) (any, error) {
  586. float1 := params[0].(float64)
  587. float2 := params[1].(float64)
  588. if math.Abs(float1-float2) < 1e-6 {
  589. return true, nil
  590. }
  591. return false, nil
  592. }
  593. func B64Decode(params ...any) (any, error) {
  594. encoded := params[0].(string)
  595. decoded, err := base64.StdEncoding.DecodeString(encoded)
  596. if err != nil {
  597. return "", err
  598. }
  599. return string(decoded), nil
  600. }
  601. func ParseKV(params ...any) (any, error) {
  602. blob := params[0].(string)
  603. target := params[1].(map[string]interface{})
  604. prefix := params[2].(string)
  605. matches := keyValuePattern.FindAllStringSubmatch(blob, -1)
  606. if matches == nil {
  607. log.Errorf("could not find any key/value pair in line")
  608. return nil, fmt.Errorf("invalid input format")
  609. }
  610. if _, ok := target[prefix]; !ok {
  611. target[prefix] = make(map[string]string)
  612. } else {
  613. _, ok := target[prefix].(map[string]string)
  614. if !ok {
  615. log.Errorf("ParseKV: target is not a map[string]string")
  616. return nil, fmt.Errorf("target is not a map[string]string")
  617. }
  618. }
  619. for _, match := range matches {
  620. key := ""
  621. value := ""
  622. for i, name := range keyValuePattern.SubexpNames() {
  623. if name == "key" {
  624. key = match[i]
  625. } else if name == "quoted_value" && match[i] != "" {
  626. value = match[i]
  627. } else if name == "value" && match[i] != "" {
  628. value = match[i]
  629. }
  630. }
  631. target[prefix].(map[string]string)[key] = value
  632. }
  633. log.Tracef("unmarshaled KV: %+v", target[prefix])
  634. return nil, nil
  635. }
  636. func Hostname(params ...any) (any, error) {
  637. hostname, err := os.Hostname()
  638. if err != nil {
  639. return "", err
  640. }
  641. return hostname, nil
  642. }