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  }