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