Skip to content

Commit c18bdb0

Browse files
committed
add 'validation-regex' config option
1 parent 93622ce commit c18bdb0

File tree

3 files changed

+151
-7
lines changed

3 files changed

+151
-7
lines changed

helper/tags/graphite.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,14 @@ type TemplateDesc struct {
190190
}
191191

192192
type TagConfig struct {
193-
Enabled bool `toml:"enabled"`
194-
Separator string `toml:"separator"`
195-
Tags []string `toml:"tags"`
196-
TagMap map[string]string `toml:"-"`
197-
Templates []string `toml:"templates"`
198-
TemplateDescs []TemplateDesc `toml:"-"`
193+
Enabled bool `toml:"enabled"`
194+
Separator string `toml:"separator"`
195+
validationRegex string `toml:"validation-regex"`
196+
ValidationRegex *regexp.Regexp `toml:"-"`
197+
Tags []string `toml:"tags"`
198+
TagMap map[string]string `toml:"-"`
199+
Templates []string `toml:"templates"`
200+
TemplateDescs []TemplateDesc `toml:"-"`
199201
}
200202

201203
func DisabledTagConfig() TagConfig {
@@ -224,6 +226,12 @@ func (cfg *TagConfig) Configure() error {
224226
cfg.TagMap = make(map[string]string)
225227
makeTagMap(cfg.TagMap, cfg.Tags)
226228

229+
var err error
230+
cfg.ValidationRegex, err = regexp.Compile(cfg.validationRegex)
231+
if err != nil {
232+
return err
233+
}
234+
227235
for _, s := range cfg.Templates {
228236
dirtyTokens := strings.Split(s, " ")
229237
tokens := dirtyTokens[:0]

receiver/plain.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,16 @@ func (base *Base) PlainParseLine(p []byte, now uint32, buf *tags.GraphiteBuf) ([
7070
i3--
7171
}
7272

73-
value, err := strconv.ParseFloat(unsafeString(p[i1+1:i2]), 64)
73+
var (
74+
err error
75+
value float64
76+
)
77+
78+
if base.Tags.ValidationRegex != nil && base.Tags.ValidationRegex.Match(p[:i1]) {
79+
return nil, 0, 0, errors.New("message contains invalid characters: '" + unsafeString(p) + "'")
80+
}
81+
82+
value, err = strconv.ParseFloat(unsafeString(p[i1+1:i2]), 64)
7483
if err != nil || math.IsNaN(value) {
7584
return nil, 0, 0, errors.New("bad message: '" + unsafeString(p) + "'")
7685
}

receiver/plain_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package receiver
33
import (
44
"context"
55
"fmt"
6+
"regexp"
67
"sync"
78
"testing"
89
"time"
@@ -177,6 +178,8 @@ func TestPlainParseLine(t *testing.T) {
177178
{"metric.name;tag=value;k=v 42.15 1422642189\r\n", "metric.name?k=v&tag=value", 42.15, 1422642189},
178179
{"metric..name 42.15 -1\n", "metric.name", 42.15, now},
179180
{"cpu.loadavg;env=test2;host=host1;env=test 21.4 1422642189\n", "cpu.loadavg?env=test&host=host1", 21.4, 1422642189},
181+
{"cpu.loadavg~ 21.4 1422642189\n", "cpu.loadavg~", 21.4, 1422642189},
182+
{"cpu.loadavg~;env=test2;host=host1;env=test 21.4 1422642189\n", "cpu.loadavg~?env=test&host=host1", 21.4, 1422642189},
180183
}
181184

182185
base := &Base{}
@@ -202,4 +205,128 @@ func TestPlainParseLine(t *testing.T) {
202205
}
203206
}
204207
}
208+
209+
tableWithValidation := [](struct {
210+
b string
211+
name string
212+
value float64
213+
timestamp uint32
214+
}){
215+
{b: "42"},
216+
{b: ""},
217+
{b: "\n"},
218+
{b: "metric..name 42 \n"},
219+
{b: "metric..name 42"},
220+
{b: "metric.name 42 a1422642189\n"},
221+
{b: "metric.name 42a 1422642189\n"},
222+
{b: "metric.name NaN 1422642189\n"},
223+
{b: "metric.name 42 NaN\n"},
224+
{"metric.name -42.76 1422642189\n", "metric.name", -42.76, 1422642189},
225+
{"metric.name 42.15 1422642189\n", "metric.name", 42.15, 1422642189},
226+
{"metric..name 42.15 1422642189\n", "metric.name", 42.15, 1422642189},
227+
{"metric...name 42.15 1422642189\n", "metric.name", 42.15, 1422642189},
228+
{"metric.name 42.15 1422642189\r\n", "metric.name", 42.15, 1422642189},
229+
{"metric.name;tag=value;k=v 42.15 1422642189\r\n", "metric.name?k=v&tag=value", 42.15, 1422642189},
230+
{"metric..name 42.15 -1\n", "metric.name", 42.15, now},
231+
{"cpu.loadavg;env=test2;host=host1;env=test 21.4 1422642189\n", "cpu.loadavg?env=test&host=host1", 21.4, 1422642189},
232+
233+
// Additional test cases for validation
234+
// Test invalid characters in metric names
235+
{b: "metric@name 42.15 1422642189\n"},
236+
{b: "metric#name 42.15 1422642189\n"},
237+
{b: "metric$name 42.15 1422642189\n"},
238+
{b: "metric%name 42.15 1422642189\n"},
239+
{b: "metric&name 42.15 1422642189\n"},
240+
{b: "metric*name 42.15 1422642189\n"},
241+
{b: "metric!name 42.15 1422642189\n"},
242+
{b: "metric name 42.15 1422642189\n"}, // space in metric name
243+
{b: "metric\tname 42.15 1422642189\n"}, // tab in metric name
244+
{b: "metric[name] 42.15 1422642189\n"},
245+
{b: "metric{name} 42.15 1422642189\n"},
246+
{b: "metric(name) 42.15 1422642189\n"},
247+
{b: "metric/name 42.15 1422642189\n"},
248+
{b: "metric\\name 42.15 1422642189\n"},
249+
{b: "metric|name 42.15 1422642189\n"},
250+
{b: "metric?name 42.15 1422642189\n"},
251+
{b: "metric<name> 42.15 1422642189\n"},
252+
{b: "metric'name' 42.15 1422642189\n"},
253+
{b: "metric\"name\" 42.15 1422642189\n"},
254+
255+
// Test valid characters that should pass
256+
{"metric-name 42.15 1422642189\n", "metric-name", 42.15, 1422642189},
257+
{"metric_name 42.15 1422642189\n", "metric_name", 42.15, 1422642189},
258+
{"metric:name 42.15 1422642189\n", "metric:name", 42.15, 1422642189},
259+
{"metric.sub.name 42.15 1422642189\n", "metric.sub.name", 42.15, 1422642189},
260+
{"metric-123_test:data 42.15 1422642189\n", "metric-123_test:data", 42.15, 1422642189},
261+
262+
// Test invalid characters in tags
263+
{b: "metric.name;tag@=value 42.15 1422642189\n"},
264+
{b: "metric.name;tag=val@ue 42.15 1422642189\n"},
265+
{b: "metric.name;t ag=value 42.15 1422642189\n"},
266+
{b: "metric.name;tag=val ue 42.15 1422642189\n"},
267+
{b: "metric.name;tag#key=value 42.15 1422642189\n"},
268+
{b: "metric.name;tag=value! 42.15 1422642189\n"},
269+
{b: "metric.name;tag=value;key=val*ue 42.15 1422642189\n"},
270+
{b: "metric.name;tag=value;k ey=value 42.15 1422642189\n"},
271+
{b: "metric.name;tag=value;key=val\tue 42.15 1422642189\n"},
272+
{b: "metric.name;tag=value;key=val\nue 42.15 1422642189\n"},
273+
274+
// Test valid tags that should pass
275+
{"metric.name;env=prod 42.15 1422642189\n", "metric.name?env=prod", 42.15, 1422642189},
276+
{"metric.name;env=prod;region=us-east-1 42.15 1422642189\n", "metric.name?env=prod&region=us-east-1", 42.15, 1422642189},
277+
{"metric.name;tag-name=tag-value 42.15 1422642189\n", "metric.name?tag-name=tag-value", 42.15, 1422642189},
278+
{"metric.name;tag_name=tag_value 42.15 1422642189\n", "metric.name?tag_name=tag_value", 42.15, 1422642189},
279+
{"metric.name;tag:name=tag:value 42.15 1422642189\n", "metric.name?tag%3Aname=tag%3Avalue", 42.15, 1422642189},
280+
{"metric.name;tag.name=tag.value 42.15 1422642189\n", "metric.name?tag.name=tag.value", 42.15, 1422642189},
281+
282+
// Test edge cases with multiple invalid characters
283+
{b: "metric@#$%name 42.15 1422642189\n"},
284+
{b: "metric.name;tag@#=value$% 42.15 1422642189\n"},
285+
{b: "met!ric.na@me;ta#g=val$ue 42.15 1422642189\n"},
286+
287+
// Test unicode characters (should fail validation)
288+
{b: "metric.名前 42.15 1422642189\n"},
289+
{b: "metric.name;tag=値 42.15 1422642189\n"},
290+
{b: "metric.name;标签=value 42.15 1422642189\n"},
291+
{b: "метрика.name 42.15 1422642189\n"},
292+
293+
// Test empty tag keys/values
294+
{b: "metric.name;=value 42.15 1422642189\n"},
295+
{b: "metric.name;= 42.15 1422642189\n"},
296+
297+
// Test metrics with numbers
298+
{"metric123 42.15 1422642189\n", "metric123", 42.15, 1422642189},
299+
{"123metric 42.15 1422642189\n", "123metric", 42.15, 1422642189},
300+
{"123 42.15 1422642189\n", "123", 42.15, 1422642189},
301+
302+
// Test metrics with only valid special characters
303+
{"metric-_.:name 42.15 1422642189\n", "metric-_.:name", 42.15, 1422642189},
304+
{"metric.name;tag-_.:key=tag-_.:value 42.15 1422642189\n", "metric.name?tag-_.%3Akey=tag-_.%3Avalue", 42.15, 1422642189},
305+
306+
// Additional tests for colon encoding
307+
{"host:port:metric 42.15 1422642189\n", "host:port:metric", 42.15, 1422642189},
308+
{"metric.name;service:port=web:8080 42.15 1422642189\n", "metric.name?service%3Aport=web%3A8080", 42.15, 1422642189},
309+
{"app:service:metric;env=prod:primary 42.15 1422642189\n", "app:service:metric?env=prod%3Aprimary", 42.15, 1422642189},
310+
}
311+
312+
baseWithValidation := &Base{Tags: tags.TagConfig{ValidationRegex: regexp.MustCompile(`[^a-zA-Z0-9.;\-_:=]{1}`)}}
313+
for _, p := range tableWithValidation {
314+
name, value, timestamp, err := baseWithValidation.PlainParseLine([]byte(p.b), now, &tagBuf)
315+
if p.name == "" {
316+
// expected error
317+
if err == nil {
318+
t.Fatal("error expected")
319+
}
320+
} else {
321+
if string(name) != p.name {
322+
t.Fatalf("%#v != %#v", string(name), p.name)
323+
}
324+
if value != p.value {
325+
t.Fatalf("%#v != %#v", value, p.value)
326+
}
327+
if timestamp != p.timestamp {
328+
t.Fatalf("%d != %d", timestamp, p.timestamp)
329+
}
330+
}
331+
}
205332
}

0 commit comments

Comments
 (0)