Skip to content

Commit e3a7573

Browse files
CXX-316 Update Connection String URI Parsing
1 parent 0204b35 commit e3a7573

File tree

5 files changed

+152
-43
lines changed

5 files changed

+152
-43
lines changed

src/mongo/client/connection_string_test.cpp

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
namespace {
2323
using mongo::ConnectionString;
2424

25-
struct URLTestCase {
26-
std::string URL;
25+
struct URITestCase {
26+
std::string URI;
2727
std::string uname;
2828
std::string password;
2929
mongo::ConnectionString::ConnectionType type;
@@ -33,10 +33,15 @@ namespace {
3333
std::string database;
3434
};
3535

36+
struct InvalidURITestCase {
37+
std::string URI;
38+
};
39+
3640
const mongo::ConnectionString::ConnectionType kMaster = mongo::ConnectionString::MASTER;
3741
const mongo::ConnectionString::ConnectionType kSet = mongo::ConnectionString::SET;
42+
const mongo::ConnectionString::ConnectionType kPair = mongo::ConnectionString::PAIR;
3843

39-
const URLTestCase cases[] = {
44+
const URITestCase validCases[] = {
4045

4146
{ "mongodb://user:pwd@127.0.0.1", "user", "pwd", kMaster, "", 1, 0, "" },
4247

@@ -111,16 +116,32 @@ namespace {
111116
{ "mongodb://[::1]:1234,[::1]:1234/dbName?foo=a&c=b&replicaSet=replName", "", "", kSet, "replName", 2, 3, "dbName"},
112117
};
113118

119+
const InvalidURITestCase invalidCases[] = {
120+
121+
{ "localhost:27017" },
122+
123+
{ "127.0.0.2:1234,104.9.12.3:27017" },
114124

115-
TEST(ConnectionString, GoodTrickyURLs) {
125+
{ "127.0.0.2:1234,104.9.12.3:27017/?replName=shaun" },
116126

117-
const size_t numCases = sizeof(cases) / sizeof(cases[0]);
127+
{ "replSetName/localhost:27027" },
128+
129+
{ "anything,anything/?thatDoesntStartWith=mongodb://" },
130+
131+
{ "mongodb://" },
132+
133+
{ "mongodb://localhost:27017,localhost:27018?replicaSet=missingSlash" },
134+
};
135+
136+
TEST(ConnectionString, GoodTrickyURIs) {
137+
138+
const size_t numCases = sizeof(validCases) / sizeof(validCases[0]);
118139

119140
for (size_t i = 0; i != numCases; ++i) {
120-
const URLTestCase testCase = cases[i];
121-
std::cout << "Testing URL: " << testCase.URL << '\n';
141+
const URITestCase testCase = validCases[i];
142+
std::cout << "Testing URI: " << testCase.URI << '\n';
122143
std::string errMsg;
123-
const ConnectionString result = ConnectionString::parse(testCase.URL, errMsg);
144+
const ConnectionString result = ConnectionString::parse(testCase.URI, errMsg);
124145
ASSERT_TRUE(result.isValid());
125146
ASSERT_TRUE(errMsg.empty());
126147
ASSERT_EQ(testCase.uname, result.getUser());
@@ -136,4 +157,72 @@ namespace {
136157
}
137158
}
138159

160+
TEST(ConnectionString, InvalidURIs) {
161+
const size_t numCases = sizeof(invalidCases) / sizeof(invalidCases[0]);
162+
163+
for (size_t i = 0; i != numCases; ++i) {
164+
const InvalidURITestCase testCase = invalidCases[i];
165+
std::cout << "Testing URI: " << testCase.URI << '\n';
166+
std::string errMsg;
167+
const ConnectionString result = ConnectionString::parse(testCase.URI, errMsg);
168+
ASSERT_FALSE(result.isValid());
169+
}
170+
}
171+
172+
// Test Deprecated URI Parsing
173+
174+
struct DeprecatedURITestCase {
175+
std::string URI;
176+
size_t numservers;
177+
mongo::ConnectionString::ConnectionType type;
178+
std::string setname;
179+
};
180+
181+
const DeprecatedURITestCase validDeprecatedCases[] = {
182+
183+
{ "localhost:27017", 1, kMaster, "" },
184+
185+
{ "localhost:27017,localhost:30000", 2, kPair, "" },
186+
187+
{ "replName/localhost:27017,127.0.0.2:1234", 2, kSet, "replName" },
188+
189+
{ "localhost:1050,localhost:1055/?replicaSet=rs-1234", 1, kSet, "localhost:1050,localhost:1055" },
190+
};
191+
192+
const InvalidURITestCase invalidDeprecatedCases[] = {
193+
194+
{ "1.2.3.4:5555,6.7.8.9:1000,127.0.0.2:1234" },
195+
196+
{ "localhost:27017,localhost:27018,localhost:27019,localhost:27020?replicaSet=myReplicaSet" },
197+
198+
};
199+
200+
TEST(ConnectionString, GoodDeprecatedURIs) {
201+
const size_t numCases = sizeof(validDeprecatedCases) / sizeof(validDeprecatedCases[0]);
202+
203+
for (size_t i = 0; i != numCases; ++i) {
204+
const DeprecatedURITestCase testCase = validDeprecatedCases[i];
205+
std::cout << "Testing URI: " << testCase.URI << '\n';
206+
std::string errMsg;
207+
const ConnectionString result = ConnectionString::parseDeprecated(testCase.URI, errMsg);
208+
ASSERT_TRUE(result.isValid());
209+
ASSERT_TRUE(errMsg.empty());
210+
ASSERT_EQ(testCase.numservers, result.getServers().size());
211+
ASSERT_EQ(testCase.type, result.type());
212+
ASSERT_EQ(testCase.setname, result.getSetName());
213+
}
214+
}
215+
216+
TEST(ConnectionString, InvalidDeprecatedURIs) {
217+
const size_t numCases = sizeof(invalidDeprecatedCases) / sizeof(invalidDeprecatedCases[0]);
218+
219+
for (size_t i = 0; i != numCases; ++i) {
220+
const InvalidURITestCase testCase = invalidDeprecatedCases[i];
221+
std::cout << "Testing URI: " << testCase.URI << '\n';
222+
std::string errMsg;
223+
const ConnectionString result = ConnectionString::parseDeprecated(testCase.URI, errMsg);
224+
ASSERT_FALSE(result.isValid());
225+
}
226+
}
227+
139228
} // namespace

src/mongo/client/dbclient.cpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ namespace mongo {
9191
} // namespace
9292

9393
void ConnectionString::_fillServers( string s ) {
94-
94+
9595
//
9696
// Custom-handled servers/replica sets start with '$'
9797
// According to RFC-1123/952, this will not overlap with valid hostnames
@@ -117,7 +117,7 @@ namespace mongo {
117117
_servers.push_back(HostAndPort(s));
118118

119119
}
120-
120+
121121
void ConnectionString::_finishInit() {
122122

123123
// Needed here as well b/c the parsing logic isn't used in all constructors
@@ -266,26 +266,29 @@ namespace mongo {
266266
verify( false );
267267
}
268268

269-
ConnectionString ConnectionString::parse( const string& url , string& errmsg ) {
270-
271-
if ( boost::algorithm::starts_with( url, "mongodb://" ) )
272-
return _parseURL( url, errmsg );
269+
ConnectionString ConnectionString::parse( const string& address , string& errmsg ) {
270+
if ( boost::algorithm::starts_with( address, "mongodb://" ) )
271+
return _parseURL( address, errmsg );
272+
errmsg = string("invalid connection string [") + address + "]";
273+
return ConnectionString(); // INVALID
274+
}
273275

274-
string::size_type i = url.find( '/' );
276+
ConnectionString ConnectionString::parseDeprecated( const string& address, string& errmsg ) {
277+
string::size_type i = address.find( '/' );
275278
if ( i != string::npos && i != 0) {
276279
// replica set
277-
return ConnectionString( SET , url.substr( i + 1 ) , url.substr( 0 , i ) );
280+
return ConnectionString( SET , address.substr( i + 1 ) , address.substr( 0 , i ) );
278281
}
279282

280-
int numCommas = str::count( url , ',' );
283+
int numCommas = str::count( address , ',' );
281284

282285
if( numCommas == 0 )
283-
return ConnectionString( HostAndPort( url ) );
286+
return ConnectionString( HostAndPort( address ) );
284287

285288
if ( numCommas == 1 )
286-
return ConnectionString( PAIR , url );
289+
return ConnectionString( PAIR , address );
287290

288-
errmsg = (string)"invalid hostname [" + url + "]";
291+
errmsg = string("invalid connection string [") + address + "]";
289292
return ConnectionString(); // INVALID
290293
}
291294

src/mongo/client/dbclientinterface.h

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -152,17 +152,32 @@ namespace mongo {
152152
class MONGO_CLIENT_API DBClientConnection;
153153

154154
/**
155-
* ConnectionString handles parsing different ways to connect to mongo and determining method
156-
* samples:
157-
* server
158-
* server:port
159-
* foo/server:port,server:port SET
155+
* ConnectionString can parse MongoDB URIs with the following format:
160156
*
161-
* tyipcal use
162-
* string errmsg,
163-
* ConnectionString cs = ConnectionString::parse( url , errmsg );
164-
* if ( ! cs.isValid() ) throw "bad: " + errmsg;
165-
* DBClientBase * conn = cs.connect( errmsg );
157+
* mongodb://[usr:pwd@]host1[:port1]...[,hostN[:portN]]][/[db][?options]]
158+
*
159+
* For a complete list of URI string options, see
160+
* https://wiki.mongodb.com/display/DH/Connection+String+Format
161+
*
162+
* Examples:
163+
*
164+
* A replica set with three members (one running on default port 27017):
165+
* string uri = mongodb://localhost,localhost:27018,localhost:27019
166+
*
167+
* Authenticated connection to db 'bedrock' with user 'barney' and pwd 'rubble':
168+
* string url = mongodb://barney:rubble@localhost/bedrock
169+
*
170+
* Use parse() to parse the url, then validate and connect:
171+
* string errmsg;
172+
* ConnectionString cs = ConnectionString::parse( url, errmsg );
173+
* if ( ! cs.isValid() ) throw "bad connection string: " + errmsg;
174+
* DBClientBase * conn = cs.connect( errmsg );
175+
*
176+
* NOTE:
177+
*
178+
* The 'rs_name/host1:port,host2:port' format has been deprecated, and parse()
179+
* will no longer recognize this as a valid URI. To use the deprecated format,
180+
* use parseDeprecated() instead.
166181
*/
167182
class MONGO_CLIENT_API ConnectionString {
168183
public:
@@ -249,7 +264,9 @@ namespace mongo {
249264
*/
250265
bool sameLogicalEndpoint( const ConnectionString& other ) const;
251266

252-
static ConnectionString MONGO_CLIENT_FUNC parse( const std::string& url , std::string& errmsg );
267+
static ConnectionString MONGO_CLIENT_FUNC parse( const std::string& address , std::string& errmsg );
268+
269+
static ConnectionString MONGO_CLIENT_FUNC parseDeprecated( const std::string& address , std::string& errmsg );
253270

254271
static std::string MONGO_CLIENT_FUNC typeToString( ConnectionType type );
255272

src/mongo/client/examples/authTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ int main( int argc, const char **argv ) {
5656
}
5757

5858
string errmsg;
59-
ConnectionString cs = ConnectionString::parse(string("127.0.0.1:") + port, errmsg);
59+
ConnectionString cs = ConnectionString::parse(string("mongodb://127.0.0.1:") + port, errmsg);
6060
if (!cs.isValid()) {
6161
cout << "error parsing url: " << errmsg << endl;
6262
return EXIT_FAILURE;

src/mongo/unittest/dbclient_test.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,29 +202,29 @@ namespace {
202202

203203
// INVALID -- default non parsed state
204204
ASSERT_TRUE(cs1.sameLogicalEndpoint(cs2));
205-
cs2 = ConnectionString::parse("host1,host2,host3", err1);
205+
cs2 = ConnectionString::parse("mongodb://host1,host2,host3", err1);
206206
ASSERT_TRUE(cs1.sameLogicalEndpoint(cs2));
207207

208208
// MASTER
209-
cs1 = ConnectionString::parse("localhost:1234", err1);
210-
cs2 = ConnectionString::parse("localhost:1234", err2);
209+
cs1 = ConnectionString::parse("mongodb://localhost:1234", err1);
210+
cs2 = ConnectionString::parse("mongodb://localhost:1234", err2);
211211
ASSERT_TRUE(cs1.sameLogicalEndpoint(cs2));
212212

213213
// PAIR -- compares the host + port even in swapped order
214-
cs1 = cs1.parse("localhost:1234,localhost:5678", err1);
215-
cs2 = cs2.parse("localhost:1234,localhost:5678", err2);
214+
cs1 = cs1.parse("mongodb://localhost:1234,localhost:5678", err1);
215+
cs2 = cs2.parse("mongodb://localhost:1234,localhost:5678", err2);
216216
ASSERT_TRUE(cs1.sameLogicalEndpoint(cs2));
217-
cs2 = cs2.parse("localhost:5678,localhost:1234", err2);
217+
cs2 = cs2.parse("mongodb://localhost:5678,localhost:1234", err2);
218218
ASSERT_TRUE(cs1.sameLogicalEndpoint(cs2));
219219

220220
// SET -- compares the set name only
221-
cs1 = cs1.parse("testset/localhost:1234,localhost:5678", err1);
222-
cs2 = cs2.parse("testset/localhost:5678,localhost:1234", err2);
221+
cs1 = cs1.parse("mongodb://localhost:1234,localhost:5678/?replicaSet=testset", err1);
222+
cs2 = cs2.parse("mongodb://localhost:5678,localhost:1234/?replicaSet=testset", err2);
223223
ASSERT_TRUE(cs1.sameLogicalEndpoint(cs2));
224224

225-
// Different types
226-
cs1 = cs1.parse("testset/localhost:1234,localhost:5678", err1);
227-
cs2 = cs2.parse("localhost:5678,localhost:1234", err2);
225+
// Different parsing methods
226+
cs1 = cs1.parseDeprecated("testset/localhost:1234,localhost:5678", err1);
227+
cs2 = cs2.parse("mongodb://localhost:5678,localhost:1234", err2);
228228
ASSERT_FALSE(cs1.sameLogicalEndpoint(cs2));
229229
}
230230

0 commit comments

Comments
 (0)