github.com/DataDog/datadog-agent/pkg/security/secl@v0.55.0-devel.0.20240517055856-10c4965fea94/compiler/eval/glob.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the Apache License Version 2.0.
     3  // This product includes software developed at Datadog (https://www.datadoghq.com/).
     4  // Copyright 2016-present Datadog, Inc.
     5  
     6  // Package eval holds eval related files
     7  package eval
     8  
     9  import (
    10  	"errors"
    11  	"strings"
    12  )
    13  
    14  // Glob describes file glob object
    15  type Glob struct {
    16  	pattern         string
    17  	elements        []patternElement
    18  	isScalar        bool
    19  	caseInsensitive bool
    20  	normalizePaths  bool
    21  }
    22  
    23  func (g *Glob) contains(filename string) bool {
    24  	if len(g.elements) == 0 || len(filename) == 0 {
    25  		return false
    26  	}
    27  
    28  	// pattern "/"
    29  	if len(g.elements) == 2 && g.elements[1].pattern == "" {
    30  		return true
    31  	}
    32  
    33  	// normalize */ == /*/
    34  	if g.elements[0].pattern == "*" {
    35  		filename = filename[1:]
    36  	}
    37  
    38  	for start, end, i := 0, 0, 0; end != len(filename); end++ {
    39  		if filename[end] == '/' {
    40  			elf, elp := filename[start:end], g.elements[i]
    41  			if !PatternMatchesWithSegments(elp, elf, g.caseInsensitive) && elp.pattern != "**" {
    42  				return false
    43  			}
    44  			start = end + 1
    45  			i++
    46  		}
    47  
    48  		if i+1 > len(g.elements) {
    49  			return true
    50  		}
    51  
    52  		if end+1 >= len(filename) {
    53  			elf, elp := filename[start:end+1], g.elements[i]
    54  			if len(elf) == 0 {
    55  				return true
    56  			}
    57  			if !PatternMatchesWithSegments(elp, elf, g.caseInsensitive) && elp.pattern != "**" {
    58  				return false
    59  			}
    60  		}
    61  	}
    62  
    63  	return true
    64  }
    65  
    66  func (g *Glob) matches(filename string) bool {
    67  	if len(g.elements) == 0 || len(filename) == 0 {
    68  		return false
    69  	}
    70  
    71  	// normalize */ == /*/
    72  	if g.elements[0].pattern == "*" {
    73  		filename = filename[1:]
    74  	}
    75  
    76  	var elp patternElement
    77  	var elf string
    78  	var start, end, i int
    79  
    80  	for start, end, i = 0, 0, 0; end != len(filename); end++ {
    81  		if filename[end] == '/' {
    82  			elf, elp = filename[start:end], g.elements[i]
    83  			if !PatternMatchesWithSegments(elp, elf, g.caseInsensitive) && elp.pattern != "**" {
    84  				return false
    85  			}
    86  			start = end + 1
    87  			i++
    88  		}
    89  
    90  		if i+1 > len(g.elements) {
    91  			return elp.pattern == "**"
    92  		}
    93  
    94  		if end+1 >= len(filename) {
    95  			elf, elp = filename[start:end+1], g.elements[i]
    96  			if len(elf) == 0 {
    97  				return elp.pattern == "*"
    98  			}
    99  			if PatternMatchesWithSegments(elp, elf, g.caseInsensitive) && i+1 == len(g.elements) {
   100  				return true
   101  			} else if elp.pattern != "**" {
   102  				return false
   103  			}
   104  		}
   105  	}
   106  
   107  	elf, elp = filename[end:], g.elements[i+1]
   108  	if len(elf) == 0 {
   109  		return false
   110  	}
   111  	return PatternMatchesWithSegments(elp, elf, g.caseInsensitive)
   112  }
   113  
   114  // Contains returns whether the glob pattern matches the beginning of the filename
   115  func (g *Glob) Contains(filename string) bool {
   116  	if g.normalizePaths {
   117  		// normalize to linux-like paths
   118  		filename = strings.ReplaceAll(filename, "\\", "/")
   119  	}
   120  
   121  	return g.contains(filename)
   122  }
   123  
   124  // Matches the given filename
   125  func (g *Glob) Matches(filename string) bool {
   126  	if g.normalizePaths {
   127  		// normalize to linux-like paths
   128  		filename = strings.ReplaceAll(filename, "\\", "/")
   129  	}
   130  
   131  	if g.isScalar {
   132  		if g.caseInsensitive {
   133  			return strings.EqualFold(g.pattern, filename)
   134  		}
   135  		return g.pattern == filename
   136  	}
   137  	return g.matches(filename)
   138  }
   139  
   140  // NewGlob returns a new glob object from the given pattern
   141  func NewGlob(pattern string, caseInsensitive bool, normalizePaths bool) (*Glob, error) {
   142  	if normalizePaths {
   143  		// normalize to linux-like paths
   144  		pattern = strings.ReplaceAll(pattern, "\\", "/")
   145  	}
   146  
   147  	els := strings.Split(pattern, "/")
   148  	elements := make([]patternElement, 0, len(els))
   149  	for i, el := range els {
   150  		if el == "**" && i+1 != len(els) || strings.Contains(el, "**") && len(el) != len("**") {
   151  			return nil, errors.New("`**` is allowed only at the end of patterns")
   152  		}
   153  		elements = append(elements, newPatternElement(el))
   154  	}
   155  
   156  	return &Glob{
   157  		pattern:         pattern,
   158  		elements:        elements,
   159  		isScalar:        !strings.Contains(pattern, "*"),
   160  		caseInsensitive: caseInsensitive,
   161  		normalizePaths:  normalizePaths,
   162  	}, nil
   163  }