github.com/hashicorp/vault/sdk@v0.11.0/helper/pathmanager/pathmanager.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package pathmanager 5 6 import ( 7 "strings" 8 "sync" 9 10 iradix "github.com/hashicorp/go-immutable-radix" 11 ) 12 13 // PathManager is a prefix searchable index of paths 14 type PathManager struct { 15 l sync.RWMutex 16 paths *iradix.Tree 17 } 18 19 // New creates a new path manager 20 func New() *PathManager { 21 return &PathManager{ 22 paths: iradix.New(), 23 } 24 } 25 26 // AddPaths adds path to the paths list 27 func (p *PathManager) AddPaths(paths []string) { 28 p.l.Lock() 29 defer p.l.Unlock() 30 31 txn := p.paths.Txn() 32 for _, prefix := range paths { 33 if len(prefix) == 0 { 34 continue 35 } 36 37 var exception bool 38 if strings.HasPrefix(prefix, "!") { 39 prefix = strings.TrimPrefix(prefix, "!") 40 exception = true 41 } 42 43 // We trim any trailing *, but we don't touch whether it is a trailing 44 // slash or not since we want to be able to ignore prefixes that fully 45 // specify a file 46 txn.Insert([]byte(strings.TrimSuffix(prefix, "*")), exception) 47 } 48 p.paths = txn.Commit() 49 } 50 51 // RemovePaths removes paths from the paths list 52 func (p *PathManager) RemovePaths(paths []string) { 53 p.l.Lock() 54 defer p.l.Unlock() 55 56 txn := p.paths.Txn() 57 for _, prefix := range paths { 58 if len(prefix) == 0 { 59 continue 60 } 61 62 // Exceptions aren't stored with the leading ! so strip it 63 if strings.HasPrefix(prefix, "!") { 64 prefix = strings.TrimPrefix(prefix, "!") 65 } 66 67 // We trim any trailing *, but we don't touch whether it is a trailing 68 // slash or not since we want to be able to ignore prefixes that fully 69 // specify a file 70 txn.Delete([]byte(strings.TrimSuffix(prefix, "*"))) 71 } 72 p.paths = txn.Commit() 73 } 74 75 // RemovePathPrefix removes all paths with the given prefix 76 func (p *PathManager) RemovePathPrefix(prefix string) { 77 p.l.Lock() 78 defer p.l.Unlock() 79 80 // We trim any trailing *, but we don't touch whether it is a trailing 81 // slash or not since we want to be able to ignore prefixes that fully 82 // specify a file 83 p.paths, _ = p.paths.DeletePrefix([]byte(strings.TrimSuffix(prefix, "*"))) 84 } 85 86 // Len returns the number of paths 87 func (p *PathManager) Len() int { 88 return p.paths.Len() 89 } 90 91 // Paths returns the path list 92 func (p *PathManager) Paths() []string { 93 p.l.RLock() 94 defer p.l.RUnlock() 95 96 paths := make([]string, 0, p.paths.Len()) 97 walkFn := func(k []byte, v interface{}) bool { 98 paths = append(paths, string(k)) 99 return false 100 } 101 p.paths.Root().Walk(walkFn) 102 return paths 103 } 104 105 // HasPath returns if the prefix for the path exists regardless if it is a path 106 // (ending with /) or a prefix for a leaf node 107 func (p *PathManager) HasPath(path string) bool { 108 p.l.RLock() 109 defer p.l.RUnlock() 110 111 if _, exceptionRaw, ok := p.paths.Root().LongestPrefix([]byte(path)); ok { 112 var exception bool 113 if exceptionRaw != nil { 114 exception = exceptionRaw.(bool) 115 } 116 return !exception 117 } 118 return false 119 } 120 121 // HasExactPath returns if the longest match is an exact match for the 122 // full path 123 func (p *PathManager) HasExactPath(path string) bool { 124 p.l.RLock() 125 defer p.l.RUnlock() 126 127 if val, exceptionRaw, ok := p.paths.Root().LongestPrefix([]byte(path)); ok { 128 var exception bool 129 if exceptionRaw != nil { 130 exception = exceptionRaw.(bool) 131 } 132 133 strVal := string(val) 134 if strings.HasSuffix(strVal, "/") || strVal == path { 135 return !exception 136 } 137 } 138 return false 139 }