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