Skip to content
This repository was archived by the owner on Oct 22, 2019. It is now read-only.

Commit 01c8937

Browse files
authored
Merge pull request #57 from motionpicturesolutions/bugfix/data-sanitization
Adds base64-encoding of label values to allow the usage of colons
2 parents f15cade + 7eb30b3 commit 01c8937

File tree

5 files changed

+229
-11
lines changed

5 files changed

+229
-11
lines changed

src/Prometheus/Storage/APC.php

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66

77
use Prometheus\MetricFamilySamples;
8+
use RuntimeException;
89

910
class APC implements Adapter
1011
{
@@ -103,7 +104,13 @@ private function metaKey(array $data)
103104
*/
104105
private function valueKey(array $data)
105106
{
106-
return implode(':', array(self::PROMETHEUS_PREFIX, $data['type'], $data['name'], json_encode($data['labelValues']), 'value'));
107+
return implode(':', array(
108+
self::PROMETHEUS_PREFIX,
109+
$data['type'],
110+
$data['name'],
111+
$this->encodeLabelValues($data['labelValues']),
112+
'value'
113+
));
107114
}
108115

109116
/**
@@ -112,7 +119,14 @@ private function valueKey(array $data)
112119
*/
113120
private function histogramBucketValueKey(array $data, $bucket)
114121
{
115-
return implode(':', array(self::PROMETHEUS_PREFIX, $data['type'], $data['name'], json_encode($data['labelValues']), $bucket, 'value'));
122+
return implode(':', array(
123+
self::PROMETHEUS_PREFIX,
124+
$data['type'],
125+
$data['name'],
126+
$this->encodeLabelValues($data['labelValues']),
127+
$bucket,
128+
'value'
129+
));
116130
}
117131

118132
/**
@@ -148,7 +162,7 @@ private function collectCounters()
148162
$data['samples'][] = array(
149163
'name' => $metaData['name'],
150164
'labelNames' => array(),
151-
'labelValues' => json_decode($labelValues),
165+
'labelValues' => $this->decodeLabelValues($labelValues),
152166
'value' => $value['value']
153167
);
154168
}
@@ -159,8 +173,8 @@ private function collectCounters()
159173
}
160174

161175
/**
162-
* @return array
163-
*/
176+
* @return array
177+
*/
164178
private function collectGauges()
165179
{
166180
$gauges = array();
@@ -178,7 +192,7 @@ private function collectGauges()
178192
$data['samples'][] = array(
179193
'name' => $metaData['name'],
180194
'labelNames' => array(),
181-
'labelValues' => json_decode($labelValues),
195+
'labelValues' => $this->decodeLabelValues($labelValues),
182196
'value' => $this->fromInteger($value['value'])
183197
);
184198
}
@@ -222,7 +236,7 @@ private function collectHistograms()
222236
sort($labels);
223237
foreach ($labels as $labelValues) {
224238
$acc = 0;
225-
$decodedLabelValues = json_decode($labelValues);
239+
$decodedLabelValues = $this->decodeLabelValues($labelValues);
226240
foreach ($data['buckets'] as $bucket) {
227241
$bucket = (string) $bucket;
228242
if (!isset($histogramBuckets[$labelValues][$bucket])) {
@@ -289,4 +303,36 @@ private function sortSamples(array &$samples)
289303
return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
290304
});
291305
}
306+
307+
/**
308+
* @param array $values
309+
* @return string
310+
* @throws RuntimeException
311+
*/
312+
private function encodeLabelValues(array $values)
313+
{
314+
$json = json_encode($values);
315+
if (false === $json) {
316+
throw new RuntimeException(json_last_error_msg());
317+
}
318+
return base64_encode($json);
319+
}
320+
321+
/**
322+
* @param string $values
323+
* @return array
324+
* @throws RuntimeException
325+
*/
326+
private function decodeLabelValues($values)
327+
{
328+
$json = base64_decode($values, true);
329+
if (false === $json) {
330+
throw new RuntimeException('Cannot base64 decode label values');
331+
}
332+
$decodedValues = json_decode($json, true);
333+
if (false === $decodedValues) {
334+
throw new RuntimeException(json_last_error_msg());
335+
}
336+
return $decodedValues;
337+
}
292338
}

src/Prometheus/Storage/InMemory.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55

66
use Prometheus\MetricFamilySamples;
7+
use RuntimeException;
78

89
class InMemory implements Adapter
910
{
@@ -60,7 +61,7 @@ private function collectHistograms()
6061
sort($labels);
6162
foreach ($labels as $labelValues) {
6263
$acc = 0;
63-
$decodedLabelValues = json_decode($labelValues);
64+
$decodedLabelValues = $this->decodeLabelValues($labelValues);
6465
foreach ($data['buckets'] as $bucket) {
6566
$bucket = (string)$bucket;
6667
if (!isset($histogramBuckets[$labelValues][$bucket])) {
@@ -120,7 +121,7 @@ private function internalCollect(array $metrics)
120121
$data['samples'][] = [
121122
'name' => $metaData['name'],
122123
'labelNames' => [],
123-
'labelValues' => json_decode($labelValues),
124+
'labelValues' => $this->decodeLabelValues($labelValues),
124125
'value' => $value
125126
];
126127
}
@@ -215,7 +216,7 @@ private function histogramBucketValueKey(array $data, $bucket)
215216
return implode(':', [
216217
$data['type'],
217218
$data['name'],
218-
json_encode($data['labelValues']),
219+
$this->encodeLabelValues($data['labelValues']),
219220
$bucket
220221
]);
221222
}
@@ -238,7 +239,7 @@ private function metaKey(array $data)
238239
private function valueKey(array $data)
239240
{
240241
return implode(':',
241-
[$data['type'], $data['name'], json_encode($data['labelValues']), 'value']);
242+
[$data['type'], $data['name'], $this->encodeLabelValues($data['labelValues']), 'value']);
242243
}
243244

244245
/**
@@ -261,4 +262,36 @@ private function sortSamples(array &$samples)
261262
return strcmp(implode("", $a['labelValues']), implode("", $b['labelValues']));
262263
});
263264
}
265+
266+
/**
267+
* @param array $values
268+
* @return string
269+
* @throws RuntimeException
270+
*/
271+
private function encodeLabelValues(array $values)
272+
{
273+
$json = json_encode($values);
274+
if (false === $json) {
275+
throw new RuntimeException(json_last_error_msg());
276+
}
277+
return base64_encode($json);
278+
}
279+
280+
/**
281+
* @param string $values
282+
* @return array
283+
* @throws RuntimeException
284+
*/
285+
private function decodeLabelValues($values)
286+
{
287+
$json = base64_decode($values, true);
288+
if (false === $json) {
289+
throw new RuntimeException('Cannot base64 decode label values');
290+
}
291+
$decodedValues = json_decode($json, true);
292+
if (false === $decodedValues) {
293+
throw new RuntimeException(json_last_error_msg());
294+
}
295+
return $decodedValues;
296+
}
264297
}

tests/Test/Prometheus/AbstractCounterTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use PHPUnit_Framework_TestCase;
77
use Prometheus\Counter;
88
use Prometheus\MetricFamilySamples;
9+
use Prometheus\Sample;
10+
use Prometheus\Storage\Adapter;
911

1012
/**
1113
* See https://prometheus.io/docs/instrumenting/exposition_formats/
@@ -139,5 +141,50 @@ public function itShouldRejectInvalidLabelNames()
139141
new Counter($this->adapter, 'test', 'some_metric', 'help', array('invalid label'));
140142
}
141143

144+
/**
145+
* @test
146+
* @dataProvider labelValuesDataProvider
147+
*
148+
* @param mixed $value The label value
149+
*/
150+
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
151+
{
152+
$label = 'foo';
153+
$histogram = new Counter($this->adapter, 'test', 'some_metric', 'help', array($label));
154+
$histogram->inc(array($value));
155+
156+
$metrics = $this->adapter->collect();
157+
self::assertInternalType('array', $metrics);
158+
self::assertCount(1, $metrics);
159+
self::assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);
160+
161+
$metric = reset($metrics);
162+
$samples = $metric->getSamples();
163+
self::assertContainsOnlyInstancesOf(Sample::class, $samples);
164+
165+
foreach ($samples as $sample) {
166+
$labels = array_combine(
167+
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
168+
$sample->getLabelValues()
169+
);
170+
self::assertEquals($value, $labels[$label]);
171+
}
172+
}
173+
174+
/**
175+
* @see isShouldAcceptArbitraryLabelValues
176+
* @return array
177+
*/
178+
public function labelValuesDataProvider()
179+
{
180+
$cases = [];
181+
// Basic Latin
182+
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
183+
for ($i = 32; $i <= 121; $i++) {
184+
$cases['ASCII code ' . $i] = array(chr($i));
185+
}
186+
return $cases;
187+
}
188+
142189
public abstract function configureAdapter();
143190
}

tests/Test/Prometheus/AbstractGaugeTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPUnit_Framework_TestCase;
77
use Prometheus\Gauge;
88
use Prometheus\MetricFamilySamples;
9+
use Prometheus\Sample;
910
use Prometheus\Storage\Adapter;
1011

1112
/**
@@ -308,5 +309,50 @@ public function itShouldRejectInvalidLabelNames()
308309
new Gauge($this->adapter, 'test', 'some_metric', 'help', array('invalid label'));
309310
}
310311

312+
/**
313+
* @test
314+
* @dataProvider labelValuesDataProvider
315+
*
316+
* @param mixed $value The label value
317+
*/
318+
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
319+
{
320+
$label = 'foo';
321+
$histogram = new Gauge($this->adapter, 'test', 'some_metric', 'help', array($label));
322+
$histogram->inc(array($value));
323+
324+
$metrics = $this->adapter->collect();
325+
self::assertInternalType('array', $metrics);
326+
self::assertCount(1, $metrics);
327+
self::assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);
328+
329+
$metric = reset($metrics);
330+
$samples = $metric->getSamples();
331+
self::assertContainsOnlyInstancesOf(Sample::class, $samples);
332+
333+
foreach ($samples as $sample) {
334+
$labels = array_combine(
335+
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
336+
$sample->getLabelValues()
337+
);
338+
self::assertEquals($value, $labels[$label]);
339+
}
340+
}
341+
342+
/**
343+
* @see isShouldAcceptArbitraryLabelValues
344+
* @return array
345+
*/
346+
public function labelValuesDataProvider()
347+
{
348+
$cases = [];
349+
// Basic Latin
350+
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
351+
for ($i = 32; $i <= 121; $i++) {
352+
$cases['ASCII code ' . $i] = array(chr($i));
353+
}
354+
return $cases;
355+
}
356+
311357
public abstract function configureAdapter();
312358
}

tests/Test/Prometheus/AbstractHistogramTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPUnit_Framework_TestCase;
77
use Prometheus\Histogram;
88
use Prometheus\MetricFamilySamples;
9+
use Prometheus\Sample;
910
use Prometheus\Storage\Adapter;
1011

1112

@@ -421,5 +422,50 @@ public function itShouldRejectInvalidLabelNames()
421422
new Histogram($this->adapter, 'test', 'some_metric', 'help', array('invalid label'), array(1));
422423
}
423424

425+
/**
426+
* @test
427+
* @dataProvider labelValuesDataProvider
428+
*
429+
* @param mixed $value The label value
430+
*/
431+
public function isShouldAcceptAnySequenceOfBasicLatinCharactersForLabelValues($value)
432+
{
433+
$label = 'foo';
434+
$histogram = new Histogram($this->adapter, 'test', 'some_metric', 'help', array($label), array(1));
435+
$histogram->observe(1, array($value));
436+
437+
$metrics = $this->adapter->collect();
438+
self::assertInternalType('array', $metrics);
439+
self::assertCount(1, $metrics);
440+
self::assertContainsOnlyInstancesOf(MetricFamilySamples::class, $metrics);
441+
442+
$metric = reset($metrics);
443+
$samples = $metric->getSamples();
444+
self::assertContainsOnlyInstancesOf(Sample::class, $samples);
445+
446+
foreach ($samples as $sample) {
447+
$labels = array_combine(
448+
array_merge($metric->getLabelNames(), $sample->getLabelNames()),
449+
$sample->getLabelValues()
450+
);
451+
self::assertEquals($value, $labels[$label]);
452+
}
453+
}
454+
455+
/**
456+
* @see isShouldAcceptArbitraryLabelValues
457+
* @return array
458+
*/
459+
public function labelValuesDataProvider()
460+
{
461+
$cases = [];
462+
// Basic Latin
463+
// See https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin
464+
for ($i = 32; $i <= 121; $i++) {
465+
$cases['ASCII code ' . $i] = array(chr($i));
466+
}
467+
return $cases;
468+
}
469+
424470
public abstract function configureAdapter();
425471
}

0 commit comments

Comments
 (0)