From bb4fb3938bb599e9a14f95d608869a53270a38a1 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Wed, 3 Jun 2026 15:34:39 +0200 Subject: [PATCH 1/5] Fix StringIndexOutOfBoundsException in DB2/AS400 JDBC URL parsing when no port is specified --- .../jdbc/JDBCConnectionUrlParser.java | 10 +++-- .../jdbc/JDBCConnectionUrlParserDB2Test.java | 42 +++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java index ce2c5bf77f4..98831eef437 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java @@ -85,9 +85,13 @@ DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) { if (type.equals("db2") || type.equals("as400")) { if (jdbcUrl.contains("=")) { paramLoc = jdbcUrl.lastIndexOf(':'); - urlPart1 = jdbcUrl.substring(0, paramLoc); - urlPart2 = jdbcUrl.substring(paramLoc + 1); - + if (paramLoc > hostIndex + 2) { + urlPart1 = jdbcUrl.substring(0, paramLoc); + urlPart2 = jdbcUrl.substring(paramLoc + 1); + } else { + urlPart1 = jdbcUrl; + urlPart2 = null; + } } else { urlPart1 = jdbcUrl; urlPart2 = null; diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java new file mode 100644 index 00000000000..8c85a8d77b1 --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java @@ -0,0 +1,42 @@ +package datadog.trace.bootstrap.instrumentation.jdbc; + +import static datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionUrlParser.extractDBInfo; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Regression tests for StringIndexOutOfBoundsException in MODIFIED_URL_LIKE.doParse() (line 109) + * when parsing DB2/AS400 URLs that contain '=' but have no explicit port number. + * + *

Root cause: {@code lastIndexOf(':')} returns the scheme colon (e.g. position 3 in + * {@code "db2://..."}) when there is no port in the URL. This makes {@code urlPart1} shorter than + * {@code hostIndex + 3}, causing the subsequent {@code urlPart1.substring(hostIndex + 3)} call to + * throw. + * + *

Triggering condition: type is {@code db2} or {@code as400}, URL contains {@code =} (e.g. + * query-string parameters), and there is no explicit port. + */ +class JDBCConnectionUrlParserDB2Test { + + @ParameterizedTest + @CsvSource({ + // DB2: path present, query-string params, no port — last ':' is the scheme colon + "jdbc:db2://db2.host/mydb?user=db2user, db2, db2.host", + // AS400: same pattern + "jdbc:as400://ashost/asdb?user=asuser, as400, ashost", + // DB2: multiple query params + "jdbc:db2://db2.host/mydb?user=db2user&connectionTimeout=30, db2, db2.host", + }) + void db2UrlWithEqualsAndNoPortShouldParseHostCorrectly( + String url, String expectedType, String expectedHost) { + DBInfo info = extractDBInfo(url.trim(), null); + + // Before the fix, MODIFIED_URL_LIKE.doParse() throws StringIndexOutOfBoundsException. + // The exception is swallowed by the catch in JDBCConnectionUrlParser.parse(), but host + // is never set, so info.getHost() returns null. + assertEquals(expectedType, info.getType()); + assertEquals(expectedHost, info.getHost()); + } +} From ad04068df974a9caf78bb83a35fb5165abe83cc8 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Wed, 3 Jun 2026 17:29:02 +0200 Subject: [PATCH 2/5] Simplify test comments --- .../jdbc/JDBCConnectionUrlParserDB2Test.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java index 8c85a8d77b1..bc1e88df501 100644 --- a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java @@ -7,16 +7,11 @@ import org.junit.jupiter.params.provider.CsvSource; /** - * Regression tests for StringIndexOutOfBoundsException in MODIFIED_URL_LIKE.doParse() (line 109) - * when parsing DB2/AS400 URLs that contain '=' but have no explicit port number. + * Tests for DB2/AS400 JDBC URL parsing when the URL contains '=' but no explicit port. * - *

Root cause: {@code lastIndexOf(':')} returns the scheme colon (e.g. position 3 in - * {@code "db2://..."}) when there is no port in the URL. This makes {@code urlPart1} shorter than - * {@code hostIndex + 3}, causing the subsequent {@code urlPart1.substring(hostIndex + 3)} call to - * throw. - * - *

Triggering condition: type is {@code db2} or {@code as400}, URL contains {@code =} (e.g. - * query-string parameters), and there is no explicit port. + *

Without a port, {@code lastIndexOf(':')} returns the scheme colon, making {@code urlPart1} + * too short for the subsequent {@code substring()} call and causing a + * {@code StringIndexOutOfBoundsException}. */ class JDBCConnectionUrlParserDB2Test { @@ -33,9 +28,7 @@ void db2UrlWithEqualsAndNoPortShouldParseHostCorrectly( String url, String expectedType, String expectedHost) { DBInfo info = extractDBInfo(url.trim(), null); - // Before the fix, MODIFIED_URL_LIKE.doParse() throws StringIndexOutOfBoundsException. - // The exception is swallowed by the catch in JDBCConnectionUrlParser.parse(), but host - // is never set, so info.getHost() returns null. + // Without the fix, host was never extracted due to StringIndexOutOfBoundsException. assertEquals(expectedType, info.getType()); assertEquals(expectedHost, info.getHost()); } From 80a104be6378b084be748c3ce63e753dc30d7a37 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Wed, 3 Jun 2026 18:25:05 +0200 Subject: [PATCH 3/5] Parse query-string params and fix instance name for DB2/AS400 URLs without port --- .../jdbc/JDBCConnectionUrlParser.java | 15 +++++-- .../jdbc/JDBCConnectionUrlParserDB2Test.java | 39 +++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java index 98831eef437..2eaccd81400 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParser.java @@ -81,6 +81,7 @@ DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) { final String urlPart1; final String urlPart2; final int paramLoc; + char paramSeparator = ';'; if (type.equals("db2") || type.equals("as400")) { if (jdbcUrl.contains("=")) { @@ -89,8 +90,16 @@ DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) { urlPart1 = jdbcUrl.substring(0, paramLoc); urlPart2 = jdbcUrl.substring(paramLoc + 1); } else { - urlPart1 = jdbcUrl; - urlPart2 = null; + // No ':' past '://': fall back to '?' query-string params + final int queryLoc = jdbcUrl.indexOf('?'); + if (queryLoc >= 0) { + urlPart1 = jdbcUrl.substring(0, queryLoc); + urlPart2 = jdbcUrl.substring(queryLoc + 1); + paramSeparator = '&'; + } else { + urlPart1 = jdbcUrl; + urlPart2 = null; + } } } else { urlPart1 = jdbcUrl; @@ -103,7 +112,7 @@ DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) { } if (urlPart2 != null) { - final Map props = splitQuery(urlPart2, ';'); + final Map props = splitQuery(urlPart2, paramSeparator); populateStandardProperties(builder, props); if (props.containsKey("servername")) { serverName = props.get("servername"); diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java index bc1e88df501..e5d699b6c4c 100644 --- a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java @@ -9,27 +9,52 @@ /** * Tests for DB2/AS400 JDBC URL parsing when the URL contains '=' but no explicit port. * - *

Without a port, {@code lastIndexOf(':')} returns the scheme colon, making {@code urlPart1} - * too short for the subsequent {@code substring()} call and causing a - * {@code StringIndexOutOfBoundsException}. + *

Without a port, {@code lastIndexOf(':')} returns the scheme colon, making {@code urlPart1} too + * short for the subsequent {@code substring()} call and causing a {@code + * StringIndexOutOfBoundsException}. */ class JDBCConnectionUrlParserDB2Test { @ParameterizedTest @CsvSource({ // DB2: path present, query-string params, no port — last ':' is the scheme colon - "jdbc:db2://db2.host/mydb?user=db2user, db2, db2.host", + "jdbc:db2://db2.host/mydb?user=db2user, db2, db2.host, mydb, db2user", // AS400: same pattern - "jdbc:as400://ashost/asdb?user=asuser, as400, ashost", + "jdbc:as400://ashost/asdb?user=asuser, as400, ashost, asdb, asuser", // DB2: multiple query params - "jdbc:db2://db2.host/mydb?user=db2user&connectionTimeout=30, db2, db2.host", + "jdbc:db2://db2.host/mydb?user=db2user&connectionTimeout=30, db2, db2.host, mydb, db2user", }) void db2UrlWithEqualsAndNoPortShouldParseHostCorrectly( - String url, String expectedType, String expectedHost) { + String url, + String expectedType, + String expectedHost, + String expectedInstance, + String expectedUser) { DBInfo info = extractDBInfo(url.trim(), null); // Without the fix, host was never extracted due to StringIndexOutOfBoundsException. assertEquals(expectedType, info.getType()); assertEquals(expectedHost, info.getHost()); + assertEquals(expectedInstance, info.getInstance()); + assertEquals(expectedUser, info.getUser()); + } + + @ParameterizedTest + @CsvSource({ + // databasename param in query string should be parsed into db field + "jdbc:db2://db2.host/mydb?user=db2user&databasename=otherdb, db2, db2.host, db2user, otherdb", + }) + void db2UrlWithDatabasenameQueryParamShouldParseDbCorrectly( + String url, + String expectedType, + String expectedHost, + String expectedUser, + String expectedDb) { + DBInfo info = extractDBInfo(url.trim(), null); + + assertEquals(expectedType, info.getType()); + assertEquals(expectedHost, info.getHost()); + assertEquals(expectedUser, info.getUser()); + assertEquals(expectedDb, info.getDb()); } } From 7c5be4ec7fb034d16925533c6c348512064df0ae Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Thu, 4 Jun 2026 10:13:23 +0200 Subject: [PATCH 4/5] Rewrite DB2 test using @TableTest and collapse into single method --- .../jdbc/JDBCConnectionUrlParserDB2Test.java | 57 +++++-------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java index e5d699b6c4c..b5e0af8ebcc 100644 --- a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java @@ -3,8 +3,7 @@ import static datadog.trace.bootstrap.instrumentation.jdbc.JDBCConnectionUrlParser.extractDBInfo; import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; +import org.tabletest.junit.TableTest; /** * Tests for DB2/AS400 JDBC URL parsing when the URL contains '=' but no explicit port. @@ -15,46 +14,20 @@ */ class JDBCConnectionUrlParserDB2Test { - @ParameterizedTest - @CsvSource({ - // DB2: path present, query-string params, no port — last ':' is the scheme colon - "jdbc:db2://db2.host/mydb?user=db2user, db2, db2.host, mydb, db2user", - // AS400: same pattern - "jdbc:as400://ashost/asdb?user=asuser, as400, ashost, asdb, asuser", - // DB2: multiple query params - "jdbc:db2://db2.host/mydb?user=db2user&connectionTimeout=30, db2, db2.host, mydb, db2user", + @TableTest({ + "scenario | url | type | host | instance | user | db ", + "DB2 with user param, no port | jdbc:db2://db2.host/mydb?user=db2user | db2 | db2.host | mydb | db2user | mydb ", + "AS400 with user param, no port | jdbc:as400://ashost/asdb?user=asuser | as400 | ashost | asdb | asuser | asdb ", + "DB2 with multiple params, no port | jdbc:db2://db2.host/mydb?user=db2user&connectionTimeout=30 | db2 | db2.host | mydb | db2user | mydb ", + "DB2 with databasename param, no port | jdbc:db2://db2.host/mydb?user=db2user&databasename=otherdb | db2 | db2.host | mydb | db2user | otherdb" }) - void db2UrlWithEqualsAndNoPortShouldParseHostCorrectly( - String url, - String expectedType, - String expectedHost, - String expectedInstance, - String expectedUser) { - DBInfo info = extractDBInfo(url.trim(), null); - - // Without the fix, host was never extracted due to StringIndexOutOfBoundsException. - assertEquals(expectedType, info.getType()); - assertEquals(expectedHost, info.getHost()); - assertEquals(expectedInstance, info.getInstance()); - assertEquals(expectedUser, info.getUser()); - } - - @ParameterizedTest - @CsvSource({ - // databasename param in query string should be parsed into db field - "jdbc:db2://db2.host/mydb?user=db2user&databasename=otherdb, db2, db2.host, db2user, otherdb", - }) - void db2UrlWithDatabasenameQueryParamShouldParseDbCorrectly( - String url, - String expectedType, - String expectedHost, - String expectedUser, - String expectedDb) { - DBInfo info = extractDBInfo(url.trim(), null); - - assertEquals(expectedType, info.getType()); - assertEquals(expectedHost, info.getHost()); - assertEquals(expectedUser, info.getUser()); - assertEquals(expectedDb, info.getDb()); + void db2UrlWithEqualsAndNoPortShouldParseCorrectly( + String url, String type, String host, String instance, String user, String db) { + DBInfo info = extractDBInfo(url, null); + assertEquals(type, info.getType()); + assertEquals(host, info.getHost()); + assertEquals(instance, info.getInstance()); + assertEquals(user, info.getUser()); + assertEquals(db, info.getDb()); } } From 115058095f21307afeee34a8f49886567890d026 Mon Sep 17 00:00:00 2001 From: Valentin Zakharov Date: Thu, 4 Jun 2026 10:25:56 +0200 Subject: [PATCH 5/5] Add regression tests for DB2/AS400 URL parsing with port and without params --- .../instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java index b5e0af8ebcc..091c1e2b02f 100644 --- a/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java @@ -19,7 +19,9 @@ class JDBCConnectionUrlParserDB2Test { "DB2 with user param, no port | jdbc:db2://db2.host/mydb?user=db2user | db2 | db2.host | mydb | db2user | mydb ", "AS400 with user param, no port | jdbc:as400://ashost/asdb?user=asuser | as400 | ashost | asdb | asuser | asdb ", "DB2 with multiple params, no port | jdbc:db2://db2.host/mydb?user=db2user&connectionTimeout=30 | db2 | db2.host | mydb | db2user | mydb ", - "DB2 with databasename param, no port | jdbc:db2://db2.host/mydb?user=db2user&databasename=otherdb | db2 | db2.host | mydb | db2user | otherdb" + "DB2 with databasename param, no port | jdbc:db2://db2.host/mydb?user=db2user&databasename=otherdb | db2 | db2.host | mydb | db2user | otherdb", + "DB2 with port and colon params | jdbc:db2://db2.host:50000/mydb:user=db2user | db2 | db2.host | mydb | db2user | mydb ", + "DB2 no params | jdbc:db2://db2.host/mydb | db2 | db2.host | mydb | | mydb " }) void db2UrlWithEqualsAndNoPortShouldParseCorrectly( String url, String type, String host, String instance, String user, String db) {