fix(jdbc): support encrypted sqlite with managed runtime

This commit is contained in:
t8y2
2026-06-01 17:52:11 +08:00
parent 84a7ff6b92
commit 6da367a47b
6 changed files with 238 additions and 13 deletions
+3 -1
View File
@@ -2,7 +2,9 @@
set -eu
DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
if [ -n "${JAVA_HOME:-}" ] && [ -x "$JAVA_HOME/bin/java" ]; then
if [ -n "${DBX_JAVA_BIN:-}" ] && [ -x "$DBX_JAVA_BIN" ]; then
JAVA_BIN="$DBX_JAVA_BIN"
elif [ -n "${JAVA_HOME:-}" ] && [ -x "$JAVA_HOME/bin/java" ]; then
JAVA_BIN="$JAVA_HOME/bin/java"
elif [ -x "/opt/homebrew/opt/openjdk/bin/java" ]; then
JAVA_BIN="/opt/homebrew/opt/openjdk/bin/java"
+7
View File
@@ -1,6 +1,13 @@
@echo off
setlocal
if defined DBX_JAVA_BIN (
if exist "%DBX_JAVA_BIN%" (
set "JAVA_BIN=%DBX_JAVA_BIN%"
goto :run
)
)
if defined JAVA_HOME (
if exist "%JAVA_HOME%\bin\java.exe" (
set "JAVA_BIN=%JAVA_HOME%\bin\java.exe"
@@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.math.BigDecimal;
import java.net.URL;
import java.net.URLClassLoader;
@@ -180,7 +181,7 @@ public final class DbxJdbcPlugin {
}
private static Connection openConnection(JsonNode connection) throws SQLException {
String url = optionalText(connection, "connection_string");
String url = jdbcUrlWithPasswordKey(optionalText(connection, "connection_string"), optionalText(connection, "password"));
if (url == null) {
throw new IllegalArgumentException("JDBC URL is required.");
}
@@ -547,6 +548,46 @@ public final class DbxJdbcPlugin {
return url != null && url.regionMatches(true, 0, "jdbc:oracle:", 0, 12);
}
static String jdbcUrlWithPasswordKey(String url, String password) {
if (url == null || password == null || password.isBlank() || !isSqliteUrl(url)) {
return url;
}
if (!urlHasQueryParam(url, "cipher") || urlHasQueryParam(url, "key")) {
return url;
}
return appendJdbcUrlParam(url, "key", password);
}
private static boolean isSqliteUrl(String url) {
return url.regionMatches(true, 0, "jdbc:sqlite:", 0, 12);
}
private static boolean urlHasQueryParam(String url, String key) {
int queryStart = url.indexOf('?');
if (queryStart < 0) {
return false;
}
int fragmentStart = url.indexOf('#', queryStart + 1);
String query = fragmentStart < 0 ? url.substring(queryStart + 1) : url.substring(queryStart + 1, fragmentStart);
for (String part : query.split("[&;]")) {
int equals = part.indexOf('=');
String name = equals < 0 ? part : part.substring(0, equals);
if (name.equalsIgnoreCase(key)) {
return true;
}
}
return false;
}
private static String appendJdbcUrlParam(String url, String key, String value) {
int fragmentStart = url.indexOf('#');
String base = fragmentStart < 0 ? url : url.substring(0, fragmentStart);
String fragment = fragmentStart < 0 ? "" : url.substring(fragmentStart);
String separator = base.contains("?") ? (base.endsWith("?") || base.endsWith("&") ? "" : "&") : "?";
String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8);
return base + separator + key + "=" + encodedValue + fragment;
}
private static String oracleEffectiveSchema(Connection conn, String schema) throws SQLException {
if (schema != null && !schema.isBlank()) {
return schema.toUpperCase();
@@ -92,6 +92,36 @@ final class DbxJdbcPluginTest {
assertEquals(false, DbxJdbcPlugin.driverQuirks(h2).useOracleMetadata());
}
@Test
void sqliteCipherUrlUsesPasswordAsKeyWhenKeyIsMissing() {
String url = DbxJdbcPlugin.jdbcUrlWithPasswordKey(
"jdbc:sqlite:/tmp/library.db?cipher=chacha20",
"my password"
);
assertEquals("jdbc:sqlite:/tmp/library.db?cipher=chacha20&key=my+password", url);
}
@Test
void sqliteCipherUrlKeepsExplicitKey() {
String url = DbxJdbcPlugin.jdbcUrlWithPasswordKey(
"jdbc:sqlite:/tmp/library.db?cipher=chacha20&key=from-url",
"from-password"
);
assertEquals("jdbc:sqlite:/tmp/library.db?cipher=chacha20&key=from-url", url);
}
@Test
void nonSqliteUrlDoesNotUsePasswordAsKey() {
String url = DbxJdbcPlugin.jdbcUrlWithPasswordKey(
"jdbc:h2:mem:dbx_cipher?cipher=sqlcipher",
"secret"
);
assertEquals("jdbc:h2:mem:dbx_cipher?cipher=sqlcipher", url);
}
@Test
void listTablesFallsBackWhenCatalogFiltersEverything() throws Exception {
request("executeQuery", """