xref: /aosp_15_r20/external/cronet/net/dns/public/dns_over_https_server_config.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2020 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_server_config.h"
6 
7 #include <optional>
8 #include <set>
9 #include <string>
10 #include <string_view>
11 #include <unordered_map>
12 
13 #include "base/containers/contains.h"
14 #include "base/json/json_reader.h"
15 #include "base/json/json_writer.h"
16 #include "base/values.h"
17 #include "net/third_party/uri_template/uri_template.h"
18 #include "url/url_canon.h"
19 #include "url/url_canon_stdstring.h"
20 #include "url/url_constants.h"
21 
22 namespace {
23 
GetHttpsHost(const std::string & url)24 std::optional<std::string> GetHttpsHost(const std::string& url) {
25   // This code is used to compute a static initializer, so it runs before GURL's
26   // scheme registry is initialized.  Since GURL is not ready yet, we need to
27   // duplicate some of its functionality here.
28   url::Parsed parsed;
29   url::ParseStandardURL(url.data(), url.size(), &parsed);
30   std::string canonical;
31   url::StdStringCanonOutput output(&canonical);
32   url::Parsed canonical_parsed;
33   bool is_valid = url::CanonicalizeStandardURL(
34       url.data(), parsed, url::SchemeType::SCHEME_WITH_HOST_AND_PORT, nullptr,
35       &output, &canonical_parsed);
36   if (!is_valid)
37     return std::nullopt;
38   const url::Component& scheme_range = canonical_parsed.scheme;
39   std::string_view scheme =
40       std::string_view(canonical).substr(scheme_range.begin, scheme_range.len);
41   if (scheme != url::kHttpsScheme)
42     return std::nullopt;
43   const url::Component& host_range = canonical_parsed.host;
44   return canonical.substr(host_range.begin, host_range.len);
45 }
46 
IsValidDohTemplate(const std::string & server_template,bool * use_post)47 bool IsValidDohTemplate(const std::string& server_template, bool* use_post) {
48   std::string url_string;
49   std::string test_query = "this_is_a_test_query";
50   std::unordered_map<std::string, std::string> template_params(
51       {{"dns", test_query}});
52   std::set<std::string> vars_found;
53   bool valid_template = uri_template::Expand(server_template, template_params,
54                                              &url_string, &vars_found);
55   if (!valid_template) {
56     // The URI template is malformed.
57     return false;
58   }
59   std::optional<std::string> host = GetHttpsHost(url_string);
60   if (!host) {
61     // The expanded template must be a valid HTTPS URL.
62     return false;
63   }
64   if (host->find(test_query) != std::string::npos) {
65     // The dns variable must not be part of the hostname.
66     return false;
67   }
68   // If the template contains a dns variable, use GET, otherwise use POST.
69   *use_post = !base::Contains(vars_found, "dns");
70   return true;
71 }
72 
73 constexpr std::string_view kJsonKeyTemplate("template");
74 constexpr std::string_view kJsonKeyEndpoints("endpoints");
75 constexpr std::string_view kJsonKeyIps("ips");
76 
77 }  // namespace
78 
79 namespace net {
80 
DnsOverHttpsServerConfig(std::string server_template,bool use_post,Endpoints endpoints)81 DnsOverHttpsServerConfig::DnsOverHttpsServerConfig(std::string server_template,
82                                                    bool use_post,
83                                                    Endpoints endpoints)
84     : server_template_(std::move(server_template)),
85       use_post_(use_post),
86       endpoints_(std::move(endpoints)) {}
87 
88 DnsOverHttpsServerConfig::DnsOverHttpsServerConfig() = default;
89 DnsOverHttpsServerConfig::DnsOverHttpsServerConfig(
90     const DnsOverHttpsServerConfig& other) = default;
91 DnsOverHttpsServerConfig& DnsOverHttpsServerConfig::operator=(
92     const DnsOverHttpsServerConfig& other) = default;
93 DnsOverHttpsServerConfig::DnsOverHttpsServerConfig(
94     DnsOverHttpsServerConfig&& other) = default;
95 DnsOverHttpsServerConfig& DnsOverHttpsServerConfig::operator=(
96     DnsOverHttpsServerConfig&& other) = default;
97 
98 DnsOverHttpsServerConfig::~DnsOverHttpsServerConfig() = default;
99 
FromString(std::string doh_template,Endpoints bindings)100 std::optional<DnsOverHttpsServerConfig> DnsOverHttpsServerConfig::FromString(
101     std::string doh_template,
102     Endpoints bindings) {
103   bool use_post;
104   if (!IsValidDohTemplate(doh_template, &use_post))
105     return std::nullopt;
106   return DnsOverHttpsServerConfig(std::move(doh_template), use_post,
107                                   std::move(bindings));
108 }
109 
operator ==(const DnsOverHttpsServerConfig & other) const110 bool DnsOverHttpsServerConfig::operator==(
111     const DnsOverHttpsServerConfig& other) const {
112   // use_post_ is derived from server_template_, so we don't need to compare it.
113   return server_template_ == other.server_template_ &&
114          endpoints_ == other.endpoints_;
115 }
116 
operator <(const DnsOverHttpsServerConfig & other) const117 bool DnsOverHttpsServerConfig::operator<(
118     const DnsOverHttpsServerConfig& other) const {
119   return std::tie(server_template_, endpoints_) <
120          std::tie(other.server_template_, other.endpoints_);
121 }
122 
server_template() const123 const std::string& DnsOverHttpsServerConfig::server_template() const {
124   return server_template_;
125 }
126 
server_template_piece() const127 std::string_view DnsOverHttpsServerConfig::server_template_piece() const {
128   return server_template_;
129 }
130 
use_post() const131 bool DnsOverHttpsServerConfig::use_post() const {
132   return use_post_;
133 }
134 
endpoints() const135 const DnsOverHttpsServerConfig::Endpoints& DnsOverHttpsServerConfig::endpoints()
136     const {
137   return endpoints_;
138 }
139 
IsSimple() const140 bool DnsOverHttpsServerConfig::IsSimple() const {
141   return endpoints_.empty();
142 }
143 
ToValue() const144 base::Value::Dict DnsOverHttpsServerConfig::ToValue() const {
145   base::Value::Dict value;
146   value.Set(kJsonKeyTemplate, server_template());
147   if (!endpoints_.empty()) {
148     base::Value::List bindings;
149     bindings.reserve(endpoints_.size());
150     for (const IPAddressList& ip_list : endpoints_) {
151       base::Value::Dict binding;
152       base::Value::List ips;
153       ips.reserve(ip_list.size());
154       for (const IPAddress& ip : ip_list) {
155         ips.Append(ip.ToString());
156       }
157       binding.Set(kJsonKeyIps, std::move(ips));
158       bindings.Append(std::move(binding));
159     }
160     value.Set(kJsonKeyEndpoints, std::move(bindings));
161   }
162   return value;
163 }
164 
165 // static
FromValue(base::Value::Dict value)166 std::optional<DnsOverHttpsServerConfig> DnsOverHttpsServerConfig::FromValue(
167     base::Value::Dict value) {
168   std::string* server_template = value.FindString(kJsonKeyTemplate);
169   if (!server_template)
170     return std::nullopt;
171   bool use_post;
172   if (!IsValidDohTemplate(*server_template, &use_post))
173     return std::nullopt;
174   Endpoints endpoints;
175   const base::Value* endpoints_json = value.Find(kJsonKeyEndpoints);
176   if (endpoints_json) {
177     if (!endpoints_json->is_list())
178       return std::nullopt;
179     const base::Value::List& json_list = endpoints_json->GetList();
180     endpoints.reserve(json_list.size());
181     for (const base::Value& endpoint : json_list) {
182       const base::Value::Dict* dict = endpoint.GetIfDict();
183       if (!dict)
184         return std::nullopt;
185       IPAddressList parsed_ips;
186       const base::Value* ips = dict->Find(kJsonKeyIps);
187       if (ips) {
188         const base::Value::List* ip_list = ips->GetIfList();
189         if (!ip_list)
190           return std::nullopt;
191         parsed_ips.reserve(ip_list->size());
192         for (const base::Value& ip : *ip_list) {
193           const std::string* ip_str = ip.GetIfString();
194           if (!ip_str)
195             return std::nullopt;
196           IPAddress parsed;
197           if (!parsed.AssignFromIPLiteral(*ip_str))
198             return std::nullopt;
199           parsed_ips.push_back(std::move(parsed));
200         }
201       }
202       endpoints.push_back(std::move(parsed_ips));
203     }
204   }
205   return DnsOverHttpsServerConfig(std::move(*server_template), use_post,
206                                   std::move(endpoints));
207 }
208 
209 }  // namespace net
210