From 880e3c102363611ef09b451737276c0ad9400d11 Mon Sep 17 00:00:00 2001 From: Tyler Kaye Date: Wed, 5 Jul 2017 17:45:41 -0400 Subject: SERVER-29923 Re-write the Mongo Server's URI parser and Testing Suite --- src/mongo/client/mongo_uri.cpp | 279 +++++++++--- src/mongo/client/mongo_uri_test.cpp | 321 +++++++++++++- .../mongo-uri-host-identifiers.json | 203 +++++++++ .../client/mongo_uri_tests/mongo-uri-invalid.json | 247 +++++++++++ .../client/mongo_uri_tests/mongo-uri-options.json | 25 ++ .../mongo-uri-unix-sockets-absolute.json | 373 +++++++++++++++++ .../mongo-uri-unix-sockets-relative.json | 466 +++++++++++++++++++++ .../mongo_uri_tests/mongo-uri-valid-auth.json | 311 ++++++++++++++ .../client/mongo_uri_tests/mongo-uri-warnings.json | 56 +++ 9 files changed, 2198 insertions(+), 83 deletions(-) create mode 100644 src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json create mode 100644 src/mongo/client/mongo_uri_tests/mongo-uri-invalid.json create mode 100644 src/mongo/client/mongo_uri_tests/mongo-uri-options.json create mode 100644 src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json create mode 100644 src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json create mode 100644 src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json create mode 100644 src/mongo/client/mongo_uri_tests/mongo-uri-warnings.json diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp index 6e6a7825b2d..0d5a9c5bd80 100644 --- a/src/mongo/client/mongo_uri.cpp +++ b/src/mongo/client/mongo_uri.cpp @@ -32,92 +32,265 @@ #include "mongo/client/mongo_uri.h" -#include +#include #include "mongo/base/status_with.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/client/dbclientinterface.h" #include "mongo/client/sasl_client_authenticate.h" +#include "mongo/db/namespace_string.h" +#include "mongo/util/hex.h" #include "mongo/util/mongoutils/str.h" -#include "mongo/util/password_digest.h" #include #include +#include #include -#include namespace mongo { namespace { -const char kMongoDBURL[] = - // scheme: non-capturing - "mongodb://" - // credentials: two inner captures for user and password - "(?:([^:]+)(?::([^@]+))?@)?" +// Helper Method for MongoURI::parse() to urlDecode the components of the URI +StatusWith urlDecode(StringData toDecode) { + std::ostringstream out; + for (size_t i = 0; i < toDecode.size(); ++i) { + if (toDecode.substr(i, 1) == "%") { + if (i + 2 > toDecode.size()) { + return Status(ErrorCodes::FailedToParse, ""); + } + out << mongo::fromHex(toDecode.substr(i + 1, 2)); + i += 2; + } else { + out << toDecode.substr(i, 1); + } + } + return out.str(); +} + +/* Helper Method for MongoURI::parse() to split a string into exactly 2 pieces by a char delimeter + */ - // servers: grabs all host:port or UNIX socket names - "((?:[^\\/]+|/.+\\.sock)(?:,(?:[^\\/]+|/.+\\.sock))*)" +/* partitionForward() splits a string by the first occurance of the character delimeter - // database and options are grouped together - "(?:/" + Params: + str: The string to be split + c: The char delimeter - // database: matches anything but the chars that cannot be part of a MongoDB database name which - // are (in order) - forward slash, back slash, dot, space, double-quote, dollar sign, asterisk, - // less than, greater than, colon, pipe, question mark. - "([^/\\\\\\.\\ \"\\$*<>:\\|\\?]*)?" + Returns: + std::tuple +*/ +std::tuple partitionForward(const StringData str, const char c) { + std::size_t delim = str.find(c); + if (delim == std::string::npos) { + return std::make_tuple(str, StringData()); + } + return std::make_tuple(str.substr(0, delim), str.substr(delim + 1)); +} - // options - "(?:\\?([^&=?]+=[^&=?]+(?:&[^&=?]+=[^&=?]+)*))?" +/* partitionBackward() splits a string by the last occurance of the character delimeter - // close db/options group - ")?"; + Params: + str: The string to be split + c: The char delimeter -} // namespace + Returns: + std::tuple +*/ +std::tuple partitionBackward(const StringData str, const char c) { + std::size_t delim = str.rfind(c); + if (delim == std::string::npos) { + return std::make_tuple(StringData(), str); + } + return std::make_tuple(str.substr(0, delim), str.substr(delim + 1)); +} +} // namespace + StatusWith MongoURI::parse(const std::string& url) { - if (!boost::algorithm::starts_with(url, "mongodb://")) { + StringData urlSD = StringData(url); + StringData prefix("mongodb://"); + if (!urlSD.startsWith(prefix)) { auto cs_status = ConnectionString::parse(url); if (!cs_status.isOK()) { return cs_status.getStatus(); } - return MongoURI(cs_status.getValue()); } - const std::regex mongoUrlRe(kMongoDBURL); - std::smatch matches; - if (!std::regex_match(url, matches, mongoUrlRe)) { - return Status(ErrorCodes::FailedToParse, - str::stream() << "Failed to parse mongodb:// URL: " << url); + + // 1. Validate and remove the scheme prefix mongodb:// + StringData uriWithoutPrefix = urlSD.substr(prefix.size()); + + // 2. Split the string by the first, unescaped / (if any), yielding: + // split[0]: User information and host identifers + // split[1]: Auth database and connection options + auto t1 = partitionForward(uriWithoutPrefix, '/'); + StringData userAndHostInfo = std::get<0>(t1); + StringData databaseAndOptions = std::get<1>(t1); + + // 2.b Make sure that there are no question marks in the left side of the / + // as any options after the ? must still have the / delimeter + if (databaseAndOptions.empty() && + userAndHostInfo.toString().find_first_of("?") != std::string::npos) { + return Status( + ErrorCodes::FailedToParse, + str::stream() + << "URI must contain slash delimeter between hosts and options for mongodb:// URL: " + << url); } - // We have the whole input plus 5 top level captures (user, password, host, db, options). - invariant(matches.size() == 6); + // 3. Split the user information and host identifiers string by the last, unescaped @, yielding: + // split[0]: User information + // split[1]: Host identifiers; + auto t2 = partitionBackward(userAndHostInfo, '@'); + StringData userInfo = std::get<0>(t2); + StringData hostIdentifiers = std::get<1>(t2); + + // 4. Validate, split (if applicable), and URL decode the user information, yielding: + // split[0] = username + // split[1] = password + auto t3 = partitionForward(userInfo, ':'); + StringData usernameSD = std::get<0>(t3); + StringData passwordSD = std::get<1>(t3); + std::size_t found = usernameSD.toString().find_first_of(":@"); + if (found != std::string::npos) { + return Status(ErrorCodes::FailedToParse, + str::stream() << "Username must be URL Encoded for mongodb:// URL: " << url); + } + found = passwordSD.toString().find_first_of(":@"); + if (found != std::string::npos) { + return Status(ErrorCodes::FailedToParse, + str::stream() << "Password must be URL Encoded for mongodb:// URL: " << url); + } - if (!matches[3].matched) { + // Get the username and make sure it did not fail to decode + auto usernameWithStatus = urlDecode(usernameSD); + if (!usernameWithStatus.isOK()) + return Status( + ErrorCodes::FailedToParse, + str::stream() << "Username cannot properly be URL decoded for mongodb:// URL: " << url); + std::string username = usernameWithStatus.getValue(); + + // Get the password and make sure it did not fail to decode + auto passwordWithStatus = urlDecode(passwordSD); + if (!passwordWithStatus.isOK()) + return Status( + ErrorCodes::FailedToParse, + str::stream() << "Password cannot properly be URL decoded for mongodb:// URL: " << url); + std::string password = passwordWithStatus.getValue(); + + // 5. Validate, split, and URL decode the host identifiers. + if (hostIdentifiers.empty()) { return Status(ErrorCodes::FailedToParse, "No server(s) specified"); } + std::string hostIdentifiersStr = hostIdentifiers.toString(); + std::vector servers; + for (auto i = boost::make_split_iterator(hostIdentifiersStr, + boost::first_finder(",", boost::is_iequal())); + i != std::remove_reference::type{}; + ++i) { + auto hostWithStatus = urlDecode(boost::copy_range(*i)); + if (!hostWithStatus.isOK()) { + return Status( + ErrorCodes::FailedToParse, + str::stream() << "Host cannot properly be URL decoded for mongodb:// URL: " << url); + } - std::map options; - - if (matches[5].matched) { - const std::string optionsMatch = matches[5].str(); + auto statusHostAndPort = HostAndPort::parse(hostWithStatus.getValue()); + if (!statusHostAndPort.isOK()) { + return statusHostAndPort.getStatus(); + } + servers.push_back(statusHostAndPort.getValue()); + } - std::vector> optionsTokens; - boost::algorithm::split(optionsTokens, optionsMatch, boost::algorithm::is_any_of("=&")); + // 6. Split the auth database and connection options string by the first, unescaped ?, yielding: + // split[0] = auth database + // split[1] = connection options + auto t4 = partitionForward(databaseAndOptions, '?'); + StringData databaseSD = std::get<0>(t4); + StringData connectionOptions = std::get<1>(t4); + auto databaseWithStatus = urlDecode(databaseSD); + if (!databaseWithStatus.isOK()) { + return Status(ErrorCodes::FailedToParse, + str::stream() + << "Database name cannot properly be URL decoded for mongodb:// URL: " + << url); + } + std::string database = databaseWithStatus.getValue(); + + // 7. Validate the database contains no prohibited characters + // Prohibited characters: + // slash ("/"), backslash ("\"), space (" "), double-quote ("""), or dollar sign ("$") + // period (".") is also prohibited, but drivers MAY allow periods + if (!database.empty() && + !NamespaceString::validDBName(database, + NamespaceString::DollarInDbNameBehavior::Disallow)) { + return Status(ErrorCodes::FailedToParse, + str::stream() + << "Database name cannot have reserved characters for mongodb:// URL: " + << url); + } - if (optionsTokens.size() % 2 != 0) { + // 8. Validate, split, and URL decode the connection options + std::map options; + if (!connectionOptions.empty()) { + std::string connectionOptionsStr = connectionOptions.toString(); + std::size_t foundQ = connectionOptionsStr.find_first_of("?"); + if (foundQ != std::string::npos) { return Status(ErrorCodes::FailedToParse, str::stream() - << "Missing a key or value in the options for mongodb:// URL: " + << "URI Cannot Contain multiple questions marks for mongodb:// URL: " << url); - ; } + for (auto i = boost::make_split_iterator(connectionOptionsStr, + boost::first_finder("&", boost::is_iequal())); + i != std::remove_reference::type{}; + ++i) { + std::string opt = boost::copy_range(*i); + if (opt.empty()) { + return Status(ErrorCodes::FailedToParse, + str::stream() + << "Missing a key/value pair in the options for mongodb:// URL: " + << url); + } + auto t5 = partitionForward(opt, '='); + StringData key = std::get<0>(t5); + StringData value = std::get<1>(t5); + if (key.empty()) { + return Status( + ErrorCodes::FailedToParse, + str::stream() + << "Missing a key for key/value pair in the options for mongodb:// URL: " + << url); + } + if (value.empty()) { + return Status( + ErrorCodes::FailedToParse, + str::stream() + << "Missing a value for key/value pair in the options for mongodb:// URL: " + << url); + } + auto keyWithStatus = urlDecode(key); + if (!keyWithStatus.isOK()) { + return Status( + ErrorCodes::FailedToParse, + str::stream() + << "Key in options cannot properly be URL decoded for mongodb:// URL: " + << url); + } + auto valueWithStatus = urlDecode(value); + if (!valueWithStatus.isOK()) { + return Status( + ErrorCodes::FailedToParse, + str::stream() + << "Value in options cannot properly be URL decoded for mongodb:// URL: " + << url); + } + - for (size_t i = 0; i != optionsTokens.size(); i = i + 2) - options[std::string(optionsTokens[i].begin(), optionsTokens[i].end())] = - std::string(optionsTokens[i + 1].begin(), optionsTokens[i + 1].end()); + options[keyWithStatus.getValue()] = valueWithStatus.getValue(); + } } OptionsMap::const_iterator optIter; @@ -129,33 +302,15 @@ StatusWith MongoURI::parse(const std::string& url) { setName = optIter->second; } - std::vector servers; - - { - std::vector servers_split; - const std::string serversStr = matches[3].str(); - boost::algorithm::split(servers_split, serversStr, boost::is_any_of(",")); - for (auto&& s : servers_split) { - auto statusHostAndPort = HostAndPort::parse(s); - if (!statusHostAndPort.isOK()) { - return statusHostAndPort.getStatus(); - } - - servers.push_back(statusHostAndPort.getValue()); - } - } - const bool direct = !haveSetName && (servers.size() == 1); if (!direct && setName.empty()) { return Status(ErrorCodes::FailedToParse, "Cannot list multiple servers in URL without 'replicaSet' option"); } - ConnectionString cs( direct ? ConnectionString::MASTER : ConnectionString::SET, servers, setName); - return MongoURI( - std::move(cs), matches[1].str(), matches[2].str(), matches[4].str(), std::move(options)); + return MongoURI(std::move(cs), username, password, database, std::move(options)); } } // namespace mongo diff --git a/src/mongo/client/mongo_uri_test.cpp b/src/mongo/client/mongo_uri_test.cpp index 6a5f6c9882a..5302ad14a9d 100644 --- a/src/mongo/client/mongo_uri_test.cpp +++ b/src/mongo/client/mongo_uri_test.cpp @@ -28,11 +28,17 @@ #include "mongo/platform/basic.h" -#include "mongo/client/mongo_uri.h" +#include #include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsontypes.h" +#include "mongo/bson/json.h" +#include "mongo/client/mongo_uri.h" #include "mongo/unittest/unittest.h" +#include + namespace { using mongo::MongoURI; @@ -60,8 +66,6 @@ const URITestCase validCases[] = { {"mongodb://user@127.0.0.1", "user", "", kMaster, "", 1, 0, ""}, - {"mongodb://127.0.0.1/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"}, - {"mongodb://localhost/?foo=bar", "", "", kMaster, "", 1, 1, ""}, {"mongodb://user:pwd@127.0.0.1:1234", "user", "pwd", kMaster, "", 1, 0, ""}, @@ -70,6 +74,65 @@ const URITestCase validCases[] = { {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"}, + {"mongodb://127.0.0.1/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"}, + + {"mongodb://user:pwd@127.0.0.1,127.0.0.2/dbname?a=b&replicaSet=replName", + "user", + "pwd", + kSet, + "replName", + 2, + 2, + "dbname"}, + + {"mongodb://needs%20encoding%25%23!%3C%3E:pwd@127.0.0.1,127.0.0.2/" + "dbname?a=b&replicaSet=replName", + "needs encoding%#!<>", + "pwd", + kSet, + "replName", + 2, + 2, + "dbname"}, + + {"mongodb://needs%20encoding%25%23!%3C%3E:pwd@127.0.0.1,127.0.0.2/" + "db@name?a=b&replicaSet=replName", + "needs encoding%#!<>", + "pwd", + kSet, + "replName", + 2, + 2, + "db@name"}, + + {"mongodb://user:needs%20encoding%25%23!%3C%3E@127.0.0.1,127.0.0.2/" + "dbname?a=b&replicaSet=replName", + "user", + "needs encoding%#!<>", + kSet, + "replName", + 2, + 2, + "dbname"}, + + {"mongodb://user:pwd@127.0.0.1,127.0.0.2/dbname?a=b&replicaSet=needs%20encoding%25%23!%3C%3E", + "user", + "pwd", + kSet, + "needs encoding%#!<>", + 2, + 2, + "dbname"}, + + {"mongodb://user:pwd@127.0.0.1,127.0.0.2/needsencoding%40hello?a=b&replicaSet=replName", + "user", + "pwd", + kSet, + "replName", + 2, + 2, + "needsencoding@hello"}, + {"mongodb://user:pwd@127.0.0.1,127.0.0.2/?replicaSet=replName", "user", "pwd", @@ -259,21 +322,27 @@ const URITestCase validCases[] = { 1, 2, ""}, - {"mongodb:///tmp/mongodb-27017.sock", "", "", kMaster, "", 1, 0, ""}, - {"mongodb:///tmp/mongodb-27017.sock,/tmp/mongodb-27018.sock/?replicaSet=replName", + {"mongodb://%2Ftmp%2Fmongodb-27017.sock", "", "", kMaster, "", 1, 0, ""}, + + {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replName", "", "", kSet, "replName", 2, 1, - ""}}; + ""}, +}; const InvalidURITestCase invalidCases[] = { // No host. {"mongodb://"}, + {"mongodb://usr:pwd@/dbname?a=b"}, + + // Username and password must be encoded (cannot have ':' or '@') + {"mongodb://usr:pwd:@127.0.0.1/dbName?foo=a&c=b"}, // Needs a "/" after the hosts and before the options. {"mongodb://localhost:27017,localhost:27018?replicaSet=missingSlash"}, @@ -284,30 +353,193 @@ const InvalidURITestCase invalidCases[] = { // Domain sockets have to end in ".sock". {"mongodb:///notareal/domainsock"}, + // Database name cannot contain slash ("/"), backslash ("\"), space (" "), double-quote ("""), + // or dollar sign ("$") + {"mongodb://usr:pwd@localhost:27017/db$name?a=b"}, + {"mongodb://usr:pwd@localhost:27017/db/name?a=b"}, + {"mongodb://usr:pwd@localhost:27017/db\\name?a=b"}, + {"mongodb://usr:pwd@localhost:27017/db name?a=b"}, + {"mongodb://usr:pwd@localhost:27017/db\"name?a=b"}, + + // Options must have a key + {"mongodb://usr:pwd@localhost:27017/dbname?=b"}, + + // Cannot skip a key value pair + {"mongodb://usr:pwd@localhost:27017/dbname?a=b&&b=c"}, + + // Multiple Unix domain sockets and auth DB resembling a socket (relative path) + {"mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin.sock?replicaSet=replName"}, + + // Multiple Unix domain sockets with auth DB resembling a path (relative path) + {"mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin.shoe?replicaSet=replName"}, + + // Multiple Unix domain sockets and auth DB resembling a socket (absolute path) + {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/" + "admin.sock?replicaSet=replName"}, + + // Multiple Unix domain sockets with auth DB resembling a path (absolute path) + {"mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/" + "admin.shoe?replicaSet=replName"}, + + // Missing value in key value pair for options + {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b&d"}, + {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b&d="}, + {"mongodb://127.0.0.1:1234/dbName?foo=a&h=&c=b&d=6"}, + {"mongodb://127.0.0.1:1234/dbName?foo=a&h&c=b&d=6"}, + // Options can't have multiple question marks. Only one. {"mongodb://localhost:27017/?foo=a?c=b&d=e?asdf=foo"}, + + // Missing a key in key value pair for options + {"mongodb://127.0.0.1:1234/dbName?foo=a&=d&c=b"}, + + // Missing an entire key-value pair + {"mongodb://127.0.0.1:1234/dbName?foo=a&&c=b"}, }; +// Helper Method to take a filename for a json file and return the array of tests inside of it +mongo::BSONObj getBsonFromJsonFile(std::string fileName) { + boost::filesystem::path directoryPath = boost::filesystem::current_path(); + boost::filesystem::path filePath(directoryPath / "src" / "mongo" / "client" / + "mongo_uri_tests" / fileName); + std::string filename(filePath.string()); + std::ifstream infile(filename.c_str()); + std::string data((std::istreambuf_iterator(infile)), std::istreambuf_iterator()); + mongo::BSONObj obj = mongo::fromjson(data); + ASSERT(obj.valid(mongo::BSONVersion::kLatest)); + ASSERT(obj.hasField("tests")); + mongo::BSONObj arr = obj.getField("tests").embeddedObject().getOwned(); + ASSERT(arr.couldBeArray()); + return arr; +} + +// Helper method to take a BSONElement and either extract its string or return an empty string +std::string returnStringFromElementOrNull(mongo::BSONElement element) { + ASSERT(!element.eoo()); + if (element.type() == mongo::jstNULL) { + return std::string(); + } + ASSERT(element.type() == mongo::String); + return element.String(); +} + +// Helper method to take a valid test case, parse() it, and assure the output is correct +void testValidURIFormat(URITestCase testCase) { + mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n'; + std::string errMsg; + auto cs_status = MongoURI::parse(testCase.URI); + if (!cs_status.getStatus().toString().empty()) { + if (!cs_status.getStatus().isOK()) + mongo::unittest::log() << "ERROR: error with uri: " << cs_status.getStatus().toString(); + } + ASSERT_TRUE(cs_status.isOK()); + auto result = cs_status.getValue(); + ASSERT_EQ(testCase.uname, result.getUser()); + ASSERT_EQ(testCase.password, result.getPassword()); + ASSERT_EQ(testCase.type, result.type()); + ASSERT_EQ(testCase.setname, result.getSetName()); + ASSERT_EQ(testCase.numservers, result.getServers().size()); + ASSERT_EQ(testCase.numOptions, result.getOptions().size()); + ASSERT_EQ(testCase.database, result.getDatabase()); +} + +// Helper method to parse a BSON array/object and extract the individual tests +// Method creates a URITestCase from every element in the array and then verifies that parse() has +// the proper output +void runTests(mongo::BSONObj tests) { + mongo::BSONObjIterator testsIter(tests); + while (testsIter.more()) { + mongo::BSONElement testElement = testsIter.next(); + if (testElement.eoo()) + break; + mongo::BSONObj test = testElement.embeddedObject(); + + // First extract the valid field and the uri field + mongo::BSONElement validDoc = test.getField("valid"); + ASSERT(!validDoc.eoo()); + ASSERT(validDoc.isBoolean()); + bool valid = validDoc.Bool(); + + mongo::BSONElement uriDoc = test.getField("uri"); + ASSERT(!uriDoc.eoo()); + ASSERT(uriDoc.type() == mongo::String); + std::string uri = uriDoc.String(); + + if (!valid) { + // This uri string is invalid --> parse the uri and ensure it fails + const InvalidURITestCase testCase = {uri}; + mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n'; + auto cs_status = MongoURI::parse(testCase.URI); + ASSERT_FALSE(cs_status.isOK()); + } else { + // This uri is valid -- > parse the remaining necessary fields + + // parse the auth options + std::string database = std::string(); + std::string username = std::string(); + std::string password = std::string(); + + mongo::BSONElement auth = test.getField("auth"); + ASSERT(!auth.eoo()); + if (auth.type() != mongo::jstNULL) { + ASSERT(auth.type() == mongo::Object); + mongo::BSONObj authObj = auth.embeddedObject(); + + mongo::BSONElement dbObj = authObj.getField("db"); + database = returnStringFromElementOrNull(dbObj); + + mongo::BSONElement usernameObj = authObj.getField("username"); + username = returnStringFromElementOrNull(usernameObj); + + mongo::BSONElement passwordObj = authObj.getField("password"); + password = returnStringFromElementOrNull(passwordObj); + } + + // parse the hosts + size_t numHosts = 0; + mongo::BSONElement hosts = test.getField("hosts"); + ASSERT(!hosts.eoo()); + ASSERT(hosts.type() == mongo::Array); + mongo::BSONObjIterator hostsIter(hosts.embeddedObject()); + while (hostsIter.more()) { + mongo::BSONElement cHost = hostsIter.next(); + if (cHost.eoo()) + break; + numHosts++; + } + + // parse the options + mongo::ConnectionString::ConnectionType connectionType = kMaster; + size_t numOptions = 0; + std::string setName = std::string(); + mongo::BSONElement optionsElement = test.getField("options"); + ASSERT(!optionsElement.eoo()); + if (optionsElement.type() != mongo::jstNULL) { + ASSERT(optionsElement.type() == mongo::Object); + mongo::BSONObj optionsObj = optionsElement.embeddedObject(); + numOptions = optionsObj.nFields(); + mongo::BSONElement replsetElement = optionsObj.getField("replicaSet"); + if (!replsetElement.eoo()) { + ASSERT(replsetElement.type() == mongo::String); + setName = replsetElement.String(); + connectionType = kSet; + } + } + + // Create the URITestCase abnd + const URITestCase testCase = { + uri, username, password, connectionType, setName, numHosts, numOptions, database}; + testValidURIFormat(testCase); + } + } +} + TEST(MongoURI, GoodTrickyURIs) { const size_t numCases = sizeof(validCases) / sizeof(validCases[0]); for (size_t i = 0; i != numCases; ++i) { const URITestCase testCase = validCases[i]; - mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n'; - std::string errMsg; - auto cs_status = MongoURI::parse(testCase.URI); - if (!cs_status.getStatus().toString().empty()) { - mongo::unittest::log() << "error with uri: " << cs_status.getStatus().toString(); - } - ASSERT_TRUE(cs_status.isOK()); - auto result = cs_status.getValue(); - ASSERT_EQ(testCase.uname, result.getUser()); - ASSERT_EQ(testCase.password, result.getPassword()); - ASSERT_EQ(testCase.type, result.type()); - ASSERT_EQ(testCase.setname, result.getSetName()); - ASSERT_EQ(testCase.numservers, result.getServers().size()); - ASSERT_EQ(testCase.numOptions, result.getOptions().size()); - ASSERT_EQ(testCase.database, result.getDatabase()); + testValidURIFormat(testCase); } } @@ -357,4 +589,51 @@ TEST(MongoURI, CloneURIForServer) { ASSERT_EQ(clonedURIOptions.at("ssl"), "true"); } +/* These tests come from the Mongo Uri Specifications for the drivers found at: + https://github.com/mongodb/specifications/tree/master/source/connection-string/tests + They have been slighly altered as the Drivers specification is slighly different from the server + specification +*/ +TEST(MongoURI, ValidAuth) { + std::string fileName = "mongo-uri-valid-auth.json"; + mongo::BSONObj tests = getBsonFromJsonFile(fileName); + runTests(tests); +} + +TEST(MongoURI, Options) { + std::string fileName = "mongo-uri-options.json"; + mongo::BSONObj tests = getBsonFromJsonFile(fileName); + runTests(tests); +} + +TEST(MongoURI, UnixSocketsAbsolute) { + std::string fileName = "mongo-uri-unix-sockets-absolute.json"; + mongo::BSONObj tests = getBsonFromJsonFile(fileName); + runTests(tests); +} + +TEST(MongoURI, UnixSocketsRelative) { + std::string fileName = "mongo-uri-unix-sockets-relative.json"; + mongo::BSONObj tests = getBsonFromJsonFile(fileName); + runTests(tests); +} + +TEST(MongoURI, Warnings) { + std::string fileName = "mongo-uri-warnings.json"; + mongo::BSONObj tests = getBsonFromJsonFile(fileName); + runTests(tests); +} + +TEST(MongoURI, HostIdentifiers) { + std::string fileName = "mongo-uri-host-identifiers.json"; + mongo::BSONObj tests = getBsonFromJsonFile(fileName); + runTests(tests); +} + +TEST(MongoURI, Invalid) { + std::string fileName = "mongo-uri-invalid.json"; + mongo::BSONObj tests = getBsonFromJsonFile(fileName); + runTests(tests); +} + } // namespace diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json b/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json new file mode 100644 index 00000000000..8a1fda580a8 --- /dev/null +++ b/src/mongo/client/mongo_uri_tests/mongo-uri-host-identifiers.json @@ -0,0 +1,203 @@ +{ + "tests": [ + { + "description": "Single IPv4 host without port", + "uri": "mongodb://127.0.0.1", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "ipv4", + "host": "127.0.0.1", + "port": null + } + ], + "auth": null, + "options": null + }, + { + "description": "Single IPv4 host with port", + "uri": "mongodb://127.0.0.1:27018", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "ipv4", + "host": "127.0.0.1", + "port": 27018 + } + ], + "auth": null, + "options": null + }, + { + "description": "Single IP literal host without port", + "uri": "mongodb://[::1]", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "ip_literal", + "host": "::1", + "port": null + } + ], + "auth": null, + "options": null + }, + { + "description": "Single IP literal host with port", + "uri": "mongodb://[::1]:27019", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "ip_literal", + "host": "::1", + "port": 27019 + } + ], + "auth": null, + "options": null + }, + { + "description": "Single hostname without port", + "uri": "mongodb://example.com", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": null + } + ], + "auth": null, + "options": null + }, + { + "description": "Single hostname with port", + "uri": "mongodb://example.com:27020", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": 27020 + } + ], + "auth": null, + "options": null + }, + { + "description": "Single hostname (resembling IPv4) without port", + "uri": "mongodb://256.0.0.1", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "256.0.0.1", + "port": null + } + ], + "auth": null, + "options": null + }, + { + "description": "Multiple hosts (mixed formats)", + "uri": "mongodb://127.0.0.1,[::1]:27018,example.com:27019", + "valid": false, + "warning": false, + "hosts": [ + { + "type": "ipv4", + "host": "127.0.0.1", + "port": null + }, + { + "type": "ip_literal", + "host": "::1", + "port": 27018 + }, + { + "type": "hostname", + "host": "example.com", + "port": 27019 + } + ], + "auth": null, + "options": null + }, + { + "description": "Multiple hosts (mixed formats)", + "uri": "mongodb://127.0.0.1,[::1]:27018,example.com:27019/?replicaSet=replset", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "ipv4", + "host": "127.0.0.1", + "port": null + }, + { + "type": "ip_literal", + "host": "::1", + "port": 27018 + }, + { + "type": "hostname", + "host": "example.com", + "port": 27019 + } + ], + "auth": null, + "options": { + "replicaSet": "replset" + } + }, + { + "description": "UTF-8 hosts", + "uri": "mongodb://bücher.example.com,umläut.example.com/", + "valid": false, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "bücher.example.com", + "port": null + }, + { + "type": "hostname", + "host": "umläut.example.com", + "port": null + } + ], + "auth": null, + "options": null + }, + { + "description": "UTF-8 hosts", + "uri": "mongodb://bücher.example.com,umläut.example.com/?replicaSet=replset", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "bücher.example.com", + "port": null + }, + { + "type": "hostname", + "host": "umläut.example.com", + "port": null + } + ], + "auth": null, + "options": { + "replicaSet": "replset" + } + } + ] +} \ No newline at end of file diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-invalid.json b/src/mongo/client/mongo_uri_tests/mongo-uri-invalid.json new file mode 100644 index 00000000000..3ee82bd535e --- /dev/null +++ b/src/mongo/client/mongo_uri_tests/mongo-uri-invalid.json @@ -0,0 +1,247 @@ +{ + "tests": [ + { + "auth": null, + "description": "Empty string", + "hosts": null, + "options": null, + "uri": "", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Missing host", + "hosts": null, + "options": null, + "uri": "mongodb://", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Double colon in host identifier", + "hosts": null, + "options": null, + "uri": "mongodb://localhost::27017", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Double colon in host identifier and trailing slash", + "hosts": null, + "options": null, + "uri": "mongodb://localhost::27017/", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Double colon in host identifier with missing host and port", + "hosts": null, + "options": null, + "uri": "mongodb://::", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Double colon in host identifier with missing port", + "hosts": null, + "options": null, + "uri": "mongodb://localhost,localhost::", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Double colon in host identifier and second host", + "hosts": null, + "options": null, + "uri": "mongodb://localhost::27017,abc", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (negative number) with hostname", + "hosts": null, + "options": null, + "uri": "mongodb://localhost:-1", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (zero) with hostname", + "hosts": null, + "options": null, + "uri": "mongodb://localhost:0/", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (positive number) with hostname", + "hosts": null, + "options": null, + "uri": "mongodb://localhost:65536", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (positive number) with hostname and trailing slash", + "hosts": null, + "options": null, + "uri": "mongodb://localhost:65536/", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (non-numeric string) with hostname", + "hosts": null, + "options": null, + "uri": "mongodb://localhost:foo", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (negative number) with IP literal", + "hosts": null, + "options": null, + "uri": "mongodb://[::1]:-1", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (zero) with IP literal", + "hosts": null, + "options": null, + "uri": "mongodb://[::1]:0/", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (positive number) with IP literal", + "hosts": null, + "options": null, + "uri": "mongodb://[::1]:65536", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (positive number) with IP literal and trailing slash", + "hosts": null, + "options": null, + "uri": "mongodb://[::1]:65536/", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Invalid port (non-numeric string) with IP literal", + "hosts": null, + "options": null, + "uri": "mongodb://[::1]:foo", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Missing delimiting slash between hosts and options", + "hosts": null, + "options": null, + "uri": "mongodb://example.com?w=1", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Incomplete key value pair for option", + "hosts": null, + "options": null, + "uri": "mongodb://example.com/?w", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Username with password containing an unescaped colon", + "hosts": null, + "options": null, + "uri": "mongodb://alice:foo:bar@127.0.0.1", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Username with password containing an unescaped colon", + "hosts": null, + "options": null, + "uri": "mongodb://alice:foo:bar@127.0.0.1", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Username containing an unescaped at-sign", + "hosts": null, + "options": null, + "uri": "mongodb://alice@@127.0.0.1", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Username with password containing an unescaped at-sign", + "hosts": null, + "options": null, + "uri": "mongodb://alice@foo:bar@127.0.0.1", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Username containing an unescaped slash", + "hosts": null, + "options": null, + "uri": "mongodb://alice/@localhost/db", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Username containing unescaped slash with password", + "hosts": null, + "options": null, + "uri": "mongodb://alice/bob:foo@localhost/db", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Username with password containing an unescaped slash", + "hosts": null, + "options": null, + "uri": "mongodb://alice:foo/bar@localhost/db", + "valid": false, + "warning": null + }, + { + "auth": null, + "description": "Host with unescaped slash", + "hosts": null, + "options": null, + "uri": "mongodb:///tmp/mongodb-27017.sock/", + "valid": false, + "warning": null + } + ] +} diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-options.json b/src/mongo/client/mongo_uri_tests/mongo-uri-options.json new file mode 100644 index 00000000000..4c2bded9e72 --- /dev/null +++ b/src/mongo/client/mongo_uri_tests/mongo-uri-options.json @@ -0,0 +1,25 @@ +{ + "tests": [ + { + "description": "Option names are normalized to lowercase", + "uri": "mongodb://alice:secret@example.com/admin?AUTHMechanism=MONGODB-CR", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": null + } + ], + "auth": { + "username": "alice", + "password": "secret", + "db": "admin" + }, + "options": { + "authmechanism": "MONGODB-CR" + } + } + ] +} diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json new file mode 100644 index 00000000000..d167c315bbc --- /dev/null +++ b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-absolute.json @@ -0,0 +1,373 @@ +{ + "tests": [ + { + "auth": null, + "description": "Unix domain socket (absolute path with trailing slash)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock/", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Unix domain socket (absolute path without trailing slash)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Unix domain socket (absolute path with spaces in path)", + "hosts": [ + { + "host": "/tmp/ /mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://%2Ftmp%2F %2Fmongodb-27017.sock", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Multiple Unix domain sockets (absolute paths)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock", + "valid": false, + "warning": false + }, + { + "auth": null, + "description": "Multiple hosts (absolute path and ipv4)", + "hosts": [ + { + "host": "127.0.0.1", + "port": 27017, + "type": "ipv4" + }, + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://127.0.0.1:27017,%2Ftmp%2Fmongodb-27017.sock", + "valid": false, + "warning": false + }, + { + "auth": null, + "description": "Multiple hosts (absolute path and hostname resembling relative path)", + "hosts": [ + { + "host": "mongodb-27017.sock", + "port": null, + "type": "hostname" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://mongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock", + "valid": false, + "warning": false + }, + { + "auth": null, + "description": "Multiple Unix domain sockets (absolute paths)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Multiple hosts (absolute path and ipv4)", + "hosts": [ + { + "host": "127.0.0.1", + "port": 27017, + "type": "ipv4" + }, + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://127.0.0.1:27017,%2Ftmp%2Fmongodb-27017.sock/?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Multiple hosts (absolute path and hostname resembling relative path)", + "hosts": [ + { + "host": "mongodb-27017.sock", + "port": null, + "type": "hostname" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://mongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "foo", + "username": "alice" + }, + "description": "Unix domain socket with auth database (absolute path)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://alice:foo@%2Ftmp%2Fmongodb-27017.sock/admin", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Unix domain socket with path resembling socket file (absolute path with trailing slash)", + "hosts": [ + { + "host": "/tmp/path.to.sock/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock/", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Unix domain socket with path resembling socket file (absolute path without trailing slash)", + "hosts": [ + { + "host": "/tmp/path.to.sock/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "Unix domain socket with path resembling socket file and auth (absolute path)", + "hosts": [ + { + "host": "/tmp/path.to.sock/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://bob:bar@%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock/admin", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": null, + "username": null + }, + "description": "Multiple Unix domain sockets and auth DB (absolute path)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin", + "valid": false, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": null, + "username": null + }, + "description": "Multiple Unix domain sockets and auth DB (absolute path)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "Multiple Unix domain sockets with auth and query string (absolute path)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "w": 1 + }, + "uri": "mongodb://bob:bar@%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?w=1", + "valid": false, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "Multiple Unix domain sockets with auth and query string (absolute path)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "w": 1, + "replicaSet":"replset" + }, + "uri": "mongodb://bob:bar@%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?w=1&replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "Multiple Unix domain sockets with auth and query string (absolute path)", + "hosts": [ + { + "host": "/tmp/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "w": 1, + "replicaSet":"replset" + }, + "uri": "mongodb://bob:bar@%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?replicaSet=replset&w=1", + "valid": true, + "warning": false + } + ] +} diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json new file mode 100644 index 00000000000..8da45c4e1b9 --- /dev/null +++ b/src/mongo/client/mongo_uri_tests/mongo-uri-unix-sockets-relative.json @@ -0,0 +1,466 @@ +{ + "tests": [ + { + "auth": null, + "description": "Unix domain socket (relative path with trailing slash)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2Fmongodb-27017.sock/", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Unix domain socket (relative path without trailing slash)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2Fmongodb-27017.sock", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Unix domain socket (relative path with spaces)", + "hosts": [ + { + "host": "rel/ /mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2F %2Fmongodb-27017.sock", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Multiple Unix domain sockets (relative paths)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock", + "valid": false, + "warning": false + }, + { + "auth": null, + "description": "Multiple Unix domain sockets (relative paths)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Multiple Unix domain sockets (relative and absolute paths)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock", + "valid": false, + "warning": false + }, + { + "auth": null, + "description": "Multiple Unix domain sockets (relative and absolute paths)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "/tmp/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://rel%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Multiple hosts (relative path and ipv4)", + "hosts": [ + { + "host": "127.0.0.1", + "port": 27017, + "type": "ipv4" + }, + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://127.0.0.1:27017,rel%2Fmongodb-27017.sock", + "valid": false, + "warning": false + }, + { + "auth": null, + "description": "Multiple hosts (relative path and ipv4)", + "hosts": [ + { + "host": "127.0.0.1", + "port": 27017, + "type": "ipv4" + }, + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://127.0.0.1:27017,rel%2Fmongodb-27017.sock/?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Multiple hosts (relative path and hostname resembling relative path)", + "hosts": [ + { + "host": "mongodb-27017.sock", + "port": null, + "type": "hostname" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://mongodb-27017.sock,rel%2Fmongodb-27018.sock", + "valid": false, + "warning": false + }, + { + "auth": null, + "description": "Multiple hosts (relative path and hostname resembling relative path)", + "hosts": [ + { + "host": "mongodb-27017.sock", + "port": null, + "type": "hostname" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://mongodb-27017.sock,rel%2Fmongodb-27018.sock/?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "foo", + "username": "alice" + }, + "description": "Unix domain socket with auth database (relative path)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://alice:foo@rel%2Fmongodb-27017.sock/admin", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Unix domain socket with path resembling socket file (relative path with trailing slash)", + "hosts": [ + { + "host": "rel/path.to.sock/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2Fpath.to.sock%2Fmongodb-27017.sock/", + "valid": true, + "warning": false + }, + { + "auth": null, + "description": "Unix domain socket with path resembling socket file (relative path without trailing slash)", + "hosts": [ + { + "host": "rel/path.to.sock/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2Fpath.to.sock%2Fmongodb-27017.sock", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "Unix domain socket with path resembling socket file and auth (relative path)", + "hosts": [ + { + "host": "rel/path.to.sock/mongodb-27017.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://bob:bar@rel%2Fpath.to.sock%2Fmongodb-27017.sock/admin", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": null, + "username": null + }, + "description": "Multiple Unix domain sockets and auth DB resembling a socket (relative path)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin", + "valid": false, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": null, + "username": null + }, + "description": "Multiple Unix domain sockets and auth DB resembling a socket (relative path)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": null, + "username": null + }, + "description": "Multiple Unix domain sockets with auth DB resembling a path (relative path)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": null, + "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin", + "valid": false, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": null, + "username": null + }, + "description": "Multiple Unix domain sockets with auth DB resembling a path (relative path)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "replicaSet": "replset" + }, + "uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "Multiple Unix domain sockets with auth and query string (relative path)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "w": 1 + }, + "uri": "mongodb://bob:bar@rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?w=1", + "valid": false, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "Multiple Unix domain sockets with auth and query string (relative path)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "w": 1, + "replicaSet": "replset" + }, + "uri": "mongodb://bob:bar@rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?w=1&replicaSet=replset", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "Multiple Unix domain sockets with auth and query string (relative path)", + "hosts": [ + { + "host": "rel/mongodb-27017.sock", + "port": null, + "type": "unix" + }, + { + "host": "rel/mongodb-27018.sock", + "port": null, + "type": "unix" + } + ], + "options": { + "w": 1, + "replicaSet": "replset", + "b": 4 + }, + "uri": "mongodb://bob:bar@rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?w=1&replicaSet=replset&b=4", + "valid": true, + "warning": false + } + ] +} diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json b/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json new file mode 100644 index 00000000000..a44236e7b62 --- /dev/null +++ b/src/mongo/client/mongo_uri_tests/mongo-uri-valid-auth.json @@ -0,0 +1,311 @@ +{ + "tests": [ + { + "auth": { + "db": null, + "password": "foo", + "username": "alice" + }, + "description": "User info for single IPv4 host without database", + "hosts": [ + { + "host": "127.0.0.1", + "port": null, + "type": "ipv4" + } + ], + "options": null, + "uri": "mongodb://alice:foo@127.0.0.1", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "test", + "password": "foo", + "username": "alice" + }, + "description": "User info for single IPv4 host with database", + "hosts": [ + { + "host": "127.0.0.1", + "port": null, + "type": "ipv4" + } + ], + "options": null, + "uri": "mongodb://alice:foo@127.0.0.1/test", + "valid": true, + "warning": false + }, + { + "auth": { + "db": null, + "password": "bar", + "username": "bob" + }, + "description": "User info for single IP literal host without database", + "hosts": [ + { + "host": "::1", + "port": 27018, + "type": "ip_literal" + } + ], + "options": null, + "uri": "mongodb://bob:bar@[::1]:27018", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "bar", + "username": "bob" + }, + "description": "User info for single IP literal host with database", + "hosts": [ + { + "host": "::1", + "port": 27018, + "type": "ip_literal" + } + ], + "options": null, + "uri": "mongodb://bob:bar@[::1]:27018/admin", + "valid": true, + "warning": false + }, + { + "auth": { + "db": null, + "password": "baz", + "username": "eve" + }, + "description": "User info for single hostname without database", + "hosts": [ + { + "host": "example.com", + "port": null, + "type": "hostname" + } + ], + "options": null, + "uri": "mongodb://eve:baz@example.com", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "db2", + "password": "baz", + "username": "eve" + }, + "description": "User info for single hostname with database", + "hosts": [ + { + "host": "example.com", + "port": null, + "type": "hostname" + } + ], + "options": null, + "uri": "mongodb://eve:baz@example.com/db2", + "valid": true, + "warning": false + }, + { + "auth": { + "db": null, + "password": "secret", + "username": "alice" + }, + "description": "User info for multiple hosts without database", + "hosts": [ + { + "host": "127.0.0.1", + "port": null, + "type": "ipv4" + }, + { + "host": "example.com", + "port": 27018, + "type": "hostname" + } + ], + "options": null, + "uri": "mongodb://alice:secret@127.0.0.1,example.com:27018", + "valid": false, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "secret", + "username": "alice" + }, + "description": "User info for multiple hosts with database", + "hosts": [ + { + "host": "example.com", + "port": null, + "type": "hostname" + }, + { + "host": "::1", + "port": 27019, + "type": "ip_literal" + } + ], + "options": null, + "uri": "mongodb://alice:secret@example.com,[::1]:27019/admin", + "valid": false, + "warning": false + }, + { + "auth": { + "db": null, + "password": null, + "username": "alice" + }, + "description": "Username without password", + "hosts": [ + { + "host": "127.0.0.1", + "port": null, + "type": "ipv4" + } + ], + "options": null, + "uri": "mongodb://alice@127.0.0.1", + "valid": true, + "warning": false + }, + { + "auth": { + "db": null, + "password": "", + "username": "alice" + }, + "description": "Username with empty password", + "hosts": [ + { + "host": "127.0.0.1", + "port": null, + "type": "ipv4" + } + ], + "options": null, + "uri": "mongodb://alice:@127.0.0.1", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "my=db", + "password": null, + "username": "@l:ce/=" + }, + "description": "Escaped username and database without password", + "hosts": [ + { + "host": "example.com", + "port": null, + "type": "hostname" + } + ], + "options": null, + "uri": "mongodb://%40l%3Ace%2F%3D@example.com/my%3Ddb", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin=", + "password": "fizzb@zz=", + "username": "$am" + }, + "description": "Escaped user info and database (MONGODB-CR)", + "hosts": [ + { + "host": "127.0.0.1", + "port": null, + "type": "ipv4" + } + ], + "options": { + "authmechanism": "MONGODB-CR" + }, + "uri": "mongodb://%24am:fizzb%40zz%3D@127.0.0.1/admin%3D?authMechanism=MONGODB-CR", + "valid": true, + "warning": false + }, + { + "auth": { + "db": null, + "password": null, + "username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry" + }, + "description": "Escaped username (MONGODB-X509)", + "hosts": [ + { + "host": "localhost", + "port": null, + "type": "hostname" + } + ], + "options": { + "authmechanism": "MONGODB-X509" + }, + "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509", + "valid": true, + "warning": false + }, + { + "auth": { + "db": null, + "password": "secret", + "username": "user@EXAMPLE.COM" + }, + "description": "Escaped username (GSSAPI)", + "hosts": [ + { + "host": "localhost", + "port": null, + "type": "hostname" + } + ], + "options": { + "authmechanism": "GSSAPI", + "authmechanismproperties": { + "CANONICALIZE_HOST_NAME": true, + "SERVICE_NAME": "other" + } + }, + "uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI", + "valid": true, + "warning": false + }, + { + "auth": { + "db": "admin", + "password": "secret", + "username": "alice" + }, + "description": "At-signs in options aren't part of the userinfo", + "hosts": [ + { + "host": "example.com", + "port": null, + "type": "hostname" + } + ], + "options": { + "replicaSet": "my@replicaset" + }, + "uri": "mongodb://alice:secret@example.com/admin?replicaSet=my@replicaset", + "valid": true, + "warning": false + } + ] +} diff --git a/src/mongo/client/mongo_uri_tests/mongo-uri-warnings.json b/src/mongo/client/mongo_uri_tests/mongo-uri-warnings.json new file mode 100644 index 00000000000..a04246847ab --- /dev/null +++ b/src/mongo/client/mongo_uri_tests/mongo-uri-warnings.json @@ -0,0 +1,56 @@ +{ + "tests": [ + { + "description": "Unrecognized option keys are ignored", + "uri": "mongodb://example.com/?foo=bar", + "valid": true, + "warning": true, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": null + } + ], + "auth": null, + "options": { + "foo": "bar" + } + }, + { + "description": "Unsupported option values are ignored", + "uri": "mongodb://example.com/?fsync=ifPossible", + "valid": true, + "warning": true, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": null + } + ], + "auth": null, + "options": { + "fsync": "ifPossible" + } + }, + { + "description": "Deprecated (or unknown) options are ignored if replacement exists", + "uri": "mongodb://example.com/?wtimeout=5&wtimeoutMS=10", + "valid": true, + "warning": true, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": null + } + ], + "auth": null, + "options": { + "wtimeoutMS": 10, + "wtimeout": 5 + } + } + ] +} \ No newline at end of file -- cgit v1.2.1