diff --git a/CHANGELOG.md b/CHANGELOG.md index 9948b8cdabdbb58f7a365b50e0ba88173560d5e5..70505c9e5719ceba2d40ba2b6c1b197e8eefe603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* #27 - star matches slashes (e.g. for `FIND ... STORED AT /*.dat`). + +* #30 - file path cannot be in quotes + - #46 - Server-side scripting failed as an unprivileged user because the was no writable home directory. diff --git a/src/main/java/caosdb/server/query/CQLParser.g4 b/src/main/java/caosdb/server/query/CQLParser.g4 index 531b61951c508e7cb4e068a4ee061e05ffa5b847..a545871d9bf82de720da6dbfab712fbaab245da7 100644 --- a/src/main/java/caosdb/server/query/CQLParser.g4 +++ b/src/main/java/caosdb/server/query/CQLParser.g4 @@ -444,7 +444,7 @@ value returns [String str] : number {$str = $text;} | datetime {$str = $text;} - | atom {$str = $atom.str;} + | atom {$str = $atom.ep.toString();} ; number @@ -454,20 +454,17 @@ number location returns [String str] : - atom {$str = $atom.str;} + atom {$str = $atom.ep.str;} | SLASHES ? ((WHICH | WITH)+ SLASHES |( TXT | COLON | HYPHEN | NUM | DOT | ESC_STAR | ESC_BS | ESC_REGEXP_END | STAR )+ SLASHES ?)* {$str = $text; } ; -atom returns [String str] - @init { - $str = null; - } +atom returns [Query.Pattern ep] : - double_quoted {$str = $double_quoted.ep.toString();} - | single_quoted {$str = $single_quoted.ep.toString();} - | (TXT | NUM | REGEXP_MARKER | STAR | A )+ {$str = $text;} + double_quoted {$ep = $double_quoted.ep;} + | single_quoted {$ep = $single_quoted.ep;} + | (TXT | NUM | REGEXP_MARKER | STAR | A )+ {$ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);} ; single_quoted returns [Query.Pattern ep] locals [StringBuffer sb, int patternType] diff --git a/src/main/java/caosdb/server/query/Query.java b/src/main/java/caosdb/server/query/Query.java index a62a6acae9bcc413e7ab06c812312f8e8b8fe323..1f85826967cf5020cdbdb6ecbbfe4e9452b9d929 100644 --- a/src/main/java/caosdb/server/query/Query.java +++ b/src/main/java/caosdb/server/query/Query.java @@ -126,7 +126,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac public static final int TYPE_LIKE = 1; public static final int TYPE_REGEXP = 2; public final int type; - private final String str; + public final String str; public Pattern(final String str, final int type) { this.type = type; diff --git a/src/main/java/caosdb/server/query/StoredAt.java b/src/main/java/caosdb/server/query/StoredAt.java index 203319fbf9885c2ce907e16b780128f5fce86a85..78a5446db17151f72d9b1a366c819c1a55adceff 100644 --- a/src/main/java/caosdb/server/query/StoredAt.java +++ b/src/main/java/caosdb/server/query/StoredAt.java @@ -45,8 +45,7 @@ public class StoredAt implements EntityFilterInterface { public StoredAt(final String loc) { Path locPath = Paths.get(loc); - this.location = - locPath.normalize().toString().replaceFirst("^/", "") + (loc.endsWith("/") ? "/" : ""); + this.location = locPath.normalize().toString() + (loc.endsWith("/") ? "/" : ""); this.pattern_matching = requiresPatternMatching(this.location); if (this.pattern_matching) { @@ -87,7 +86,7 @@ public class StoredAt implements EntityFilterInterface { java.util.regex.Pattern multiplePat = Pattern.compile(findMultipleAst); // Simple SQL escape replacements - String converted = location.replace("%", "\\%").replace("_", "\\_"); + String converted = location.replaceFirst("^/", "").replace("%", "\\%").replace("_", "\\_"); // * -> %%, has to run mutliple times, because otherwise the separation between patterns would // need to be larger than one character while (singlePat.matcher(converted).find()) { @@ -101,7 +100,11 @@ public class StoredAt implements EntityFilterInterface { converted = converted.replaceAll(singleEscapedAst.replace("b", "\\\\"), "*"); // Final exceptions - converted = converted.replaceFirst("^%{2,}", "%"); // .replaceFirst("^/", ""); + if (location.startsWith("/*") && !location.startsWith("/**")) { + converted = converted.replaceFirst("^/", ""); + } else { + converted = converted.replaceFirst("^%{2,}", "%"); // .replaceFirst("^/", ""); + } return converted; } @@ -128,7 +131,8 @@ public class StoredAt implements EntityFilterInterface { if (this.location == null) { // location callSAT.setNull(3, VARCHAR); } else { - callSAT.setString(3, (this.pattern_matching ? this.likeLocation : this.location)); + callSAT.setString( + 3, (this.pattern_matching ? this.likeLocation : this.location.replaceFirst("^/", ""))); } callSAT.setString(4, (this.pattern_matching ? "LIKE" : "=")); diff --git a/src/test/java/caosdb/server/query/TestCQL.java b/src/test/java/caosdb/server/query/TestCQL.java index d5a939577b274f4a01355be0a8a8fd1b2a948756..6f311ad2b4425bda054c10e5956e39d7d088d6c4 100644 --- a/src/test/java/caosdb/server/query/TestCQL.java +++ b/src/test/java/caosdb/server/query/TestCQL.java @@ -28,15 +28,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; - -import caosdb.server.CaosDBServer; -import caosdb.server.database.access.Access; -import caosdb.server.database.backend.implementation.MySQL.ConnectionException; -import caosdb.server.database.exceptions.TransactionException; -import caosdb.server.query.CQLParser.CqContext; -import caosdb.server.query.Query.Pattern; -import caosdb.server.query.Query.QueryException; -import caosdb.server.utils.Initialization; import java.io.IOException; import java.sql.SQLException; import java.util.LinkedList; @@ -47,6 +38,14 @@ import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; import org.junit.BeforeClass; import org.junit.Test; +import caosdb.server.CaosDBServer; +import caosdb.server.database.access.Access; +import caosdb.server.database.backend.implementation.MySQL.ConnectionException; +import caosdb.server.database.exceptions.TransactionException; +import caosdb.server.query.CQLParser.CqContext; +import caosdb.server.query.Query.Pattern; +import caosdb.server.query.Query.QueryException; +import caosdb.server.utils.Initialization; public class TestCQL { @@ -229,6 +228,10 @@ public class TestCQL { String filepath_pat02 = "/foo\\\\*/"; // -> \\* (2) String filepath_pat03 = "/foo\\\\\\\\*/"; // -> \\\\* (4) String filepath_pat04 = "/foo**/"; + String query_filepath_quotes = + "FIND FILE WHICH IS STORED AT '/SimulationData/2016_single/2018-01-10/**'"; + String query_filepath_quotes_2 = + "FIND FILE WHICH IS STORED AT /SimulationData/2016_single/2018-01-10/**"; String referenceByLikePattern = "FIND ENTITY WHICH IS REFERENCED BY *name*"; @@ -1596,7 +1599,7 @@ public class TestCQL { assertNotNull(sfq.filter); assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName()); - assertEquals("SAT(bla/bla/bla)", ((StoredAt) sfq.filter).toString()); + assertEquals("SAT(/bla/bla/bla)", ((StoredAt) sfq.filter).toString()); } @Test @@ -1642,7 +1645,7 @@ public class TestCQL { assertNotNull(sfq.filter); assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName()); - assertEquals("SAT(bla/bla/bla)", ((StoredAt) sfq.filter).toString()); + assertEquals("SAT(/bla/bla/bla)", ((StoredAt) sfq.filter).toString()); } @Test @@ -1687,7 +1690,7 @@ public class TestCQL { assertEquals(Query.Role.FILE, sfq.r); assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName()); - assertEquals("SAT(bla/bla/bla/)", ((StoredAt) sfq.filter).toString()); + assertEquals("SAT(/bla/bla/bla/)", ((StoredAt) sfq.filter).toString()); } /** @@ -1738,7 +1741,7 @@ public class TestCQL { assertNotNull(sfq.filter); assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName()); - assertEquals("SAT(bla/bla/bla.html)", ((StoredAt) sfq.filter).toString()); + assertEquals("SAT(/bla/bla/bla.html)", ((StoredAt) sfq.filter).toString()); } /** @@ -1789,7 +1792,7 @@ public class TestCQL { assertNotNull(sfq.filter); assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName()); - assertEquals("SAT(bla/bla.html)", ((StoredAt) sfq.filter).toString()); + assertEquals("SAT(/bla/bla.html)", ((StoredAt) sfq.filter).toString()); } /** @@ -1840,7 +1843,7 @@ public class TestCQL { assertNotNull(sfq.filter); assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName()); - assertEquals("SAT(bla/bla_bla.html)", ((StoredAt) sfq.filter).toString()); + assertEquals("SAT(/bla/bla_bla.html)", ((StoredAt) sfq.filter).toString()); } /** @@ -1894,9 +1897,9 @@ public class TestCQL { assertEquals(StoredAt.class.getName(), sfq.filter.getClass().getName()); final StoredAt storedAt = (StoredAt) sfq.filter; - assertFalse(storedAt.requiresPatternMatching(storedAt.location)); + assertFalse(StoredAt.requiresPatternMatching(storedAt.location)); assertEquals( - "SAT(" + absPath.toString().replaceFirst("^/", "") + ")", + "SAT(/" + absPath.toString().replaceFirst("^/", "") + ")", ((StoredAt) sfq.filter).toString()); } @@ -1955,7 +1958,7 @@ public class TestCQL { assertFalse(c.getFilters().isEmpty()); assertNotNull(c.getFilters().get(0)); assertEquals(StoredAt.class.getName(), c.getFilters().get(0).getClass().getName()); - assertEquals("SAT(bla/bla/bla)", ((StoredAt) c.getFilters().get(0)).toString()); + assertEquals("SAT(/bla/bla/bla)", ((StoredAt) c.getFilters().get(0)).toString()); assertNotNull(c.getFilters().get(1)); assertEquals(POV.class.getName(), c.getFilters().get(1).getClass().getName()); assertEquals("POV(pname2,=,val2)", ((POV) c.getFilters().get(1)).toString()); @@ -2022,7 +2025,7 @@ public class TestCQL { assertFalse(c.getFilters().isEmpty()); assertNotNull(c.getFilters().get(1)); assertEquals(StoredAt.class.getName(), c.getFilters().get(1).getClass().getName()); - assertEquals("SAT(bla/bla/bla)", ((StoredAt) c.getFilters().get(1)).toString()); + assertEquals("SAT(/bla/bla/bla)", ((StoredAt) c.getFilters().get(1)).toString()); assertNotNull(c.getFilters().get(0)); assertEquals(POV.class.getName(), c.getFilters().get(0).getClass().getName()); assertEquals("POV(pname2,=,val2)", ((POV) c.getFilters().get(0)).toString()); @@ -4972,7 +4975,7 @@ public class TestCQL { assertTrue(sfq.filter instanceof StoredAt); final StoredAt storedAt = (StoredAt) sfq.filter; - assertEquals("SAT(data/bla.acq)", storedAt.toString()); + assertEquals("SAT(/data/bla.acq)", storedAt.toString()); } /** String query42 = "FIND FILE WHICH IS STORED AT /*"; */ @@ -5010,7 +5013,7 @@ public class TestCQL { assertTrue(sfq.filter instanceof StoredAt); final StoredAt storedAt = (StoredAt) sfq.filter; assertTrue(storedAt.requiresPatternMatching(storedAt.location)); - assertEquals("SAT(%)", storedAt.toString()); + assertEquals("SAT(%%)", storedAt.toString()); } /** String query43 = "FIND FILE WHICH IS STORED AT /* /" (without the space); */ @@ -5048,7 +5051,7 @@ public class TestCQL { assertTrue(sfq.filter instanceof StoredAt); final StoredAt storedAt = (StoredAt) sfq.filter; assertTrue(storedAt.requiresPatternMatching(storedAt.location)); - assertEquals("SAT(%/)", storedAt.toString()); + assertEquals("SAT(%%/)", storedAt.toString()); } /** String query44 = "FIND FILE WHICH IS STORED AT /** /"; */ @@ -5159,7 +5162,7 @@ public class TestCQL { assertTrue(sfq.filter instanceof StoredAt); final StoredAt storedAt = (StoredAt) sfq.filter; - assertEquals("SAT(%/%%)", storedAt.toString()); + assertEquals("SAT(%%/%%)", storedAt.toString()); } /** String query47 = "FIND FILE WHICH IS STORED AT /* /*.acq"; */ @@ -5196,7 +5199,7 @@ public class TestCQL { assertTrue(sfq.filter instanceof StoredAt); final StoredAt storedAt = (StoredAt) sfq.filter; - assertEquals("SAT(%/%%.acq)", storedAt.toString()); + assertEquals("SAT(%%/%%.acq)", storedAt.toString()); } /** String query48 = "FIND FILE WHICH IS STORED AT /** /*.acq"; */ @@ -5270,7 +5273,7 @@ public class TestCQL { assertTrue(sfq.filter instanceof StoredAt); final StoredAt storedAt = (StoredAt) sfq.filter; - assertEquals("SAT(%.acq)", storedAt.toString()); + assertEquals("SAT(%%.acq)", storedAt.toString()); } /** String query50 = "FIND FILE WHICH IS STORED AT *.acq"; */ @@ -6016,7 +6019,7 @@ public class TestCQL { assertTrue(sfq.filter instanceof StoredAt); final StoredAt storedAt = (StoredAt) sfq.filter; - assertEquals("SAT(dir/with/date/2016-05-15)", storedAt.toString()); + assertEquals("SAT(/dir/with/date/2016-05-15)", storedAt.toString()); } /** String query55b = "FIND ename WHICH IS STORED AT /dir/with/date/2016-05-15/**"; */ @@ -6178,4 +6181,76 @@ public class TestCQL { assertTrue(f instanceof Backreference); assertEquals("%name%", ((Backreference) f).getEntity()); } + + @Test + public void testFilePathInQuotes() { + CQLLexer lexer = new CQLLexer(CharStreams.fromString(query_filepath_quotes)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + // 4 children: FIND, role, WHICHCLAUSE, EOF + assertEquals(4, sfq.getChildCount()); + assertEquals("FIND", sfq.getChild(0).getText()); + assertEquals("FILE", sfq.getChild(1).getText()); + assertEquals( + "WHICHIS STORED AT'/SimulationData/2016_single/2018-01-10/**'", sfq.getChild(2).getText()); + assertEquals("FILE", sfq.r.toString()); + assertNull(sfq.e); + assertEquals("StoredAt", sfq.filter.getClass().getSimpleName()); + + final ParseTree whichclause = sfq.getChild(2); + // 2 children: WHICH, POV + assertEquals(2, whichclause.getChildCount()); + assertEquals("WHICH", whichclause.getChild(0).getText()); + assertEquals( + "IS STORED AT'/SimulationData/2016_single/2018-01-10/**'", + whichclause.getChild(1).getText()); + + final ParseTree satFilter = whichclause.getChild(1).getChild(0); + assertEquals(2, satFilter.getChildCount()); + assertEquals("IS STORED AT", satFilter.getChild(0).getText()); + assertEquals("'/SimulationData/2016_single/2018-01-10/**'", satFilter.getChild(1).getText()); + + assertTrue(sfq.filter instanceof StoredAt); + final StoredAt storedAt = (StoredAt) sfq.filter; + assertTrue(storedAt.pattern_matching); + assertEquals("SAT(SimulationData/2016\\_single/2018-01-10/%)", storedAt.toString()); + } + + public void testFilePathInQuotes2() { + CQLLexer lexer = new CQLLexer(CharStreams.fromString(query_filepath_quotes_2)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + // 4 children: FIND, role, WHICHCLAUSE, EOF + assertEquals(4, sfq.getChildCount()); + assertEquals("FIND", sfq.getChild(0).getText()); + assertEquals("FILE", sfq.getChild(1).getText()); + assertEquals( + "WHICHIS STORED AT/SimulationData/2016_single/2018-01-10/**", sfq.getChild(2).getText()); + assertEquals("FILE", sfq.r.toString()); + assertNull(sfq.e); + assertEquals("StoredAt", sfq.filter.getClass().getSimpleName()); + + final ParseTree whichclause = sfq.getChild(2); + // 2 children: WHICH, POV + assertEquals(2, whichclause.getChildCount()); + assertEquals("WHICH", whichclause.getChild(0).getText()); + assertEquals( + "IS STORED AT/SimulationData/2016_single/2018-01-10/**", whichclause.getChild(1).getText()); + + final ParseTree satFilter = whichclause.getChild(1).getChild(0); + assertEquals(2, satFilter.getChildCount()); + assertEquals("IS STORED AT", satFilter.getChild(0).getText()); + assertEquals("'/SimulationData/2016_single/2018-01-10/**'", satFilter.getChild(1).getText()); + + assertTrue(sfq.filter instanceof StoredAt); + final StoredAt storedAt = (StoredAt) sfq.filter; + assertTrue(storedAt.pattern_matching); + assertEquals("SAT(SimulationData/2016\\_single/2018-01-10/%%)", storedAt.toString()); + } }