1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #include "mongo/platform/basic.h"
32 
33 #include <fstream>
34 
35 #include "mongo/base/string_data.h"
36 #include "mongo/bson/bsonobj.h"
37 #include "mongo/bson/bsontypes.h"
38 #include "mongo/bson/json.h"
39 #include "mongo/client/mongo_uri.h"
40 #include "mongo/unittest/unittest.h"
41 
42 #include <boost/filesystem/operations.hpp>
43 
44 namespace mongo {
45 namespace {
46 using mongo::MongoURI;
47 
48 struct URITestCase {
49     std::string URI;
50     std::string uname;
51     std::string password;
52     mongo::ConnectionString::ConnectionType type;
53     std::string setname;
54     size_t numservers;
55     size_t numOptions;
56     std::string database;
57 };
58 
59 struct InvalidURITestCase {
60     std::string URI;
61 };
62 
63 const mongo::ConnectionString::ConnectionType kMaster = mongo::ConnectionString::MASTER;
64 const mongo::ConnectionString::ConnectionType kSet = mongo::ConnectionString::SET;
65 
66 const URITestCase validCases[] = {
67 
68     {"mongodb://user:pwd@127.0.0.1", "user", "pwd", kMaster, "", 1, 0, ""},
69 
70     {"mongodb://user@127.0.0.1", "user", "", kMaster, "", 1, 0, ""},
71 
72     {"mongodb://localhost/?foo=bar", "", "", kMaster, "", 1, 1, ""},
73 
74     {"mongodb://localhost,/?foo=bar", "", "", kMaster, "", 1, 1, ""},
75 
76     {"mongodb://user:pwd@127.0.0.1:1234", "user", "pwd", kMaster, "", 1, 0, ""},
77 
78     {"mongodb://user@127.0.0.1:1234", "user", "", kMaster, "", 1, 0, ""},
79 
80     {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
81 
82     {"mongodb://127.0.0.1/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
83 
84     {"mongodb://user:pwd@127.0.0.1,/dbName?foo=a&c=b", "user", "pwd", kMaster, "", 1, 2, "dbName"},
85 
86     {"mongodb://user:pwd@127.0.0.1,127.0.0.2/dbname?a=b&replicaSet=replName",
87      "user",
88      "pwd",
89      kSet,
90      "replName",
91      2,
92      2,
93      "dbname"},
94 
95     {"mongodb://needs%20encoding%25%23!%3C%3E:pwd@127.0.0.1,127.0.0.2/"
96      "dbname?a=b&replicaSet=replName",
97      "needs encoding%#!<>",
98      "pwd",
99      kSet,
100      "replName",
101      2,
102      2,
103      "dbname"},
104 
105     {"mongodb://needs%20encoding%25%23!%3C%3E:pwd@127.0.0.1,127.0.0.2/"
106      "db@name?a=b&replicaSet=replName",
107      "needs encoding%#!<>",
108      "pwd",
109      kSet,
110      "replName",
111      2,
112      2,
113      "db@name"},
114 
115     {"mongodb://user:needs%20encoding%25%23!%3C%3E@127.0.0.1,127.0.0.2/"
116      "dbname?a=b&replicaSet=replName",
117      "user",
118      "needs encoding%#!<>",
119      kSet,
120      "replName",
121      2,
122      2,
123      "dbname"},
124 
125     {"mongodb://user:pwd@127.0.0.1,127.0.0.2/dbname?a=b&replicaSet=needs%20encoding%25%23!%3C%3E",
126      "user",
127      "pwd",
128      kSet,
129      "needs encoding%#!<>",
130      2,
131      2,
132      "dbname"},
133 
134     {"mongodb://user:pwd@127.0.0.1,127.0.0.2/needsencoding%40hello?a=b&replicaSet=replName",
135      "user",
136      "pwd",
137      kSet,
138      "replName",
139      2,
140      2,
141      "needsencoding@hello"},
142 
143     {"mongodb://user:pwd@127.0.0.1,127.0.0.2/?replicaSet=replName",
144      "user",
145      "pwd",
146      kSet,
147      "replName",
148      2,
149      1,
150      ""},
151 
152     {"mongodb://user@127.0.0.1,127.0.0.2/?replicaSet=replName",
153      "user",
154      "",
155      kSet,
156      "replName",
157      2,
158      1,
159      ""},
160 
161     {"mongodb://127.0.0.1,127.0.0.2/dbName?foo=a&c=b&replicaSet=replName",
162      "",
163      "",
164      kSet,
165      "replName",
166      2,
167      3,
168      "dbName"},
169 
170     {"mongodb://user:pwd@127.0.0.1:1234,127.0.0.2:1234/?replicaSet=replName",
171      "user",
172      "pwd",
173      kSet,
174      "replName",
175      2,
176      1,
177      ""},
178 
179     {"mongodb://user@127.0.0.1:1234,127.0.0.2:1234/?replicaSet=replName",
180      "user",
181      "",
182      kSet,
183      "replName",
184      2,
185      1,
186      ""},
187 
188     {"mongodb://127.0.0.1:1234,127.0.0.1:1234/dbName?foo=a&c=b&replicaSet=replName",
189      "",
190      "",
191      kSet,
192      "replName",
193      2,
194      3,
195      "dbName"},
196 
197     {"mongodb://user:pwd@[::1]", "user", "pwd", kMaster, "", 1, 0, ""},
198 
199     {"mongodb://user@[::1]", "user", "", kMaster, "", 1, 0, ""},
200 
201     {"mongodb://[::1]/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
202 
203     {"mongodb://user:pwd@[::1]:1234", "user", "pwd", kMaster, "", 1, 0, ""},
204 
205     {"mongodb://user@[::1]:1234", "user", "", kMaster, "", 1, 0, ""},
206 
207     {"mongodb://[::1]:1234/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
208 
209     {"mongodb://user:pwd@[::1],127.0.0.2/?replicaSet=replName",
210      "user",
211      "pwd",
212      kSet,
213      "replName",
214      2,
215      1,
216      ""},
217 
218     {"mongodb://user@[::1],127.0.0.2/?replicaSet=replName", "user", "", kSet, "replName", 2, 1, ""},
219 
220     {"mongodb://[::1],127.0.0.2/dbName?foo=a&c=b&replicaSet=replName",
221      "",
222      "",
223      kSet,
224      "replName",
225      2,
226      3,
227      "dbName"},
228 
229     {"mongodb://user:pwd@[::1]:1234,127.0.0.2:1234/?replicaSet=replName",
230      "user",
231      "pwd",
232      kSet,
233      "replName",
234      2,
235      1,
236      ""},
237 
238     {"mongodb://user@[::1]:1234,127.0.0.2:1234/?replicaSet=replName",
239      "user",
240      "",
241      kSet,
242      "replName",
243      2,
244      1,
245      ""},
246 
247     {"mongodb://[::1]:1234,[::1]:1234/dbName?foo=a&c=b&replicaSet=replName",
248      "",
249      "",
250      kSet,
251      "replName",
252      2,
253      3,
254      "dbName"},
255 
256     {"mongodb://user:pwd@[::1]", "user", "pwd", kMaster, "", 1, 0, ""},
257 
258     {"mongodb://user@[::1]", "user", "", kMaster, "", 1, 0, ""},
259 
260     {"mongodb://[::1]/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
261 
262     {"mongodb://user:pwd@[::1]:1234", "user", "pwd", kMaster, "", 1, 0, ""},
263 
264     {"mongodb://user@[::1]:1234", "user", "", kMaster, "", 1, 0, ""},
265 
266     {"mongodb://[::1]:1234/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"},
267 
268     {"mongodb://user:pwd@[::1],127.0.0.2/?replicaSet=replName",
269      "user",
270      "pwd",
271      kSet,
272      "replName",
273      2,
274      1,
275      ""},
276 
277     {"mongodb://user@[::1],127.0.0.2/?replicaSet=replName", "user", "", kSet, "replName", 2, 1, ""},
278 
279     {"mongodb://[::1],127.0.0.2/dbName?foo=a&c=b&replicaSet=replName",
280      "",
281      "",
282      kSet,
283      "replName",
284      2,
285      3,
286      "dbName"},
287 
288     {"mongodb://user:pwd@[::1]:1234,127.0.0.2:1234/?replicaSet=replName",
289      "user",
290      "pwd",
291      kSet,
292      "replName",
293      2,
294      1,
295      ""},
296 
297     {"mongodb://user@[::1]:1234,127.0.0.2:1234/?replicaSet=replName",
298      "user",
299      "",
300      kSet,
301      "replName",
302      2,
303      1,
304      ""},
305 
306     {"mongodb://[::1]:1234,[::1]:1234/dbName?foo=a&c=b&replicaSet=replName",
307      "",
308      "",
309      kSet,
310      "replName",
311      2,
312      3,
313      "dbName"},
314 
315     {"mongodb://user:pwd@[::1]/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:foobar",
316      "user",
317      "pwd",
318      kMaster,
319      "",
320      1,
321      2,
322      ""},
323 
324     {"mongodb://user:pwd@[::1]/?authMechanism=GSSAPI&gssapiServiceName=foobar",
325      "user",
326      "pwd",
327      kMaster,
328      "",
329      1,
330      2,
331      ""},
332 
333     {"mongodb://%2Ftmp%2Fmongodb-27017.sock", "", "", kMaster, "", 1, 0, ""},
334 
335     {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replName",
336      "",
337      "",
338      kSet,
339      "replName",
340      2,
341      1,
342      ""},
343 };
344 
345 const InvalidURITestCase invalidCases[] = {
346 
347     // No host.
348     {"mongodb://"},
349     {"mongodb://usr:pwd@/dbname?a=b"},
350 
351     // Username and password must be encoded (cannot have ':' or '@')
352     {"mongodb://usr:pwd:@127.0.0.1/dbName?foo=a&c=b"},
353 
354     // Needs a "/" after the hosts and before the options.
355     {"mongodb://localhost:27017,localhost:27018?replicaSet=missingSlash"},
356 
357     // Host list must actually be comma separated.
358     {"mongodb://localhost:27017localhost:27018"},
359 
360     // Domain sockets have to end in ".sock".
361     {"mongodb://%2Fnotareal%2Fdomainsock"},
362 
363     // Database name cannot contain slash ("/"), backslash ("\"), space (" "), double-quote ("""),
364     // or dollar sign ("$")
365     {"mongodb://usr:pwd@localhost:27017/db$name?a=b"},
366     {"mongodb://usr:pwd@localhost:27017/db/name?a=b"},
367     {"mongodb://usr:pwd@localhost:27017/db\\name?a=b"},
368     {"mongodb://usr:pwd@localhost:27017/db name?a=b"},
369     {"mongodb://usr:pwd@localhost:27017/db\"name?a=b"},
370 
371     // Options must have a key
372     {"mongodb://usr:pwd@localhost:27017/dbname?=b"},
373 
374     // Cannot skip a key value pair
375     {"mongodb://usr:pwd@localhost:27017/dbname?a=b&&b=c"},
376 
377     // Multiple Unix domain sockets and auth DB resembling a socket (relative path)
378     {"mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin.sock?replicaSet=replName"},
379 
380     // Multiple Unix domain sockets with auth DB resembling a path (relative path)
381     {"mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin.shoe?replicaSet=replName"},
382 
383     // Multiple Unix domain sockets and auth DB resembling a socket (absolute path)
384     {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/"
385      "admin.sock?replicaSet=replName"},
386 
387     // Multiple Unix domain sockets with auth DB resembling a path (absolute path)
388     {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/"
389      "admin.shoe?replicaSet=replName"},
390 
391     // Missing value in key value pair for options
392     {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b&d"},
393     {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b&d="},
394     {"mongodb://127.0.0.1:1234/dbName?foo=a&h=&c=b&d=6"},
395     {"mongodb://127.0.0.1:1234/dbName?foo=a&h&c=b&d=6"},
396 
397     // Missing a hostname, or unparsable hostname(s)
398     {"mongodb://,/dbName"},
399     {"mongodb://user:pwd@,/dbName"},
400     {"mongodb://localhost:1234:5678/dbName"},
401 
402     // Options can't have multiple question marks. Only one.
403     {"mongodb://localhost:27017/?foo=a?c=b&d=e?asdf=foo"},
404 
405     // Missing a key in key value pair for options
406     {"mongodb://127.0.0.1:1234/dbName?foo=a&=d&c=b"},
407 
408     // Missing an entire key-value pair
409     {"mongodb://127.0.0.1:1234/dbName?foo=a&&c=b"},
410 };
411 
412 // Helper Method to take a filename for a json file and return the array of tests inside of it
getBsonFromJsonFile(std::string fileName)413 mongo::BSONObj getBsonFromJsonFile(std::string fileName) {
414     boost::filesystem::path directoryPath = boost::filesystem::current_path();
415     boost::filesystem::path filePath(directoryPath / "src" / "mongo" / "client" /
416                                      "mongo_uri_tests" / fileName);
417     std::string filename(filePath.string());
418     std::ifstream infile(filename.c_str());
419     std::string data((std::istreambuf_iterator<char>(infile)), std::istreambuf_iterator<char>());
420     mongo::BSONObj obj = mongo::fromjson(data);
421     ASSERT_TRUE(obj.valid(mongo::BSONVersion::kLatest));
422     ASSERT_TRUE(obj.hasField("tests"));
423     mongo::BSONObj arr = obj.getField("tests").embeddedObject().getOwned();
424     ASSERT_TRUE(arr.couldBeArray());
425     return arr;
426 }
427 
428 // Helper method to take a BSONElement and either extract its string or return an empty string
returnStringFromElementOrNull(mongo::BSONElement element)429 std::string returnStringFromElementOrNull(mongo::BSONElement element) {
430     ASSERT_TRUE(!element.eoo());
431     if (element.type() == mongo::jstNULL) {
432         return std::string();
433     }
434     ASSERT_EQ(element.type(), mongo::String);
435     return element.String();
436 }
437 
438 // Helper method to take a valid test case, parse() it, and assure the output is correct
testValidURIFormat(URITestCase testCase)439 void testValidURIFormat(URITestCase testCase) {
440     mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n';
441     std::string errMsg;
442     const auto cs_status = MongoURI::parse(testCase.URI);
443     ASSERT_OK(cs_status);
444     auto result = cs_status.getValue();
445     ASSERT_EQ(testCase.uname, result.getUser());
446     ASSERT_EQ(testCase.password, result.getPassword());
447     ASSERT_EQ(testCase.type, result.type());
448     ASSERT_EQ(testCase.setname, result.getSetName());
449     ASSERT_EQ(testCase.numservers, result.getServers().size());
450     ASSERT_EQ(testCase.numOptions, result.getOptions().size());
451     ASSERT_EQ(testCase.database, result.getDatabase());
452 }
453 
TEST(MongoURI,GoodTrickyURIs)454 TEST(MongoURI, GoodTrickyURIs) {
455     const size_t numCases = sizeof(validCases) / sizeof(validCases[0]);
456 
457     for (size_t i = 0; i != numCases; ++i) {
458         const URITestCase testCase = validCases[i];
459         testValidURIFormat(testCase);
460     }
461 }
462 
TEST(MongoURI,InvalidURIs)463 TEST(MongoURI, InvalidURIs) {
464     const size_t numCases = sizeof(invalidCases) / sizeof(invalidCases[0]);
465 
466     for (size_t i = 0; i != numCases; ++i) {
467         const InvalidURITestCase testCase = invalidCases[i];
468         mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n';
469         auto cs_status = MongoURI::parse(testCase.URI);
470         ASSERT_NOT_OK(cs_status);
471     }
472 }
473 
TEST(MongoURI,ValidButBadURIsFailToConnect)474 TEST(MongoURI, ValidButBadURIsFailToConnect) {
475     // "invalid" is a TLD that cannot exit on the public internet (see rfc2606). It should always
476     // parse as a valid URI, but connecting should always fail.
477     auto sw_uri = MongoURI::parse("mongodb://user:pass@hostname.invalid:12345");
478     ASSERT_OK(sw_uri.getStatus());
479     auto uri = sw_uri.getValue();
480     ASSERT_TRUE(uri.isValid());
481 
482     std::string errmsg;
483     auto dbclient = uri.connect(mongo::StringData(), errmsg);
484     ASSERT_EQ(dbclient, static_cast<decltype(dbclient)>(nullptr));
485 }
486 
TEST(MongoURI,CloneURIForServer)487 TEST(MongoURI, CloneURIForServer) {
488     auto sw_uri = MongoURI::parse(
489         "mongodb://localhost:27017,localhost:27018,localhost:27019/admin?replicaSet=rs1&ssl=true");
490     ASSERT_OK(sw_uri.getStatus());
491 
492     auto uri = sw_uri.getValue();
493     ASSERT_EQ(uri.type(), kSet);
494     ASSERT_EQ(uri.getSetName(), "rs1");
495     ASSERT_EQ(uri.getServers().size(), static_cast<std::size_t>(3));
496 
497     auto& uriOptions = uri.getOptions();
498     ASSERT_EQ(uriOptions.at("ssl"), "true");
499 
500     auto clonedURI = uri.cloneURIForServer(mongo::HostAndPort{"localhost:27020"});
501 
502     ASSERT_EQ(clonedURI.type(), kMaster);
503     ASSERT_TRUE(clonedURI.getSetName().empty());
504     ASSERT_EQ(clonedURI.getServers().size(), static_cast<std::size_t>(1));
505     auto& clonedURIOptions = clonedURI.getOptions();
506     ASSERT_EQ(clonedURIOptions.at("ssl"), "true");
507 }
508 
509 /**
510  * These tests come from the Mongo Uri Specifications for the drivers found at:
511  * https://github.com/mongodb/specifications/tree/master/source/connection-string/tests
512  * They have been altered as the Drivers specification is somewhat different from the shell
513  * implementation.
514  */
TEST(MongoURI,specTests)515 TEST(MongoURI, specTests) {
516     const std::string files[] = {
517         "mongo-uri-valid-auth.json",
518         "mongo-uri-options.json",
519         "mongo-uri-unix-sockets-absolute.json",
520         "mongo-uri-unix-sockets-relative.json",
521         "mongo-uri-warnings.json",
522         "mongo-uri-host-identifiers.json",
523         "mongo-uri-invalid.json",
524     };
525 
526     for (const auto& file : files) {
527         const auto testBson = getBsonFromJsonFile(file);
528 
529         for (const auto& testElement : testBson) {
530             ASSERT_EQ(testElement.type(), mongo::Object);
531             const auto test = testElement.Obj();
532 
533             // First extract the valid field and the uri field
534             const auto validDoc = test.getField("valid");
535             ASSERT_FALSE(validDoc.eoo());
536             ASSERT_TRUE(validDoc.isBoolean());
537             const auto valid = validDoc.Bool();
538 
539             const auto uriDoc = test.getField("uri");
540             ASSERT_FALSE(uriDoc.eoo());
541             ASSERT_EQ(uriDoc.type(), mongo::String);
542             const auto uri = uriDoc.String();
543 
544             if (!valid) {
545                 // This uri string is invalid --> parse the uri and ensure it fails
546                 const InvalidURITestCase testCase = {uri};
547                 mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n';
548                 auto cs_status = MongoURI::parse(testCase.URI);
549                 ASSERT_NOT_OK(cs_status);
550             } else {
551                 // This uri is valid -- > parse the remaining necessary fields
552 
553                 // parse the auth options
554                 std::string database, username, password;
555 
556                 const auto auth = test.getField("auth");
557                 ASSERT_FALSE(auth.eoo());
558                 if (auth.type() != mongo::jstNULL) {
559                     ASSERT_EQ(auth.type(), mongo::Object);
560                     const auto authObj = auth.embeddedObject();
561                     database = returnStringFromElementOrNull(authObj.getField("db"));
562                     username = returnStringFromElementOrNull(authObj.getField("username"));
563                     password = returnStringFromElementOrNull(authObj.getField("password"));
564                 }
565 
566                 // parse the hosts
567                 const auto hosts = test.getField("hosts");
568                 ASSERT_FALSE(hosts.eoo());
569                 ASSERT_EQ(hosts.type(), mongo::Array);
570                 const auto numHosts = static_cast<size_t>(hosts.Obj().nFields());
571 
572                 // parse the options
573                 mongo::ConnectionString::ConnectionType connectionType = kMaster;
574                 size_t numOptions = 0;
575                 std::string setName;
576                 const auto optionsElement = test.getField("options");
577                 ASSERT_FALSE(optionsElement.eoo());
578                 if (optionsElement.type() != mongo::jstNULL) {
579                     ASSERT_EQ(optionsElement.type(), mongo::Object);
580                     const auto optionsObj = optionsElement.Obj();
581                     numOptions = optionsObj.nFields();
582                     const auto replsetElement = optionsObj.getField("replicaSet");
583                     if (!replsetElement.eoo()) {
584                         ASSERT_EQ(replsetElement.type(), mongo::String);
585                         setName = replsetElement.String();
586                         connectionType = kSet;
587                     }
588                 }
589 
590                 // Create the URITestCase abnd
591                 const URITestCase testCase = {uri,
592                                               username,
593                                               password,
594                                               connectionType,
595                                               setName,
596                                               numHosts,
597                                               numOptions,
598                                               database};
599                 testValidURIFormat(testCase);
600             }
601         }
602     }
603 }
604 
TEST(MongoURI,srvRecordTest)605 TEST(MongoURI, srvRecordTest) {
606     using namespace mongo;
607     enum Expectation : bool { success = true, failure = false };
608     const struct {
609         int lineNumber;
610         std::string uri;
611         std::string user;
612         std::string password;
613         std::string database;
614         std::vector<HostAndPort> hosts;
615         std::map<std::string, std::string> options;
616         Expectation expectation;
617     } tests[] = {
618         // Test some non-SRV URIs to make sure that they do not perform expansions
619         {__LINE__,
620          "mongodb://test1.test.build.10gen.cc:12345/",
621          "",
622          "",
623          "",
624          {{"test1.test.build.10gen.cc", 12345}},
625          {},
626          success},
627 
628         {__LINE__,
629          "mongodb://test6.test.build.10gen.cc:12345/",
630          "",
631          "",
632          "",
633          {{"test6.test.build.10gen.cc", 12345}},
634          {},
635          success},
636 
637         // Test a sample URI against each provided testing DNS entry
638         {__LINE__,
639          "mongodb+srv://test1.test.build.10gen.cc/",
640          "",
641          "",
642          "",
643          {{"localhost.test.build.10gen.cc.", 27017}, {"localhost.test.build.10gen.cc.", 27018}},
644          {{"ssl", "true"}},
645          success},
646 
647         // Test a sample URI against each provided testing DNS entry
648         {__LINE__,
649          "mongodb+srv://test1.test.build.10gen.cc/?ssl=false",
650          "",
651          "",
652          "",
653          {{"localhost.test.build.10gen.cc.", 27017}, {"localhost.test.build.10gen.cc.", 27018}},
654          {{"ssl", "false"}},
655          success},
656 
657         // Test a sample URI against the need for deep DNS relation
658         {__LINE__,
659          "mongodb+srv://test18.test.build.10gen.cc/?replicaSet=repl0",
660          "",
661          "",
662          "",
663          {
664              {"localhost.sub.test.build.10gen.cc.", 27017},
665          },
666          {
667              {"ssl", "true"}, {"replicaSet", "repl0"},
668          },
669          success},
670 
671         // Test a sample URI with FQDN against the need for deep DNS relation
672         {__LINE__,
673          "mongodb+srv://test18.test.build.10gen.cc./?replicaSet=repl0",
674          "",
675          "",
676          "",
677          {
678              {"localhost.sub.test.build.10gen.cc.", 27017},
679          },
680          {
681              {"ssl", "true"}, {"replicaSet", "repl0"},
682          },
683          success},
684 
685         {__LINE__,
686          "mongodb+srv://user:password@test2.test.build.10gen.cc/"
687          "database?someOption=someValue&someOtherOption=someOtherValue",
688          "user",
689          "password",
690          "database",
691          {{"localhost.test.build.10gen.cc.", 27018}, {"localhost.test.build.10gen.cc.", 27019}},
692          {{"someOption", "someValue"}, {"someOtherOption", "someOtherValue"}, {"ssl", "true"}},
693          success},
694 
695 
696         {__LINE__,
697          "mongodb+srv://user:password@test3.test.build.10gen.cc/"
698          "database?someOption=someValue&someOtherOption=someOtherValue",
699          "user",
700          "password",
701          "database",
702          {{"localhost.test.build.10gen.cc.", 27017}},
703          {{"someOption", "someValue"}, {"someOtherOption", "someOtherValue"}, {"ssl", "true"}},
704          success},
705 
706 
707         {__LINE__,
708          "mongodb+srv://user:password@test5.test.build.10gen.cc/"
709          "database?someOption=someValue&someOtherOption=someOtherValue",
710          "user",
711          "password",
712          "database",
713          {{"localhost.test.build.10gen.cc.", 27017}},
714          {{"someOption", "someValue"},
715           {"someOtherOption", "someOtherValue"},
716           {"replicaSet", "repl0"},
717           {"authSource", "thisDB"},
718           {"ssl", "true"}},
719          success},
720 
721         {__LINE__,
722          "mongodb+srv://user:password@test5.test.build.10gen.cc/"
723          "database?someOption=someValue&authSource=anotherDB&someOtherOption=someOtherValue",
724          "user",
725          "password",
726          "database",
727          {{"localhost.test.build.10gen.cc.", 27017}},
728          {{"someOption", "someValue"},
729           {"someOtherOption", "someOtherValue"},
730           {"replicaSet", "repl0"},
731           {"replicaSet", "repl0"},
732           {"authSource", "anotherDB"},
733           {"ssl", "true"}},
734          success},
735 
736         {__LINE__, "mongodb+srv://test6.test.build.10gen.cc/", "", "", "", {}, {}, failure},
737 
738         {__LINE__,
739          "mongodb+srv://test6.test.build.10gen.cc/database",
740          "",
741          "",
742          "database",
743          {},
744          {},
745          failure},
746 
747         {__LINE__,
748          "mongodb+srv://test6.test.build.10gen.cc/?authSource=anotherDB",
749          "",
750          "",
751          "",
752          {},
753          {},
754          failure},
755 
756         {__LINE__,
757          "mongodb+srv://test6.test.build.10gen.cc/?irrelevantOption=irrelevantValue",
758          "",
759          "",
760          "",
761          {},
762          {},
763          failure},
764 
765 
766         {__LINE__,
767          "mongodb+srv://test6.test.build.10gen.cc/"
768          "?irrelevantOption=irrelevantValue&authSource=anotherDB",
769          "",
770          "",
771          "",
772          {},
773          {},
774          failure},
775 
776         {__LINE__,
777          "mongodb+srv://test7.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
778          "",
779          "",
780          "",
781          {},
782          {},
783          failure},
784 
785         {__LINE__, "mongodb+srv://test7.test.build.10gen.cc./", "", "", "", {}, {}, failure},
786 
787         {__LINE__, "mongodb+srv://test8.test.build.10gen.cc./", "", "", "", {}, {}, failure},
788 
789         {__LINE__,
790          "mongodb+srv://test10.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
791          "",
792          "",
793          "",
794          {},
795          {},
796          failure},
797 
798         {__LINE__,
799          "mongodb+srv://test11.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
800          "",
801          "",
802          "",
803          {},
804          {},
805          failure},
806 
807         {__LINE__, "mongodb+srv://test12.test.build.10gen.cc./", "", "", "", {}, {}, failure},
808         {__LINE__, "mongodb+srv://test13.test.build.10gen.cc./", "", "", "", {}, {}, failure},
809         {__LINE__, "mongodb+srv://test14.test.build.10gen.cc./", "", "", "", {}, {}, failure},
810         {__LINE__, "mongodb+srv://test15.test.build.10gen.cc./", "", "", "", {}, {}, failure},
811         {__LINE__, "mongodb+srv://test16.test.build.10gen.cc./", "", "", "", {}, {}, failure},
812         {__LINE__, "mongodb+srv://test17.test.build.10gen.cc./", "", "", "", {}, {}, failure},
813         {__LINE__, "mongodb+srv://test19.test.build.10gen.cc./", "", "", "", {}, {}, failure},
814 
815         {__LINE__, "mongodb+srv://test12.test.build.10gen.cc/", "", "", "", {}, {}, failure},
816         {__LINE__, "mongodb+srv://test13.test.build.10gen.cc/", "", "", "", {}, {}, failure},
817         {__LINE__, "mongodb+srv://test14.test.build.10gen.cc/", "", "", "", {}, {}, failure},
818         {__LINE__, "mongodb+srv://test15.test.build.10gen.cc/", "", "", "", {}, {}, failure},
819         {__LINE__, "mongodb+srv://test16.test.build.10gen.cc/", "", "", "", {}, {}, failure},
820         {__LINE__, "mongodb+srv://test17.test.build.10gen.cc/", "", "", "", {}, {}, failure},
821         {__LINE__, "mongodb+srv://test19.test.build.10gen.cc/", "", "", "", {}, {}, failure},
822     };
823 
824     for (const auto& test : tests) {
825         auto rs = MongoURI::parse(test.uri);
826         if (test.expectation == failure) {
827             ASSERT_FALSE(rs.getStatus().isOK()) << "Failing URI: " << test.uri
828                                                 << " data on line: " << test.lineNumber;
829             continue;
830         }
831         ASSERT_OK(rs.getStatus()) << "Failed on URI: " << test.uri
832                                   << " data on line: " << test.lineNumber;
833         auto rv = rs.getValue();
834         ASSERT_EQ(rv.getUser(), test.user) << "Failed on URI: " << test.uri
835                                            << " data on line: " << test.lineNumber;
836         ASSERT_EQ(rv.getPassword(), test.password) << "Failed on URI: " << test.uri
837                                                    << " data on line : " << test.lineNumber;
838         ASSERT_EQ(rv.getDatabase(), test.database) << "Failed on URI: " << test.uri
839                                                    << " data on line : " << test.lineNumber;
840         std::vector<std::pair<std::string, std::string>> options(begin(rv.getOptions()),
841                                                                  end(rv.getOptions()));
842         std::sort(begin(options), end(options));
843         std::vector<std::pair<std::string, std::string>> expectedOptions(begin(test.options),
844                                                                          end(test.options));
845         std::sort(begin(expectedOptions), end(expectedOptions));
846 
847         for (std::size_t i = 0; i < std::min(options.size(), expectedOptions.size()); ++i) {
848             if (options[i] != expectedOptions[i]) {
849                 mongo::unittest::log() << "Option: \"" << options[i].first << "="
850                                        << options[i].second << "\" doesn't equal: \""
851                                        << expectedOptions[i].first << "="
852                                        << expectedOptions[i].second << "\""
853                                        << " data on line: " << test.lineNumber << std::endl;
854                 std::cerr << "Failing URI: \"" << test.uri << "\""
855                           << " data on line: " << test.lineNumber << std::endl;
856                 ASSERT(false);
857             }
858         }
859         ASSERT_EQ(options.size(), expectedOptions.size()) << "Failing URI: "
860                                                           << " data on line: " << test.lineNumber
861                                                           << test.uri;
862 
863         std::vector<HostAndPort> hosts(begin(rv.getServers()), end(rv.getServers()));
864         std::sort(begin(hosts), end(hosts));
865         auto expectedHosts = test.hosts;
866         std::sort(begin(expectedHosts), end(expectedHosts));
867 
868         for (std::size_t i = 0; i < std::min(hosts.size(), expectedHosts.size()); ++i) {
869             ASSERT_EQ(hosts[i], expectedHosts[i]) << "Failed on URI: " << test.uri
870                                                   << " at host number" << i
871                                                   << " data on line: " << test.lineNumber;
872         }
873         ASSERT_TRUE(hosts.size() == expectedHosts.size())
874             << "Failed on URI: " << test.uri << " Found " << hosts.size() << " hosts, expected "
875             << expectedHosts.size() << " data on line: " << test.lineNumber;
876     }
877 }
878 
879 /*
880  * Checks that redacting various secret info from URIs produces actually redacted URIs.
881  * Also checks that SRV URI's don't turn into non-SRV URIs after redaction.
882  */
TEST(MongoURI,Redact)883 TEST(MongoURI, Redact) {
884     constexpr auto goodWithDBName = "mongodb://admin@localhost/admin"_sd;
885     constexpr auto goodWithoutDBName = "mongodb://admin@localhost"_sd;
886     constexpr auto goodWithOnlyDBAndHost = "mongodb://localhost/admin"_sd;
887     const std::initializer_list<std::pair<StringData, StringData>> testCases = {
888         {"mongodb://admin:password@localhost/admin"_sd, goodWithDBName},
889         {"mongodb://admin@localhost/admin?secretConnectionOption=foo"_sd, goodWithDBName},
890         {"mongodb://admin:password@localhost/admin?secretConnectionOptions"_sd, goodWithDBName},
891         {"mongodb://admin@localhost/admin"_sd, goodWithDBName},
892         {"mongodb://admin@localhost/admin?secretConnectionOptions", goodWithDBName},
893         {"mongodb://admin:password@localhost"_sd, goodWithoutDBName},
894         {"mongodb://admin@localhost", goodWithoutDBName},
895         {"mongodb://localhost/admin?socketTimeoutMS=5", goodWithOnlyDBAndHost},
896         {"mongodb://localhost/admin", goodWithOnlyDBAndHost},
897     };
898 
899     for (const auto& testCase : testCases) {
900         ASSERT_TRUE(MongoURI::isMongoURI(testCase.first));
901         ASSERT_EQ(MongoURI::redact(testCase.first), testCase.second);
902     }
903 
904     const auto toRedactSRV = "mongodb+srv://admin:password@localhost/admin?secret=foo"_sd;
905     const auto redactedSRV = "mongodb+srv://admin@localhost/admin"_sd;
906     ASSERT_EQ(MongoURI::redact(toRedactSRV), redactedSRV);
907 }
908 
909 }  // namespace
910 }  // namespace mongo
911