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..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,13 +81,26 @@ 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("=")) { 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 { + // 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; urlPart2 = null; @@ -99,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 new file mode 100644 index 00000000000..091c1e2b02f --- /dev/null +++ b/dd-java-agent/agent-bootstrap/src/test/java/datadog/trace/bootstrap/instrumentation/jdbc/JDBCConnectionUrlParserDB2Test.java @@ -0,0 +1,35 @@ +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.tabletest.junit.TableTest; + +/** + * 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}. + */ +class JDBCConnectionUrlParserDB2Test { + + @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", + "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) { + 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()); + } +}