| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/dns/host_resolver_internal_result.h" |
| |
| #include <map> |
| #include <memory> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check_op.h" |
| #include "base/json/values_util.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "net/base/connection_endpoint_metadata.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/net_errors.h" |
| #include "net/dns/https_record_rdata.h" |
| #include "net/dns/public/dns_query_type.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/url_canon.h" |
| #include "url/url_canon_stdstring.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // base::Value keys |
| constexpr base::StringPiece kValueDomainNameKey = "domain_name"; |
| constexpr base::StringPiece kValueQueryTypeKey = "query_type"; |
| constexpr base::StringPiece kValueTypeKey = "type"; |
| constexpr base::StringPiece kValueSourceKey = "source"; |
| constexpr base::StringPiece kValueTimedExpirationKey = "timed_expiration"; |
| constexpr base::StringPiece kValueEndpointsKey = "endpoints"; |
| constexpr base::StringPiece kValueStringsKey = "strings"; |
| constexpr base::StringPiece kValueHostsKey = "hosts"; |
| constexpr base::StringPiece kValueMetadatasKey = "metadatas"; |
| constexpr base::StringPiece kValueMetadataWeightKey = "metadata_weight"; |
| constexpr base::StringPiece kValueMetadataValueKey = "metadata_value"; |
| constexpr base::StringPiece kValueErrorKey = "error"; |
| constexpr base::StringPiece kValueAliasTargetKey = "alias_target"; |
| |
| // Returns `domain_name` as-is if it could not be canonicalized. |
| std::string MaybeCanonicalizeName(std::string domain_name) { |
| std::string canonicalized; |
| url::StdStringCanonOutput output(&canonicalized); |
| url::CanonHostInfo host_info; |
| |
| url::CanonicalizeHostVerbose(domain_name.data(), |
| url::Component(0, domain_name.size()), &output, |
| &host_info); |
| |
| if (host_info.family == url::CanonHostInfo::Family::NEUTRAL) { |
| output.Complete(); |
| return canonicalized; |
| } else { |
| return domain_name; |
| } |
| } |
| |
| base::Value EndpointMetadataPairToValue( |
| const std::pair<HttpsRecordPriority, ConnectionEndpointMetadata>& pair) { |
| base::Value::Dict dictionary; |
| dictionary.Set(kValueMetadataWeightKey, pair.first); |
| dictionary.Set(kValueMetadataValueKey, pair.second.ToValue()); |
| return base::Value(std::move(dictionary)); |
| } |
| |
| absl::optional<std::pair<HttpsRecordPriority, ConnectionEndpointMetadata>> |
| EndpointMetadataPairFromValue(const base::Value& value) { |
| const base::Value::Dict* dict = value.GetIfDict(); |
| if (!dict) |
| return absl::nullopt; |
| |
| absl::optional<int> weight = dict->FindInt(kValueMetadataWeightKey); |
| if (!weight || !base::IsValueInRangeForNumericType<HttpsRecordPriority>( |
| weight.value())) { |
| return absl::nullopt; |
| } |
| |
| const base::Value* metadata_value = dict->Find(kValueMetadataValueKey); |
| if (!metadata_value) |
| return absl::nullopt; |
| absl::optional<ConnectionEndpointMetadata> metadata = |
| ConnectionEndpointMetadata::FromValue(*metadata_value); |
| if (!metadata) |
| return absl::nullopt; |
| |
| return std::make_pair(base::checked_cast<HttpsRecordPriority>(weight.value()), |
| std::move(metadata).value()); |
| } |
| |
| absl::optional<DnsQueryType> QueryTypeFromValue(const base::Value& value) { |
| const std::string* query_type_string = value.GetIfString(); |
| if (!query_type_string) |
| return absl::nullopt; |
| /* Cobalt |
| const auto* query_type_it = |
| Cobalt */ |
| const auto query_type_it = |
| base::ranges::find(kDnsQueryTypes, *query_type_string, |
| &decltype(kDnsQueryTypes)::value_type::second); |
| if (query_type_it == kDnsQueryTypes.end()) |
| return absl::nullopt; |
| |
| return query_type_it->first; |
| } |
| |
| base::Value TypeToValue(HostResolverInternalResult::Type type) { |
| switch (type) { |
| case HostResolverInternalResult::Type::kData: |
| return base::Value("data"); |
| case HostResolverInternalResult::Type::kMetadata: |
| return base::Value("metadata"); |
| case HostResolverInternalResult::Type::kError: |
| return base::Value("error"); |
| case HostResolverInternalResult::Type::kAlias: |
| return base::Value("alias"); |
| } |
| } |
| |
| absl::optional<HostResolverInternalResult::Type> TypeFromValue( |
| const base::Value& value) { |
| const std::string* string = value.GetIfString(); |
| if (!string) |
| return absl::nullopt; |
| |
| if (*string == "data") { |
| return HostResolverInternalResult::Type::kData; |
| } else if (*string == "metadata") { |
| return HostResolverInternalResult::Type::kMetadata; |
| } else if (*string == "error") { |
| return HostResolverInternalResult::Type::kError; |
| } else if (*string == "alias") { |
| return HostResolverInternalResult::Type::kAlias; |
| } else { |
| return absl::nullopt; |
| } |
| } |
| |
| base::Value SourceToValue(HostResolverInternalResult::Source source) { |
| switch (source) { |
| case HostResolverInternalResult::Source::kDns: |
| return base::Value("dns"); |
| case HostResolverInternalResult::Source::kHosts: |
| return base::Value("hosts"); |
| case HostResolverInternalResult::Source::kUnknown: |
| return base::Value("unknown"); |
| } |
| } |
| |
| absl::optional<HostResolverInternalResult::Source> SourceFromValue( |
| const base::Value& value) { |
| const std::string* string = value.GetIfString(); |
| if (!string) |
| return absl::nullopt; |
| |
| if (*string == "dns") { |
| return HostResolverInternalResult::Source::kDns; |
| } else if (*string == "hosts") { |
| return HostResolverInternalResult::Source::kHosts; |
| } else if (*string == "unknown") { |
| return HostResolverInternalResult::Source::kUnknown; |
| } else { |
| return absl::nullopt; |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<HostResolverInternalResult> |
| HostResolverInternalResult::FromValue(const base::Value& value) { |
| const base::Value::Dict* dict = value.GetIfDict(); |
| if (!dict) |
| return nullptr; |
| |
| const base::Value* type_value = dict->Find(kValueTypeKey); |
| if (!type_value) |
| return nullptr; |
| absl::optional<Type> type = TypeFromValue(*type_value); |
| if (!type.has_value()) |
| return nullptr; |
| |
| switch (type.value()) { |
| case Type::kData: |
| return HostResolverInternalDataResult::FromValue(value); |
| case Type::kMetadata: |
| return HostResolverInternalMetadataResult::FromValue(value); |
| case Type::kError: |
| return HostResolverInternalErrorResult::FromValue(value); |
| case Type::kAlias: |
| return HostResolverInternalAliasResult::FromValue(value); |
| } |
| } |
| |
| const HostResolverInternalDataResult& HostResolverInternalResult::AsData() |
| const { |
| CHECK_EQ(type_, Type::kData); |
| return *static_cast<const HostResolverInternalDataResult*>(this); |
| } |
| |
| const HostResolverInternalMetadataResult& |
| HostResolverInternalResult::AsMetadata() const { |
| CHECK_EQ(type_, Type::kMetadata); |
| return *static_cast<const HostResolverInternalMetadataResult*>(this); |
| } |
| |
| const HostResolverInternalErrorResult& HostResolverInternalResult::AsError() |
| const { |
| CHECK_EQ(type_, Type::kError); |
| return *static_cast<const HostResolverInternalErrorResult*>(this); |
| } |
| |
| const HostResolverInternalAliasResult& HostResolverInternalResult::AsAlias() |
| const { |
| CHECK_EQ(type_, Type::kAlias); |
| return *static_cast<const HostResolverInternalAliasResult*>(this); |
| } |
| |
| HostResolverInternalResult::HostResolverInternalResult( |
| std::string domain_name, |
| DnsQueryType query_type, |
| absl::optional<base::TimeTicks> expiration, |
| absl::optional<base::Time> timed_expiration, |
| Type type, |
| Source source) |
| : domain_name_(MaybeCanonicalizeName(std::move(domain_name))), |
| query_type_(query_type), |
| type_(type), |
| source_(source), |
| expiration_(expiration), |
| timed_expiration_(timed_expiration) { |
| DCHECK(!domain_name_.empty()); |
| // If `expiration` has a value, `timed_expiration` must too. |
| DCHECK(!expiration_.has_value() || timed_expiration.has_value()); |
| } |
| |
| HostResolverInternalResult::HostResolverInternalResult( |
| const base::Value::Dict& dict) |
| : domain_name_(*dict.FindString(kValueDomainNameKey)), |
| query_type_(QueryTypeFromValue(*dict.Find(kValueQueryTypeKey)).value()), |
| type_(TypeFromValue(*dict.Find(kValueTypeKey)).value()), |
| source_(SourceFromValue(*dict.Find(kValueSourceKey)).value()), |
| timed_expiration_( |
| dict.contains(kValueTimedExpirationKey) |
| ? base::ValueToTime(*dict.Find(kValueTimedExpirationKey)) |
| : absl::optional<base::Time>()) {} |
| |
| // static |
| bool HostResolverInternalResult::ValidateValueBaseDict( |
| const base::Value::Dict& dict, |
| bool require_timed_expiration) { |
| const std::string* domain_name = dict.FindString(kValueDomainNameKey); |
| if (!domain_name) |
| return false; |
| |
| const std::string* query_type_string = dict.FindString(kValueQueryTypeKey); |
| if (!query_type_string) |
| return false; |
| /* Cobalt |
| const auto* query_type_it = |
| Cobalt */ |
| const auto query_type_it = |
| base::ranges::find(kDnsQueryTypes, *query_type_string, |
| &decltype(kDnsQueryTypes)::value_type::second); |
| if (query_type_it == kDnsQueryTypes.end()) |
| return false; |
| |
| const base::Value* type_value = dict.Find(kValueTypeKey); |
| if (!type_value) |
| return false; |
| absl::optional<Type> type = TypeFromValue(*type_value); |
| if (!type.has_value()) |
| return false; |
| |
| const base::Value* source_value = dict.Find(kValueSourceKey); |
| if (!source_value) |
| return false; |
| absl::optional<Source> source = SourceFromValue(*source_value); |
| if (!source.has_value()) |
| return false; |
| |
| absl::optional<base::Time> timed_expiration; |
| const base::Value* timed_expiration_value = |
| dict.Find(kValueTimedExpirationKey); |
| if (require_timed_expiration && !timed_expiration_value) |
| return false; |
| if (timed_expiration_value) { |
| timed_expiration = base::ValueToTime(timed_expiration_value); |
| if (!timed_expiration.has_value()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| base::Value::Dict HostResolverInternalResult::ToValueBaseDict() const { |
| base::Value::Dict dict; |
| |
| dict.Set(kValueDomainNameKey, domain_name_); |
| dict.Set(kValueQueryTypeKey, kDnsQueryTypes.at(query_type_)); |
| dict.Set(kValueTypeKey, TypeToValue(type_)); |
| dict.Set(kValueSourceKey, SourceToValue(source_)); |
| |
| // `expiration_` is not serialized because it is TimeTicks. |
| |
| if (timed_expiration_.has_value()) { |
| dict.Set(kValueTimedExpirationKey, |
| base::TimeToValue(timed_expiration_.value())); |
| } |
| |
| return dict; |
| } |
| |
| // static |
| std::unique_ptr<HostResolverInternalDataResult> |
| HostResolverInternalDataResult::FromValue(const base::Value& value) { |
| const base::Value::Dict* dict = value.GetIfDict(); |
| if (!dict || !ValidateValueBaseDict(*dict, /*require_timed_expiration=*/true)) |
| return nullptr; |
| |
| const base::Value::List* endpoint_values = dict->FindList(kValueEndpointsKey); |
| if (!endpoint_values) |
| return nullptr; |
| |
| std::vector<IPEndPoint> endpoints; |
| endpoints.reserve(endpoint_values->size()); |
| for (const base::Value& endpoint_value : *endpoint_values) { |
| absl::optional<IPEndPoint> endpoint = IPEndPoint::FromValue(endpoint_value); |
| if (!endpoint.has_value()) |
| return nullptr; |
| |
| endpoints.push_back(std::move(endpoint).value()); |
| } |
| |
| const base::Value::List* string_values = dict->FindList(kValueStringsKey); |
| if (!string_values) |
| return nullptr; |
| |
| std::vector<std::string> strings; |
| strings.reserve(string_values->size()); |
| for (const base::Value& string_value : *string_values) { |
| const std::string* string = string_value.GetIfString(); |
| if (!string) |
| return nullptr; |
| |
| strings.push_back(*string); |
| } |
| |
| const base::Value::List* host_values = dict->FindList(kValueHostsKey); |
| if (!host_values) |
| return nullptr; |
| |
| std::vector<HostPortPair> hosts; |
| hosts.reserve(host_values->size()); |
| for (const base::Value& host_value : *host_values) { |
| absl::optional<HostPortPair> host = HostPortPair::FromValue(host_value); |
| if (!host.has_value()) |
| return nullptr; |
| |
| hosts.push_back(std::move(host).value()); |
| } |
| |
| // WrapUnique due to private constructor. |
| return base::WrapUnique(new HostResolverInternalDataResult( |
| *dict, std::move(endpoints), std::move(strings), std::move(hosts))); |
| } |
| |
| HostResolverInternalDataResult::HostResolverInternalDataResult( |
| std::string domain_name, |
| DnsQueryType query_type, |
| absl::optional<base::TimeTicks> expiration, |
| base::Time timed_expiration, |
| Source source, |
| std::vector<IPEndPoint> endpoints, |
| std::vector<std::string> strings, |
| std::vector<HostPortPair> hosts) |
| : HostResolverInternalResult(std::move(domain_name), |
| query_type, |
| expiration, |
| timed_expiration, |
| Type::kData, |
| source), |
| endpoints_(std::move(endpoints)), |
| strings_(std::move(strings)), |
| hosts_(std::move(hosts)) { |
| DCHECK(!endpoints_.empty() || !strings_.empty() || !hosts_.empty()); |
| } |
| |
| HostResolverInternalDataResult::~HostResolverInternalDataResult() = default; |
| |
| base::Value HostResolverInternalDataResult::ToValue() const { |
| base::Value::Dict dict = ToValueBaseDict(); |
| |
| base::Value::List endpoints_list; |
| endpoints_list.reserve(endpoints_.size()); |
| for (IPEndPoint endpoint : endpoints_) { |
| endpoints_list.Append(endpoint.ToValue()); |
| } |
| dict.Set(kValueEndpointsKey, std::move(endpoints_list)); |
| |
| base::Value::List strings_list; |
| strings_list.reserve(strings_.size()); |
| for (const std::string& string : strings_) { |
| strings_list.Append(string); |
| } |
| dict.Set(kValueStringsKey, std::move(strings_list)); |
| |
| base::Value::List hosts_list; |
| hosts_list.reserve(hosts_.size()); |
| for (const HostPortPair& host : hosts_) { |
| hosts_list.Append(host.ToValue()); |
| } |
| dict.Set(kValueHostsKey, std::move(hosts_list)); |
| |
| return base::Value(std::move(dict)); |
| } |
| |
| HostResolverInternalDataResult::HostResolverInternalDataResult( |
| const base::Value::Dict& dict, |
| std::vector<IPEndPoint> endpoints, |
| std::vector<std::string> strings, |
| std::vector<HostPortPair> hosts) |
| : HostResolverInternalResult(dict), |
| endpoints_(std::move(endpoints)), |
| strings_(std::move(strings)), |
| hosts_(std::move(hosts)) {} |
| |
| // static |
| std::unique_ptr<HostResolverInternalMetadataResult> |
| HostResolverInternalMetadataResult::FromValue(const base::Value& value) { |
| const base::Value::Dict* dict = value.GetIfDict(); |
| if (!dict || !ValidateValueBaseDict(*dict, /*require_timed_expiration=*/true)) |
| return nullptr; |
| |
| const base::Value::List* metadata_values = dict->FindList(kValueMetadatasKey); |
| if (!metadata_values) |
| return nullptr; |
| |
| std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> metadatas; |
| for (const base::Value& metadata_value : *metadata_values) { |
| absl::optional<std::pair<HttpsRecordPriority, ConnectionEndpointMetadata>> |
| metadata = EndpointMetadataPairFromValue(metadata_value); |
| if (!metadata.has_value()) |
| return nullptr; |
| metadatas.insert(std::move(metadata).value()); |
| } |
| |
| // WrapUnique due to private constructor. |
| return base::WrapUnique( |
| new HostResolverInternalMetadataResult(*dict, std::move(metadatas))); |
| } |
| |
| HostResolverInternalMetadataResult::HostResolverInternalMetadataResult( |
| std::string domain_name, |
| DnsQueryType query_type, |
| absl::optional<base::TimeTicks> expiration, |
| base::Time timed_expiration, |
| Source source, |
| std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> metadatas) |
| : HostResolverInternalResult(std::move(domain_name), |
| query_type, |
| expiration, |
| timed_expiration, |
| Type::kMetadata, |
| source), |
| metadatas_(std::move(metadatas)) {} |
| |
| HostResolverInternalMetadataResult::~HostResolverInternalMetadataResult() = |
| default; |
| |
| base::Value HostResolverInternalMetadataResult::ToValue() const { |
| base::Value::Dict dict = ToValueBaseDict(); |
| |
| base::Value::List metadatas_list; |
| metadatas_list.reserve(metadatas_.size()); |
| for (const std::pair<const HttpsRecordPriority, ConnectionEndpointMetadata>& |
| metadata_pair : metadatas_) { |
| metadatas_list.Append(EndpointMetadataPairToValue(metadata_pair)); |
| } |
| dict.Set(kValueMetadatasKey, std::move(metadatas_list)); |
| |
| return base::Value(std::move(dict)); |
| } |
| |
| HostResolverInternalMetadataResult::HostResolverInternalMetadataResult( |
| const base::Value::Dict& dict, |
| std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> metadatas) |
| : HostResolverInternalResult(dict), metadatas_(std::move(metadatas)) {} |
| |
| // static |
| std::unique_ptr<HostResolverInternalErrorResult> |
| HostResolverInternalErrorResult::FromValue(const base::Value& value) { |
| const base::Value::Dict* dict = value.GetIfDict(); |
| if (!dict || |
| !ValidateValueBaseDict(*dict, /*require_timed_expiration=*/false)) { |
| return nullptr; |
| } |
| |
| absl::optional<int> error = dict->FindInt(kValueErrorKey); |
| if (!error.has_value()) |
| return nullptr; |
| |
| // WrapUnique due to private constructor. |
| return base::WrapUnique( |
| new HostResolverInternalErrorResult(*dict, error.value())); |
| } |
| |
| HostResolverInternalErrorResult::HostResolverInternalErrorResult( |
| std::string domain_name, |
| DnsQueryType query_type, |
| absl::optional<base::TimeTicks> expiration, |
| absl::optional<base::Time> timed_expiration, |
| Source source, |
| int error) |
| : HostResolverInternalResult(std::move(domain_name), |
| query_type, |
| expiration, |
| timed_expiration, |
| Type::kError, |
| source), |
| error_(error) {} |
| |
| base::Value HostResolverInternalErrorResult::ToValue() const { |
| base::Value::Dict dict = ToValueBaseDict(); |
| |
| dict.Set(kValueErrorKey, error_); |
| |
| return base::Value(std::move(dict)); |
| } |
| |
| HostResolverInternalErrorResult::HostResolverInternalErrorResult( |
| const base::Value::Dict& dict, |
| int error) |
| : HostResolverInternalResult(dict), error_(error) { |
| DCHECK_NE(error_, OK); |
| } |
| |
| // static |
| std::unique_ptr<HostResolverInternalAliasResult> |
| HostResolverInternalAliasResult::FromValue(const base::Value& value) { |
| const base::Value::Dict* dict = value.GetIfDict(); |
| if (!dict || !ValidateValueBaseDict(*dict, /*require_timed_expiration=*/true)) |
| return nullptr; |
| |
| const std::string* target = dict->FindString(kValueAliasTargetKey); |
| if (!target) |
| return nullptr; |
| |
| // WrapUnique due to private constructor. |
| return base::WrapUnique(new HostResolverInternalAliasResult(*dict, *target)); |
| } |
| |
| HostResolverInternalAliasResult::HostResolverInternalAliasResult( |
| std::string domain_name, |
| DnsQueryType query_type, |
| absl::optional<base::TimeTicks> expiration, |
| base::Time timed_expiration, |
| Source source, |
| std::string alias_target) |
| : HostResolverInternalResult(std::move(domain_name), |
| query_type, |
| expiration, |
| timed_expiration, |
| Type::kAlias, |
| source), |
| alias_target_(MaybeCanonicalizeName(std::move(alias_target))) { |
| DCHECK(!alias_target_.empty()); |
| } |
| |
| base::Value HostResolverInternalAliasResult::ToValue() const { |
| base::Value::Dict dict = ToValueBaseDict(); |
| |
| dict.Set(kValueAliasTargetKey, alias_target_); |
| |
| return base::Value(std::move(dict)); |
| } |
| |
| HostResolverInternalAliasResult::HostResolverInternalAliasResult( |
| const base::Value::Dict& dict, |
| std::string alias_target) |
| : HostResolverInternalResult(dict), |
| alias_target_(MaybeCanonicalizeName(std::move(alias_target))) {} |
| |
| } // namespace net |