From bd0c1a261e14673ba4b93d21c18cc8627242583d Mon Sep 17 00:00:00 2001 From: lostfields Date: Thu, 7 Mar 2024 12:42:33 +0100 Subject: [PATCH 1/2] fix: escaping of slash #deploy_branch --- src/linq/peg/grammar/odata.pegjs | 5 ++- src/linq/peg/parser/odata-parser.js | 51 +++++++++++++--------- src/linq/peg/translator/odatatranslator.ts | 2 +- src/test/odatavisitor.ts | 9 +++- src/test/translator/odata.ts | 21 +++++++++ 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/linq/peg/grammar/odata.pegjs b/src/linq/peg/grammar/odata.pegjs index 0f81062..3dd6463 100644 --- a/src/linq/peg/grammar/odata.pegjs +++ b/src/linq/peg/grammar/odata.pegjs @@ -343,8 +343,8 @@ HexDigit = [a-f] / [A-F] / [0-9] StringLiteral - = "\'" chars:(Escape / !['\\\n\r] . )* "\'" - { return { type: 'Literal', value: chars.map(l => l[0] == undefined ? l[1] : l[0] + l[1]).join('').replace(/\\(["'\\])|'(')/g, '$1$2') } } + = "'" chars:(Escape / ![\\'\n\r] . )* "'" + { return { type: 'Literal', value: chars.map(l => l[0] || l[1]).join('').replace(/\\\\/g, '\\') } } ArrayLiteral = LPAR __ elements:(first:Expression rest:(COMMA __ Expression)* { return buildList(first, rest, 2)})? (COMMA __)? __ RPAR __ @@ -352,6 +352,7 @@ ArrayLiteral Escape = "''" + / "\\" / "\\" ([btnfr"'\\] / OctalEscape / UnicodeEscape) OctalEscape diff --git a/src/linq/peg/parser/odata-parser.js b/src/linq/peg/parser/odata-parser.js index f21334e..50f98b6 100644 --- a/src/linq/peg/parser/odata-parser.js +++ b/src/linq/peg/parser/odata-parser.js @@ -231,7 +231,7 @@ function peg$parse(input, options) { var peg$r11 = /^[+\-]/; var peg$r12 = /^[pP]/; var peg$r13 = /^[0-9A-Fa-f]/; - var peg$r14 = /^['\\\n\r]/; + var peg$r14 = /^[\\'\n\r]/; var peg$r15 = /^[btnfr"'\\]/; var peg$r16 = /^[0-3]/; @@ -266,7 +266,7 @@ function peg$parse(input, options) { var peg$e28 = peg$classExpectation(["+", "-"], false, false); var peg$e29 = peg$classExpectation(["p", "P"], false, false); var peg$e30 = peg$classExpectation([["0", "9"], ["A", "F"], ["a", "f"]], false, false); - var peg$e31 = peg$classExpectation(["'", "\\", "\n", "\r"], false, false); + var peg$e31 = peg$classExpectation(["\\", "'", "\n", "\r"], false, false); var peg$e32 = peg$anyExpectation(); var peg$e33 = peg$literalExpectation("''", false); var peg$e34 = peg$literalExpectation("\\", false); @@ -438,7 +438,7 @@ function peg$parse(input, options) { var peg$f26 = function(value) { return { type: 'DateLiteral', value: flattenArray(value).join('') }; }; var peg$f27 = function(first, value, last) { return { type: 'DateLiteral', value: flattenArray(value).join('') }; }; var peg$f28 = function() { return { type: 'NumberLiteral', value: text() }; }; - var peg$f29 = function(chars) { return { type: 'Literal', value: chars.map(l => l[0] == undefined ? l[1] : l[0] + l[1]).join('').replace(/\\(["'\\])|'(')/g, '$1$2') } }; + var peg$f29 = function(chars) { return { type: 'Literal', value: chars.map(l => l[0] || l[1]).join('').replace(/\\\\/g, '\\') } }; var peg$f30 = function(first, rest) { return buildList(first, rest, 2)}; var peg$f31 = function(elements) { return { type: 'ArrayLiteral', elements: elements } }; var peg$currPos = options.peg$currPos | 0; @@ -4013,38 +4013,47 @@ function peg$parse(input, options) { if (peg$silentFails === 0) { peg$fail(peg$e33); } } if (s0 === peg$FAILED) { - s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c18; + s0 = peg$c18; peg$currPos++; } else { - s1 = peg$FAILED; + s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e34); } } - if (s1 !== peg$FAILED) { - s2 = input.charAt(peg$currPos); - if (peg$r15.test(s2)) { + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c18; peg$currPos++; } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e34); } } - if (s2 === peg$FAILED) { - s2 = peg$parseOctalEscape(); + if (s1 !== peg$FAILED) { + s2 = input.charAt(peg$currPos); + if (peg$r15.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e35); } + } if (s2 === peg$FAILED) { - s2 = peg$parseUnicodeEscape(); + s2 = peg$parseOctalEscape(); + if (s2 === peg$FAILED) { + s2 = peg$parseUnicodeEscape(); + } + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; } - } - if (s2 !== peg$FAILED) { - s1 = [s1, s2]; - s0 = s1; } else { peg$currPos = s0; s0 = peg$FAILED; } - } else { - peg$currPos = s0; - s0 = peg$FAILED; } } diff --git a/src/linq/peg/translator/odatatranslator.ts b/src/linq/peg/translator/odatatranslator.ts index 20a5b7a..4ff6da2 100644 --- a/src/linq/peg/translator/odatatranslator.ts +++ b/src/linq/peg/translator/odatatranslator.ts @@ -51,7 +51,7 @@ export class ODataTranslator implements IExpressionVisitor { return `${expression.value}` case 'string': - return `'${expression.value.replace(/'/g, '\'')}'` + return `'${expression.value.replace(/'/g, '\'\'')}'` case 'boolean': return expression.value ? 'true' : 'false' diff --git a/src/test/odatavisitor.ts b/src/test/odatavisitor.ts index 8feb9dc..103c632 100644 --- a/src/test/odatavisitor.ts +++ b/src/test/odatavisitor.ts @@ -131,7 +131,6 @@ describe('When using OData for ExpressionVisitor', () => { assert.equal((expr).value, true) }) - it('should evaluate a expression with date as string', () => { let reduced = reducer.parseOData('date ge datetime\'2017-05-01Z\''), expr = reducer.evaluate(reduced, vars) @@ -254,6 +253,14 @@ describe('When using OData for ExpressionVisitor', () => { assert.ok((expr).value == false, 'Expected a literal of value true') }) + it('should handle escaping of special character \\', () => { + let reduced = reducer.parseOData(`contains(string, '\\\\')`), + expr = reducer.evaluate(reduced, vars) + + assert.ok(expr.type == Expr.ExpressionType.Literal, 'Expected a literal') + assert.ok((expr).value == false, 'Expected a literal of value false') + }) + describe('for IN operator', () => { it('should evaluate in operator using constant with primary array where it is true', () => { let reduced = reducer.parseOData('13 in array'), diff --git a/src/test/translator/odata.ts b/src/test/translator/odata.ts index 45c1af4..6f0388c 100644 --- a/src/test/translator/odata.ts +++ b/src/test/translator/odata.ts @@ -36,5 +36,26 @@ describe('When using Translator', () => { chai.expect(odata).to.equal(`(contains(customer/name, 'kalle') eq true) or (contains(customer/no, '54'))`) }) + + it('should handle escape of character \'', () => { + let expr = visitor.parseOData(`contains(customer/name, 'ka''e')`) + let odata = translator.visit(expr) + + chai.expect(odata).to.equal(`contains(customer/name, 'ka''e')`) + }) + + it('should handle escape of character \\', () => { + let expr = visitor.parseOData(`contains(customer/name, 'ka\\e')`) + let odata = translator.visit(expr) + + chai.expect(odata).to.equal(`contains(customer/name, 'ka\\e')`) + }) + + it('should handle unicode if that is a thing', () => { + let expr = visitor.parseOData(`contains(customer/name, 'ka\\u1128')`) + let odata = translator.visit(expr) + + chai.expect(odata).to.equal(`contains(customer/name, 'ka\\u1128')`) + }) }) }) \ No newline at end of file From 3ba6b9398fae3f158d0025ac5ee2a83af4571ce7 Mon Sep 17 00:00:00 2001 From: lostfields Date: Fri, 8 Mar 2024 08:02:07 +0100 Subject: [PATCH 2/2] fix: double slash #deploy_branch --- src/linq/peg/grammar/odata.pegjs | 4 ++-- src/linq/peg/parser/odata-parser.js | 6 +++--- src/test/translator/odata.ts | 9 ++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/linq/peg/grammar/odata.pegjs b/src/linq/peg/grammar/odata.pegjs index 3dd6463..d8f70f2 100644 --- a/src/linq/peg/grammar/odata.pegjs +++ b/src/linq/peg/grammar/odata.pegjs @@ -343,8 +343,8 @@ HexDigit = [a-f] / [A-F] / [0-9] StringLiteral - = "'" chars:(Escape / ![\\'\n\r] . )* "'" - { return { type: 'Literal', value: chars.map(l => l[0] || l[1]).join('').replace(/\\\\/g, '\\') } } + = "'" chars:(Escape / !['\n\r] . )* "'" + { return { type: 'Literal', value: chars.map(l => l[0] || l[1]).join('') } } ArrayLiteral = LPAR __ elements:(first:Expression rest:(COMMA __ Expression)* { return buildList(first, rest, 2)})? (COMMA __)? __ RPAR __ diff --git a/src/linq/peg/parser/odata-parser.js b/src/linq/peg/parser/odata-parser.js index 50f98b6..384fab6 100644 --- a/src/linq/peg/parser/odata-parser.js +++ b/src/linq/peg/parser/odata-parser.js @@ -231,7 +231,7 @@ function peg$parse(input, options) { var peg$r11 = /^[+\-]/; var peg$r12 = /^[pP]/; var peg$r13 = /^[0-9A-Fa-f]/; - var peg$r14 = /^[\\'\n\r]/; + var peg$r14 = /^['\n\r]/; var peg$r15 = /^[btnfr"'\\]/; var peg$r16 = /^[0-3]/; @@ -266,7 +266,7 @@ function peg$parse(input, options) { var peg$e28 = peg$classExpectation(["+", "-"], false, false); var peg$e29 = peg$classExpectation(["p", "P"], false, false); var peg$e30 = peg$classExpectation([["0", "9"], ["A", "F"], ["a", "f"]], false, false); - var peg$e31 = peg$classExpectation(["\\", "'", "\n", "\r"], false, false); + var peg$e31 = peg$classExpectation(["'", "\n", "\r"], false, false); var peg$e32 = peg$anyExpectation(); var peg$e33 = peg$literalExpectation("''", false); var peg$e34 = peg$literalExpectation("\\", false); @@ -438,7 +438,7 @@ function peg$parse(input, options) { var peg$f26 = function(value) { return { type: 'DateLiteral', value: flattenArray(value).join('') }; }; var peg$f27 = function(first, value, last) { return { type: 'DateLiteral', value: flattenArray(value).join('') }; }; var peg$f28 = function() { return { type: 'NumberLiteral', value: text() }; }; - var peg$f29 = function(chars) { return { type: 'Literal', value: chars.map(l => l[0] || l[1]).join('').replace(/\\\\/g, '\\') } }; + var peg$f29 = function(chars) { return { type: 'Literal', value: chars.map(l => l[0] || l[1]).join('') } }; var peg$f30 = function(first, rest) { return buildList(first, rest, 2)}; var peg$f31 = function(elements) { return { type: 'ArrayLiteral', elements: elements } }; var peg$currPos = options.peg$currPos | 0; diff --git a/src/test/translator/odata.ts b/src/test/translator/odata.ts index 6f0388c..614bf83 100644 --- a/src/test/translator/odata.ts +++ b/src/test/translator/odata.ts @@ -49,7 +49,14 @@ describe('When using Translator', () => { let odata = translator.visit(expr) chai.expect(odata).to.equal(`contains(customer/name, 'ka\\e')`) - }) + }) + + it('should handle escape of character \\ twice', () => { + let expr = visitor.parseOData(`contains(customer/name, 'ka\\\\e')`) + let odata = translator.visit(expr) + + chai.expect(odata).to.equal(`contains(customer/name, 'ka\\\\e')`) + }) it('should handle unicode if that is a thing', () => { let expr = visitor.parseOData(`contains(customer/name, 'ka\\u1128')`)