diff options
author | Jonathan Reams <jbreams@mongodb.com> | 2015-09-25 16:19:01 -0400 |
---|---|---|
committer | Jonathan Reams <jbreams@mongodb.com> | 2015-10-13 21:26:12 -0400 |
commit | 53c52c43a99d14a9e6c47bcc1ca1e7a2fa044ef0 (patch) | |
tree | 7fbc2ff93c5ac378c14edaa8e49173c6786af86e /src/mongo/client | |
parent | 90cd064713dfcf5c82be07742f7377c83ea0a4f2 (diff) | |
download | mongo-53c52c43a99d14a9e6c47bcc1ca1e7a2fa044ef0.tar.gz |
SERVER-6233 Add URI parsing to mongo shell
Diffstat (limited to 'src/mongo/client')
-rw-r--r-- | src/mongo/client/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/client/connection_string.cpp | 30 | ||||
-rw-r--r-- | src/mongo/client/connection_string.h | 13 | ||||
-rw-r--r-- | src/mongo/client/mongo_uri.cpp | 154 | ||||
-rw-r--r-- | src/mongo/client/mongo_uri.h | 133 | ||||
-rw-r--r-- | src/mongo/client/mongo_uri_connect.cpp | 172 | ||||
-rw-r--r-- | src/mongo/client/mongo_uri_test.cpp | 314 |
7 files changed, 805 insertions, 14 deletions
diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript index 63862a8f71c..2e68512e5ce 100644 --- a/src/mongo/client/SConscript +++ b/src/mongo/client/SConscript @@ -8,6 +8,7 @@ env.Library( target='connection_string', source=[ 'connection_string.cpp', + 'mongo_uri.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/util/net/hostandport', @@ -18,6 +19,7 @@ env.CppUnitTest( target='connection_string_test', source=[ 'connection_string_test.cpp', + 'mongo_uri_test.cpp', ], LIBDEPS=[ 'connection_string', @@ -120,6 +122,7 @@ env.Library( target='clientdriver', source=[ 'connection_string_connect.cpp', + 'mongo_uri_connect.cpp', 'connpool.cpp', 'dbclient.cpp', 'dbclient_rs.cpp', diff --git a/src/mongo/client/connection_string.cpp b/src/mongo/client/connection_string.cpp index b2802343646..69b3f952039 100644 --- a/src/mongo/client/connection_string.cpp +++ b/src/mongo/client/connection_string.cpp @@ -53,19 +53,13 @@ ConnectionString::ConnectionString(ConnectionType type, _type = type; _setName = setName; _fillServers(s); + _finishInit(); +} - switch (_type) { - case MASTER: - verify(_servers.size() == 1); - break; - case SET: - verify(_setName.size()); - verify(_servers.size() >= 1); // 1 is ok since we can derive - break; - default: - verify(_servers.size() > 0); - } - +ConnectionString::ConnectionString(ConnectionType type, + std::vector<HostAndPort> servers, + const std::string& setName) + : _type(type), _servers(std::move(servers)), _setName(setName) { _finishInit(); } @@ -118,6 +112,18 @@ void ConnectionString::_fillServers(std::string s) { } void ConnectionString::_finishInit() { + switch (_type) { + case MASTER: + verify(_servers.size() == 1); + break; + case SET: + verify(_setName.size()); + verify(_servers.size() >= 1); // 1 is ok since we can derive + break; + default: + verify(_servers.size() > 0); + } + // Needed here as well b/c the parsing logic isn't used in all constructors // TODO: Refactor so that the parsing logic *is* used in all constructors if (_type == MASTER && _servers.size() > 0) { diff --git a/src/mongo/client/connection_string.h b/src/mongo/client/connection_string.h index 1fed4868fa5..83e20906ed3 100644 --- a/src/mongo/client/connection_string.h +++ b/src/mongo/client/connection_string.h @@ -31,6 +31,7 @@ #include <string> #include <vector> +#include "mongo/base/status_with.h" #include "mongo/base/string_data.h" #include "mongo/stdx/mutex.h" #include "mongo/util/assert_util.h" @@ -39,8 +40,6 @@ namespace mongo { class DBClientBase; -template <typename T> -class StatusWith; /** * ConnectionString handles parsing different ways to connect to mongo and determining method @@ -75,8 +74,18 @@ public: */ explicit ConnectionString(const HostAndPort& server); + /** + * Creates a connection string from an unparsed list of servers, type, and setName. + */ ConnectionString(ConnectionType type, const std::string& s, const std::string& setName); + /** + * Creates a connection string from a pre-parsed list of servers, type, and setName. + */ + ConnectionString(ConnectionType type, + std::vector<HostAndPort> servers, + const std::string& setName); + ConnectionString(const std::string& s, ConnectionType favoredMultipleType); bool isValid() const { diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp new file mode 100644 index 00000000000..443a35f9858 --- /dev/null +++ b/src/mongo/client/mongo_uri.cpp @@ -0,0 +1,154 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork + +#include "mongo/platform/basic.h" + +#include "mongo/client/mongo_uri.h" + +#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/util/mongoutils/str.h" +#include "mongo/util/password_digest.h" + +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/regex.hpp> + +namespace mongo { + +namespace { +const char kMongoDBURL[] = + // scheme: non-capturing + "mongodb://" + + // credentials: two inner captures for user and password + "(?:([^:]+)(?::([^@]+))?@)?" + + // servers: grabs all host:port or UNIX socket names + "((?:(?:[^\\/]+|/.+.sock?),?)+)" + + // database: matches anything but the chars that cannot + // be part of a MongoDB database name. + "(?:/([^/\\.\\ \"*<>:\\|\\?]*))?" + + // options + "(?:\\?(?:(.+=.+)&?)+)*"; + +} // namespace + + +StatusWith<MongoURI> MongoURI::parse(const std::string& url) { + if (!boost::algorithm::starts_with(url, "mongodb://")) { + auto cs_status = ConnectionString::parse(url); + if (!cs_status.isOK()) { + return cs_status.getStatus(); + } + + return MongoURI(cs_status.getValue()); + } + + const boost::regex mongoUrlRe(kMongoDBURL); + + boost::smatch matches; + if (!boost::regex_match(url, matches, mongoUrlRe)) { + return Status(ErrorCodes::FailedToParse, + str::stream() << "Failed to parse mongodb:// URL: " << url); + } + + // We have 5 top level captures, plus the whole input. + invariant(matches.size() == 6); + + if (!matches[3].matched) { + return Status(ErrorCodes::FailedToParse, "No server(s) specified"); + } + + std::map<std::string, std::string> options; + + if (matches[5].matched) { + const std::string optionsMatch = matches[5].str(); + + std::vector<boost::iterator_range<std::string::const_iterator>> optionsTokens; + boost::algorithm::split(optionsTokens, optionsMatch, boost::algorithm::is_any_of("=&")); + + if (optionsTokens.size() % 2 != 0) { + return Status(ErrorCodes::FailedToParse, + str::stream() + << "Missing a key or value in the options 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()); + } + + std::map<std::string, std::string>::const_iterator optIter; + + // If a replica set option was specified, store it in the 'setName' field. + bool haveSetName; + std::string setName; + if ((haveSetName = ((optIter = options.find("replicaSet")) != options.end()))) + setName = optIter->second; + + // Add all remaining options into the bson object + BSONObjBuilder optionsBob; + for (optIter = options.begin(); optIter != options.end(); ++optIter) + optionsBob.append(optIter->first, optIter->second); + + std::string servers_str = matches[3].str(); + std::vector<HostAndPort> servers; + std::vector<std::string> servers_split; + boost::algorithm::split(servers_split, servers_str, boost::is_any_of(",")); + for (auto&& s : servers_split) { + try { + servers.push_back(HostAndPort(s)); + } catch (std::exception e) { + return Status(ErrorCodes::FailedToParse, e.what()); + } + } + + 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(cs, matches[1].str(), matches[2].str(), matches[4].str(), optionsBob.obj()); +} + +} // namespace mongo diff --git a/src/mongo/client/mongo_uri.h b/src/mongo/client/mongo_uri.h new file mode 100644 index 00000000000..bc051f24e5d --- /dev/null +++ b/src/mongo/client/mongo_uri.h @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> +#include <vector> + +#include "mongo/base/status_with.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/client/connection_string.h" +#include "mongo/stdx/mutex.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/net/hostandport.h" + +namespace mongo { + +/** + * MongoURI handles parsing of URIs for mongodb, and falls back to old-style + * ConnectionString parsing. It's used primarily by the shell. + * It parses URIs with the following format: + * + * mongodb://[usr:pwd@]host1[:port1]...[,hostN[:portN]]][/[db][?options]] + * + * For a complete list of URI string options, see + * https://wiki.mongodb.com/display/DH/Connection+String+Format + * + * Examples: + * + * A replica set with three members (one running on default port 27017): + * string uri = mongodb://localhost,localhost:27018,localhost:27019 + * + * Authenticated connection to db 'bedrock' with user 'barney' and pwd 'rubble': + * string url = mongodb://barney:rubble@localhost/bedrock + * + * Use parse() to parse the url, then validate and connect: + * string errmsg; + * ConnectionString cs = ConnectionString::parse( url, errmsg ); + * if ( ! cs.isValid() ) throw "bad connection string: " + errmsg; + * DBClientBase * conn = cs.connect( errmsg ); + */ +class MongoURI { +public: + static StatusWith<MongoURI> parse(const std::string& url); + + DBClientBase* connect(std::string& errmsg, double socketTimeout = 0) const; + + const std::string& getUser() const { + return _user; + } + + const std::string& getPassword() const { + return _password; + } + + const BSONObj& getOptions() const { + return _options; + } + + const std::string& getDatabase() const { + return _database; + } + + bool isValid() const { + return _connectString.isValid(); + } + + const std::string& toString() const { + return _connectString.toString(); + } + + const std::string& getSetName() const { + return _connectString.getSetName(); + } + + const std::vector<HostAndPort>& getServers() const { + return _connectString.getServers(); + } + + ConnectionString::ConnectionType type() const { + return _connectString.type(); + } + +private: + explicit MongoURI(const ConnectionString cs) + : _connectString(std::move(cs)), _user(), _password(), _database(), _options(){}; + + MongoURI(ConnectionString _connectString, + const std::string& user, + const std::string& password, + const std::string& database, + const BSONObj& options) + : _connectString(std::move(_connectString)), + _user(user), + _password(password), + _database(database), + _options(options){}; + + BSONObj _makeAuthObjFromOptions(int maxWireVersion) const; + + ConnectionString _connectString; + std::string _user; + std::string _password; + std::string _database; + BSONObj _options; +}; +} // namespace mongo diff --git a/src/mongo/client/mongo_uri_connect.cpp b/src/mongo/client/mongo_uri_connect.cpp new file mode 100644 index 00000000000..aaee6d2bcc2 --- /dev/null +++ b/src/mongo/client/mongo_uri_connect.cpp @@ -0,0 +1,172 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork + +#include "mongo/platform/basic.h" + +#include "mongo/client/mongo_uri.h" + +#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/util/mongoutils/str.h" +#include "mongo/util/password_digest.h" + +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/split.hpp> + +#include <iterator> + +namespace mongo { + +namespace { +const char kAuthMechanismPropertiesKey[] = "mechanism_properties"; + +// CANONICALIZE_HOST_NAME is currently unsupported +const char kAuthServiceName[] = "SERVICE_NAME"; +const char kAuthServiceRealm[] = "SERVICE_REALM"; + +const char kAuthMechMongoCR[] = "MONGODB-CR"; +const char kAuthMechScramSha1[] = "SCRAM-SHA-1"; +const char kAuthMechDefault[] = "DEFAULT"; + +const char* const kSupportedAuthMechanismProperties[] = {kAuthServiceName, kAuthServiceRealm}; + +BSONObj parseAuthMechanismProperties(const std::string& propStr) { + BSONObjBuilder bob; + std::vector<std::string> props; + boost::algorithm::split(props, propStr, boost::algorithm::is_any_of(",:")); + for (std::vector<std::string>::const_iterator it = props.begin(); it != props.end(); ++it) { + std::string prop((boost::algorithm::to_upper_copy(*it))); // normalize case + uassert(ErrorCodes::FailedToParse, + str::stream() << "authMechanismProperty: " << *it << " is not supported", + std::count(kSupportedAuthMechanismProperties, + std::end(kSupportedAuthMechanismProperties), + prop)); + ++it; + uassert(ErrorCodes::FailedToParse, + str::stream() << "authMechanismProperty: " << prop << " must have a value", + it != props.end()); + bob.append(prop, *it); + } + return bob.obj(); +} + +std::string authKeyCopyDBMongoCR(const std::string& username, + const std::string& password, + const std::string& nonce) { + md5digest d; + std::string passwordDigest = createPasswordDigest(username, password); + { + md5_state_t st; + md5_init(&st); + md5_append(&st, reinterpret_cast<const md5_byte_t*>(nonce.c_str()), nonce.size()); + md5_append(&st, reinterpret_cast<const md5_byte_t*>(username.data()), username.length()); + md5_append(&st, + reinterpret_cast<const md5_byte_t*>(passwordDigest.c_str()), + passwordDigest.size()); + md5_finish(&st, d); + } + return digestToString(d); +} + +} // namespace + +BSONObj MongoURI::_makeAuthObjFromOptions(int maxWireVersion) const { + BSONObjBuilder bob; + + // Add the username and optional password + invariant(!_user.empty()); + std::string username(_user); // may have to tack on service realm before we append + + if (!_password.empty()) + bob.append(saslCommandPasswordFieldName, _password); + + BSONElement elt = _options.getField("authSource"); + if (!elt.eoo()) { + bob.appendAs(elt, saslCommandUserDBFieldName); + } else if (!_database.empty()) { + bob.append(saslCommandUserDBFieldName, _database); + } else { + bob.append(saslCommandUserDBFieldName, "admin"); + } + + elt = _options.getField("authMechanism"); + if (!elt.eoo()) { + bob.appendAs(elt, saslCommandMechanismFieldName); + } else if (maxWireVersion >= 3) { + bob.append(saslCommandMechanismFieldName, kAuthMechScramSha1); + } else { + bob.append(saslCommandMechanismFieldName, kAuthMechMongoCR); + } + + elt = _options.getField("authMechanismProperties"); + if (!elt.eoo()) { + BSONObj parsed(parseAuthMechanismProperties(elt.String())); + + bool hasNameProp = parsed.hasField(kAuthServiceName); + bool hasRealmProp = parsed.hasField(kAuthServiceRealm); + + uassert(ErrorCodes::FailedToParse, + "Cannot specify both gssapiServiceName and SERVICE_NAME", + !(hasNameProp && _options.hasField("gssapiServiceName"))); + // we append the parsed object so that mechanisms that don't accept it can assert. + bob.append(kAuthMechanismPropertiesKey, parsed); + // we still append using the old way the SASL code expects it + if (hasNameProp) { + bob.append(saslCommandServiceNameFieldName, parsed[kAuthServiceName].String()); + } + // if we specified a realm, we just append it to the username as the SASL code + // expects it that way. + if (hasRealmProp) { + username.append("@").append(parsed[kAuthServiceRealm].String()); + } + } + + elt = _options.getField("gssapiServiceName"); + if (!elt.eoo()) + bob.appendAs(elt, saslCommandServiceNameFieldName); + + bob.append("user", username); + + return bob.obj(); +} + +DBClientBase* MongoURI::connect(std::string& errmsg, double socketTimeout) const { + auto ret = _connectString.connect(errmsg, socketTimeout); + if (!_user.empty()) { + ret->auth(_makeAuthObjFromOptions(ret->getMaxWireVersion())); + } + return ret; +} + +} // namespace mongo diff --git a/src/mongo/client/mongo_uri_test.cpp b/src/mongo/client/mongo_uri_test.cpp new file mode 100644 index 00000000000..baa24dd2f57 --- /dev/null +++ b/src/mongo/client/mongo_uri_test.cpp @@ -0,0 +1,314 @@ +/** + * Copyright (C) 2009-2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/client/mongo_uri.h" + +#include "mongo/unittest/unittest.h" + +namespace { +using mongo::MongoURI; + +struct URITestCase { + std::string URI; + std::string uname; + std::string password; + mongo::ConnectionString::ConnectionType type; + std::string setname; + size_t numservers; + size_t numOptions; + std::string database; +}; + +struct InvalidURITestCase { + std::string URI; +}; + +const mongo::ConnectionString::ConnectionType kMaster = mongo::ConnectionString::MASTER; +const mongo::ConnectionString::ConnectionType kSet = mongo::ConnectionString::SET; + +const URITestCase validCases[] = { + + {"mongodb://user:pwd@127.0.0.1", "user", "pwd", kMaster, "", 1, 0, ""}, + + {"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://user:pwd@127.0.0.1:1234", "user", "pwd", kMaster, "", 1, 0, ""}, + + {"mongodb://user@127.0.0.1:1234", "user", "", kMaster, "", 1, 0, ""}, + + {"mongodb://127.0.0.1:1234/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"}, + + {"mongodb://user:pwd@127.0.0.1,127.0.0.2/?replicaSet=replName", + "user", + "pwd", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://user@127.0.0.1,127.0.0.2/?replicaSet=replName", + "user", + "", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://127.0.0.1,127.0.0.2/dbName?foo=a&c=b&replicaSet=replName", + "", + "", + kSet, + "replName", + 2, + 3, + "dbName"}, + + {"mongodb://user:pwd@127.0.0.1:1234,127.0.0.2:1234/?replicaSet=replName", + "user", + "pwd", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://user@127.0.0.1:1234,127.0.0.2:1234/?replicaSet=replName", + "user", + "", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://127.0.0.1:1234,127.0.0.1:1234/dbName?foo=a&c=b&replicaSet=replName", + "", + "", + kSet, + "replName", + 2, + 3, + "dbName"}, + + {"mongodb://user:pwd@[::1]", "user", "pwd", kMaster, "", 1, 0, ""}, + + {"mongodb://user@[::1]", "user", "", kMaster, "", 1, 0, ""}, + + {"mongodb://[::1]/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"}, + + {"mongodb://user:pwd@[::1]:1234", "user", "pwd", kMaster, "", 1, 0, ""}, + + {"mongodb://user@[::1]:1234", "user", "", kMaster, "", 1, 0, ""}, + + {"mongodb://[::1]:1234/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"}, + + {"mongodb://user:pwd@[::1],127.0.0.2/?replicaSet=replName", + "user", + "pwd", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://user@[::1],127.0.0.2/?replicaSet=replName", "user", "", kSet, "replName", 2, 1, ""}, + + {"mongodb://[::1],127.0.0.2/dbName?foo=a&c=b&replicaSet=replName", + "", + "", + kSet, + "replName", + 2, + 3, + "dbName"}, + + {"mongodb://user:pwd@[::1]:1234,127.0.0.2:1234/?replicaSet=replName", + "user", + "pwd", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://user@[::1]:1234,127.0.0.2:1234/?replicaSet=replName", + "user", + "", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://[::1]:1234,[::1]:1234/dbName?foo=a&c=b&replicaSet=replName", + "", + "", + kSet, + "replName", + 2, + 3, + "dbName"}, + + {"mongodb://user:pwd@[::1]", "user", "pwd", kMaster, "", 1, 0, ""}, + + {"mongodb://user@[::1]", "user", "", kMaster, "", 1, 0, ""}, + + {"mongodb://[::1]/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"}, + + {"mongodb://user:pwd@[::1]:1234", "user", "pwd", kMaster, "", 1, 0, ""}, + + {"mongodb://user@[::1]:1234", "user", "", kMaster, "", 1, 0, ""}, + + {"mongodb://[::1]:1234/dbName?foo=a&c=b", "", "", kMaster, "", 1, 2, "dbName"}, + + {"mongodb://user:pwd@[::1],127.0.0.2/?replicaSet=replName", + "user", + "pwd", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://user@[::1],127.0.0.2/?replicaSet=replName", "user", "", kSet, "replName", 2, 1, ""}, + + {"mongodb://[::1],127.0.0.2/dbName?foo=a&c=b&replicaSet=replName", + "", + "", + kSet, + "replName", + 2, + 3, + "dbName"}, + + {"mongodb://user:pwd@[::1]:1234,127.0.0.2:1234/?replicaSet=replName", + "user", + "pwd", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://user@[::1]:1234,127.0.0.2:1234/?replicaSet=replName", + "user", + "", + kSet, + "replName", + 2, + 1, + ""}, + + {"mongodb://[::1]:1234,[::1]:1234/dbName?foo=a&c=b&replicaSet=replName", + "", + "", + kSet, + "replName", + 2, + 3, + "dbName"}, + + {"mongodb://user:pwd@[::1]/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:foobar", + "user", + "pwd", + kMaster, + "", + 1, + 2, + ""}, + + {"mongodb://user:pwd@[::1]/?authMechanism=GSSAPI&gssapiServiceName=foobar", + "user", + "pwd", + kMaster, + "", + 1, + 2, + ""}, + {"mongodb:///tmp/mongodb-27017.sock", "", "", kMaster, "", 1, 0, ""}, + + {"mongodb:///tmp/mongodb-27017.sock,/tmp/mongodb-27018.sock/?replicaSet=replName", + "", + "", + kSet, + "replName", + 2, + 1, + ""}}; + +const InvalidURITestCase invalidCases[] = { + + {"mongodb://"}, + + {"mongodb://localhost:27017,localhost:27018?replicaSet=missingSlash"}, +}; + +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()); + auto options = result.getOptions(); + std::set<std::string> fieldNames; + options.getFieldNames(fieldNames); + ASSERT_EQ(testCase.numOptions, fieldNames.size()); + ASSERT_EQ(testCase.database, result.getDatabase()); + } +} + +TEST(MongoURI, InvalidURIs) { + const size_t numCases = sizeof(invalidCases) / sizeof(invalidCases[0]); + + for (size_t i = 0; i != numCases; ++i) { + const InvalidURITestCase testCase = invalidCases[i]; + mongo::unittest::log() << "Testing URI: " << testCase.URI << '\n'; + auto cs_status = MongoURI::parse(testCase.URI); + ASSERT_FALSE(cs_status.isOK()); + } +} + +} // namespace |