xref: /aosp_15_r20/external/cronet/net/dns/public/dns_over_https_config.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/dns/public/dns_over_https_config.h"
6 
7 #include <iterator>
8 #include <optional>
9 #include <string>
10 #include <string_view>
11 #include <vector>
12 
13 #include "base/json/json_reader.h"
14 #include "base/json/json_writer.h"
15 #include "base/ranges/algorithm.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/values.h"
19 #include "net/dns/public/dns_over_https_server_config.h"
20 #include "net/dns/public/util.h"
21 
22 namespace net {
23 
24 namespace {
25 
SplitGroup(std::string_view group)26 std::vector<std::string> SplitGroup(std::string_view group) {
27   // Templates in a group are whitespace-separated.
28   return SplitString(group, base::kWhitespaceASCII, base::TRIM_WHITESPACE,
29                      base::SPLIT_WANT_NONEMPTY);
30 }
31 
ParseTemplates(std::vector<std::string> templates)32 std::vector<std::optional<DnsOverHttpsServerConfig>> ParseTemplates(
33     std::vector<std::string> templates) {
34   std::vector<std::optional<DnsOverHttpsServerConfig>> parsed;
35   parsed.reserve(templates.size());
36   base::ranges::transform(templates, std::back_inserter(parsed), [](auto& s) {
37     return DnsOverHttpsServerConfig::FromString(std::move(s));
38   });
39   return parsed;
40 }
41 
42 constexpr std::string_view kJsonKeyServers("servers");
43 
FromValue(base::Value::Dict value)44 std::optional<DnsOverHttpsConfig> FromValue(base::Value::Dict value) {
45   base::Value::List* servers_value = value.FindList(kJsonKeyServers);
46   if (!servers_value)
47     return std::nullopt;
48   std::vector<DnsOverHttpsServerConfig> servers;
49   servers.reserve(servers_value->size());
50   for (base::Value& elt : *servers_value) {
51     base::Value::Dict* dict = elt.GetIfDict();
52     if (!dict)
53       return std::nullopt;
54     auto parsed = DnsOverHttpsServerConfig::FromValue(std::move(*dict));
55     if (!parsed.has_value())
56       return std::nullopt;
57     servers.push_back(std::move(*parsed));
58   }
59   return DnsOverHttpsConfig(servers);
60 }
61 
FromJson(std::string_view json)62 std::optional<DnsOverHttpsConfig> FromJson(std::string_view json) {
63   std::optional<base::Value> value = base::JSONReader::Read(json);
64   if (!value || !value->is_dict())
65     return std::nullopt;
66   return FromValue(std::move(*value).TakeDict());
67 }
68 
69 }  // namespace
70 
71 DnsOverHttpsConfig::DnsOverHttpsConfig() = default;
72 DnsOverHttpsConfig::~DnsOverHttpsConfig() = default;
73 DnsOverHttpsConfig::DnsOverHttpsConfig(const DnsOverHttpsConfig& other) =
74     default;
75 DnsOverHttpsConfig& DnsOverHttpsConfig::operator=(
76     const DnsOverHttpsConfig& other) = default;
77 DnsOverHttpsConfig::DnsOverHttpsConfig(DnsOverHttpsConfig&& other) = default;
78 DnsOverHttpsConfig& DnsOverHttpsConfig::operator=(DnsOverHttpsConfig&& other) =
79     default;
80 
DnsOverHttpsConfig(std::vector<DnsOverHttpsServerConfig> servers)81 DnsOverHttpsConfig::DnsOverHttpsConfig(
82     std::vector<DnsOverHttpsServerConfig> servers)
83     : servers_(std::move(servers)) {}
84 
85 // static
FromTemplates(std::vector<std::string> server_templates)86 std::optional<DnsOverHttpsConfig> DnsOverHttpsConfig::FromTemplates(
87     std::vector<std::string> server_templates) {
88   // All templates must be valid for the group to be considered valid.
89   std::vector<DnsOverHttpsServerConfig> servers;
90   for (auto& server_config : ParseTemplates(server_templates)) {
91     if (!server_config)
92       return std::nullopt;
93     servers.push_back(std::move(*server_config));
94   }
95   return DnsOverHttpsConfig(std::move(servers));
96 }
97 
98 // static
FromTemplatesForTesting(std::vector<std::string> server_templates)99 std::optional<DnsOverHttpsConfig> DnsOverHttpsConfig::FromTemplatesForTesting(
100     std::vector<std::string> server_templates) {
101   return FromTemplates(std::move(server_templates));
102 }
103 
104 // static
FromString(std::string_view doh_config)105 std::optional<DnsOverHttpsConfig> DnsOverHttpsConfig::FromString(
106     std::string_view doh_config) {
107   std::optional<DnsOverHttpsConfig> parsed = FromJson(doh_config);
108   if (parsed && !parsed->servers().empty())
109     return parsed;
110   std::vector<std::string> server_templates = SplitGroup(doh_config);
111   if (server_templates.empty())
112     return std::nullopt;  // `doh_config` must contain at least one server.
113   return FromTemplates(std::move(server_templates));
114 }
115 
116 // static
FromStringLax(std::string_view doh_config)117 DnsOverHttpsConfig DnsOverHttpsConfig::FromStringLax(
118     std::string_view doh_config) {
119   if (std::optional<DnsOverHttpsConfig> parsed = FromJson(doh_config)) {
120     return *parsed;
121   }
122   auto parsed = ParseTemplates(SplitGroup(doh_config));
123   std::vector<DnsOverHttpsServerConfig> servers;
124   for (auto& server_config : parsed) {
125     if (server_config)
126       servers.push_back(std::move(*server_config));
127   }
128   return DnsOverHttpsConfig(std::move(servers));
129 }
130 
operator ==(const DnsOverHttpsConfig & other) const131 bool DnsOverHttpsConfig::operator==(const DnsOverHttpsConfig& other) const {
132   return servers() == other.servers();
133 }
134 
ToString() const135 std::string DnsOverHttpsConfig::ToString() const {
136   if (base::ranges::all_of(servers(), &DnsOverHttpsServerConfig::IsSimple)) {
137     // Return the templates on separate lines.
138     std::vector<std::string_view> strings;
139     strings.reserve(servers().size());
140     base::ranges::transform(servers(), std::back_inserter(strings),
141                             &DnsOverHttpsServerConfig::server_template_piece);
142     return base::JoinString(std::move(strings), "\n");
143   }
144   std::string json;
145   CHECK(base::JSONWriter::WriteWithOptions(
146       ToValue(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &json));
147   // Remove the trailing newline from pretty-print output.
148   base::TrimWhitespaceASCII(json, base::TRIM_TRAILING, &json);
149   return json;
150 }
151 
ToValue() const152 base::Value::Dict DnsOverHttpsConfig::ToValue() const {
153   base::Value::List list;
154   list.reserve(servers().size());
155   for (const auto& server : servers()) {
156     list.Append(server.ToValue());
157   }
158   base::Value::Dict dict;
159   dict.Set(kJsonKeyServers, std::move(list));
160   return dict;
161 }
162 
163 }  // namespace net
164