From a093dec7322e76c7c912389f52d61b51c19deefc Mon Sep 17 00:00:00 2001
From: HandSonic <8078023+handsonic@users.noreply.github.com>
Date: Sun, 26 Jul 2026 21:16:06 +0800
Subject: [PATCH 1/2] fix(mongodb): escape SQL identifiers and literals in
metadata/DDL paths (#1914)
---
.../chat2db-community-mongodb/pom.xml | 5 +
.../plugin/mongodb/MongodbDBManager.java | 9 +-
.../plugin/mongodb/MongodbMetaData.java | 6 +-
.../plugin/mongodb/MongodbScriptExecutor.java | 2 +-
.../plugin/mongodb/MongodbSqlBuilder.java | 13 +-
.../plugin/mongodb/MongodbSqlEscapes.java | 71 ++++++++++
.../plugin/mongodb/MongodbSqlEscapesTest.java | 128 ++++++++++++++++++
7 files changed, 220 insertions(+), 14 deletions(-)
create mode 100644 chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapes.java
create mode 100644 chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapesTest.java
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/pom.xml b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/pom.xml
index 70e02a35a..209f9b89d 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/pom.xml
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/pom.xml
@@ -15,6 +15,11 @@
ai.chat2db
chat2db-community-spi
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbDBManager.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbDBManager.java
index 0990cd651..2c4d3dd64 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbDBManager.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbDBManager.java
@@ -29,7 +29,7 @@ public void connectDatabase(Connection connection, String database) {
return;
}
try {
- DefaultSQLExecutor.getInstance().execute(connection, String.format(SCRIPT_USE_SCHEMA, schemaName));
+ DefaultSQLExecutor.getInstance().execute(connection, String.format(SCRIPT_USE_SCHEMA, MongodbSqlEscapes.requireMongoName(schemaName, "database name")));
} catch (SQLException e) {
throw new RuntimeException(e);
}
@@ -37,17 +37,18 @@ public void connectDatabase(Connection connection, String database) {
@Override
public String dropTable(Connection connection, String databaseName, String schemaName, String tableName) {
- return String.format(SCRIPT_DROP_COLLECTION, tableName);
+ return String.format(SCRIPT_DROP_COLLECTION, MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
}
@Override
public String truncateTable(Connection connection, String databaseName, String schemaName, String tableName) throws SQLException {
- return String.format(SCRIPT_TRUNCATE_COLLECTION, tableName);
+ return String.format(SCRIPT_TRUNCATE_COLLECTION, MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
}
@Override
public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName,boolean copyData) throws SQLException {
- String sql = String.format(SCRIPT_COPY_COLLECTION, newTableName, tableName);
+ String sql = String.format(SCRIPT_COPY_COLLECTION, MongodbSqlEscapes.requireMongoName(newTableName, "collection name"),
+ MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
DefaultSQLExecutor.getInstance().execute(connection, sql, resultSet -> null);
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java
index f1c37a565..8431a90f9 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java
@@ -82,7 +82,7 @@ public PageResult
tables(Connection connection, String databaseName, Stri
@Override
public List columns(Connection connection, String databaseName, String schemaName, String tableName) {
- String sql = String.format(SELECT_TABLE_COLUMNS, tableName);
+ String sql = String.format(SELECT_TABLE_COLUMNS, MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
List tableColumns = new ArrayList();
return (List) DefaultSQLExecutor.getInstance().execute(connection, sql, (resultSet) -> {
while (resultSet.next()) {
@@ -106,7 +106,7 @@ public List columns(Connection connection, String databaseName, String schemaNam
}
public List indexes(Connection connection, String databaseName, String schemaName, String tableName) {
- String sql = String.format(SELECT_TABLE_INDEX, tableName);
+ String sql = String.format(SELECT_TABLE_INDEX, MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
List tableIndexes = new ArrayList<>();
DefaultSQLExecutor.getInstance().execute(connection, sql, resultSet -> {
while (resultSet.next()) {
@@ -141,7 +141,7 @@ private void executeUse(String schemaName) {
return;
}
Connection connection = Chat2DBContext.getConnection();
- String sql = String.format(SCRIPT_USE_SCHEMA, schemaName);
+ String sql = String.format(SCRIPT_USE_SCHEMA, MongodbSqlEscapes.requireMongoName(schemaName, "database name"));
try {
DefaultSQLExecutor.getInstance().execute(connection, sql);
} catch (SQLException e) {
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbScriptExecutor.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbScriptExecutor.java
index 6582e8502..b52b7796a 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbScriptExecutor.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbScriptExecutor.java
@@ -62,7 +62,7 @@ public List executeSelectTable(SqlExecuteRequest command) {
if (StringUtils.isEmpty(command.getTableName())) {
return Collections.emptyList();
}
- String sql = String.format(EXECUTE_SQL, command.getTableName());
+ String sql = String.format(EXECUTE_SQL, MongodbSqlEscapes.requireMongoName(command.getTableName(), "collection name"));
command.setScript(sql);
return execute(command);
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlBuilder.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlBuilder.java
index 4b0f5cf3b..aab370897 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlBuilder.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlBuilder.java
@@ -79,7 +79,8 @@ private String buildDeleteSql(String tableName, List deleteSqlCommands,
if (StringUtils.isEmpty(idValue)) {
return StringUtils.EMPTY;
}
- String sql = String.format(SQL_DB_DOT_FORMAT_DOT_DELETEONE_OPEN_PAREN_OPEN_BRACE, tableName, idValue);
+ String sql = String.format(SQL_DB_DOT_FORMAT_DOT_DELETEONE_OPEN_PAREN_OPEN_BRACE,
+ MongodbSqlEscapes.requireMongoName(tableName, "collection name"), MongodbSqlEscapes.escapeJsonString(idValue));
log.info(LOG_DELETE_SQL, sql);
return sql;
@@ -95,13 +96,13 @@ private String buildInsertSql(String tableName, List headerList, ResultO
for (int i = 2; i < newDataList.size(); i++) {
Header header = headerList.get(i);
String newValue = newDataList.get(i);
- sql.append(header.getName()).append(SQLConstants.COLON).append(SQLConstants.DOUBLE_QUOTE).append(newValue).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.COMMA);
+ sql.append(MongodbSqlEscapes.requireMongoName(header.getName(), "field name")).append(SQLConstants.COLON).append(SQLConstants.DOUBLE_QUOTE).append(MongodbSqlEscapes.escapeJsonString(newValue)).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.COMMA);
}
if (sql.isEmpty()) {
return StringUtils.EMPTY;
}
StringBuffer insertSql = new StringBuffer();
- insertSql.append(String.format(SQL_DB_DOT_FORMAT_DOT_INSERTONE, tableName)).append(SQLConstants.OPEN_PARENTHESIS)
+ insertSql.append(String.format(SQL_DB_DOT_FORMAT_DOT_INSERTONE, MongodbSqlEscapes.requireMongoName(tableName, "collection name"))).append(SQLConstants.OPEN_PARENTHESIS)
.append(SQLConstants.OPEN_CURLY_BRACE)
.append(sql.deleteCharAt(sql.length() - 1))
.append(SQLConstants.CLOSE_CURLY_BRACE)
@@ -127,20 +128,20 @@ private String buildUpdate(String tableName, List headerList, ResultOper
if (_idValue.isEmpty()) {
_idValue.append(oldDataList.get(1));
}
- setSql.append(header.getName()).append(SQLConstants.COLON).append(SQLConstants.DOUBLE_QUOTE).append(newValue).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.COMMA);
+ setSql.append(MongodbSqlEscapes.requireMongoName(header.getName(), "field name")).append(SQLConstants.COLON).append(SQLConstants.DOUBLE_QUOTE).append(MongodbSqlEscapes.escapeJsonString(newValue)).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.COMMA);
}
if (_idValue.isEmpty() || setSql.isEmpty()) {
return StringUtils.EMPTY;
}
StringBuffer sql = new StringBuffer();
- sql.append(String.format(SQL_DB_DOT_FORMAT_DOT_UPDATEONE, tableName)).append(SQLConstants.OPEN_PARENTHESIS)
+ sql.append(String.format(SQL_DB_DOT_FORMAT_DOT_UPDATEONE, MongodbSqlEscapes.requireMongoName(tableName, "collection name"))).append(SQLConstants.OPEN_PARENTHESIS)
.append(SQLConstants.OPEN_CURLY_BRACE)
.append(MONGODB_ID_FIELD)
.append(SQLConstants.COLON)
.append(MONGODB_OBJECT_ID_TYPE)
.append(SQLConstants.OPEN_PARENTHESIS)
.append(SQLConstants.DOUBLE_QUOTE)
- .append(_idValue.toString())
+ .append(MongodbSqlEscapes.escapeJsonString(_idValue.toString()))
.append(SQLConstants.DOUBLE_QUOTE)
.append(SQLConstants.CLOSE_PARENTHESIS)
.append(SQLConstants.CLOSE_CURLY_BRACE)
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapes.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapes.java
new file mode 100644
index 000000000..ee1df6c29
--- /dev/null
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapes.java
@@ -0,0 +1,71 @@
+package ai.chat2db.plugin.mongodb;
+
+import java.util.regex.Pattern;
+
+/**
+ * Neutralization helpers for values interpolated into Mongo shell command text (#1914).
+ * Shell commands are not plain SQL: database/collection names are validated against a strict
+ * allowlist, and string values inside JSON documents are JSON-escaped.
+ */
+public final class MongodbSqlEscapes {
+
+ private static final Pattern MONGO_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_$-]+$");
+
+ private MongodbSqlEscapes() {
+ }
+
+ /**
+ * Validate a database/collection/field name interpolated into shell command text such as
+ * {@code use } or {@code db..find()}. Rejects anything outside the allowlist.
+ */
+ public static String requireMongoName(String name, String what) {
+ if (name == null || !MONGO_NAME_PATTERN.matcher(name).matches()) {
+ throw new IllegalArgumentException("Invalid MongoDB " + what + ": " + name);
+ }
+ return name;
+ }
+
+ /**
+ * Escape a value interpolated into a double-quoted JSON string inside a shell command
+ * (surrounding quotes NOT added).
+ */
+ public static String escapeJsonString(String value) {
+ if (value == null) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder(value.length());
+ for (int i = 0; i < value.length(); i++) {
+ char c = value.charAt(i);
+ switch (c) {
+ case '\\':
+ sb.append("\\\\");
+ break;
+ case '"':
+ sb.append("\\\"");
+ break;
+ case '\n':
+ sb.append("\\n");
+ break;
+ case '\r':
+ sb.append("\\r");
+ break;
+ case '\t':
+ sb.append("\\t");
+ break;
+ case '\b':
+ sb.append("\\b");
+ break;
+ case '\f':
+ sb.append("\\f");
+ break;
+ default:
+ if (c < 0x20) {
+ sb.append(String.format("\\u%04x", (int) c));
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapesTest.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapesTest.java
new file mode 100644
index 000000000..65c34dd00
--- /dev/null
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapesTest.java
@@ -0,0 +1,128 @@
+package ai.chat2db.plugin.mongodb;
+
+import java.util.List;
+
+import ai.chat2db.community.domain.api.model.result.Header;
+import ai.chat2db.community.domain.api.model.result.QueryResponse;
+import ai.chat2db.community.domain.api.model.result.ResultOperation;
+import ai.chat2db.spi.constant.SQLConstants;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class MongodbSqlEscapesTest {
+
+ @Test
+ void requireMongoNameAcceptsLegitimateNames() {
+ assertEquals("mydb", MongodbSqlEscapes.requireMongoName("mydb", "database name"));
+ assertEquals("my_db-1$A", MongodbSqlEscapes.requireMongoName("my_db-1$A", "collection name"));
+ }
+
+ @Test
+ void requireMongoNameRejectsInjection() {
+ assertThrows(IllegalArgumentException.class,
+ () -> MongodbSqlEscapes.requireMongoName("a.b", "collection name"));
+ assertThrows(IllegalArgumentException.class,
+ () -> MongodbSqlEscapes.requireMongoName("a b", "collection name"));
+ assertThrows(IllegalArgumentException.class,
+ () -> MongodbSqlEscapes.requireMongoName("x; db.dropDatabase(); //", "database name"));
+ assertThrows(IllegalArgumentException.class,
+ () -> MongodbSqlEscapes.requireMongoName("x\")}", "collection name"));
+ assertThrows(IllegalArgumentException.class,
+ () -> MongodbSqlEscapes.requireMongoName("", "collection name"));
+ assertThrows(IllegalArgumentException.class,
+ () -> MongodbSqlEscapes.requireMongoName(null, "collection name"));
+ }
+
+ @Test
+ void escapeJsonStringEscapesQuotesBackslashAndControls() {
+ assertEquals("plain", MongodbSqlEscapes.escapeJsonString("plain"));
+ assertEquals("a\\\"b", MongodbSqlEscapes.escapeJsonString("a\"b"));
+ assertEquals("a\\\\b", MongodbSqlEscapes.escapeJsonString("a\\b"));
+ assertEquals("a\\nb", MongodbSqlEscapes.escapeJsonString("a\nb"));
+ assertEquals("\\u0001", MongodbSqlEscapes.escapeJsonString("\u0001"));
+ assertNull(MongodbSqlEscapes.escapeJsonString(null));
+ }
+
+ @Test
+ void dropTableRejectsMaliciousName() {
+ MongodbDBManager manager = new MongodbDBManager();
+ assertEquals(" db. users.drop();", manager.dropTable(null, null, null, "users"));
+ assertThrows(IllegalArgumentException.class,
+ () -> manager.dropTable(null, null, null, "users; db.dropDatabase(); //"));
+ }
+
+ @Test
+ void truncateTableRejectsMaliciousName() throws Exception {
+ MongodbDBManager manager = new MongodbDBManager();
+ assertEquals("db.users.deleteMany({})", manager.truncateTable(null, null, null, "users"));
+ assertThrows(IllegalArgumentException.class,
+ () -> manager.truncateTable(null, null, null, "a.b"));
+ }
+
+ @Test
+ void deleteCommandEscapesObjectIdAndValidatesTable() {
+ QueryResponse response = new QueryResponse();
+ response.setTableName("users");
+ response.setHeaderList(List.of(
+ Header.builder().name("rn").build(),
+ Header.builder().name("_id").build()));
+ ResultOperation operation = new ResultOperation();
+ operation.setType(SQLConstants.DELETE_KEYWORD);
+ operation.setOldDataList(List.of("1", "abc123"));
+ response.setOperations(List.of(operation));
+ assertEquals("db.users.deleteOne({_id: ObjectId(\"abc123\")})",
+ MongodbSqlBuilder.getInstance().buildByQueryResult(response));
+
+ operation.setOldDataList(List.of("1", "a\"), $where: 1, x: (\""));
+ String sql = MongodbSqlBuilder.getInstance().buildByQueryResult(response);
+ assertEquals("db.users.deleteOne({_id: ObjectId(\"a\\\"), $where: 1, x: (\\\"\")})", sql);
+
+ response.setTableName("users; //");
+ assertThrows(IllegalArgumentException.class,
+ () -> MongodbSqlBuilder.getInstance().buildByQueryResult(response));
+ }
+
+ @Test
+ void insertCommandEscapesValuesAndValidatesNames() {
+ QueryResponse response = new QueryResponse();
+ response.setTableName("users");
+ response.setHeaderList(List.of(
+ Header.builder().name("rn").build(),
+ Header.builder().name("_id").build(),
+ Header.builder().name("name").build()));
+ ResultOperation operation = new ResultOperation();
+ operation.setType(SQLConstants.CREATE_KEYWORD);
+ operation.setDataList(List.of("1", "id1", "a\"}), db.dropDatabase(), //"));
+ response.setOperations(List.of(operation));
+ String sql = MongodbSqlBuilder.getInstance().buildByQueryResult(response);
+ assertEquals("db.users.insertOne({name:\"a\\\"}), db.dropDatabase(), //\"})", sql);
+
+ // malicious field name is rejected
+ response.setHeaderList(List.of(
+ Header.builder().name("rn").build(),
+ Header.builder().name("_id").build(),
+ Header.builder().name("a}), x:(").build()));
+ assertThrows(IllegalArgumentException.class,
+ () -> MongodbSqlBuilder.getInstance().buildByQueryResult(response));
+ }
+
+ @Test
+ void updateCommandEscapesValuesAndId() {
+ QueryResponse response = new QueryResponse();
+ response.setTableName("users");
+ response.setHeaderList(List.of(
+ Header.builder().name("rn").build(),
+ Header.builder().name("_id").build(),
+ Header.builder().name("name").build()));
+ ResultOperation operation = new ResultOperation();
+ operation.setType(SQLConstants.UPDATE_KEYWORD);
+ operation.setOldDataList(List.of("1", "id\"1", "old"));
+ operation.setDataList(List.of("1", "id\"1", "new\"value"));
+ response.setOperations(List.of(operation));
+ String sql = MongodbSqlBuilder.getInstance().buildByQueryResult(response);
+ assertEquals("db.users.updateOne({_id:ObjectId(\"id\\\"1\")},{$set:{name:\"new\\\"value\"}})", sql);
+ }
+}
From c694cc435ffd7286549b93d225fe0c65c4dae8fc Mon Sep 17 00:00:00 2001
From: HandSonic <8078023+handsonic@users.noreply.github.com>
Date: Mon, 27 Jul 2026 02:05:06 +0800
Subject: [PATCH 2/2] refactor(mongodb): rename MongodbSqlEscapes to
MongodbSqlGuards per maintainer review (#1914)
- non-SQL module: no SQL identifier/string escaping, default
ISQLIdentifierProcessor left untouched
- requireMongoName allowlist + escapeJsonString are validation/JSON
guards, not SQL escaping; class renamed accordingly
- callers rewired (DBManager/MetaData/ScriptExecutor/SqlBuilder)
- tests migrated to MongodbSqlGuardsTest (8 green)
---
.../plugin/mongodb/MongodbDBManager.java | 10 +++----
.../plugin/mongodb/MongodbMetaData.java | 6 ++--
.../plugin/mongodb/MongodbScriptExecutor.java | 2 +-
.../plugin/mongodb/MongodbSqlBuilder.java | 12 ++++----
...bSqlEscapes.java => MongodbSqlGuards.java} | 10 +++----
...pesTest.java => MongodbSqlGuardsTest.java} | 30 +++++++++----------
6 files changed, 35 insertions(+), 35 deletions(-)
rename chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/{MongodbSqlEscapes.java => MongodbSqlGuards.java} (85%)
rename chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/{MongodbSqlEscapesTest.java => MongodbSqlGuardsTest.java} (80%)
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbDBManager.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbDBManager.java
index 2c4d3dd64..533fdb075 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbDBManager.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbDBManager.java
@@ -29,7 +29,7 @@ public void connectDatabase(Connection connection, String database) {
return;
}
try {
- DefaultSQLExecutor.getInstance().execute(connection, String.format(SCRIPT_USE_SCHEMA, MongodbSqlEscapes.requireMongoName(schemaName, "database name")));
+ DefaultSQLExecutor.getInstance().execute(connection, String.format(SCRIPT_USE_SCHEMA, MongodbSqlGuards.requireMongoName(schemaName, "database name")));
} catch (SQLException e) {
throw new RuntimeException(e);
}
@@ -37,18 +37,18 @@ public void connectDatabase(Connection connection, String database) {
@Override
public String dropTable(Connection connection, String databaseName, String schemaName, String tableName) {
- return String.format(SCRIPT_DROP_COLLECTION, MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
+ return String.format(SCRIPT_DROP_COLLECTION, MongodbSqlGuards.requireMongoName(tableName, "collection name"));
}
@Override
public String truncateTable(Connection connection, String databaseName, String schemaName, String tableName) throws SQLException {
- return String.format(SCRIPT_TRUNCATE_COLLECTION, MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
+ return String.format(SCRIPT_TRUNCATE_COLLECTION, MongodbSqlGuards.requireMongoName(tableName, "collection name"));
}
@Override
public void copyTable(Connection connection, String databaseName, String schemaName, String tableName, String newTableName,boolean copyData) throws SQLException {
- String sql = String.format(SCRIPT_COPY_COLLECTION, MongodbSqlEscapes.requireMongoName(newTableName, "collection name"),
- MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
+ String sql = String.format(SCRIPT_COPY_COLLECTION, MongodbSqlGuards.requireMongoName(newTableName, "collection name"),
+ MongodbSqlGuards.requireMongoName(tableName, "collection name"));
DefaultSQLExecutor.getInstance().execute(connection, sql, resultSet -> null);
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java
index 8431a90f9..ab7f7190e 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java
@@ -82,7 +82,7 @@ public PageResult tables(Connection connection, String databaseName, Stri
@Override
public List columns(Connection connection, String databaseName, String schemaName, String tableName) {
- String sql = String.format(SELECT_TABLE_COLUMNS, MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
+ String sql = String.format(SELECT_TABLE_COLUMNS, MongodbSqlGuards.requireMongoName(tableName, "collection name"));
List tableColumns = new ArrayList();
return (List) DefaultSQLExecutor.getInstance().execute(connection, sql, (resultSet) -> {
while (resultSet.next()) {
@@ -106,7 +106,7 @@ public List columns(Connection connection, String databaseName, String schemaNam
}
public List indexes(Connection connection, String databaseName, String schemaName, String tableName) {
- String sql = String.format(SELECT_TABLE_INDEX, MongodbSqlEscapes.requireMongoName(tableName, "collection name"));
+ String sql = String.format(SELECT_TABLE_INDEX, MongodbSqlGuards.requireMongoName(tableName, "collection name"));
List tableIndexes = new ArrayList<>();
DefaultSQLExecutor.getInstance().execute(connection, sql, resultSet -> {
while (resultSet.next()) {
@@ -141,7 +141,7 @@ private void executeUse(String schemaName) {
return;
}
Connection connection = Chat2DBContext.getConnection();
- String sql = String.format(SCRIPT_USE_SCHEMA, MongodbSqlEscapes.requireMongoName(schemaName, "database name"));
+ String sql = String.format(SCRIPT_USE_SCHEMA, MongodbSqlGuards.requireMongoName(schemaName, "database name"));
try {
DefaultSQLExecutor.getInstance().execute(connection, sql);
} catch (SQLException e) {
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbScriptExecutor.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbScriptExecutor.java
index b52b7796a..5cc2a5258 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbScriptExecutor.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbScriptExecutor.java
@@ -62,7 +62,7 @@ public List executeSelectTable(SqlExecuteRequest command) {
if (StringUtils.isEmpty(command.getTableName())) {
return Collections.emptyList();
}
- String sql = String.format(EXECUTE_SQL, MongodbSqlEscapes.requireMongoName(command.getTableName(), "collection name"));
+ String sql = String.format(EXECUTE_SQL, MongodbSqlGuards.requireMongoName(command.getTableName(), "collection name"));
command.setScript(sql);
return execute(command);
}
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlBuilder.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlBuilder.java
index aab370897..dc5c2f075 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlBuilder.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlBuilder.java
@@ -80,7 +80,7 @@ private String buildDeleteSql(String tableName, List deleteSqlCommands,
return StringUtils.EMPTY;
}
String sql = String.format(SQL_DB_DOT_FORMAT_DOT_DELETEONE_OPEN_PAREN_OPEN_BRACE,
- MongodbSqlEscapes.requireMongoName(tableName, "collection name"), MongodbSqlEscapes.escapeJsonString(idValue));
+ MongodbSqlGuards.requireMongoName(tableName, "collection name"), MongodbSqlGuards.escapeJsonString(idValue));
log.info(LOG_DELETE_SQL, sql);
return sql;
@@ -96,13 +96,13 @@ private String buildInsertSql(String tableName, List headerList, ResultO
for (int i = 2; i < newDataList.size(); i++) {
Header header = headerList.get(i);
String newValue = newDataList.get(i);
- sql.append(MongodbSqlEscapes.requireMongoName(header.getName(), "field name")).append(SQLConstants.COLON).append(SQLConstants.DOUBLE_QUOTE).append(MongodbSqlEscapes.escapeJsonString(newValue)).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.COMMA);
+ sql.append(MongodbSqlGuards.requireMongoName(header.getName(), "field name")).append(SQLConstants.COLON).append(SQLConstants.DOUBLE_QUOTE).append(MongodbSqlGuards.escapeJsonString(newValue)).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.COMMA);
}
if (sql.isEmpty()) {
return StringUtils.EMPTY;
}
StringBuffer insertSql = new StringBuffer();
- insertSql.append(String.format(SQL_DB_DOT_FORMAT_DOT_INSERTONE, MongodbSqlEscapes.requireMongoName(tableName, "collection name"))).append(SQLConstants.OPEN_PARENTHESIS)
+ insertSql.append(String.format(SQL_DB_DOT_FORMAT_DOT_INSERTONE, MongodbSqlGuards.requireMongoName(tableName, "collection name"))).append(SQLConstants.OPEN_PARENTHESIS)
.append(SQLConstants.OPEN_CURLY_BRACE)
.append(sql.deleteCharAt(sql.length() - 1))
.append(SQLConstants.CLOSE_CURLY_BRACE)
@@ -128,20 +128,20 @@ private String buildUpdate(String tableName, List headerList, ResultOper
if (_idValue.isEmpty()) {
_idValue.append(oldDataList.get(1));
}
- setSql.append(MongodbSqlEscapes.requireMongoName(header.getName(), "field name")).append(SQLConstants.COLON).append(SQLConstants.DOUBLE_QUOTE).append(MongodbSqlEscapes.escapeJsonString(newValue)).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.COMMA);
+ setSql.append(MongodbSqlGuards.requireMongoName(header.getName(), "field name")).append(SQLConstants.COLON).append(SQLConstants.DOUBLE_QUOTE).append(MongodbSqlGuards.escapeJsonString(newValue)).append(SQLConstants.DOUBLE_QUOTE).append(SQLConstants.COMMA);
}
if (_idValue.isEmpty() || setSql.isEmpty()) {
return StringUtils.EMPTY;
}
StringBuffer sql = new StringBuffer();
- sql.append(String.format(SQL_DB_DOT_FORMAT_DOT_UPDATEONE, MongodbSqlEscapes.requireMongoName(tableName, "collection name"))).append(SQLConstants.OPEN_PARENTHESIS)
+ sql.append(String.format(SQL_DB_DOT_FORMAT_DOT_UPDATEONE, MongodbSqlGuards.requireMongoName(tableName, "collection name"))).append(SQLConstants.OPEN_PARENTHESIS)
.append(SQLConstants.OPEN_CURLY_BRACE)
.append(MONGODB_ID_FIELD)
.append(SQLConstants.COLON)
.append(MONGODB_OBJECT_ID_TYPE)
.append(SQLConstants.OPEN_PARENTHESIS)
.append(SQLConstants.DOUBLE_QUOTE)
- .append(MongodbSqlEscapes.escapeJsonString(_idValue.toString()))
+ .append(MongodbSqlGuards.escapeJsonString(_idValue.toString()))
.append(SQLConstants.DOUBLE_QUOTE)
.append(SQLConstants.CLOSE_PARENTHESIS)
.append(SQLConstants.CLOSE_CURLY_BRACE)
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapes.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlGuards.java
similarity index 85%
rename from chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapes.java
rename to chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlGuards.java
index ee1df6c29..398519e33 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapes.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbSqlGuards.java
@@ -3,15 +3,15 @@
import java.util.regex.Pattern;
/**
- * Neutralization helpers for values interpolated into Mongo shell command text (#1914).
- * Shell commands are not plain SQL: database/collection names are validated against a strict
- * allowlist, and string values inside JSON documents are JSON-escaped.
+ * Validation and JSON-escaping guards for values interpolated into Mongo shell command
+ * text (#1914). Shell commands are not plain SQL: database/collection names are validated
+ * against a strict allowlist, and string values inside JSON documents are JSON-escaped.
*/
-public final class MongodbSqlEscapes {
+public final class MongodbSqlGuards {
private static final Pattern MONGO_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_$-]+$");
- private MongodbSqlEscapes() {
+ private MongodbSqlGuards() {
}
/**
diff --git a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapesTest.java b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlGuardsTest.java
similarity index 80%
rename from chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapesTest.java
rename to chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlGuardsTest.java
index 65c34dd00..c4e9939d9 100644
--- a/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlEscapesTest.java
+++ b/chat2db-community-server/chat2db-community-plugins/chat2db-community-mongodb/src/test/java/ai/chat2db/plugin/mongodb/MongodbSqlGuardsTest.java
@@ -12,38 +12,38 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
-class MongodbSqlEscapesTest {
+class MongodbSqlGuardsTest {
@Test
void requireMongoNameAcceptsLegitimateNames() {
- assertEquals("mydb", MongodbSqlEscapes.requireMongoName("mydb", "database name"));
- assertEquals("my_db-1$A", MongodbSqlEscapes.requireMongoName("my_db-1$A", "collection name"));
+ assertEquals("mydb", MongodbSqlGuards.requireMongoName("mydb", "database name"));
+ assertEquals("my_db-1$A", MongodbSqlGuards.requireMongoName("my_db-1$A", "collection name"));
}
@Test
void requireMongoNameRejectsInjection() {
assertThrows(IllegalArgumentException.class,
- () -> MongodbSqlEscapes.requireMongoName("a.b", "collection name"));
+ () -> MongodbSqlGuards.requireMongoName("a.b", "collection name"));
assertThrows(IllegalArgumentException.class,
- () -> MongodbSqlEscapes.requireMongoName("a b", "collection name"));
+ () -> MongodbSqlGuards.requireMongoName("a b", "collection name"));
assertThrows(IllegalArgumentException.class,
- () -> MongodbSqlEscapes.requireMongoName("x; db.dropDatabase(); //", "database name"));
+ () -> MongodbSqlGuards.requireMongoName("x; db.dropDatabase(); //", "database name"));
assertThrows(IllegalArgumentException.class,
- () -> MongodbSqlEscapes.requireMongoName("x\")}", "collection name"));
+ () -> MongodbSqlGuards.requireMongoName("x\")}", "collection name"));
assertThrows(IllegalArgumentException.class,
- () -> MongodbSqlEscapes.requireMongoName("", "collection name"));
+ () -> MongodbSqlGuards.requireMongoName("", "collection name"));
assertThrows(IllegalArgumentException.class,
- () -> MongodbSqlEscapes.requireMongoName(null, "collection name"));
+ () -> MongodbSqlGuards.requireMongoName(null, "collection name"));
}
@Test
void escapeJsonStringEscapesQuotesBackslashAndControls() {
- assertEquals("plain", MongodbSqlEscapes.escapeJsonString("plain"));
- assertEquals("a\\\"b", MongodbSqlEscapes.escapeJsonString("a\"b"));
- assertEquals("a\\\\b", MongodbSqlEscapes.escapeJsonString("a\\b"));
- assertEquals("a\\nb", MongodbSqlEscapes.escapeJsonString("a\nb"));
- assertEquals("\\u0001", MongodbSqlEscapes.escapeJsonString("\u0001"));
- assertNull(MongodbSqlEscapes.escapeJsonString(null));
+ assertEquals("plain", MongodbSqlGuards.escapeJsonString("plain"));
+ assertEquals("a\\\"b", MongodbSqlGuards.escapeJsonString("a\"b"));
+ assertEquals("a\\\\b", MongodbSqlGuards.escapeJsonString("a\\b"));
+ assertEquals("a\\nb", MongodbSqlGuards.escapeJsonString("a\nb"));
+ assertEquals("\\u0001", MongodbSqlGuards.escapeJsonString("\u0001"));
+ assertNull(MongodbSqlGuards.escapeJsonString(null));
}
@Test