Commit 9561f8ae authored by ttuttle@chromium.org's avatar ttuttle@chromium.org

ParseHosts: Allow commas as separators on Mac OS X

Apparently, OS X allows commas as separators between hostnames in the hosts
file. Treat commas the same as whitespace to support this. (Hostnames and IP
addresses will never contain commas, so this shouldn't break anything.)

BUG=396309

Review URL: https://codereview.chromium.org/415153002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285718 0039d316-1c4b-4281-b951-d872f2087c98
parent 4a9a0da6
...@@ -8,24 +8,25 @@ ...@@ -8,24 +8,25 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/metrics/histogram.h" #include "base/metrics/histogram.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/string_tokenizer.h"
using base::StringPiece; using base::StringPiece;
namespace net { namespace net {
namespace {
// Parses the contents of a hosts file. Returns one token (IP or hostname) at // Parses the contents of a hosts file. Returns one token (IP or hostname) at
// a time. Doesn't copy anything; accepts the file as a StringPiece and // a time. Doesn't copy anything; accepts the file as a StringPiece and
// returns tokens as StringPieces. // returns tokens as StringPieces.
class HostsParser { class HostsParser {
public: public:
explicit HostsParser(const StringPiece& text) explicit HostsParser(const StringPiece& text, ParseHostsCommaMode comma_mode)
: text_(text), : text_(text),
data_(text.data()), data_(text.data()),
end_(text.size()), end_(text.size()),
pos_(0), pos_(0),
token_(), token_is_ip_(false),
token_is_ip_(false) {} comma_mode_(comma_mode) {}
// Advances to the next token (IP or hostname). Returns whether another // Advances to the next token (IP or hostname). Returns whether another
// token was available. |token_is_ip| and |token| can be used to find out // token was available. |token_is_ip| and |token| can be used to find out
...@@ -49,6 +50,14 @@ class HostsParser { ...@@ -49,6 +50,14 @@ class HostsParser {
SkipRestOfLine(); SkipRestOfLine();
break; break;
case ',':
if (comma_mode_ == PARSE_HOSTS_COMMA_IS_WHITESPACE) {
SkipWhitespace();
break;
}
// If comma_mode_ is COMMA_IS_TOKEN, fall through:
default: { default: {
size_t token_start = pos_; size_t token_start = pos_;
SkipToken(); SkipToken();
...@@ -62,7 +71,6 @@ class HostsParser { ...@@ -62,7 +71,6 @@ class HostsParser {
} }
} }
text_ = StringPiece();
return false; return false;
} }
...@@ -85,14 +93,28 @@ class HostsParser { ...@@ -85,14 +93,28 @@ class HostsParser {
private: private:
void SkipToken() { void SkipToken() {
switch (comma_mode_) {
case PARSE_HOSTS_COMMA_IS_TOKEN:
pos_ = text_.find_first_of(" \t\n\r#", pos_); pos_ = text_.find_first_of(" \t\n\r#", pos_);
break;
case PARSE_HOSTS_COMMA_IS_WHITESPACE:
pos_ = text_.find_first_of(" ,\t\n\r#", pos_);
break;
}
} }
void SkipWhitespace() { void SkipWhitespace() {
switch (comma_mode_) {
case PARSE_HOSTS_COMMA_IS_TOKEN:
pos_ = text_.find_first_not_of(" \t", pos_); pos_ = text_.find_first_not_of(" \t", pos_);
break;
case PARSE_HOSTS_COMMA_IS_WHITESPACE:
pos_ = text_.find_first_not_of(" ,\t", pos_);
break;
}
} }
StringPiece text_; const StringPiece text_;
const char* data_; const char* data_;
const size_t end_; const size_t end_;
...@@ -100,19 +122,21 @@ class HostsParser { ...@@ -100,19 +122,21 @@ class HostsParser {
StringPiece token_; StringPiece token_;
bool token_is_ip_; bool token_is_ip_;
const ParseHostsCommaMode comma_mode_;
DISALLOW_COPY_AND_ASSIGN(HostsParser); DISALLOW_COPY_AND_ASSIGN(HostsParser);
}; };
void ParseHostsWithCommaMode(const std::string& contents,
DnsHosts* dns_hosts,
void ParseHosts(const std::string& contents, DnsHosts* dns_hosts) { ParseHostsCommaMode comma_mode) {
CHECK(dns_hosts); CHECK(dns_hosts);
DnsHosts& hosts = *dns_hosts; DnsHosts& hosts = *dns_hosts;
StringPiece ip_text; StringPiece ip_text;
IPAddressNumber ip; IPAddressNumber ip;
AddressFamily family = ADDRESS_FAMILY_IPV4; AddressFamily family = ADDRESS_FAMILY_IPV4;
HostsParser parser(contents); HostsParser parser(contents, comma_mode);
while (parser.Advance()) { while (parser.Advance()) {
if (parser.token_is_ip()) { if (parser.token_is_ip()) {
StringPiece new_ip_text = parser.token(); StringPiece new_ip_text = parser.token();
...@@ -140,6 +164,27 @@ void ParseHosts(const std::string& contents, DnsHosts* dns_hosts) { ...@@ -140,6 +164,27 @@ void ParseHosts(const std::string& contents, DnsHosts* dns_hosts) {
} }
} }
} // namespace
void ParseHostsWithCommaModeForTesting(const std::string& contents,
DnsHosts* dns_hosts,
ParseHostsCommaMode comma_mode) {
ParseHostsWithCommaMode(contents, dns_hosts, comma_mode);
}
void ParseHosts(const std::string& contents, DnsHosts* dns_hosts) {
ParseHostsCommaMode comma_mode;
#if defined(OS_MACOSX)
// Mac OS X allows commas to separate hostnames.
comma_mode = PARSE_HOSTS_COMMA_IS_WHITESPACE;
#else
// Linux allows commas in hostnames.
comma_mode = PARSE_HOSTS_COMMA_IS_TOKEN;
#endif
ParseHostsWithCommaMode(contents, dns_hosts, comma_mode);
}
bool ParseHostsFile(const base::FilePath& path, DnsHosts* dns_hosts) { bool ParseHostsFile(const base::FilePath& path, DnsHosts* dns_hosts) {
dns_hosts->clear(); dns_hosts->clear();
// Missing file indicates empty HOSTS. // Missing file indicates empty HOSTS.
......
...@@ -44,6 +44,17 @@ inline size_t hash_value(const net::DnsHostsKey& key) { ...@@ -44,6 +44,17 @@ inline size_t hash_value(const net::DnsHostsKey& key) {
namespace net { namespace net {
// There are OS-specific variations in how commas in the hosts file behave.
enum ParseHostsCommaMode {
// Comma is treated as part of a hostname:
// "127.0.0.1 foo,bar" parses as "foo,bar" mapping to "127.0.0.1".
PARSE_HOSTS_COMMA_IS_TOKEN,
// Comma is treated as a hostname separator:
// "127.0.0.1 foo,bar" parses as "foo" and "bar" both mapping to "127.0.0.1".
PARSE_HOSTS_COMMA_IS_WHITESPACE,
};
// Parsed results of a Hosts file. // Parsed results of a Hosts file.
// //
// Although Hosts files map IP address to a list of domain names, for name // Although Hosts files map IP address to a list of domain names, for name
...@@ -62,6 +73,15 @@ typedef base::hash_map<DnsHostsKey, IPAddressNumber> DnsHosts; ...@@ -62,6 +73,15 @@ typedef base::hash_map<DnsHostsKey, IPAddressNumber> DnsHosts;
typedef std::map<DnsHostsKey, IPAddressNumber> DnsHosts; typedef std::map<DnsHostsKey, IPAddressNumber> DnsHosts;
#endif #endif
// Parses |contents| (as read from /etc/hosts or equivalent) and stores results
// in |dns_hosts|. Invalid lines are ignored (as in most implementations).
// Overrides the OS-specific default handling of commas, so unittests can test
// both modes.
void NET_EXPORT_PRIVATE ParseHostsWithCommaModeForTesting(
const std::string& contents,
DnsHosts* dns_hosts,
ParseHostsCommaMode comma_mode);
// Parses |contents| (as read from /etc/hosts or equivalent) and stores results // Parses |contents| (as read from /etc/hosts or equivalent) and stores results
// in |dns_hosts|. Invalid lines are ignored (as in most implementations). // in |dns_hosts|. Invalid lines are ignored (as in most implementations).
void NET_EXPORT_PRIVATE ParseHosts(const std::string& contents, void NET_EXPORT_PRIVATE ParseHosts(const std::string& contents,
......
...@@ -10,8 +10,27 @@ namespace net { ...@@ -10,8 +10,27 @@ namespace net {
namespace { namespace {
struct ExpectedHostsEntry {
const char* host;
AddressFamily family;
const char* ip;
};
void PopulateExpectedHosts(const ExpectedHostsEntry* entries,
size_t num_entries,
DnsHosts* expected_hosts_out) {
for (size_t i = 0; i < num_entries; ++i) {
DnsHostsKey key(entries[i].host, entries[i].family);
IPAddressNumber& ip_ref = (*expected_hosts_out)[key];
ASSERT_TRUE(ip_ref.empty());
ASSERT_TRUE(ParseIPLiteralToNumber(entries[i].ip, &ip_ref));
ASSERT_EQ(ip_ref.size(),
(entries[i].family == ADDRESS_FAMILY_IPV4) ? 4u : 16u);
}
}
TEST(DnsHostsTest, ParseHosts) { TEST(DnsHostsTest, ParseHosts) {
std::string contents = const std::string kContents =
"127.0.0.1 localhost\tlocalhost.localdomain # standard\n" "127.0.0.1 localhost\tlocalhost.localdomain # standard\n"
"\n" "\n"
"1.0.0.1 localhost # ignored, first hit above\n" "1.0.0.1 localhost # ignored, first hit above\n"
...@@ -30,11 +49,7 @@ TEST(DnsHostsTest, ParseHosts) { ...@@ -30,11 +49,7 @@ TEST(DnsHostsTest, ParseHosts) {
"127.0.0.2 cache5\n" "127.0.0.2 cache5\n"
"gibberish"; "gibberish";
const struct { const ExpectedHostsEntry kEntries[] = {
const char* host;
AddressFamily family;
const char* ip;
} entries[] = {
{ "localhost", ADDRESS_FAMILY_IPV4, "127.0.0.1" }, { "localhost", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
{ "localhost.localdomain", ADDRESS_FAMILY_IPV4, "127.0.0.1" }, { "localhost.localdomain", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
{ "company", ADDRESS_FAMILY_IPV4, "1.0.0.1" }, { "company", ADDRESS_FAMILY_IPV4, "1.0.0.1" },
...@@ -50,18 +65,60 @@ TEST(DnsHostsTest, ParseHosts) { ...@@ -50,18 +65,60 @@ TEST(DnsHostsTest, ParseHosts) {
{ "cache5", ADDRESS_FAMILY_IPV4, "127.0.0.2" }, { "cache5", ADDRESS_FAMILY_IPV4, "127.0.0.2" },
}; };
DnsHosts expected; DnsHosts expected_hosts, actual_hosts;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(entries); ++i) { PopulateExpectedHosts(kEntries, ARRAYSIZE_UNSAFE(kEntries), &expected_hosts);
DnsHostsKey key(entries[i].host, entries[i].family); ParseHosts(kContents, &actual_hosts);
IPAddressNumber& ip = expected[key]; ASSERT_EQ(expected_hosts, actual_hosts);
ASSERT_TRUE(ip.empty()); }
ASSERT_TRUE(ParseIPLiteralToNumber(entries[i].ip, &ip));
ASSERT_EQ(ip.size(), (entries[i].family == ADDRESS_FAMILY_IPV4) ? 4u : 16u);
}
DnsHosts hosts; TEST(DnsHostsTest, ParseHosts_CommaIsToken) {
ParseHosts(contents, &hosts); const std::string kContents = "127.0.0.1 comma1,comma2";
ASSERT_EQ(expected, hosts);
const ExpectedHostsEntry kEntries[] = {
{ "comma1,comma2", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
};
DnsHosts expected_hosts, actual_hosts;
PopulateExpectedHosts(kEntries, ARRAYSIZE_UNSAFE(kEntries), &expected_hosts);
ParseHostsWithCommaModeForTesting(
kContents, &actual_hosts, PARSE_HOSTS_COMMA_IS_TOKEN);
ASSERT_EQ(expected_hosts, actual_hosts);
}
TEST(DnsHostsTest, ParseHosts_CommaIsWhitespace) {
std::string kContents = "127.0.0.1 comma1,comma2";
const ExpectedHostsEntry kEntries[] = {
{ "comma1", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
{ "comma2", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
};
DnsHosts expected_hosts, actual_hosts;
PopulateExpectedHosts(kEntries, ARRAYSIZE_UNSAFE(kEntries), &expected_hosts);
ParseHostsWithCommaModeForTesting(
kContents, &actual_hosts, PARSE_HOSTS_COMMA_IS_WHITESPACE);
ASSERT_EQ(expected_hosts, actual_hosts);
}
// Test that the right comma mode is used on each platform.
TEST(DnsHostsTest, ParseHosts_CommaModeByPlatform) {
std::string kContents = "127.0.0.1 comma1,comma2";
#if defined(OS_MACOSX)
const ExpectedHostsEntry kEntries[] = {
{ "comma1", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
{ "comma2", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
};
#else
const ExpectedHostsEntry kEntries[] = {
{ "comma1,comma2", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
};
#endif
DnsHosts expected_hosts, actual_hosts;
PopulateExpectedHosts(kEntries, ARRAYSIZE_UNSAFE(kEntries), &expected_hosts);
ParseHosts(kContents, &actual_hosts);
ASSERT_EQ(expected_hosts, actual_hosts);
} }
TEST(DnsHostsTest, HostsParser_Empty) { TEST(DnsHostsTest, HostsParser_Empty) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment