Loki: Fix wrongly escaped label values when using LabelFilter (#59812)

* change backticks to quotes

* add unescaping to `addFilterAsLabelFilter`

* fix removal of wrong quotes

* change unescape order
This commit is contained in:
Sven Grossmann 2022-12-06 12:30:03 +01:00 committed by GitHub
parent 78ef55eb06
commit 932349b5ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 95 additions and 39 deletions

View File

@ -1,4 +1,4 @@
import { isBytesString } from './languageUtils';
import { escapeLabelValueInExactSelector, isBytesString, unescapeLabelValue } from './languageUtils';
describe('isBytesString', () => {
it('correctly matches bytes string with integers', () => {
@ -18,3 +18,27 @@ describe('isBytesString', () => {
expect(isBytesString('1.234')).toBe(false);
});
});
describe('escapeLabelValueInExactSelector', () => {
it.each`
value | escapedValue
${'nothing to escape'} | ${'nothing to escape'}
${'escape quote: "'} | ${'escape quote: \\"'}
${'escape newline: \nend'} | ${'escape newline: \\nend'}
${'escape slash: \\'} | ${'escape slash: \\\\'}
`('when called with $value', ({ value, escapedValue }) => {
expect(escapeLabelValueInExactSelector(value)).toEqual(escapedValue);
});
});
describe('unescapeLabelValueInExactSelector', () => {
it.each`
value | unescapedValue
${'nothing to unescape'} | ${'nothing to unescape'}
${'escape quote: \\"'} | ${'escape quote: "'}
${'escape newline: \\nend'} | ${'escape newline: \nend'}
${'escape slash: \\\\'} | ${'escape slash: \\'}
`('when called with $value', ({ value, unescapedValue }) => {
expect(unescapeLabelValue(value)).toEqual(unescapedValue);
});
});

View File

@ -35,6 +35,10 @@ export function escapeLabelValueInExactSelector(labelValue: string): string {
return labelValue.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"');
}
export function unescapeLabelValue(labelValue: string): string {
return labelValue.replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
export function escapeLabelValueInRegexSelector(labelValue: string): string {
return escapeLabelValueInExactSelector(escapeLokiRegexp(labelValue));
}

View File

@ -44,6 +44,14 @@ describe('addLabelToQuery()', () => {
${'{} | logfmt | x="y"'} | ${'query without stream selector and with parser and label filter'} | ${'bar'} | ${'='} | ${'baz'} | ${'{bar="baz"}| logfmt | x="y"'}
${'sum(rate({x="y"} [5m])) + sum(rate({} | logfmt [5m]))'} | ${'metric query with 1 empty and 1 not empty stream selector with parser'} | ${'bar'} | ${'='} | ${'baz'} | ${'sum(rate({x="y", bar="baz"} [5m])) + sum(rate({bar="baz"}| logfmt [5m]))'}
${'sum(rate({x="y"} | logfmt [5m])) + sum(rate({} [5m]))'} | ${'metric query with 1 non-empty and 1 not empty stream selector with parser'} | ${'bar'} | ${'='} | ${'baz'} | ${'sum(rate({x="y", bar="baz"} | logfmt [5m])) + sum(rate({bar="baz"}[5m]))'}
${'{x="yy"}'} | ${'simple query with escaped value'} | ${'bar'} | ${'='} | ${'"baz"'} | ${'{x="yy", bar=""baz""}'}
${'{x="yy"}'} | ${'simple query with escaped value'} | ${'bar'} | ${'='} | ${'\\"baz\\"'} | ${'{x="yy", bar="\\"baz\\""}'}
${'{x="yy"}'} | ${'simple query with an other escaped value'} | ${'bar'} | ${'='} | ${'baz\\\\'} | ${'{x="yy", bar="baz\\\\"}'}
${'{x="yy"}'} | ${'simple query with escaped value and regex operator'} | ${'bar'} | ${'~='} | ${'baz\\\\'} | ${'{x="yy", bar~="baz\\\\"}'}
${'{foo="bar"} | logfmt'} | ${'query with parser with escaped value'} | ${'bar'} | ${'='} | ${'\\"baz\\"'} | ${'{foo="bar"} | logfmt | bar=`"baz"`'}
${'{foo="bar"} | logfmt'} | ${'query with parser with an other escaped value'} | ${'bar'} | ${'='} | ${'baz\\\\'} | ${'{foo="bar"} | logfmt | bar=`baz\\`'}
${'{foo="bar"} | logfmt'} | ${'query with parser with escaped value and regex operator'} | ${'bar'} | ${'~='} | ${'\\"baz\\"'} | ${'{foo="bar"} | logfmt | bar~=`"baz"`'}
${'{foo="bar"} | logfmt'} | ${'query with parser with escaped value and regex operator'} | ${'bar'} | ${'~='} | ${'\\"baz\\"'} | ${'{foo="bar"} | logfmt | bar~=`"baz"`'}
`(
'should add label to query: $query, description: $description',
({ query, description, label, operator, value, expectedResult }) => {

View File

@ -18,6 +18,7 @@ import {
import { QueryBuilderLabelFilter } from '../prometheus/querybuilder/shared/types';
import { unescapeLabelValue } from './languageUtils';
import { LokiQueryModeller } from './querybuilder/LokiQueryModeller';
import { buildVisualQueryFromString } from './querybuilder/parsing';
@ -329,7 +330,9 @@ function addFilterAsLabelFilter(
const start = query.substring(prev, match.to);
const end = isLast ? query.substring(match.to) : '';
const labelFilter = ` | ${filter.label}${filter.op}\`${filter.value}\``;
// we now unescape all escaped values again, because we are using backticks which can handle those cases.
// we also don't care about the operator here, because we need to unescape for both, regex and equal.
const labelFilter = ` | ${filter.label}${filter.op}\`${unescapeLabelValue(filter.value)}\``;
newQuery += start + labelFilter + end;
prev = match.to;
}

View File

@ -571,6 +571,21 @@ describe('buildVisualQueryFromString', () => {
})
);
});
it('parses simple query with quotes in label value', () => {
expect(buildVisualQueryFromString('{app="\\"frontend\\""}')).toEqual(
noErrors({
labels: [
{
op: '=',
value: '\\"frontend\\"',
label: 'app',
},
],
operations: [],
})
);
});
});
function noErrors(query: LokiVisualQuery) {

View File

@ -212,7 +212,9 @@ function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter {
const labelNode = node.getChild(Identifier);
const label = getString(expr, labelNode);
const op = getString(expr, labelNode!.nextSibling);
const value = getString(expr, node.getChild(String)).replace(/"/g, '');
let value = getString(expr, node.getChild(String));
// `value` is wrapped in double quotes, so we need to remove them. As a value can contain double quotes, we can't use RegEx here.
value = value.substring(1, value.length - 1);
return {
label,