github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/filters/parser.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package filters 18 19 import ( 20 "fmt" 21 "io" 22 23 "github.com/containerd/containerd/errdefs" 24 "github.com/pkg/errors" 25 ) 26 27 /* 28 Parse the strings into a filter that may be used with an adaptor. 29 30 The filter is made up of zero or more selectors. 31 32 The format is a comma separated list of expressions, in the form of 33 `<fieldpath><op><value>`, known as selectors. All selectors must match the 34 target object for the filter to be true. 35 36 We define the operators "==" for equality, "!=" for not equal and "~=" for a 37 regular expression. If the operator and value are not present, the matcher will 38 test for the presence of a value, as defined by the target object. 39 40 The formal grammar is as follows: 41 42 selectors := selector ("," selector)* 43 selector := fieldpath (operator value) 44 fieldpath := field ('.' field)* 45 field := quoted | [A-Za-z] [A-Za-z0-9_]+ 46 operator := "==" | "!=" | "~=" 47 value := quoted | [^\s,]+ 48 quoted := <go string syntax> 49 50 */ 51 func Parse(s string) (Filter, error) { 52 // special case empty to match all 53 if s == "" { 54 return Always, nil 55 } 56 57 p := parser{input: s} 58 return p.parse() 59 } 60 61 // ParseAll parses each filter in ss and returns a filter that will return true 62 // if any filter matches the expression. 63 // 64 // If no filters are provided, the filter will match anything. 65 func ParseAll(ss ...string) (Filter, error) { 66 if len(ss) == 0 { 67 return Always, nil 68 } 69 70 var fs []Filter 71 for _, s := range ss { 72 f, err := Parse(s) 73 if err != nil { 74 return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error()) 75 } 76 77 fs = append(fs, f) 78 } 79 80 return Any(fs), nil 81 } 82 83 type parser struct { 84 input string 85 scanner scanner 86 } 87 88 func (p *parser) parse() (Filter, error) { 89 p.scanner.init(p.input) 90 91 ss, err := p.selectors() 92 if err != nil { 93 return nil, errors.Wrap(err, "filters") 94 } 95 96 return ss, nil 97 } 98 99 func (p *parser) selectors() (Filter, error) { 100 s, err := p.selector() 101 if err != nil { 102 return nil, err 103 } 104 105 ss := All{s} 106 107 loop: 108 for { 109 tok := p.scanner.peek() 110 switch tok { 111 case ',': 112 pos, tok, _ := p.scanner.scan() 113 if tok != tokenSeparator { 114 return nil, p.mkerr(pos, "expected a separator") 115 } 116 117 s, err := p.selector() 118 if err != nil { 119 return nil, err 120 } 121 122 ss = append(ss, s) 123 case tokenEOF: 124 break loop 125 default: 126 return nil, p.mkerr(p.scanner.ppos, "unexpected input: %v", string(tok)) 127 } 128 } 129 130 return ss, nil 131 } 132 133 func (p *parser) selector() (selector, error) { 134 fieldpath, err := p.fieldpath() 135 if err != nil { 136 return selector{}, err 137 } 138 139 switch p.scanner.peek() { 140 case ',', tokenSeparator, tokenEOF: 141 return selector{ 142 fieldpath: fieldpath, 143 operator: operatorPresent, 144 }, nil 145 } 146 147 op, err := p.operator() 148 if err != nil { 149 return selector{}, err 150 } 151 152 var allowAltQuotes bool 153 if op == operatorMatches { 154 allowAltQuotes = true 155 } 156 157 value, err := p.value(allowAltQuotes) 158 if err != nil { 159 if err == io.EOF { 160 return selector{}, io.ErrUnexpectedEOF 161 } 162 return selector{}, err 163 } 164 165 return selector{ 166 fieldpath: fieldpath, 167 value: value, 168 operator: op, 169 }, nil 170 } 171 172 func (p *parser) fieldpath() ([]string, error) { 173 f, err := p.field() 174 if err != nil { 175 return nil, err 176 } 177 178 fs := []string{f} 179 loop: 180 for { 181 tok := p.scanner.peek() // lookahead to consume field separator 182 183 switch tok { 184 case '.': 185 pos, tok, _ := p.scanner.scan() // consume separator 186 if tok != tokenSeparator { 187 return nil, p.mkerr(pos, "expected a field separator (`.`)") 188 } 189 190 f, err := p.field() 191 if err != nil { 192 return nil, err 193 } 194 195 fs = append(fs, f) 196 default: 197 // let the layer above handle the other bad cases. 198 break loop 199 } 200 } 201 202 return fs, nil 203 } 204 205 func (p *parser) field() (string, error) { 206 pos, tok, s := p.scanner.scan() 207 switch tok { 208 case tokenField: 209 return s, nil 210 case tokenQuoted: 211 return p.unquote(pos, s, false) 212 case tokenIllegal: 213 return "", p.mkerr(pos, p.scanner.err) 214 } 215 216 return "", p.mkerr(pos, "expected field or quoted") 217 } 218 219 func (p *parser) operator() (operator, error) { 220 pos, tok, s := p.scanner.scan() 221 switch tok { 222 case tokenOperator: 223 switch s { 224 case "==": 225 return operatorEqual, nil 226 case "!=": 227 return operatorNotEqual, nil 228 case "~=": 229 return operatorMatches, nil 230 default: 231 return 0, p.mkerr(pos, "unsupported operator %q", s) 232 } 233 case tokenIllegal: 234 return 0, p.mkerr(pos, p.scanner.err) 235 } 236 237 return 0, p.mkerr(pos, `expected an operator ("=="|"!="|"~=")`) 238 } 239 240 func (p *parser) value(allowAltQuotes bool) (string, error) { 241 pos, tok, s := p.scanner.scan() 242 243 switch tok { 244 case tokenValue, tokenField: 245 return s, nil 246 case tokenQuoted: 247 return p.unquote(pos, s, allowAltQuotes) 248 case tokenIllegal: 249 return "", p.mkerr(pos, p.scanner.err) 250 } 251 252 return "", p.mkerr(pos, "expected value or quoted") 253 } 254 255 func (p *parser) unquote(pos int, s string, allowAlts bool) (string, error) { 256 if !allowAlts && s[0] != '\'' && s[0] != '"' { 257 return "", p.mkerr(pos, "invalid quote encountered") 258 } 259 260 uq, err := unquote(s) 261 if err != nil { 262 return "", p.mkerr(pos, "unquoting failed: %v", err) 263 } 264 265 return uq, nil 266 } 267 268 type parseError struct { 269 input string 270 pos int 271 msg string 272 } 273 274 func (pe parseError) Error() string { 275 if pe.pos < len(pe.input) { 276 before := pe.input[:pe.pos] 277 location := pe.input[pe.pos : pe.pos+1] // need to handle end 278 after := pe.input[pe.pos+1:] 279 280 return fmt.Sprintf("[%s >|%s|< %s]: %v", before, location, after, pe.msg) 281 } 282 283 return fmt.Sprintf("[%s]: %v", pe.input, pe.msg) 284 } 285 286 func (p *parser) mkerr(pos int, format string, args ...interface{}) error { 287 return errors.Wrap(parseError{ 288 input: p.input, 289 pos: pos, 290 msg: fmt.Sprintf(format, args...), 291 }, "parse error") 292 }