Skip to content

Commit a5814dc

Browse files
committed
Fall back to the text format for a non-numeric Accept version
choose_encoder compared the version parameter of the Accept header as a tuple of mixed ints and strings, so a version such as 1.0.0-rc.1, a quoted "1.0.0" or latest raised TypeError and the scrape answered 500. Treat such a version as unsupported, which falls through to the next media range and finally to text/plain version 0.0.4. Signed-off-by: Christopher Krah <github@christopher-krah.com>
1 parent 9cd073c commit a5814dc

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

‎prometheus_client/exposition.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ def choose_encoder(accept_header: str) -> Tuple[Callable[[Collector], bytes], st
372372
# mimetype.
373373
if not version:
374374
return (partial(openmetrics.generate_latest, escaping=openmetrics.UNDERSCORES, version="1.0.0"), openmetrics.CONTENT_TYPE_LATEST)
375-
if version and parse_version(version) >= (1, 0, 0):
375+
if version and _version_at_least(version, (1, 0, 0)):
376376
return (partial(openmetrics.generate_latest, escaping=escaping, version=version),
377377
f'application/openmetrics-text; version={version}; charset=utf-8; escaping=' + str(escaping))
378378
elif accepted.split(';')[0].strip() == 'text/plain':
@@ -381,12 +381,23 @@ def choose_encoder(accept_header: str) -> Tuple[Callable[[Collector], bytes], st
381381
escaping = _get_escaping(toks)
382382
# Only return an escaping header if we have a good version and
383383
# mimetype.
384-
if version and parse_version(version) >= (1, 0, 0):
384+
if version and _version_at_least(version, (1, 0, 0)):
385385
return (partial(generate_latest, escaping=escaping),
386386
CONTENT_TYPE_LATEST + '; escaping=' + str(escaping))
387387
return generate_latest, CONTENT_TYPE_PLAIN_0_0_4
388388

389389

390+
def _version_at_least(version: str, minimum: Tuple[int, ...]) -> bool:
391+
"""Compare a version parameter from the Accept header.
392+
393+
A version with a non-numeric part, such as 1.0.0-rc.1 or a quoted
394+
value, is treated as unsupported."""
395+
parsed = parse_version(version)
396+
if not all(isinstance(part, int) for part in parsed):
397+
return False
398+
return parsed >= minimum
399+
400+
390401
def _get_version(accept_header: List[str]) -> str:
391402
"""Return the version tag from the Accept header.
392403

‎tests/test_exposition.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,20 @@ def test_prom_no_version(self):
635635
self.assert_is_prom(exp)
636636

637637

638+
def test_non_numeric_version(self):
639+
for accept_header in (
640+
'application/openmetrics-text; version=1.0.0-rc.1',
641+
'application/openmetrics-text; version="1.0.0"',
642+
'text/plain; version=latest',
643+
):
644+
with self.subTest(accept_header=accept_header):
645+
generator, content_type = choose_encoder(accept_header)
646+
assert content_type == CONTENT_TYPE_PLAIN_0_0_4
647+
exp = generator(self.registry).decode('utf-8')
648+
self.assert_is_escaped(exp)
649+
self.assert_is_prom(exp)
650+
651+
638652
class TestWsgiTLS(unittest.TestCase):
639653
def setUp(self):
640654
self.certs_dir = os.path.join(

0 commit comments

Comments
 (0)