summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@mongodb.com>2019-04-23 17:27:07 -0400
committerA. Jesse Jiryu Davis <jesse@mongodb.com>2019-05-14 15:50:55 -0400
commitad9267a722e21268d1005c1428ccad85d5a98946 (patch)
tree4af25a065685aef29a20106b3f0f9e05a83c19e8
parenta566943aac06895581c16530b953a5724f4177e0 (diff)
downloadmongo-ad9267a722e21268d1005c1428ccad85d5a98946.tar.gz
SERVER-4999 Normalize all hostnames to lowercase
Hostnames passed to replSetInitiate, replSetReconfig, addShard, etc. are all normalized by replacing ASCII uppercase characters with lowercase characters, consistent with how MongoDB drivers treat hostnames. The shell's getHostName() function now returns the hostname lowercased. Fixes undefined behavior in mongo::str::toLower().
-rw-r--r--jstests/core/currentop_cursors.js2
-rw-r--r--jstests/noPassthrough/self_case_insensitive.js40
-rw-r--r--jstests/sharding/addshard2.js23
-rw-r--r--src/mongo/client/replica_set_monitor_scan_test.cpp38
-rw-r--r--src/mongo/db/repl/isself_test.cpp4
-rw-r--r--src/mongo/db/repl/repl_set_config_test.cpp19
-rw-r--r--src/mongo/db/repl/repl_set_heartbeat_response_test.cpp2
-rw-r--r--src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp4
-rw-r--r--src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp6
-rw-r--r--src/mongo/dbtests/replica_set_monitor_test.cpp30
-rw-r--r--src/mongo/shell/replsettest.js7
-rw-r--r--src/mongo/shell/shell_utils_extended.cpp6
-rw-r--r--src/mongo/unittest/unittest.h5
-rw-r--r--src/mongo/util/net/hostandport.cpp7
-rw-r--r--src/mongo/util/net/hostandport.h2
-rw-r--r--src/mongo/util/net/hostandport_test.cpp24
-rw-r--r--src/mongo/util/str.h3
17 files changed, 206 insertions, 16 deletions
diff --git a/jstests/core/currentop_cursors.js b/jstests/core/currentop_cursors.js
index 5263c80f1ed..dc547ca01eb 100644
--- a/jstests/core/currentop_cursors.js
+++ b/jstests/core/currentop_cursors.js
@@ -81,7 +81,7 @@
}
const uri = new MongoURI(db.getMongo().host);
assert(uri.servers.some((server) => {
- return result[0].host == getHostName() + ":" + server.port;
+ return result[0].host.toLowerCase() == getHostName() + ":" + server.port;
}));
const idleCursor = result[0].cursor;
assert.eq(idleCursor.nDocsReturned, 2, result);
diff --git a/jstests/noPassthrough/self_case_insensitive.js b/jstests/noPassthrough/self_case_insensitive.js
new file mode 100644
index 00000000000..b376b90f83e
--- /dev/null
+++ b/jstests/noPassthrough/self_case_insensitive.js
@@ -0,0 +1,40 @@
+/**
+ * Tests that replSetInitiate and replSetReconfig ignore hostname case.
+ *
+ * @tags: [requires_replication]
+ */
+
+(function() {
+ "use strict";
+
+ const rst = new ReplSetTest({nodes: 2});
+ rst.startSet();
+
+ jsTestLog("replSetInitiate with uppercase hostname");
+
+ let conf = rst.getReplSetConfig();
+ conf.members[0].host = conf.members[0].host.toUpperCase();
+
+ rst.initiate(conf);
+
+ const dbName = "test";
+ const collName = "test";
+ const primary = rst.getPrimary();
+ const testColl = primary.getDB(dbName).getCollection(collName);
+ const doc = {a: 1};
+
+ assert.commandWorked(testColl.insert(doc, {writeConcern: {w: 2}}));
+
+ jsTestLog("replSetReconfig with uppercase hostname");
+
+ let secondary2 = MongoRunner.runMongod({replSet: rst.name});
+ conf = rst.getReplSetConfigFromNode();
+ conf.version++;
+ conf.members.push({_id: 5, host: secondary2.host.toUpperCase()});
+ assert.commandWorked(primary.getDB("admin").runCommand({replSetReconfig: conf}));
+ assert.commandWorked(testColl.insert(doc, {writeConcern: {w: 2}}));
+ assert.commandWorked(testColl.insert(doc, {writeConcern: {w: 3}}));
+
+ MongoRunner.stopMongod(secondary2);
+ rst.stopSet();
+})();
diff --git a/jstests/sharding/addshard2.js b/jstests/sharding/addshard2.js
index cb61d4b4245..8ded2008f0a 100644
--- a/jstests/sharding/addshard2.js
+++ b/jstests/sharding/addshard2.js
@@ -86,6 +86,15 @@
addShardRes = st.s.adminCommand({addShard: getHostName() + ":" + portWithoutHostRunning});
assertAddShardFailed(addShardRes);
+ // 1.c. with uppercase hostname.
+
+ jsTest.log("Adding a standalone with an uppercase hostname should succeed.");
+ let standalone1c = MongoRunner.runMongod({shardsvr: ''});
+ addShardRes = st.s.adminCommand({addshard: standalone1c.name.toUpperCase()});
+ assertAddShardSucceeded(addShardRes);
+ removeShardWithName(addShardRes.shardAdded);
+ MongoRunner.stopMongod(standalone1c);
+
// 2. Test adding a *replica set* with an ordinary set name
// 2.a. with or without specifying the shardName.
@@ -140,6 +149,20 @@
rst3.stopSet();
+ // 2.c. with uppercase hostnames.
+
+ jsTest.log("Adding a replica set uppercase hostname should succeed.");
+ let rst2c = new ReplSetTest({name: "rst2c", nodes: 1});
+ rst2c.startSet({shardsvr: ''});
+ rst2c.initiate();
+ let rst2cURI = `${rst2c.name}/${rst2c.getPrimary().name.toUpperCase()}`;
+ jsTestLog(`URI with uppercase hostname: ${rst2cURI}`);
+ addShardRes = st.s.adminCommand({addShard: rst2cURI});
+ assertAddShardSucceeded(addShardRes);
+ assert.eq(rst2c.name, addShardRes.shardAdded);
+ removeShardWithName(addShardRes.shardAdded);
+ rst2c.stopSet();
+
// 3. Test adding a replica set whose *set name* is "config" with or without specifying the
// shardName.
diff --git a/src/mongo/client/replica_set_monitor_scan_test.cpp b/src/mongo/client/replica_set_monitor_scan_test.cpp
index 02af7f2b7e6..6518d1b4a98 100644
--- a/src/mongo/client/replica_set_monitor_scan_test.cpp
+++ b/src/mongo/client/replica_set_monitor_scan_test.cpp
@@ -1023,6 +1023,44 @@ TEST_F(CoreScanTest, TwoPrimaries2ndHasOlderConfigVersion) {
ASSERT_EQUALS(state->configVersion, 2);
}
+// U+2603, Snowman, encoded as UTF-8.
+#define SNOWMAN "\xe2\x98\x83"
+
+TEST_F(CoreScanTest, CaseInsensitive) {
+ const auto host0 = "a";
+ const auto host1 = SNOWMAN "B";
+ const auto host2 = "c" SNOWMAN;
+ const std::vector<HostAndPort> seeds = {
+ HostAndPort(host0), HostAndPort(host1), HostAndPort(host2)};
+ const MongoURI uri(ConnectionString::forReplicaSet("name", seeds));
+ auto state = makeState(basicUri);
+ Refresher refresher(state);
+
+ // mock all replies
+ for (size_t i = 0; i != basicSeeds.size(); ++i) {
+ refresher.receivedIsMaster(HostAndPort(seeds[i]),
+ -1,
+ BSON("setName"
+ << "name"
+ << "ismaster"
+ << (i == 0)
+ << "secondary"
+ << (i != 0)
+ << "hosts"
+ // Different order and capitalization.
+ << BSON_ARRAY("A"
+ << "C" SNOWMAN
+ << SNOWMAN "b")
+ << "ok"
+ << true));
+ }
+
+ ASSERT_EQUALS(state->nodes.size(), static_cast<size_t>(3));
+ for (const auto& seed : seeds) {
+ ASSERT(state->findNode(HostAndPort(seed)));
+ }
+}
+
using MaxStalenessMSTest = ReplicaSetMonitorTest;
/**
diff --git a/src/mongo/db/repl/isself_test.cpp b/src/mongo/db/repl/isself_test.cpp
index 511ef122ecf..bbb03d0b26c 100644
--- a/src/mongo/db/repl/isself_test.cpp
+++ b/src/mongo/db/repl/isself_test.cpp
@@ -79,6 +79,10 @@ TEST_F(ServiceContextTest, DetectsSameHostIPv6) {
#endif
}
+TEST_F(ServiceContextTest, DetectsSameHostDifferentCapitalization) {
+ ASSERT(isSelf(HostAndPort("LoCalHOST", serverGlobalParams.port), getGlobalServiceContext()));
+}
+
} // namespace
} // namespace repl
diff --git a/src/mongo/db/repl/repl_set_config_test.cpp b/src/mongo/db/repl/repl_set_config_test.cpp
index 7f3af1345bb..14295fc86fd 100644
--- a/src/mongo/db/repl/repl_set_config_test.cpp
+++ b/src/mongo/db/repl/repl_set_config_test.cpp
@@ -975,6 +975,25 @@ TEST(ReplSetConfig, ValidateFailsWithDuplicateMemberId) {
ASSERT_EQUALS(ErrorCodes::BadValue, status);
}
+TEST(ReplSetConfig, ValidateFailsWithDuplicateHostnameCaseInsensitive) {
+ ReplSetConfig config;
+ Status status = config.initialize(BSON("_id"
+ << "rs0"
+ << "version"
+ << 1
+ << "protocolVersion"
+ << 1
+ << "members"
+ << BSON_ARRAY(BSON("_id" << 0 << "host"
+ << "localhost:12345")
+ << BSON("_id" << 1 << "host"
+ << "LOCALHOST:12345"))));
+ ASSERT_OK(status);
+
+ status = config.validate();
+ ASSERT_EQUALS(ErrorCodes::BadValue, status);
+}
+
TEST(ReplSetConfig, ValidateFailsWithInvalidMember) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
diff --git a/src/mongo/db/repl/repl_set_heartbeat_response_test.cpp b/src/mongo/db/repl/repl_set_heartbeat_response_test.cpp
index 72c7d31190a..cd9c331dcd3 100644
--- a/src/mongo/db/repl/repl_set_heartbeat_response_test.cpp
+++ b/src/mongo/db/repl/repl_set_heartbeat_response_test.cpp
@@ -118,7 +118,7 @@ TEST(ReplSetHeartbeatResponse, DefaultConstructThenSlowlyBuildToFullObj) {
ASSERT_EQUALS(Timestamp(0, 10), hbResponseObj["durableOpTime"]["ts"].timestamp());
ASSERT_EQUALS(config.toBSON().toString(), hbResponseObj["config"].Obj().toString());
ASSERT_EQUALS(2, hbResponseObj["state"].numberLong());
- ASSERT_EQUALS("syncTarget:27017", hbResponseObj["syncingTo"].String());
+ ASSERT_EQUALS_CI("syncTarget:27017", hbResponseObj["syncingTo"].String());
initializeResult =
hbResponseObjRoundTripChecker.initialize(hbResponseObj, 0, /*requireWallTime*/ true);
diff --git a/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp b/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp
index ce9460cf3e6..23a107a43b5 100644
--- a/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp
+++ b/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp
@@ -287,7 +287,7 @@ protected:
auto foundShard = assertGet(getShardDoc(operationContext(), expectedShard.getName()));
ASSERT_EQUALS(expectedShard.getName(), foundShard.getName());
- ASSERT_EQUALS(expectedShard.getHost(), foundShard.getHost());
+ ASSERT_EQUALS_CI(expectedShard.getHost(), foundShard.getHost());
ASSERT_EQUALS(expectedShard.getMaxSizeMB(), foundShard.getMaxSizeMB());
ASSERT_EQUALS(expectedShard.getDraining(), foundShard.getDraining());
ASSERT_EQUALS((int)expectedShard.getState(), (int)foundShard.getState());
@@ -332,7 +332,7 @@ protected:
auto logEntry = assertGet(ChangeLogType::fromBSON(logEntryBSON));
ASSERT_EQUALS(addedShard.getName(), logEntry.getDetails()["name"].String());
- ASSERT_EQUALS(addedShard.getHost(), logEntry.getDetails()["host"].String());
+ ASSERT_EQUALS_CI(addedShard.getHost(), logEntry.getDetails()["host"].String());
}
void forwardAddShardNetwork(Date_t when) {
diff --git a/src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp b/src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp
index 59fe4af9e98..895783e6254 100644
--- a/src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp
+++ b/src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp
@@ -106,7 +106,7 @@ TEST_F(CreateDatabaseTest, createDatabaseSuccess) {
// Return size information about first shard
onCommand([&](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(s0.getHost(), request.target.toString());
+ ASSERT_EQUALS(HostAndPort(s0.getHost()), request.target);
ASSERT_EQUALS("admin", request.dbname);
std::string cmdName = request.cmdObj.firstElement().fieldName();
ASSERT_EQUALS("listDatabases", cmdName);
@@ -121,7 +121,7 @@ TEST_F(CreateDatabaseTest, createDatabaseSuccess) {
// Return size information about second shard
onCommand([&](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(s1.getHost(), request.target.toString());
+ ASSERT_EQUALS(HostAndPort(s1.getHost()), request.target);
ASSERT_EQUALS("admin", request.dbname);
std::string cmdName = request.cmdObj.firstElement().fieldName();
ASSERT_EQUALS("listDatabases", cmdName);
@@ -136,7 +136,7 @@ TEST_F(CreateDatabaseTest, createDatabaseSuccess) {
// Return size information about third shard
onCommand([&](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(s2.getHost(), request.target.toString());
+ ASSERT_EQUALS(HostAndPort(s2.getHost()), request.target);
ASSERT_EQUALS("admin", request.dbname);
std::string cmdName = request.cmdObj.firstElement().fieldName();
ASSERT_EQUALS("listDatabases", cmdName);
diff --git a/src/mongo/dbtests/replica_set_monitor_test.cpp b/src/mongo/dbtests/replica_set_monitor_test.cpp
index 89ae6bd1085..f168d7bbad9 100644
--- a/src/mongo/dbtests/replica_set_monitor_test.cpp
+++ b/src/mongo/dbtests/replica_set_monitor_test.cpp
@@ -29,9 +29,6 @@
#include "mongo/platform/basic.h"
-#include <set>
-#include <vector>
-
#include "mongo/base/init.h"
#include "mongo/client/connpool.h"
#include "mongo/client/dbclient_rs.h"
@@ -41,6 +38,11 @@
#include "mongo/dbtests/mock/mock_replica_set.h"
#include "mongo/unittest/unittest.h"
+#include <boost/algorithm/string/case_conv.hpp>
+
+#include <set>
+#include <vector>
+
namespace mongo {
namespace {
@@ -373,5 +375,27 @@ TEST_F(TwoNodeWithTags, SecDownRetryExpiredTimeout) {
monitor.reset();
}
+// Tests that hostnames with different capitalization are considered equal.
+TEST_F(TwoNodeWithTags, CaseInsensitive) {
+ const auto primaryOnly = mongo::ReadPreference::PrimaryOnly;
+ const auto secondaryOnly = mongo::ReadPreference::SecondaryOnly;
+
+ MockReplicaSet* replSet = getReplSet();
+
+ // Initialize with upcased primary hostname.
+ set<HostAndPort> seedList;
+ seedList.insert(HostAndPort(boost::algorithm::to_lower_copy(replSet->getPrimary())));
+ auto monitor = ReplicaSetMonitor::createIfNeeded(replSet->getSetName(), seedList);
+ monitor->runScanForMockReplicaSet();
+
+ auto selectMember = [&](mongo::ReadPreference pref) {
+ return monitor->getHostOrRefresh(ReadPreferenceSetting(pref, TagSet()), Milliseconds(1))
+ .get()
+ .toString();
+ };
+
+ ASSERT_EQ(selectMember(primaryOnly), replSet->getPrimary());
+ ASSERT_EQ(selectMember(secondaryOnly), replSet->getSecondaries()[0]);
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/shell/replsettest.js b/src/mongo/shell/replsettest.js
index df1009e0d66..565fd3fb7b5 100644
--- a/src/mongo/shell/replsettest.js
+++ b/src/mongo/shell/replsettest.js
@@ -279,7 +279,8 @@ var ReplSetTest = function(opts) {
"/" + node.name);
}
- if (status.members[i].name == node.host || status.members[i].name == node.name) {
+ if (status.members[i].name.toLowerCase() == node.host.toLowerCase() ||
+ status.members[i].name.toLowerCase() == node.name.toLowerCase()) {
for (var j = 0; j < states.length; j++) {
if (printStatus) {
print("Status -- " + " current state: " + status.members[i][ind] +
@@ -2597,7 +2598,7 @@ var ReplSetTest = function(opts) {
print('Starting new replica set ' + self.name);
self.useHostName = opts.useHostName == undefined ? true : opts.useHostName;
- self.host = self.useHostName ? (opts.host || getHostName()) : 'localhost';
+ self.host = (self.useHostName ? (opts.host || getHostName()) : 'localhost').toLowerCase();
self.oplogSize = opts.oplogSize || 40;
self.useSeedList = opts.useSeedList || false;
self.keyFile = opts.keyFile;
@@ -2693,7 +2694,7 @@ var ReplSetTest = function(opts) {
self.ports = existingNodes.map(node => node.split(':')[1]);
self.nodes = existingNodes.map(node => new Mongo(node));
self.waitForKeys = false;
- self.host = existingNodes[0].split(':')[0];
+ self.host = existingNodes[0].split(':')[0].toLowerCase();
self.name = conf._id;
}
diff --git a/src/mongo/shell/shell_utils_extended.cpp b/src/mongo/shell/shell_utils_extended.cpp
index 2db4d7713a3..dadf2300eae 100644
--- a/src/mongo/shell/shell_utils_extended.cpp
+++ b/src/mongo/shell/shell_utils_extended.cpp
@@ -38,6 +38,7 @@
#include <boost/filesystem.hpp>
#include <fstream>
+#include <string>
#include "mongo/scripting/engine.h"
#include "mongo/shell/shell_utils.h"
@@ -330,11 +331,16 @@ BSONObj writeFile(const BSONObj& args, void* data) {
return undefinedReturn;
}
+// Return hostname normalized to lowercase for ease of use in tests.
BSONObj getHostName(const BSONObj& a, void* data) {
uassert(13411, "getHostName accepts no arguments", a.nFields() == 0);
char buf[260]; // HOST_NAME_MAX is usually 255
verify(gethostname(buf, 260) == 0);
buf[259] = '\0';
+ for (char* c = buf; *c; c++) {
+ *c = static_cast<char>(tolower(static_cast<unsigned char>(*c)));
+ }
+
return BSON("" << buf);
}
diff --git a/src/mongo/unittest/unittest.h b/src/mongo/unittest/unittest.h
index 7d52c333934..415a0e697fd 100644
--- a/src/mongo/unittest/unittest.h
+++ b/src/mongo/unittest/unittest.h
@@ -115,6 +115,11 @@
#define ASSERT_APPROX_EQUAL(a, b, ABSOLUTE_ERR) ASSERT_LTE(std::abs((a) - (b)), ABSOLUTE_ERR)
/**
+ * ASCII case-insensitive comparison.
+ */
+#define ASSERT_EQUALS_CI(a, b) ASSERT_EQUALS(str::toLower(a), str::toLower(b))
+
+/**
* Assert a function call returns its input unchanged.
*/
#define ASSERT_IDENTITY(INPUT, FUNCTION) \
diff --git a/src/mongo/util/net/hostandport.cpp b/src/mongo/util/net/hostandport.cpp
index 4515dc5ec8f..9b9fa87a898 100644
--- a/src/mongo/util/net/hostandport.cpp
+++ b/src/mongo/util/net/hostandport.cpp
@@ -58,7 +58,9 @@ HostAndPort::HostAndPort(StringData text) {
uassertStatusOK(initialize(text));
}
-HostAndPort::HostAndPort(const std::string& h, int p) : _host(h), _port(p) {}
+// Normalize hostname by lowercasing ASCII uppercase characters. Ignore non-ASCII characters.
+// Assume input is ASCII or UTF-8.
+HostAndPort::HostAndPort(const std::string& h, int p) : _host(str::toLower(h)), _port(p) {}
HostAndPort::HostAndPort(SockAddr addr) : _addr(std::move(addr)) {
uassertStatusOK(initialize(_addr->toString(true)));
@@ -213,7 +215,8 @@ Status HostAndPort::initialize(StringData s) {
} else {
port = -1;
}
- _host = hostPart.toString();
+
+ _host = str::toLower(hostPart);
_port = port;
return Status::OK();
}
diff --git a/src/mongo/util/net/hostandport.h b/src/mongo/util/net/hostandport.h
index b889b65b7b7..961db2c8dcc 100644
--- a/src/mongo/util/net/hostandport.h
+++ b/src/mongo/util/net/hostandport.h
@@ -51,6 +51,8 @@ class StringData;
* Composed of some name component, followed optionally by a colon and a numeric port. The name
* might be an IPv4 or IPv6 address or a relative or fully qualified host name, or an absolute
* path to a unix socket.
+ *
+ * Hostnames are converted to lowercase.
*/
struct HostAndPort {
/**
diff --git a/src/mongo/util/net/hostandport_test.cpp b/src/mongo/util/net/hostandport_test.cpp
index 5b5e49af606..d83a4cda097 100644
--- a/src/mongo/util/net/hostandport_test.cpp
+++ b/src/mongo/util/net/hostandport_test.cpp
@@ -39,6 +39,9 @@
namespace mongo {
namespace {
+// U+2603, Snowman, encoded as UTF-8.
+#define SNOWMAN "\xe2\x98\x83"
+
TEST(HostAndPort, BasicLessThanComparison) {
// Not less than self.
ASSERT_FALSE(HostAndPort("a", 1) < HostAndPort("a", 1));
@@ -50,6 +53,12 @@ TEST(HostAndPort, BasicLessThanComparison) {
// Then, order by port number.
ASSERT_LESS_THAN(HostAndPort("a", 1), HostAndPort("a", 2));
ASSERT_FALSE(HostAndPort("a", 2) < HostAndPort("a", 1));
+
+ // Case insensitive.
+ ASSERT_LESS_THAN(HostAndPort("A", 1), HostAndPort("a", 2));
+ ASSERT_LESS_THAN(HostAndPort("a", 1), HostAndPort("A", 2));
+ ASSERT_FALSE(HostAndPort("A", 2) < HostAndPort("a", 1));
+ ASSERT_FALSE(HostAndPort("a", 2) < HostAndPort("A", 1));
}
TEST(HostAndPort, BasicEquality) {
@@ -59,11 +68,23 @@ TEST(HostAndPort, BasicEquality) {
ASSERT_FALSE(HostAndPort("a", 1) != HostAndPort("a", 1));
ASSERT_NOT_EQUALS(HostAndPort("b", 1), HostAndPort("a", 1));
+ // Case insensitive.
+ ASSERT_EQUALS(HostAndPort("A", 1), HostAndPort("a", 1));
+ ASSERT_FALSE(HostAndPort("A", 1) != HostAndPort("a", 1));
+ ASSERT_NOT_EQUALS(HostAndPort("B", 1), HostAndPort("a", 1));
+
// Comparison on port field
ASSERT_FALSE(HostAndPort("a", 1) == HostAndPort("a", 2));
ASSERT_NOT_EQUALS(HostAndPort("a", 1), HostAndPort("a", 2));
}
+TEST(HostAndPort, CaseNormalization) {
+ ASSERT_EQUALS(HostAndPort(SNOWMAN "A", 1), HostAndPort(SNOWMAN "a", 1));
+ ASSERT_EQUALS(HostAndPort("A" SNOWMAN, 1), HostAndPort("a" SNOWMAN, 1));
+ ASSERT_NOT_EQUALS(HostAndPort(SNOWMAN "B", 1), HostAndPort(SNOWMAN "a", 1));
+ ASSERT_NOT_EQUALS(HostAndPort("B" SNOWMAN, 1), HostAndPort("a" SNOWMAN, 1));
+}
+
TEST(HostAndPort, ImplicitPortSelection) {
ASSERT_EQUALS(HostAndPort("a", -1), HostAndPort("a", int(ServerGlobalParams::DefaultDBPort)));
ASSERT_EQUALS(int(ServerGlobalParams::DefaultDBPort), HostAndPort("a", -1).port());
@@ -106,6 +127,9 @@ TEST(HostAndPort, StaticParseFunction) {
HostAndPort("abc.def", 3421));
ASSERT_EQUALS(unittest::assertGet(HostAndPort::parse("[243:1bc]:21")),
HostAndPort("243:1bc", 21));
+ ASSERT_EQUALS(unittest::assertGet(HostAndPort::parse("aBcD:21")), HostAndPort("abcd", 21));
+ ASSERT_EQUALS(unittest::assertGet(HostAndPort::parse("aB" SNOWMAN "cD:21")),
+ HostAndPort("ab" SNOWMAN "cd", 21));
}
TEST(HostAndPort, RoundTripAbility) {
diff --git a/src/mongo/util/str.h b/src/mongo/util/str.h
index bddfff282b2..853fee0c53c 100644
--- a/src/mongo/util/str.h
+++ b/src/mongo/util/str.h
@@ -302,7 +302,8 @@ inline std::string toLower(StringData input) {
for (std::string::size_type i = 0; i < sz; i++) {
char c = input[i];
- copy[i] = (char)tolower((int)c);
+ // See https://en.cppreference.com/w/cpp/string/byte/tolower
+ copy[i] = static_cast<char>(tolower(static_cast<unsigned char>(c)));
}
copy[sz] = 0;
return copy;