• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   std::string canonical;
29   url::StdStringCanonOutput output(&canonical);
30   url::Parsed canonical_parsed;
31   bool is_valid =
32       url::CanonicalizeStandardURL(url.data(), url::ParseStandardURL(url),
33                                    url::SchemeType::SCHEME_WITH_HOST_AND_PORT,
34                                    nullptr, &output, &canonical_parsed);
35   if (!is_valid)
36     return std::nullopt;
37   const url::Component& scheme_range = canonical_parsed.scheme;
38   std::string_view scheme =
39       std::string_view(canonical).substr(scheme_range.begin, scheme_range.len);
40   if (scheme != url::kHttpsScheme)
41     return std::nullopt;
42   const url::Component& host_range = canonical_parsed.host;
43   return canonical.substr(host_range.begin, host_range.len);
44 }
45 
IsValidDohTemplate(const std::string & server_template,bool * use_post)46 bool IsValidDohTemplate(const std::string& server_template, bool* use_post) {
47   std::string url_string;
48   std::string test_query = "this_is_a_test_query";
49   std::unordered_map<std::string, std::string> template_params(
50       {{"dns", test_query}});
51   std::set<std::string> vars_found;
52   bool valid_template = uri_template::Expand(server_template, template_params,
53                                              &url_string, &vars_found);
54   if (!valid_template) {
55     // The URI template is malformed.
56     return false;
57   }
58   std::optional<std::string> host = GetHttpsHost(url_string);
59   if (!host) {
60     // The expanded template must be a valid HTTPS URL.
61     return false;
62   }
63   if (host->find(test_query) != std::string::npos) {
64     // The dns variable must not be part of the hostname.
65     return false;
66   }
67   // If the template contains a dns variable, use GET, otherwise use POST.
68   *use_post = !base::Contains(vars_found, "dns");
69   return true;
70 }
71 
72 constexpr std::string_view kJsonKeyTemplate("template");
73 constexpr std::string_view kJsonKeyEndpoints("endpoints");
74 constexpr std::string_view kJsonKeyIps("ips");
75 
76 }  // namespace
77 
78 namespace net {
79 
DnsOverHttpsServerConfig(std::string server_template,bool use_post,Endpoints endpoints)80 DnsOverHttpsServerConfig::DnsOverHttpsServerConfig(std::string server_template,
81                                                    bool use_post,
82                                                    Endpoints endpoints)
83     : server_template_(std::move(server_template)),
84       use_post_(use_post),
85       endpoints_(std::move(endpoints)) {}
86 
87 DnsOverHttpsServerConfig::DnsOverHttpsServerConfig() = default;
88 DnsOverHttpsServerConfig::DnsOverHttpsServerConfig(
89     const DnsOverHttpsServerConfig& other) = default;
90 DnsOverHttpsServerConfig& DnsOverHttpsServerConfig::operator=(
91     const DnsOverHttpsServerConfig& other) = default;
92 DnsOverHttpsServerConfig::DnsOverHttpsServerConfig(
93     DnsOverHttpsServerConfig&& other) = default;
94 DnsOverHttpsServerConfig& DnsOverHttpsServerConfig::operator=(
95     DnsOverHttpsServerConfig&& other) = default;
96 
97 DnsOverHttpsServerConfig::~DnsOverHttpsServerConfig() = default;
98 
FromString(std::string doh_template,Endpoints bindings)99 std::optional<DnsOverHttpsServerConfig> DnsOverHttpsServerConfig::FromString(
100     std::string doh_template,
101     Endpoints bindings) {
102   bool use_post;
103   if (!IsValidDohTemplate(doh_template, &use_post))
104     return std::nullopt;
105   return DnsOverHttpsServerConfig(std::move(doh_template), use_post,
106                                   std::move(bindings));
107 }
108 
operator ==(const DnsOverHttpsServerConfig & other) const109 bool DnsOverHttpsServerConfig::operator==(
110     const DnsOverHttpsServerConfig& other) const {
111   // use_post_ is derived from server_template_, so we don't need to compare it.
112   return server_template_ == other.server_template_ &&
113          endpoints_ == other.endpoints_;
114 }
115 
operator <(const DnsOverHttpsServerConfig & other) const116 bool DnsOverHttpsServerConfig::operator<(
117     const DnsOverHttpsServerConfig& other) const {
118   return std::tie(server_template_, endpoints_) <
119          std::tie(other.server_template_, other.endpoints_);
120 }
121 
server_template() const122 const std::string& DnsOverHttpsServerConfig::server_template() const {
123   return server_template_;
124 }
125 
server_template_piece() const126 std::string_view DnsOverHttpsServerConfig::server_template_piece() const {
127   return server_template_;
128 }
129 
use_post() const130 bool DnsOverHttpsServerConfig::use_post() const {
131   return use_post_;
132 }
133 
endpoints() const134 const DnsOverHttpsServerConfig::Endpoints& DnsOverHttpsServerConfig::endpoints()
135     const {
136   return endpoints_;
137 }
138 
IsSimple() const139 bool DnsOverHttpsServerConfig::IsSimple() const {
140   return endpoints_.empty();
141 }
142 
ToValue() const143 base::Value::Dict DnsOverHttpsServerConfig::ToValue() const {
144   base::Value::Dict value;
145   value.Set(kJsonKeyTemplate, server_template());
146   if (!endpoints_.empty()) {
147     base::Value::List bindings;
148     bindings.reserve(endpoints_.size());
149     for (const IPAddressList& ip_list : endpoints_) {
150       base::Value::Dict binding;
151       base::Value::List ips;
152       ips.reserve(ip_list.size());
153       for (const IPAddress& ip : ip_list) {
154         ips.Append(ip.ToString());
155       }
156       binding.Set(kJsonKeyIps, std::move(ips));
157       bindings.Append(std::move(binding));
158     }
159     value.Set(kJsonKeyEndpoints, std::move(bindings));
160   }
161   return value;
162 }
163 
164 // static
FromValue(base::Value::Dict value)165 std::optional<DnsOverHttpsServerConfig> DnsOverHttpsServerConfig::FromValue(
166     base::Value::Dict value) {
167   std::string* server_template = value.FindString(kJsonKeyTemplate);
168   if (!server_template)
169     return std::nullopt;
170   bool use_post;
171   if (!IsValidDohTemplate(*server_template, &use_post))
172     return std::nullopt;
173   Endpoints endpoints;
174   const base::Value* endpoints_json = value.Find(kJsonKeyEndpoints);
175   if (endpoints_json) {
176     if (!endpoints_json->is_list())
177       return std::nullopt;
178     const base::Value::List& json_list = endpoints_json->GetList();
179     endpoints.reserve(json_list.size());
180     for (const base::Value& endpoint : json_list) {
181       const base::Value::Dict* dict = endpoint.GetIfDict();
182       if (!dict)
183         return std::nullopt;
184       IPAddressList parsed_ips;
185       const base::Value* ips = dict->Find(kJsonKeyIps);
186       if (ips) {
187         const base::Value::List* ip_list = ips->GetIfList();
188         if (!ip_list)
189           return std::nullopt;
190         parsed_ips.reserve(ip_list->size());
191         for (const base::Value& ip : *ip_list) {
192           const std::string* ip_str = ip.GetIfString();
193           if (!ip_str)
194             return std::nullopt;
195           IPAddress parsed;
196           if (!parsed.AssignFromIPLiteral(*ip_str))
197             return std::nullopt;
198           parsed_ips.push_back(std::move(parsed));
199         }
200       }
201       endpoints.push_back(std::move(parsed_ips));
202     }
203   }
204   return DnsOverHttpsServerConfig(std::move(*server_template), use_post,
205                                   std::move(endpoints));
206 }
207 
208 }  // namespace net
209