blob: b72823a4c1577667bc8a59f62c93ce9341c7e856 [file] [log] [blame]
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"encoding/json"
"regexp"
"sort"
"strings"
"sync"
)
var evalReg = regexp.MustCompile(`\beval\((?P<rule>[^)]*)\)`)
var escapeAssertionRegex = regexp.MustCompile(`([()\s|&,=!><+\-*/]|^)((r|p)[0-9]*)\.`)
func JsonToMap(jsonStr string) (map[string]interface{}, error) {
result := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonStr), &result)
if err != nil {
return result, err
}
return result, nil
}
// EscapeAssertion escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.
func EscapeAssertion(s string) string {
s = escapeAssertionRegex.ReplaceAllStringFunc(s, func(m string) string {
// Replace only the last dot with underscore (preserve the prefix character)
lastDotIdx := strings.LastIndex(m, ".")
if lastDotIdx > 0 {
return m[:lastDotIdx] + "_"
}
return m
})
return s
}
// RemoveComments removes the comments starting with # in the text.
func RemoveComments(s string) string {
pos := strings.Index(s, "#")
if pos == -1 {
return s
}
return strings.TrimSpace(s[0:pos])
}
// ArrayEquals determines whether two string arrays are identical.
func ArrayEquals(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// Array2DEquals determines whether two 2-dimensional string arrays are identical.
func Array2DEquals(a [][]string, b [][]string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if !ArrayEquals(v, b[i]) {
return false
}
}
return true
}
// SortArray2D Sorts the two-dimensional string array.
func SortArray2D(arr [][]string) {
if len(arr) == 0 {
return
}
sort.Slice(arr, func(i, j int) bool {
minArrLen := len(arr[i])
if len(arr[j]) < minArrLen {
minArrLen = len(arr[j])
}
for k := 0; k < minArrLen; k++ {
if arr[i][k] != arr[j][k] {
return arr[i][k] < arr[j][k]
}
}
return len(arr[i]) < len(arr[j])
})
}
// SortedArray2DEquals determines whether two 2-dimensional string arrays are identical.
func SortedArray2DEquals(a [][]string, b [][]string) bool {
if len(a) != len(b) {
return false
}
copyA := make([][]string, len(a))
copy(copyA, a)
copyB := make([][]string, len(b))
copy(copyB, b)
SortArray2D(copyA)
SortArray2D(copyB)
for i, v := range copyA {
if !ArrayEquals(v, copyB[i]) {
return false
}
}
return true
}
// ArrayRemoveDuplicates removes any duplicated elements in a string array.
func ArrayRemoveDuplicates(s *[]string) {
found := make(map[string]bool)
j := 0
for i, x := range *s {
if !found[x] {
found[x] = true
(*s)[j] = (*s)[i]
j++
}
}
*s = (*s)[:j]
}
// ArrayToString gets a printable string for a string array.
func ArrayToString(s []string) string {
return strings.Join(s, ", ")
}
// ParamsToString gets a printable string for variable number of parameters.
func ParamsToString(s ...string) string {
return strings.Join(s, ", ")
}
// SetEquals determines whether two string sets are identical.
func SetEquals(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
sort.Strings(a)
sort.Strings(b)
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// SetEquals determines whether two int sets are identical.
func SetEqualsInt(a []int, b []int) bool {
if len(a) != len(b) {
return false
}
sort.Ints(a)
sort.Ints(b)
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// Set2DEquals determines whether two string slice sets are identical.
func Set2DEquals(a [][]string, b [][]string) bool {
if len(a) != len(b) {
return false
}
var aa []string
for _, v := range a {
sort.Strings(v)
aa = append(aa, strings.Join(v, ", "))
}
var bb []string
for _, v := range b {
sort.Strings(v)
bb = append(bb, strings.Join(v, ", "))
}
return SetEquals(aa, bb)
}
// JoinSlice joins a string and a slice into a new slice.
func JoinSlice(a string, b ...string) []string {
res := make([]string, 0, len(b)+1)
res = append(res, a)
res = append(res, b...)
return res
}
// JoinSliceAny joins a string and a slice into a new interface{} slice.
func JoinSliceAny(a string, b ...string) []interface{} {
res := make([]interface{}, 0, len(b)+1)
res = append(res, a)
for _, s := range b {
res = append(res, s)
}
return res
}
// SetSubtract returns the elements in `a` that aren't in `b`.
func SetSubtract(a []string, b []string) []string {
mb := make(map[string]struct{}, len(b))
for _, x := range b {
mb[x] = struct{}{}
}
var diff []string
for _, x := range a {
if _, found := mb[x]; !found {
diff = append(diff, x)
}
}
return diff
}
// HasEval determine whether matcher contains function eval.
func HasEval(s string) bool {
return evalReg.MatchString(s)
}
// ReplaceEval replace function eval with the value of its parameters.
func ReplaceEval(s string, rule string) string {
return evalReg.ReplaceAllString(s, "("+rule+")")
}
// ReplaceEvalWithMap replace function eval with the value of its parameters via given sets.
func ReplaceEvalWithMap(src string, sets map[string]string) string {
return evalReg.ReplaceAllStringFunc(src, func(s string) string {
subs := evalReg.FindStringSubmatch(s)
if subs == nil {
return s
}
key := subs[1]
value, found := sets[key]
if !found {
return s
}
return evalReg.ReplaceAllString(s, value)
})
}
// GetEvalValue returns the parameters of function eval.
func GetEvalValue(s string) []string {
subMatch := evalReg.FindAllStringSubmatch(s, -1)
var rules []string
for _, rule := range subMatch {
rules = append(rules, rule[1])
}
return rules
}
// EscapeStringLiterals escapes backslashes in string literals within an expression
// to ensure consistent handling between govaluate (which interprets escape sequences)
// and CSV parsing (which treats backslashes as literal characters).
// This function doubles all backslashes within single-quoted and double-quoted strings.
func EscapeStringLiterals(expr string) string {
var result strings.Builder
inString := false
var quote rune
for i := 0; i < len(expr); i++ {
ch := rune(expr[i])
if inString {
result.WriteRune(ch)
if ch == '\\' {
// Found a backslash inside a string - double it
result.WriteRune('\\')
} else if ch == quote {
// End of string literal
inString = false
}
continue
}
// Not inside a string literal
if ch == '\'' || ch == '"' {
inString = true
quote = ch
}
result.WriteRune(ch)
}
return result.String()
}
func RemoveDuplicateElement(s []string) []string {
result := make([]string, 0, len(s))
temp := map[string]struct{}{}
for _, item := range s {
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}
type node struct {
key interface{}
value interface{}
prev *node
next *node
}
type LRUCache struct {
capacity int
m map[interface{}]*node
head *node
tail *node
}
func NewLRUCache(capacity int) *LRUCache {
cache := &LRUCache{}
cache.capacity = capacity
cache.m = map[interface{}]*node{}
head := &node{}
tail := &node{}
head.next = tail
tail.prev = head
cache.head = head
cache.tail = tail
return cache
}
func (cache *LRUCache) remove(n *node, listOnly bool) {
if !listOnly {
delete(cache.m, n.key)
}
n.prev.next = n.next
n.next.prev = n.prev
}
func (cache *LRUCache) add(n *node, listOnly bool) {
if !listOnly {
cache.m[n.key] = n
}
headNext := cache.head.next
cache.head.next = n
headNext.prev = n
n.next = headNext
n.prev = cache.head
}
func (cache *LRUCache) moveToHead(n *node) {
cache.remove(n, true)
cache.add(n, true)
}
func (cache *LRUCache) Get(key interface{}) (value interface{}, ok bool) {
n, ok := cache.m[key]
if ok {
cache.moveToHead(n)
return n.value, ok
} else {
return nil, ok
}
}
func (cache *LRUCache) Put(key interface{}, value interface{}) {
n, ok := cache.m[key]
if ok {
cache.remove(n, false)
} else {
n = &node{key, value, nil, nil}
if len(cache.m) >= cache.capacity {
cache.remove(cache.tail.prev, false)
}
}
cache.add(n, false)
}
type SyncLRUCache struct {
rwm sync.RWMutex
*LRUCache
}
func NewSyncLRUCache(capacity int) *SyncLRUCache {
cache := &SyncLRUCache{}
cache.LRUCache = NewLRUCache(capacity)
return cache
}
func (cache *SyncLRUCache) Get(key interface{}) (value interface{}, ok bool) {
cache.rwm.Lock()
defer cache.rwm.Unlock()
return cache.LRUCache.Get(key)
}
func (cache *SyncLRUCache) Put(key interface{}, value interface{}) {
cache.rwm.Lock()
defer cache.rwm.Unlock()
cache.LRUCache.Put(key, value)
}