#include "net/dns/dns_hosts.h"
#include <string>
#include <utility>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "build/build_config.h"
#include "net/base/cronet_buildflags.h"
#include "net/base/url_util.h"
#include "net/dns/dns_util.h"
#include "url/url_canon.h"
using base::StringPiece;
namespace net {
namespace {
class HostsParser {
public:
explicit HostsParser(const StringPiece& text, ParseHostsCommaMode comma_mode)
: text_(text),
data_(text.data()),
end_(text.size()),
comma_mode_(comma_mode) {}
HostsParser(const HostsParser&) = delete;
HostsParser& operator=(const HostsParser&) = delete;
bool Advance() {
bool next_is_ip = (pos_ == 0);
while (pos_ < end_ && pos_ != std::string::npos) {
switch (text_[pos_]) {
case ' ':
case '\t':
SkipWhitespace();
break;
case '\r':
case '\n':
next_is_ip = true;
pos_++;
break;
case '#':
SkipRestOfLine();
break;
case ',':
if (comma_mode_ == PARSE_HOSTS_COMMA_IS_WHITESPACE) {
SkipWhitespace();
break;
}
[[fallthrough]];
default: {
size_t token_start = pos_;
SkipToken();
size_t token_end = (pos_ == std::string::npos) ? end_ : pos_;
token_ = StringPiece(data_ + token_start, token_end - token_start);
token_is_ip_ = next_is_ip;
return true;
}
}
}
return false;
}
void SkipRestOfLine() { pos_ = text_.find("\n", pos_); }
bool token_is_ip() { return token_is_ip_; }
const StringPiece& token() { return token_; }
private:
void SkipToken() {
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() {
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;
}
}
const StringPiece text_;
const char* data_;
const size_t end_;
size_t pos_ = 0;
StringPiece token_;
bool token_is_ip_ = false;
const ParseHostsCommaMode comma_mode_;
};
void ParseHostsWithCommaMode(const std::string& contents,
DnsHosts* dns_hosts,
ParseHostsCommaMode comma_mode) {
CHECK(dns_hosts);
StringPiece ip_text;
IPAddress ip;
AddressFamily family = ADDRESS_FAMILY_IPV4;
HostsParser parser(contents, comma_mode);
while (parser.Advance()) {
if (parser.token_is_ip()) {
StringPiece new_ip_text = parser.token();
if (new_ip_text != ip_text) {
IPAddress new_ip;
if (new_ip.AssignFromIPLiteral(parser.token())) {
ip_text = new_ip_text;
ip = new_ip;
family = (ip.IsIPv4()) ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6;
} else {
parser.SkipRestOfLine();
}
}
} else {
url::CanonHostInfo canonicalization_info;
std::string canonicalized_host =
CanonicalizeHost(parser.token(), &canonicalization_info);
if (canonicalization_info.family != url::CanonHostInfo::NEUTRAL)
continue;
DnsHostsKey key(std::move(canonicalized_host), family);
if (!IsCanonicalizedHostCompliant(key.first))
continue;
IPAddress* mapped_ip = &(*dns_hosts)[key];
if (mapped_ip->empty())
*mapped_ip = ip;
}
}
}
}
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 BUILDFLAG(IS_APPLE)
comma_mode = PARSE_HOSTS_COMMA_IS_WHITESPACE;
#else
comma_mode = PARSE_HOSTS_COMMA_IS_TOKEN;
#endif
ParseHostsWithCommaMode(contents, dns_hosts, comma_mode);
base::UmaHistogramCounts100000("Net.DNS.DnsHosts.Count", dns_hosts->size());
#if !BUILDFLAG(CRONET_BUILD)
base::UmaHistogramMemoryKB(
"Net.DNS.DnsHosts.EstimateMemoryUsage",
base::trace_event::EstimateMemoryUsage(*dns_hosts));
#endif
}
DnsHostsParser::~DnsHostsParser() = default;
DnsHostsFileParser::DnsHostsFileParser(base::FilePath hosts_file_path)
: hosts_file_path_(std::move(hosts_file_path)) {}
DnsHostsFileParser::~DnsHostsFileParser() = default;
bool DnsHostsFileParser::ParseHosts(DnsHosts* dns_hosts) const {
dns_hosts->clear();
if (!base::PathExists(hosts_file_path_))
return true;
int64_t size;
if (!base::GetFileSize(hosts_file_path_, &size))
return false;
const int64_t kMaxHostsSize = 1 << 25;
base::UmaHistogramCustomCounts("Net.DNS.DnsHosts.FileSize", size, 1,
kMaxHostsSize * 2, 50);
if (size > kMaxHostsSize)
return false;
std::string contents;
if (!base::ReadFileToString(hosts_file_path_, &contents))
return false;
net::ParseHosts(contents, dns_hosts);
return true;
}
}