diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index c4e50d88020..82c6b3a00ec 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -368,16 +368,18 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient // Split out the user and host information String userAndHostInformation; - int idx = unprocessedConnectionString.indexOf("/"); - if (idx == -1) { - if (unprocessedConnectionString.contains("?")) { - throw new IllegalArgumentException("The connection string contains options without trailing slash"); - } + int firstForwardSlashIdx = unprocessedConnectionString.indexOf("/"); + int firstQuestionMarkIdx = unprocessedConnectionString.indexOf("?"); + if (firstQuestionMarkIdx == -1 && firstForwardSlashIdx == -1) { userAndHostInformation = unprocessedConnectionString; unprocessedConnectionString = ""; + } else if (firstQuestionMarkIdx != -1 && (firstForwardSlashIdx == -1 || firstQuestionMarkIdx < firstForwardSlashIdx)) { + // there is a question mark, and there is no slash or the question mark comes before any slash + userAndHostInformation = unprocessedConnectionString.substring(0, firstQuestionMarkIdx); + unprocessedConnectionString = unprocessedConnectionString.substring(firstQuestionMarkIdx); } else { - userAndHostInformation = unprocessedConnectionString.substring(0, idx); - unprocessedConnectionString = unprocessedConnectionString.substring(idx + 1); + userAndHostInformation = unprocessedConnectionString.substring(0, firstForwardSlashIdx); + unprocessedConnectionString = unprocessedConnectionString.substring(firstForwardSlashIdx + 1); } // Split the user and host information @@ -385,7 +387,7 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient String hostIdentifier; String userName = null; char[] password = null; - idx = userAndHostInformation.lastIndexOf("@"); + int idx = userAndHostInformation.lastIndexOf("@"); if (idx > 0) { userInfo = userAndHostInformation.substring(0, idx).replace("+", "%2B"); hostIdentifier = userAndHostInformation.substring(idx + 1); diff --git a/driver-core/src/test/resources/connection-string/invalid-uris.json b/driver-core/src/test/resources/connection-string/invalid-uris.json index 2a182fac7e2..a7accbd27d6 100644 --- a/driver-core/src/test/resources/connection-string/invalid-uris.json +++ b/driver-core/src/test/resources/connection-string/invalid-uris.json @@ -162,15 +162,6 @@ "auth": null, "options": null }, - { - "description": "Missing delimiting slash between hosts and options", - "uri": "mongodb://example.com?w=1", - "valid": false, - "warning": null, - "hosts": null, - "auth": null, - "options": null - }, { "description": "Incomplete key value pair for option", "uri": "mongodb://example.com/?w", @@ -269,6 +260,24 @@ "hosts": null, "auth": null, "options": null + }, + { + "description": "Username with password containing an unescaped percent sign and an escaped one", + "uri": "mongodb://user%20%:password@localhost", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Username with password containing an unescaped percent sign (non hex digit)", + "uri": "mongodb://user%w:password@localhost", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null } ] } diff --git a/driver-core/src/test/resources/connection-string/valid-auth.json b/driver-core/src/test/resources/connection-string/valid-auth.json index d3cafb029b9..176a54a096a 100644 --- a/driver-core/src/test/resources/connection-string/valid-auth.json +++ b/driver-core/src/test/resources/connection-string/valid-auth.json @@ -242,7 +242,7 @@ }, { "description": "Subdelimiters in user/pass don't need escaping (MONGODB-CR)", - "uri": "mongodb://!$&'()*,;=:!$&'()*,;=@127.0.0.1/admin?authMechanism=MONGODB-CR", + "uri": "mongodb://!$&'()*+,;=:!$&'()*+,;=@127.0.0.1/admin?authMechanism=MONGODB-CR", "valid": true, "warning": false, "hosts": [ @@ -253,8 +253,8 @@ } ], "auth": { - "username": "!$&'()*,;=", - "password": "!$&'()*,;=", + "username": "!$&'()*+,;=", + "password": "!$&'()*+,;=", "db": "admin" }, "options": { @@ -284,7 +284,7 @@ }, { "description": "Escaped username (GSSAPI)", - "uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI", + "uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authMechanism=GSSAPI", "valid": true, "warning": false, "hosts": [ @@ -303,7 +303,8 @@ "authmechanism": "GSSAPI", "authmechanismproperties": { "SERVICE_NAME": "other", - "CANONICALIZE_HOST_NAME": true + "SERVICE_HOST": "example.com", + "CANONICALIZE_HOST_NAME": "forward" } } }, diff --git a/driver-core/src/test/resources/connection-string/valid-options.json b/driver-core/src/test/resources/connection-string/valid-options.json index cb2027f86ff..3c79fe7ae55 100644 --- a/driver-core/src/test/resources/connection-string/valid-options.json +++ b/driver-core/src/test/resources/connection-string/valid-options.json @@ -21,6 +21,23 @@ "authmechanism": "MONGODB-CR" } }, + { + "description": "Missing delimiting slash between hosts and options", + "uri": "mongodb://example.com?tls=true", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": null + } + ], + "auth": null, + "options": { + "tls": true + } + }, { "description": "Colon in a key value pair", "uri": "mongodb://example.com/?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://test-cluster", diff --git a/driver-core/src/test/resources/connection-string/valid-unix_socket-absolute.json b/driver-core/src/test/resources/connection-string/valid-unix_socket-absolute.json index 5bb02476eb7..66491db13ba 100644 --- a/driver-core/src/test/resources/connection-string/valid-unix_socket-absolute.json +++ b/driver-core/src/test/resources/connection-string/valid-unix_socket-absolute.json @@ -30,6 +30,21 @@ "auth": null, "options": null }, + { + "description": "Unix domain socket (mixed case)", + "uri": "mongodb://%2Ftmp%2FMongoDB-27017.sock", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "unix", + "host": "/tmp/MongoDB-27017.sock", + "port": null + } + ], + "auth": null, + "options": null + }, { "description": "Unix domain socket (absolute path with spaces in path)", "uri": "mongodb://%2Ftmp%2F %2Fmongodb-27017.sock", diff --git a/driver-core/src/test/resources/connection-string/valid-unix_socket-relative.json b/driver-core/src/test/resources/connection-string/valid-unix_socket-relative.json index 2ce649ffc23..788720920ba 100644 --- a/driver-core/src/test/resources/connection-string/valid-unix_socket-relative.json +++ b/driver-core/src/test/resources/connection-string/valid-unix_socket-relative.json @@ -30,6 +30,21 @@ "auth": null, "options": null }, + { + "description": "Unix domain socket (mixed case)", + "uri": "mongodb://rel%2FMongoDB-27017.sock", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "unix", + "host": "rel/MongoDB-27017.sock", + "port": null + } + ], + "auth": null, + "options": null + }, { "description": "Unix domain socket (relative path with spaces)", "uri": "mongodb://rel%2F %2Fmongodb-27017.sock", diff --git a/driver-core/src/test/resources/connection-string/valid-warnings.json b/driver-core/src/test/resources/connection-string/valid-warnings.json index 87f7248f21e..f0e8288bc73 100644 --- a/driver-core/src/test/resources/connection-string/valid-warnings.json +++ b/driver-core/src/test/resources/connection-string/valid-warnings.json @@ -63,6 +63,51 @@ "options": { "wtimeoutms": 10 } + }, + { + "description": "Empty integer option values are ignored", + "uri": "mongodb://localhost/?maxIdleTimeMS=", + "valid": true, + "warning": true, + "hosts": [ + { + "type": "hostname", + "host": "localhost", + "port": null + } + ], + "auth": null, + "options": null + }, + { + "description": "Empty boolean option value are ignored", + "uri": "mongodb://localhost/?journal=", + "valid": true, + "warning": true, + "hosts": [ + { + "type": "hostname", + "host": "localhost", + "port": null + } + ], + "auth": null, + "options": null + }, + { + "description": "Comma in a key value pair causes a warning", + "uri": "mongodb://localhost?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://host1%2Chost2", + "valid": true, + "warning": true, + "hosts": [ + { + "type": "hostname", + "host": "localhost", + "port": null + } + ], + "auth": null, + "options": null } ] } diff --git a/driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy b/driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy index d56aa8a9c7c..72fdf108698 100644 --- a/driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy @@ -141,6 +141,28 @@ class ConnectionStringSpecification extends Specification { .withWTimeout(5, MILLISECONDS).withJournal(true) } + @Unroll + def 'should treat trailing slash before query parameters as optional'() { + expect: + uri.getApplicationName() == appName + uri.getDatabase() == db + + where: + uri | appName | db + new ConnectionString('mongodb://mongodb.com') | null | null + new ConnectionString('mongodb://mongodb.com?') | null | null + new ConnectionString('mongodb://mongodb.com/') | null | null + new ConnectionString('mongodb://mongodb.com/?') | null | null + new ConnectionString('mongodb://mongodb.com/test') | null | "test" + new ConnectionString('mongodb://mongodb.com/test?') | null | "test" + new ConnectionString('mongodb://mongodb.com/?appName=a1') | "a1" | null + new ConnectionString('mongodb://mongodb.com?appName=a1') | "a1" | null + new ConnectionString('mongodb://mongodb.com/?appName=a1/a2') | "a1/a2" | null + new ConnectionString('mongodb://mongodb.com?appName=a1/a2') | "a1/a2" | null + new ConnectionString('mongodb://mongodb.com/test?appName=a1') | "a1" | "test" + new ConnectionString('mongodb://mongodb.com/test?appName=a1/a2') | "a1/a2" | "test" + } + def 'should correctly parse different UUID representations'() { expect: uri.getUuidRepresentation() == uuidRepresentation @@ -473,7 +495,6 @@ class ConnectionStringSpecification extends Specification { 'has an empty host' | 'mongodb://localhost:27017,,localhost:27019' 'has an malformed IPv6 host' | 'mongodb://[::1' 'has unescaped colons' | 'mongodb://locahost::1' - 'is missing a slash' | 'mongodb://localhost?wTimeout=5' 'contains an invalid port string' | 'mongodb://localhost:twenty' 'contains an invalid port negative' | 'mongodb://localhost:-1' 'contains an invalid port out of range' | 'mongodb://localhost:1000000' diff --git a/driver-core/src/test/unit/com/mongodb/ConnectionStringTest.java b/driver-core/src/test/unit/com/mongodb/ConnectionStringTest.java index 3b28460e866..80cc9f65e83 100644 --- a/driver-core/src/test/unit/com/mongodb/ConnectionStringTest.java +++ b/driver-core/src/test/unit/com/mongodb/ConnectionStringTest.java @@ -29,6 +29,8 @@ import java.util.Collection; import java.util.List; +import static org.junit.Assume.assumeFalse; + // See https://github.com/mongodb/specifications/tree/master/source/connection-string/tests public class ConnectionStringTest extends AbstractConnectionStringTest { public ConnectionStringTest(final String filename, final String description, final String input, final BsonDocument definition) { @@ -37,6 +39,10 @@ public ConnectionStringTest(final String filename, final String description, fin @Test public void shouldPassAllOutcomes() { + // Java driver currently throws an IllegalArgumentException for these tests + assumeFalse(getDescription().equals("Empty integer option values are ignored")); + assumeFalse(getDescription().equals("Comma in a key value pair causes a warning")); + if (getFilename().equals("invalid-uris.json")) { testInvalidUris(); } else if (getFilename().equals("valid-auth.json")) { diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy index 3db0e1a45d8..b187df8dab8 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy @@ -34,14 +34,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS class MongoClientURISpecification extends Specification { - def 'should throw Exception if URI does not have a trailing slash'() { - when: - new MongoClientURI('mongodb://localhost?wtimeoutMS=5') - - then: - thrown(IllegalArgumentException) - } - def 'should not throw an Exception if URI contains an unknown option'() { when: new MongoClientURI('mongodb://localhost/?unknownOption=5')