1/* 2** Zabbix 3** Copyright (C) 2001-2021 Zabbix SIA 4** 5** This program is free software; you can redistribute it and/or modify 6** it under the terms of the GNU General Public License as published by 7** the Free Software Foundation; either version 2 of the License, or 8** (at your option) any later version. 9** 10** This program is distributed in the hope that it will be useful, 11** but WITHOUT ANY WARRANTY; without even the implied warranty of 12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13** GNU General Public License for more details. 14** 15** You should have received a copy of the GNU General Public License 16** along with this program; if not, write to the Free Software 17** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18**/ 19 20package itemutil 21 22import ( 23 "bytes" 24 "errors" 25 "fmt" 26) 27 28func isKeyChar(c byte, wildcard bool) bool { 29 if c >= 'a' && c <= 'z' { 30 return true 31 } 32 if c == '.' || c == '-' || c == '_' { 33 return true 34 } 35 if c >= '0' && c <= '9' { 36 return true 37 } 38 if c >= 'A' && c <= 'Z' { 39 return true 40 } 41 if wildcard && c == '*' { 42 return true 43 } 44 return false 45} 46 47// parseQuotedParam parses item key quoted parameter "..." and returns 48// the parsed parameter (including quotes, but without whitespace outside quotes) 49// and the data after the parameter (skipping also whitespace after closing quotes). 50func parseQuotedParam(data []byte) (param []byte, left []byte, err error) { 51 var last byte 52 for i, c := range data[1:] { 53 if c == '"' && last != '\\' { 54 i += 2 55 param = data[:i] 56 for ; i < len(data) && data[i] == ' '; i++ { 57 } 58 left = data[i:] 59 return 60 } 61 last = c 62 } 63 err = errors.New("unterminated quoted string") 64 return 65} 66 67// parseUnquotedParam parses item key normal parameter (any combination of any characters except ',' and ']', 68// including trailing whitespace) and returns the parsed parameter and the data after the parameter. 69func parseUnquotedParam(data []byte) (param []byte, left []byte, err error) { 70 for i, c := range data { 71 if c == ',' || c == ']' { 72 param = data[:i] 73 left = data[i:] 74 return 75 } 76 } 77 err = errors.New("unterminated parameter") 78 return 79} 80 81// parseArrayParam parses item key array parameter [...] and returns. 82func parseArrayParam(data []byte) (param []byte, left []byte, err error) { 83 var pos int 84 b := data[1:] 85 86 for len(b) > 0 { 87 loop: 88 for i, c := range b { 89 switch c { 90 case ' ': 91 continue 92 case '"': 93 if _, b, err = parseQuotedParam(b[i:]); err != nil { 94 return 95 } 96 break loop 97 case '[': 98 err = errors.New("nested arrays are not supported") 99 return 100 default: 101 if _, b, err = parseUnquotedParam(b[i:]); err != nil { 102 return 103 } 104 break loop 105 } 106 } 107 if len(b) == 0 { 108 err = errors.New("unterminated array parameter") 109 return 110 } 111 if b[0] == ']' { 112 pos = cap(data) - cap(b) 113 left = b[1:] 114 break 115 } 116 if b[0] != ',' { 117 err = errors.New("unterminated array parameter") 118 return 119 } 120 b = b[1:] 121 } 122 if left == nil { 123 err = errors.New("unterminated array parameter X") 124 return 125 } 126 param = data[:pos+1] 127 return 128} 129 130// unquoteParam unquotes quoted parameter by removing enclosing double quotes '"' and 131// unescaping '\"' escape sequences. 132func unquoteParam(data []byte) (param []byte) { 133 param = make([]byte, 0, len(data)) 134 var last byte 135 for _, c := range data[1:] { 136 switch c { 137 case '"': 138 if last != '\\' { 139 return 140 } 141 param = append(param, c) 142 case '\\': 143 if last == '\\' { 144 param = append(param, '\\') 145 } 146 default: 147 if last == '\\' { 148 param = append(param, '\\') 149 } 150 param = append(param, c) 151 } 152 last = c 153 } 154 return 155} 156 157// expandArray expands array parameter by removing enclosing brackets '[]' and, 158// removing whitespace before normal array items and around quoted items. 159func expandArray(data []byte) (param []byte) { 160 param = make([]byte, 0, len(data)) 161 var p []byte 162 b := data[1:] 163 for len(b) > 0 { 164 loop: 165 for i, c := range b { 166 switch c { 167 case ' ': 168 continue 169 case '"': 170 p, b, _ = parseQuotedParam(b[i:]) 171 break loop 172 default: 173 p, b, _ = parseUnquotedParam(b[i:]) 174 break loop 175 } 176 } 177 param = append(param, p...) 178 if b[0] == ']' { 179 break 180 } 181 param = append(param, ',') 182 b = b[1:] 183 } 184 return 185} 186 187// parseParams parses single item key parameter. 188func parseParam(data []byte) (param []byte, left []byte, err error) { 189 for i, c := range data { 190 switch c { 191 case ' ': 192 continue 193 case '"': 194 if param, left, err = parseQuotedParam(data[i:]); err == nil { 195 param = unquoteParam(param) 196 } 197 return 198 case '[': 199 if param, left, err = parseArrayParam(data[i:]); err == nil { 200 param = expandArray(param) 201 } 202 return 203 case ']', ',': 204 return data[i:i], data[i:], nil 205 default: 206 param, left, err = parseUnquotedParam(data[i:]) 207 return 208 } 209 } 210 err = errors.New("unterminated parameter list") 211 return 212} 213 214// parseParams parses item key parameters. 215func parseParams(data []byte) (params []string, left []byte, err error) { 216 if data[0] != '[' { 217 err = fmt.Errorf("key name must be followed by '['") 218 return 219 } 220 if len(data) == 1 { 221 err = fmt.Errorf("unterminated parameter list") 222 return 223 } 224 var param []byte 225 b := data[1:] 226 for len(b) > 0 { 227 if param, b, err = parseParam(b); err != nil { 228 return 229 } 230 if len(b) == 0 { 231 err = errors.New("key parameters ended unexpectedly") 232 return 233 } 234 if b[0] == ']' { 235 if len(b) > 1 { 236 left = b[1:] 237 } 238 break 239 } 240 if b[0] != ',' { 241 err = errors.New("invalid parameter separator") 242 return 243 } 244 b = b[1:] 245 params = append(params, string(param)) 246 } 247 if len(b) == 0 { 248 err = fmt.Errorf("unterminated parameter list") 249 } 250 params = append(params, string(param)) 251 return 252} 253 254func newKeyError() (err error) { 255 return errors.New("Invalid item key format.") 256} 257 258func parseKey(data []byte, wildcard bool) (key string, params []string, left []byte, err error) { 259 for i, c := range data { 260 if !isKeyChar(c, wildcard) { 261 if i == 0 { 262 err = newKeyError() 263 return 264 } 265 if c != '[' { 266 key = string(data[:i]) 267 left = data[i:] 268 return 269 } 270 if params, left, err = parseParams(data[i:]); err != nil { 271 err = newKeyError() 272 return 273 } 274 key = string(data[:i]) 275 return 276 } 277 } 278 key = string(data) 279 return 280} 281 282func parseMetricKey(text string, wildcard bool) (key string, params []string, err error) { 283 if text == "" { 284 err = newKeyError() 285 return 286 } 287 var left []byte 288 if key, params, left, err = parseKey([]byte(text), wildcard); err != nil { 289 return 290 } 291 if len(left) > 0 { 292 err = newKeyError() 293 } 294 return 295} 296 297// ParseKey parses item key in format key[param1, param2, ...] and returns 298// the parsed key and parameteres. 299func ParseKey(text string) (key string, params []string, err error) { 300 return parseMetricKey(text, false) 301} 302 303// ParseWildcardKey parses item key in format key[param1, param2, ...] and returns 304// the parsed key and parameteres. 305func ParseWildcardKey(text string) (key string, params []string, err error) { 306 return parseMetricKey(text, true) 307} 308 309// ParseAlias parses Alias in format name:key and returns the name 310// and the key separately without changes 311func ParseAlias(text string) (key1, key2 string, err error) { 312 var left, left2 []byte 313 if _, _, left, err = parseKey([]byte(text), false); err != nil { 314 return 315 } 316 if len(left) < 2 || left[0] != ':' { 317 err = fmt.Errorf("syntax error") 318 return 319 } 320 key1 = text[:len(text)-len(left)] 321 if _, _, left2, err = parseKey(left[1:], false); err != nil { 322 return 323 } 324 if len(left2) != 0 { 325 err = fmt.Errorf("syntax error") 326 return 327 } 328 key2 = string(left[1:]) 329 return 330} 331 332func mustQuote(param string) bool { 333 if len(param) > 0 && (param[0] == '"' || param[0] == ' ') { 334 return true 335 } 336 for _, b := range param { 337 switch b { 338 case ',', ']': 339 return true 340 } 341 } 342 return false 343} 344 345func quoteParam(buf *bytes.Buffer, param string) { 346 buf.WriteByte('"') 347 for _, b := range param { 348 if b == '"' { 349 buf.WriteByte('\\') 350 } 351 buf.WriteRune(b) 352 } 353 buf.WriteByte('"') 354} 355 356func MakeKey(key string, params []string) (text string) { 357 buf := bytes.Buffer{} 358 buf.WriteString(key) 359 360 if len(params) > 0 { 361 buf.WriteByte('[') 362 for i, p := range params { 363 if i != 0 { 364 buf.WriteByte(',') 365 } 366 if !mustQuote(p) { 367 buf.WriteString(p) 368 } else { 369 quoteParam(&buf, p) 370 } 371 } 372 buf.WriteByte(']') 373 } 374 return buf.String() 375} 376 377func CompareKeysParams(key1 string, params1 []string, key2 string, params2 []string) bool { 378 if key1 != key2 { 379 return false 380 } 381 if len(params1) != len(params2) { 382 return false 383 } 384 385 for i := 0; i < len(params1); i++ { 386 if params1[i] != params2[i] { 387 return false 388 } 389 } 390 return true 391} 392