summaryrefslogtreecommitdiff
path: root/src/mongo/client
diff options
context:
space:
mode:
authorJonathan Reams <jbreams@mongodb.com>2015-09-25 16:19:01 -0400
committerJonathan Reams <jbreams@mongodb.com>2015-10-13 21:26:12 -0400
commit53c52c43a99d14a9e6c47bcc1ca1e7a2fa044ef0 (patch)
tree7fbc2ff93c5ac378c14edaa8e49173c6786af86e /src/mongo/client
parent90cd064713dfcf5c82be07742f7377c83ea0a4f2 (diff)
downloadmongo-53c52c43a99d14a9e6c47bcc1ca1e7a2fa044ef0.tar.gz
SERVER-6233 Add URI parsing to mongo shell
Diffstat (limited to 'src/mongo/client')
-rw-r--r--src/mongo/client/SConscript3
-rw-r--r--src/mongo/client/connection_string.cpp30
-rw-r--r--src/mongo/client/connection_string.h13
-rw-r--r--src/mongo/client/mongo_uri.cpp154
-rw-r--r--src/mongo/client/mongo_uri.h133
-rw-r--r--src/mongo/client/mongo_uri_connect.cpp172
-rw-r--r--src/mongo/client/mongo_uri_test.cpp314
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