Skip to content

Commit f2436ca

Browse files
immerrrbrian-brazil
authored andcommitted
Fix unescaping (prometheus#291)
* Extend parser tests * parser: fix handing of escape sequences Signed-off-by: immerrr <immerrr@gmail.com>
1 parent 5dd220d commit f2436ca

File tree

2 files changed

+95
-5
lines changed

2 files changed

+95
-5
lines changed

prometheus_client/parser.py

100644100755
Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import unicode_literals
44

5+
import re
6+
57
try:
68
import StringIO
79
except ImportError:
@@ -19,11 +21,37 @@ def text_string_to_metric_families(text):
1921
for metric_family in text_fd_to_metric_families(StringIO.StringIO(text)):
2022
yield metric_family
2123

24+
25+
ESCAPE_SEQUENCES = {
26+
'\\\\': '\\',
27+
'\\n': '\n',
28+
'\\"': '"',
29+
}
30+
31+
32+
def replace_escape_sequence(match):
33+
return ESCAPE_SEQUENCES[match.group(0)]
34+
35+
36+
HELP_ESCAPING_RE = re.compile(r'\\[\\n]')
37+
ESCAPING_RE = re.compile(r'\\[\\n"]')
38+
39+
2240
def _replace_help_escaping(s):
23-
return s.replace("\\n", "\n").replace('\\\\', '\\')
41+
return HELP_ESCAPING_RE.sub(replace_escape_sequence, s)
42+
2443

2544
def _replace_escaping(s):
26-
return s.replace("\\n", "\n").replace('\\\\', '\\').replace('\\"', '"')
45+
return ESCAPING_RE.sub(replace_escape_sequence, s)
46+
47+
48+
def _is_character_escaped(s, charpos):
49+
num_bslashes = 0
50+
while (charpos > num_bslashes and
51+
s[charpos - 1 - num_bslashes] == '\\'):
52+
num_bslashes += 1
53+
return num_bslashes % 2 == 1
54+
2755

2856
def _parse_labels(labels_string):
2957
labels = {}
@@ -52,7 +80,7 @@ def _parse_labels(labels_string):
5280
i = 0
5381
while i < len(value_substr):
5482
i = value_substr.index('"', i)
55-
if value_substr[i - 1] != "\\":
83+
if not _is_character_escaped(value_substr, i):
5684
break
5785
i += 1
5886

@@ -62,7 +90,7 @@ def _parse_labels(labels_string):
6290
# Replace escaping if needed
6391
if escaping:
6492
label_value = _replace_escaping(label_value)
65-
labels[label_name.strip()] = label_value.strip()
93+
labels[label_name.strip()] = label_value
6694

6795
# Remove the processed label from the sub-slice for next iteration
6896
sub_labels = sub_labels[quote_end + 1:]

tests/test_parser.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,32 @@ def test_commas(self):
167167
families = text_string_to_metric_families("""# TYPE a counter
168168
# HELP a help
169169
a{foo="bar",} 1
170+
a{foo="baz", } 1
170171
# TYPE b counter
171172
# HELP b help
172173
b{,} 2
174+
# TYPE c counter
175+
# HELP c help
176+
c{ ,} 3
177+
# TYPE d counter
178+
# HELP d help
179+
d{, } 4
173180
""")
174181
a = CounterMetricFamily("a", "help", labels=["foo"])
175182
a.add_metric(["bar"], 1)
183+
a.add_metric(["baz"], 1)
176184
b = CounterMetricFamily("b", "help", value=2)
177-
self.assertEqual([a, b], list(families))
185+
c = CounterMetricFamily("c", "help", value=3)
186+
d = CounterMetricFamily("d", "help", value=4)
187+
self.assertEqual([a, b, c, d], list(families))
188+
189+
def test_multiple_trailing_commas(self):
190+
text = """# TYPE a counter
191+
# HELP a help
192+
a{foo="bar",, } 1
193+
"""
194+
self.assertRaises(ValueError,
195+
lambda: list(text_string_to_metric_families(text)))
178196

179197
def test_empty_brackets(self):
180198
families = text_string_to_metric_families("""# TYPE a counter
@@ -200,6 +218,50 @@ def test_empty_label(self):
200218
metric_family.add_metric([""], 2)
201219
self.assertEqual([metric_family], list(families))
202220

221+
def test_label_escaping(self):
222+
for escaped_val, unescaped_val in [
223+
('foo', 'foo'),
224+
('\\foo', '\\foo'),
225+
('\\\\foo', '\\foo'),
226+
('foo\\\\', 'foo\\'),
227+
('\\\\', '\\'),
228+
('\\n', '\n'),
229+
('\\\\n', '\\n'),
230+
('\\\\\\n', '\\\n'),
231+
('\\"', '"'),
232+
('\\\\\\"', '\\"')]:
233+
families = list(text_string_to_metric_families("""
234+
# TYPE a counter
235+
# HELP a help
236+
a{foo="%s",bar="baz"} 1
237+
""" % escaped_val))
238+
metric_family = CounterMetricFamily(
239+
"a", "help", labels=["foo", "bar"])
240+
metric_family.add_metric([unescaped_val, "baz"], 1)
241+
self.assertEqual([metric_family], list(families))
242+
243+
def test_help_escaping(self):
244+
for escaped_val, unescaped_val in [
245+
('foo', 'foo'),
246+
('\\foo', '\\foo'),
247+
('\\\\foo', '\\foo'),
248+
('foo\\', 'foo\\'),
249+
('foo\\\\', 'foo\\'),
250+
('\\n', '\n'),
251+
('\\\\n', '\\n'),
252+
('\\\\\\n', '\\\n'),
253+
('\\"', '\\"'),
254+
('\\\\"', '\\"'),
255+
('\\\\\\"', '\\\\"')]:
256+
families = list(text_string_to_metric_families("""
257+
# TYPE a counter
258+
# HELP a %s
259+
a{foo="bar"} 1
260+
""" % escaped_val))
261+
metric_family = CounterMetricFamily("a", unescaped_val, labels=["foo"])
262+
metric_family.add_metric(["bar"], 1)
263+
self.assertEqual([metric_family], list(families))
264+
203265
def test_escaping(self):
204266
families = text_string_to_metric_families("""# TYPE a counter
205267
# HELP a he\\n\\\\l\\tp

0 commit comments

Comments
 (0)