Skip to content

Commit

Permalink
ParseHosts: Allow commas as separators on Mac OS X
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ttuttle@chromium.org committed Jul 25, 2014
1 parent 4a9a0da commit 9561f8a
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 29 deletions.
69 changes: 57 additions & 12 deletions net/dns/dns_hosts.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,25 @@
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "base/strings/string_tokenizer.h"

using base::StringPiece;

namespace net {

namespace {

// 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
// returns tokens as StringPieces.
class HostsParser {
public:
explicit HostsParser(const StringPiece& text)
explicit HostsParser(const StringPiece& text, ParseHostsCommaMode comma_mode)
: text_(text),
data_(text.data()),
end_(text.size()),
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
// token was available. |token_is_ip| and |token| can be used to find out
Expand All @@ -49,6 +50,14 @@ class HostsParser {
SkipRestOfLine();
break;

case ',':
if (comma_mode_ == PARSE_HOSTS_COMMA_IS_WHITESPACE) {
SkipWhitespace();
break;
}

// If comma_mode_ is COMMA_IS_TOKEN, fall through:

default: {
size_t token_start = pos_;
SkipToken();
Expand All @@ -62,7 +71,6 @@ class HostsParser {
}
}

text_ = StringPiece();
return false;
}

Expand All @@ -85,34 +93,50 @@ class HostsParser {

private:
void SkipToken() {
pos_ = text_.find_first_of(" \t\n\r#", pos_);
switch (comma_mode_) {
case PARSE_HOSTS_COMMA_IS_TOKEN:
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() {
pos_ = text_.find_first_not_of(" \t", pos_);
switch (comma_mode_) {
case PARSE_HOSTS_COMMA_IS_TOKEN:
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 size_t end_;

size_t pos_;
StringPiece token_;
bool token_is_ip_;

const ParseHostsCommaMode comma_mode_;

DISALLOW_COPY_AND_ASSIGN(HostsParser);
};



void ParseHosts(const std::string& contents, DnsHosts* dns_hosts) {
void ParseHostsWithCommaMode(const std::string& contents,
DnsHosts* dns_hosts,
ParseHostsCommaMode comma_mode) {
CHECK(dns_hosts);
DnsHosts& hosts = *dns_hosts;

StringPiece ip_text;
IPAddressNumber ip;
AddressFamily family = ADDRESS_FAMILY_IPV4;
HostsParser parser(contents);
HostsParser parser(contents, comma_mode);
while (parser.Advance()) {
if (parser.token_is_ip()) {
StringPiece new_ip_text = parser.token();
Expand Down Expand Up @@ -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) {
dns_hosts->clear();
// Missing file indicates empty HOSTS.
Expand Down
20 changes: 20 additions & 0 deletions net/dns/dns_hosts.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ inline size_t hash_value(const net::DnsHostsKey& key) {

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.
//
// Although Hosts files map IP address to a list of domain names, for name
Expand All @@ -62,6 +73,15 @@ typedef base::hash_map<DnsHostsKey, IPAddressNumber> DnsHosts;
typedef std::map<DnsHostsKey, IPAddressNumber> DnsHosts;
#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
// in |dns_hosts|. Invalid lines are ignored (as in most implementations).
void NET_EXPORT_PRIVATE ParseHosts(const std::string& contents,
Expand Down
91 changes: 74 additions & 17 deletions net/dns/dns_hosts_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,27 @@ namespace net {

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) {
std::string contents =
const std::string kContents =
"127.0.0.1 localhost\tlocalhost.localdomain # standard\n"
"\n"
"1.0.0.1 localhost # ignored, first hit above\n"
Expand All @@ -30,11 +49,7 @@ TEST(DnsHostsTest, ParseHosts) {
"127.0.0.2 cache5\n"
"gibberish";

const struct {
const char* host;
AddressFamily family;
const char* ip;
} entries[] = {
const ExpectedHostsEntry kEntries[] = {
{ "localhost", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
{ "localhost.localdomain", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
{ "company", ADDRESS_FAMILY_IPV4, "1.0.0.1" },
Expand All @@ -50,18 +65,60 @@ TEST(DnsHostsTest, ParseHosts) {
{ "cache5", ADDRESS_FAMILY_IPV4, "127.0.0.2" },
};

DnsHosts expected;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(entries); ++i) {
DnsHostsKey key(entries[i].host, entries[i].family);
IPAddressNumber& ip = expected[key];
ASSERT_TRUE(ip.empty());
ASSERT_TRUE(ParseIPLiteralToNumber(entries[i].ip, &ip));
ASSERT_EQ(ip.size(), (entries[i].family == ADDRESS_FAMILY_IPV4) ? 4u : 16u);
}
DnsHosts expected_hosts, actual_hosts;
PopulateExpectedHosts(kEntries, ARRAYSIZE_UNSAFE(kEntries), &expected_hosts);
ParseHosts(kContents, &actual_hosts);
ASSERT_EQ(expected_hosts, actual_hosts);
}

DnsHosts hosts;
ParseHosts(contents, &hosts);
ASSERT_EQ(expected, hosts);
TEST(DnsHostsTest, ParseHosts_CommaIsToken) {
const std::string kContents = "127.0.0.1 comma1,comma2";

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) {
Expand Down

0 comments on commit 9561f8a

Please sign in to comment.