Skip to content

Commit 1ce52ac

Browse files
authored
Forward slash in connection string is optional (#1420)
JAVA-5166
1 parent 24b3aff commit 1ce52ac

File tree

10 files changed

+154
-31
lines changed

10 files changed

+154
-31
lines changed

driver-core/src/main/com/mongodb/ConnectionString.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -368,24 +368,26 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient
368368

369369
// Split out the user and host information
370370
String userAndHostInformation;
371-
int idx = unprocessedConnectionString.indexOf("/");
372-
if (idx == -1) {
373-
if (unprocessedConnectionString.contains("?")) {
374-
throw new IllegalArgumentException("The connection string contains options without trailing slash");
375-
}
371+
int firstForwardSlashIdx = unprocessedConnectionString.indexOf("/");
372+
int firstQuestionMarkIdx = unprocessedConnectionString.indexOf("?");
373+
if (firstQuestionMarkIdx == -1 && firstForwardSlashIdx == -1) {
376374
userAndHostInformation = unprocessedConnectionString;
377375
unprocessedConnectionString = "";
376+
} else if (firstQuestionMarkIdx != -1 && (firstForwardSlashIdx == -1 || firstQuestionMarkIdx < firstForwardSlashIdx)) {
377+
// there is a question mark, and there is no slash or the question mark comes before any slash
378+
userAndHostInformation = unprocessedConnectionString.substring(0, firstQuestionMarkIdx);
379+
unprocessedConnectionString = unprocessedConnectionString.substring(firstQuestionMarkIdx);
378380
} else {
379-
userAndHostInformation = unprocessedConnectionString.substring(0, idx);
380-
unprocessedConnectionString = unprocessedConnectionString.substring(idx + 1);
381+
userAndHostInformation = unprocessedConnectionString.substring(0, firstForwardSlashIdx);
382+
unprocessedConnectionString = unprocessedConnectionString.substring(firstForwardSlashIdx + 1);
381383
}
382384

383385
// Split the user and host information
384386
String userInfo;
385387
String hostIdentifier;
386388
String userName = null;
387389
char[] password = null;
388-
idx = userAndHostInformation.lastIndexOf("@");
390+
int idx = userAndHostInformation.lastIndexOf("@");
389391
if (idx > 0) {
390392
userInfo = userAndHostInformation.substring(0, idx).replace("+", "%2B");
391393
hostIdentifier = userAndHostInformation.substring(idx + 1);

driver-core/src/test/resources/connection-string/invalid-uris.json

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,6 @@
162162
"auth": null,
163163
"options": null
164164
},
165-
{
166-
"description": "Missing delimiting slash between hosts and options",
167-
"uri": "mongodb://example.com?w=1",
168-
"valid": false,
169-
"warning": null,
170-
"hosts": null,
171-
"auth": null,
172-
"options": null
173-
},
174165
{
175166
"description": "Incomplete key value pair for option",
176167
"uri": "mongodb://example.com/?w",
@@ -269,6 +260,24 @@
269260
"hosts": null,
270261
"auth": null,
271262
"options": null
263+
},
264+
{
265+
"description": "Username with password containing an unescaped percent sign and an escaped one",
266+
"uri": "mongodb://user%20%:password@localhost",
267+
"valid": false,
268+
"warning": null,
269+
"hosts": null,
270+
"auth": null,
271+
"options": null
272+
},
273+
{
274+
"description": "Username with password containing an unescaped percent sign (non hex digit)",
275+
"uri": "mongodb://user%w:password@localhost",
276+
"valid": false,
277+
"warning": null,
278+
"hosts": null,
279+
"auth": null,
280+
"options": null
272281
}
273282
]
274283
}

driver-core/src/test/resources/connection-string/valid-auth.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@
242242
},
243243
{
244244
"description": "Subdelimiters in user/pass don't need escaping (MONGODB-CR)",
245-
"uri": "mongodb://!$&'()*,;=:!$&'()*,;[email protected]/admin?authMechanism=MONGODB-CR",
245+
"uri": "mongodb://!$&'()*+,;=:!$&'()*+,;[email protected]/admin?authMechanism=MONGODB-CR",
246246
"valid": true,
247247
"warning": false,
248248
"hosts": [
@@ -253,8 +253,8 @@
253253
}
254254
],
255255
"auth": {
256-
"username": "!$&'()*,;=",
257-
"password": "!$&'()*,;=",
256+
"username": "!$&'()*+,;=",
257+
"password": "!$&'()*+,;=",
258258
"db": "admin"
259259
},
260260
"options": {
@@ -284,7 +284,7 @@
284284
},
285285
{
286286
"description": "Escaped username (GSSAPI)",
287-
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI",
287+
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authMechanism=GSSAPI",
288288
"valid": true,
289289
"warning": false,
290290
"hosts": [
@@ -303,7 +303,8 @@
303303
"authmechanism": "GSSAPI",
304304
"authmechanismproperties": {
305305
"SERVICE_NAME": "other",
306-
"CANONICALIZE_HOST_NAME": true
306+
"SERVICE_HOST": "example.com",
307+
"CANONICALIZE_HOST_NAME": "forward"
307308
}
308309
}
309310
},

driver-core/src/test/resources/connection-string/valid-options.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@
2121
"authmechanism": "MONGODB-CR"
2222
}
2323
},
24+
{
25+
"description": "Missing delimiting slash between hosts and options",
26+
"uri": "mongodb://example.com?tls=true",
27+
"valid": true,
28+
"warning": false,
29+
"hosts": [
30+
{
31+
"type": "hostname",
32+
"host": "example.com",
33+
"port": null
34+
}
35+
],
36+
"auth": null,
37+
"options": {
38+
"tls": true
39+
}
40+
},
2441
{
2542
"description": "Colon in a key value pair",
2643
"uri": "mongodb://example.com/?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://test-cluster",

driver-core/src/test/resources/connection-string/valid-unix_socket-absolute.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@
3030
"auth": null,
3131
"options": null
3232
},
33+
{
34+
"description": "Unix domain socket (mixed case)",
35+
"uri": "mongodb://%2Ftmp%2FMongoDB-27017.sock",
36+
"valid": true,
37+
"warning": false,
38+
"hosts": [
39+
{
40+
"type": "unix",
41+
"host": "/tmp/MongoDB-27017.sock",
42+
"port": null
43+
}
44+
],
45+
"auth": null,
46+
"options": null
47+
},
3348
{
3449
"description": "Unix domain socket (absolute path with spaces in path)",
3550
"uri": "mongodb://%2Ftmp%2F %2Fmongodb-27017.sock",

driver-core/src/test/resources/connection-string/valid-unix_socket-relative.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@
3030
"auth": null,
3131
"options": null
3232
},
33+
{
34+
"description": "Unix domain socket (mixed case)",
35+
"uri": "mongodb://rel%2FMongoDB-27017.sock",
36+
"valid": true,
37+
"warning": false,
38+
"hosts": [
39+
{
40+
"type": "unix",
41+
"host": "rel/MongoDB-27017.sock",
42+
"port": null
43+
}
44+
],
45+
"auth": null,
46+
"options": null
47+
},
3348
{
3449
"description": "Unix domain socket (relative path with spaces)",
3550
"uri": "mongodb://rel%2F %2Fmongodb-27017.sock",

driver-core/src/test/resources/connection-string/valid-warnings.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,51 @@
6363
"options": {
6464
"wtimeoutms": 10
6565
}
66+
},
67+
{
68+
"description": "Empty integer option values are ignored",
69+
"uri": "mongodb://localhost/?maxIdleTimeMS=",
70+
"valid": true,
71+
"warning": true,
72+
"hosts": [
73+
{
74+
"type": "hostname",
75+
"host": "localhost",
76+
"port": null
77+
}
78+
],
79+
"auth": null,
80+
"options": null
81+
},
82+
{
83+
"description": "Empty boolean option value are ignored",
84+
"uri": "mongodb://localhost/?journal=",
85+
"valid": true,
86+
"warning": true,
87+
"hosts": [
88+
{
89+
"type": "hostname",
90+
"host": "localhost",
91+
"port": null
92+
}
93+
],
94+
"auth": null,
95+
"options": null
96+
},
97+
{
98+
"description": "Comma in a key value pair causes a warning",
99+
"uri": "mongodb://localhost?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://host1%2Chost2",
100+
"valid": true,
101+
"warning": true,
102+
"hosts": [
103+
{
104+
"type": "hostname",
105+
"host": "localhost",
106+
"port": null
107+
}
108+
],
109+
"auth": null,
110+
"options": null
66111
}
67112
]
68113
}

driver-core/src/test/unit/com/mongodb/ConnectionStringSpecification.groovy

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,28 @@ class ConnectionStringSpecification extends Specification {
141141
.withWTimeout(5, MILLISECONDS).withJournal(true)
142142
}
143143

144+
@Unroll
145+
def 'should treat trailing slash before query parameters as optional'() {
146+
expect:
147+
uri.getApplicationName() == appName
148+
uri.getDatabase() == db
149+
150+
where:
151+
uri | appName | db
152+
new ConnectionString('mongodb://mongodb.com') | null | null
153+
new ConnectionString('mongodb://mongodb.com?') | null | null
154+
new ConnectionString('mongodb://mongodb.com/') | null | null
155+
new ConnectionString('mongodb://mongodb.com/?') | null | null
156+
new ConnectionString('mongodb://mongodb.com/test') | null | "test"
157+
new ConnectionString('mongodb://mongodb.com/test?') | null | "test"
158+
new ConnectionString('mongodb://mongodb.com/?appName=a1') | "a1" | null
159+
new ConnectionString('mongodb://mongodb.com?appName=a1') | "a1" | null
160+
new ConnectionString('mongodb://mongodb.com/?appName=a1/a2') | "a1/a2" | null
161+
new ConnectionString('mongodb://mongodb.com?appName=a1/a2') | "a1/a2" | null
162+
new ConnectionString('mongodb://mongodb.com/test?appName=a1') | "a1" | "test"
163+
new ConnectionString('mongodb://mongodb.com/test?appName=a1/a2') | "a1/a2" | "test"
164+
}
165+
144166
def 'should correctly parse different UUID representations'() {
145167
expect:
146168
uri.getUuidRepresentation() == uuidRepresentation
@@ -473,7 +495,6 @@ class ConnectionStringSpecification extends Specification {
473495
'has an empty host' | 'mongodb://localhost:27017,,localhost:27019'
474496
'has an malformed IPv6 host' | 'mongodb://[::1'
475497
'has unescaped colons' | 'mongodb://locahost::1'
476-
'is missing a slash' | 'mongodb://localhost?wTimeout=5'
477498
'contains an invalid port string' | 'mongodb://localhost:twenty'
478499
'contains an invalid port negative' | 'mongodb://localhost:-1'
479500
'contains an invalid port out of range' | 'mongodb://localhost:1000000'

driver-core/src/test/unit/com/mongodb/ConnectionStringTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import java.util.Collection;
3030
import java.util.List;
3131

32+
import static org.junit.Assume.assumeFalse;
33+
3234
// See https://github.com/mongodb/specifications/tree/master/source/connection-string/tests
3335
public class ConnectionStringTest extends AbstractConnectionStringTest {
3436
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
3739

3840
@Test
3941
public void shouldPassAllOutcomes() {
42+
// Java driver currently throws an IllegalArgumentException for these tests
43+
assumeFalse(getDescription().equals("Empty integer option values are ignored"));
44+
assumeFalse(getDescription().equals("Comma in a key value pair causes a warning"));
45+
4046
if (getFilename().equals("invalid-uris.json")) {
4147
testInvalidUris();
4248
} else if (getFilename().equals("valid-auth.json")) {

driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS
3434

3535
class MongoClientURISpecification extends Specification {
3636

37-
def 'should throw Exception if URI does not have a trailing slash'() {
38-
when:
39-
new MongoClientURI('mongodb://localhost?wtimeoutMS=5')
40-
41-
then:
42-
thrown(IllegalArgumentException)
43-
}
44-
4537
def 'should not throw an Exception if URI contains an unknown option'() {
4638
when:
4739
new MongoClientURI('mongodb://localhost/?unknownOption=5')

0 commit comments

Comments
 (0)