[kql] Fix toKqlExpression for quoted values with escaped quotes (#162599)

## Summary

`toKqlExpression` support was added in
https://github.com/elastic/kibana/pull/161601. This PR Fixes a bug with
`toKqlExpression` that did not properly re-escape quotes inside of
quoted values.

Without this fix, calling `toKqlExpression` on the AST generated from a
KQL expression like `my_field: "quoted \"value\""` would result in a
string like `my_field: "quoted "value""` instead of the original
expression.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Lukas Olson 2023-07-27 13:36:22 -07:00 committed by GitHub
parent a1be0029c3
commit e8978ee630
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 39 additions and 48 deletions

View file

@ -116,6 +116,7 @@ export {
nodeTypes,
toElasticsearchQuery,
escapeKuery,
escapeQuotes,
} from './src/kuery';
export {

View file

@ -23,5 +23,5 @@ export const toElasticsearchQuery = (...params: Parameters<typeof astToElasticse
export { KQLSyntaxError } from './kuery_syntax_error';
export { nodeTypes, nodeBuilder } from './node_types';
export { fromKueryExpression, toKqlExpression } from './ast';
export { escapeKuery } from './utils';
export { escapeKuery, escapeQuotes } from './utils';
export type { DslQuery, KueryNode, KueryQueryOptions, KueryParseOptions } from './types';

View file

@ -31,13 +31,13 @@ describe('kuery node types', () => {
});
describe('toKqlExpression', () => {
test('quoted', () => {
test('unquoted', () => {
const node = buildNode('foo');
const result = toKqlExpression(node);
expect(result).toBe('foo');
});
test('unquoted', () => {
test('quoted', () => {
const node = buildNode('foo', true);
const result = toKqlExpression(node);
expect(result).toBe('"foo"');
@ -54,6 +54,12 @@ describe('kuery node types', () => {
const result = toKqlExpression(node);
expect(result).toBe('foo \\and bar \\not baz \\or qux');
});
test('quoted with escaped quotes', () => {
const node = buildNode(`I said, "Hello."`, true);
const result = toKqlExpression(node);
expect(result).toBe(`"I said, \\"Hello.\\""`);
});
});
});
});

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { type KueryNode, escapeKuery } from '..';
import { type KueryNode, escapeKuery, escapeQuotes } from '..';
export const KQL_NODE_TYPE_LITERAL = 'literal';
@ -35,5 +35,5 @@ export function toElasticsearchQuery(node: KqlLiteralNode) {
}
export function toKqlExpression(node: KqlLiteralNode): string {
return node.isQuoted ? `"${node.value}"` : escapeKuery(`${node.value}`);
return node.isQuoted ? `"${escapeQuotes(`${node.value}`)}"` : escapeKuery(`${node.value}`);
}

View file

@ -6,7 +6,23 @@
* Side Public License, v 1.
*/
import { escapeKuery } from './escape_kuery';
import { escapeKuery, escapeQuotes } from './escape_kuery';
describe('escapeQuotes', () => {
test('should escape quotes', () => {
const value = 'I said, "Hello."';
const expected = 'I said, \\"Hello.\\"';
expect(escapeQuotes(value)).toBe(expected);
});
test('should escape backslashes and quotes', () => {
const value = 'Backslashes \\" in the middle and ends with quotes \\"';
const expected = 'Backslashes \\\\\\" in the middle and ends with quotes \\\\\\"';
expect(escapeQuotes(value)).toBe(expected);
});
});
describe('escapeKuery', () => {
test('should escape special characters', () => {

View file

@ -8,6 +8,14 @@
import { flow } from 'lodash';
/**
* Escapes backslashes and double-quotes. (Useful when putting a string in quotes to use as a value
* in a KQL expression. See the QuotedCharacter rule in kuery.peg.)
*/
export function escapeQuotes(str: string) {
return str.replace(/[\\"]/g, '\\$&');
}
/**
* Escapes a Kuery node value to ensure that special characters, operators, and whitespace do not result in a parsing error or unintended
* behavior when using the value as an argument for the `buildNode` function.

View file

@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
export { escapeKuery } from './escape_kuery';
export { escapeKuery, escapeQuotes } from './escape_kuery';

View file

@ -1,25 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { escapeQuotes } from './escape_kuery';
describe('Kuery escape', () => {
test('should escape quotes', () => {
const value = 'I said, "Hello."';
const expected = 'I said, \\"Hello.\\"';
expect(escapeQuotes(value)).toBe(expected);
});
test('should escape backslashes and quotes', () => {
const value = 'Backslashes \\" in the middle and ends with quotes \\"';
const expected = 'Backslashes \\\\\\" in the middle and ends with quotes \\\\\\"';
expect(escapeQuotes(value)).toBe(expected);
});
});

View file

@ -1,15 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* Escapes backslashes and double-quotes. (Useful when putting a string in quotes to use as a value
* in a KQL expression. See the QuotedCharacter rule in kuery.peg.)
*/
export function escapeQuotes(str: string) {
return str.replace(/[\\"]/g, '\\$&');
}

View file

@ -9,7 +9,7 @@
import { flatten } from 'lodash';
import { CoreSetup } from '@kbn/core/public';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import { escapeQuotes } from './lib/escape_kuery';
import { escapeQuotes } from '@kbn/es-query';
import { KqlQuerySuggestionProvider } from './types';
import type { UnifiedSearchPublicPluginStart } from '../../../types';
import { QuerySuggestion, QuerySuggestionTypes } from '../query_suggestion_provider';