diff --git a/CHANGELOG.md b/CHANGELOG.md index 095ef096dea69bfce7ff7df78cc5069303be762e..3db014dca1098328a03fa636a60717f156ad3cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Uncaught exception in dropdown menus of the edit mode when pressing arrow down or arrow up. +* [#182](https://gitlab.com/caosdb/caosdb-webui/-/issues/182) - Quotes breake + "googly" search * Hidden boolean select in edit mode. ### Security diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js index d4f1d34591863bddc46051a0727b4f7433baee37..03e372919377e537017a0f2917c0d66db9ed580e 100644 --- a/src/core/js/webcaosdb.js +++ b/src/core/js/webcaosdb.js @@ -1403,6 +1403,24 @@ var queryForm = new function () { location.href = connection.getBasePath() + "Entity/?" + pagingparam + "query=" + query; } + const _splitSearchTermsPattern = /"(?<dq>[^"]*)" |'(?<sq>[^']*)' |(?<nq>[^ ]+)/g; + + /** + * Split a query string into single terms. + * + * Terms are separated by white spaces. Terms which contain white spaces + * which are to be preserved must be enclosed in " or ' quotes. The + * enclosing quotation marks are being stripped. Currently no support for + * escape sequences for quotation marks. + * + * @param {string} query - complete query string. + * @return {string[]} array of the search terms. + */ + this.splitSearchTerms = function (query) { + // add empty space at the end, so every matching group ends with it -> easier regex. Also, undefined is filtered out + return Array.from((query + " ").matchAll(_splitSearchTermsPattern), (m) => m[1] || m[2] || m[3]).filter((word) => word); + } + this.bindOnClick = function (form, setter) { if (setter == null || typeof (setter) !== 'function' || setter.length !== 1) { throw new Error("setter must be a function with one param"); @@ -1422,7 +1440,10 @@ var queryForm = new function () { } if (!(value.startsWith("FIND") || value.startsWith("COUNT") || value.startsWith("SELECT"))) { // split words in query field at space and create query fragments - var words = queryField.value.split(" ").map(word => `A PROPERTY LIKE '*${word}*'`); + var words = queryForm.splitSearchTerms(queryField.value).map(word => `A PROPERTY LIKE '*${word.replaceAll("'", `\\'`)}*'`); + if (!words.length) { + return false; + } var query_string = "FIND ENTITY WHICH HAS "; // send a query that combines all fragments with an AND queryField.value = query_string + words.join(" AND "); diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js index 1c456847e1dfda20eb3a4dd70b7b3bcd37086d08..328a057f19af953399e08867085f6e04b5e785c5 100644 --- a/test/core/js/modules/webcaosdb.js.js +++ b/test/core/js/modules/webcaosdb.js.js @@ -1160,7 +1160,7 @@ QUnit.test("restoreLastQuery", function (assert) { QUnit.test("bindOnClick", function (assert) { assert.ok(queryForm.bindOnClick, "available"); - var done = assert.async(3); + var done = assert.async(4); queryForm.redirect = function (a, b) { done(); }; @@ -1215,7 +1215,41 @@ QUnit.test("bindOnClick", function (assert) { assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*free*' AND A PROPERTY LIKE '*text*' AND A PROPERTY LIKE '*3*'", "after4: storage not empty."); $(form).remove(); -}) + + // ... then with quotation marks gone rogue + form.query.value = "\"with double\" 'and single' \"what's wrong?\" ' \"nothin'\" \"'bla"; + $("body").append(form); + $(form).append(submitButton); + submitButton.click(); + assert.equal(storage(), `FIND ENTITY WHICH HAS A PROPERTY LIKE '*with double*' AND A PROPERTY LIKE '*and single*' AND A PROPERTY LIKE '*what\\'s wrong?*' AND A PROPERTY LIKE '*\\'*' AND A PROPERTY LIKE '*nothin\\'*' AND A PROPERTY LIKE '*"\\'bla*'`, "after5: stuff with quotation marks"); + + // ... then with empty quotation marks. this will not trigger the query execution at all + storage("not triggered"); + form.query.value = '""' + $("body").append(form); + $(form).append(submitButton); + submitButton.click(); + assert.equal(storage(), "not triggered", "not triggered"); + + $(form).remove(); +}); + +QUnit.test("splitSearchTerms", function (assert) { + assert.ok(queryForm.splitSearchTerms, "available"); + const cases = [ + ["", []], + ['"', ['"']], + ["a", ["a"]], + ["a b", ["a", "b"]], + ["'a b'", ["a b"]], + ['"a b"', ["a b"]], + [`"with double" 'and single' "what's wrong?" ' "nothin'" "'bla`, + ["with double", "and single", "what's wrong?", "'", "nothin'", `"'bla`]], + ]; + for(let testCase of cases) { + assert.deepEqual(queryForm.splitSearchTerms(testCase[0]), testCase[1], `test case ${testCase[0]}`); + } +}); /* MODULE paging */ QUnit.module("webcaosdb.js - paging", {