@@ -1317,6 +1317,34 @@ impl ClientOptions {
1317
1317
}
1318
1318
}
1319
1319
1320
+ /// Splits the string once on the first instance of the given delimiter. If the delimiter is not
1321
+ /// present, returns the entire string as the "left" side.
1322
+ ///
1323
+ /// e.g.
1324
+ /// "abc.def" split on "." -> ("abc", Some("def"))
1325
+ /// "ab.cd.ef" split on "." -> ("ab", Some("cd.ef"))
1326
+ /// "abcdef" split on "." -> ("abcdef", None)
1327
+ fn split_once_left < ' a > ( s : & ' a str , delimiter : & str ) -> ( & ' a str , Option < & ' a str > ) {
1328
+ match s. split_once ( delimiter) {
1329
+ Some ( ( l, r) ) => ( l, Some ( r) ) ,
1330
+ None => ( s, None ) ,
1331
+ }
1332
+ }
1333
+
1334
+ /// Splits the string once on the last instance of the given delimiter. If the delimiter is not
1335
+ /// present, returns the entire string as the "right" side.
1336
+ ///
1337
+ /// e.g.
1338
+ /// "abd.def" split on "." -> (Some("abc"), "def")
1339
+ /// "ab.cd.ef" split on "." -> (Some("ab.cd"), "ef")
1340
+ /// "abcdef" split on "." -> (None, "abcdef")
1341
+ fn split_once_right < ' a > ( s : & ' a str , delimiter : & str ) -> ( Option < & ' a str > , & ' a str ) {
1342
+ match s. rsplit_once ( delimiter) {
1343
+ Some ( ( l, r) ) => ( Some ( l) , r) ,
1344
+ None => ( None , s) ,
1345
+ }
1346
+ }
1347
+
1320
1348
/// Splits a string into a section before a given index and a section exclusively after the index.
1321
1349
/// Empty portions are returned as `None`.
1322
1350
fn exclusive_split_at ( s : & str , i : usize ) -> ( Option < & str > , Option < & str > ) {
@@ -1338,12 +1366,12 @@ fn percent_decode(s: &str, err_message: &str) -> Result<String> {
1338
1366
}
1339
1367
}
1340
1368
1341
- fn validate_userinfo ( s : & str , userinfo_type : & str ) -> Result < ( ) > {
1369
+ fn validate_and_parse_userinfo ( s : & str , userinfo_type : & str ) -> Result < String > {
1342
1370
if s. chars ( ) . any ( |c| USERINFO_RESERVED_CHARACTERS . contains ( & c) ) {
1343
- return Err ( ErrorKind :: InvalidArgument {
1344
- message : format ! ( "{} must be URL encoded" , userinfo_type ) ,
1345
- }
1346
- . into ( ) ) ;
1371
+ return Err ( Error :: invalid_argument ( format ! (
1372
+ "{} must be URL encoded" ,
1373
+ userinfo_type
1374
+ ) ) ) ;
1347
1375
}
1348
1376
1349
1377
// All instances of '%' in the username must be part of an percent-encoded substring. This means
@@ -1352,13 +1380,13 @@ fn validate_userinfo(s: &str, userinfo_type: &str) -> Result<()> {
1352
1380
. skip ( 1 )
1353
1381
. any ( |part| part. len ( ) < 2 || part[ 0 ..2 ] . chars ( ) . any ( |c| !c. is_ascii_hexdigit ( ) ) )
1354
1382
{
1355
- return Err ( ErrorKind :: InvalidArgument {
1356
- message : "username/password cannot contain unescaped %". to_string ( ) ,
1357
- }
1358
- . into ( ) ) ;
1383
+ return Err ( Error :: invalid_argument ( format ! (
1384
+ "{} cannot contain unescaped %",
1385
+ userinfo_type
1386
+ ) ) ) ;
1359
1387
}
1360
1388
1361
- Ok ( ( ) )
1389
+ percent_decode ( s , & format ! ( "{} must be URL encoded" , userinfo_type ) )
1362
1390
}
1363
1391
1364
1392
impl TryFrom < & str > for ConnectionString {
@@ -1390,116 +1418,60 @@ impl ConnectionString {
1390
1418
/// malformed or one of the options has an invalid value, an error will be returned.
1391
1419
pub fn parse ( s : impl AsRef < str > ) -> Result < Self > {
1392
1420
let s = s. as_ref ( ) ;
1393
- let end_of_scheme = match s. find ( "://" ) {
1394
- Some ( index) => index,
1395
- None => {
1396
- return Err ( ErrorKind :: InvalidArgument {
1397
- message : "connection string contains no scheme" . to_string ( ) ,
1398
- }
1399
- . into ( ) )
1400
- }
1421
+
1422
+ let Some ( ( scheme, after_scheme) ) = s. split_once ( "://" ) else {
1423
+ return Err ( Error :: invalid_argument (
1424
+ "connection string contains no scheme" ,
1425
+ ) ) ;
1401
1426
} ;
1402
1427
1403
- let srv = match & s [ ..end_of_scheme ] {
1428
+ let srv = match scheme {
1404
1429
"mongodb" => false ,
1430
+ #[ cfg( feature = "dns-resolver" ) ]
1405
1431
"mongodb+srv" => true ,
1406
- _ => {
1407
- return Err ( ErrorKind :: InvalidArgument {
1408
- message : format ! ( "invalid connection string scheme: {}" , & s[ ..end_of_scheme] ) ,
1409
- }
1410
- . into ( ) )
1432
+ #[ cfg( not( feature = "dns-resolver" ) ) ]
1433
+ "mongodb+srv" => {
1434
+ return Err ( Error :: invalid_argument (
1435
+ "mongodb+srv connection strings cannot be used when the 'dns-resolver' \
1436
+ feature is disabled",
1437
+ ) )
1411
1438
}
1412
- } ;
1413
- #[ cfg( not( feature = "dns-resolver" ) ) ]
1414
- if srv {
1415
- return Err ( Error :: invalid_argument (
1416
- "mongodb+srv connection strings cannot be used when the 'dns-resolver' feature is \
1417
- disabled",
1418
- ) ) ;
1419
- }
1420
-
1421
- let after_scheme = & s[ end_of_scheme + 3 ..] ;
1422
-
1423
- let ( pre_slash, post_slash) = match after_scheme. find ( '/' ) {
1424
- Some ( slash_index) => match exclusive_split_at ( after_scheme, slash_index) {
1425
- ( Some ( section) , o) => ( section, o) ,
1426
- ( None , _) => {
1427
- return Err ( ErrorKind :: InvalidArgument {
1428
- message : "missing hosts" . to_string ( ) ,
1429
- }
1430
- . into ( ) )
1431
- }
1432
- } ,
1433
- None => {
1434
- if after_scheme. find ( '?' ) . is_some ( ) {
1435
- return Err ( ErrorKind :: InvalidArgument {
1436
- message : "Missing delimiting slash between hosts and options" . to_string ( ) ,
1437
- }
1438
- . into ( ) ) ;
1439
- }
1440
- ( after_scheme, None )
1439
+ other => {
1440
+ return Err ( Error :: invalid_argument ( format ! (
1441
+ "unsupported connection string scheme: {}" ,
1442
+ other
1443
+ ) ) )
1441
1444
}
1442
1445
} ;
1443
1446
1444
- let ( database, options_section) = match post_slash {
1445
- Some ( section) => match section. find ( '?' ) {
1446
- Some ( index) => exclusive_split_at ( section, index) ,
1447
- None => ( post_slash, None ) ,
1448
- } ,
1449
- None => ( None , None ) ,
1450
- } ;
1451
-
1452
- let db = match database {
1453
- Some ( db) => {
1454
- let decoded = percent_decode ( db, "database name must be URL encoded" ) ?;
1455
- if decoded
1456
- . chars ( )
1457
- . any ( |c| ILLEGAL_DATABASE_CHARACTERS . contains ( & c) )
1458
- {
1459
- return Err ( ErrorKind :: InvalidArgument {
1460
- message : "illegal character in database name" . to_string ( ) ,
1461
- }
1462
- . into ( ) ) ;
1463
- }
1464
- Some ( decoded)
1465
- }
1466
- None => None ,
1467
- } ;
1447
+ let ( pre_options, options) = split_once_left ( after_scheme, "?" ) ;
1448
+ let ( user_info, hosts_and_auth_db) = split_once_right ( pre_options, "@" ) ;
1468
1449
1469
- let ( authentication_requested, cred_section, hosts_section) = match pre_slash. rfind ( '@' ) {
1470
- Some ( index) => {
1471
- // if '@' is in the host section, it MUST be interpreted as a request for
1472
- // authentication, even if the credentials are empty.
1473
- let ( creds, hosts) = exclusive_split_at ( pre_slash, index) ;
1474
- match hosts {
1475
- Some ( hs) => ( true , creds, hs) ,
1476
- None => {
1477
- return Err ( ErrorKind :: InvalidArgument {
1478
- message : "missing hosts" . to_string ( ) ,
1479
- }
1480
- . into ( ) )
1481
- }
1482
- }
1450
+ // if '@' is in the host section, it MUST be interpreted as a request for authentication
1451
+ let authentication_requested = user_info. is_some ( ) ;
1452
+ let ( username, password) = match user_info {
1453
+ Some ( user_info) => {
1454
+ let ( username, password) = split_once_left ( user_info, ":" ) ;
1455
+ let username = if username. is_empty ( ) {
1456
+ None
1457
+ } else {
1458
+ Some ( validate_and_parse_userinfo ( username, "username" ) ?)
1459
+ } ;
1460
+ let password = match password {
1461
+ Some ( password) => Some ( validate_and_parse_userinfo ( password, "password" ) ?) ,
1462
+ None => None ,
1463
+ } ;
1464
+ ( username, password)
1483
1465
}
1484
- None => ( false , None , pre_slash) ,
1485
- } ;
1486
-
1487
- let ( username, password) = match cred_section {
1488
- Some ( creds) => match creds. find ( ':' ) {
1489
- Some ( index) => match exclusive_split_at ( creds, index) {
1490
- ( username, None ) => ( username, Some ( "" ) ) ,
1491
- ( username, password) => ( username, password) ,
1492
- } ,
1493
- None => ( Some ( creds) , None ) , // Lack of ":" implies whole string is username
1494
- } ,
1495
1466
None => ( None , None ) ,
1496
1467
} ;
1497
1468
1498
- let hosts = hosts_section
1499
- . split ( ',' )
1469
+ let ( hosts, auth_db) = split_once_left ( hosts_and_auth_db, "/" ) ;
1470
+
1471
+ let hosts = hosts
1472
+ . split ( "," )
1500
1473
. map ( ServerAddress :: parse)
1501
1474
. collect :: < Result < Vec < ServerAddress > > > ( ) ?;
1502
-
1503
1475
let host_info = if !srv {
1504
1476
HostInfo :: HostIdentifiers ( hosts)
1505
1477
} else {
@@ -1527,17 +1499,32 @@ impl ConnectionString {
1527
1499
}
1528
1500
} ;
1529
1501
1502
+ let db = match auth_db {
1503
+ Some ( "" ) | None => None ,
1504
+ Some ( db) => {
1505
+ let decoded = percent_decode ( db, "database name must be URL encoded" ) ?;
1506
+ for c in decoded. chars ( ) {
1507
+ if ILLEGAL_DATABASE_CHARACTERS . contains ( & c) {
1508
+ return Err ( Error :: invalid_argument ( format ! (
1509
+ "illegal character in database name: {}" ,
1510
+ c
1511
+ ) ) ) ;
1512
+ }
1513
+ }
1514
+ Some ( decoded)
1515
+ }
1516
+ } ;
1517
+
1530
1518
let mut conn_str = ConnectionString {
1531
1519
host_info,
1532
1520
#[ cfg( test) ]
1533
1521
original_uri : s. into ( ) ,
1534
1522
..Default :: default ( )
1535
1523
} ;
1536
1524
1537
- let mut parts = if let Some ( opts) = options_section {
1538
- conn_str. parse_options ( opts) ?
1539
- } else {
1540
- ConnectionStringParts :: default ( )
1525
+ let mut parts = match options {
1526
+ Some ( options) => conn_str. parse_options ( options) ?,
1527
+ None => ConnectionStringParts :: default ( ) ,
1541
1528
} ;
1542
1529
1543
1530
if conn_str. srv_service_name . is_some ( ) && !srv {
@@ -1566,19 +1553,10 @@ impl ConnectionString {
1566
1553
}
1567
1554
}
1568
1555
1569
- // Set username and password.
1570
- if let Some ( u) = username {
1556
+ if let Some ( username) = username {
1571
1557
let credential = conn_str. credential . get_or_insert_with ( Default :: default) ;
1572
- validate_userinfo ( u, "username" ) ?;
1573
- let decoded_u = percent_decode ( u, "username must be URL encoded" ) ?;
1574
-
1575
- credential. username = Some ( decoded_u) ;
1576
-
1577
- if let Some ( pass) = password {
1578
- validate_userinfo ( pass, "password" ) ?;
1579
- let decoded_p = percent_decode ( pass, "password must be URL encoded" ) ?;
1580
- credential. password = Some ( decoded_p)
1581
- }
1558
+ credential. username = Some ( username) ;
1559
+ credential. password = password;
1582
1560
}
1583
1561
1584
1562
if parts. auth_source . as_deref ( ) == Some ( "" ) {
0 commit comments