diff --git a/addons/web/static/src/core/domain.js b/addons/web/static/src/core/domain.js index d0f76dec02758..35c0c110eaeb1 100644 --- a/addons/web/static/src/core/domain.js +++ b/addons/web/static/src/core/domain.js @@ -10,6 +10,8 @@ import { toPyValue } from "./py_js/py_utils"; * @typedef {DomainListRepr | string | Domain} DomainRepr */ +class InvalidDomainError extends Error {} + /** * Javascript representation of an Odoo domain */ @@ -151,19 +153,27 @@ function normalizeDomainAST(domain, op = "&") { if (domain.type !== 4 /* List */) { throw new Error("Invalid domain AST"); } - let expected = -1; + if (domain.value.length === 0) { + return domain; + } + let expected = 1; for (let child of domain.value) { if (child.type === 1 /* String */ && (child.value === "&" || child.value === "|")) { - expected--; - } else if (child.type !== 1 /* String */ || child.value !== "!") { expected++; + } else if (child.type !== 1 /* String */ || child.value !== "!") { + expected--; } } let values = domain.value.slice(); - while (expected > 0) { - expected--; + while (expected < 0) { + expected++; values.unshift({ type: 1 /* String */, value: op }); } + if (expected > 0) { + throw new InvalidDomainError( + `invalid domain ${formatAST(domain)} (missing ${expected} segment(s))` + ); + } return { type: 4 /* List */, value: values }; } @@ -212,7 +222,7 @@ function matchCondition(record, condition) { if (fieldValue === false) { return false; } - return new RegExp(value.replace(/%/g, '.*')).test(fieldValue); + return new RegExp(value.replace(/%/g, ".*")).test(fieldValue); case "ilike": if (fieldValue === false) { return false; diff --git a/addons/web/static/tests/core/domain_tests.js b/addons/web/static/tests/core/domain_tests.js index 5e01589b6ed22..340db8862df00 100644 --- a/addons/web/static/tests/core/domain_tests.js +++ b/addons/web/static/tests/core/domain_tests.js @@ -89,25 +89,25 @@ QUnit.module("domain", {}, () => { QUnit.test("like, =like, ilike and =ilike", function (assert) { assert.expect(16); - assert.ok(new Domain([['a', 'like', 'value']]).contains({ a: 'value' })); - assert.ok(new Domain([['a', 'like', 'value']]).contains({ a: 'some value' })); - assert.notOk(new Domain([['a', 'like', 'value']]).contains({ a: 'Some Value' })); - assert.notOk(new Domain([['a', 'like', 'value']]).contains({ a: false })); - - assert.ok(new Domain([['a', '=like', '%value']]).contains({ a: 'value' })); - assert.ok(new Domain([['a', '=like', '%value']]).contains({ a: 'some value' })); - assert.notOk(new Domain([['a', '=like', '%value']]).contains({ a: 'Some Value' })); - assert.notOk(new Domain([['a', '=like', '%value']]).contains({ a: false })); - - assert.ok(new Domain([['a', 'ilike', 'value']]).contains({ a: 'value' })); - assert.ok(new Domain([['a', 'ilike', 'value']]).contains({ a: 'some value' })); - assert.ok(new Domain([['a', 'ilike', 'value']]).contains({ a: 'Some Value' })); - assert.notOk(new Domain([['a', 'ilike', 'value']]).contains({ a: false })); - - assert.ok(new Domain([['a', '=ilike', '%value']]).contains({ a: 'value' })); - assert.ok(new Domain([['a', '=ilike', '%value']]).contains({ a: 'some value' })); - assert.ok(new Domain([['a', '=ilike', '%value']]).contains({ a: 'Some Value' })); - assert.notOk(new Domain([['a', '=ilike', '%value']]).contains({ a: false })); + assert.ok(new Domain([["a", "like", "value"]]).contains({ a: "value" })); + assert.ok(new Domain([["a", "like", "value"]]).contains({ a: "some value" })); + assert.notOk(new Domain([["a", "like", "value"]]).contains({ a: "Some Value" })); + assert.notOk(new Domain([["a", "like", "value"]]).contains({ a: false })); + + assert.ok(new Domain([["a", "=like", "%value"]]).contains({ a: "value" })); + assert.ok(new Domain([["a", "=like", "%value"]]).contains({ a: "some value" })); + assert.notOk(new Domain([["a", "=like", "%value"]]).contains({ a: "Some Value" })); + assert.notOk(new Domain([["a", "=like", "%value"]]).contains({ a: false })); + + assert.ok(new Domain([["a", "ilike", "value"]]).contains({ a: "value" })); + assert.ok(new Domain([["a", "ilike", "value"]]).contains({ a: "some value" })); + assert.ok(new Domain([["a", "ilike", "value"]]).contains({ a: "Some Value" })); + assert.notOk(new Domain([["a", "ilike", "value"]]).contains({ a: false })); + + assert.ok(new Domain([["a", "=ilike", "%value"]]).contains({ a: "value" })); + assert.ok(new Domain([["a", "=ilike", "%value"]]).contains({ a: "some value" })); + assert.ok(new Domain([["a", "=ilike", "%value"]]).contains({ a: "Some Value" })); + assert.notOk(new Domain([["a", "=ilike", "%value"]]).contains({ a: false })); }); QUnit.test("complex domain", function (assert) { @@ -257,6 +257,32 @@ QUnit.module("domain", {}, () => { assert.notOk(Domain.and([Domain.FALSE, new Domain([["a", "=", 3]])]).contains({ a: 3 })); }); + QUnit.test("invalid domains should not succeed", function (assert) { + assert.throws( + () => new Domain(["|", ["hr_presence_state", "=", "absent"]]), + /invalid domain .* \(missing 1 segment/ + ); + assert.throws( + () => + new Domain([ + "|", + "|", + ["hr_presence_state", "=", "absent"], + ["attendance_state", "=", "checked_in"], + ]), + /invalid domain .* \(missing 1 segment/ + ); + assert.throws( + () => new Domain(["|", "|", ["hr_presence_state", "=", "absent"]]), + /invalid domain .* \(missing 2 segment\(s\)/ + ); + assert.throws( + () => new Domain(["&", ["composition_mode", "!=", "mass_post"]]), + /invalid domain .* \(missing 1 segment/ + ); + assert.throws(() => new Domain(["!"]), /invalid domain .* \(missing 1 segment/); + }); + // --------------------------------------------------------------------------- // Normalization // ---------------------------------------------------------------------------