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