From ca93aa01c984991762e635427f1f83ed73c28b9f Mon Sep 17 00:00:00 2001 From: sarankumar1325 <120080287+sarankumar1325@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:27:06 +0000 Subject: [PATCH] Base64-encode "." and ".." in Pushgateway URL path segments A job or grouping key value of "." or ".." was put into the URL path as is. These are dot segments: the Pushgateway router cleans the path and answers with a 307 redirect to a different group (".." points at the parent), so push_to_gateway, pushadd_to_gateway and delete_from_gateway fail with "HTTP Error 307". Encode such values with base64, as client_golang does. Signed-off-by: sarankumar1325 <120080287+sarankumar1325@users.noreply.github.com> --- prometheus_client/exposition.py | 6 ++++-- tests/test_exposition.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index cbdbb379..281fb1ea 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -822,9 +822,11 @@ def _escape_grouping_key(k, v): if v == "": # Per https://github.com/prometheus/pushgateway/pull/346. return k + "@base64", "=" - elif '/' in v or ' ' in v: + elif '/' in v or ' ' in v or v in ('.', '..'): # Added in Pushgateway 0.9.0. - # Use base64 encoding for values containing slashes or spaces + # Use base64 encoding for values containing slashes or spaces, and + # for "." and "..", which would otherwise be removed from the URL + # path as dot segments (the Pushgateway answers with a redirect). return k + "@base64", base64.urlsafe_b64encode(v.encode("utf-8")).decode("utf-8") else: return k, quote_plus(v) diff --git a/tests/test_exposition.py b/tests/test_exposition.py index b1acca44..c0fbde24 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -317,6 +317,17 @@ def test_push_with_complex_groupingkey(self): self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_PLAIN_0_0_4) self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n') + def test_push_with_groupingkey_dot_segments(self): + push_to_gateway(self.address, "my_job", self.registry, {'a': '.', 'b': '..'}) + self.assertEqual(self.requests[0][0].command, 'PUT') + self.assertEqual(self.requests[0][0].path, '/metrics/job/my_job/a@base64/Lg==/b@base64/Li4=') + self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n') + + def test_push_with_dot_segment_job(self): + push_to_gateway(self.address, "..", self.registry) + self.assertEqual(self.requests[0][0].command, 'PUT') + self.assertEqual(self.requests[0][0].path, '/metrics/job@base64/Li4=') + def test_push_with_complex_job(self): push_to_gateway(self.address, "my/job", self.registry) self.assertEqual(self.requests[0][0].command, 'PUT')