xref: /aosp_15_r20/external/cronet/net/dns/dns_names_util.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/dns_names_util.h"
6 
7 #include <cstddef>
8 #include <cstdint>
9 #include <cstring>
10 #include <optional>
11 #include <string>
12 #include <string_view>
13 #include <vector>
14 
15 #include "base/check.h"
16 #include "base/containers/span.h"
17 #include "base/containers/span_reader.h"
18 #include "net/base/ip_address.h"
19 #include "net/base/url_util.h"
20 #include "net/dns/public/dns_protocol.h"
21 #include "url/third_party/mozilla/url_parse.h"
22 #include "url/url_canon.h"
23 #include "url/url_canon_stdstring.h"
24 
25 namespace net::dns_names_util {
26 
IsValidDnsName(std::string_view dotted_form_name)27 bool IsValidDnsName(std::string_view dotted_form_name) {
28   return DottedNameToNetwork(dotted_form_name,
29                              /*require_valid_internet_hostname=*/false)
30       .has_value();
31 }
32 
IsValidDnsRecordName(std::string_view dotted_form_name)33 bool IsValidDnsRecordName(std::string_view dotted_form_name) {
34   IPAddress ip_address;
35   return IsValidDnsName(dotted_form_name) &&
36          !HostStringIsLocalhost(dotted_form_name) &&
37          !ip_address.AssignFromIPLiteral(dotted_form_name) &&
38          !ParseURLHostnameToAddress(dotted_form_name, &ip_address);
39 }
40 
41 // Based on DJB's public domain code.
DottedNameToNetwork(std::string_view dotted_form_name,bool require_valid_internet_hostname)42 std::optional<std::vector<uint8_t>> DottedNameToNetwork(
43     std::string_view dotted_form_name,
44     bool require_valid_internet_hostname) {
45   // Use full IsCanonicalizedHostCompliant() validation if not
46   // `is_unrestricted`. All subsequent validity checks should not apply unless
47   // `is_unrestricted` because IsCanonicalizedHostCompliant() is expected to be
48   // more strict than any validation here.
49   if (require_valid_internet_hostname &&
50       !IsCanonicalizedHostCompliant(dotted_form_name))
51     return std::nullopt;
52 
53   const char* buf = dotted_form_name.data();
54   size_t n = dotted_form_name.size();
55   uint8_t label[dns_protocol::kMaxLabelLength];
56   size_t labellen = 0; /* <= sizeof label */
57   std::vector<uint8_t> name(dns_protocol::kMaxNameLength, 0);
58   size_t namelen = 0; /* <= sizeof name */
59   char ch;
60 
61   for (;;) {
62     if (!n)
63       break;
64     ch = *buf++;
65     --n;
66     if (ch == '.') {
67       // Don't allow empty labels per http://crbug.com/456391.
68       if (!labellen) {
69         DCHECK(!require_valid_internet_hostname);
70         return std::nullopt;
71       }
72       if (namelen + labellen + 1 > name.size()) {
73         DCHECK(!require_valid_internet_hostname);
74         return std::nullopt;
75       }
76       name[namelen++] = static_cast<uint8_t>(labellen);
77       memcpy(name.data() + namelen, label, labellen);
78       namelen += labellen;
79       labellen = 0;
80       continue;
81     }
82     if (labellen >= sizeof(label)) {
83       DCHECK(!require_valid_internet_hostname);
84       return std::nullopt;
85     }
86     label[labellen++] = ch;
87   }
88 
89   // Allow empty label at end of name to disable suffix search.
90   if (labellen) {
91     if (namelen + labellen + 1 > name.size()) {
92       DCHECK(!require_valid_internet_hostname);
93       return std::nullopt;
94     }
95     name[namelen++] = static_cast<uint8_t>(labellen);
96     memcpy(name.data() + namelen, label, labellen);
97     namelen += labellen;
98     labellen = 0;
99   }
100 
101   if (namelen + 1 > name.size()) {
102     DCHECK(!require_valid_internet_hostname);
103     return std::nullopt;
104   }
105   if (namelen == 0) {  // Empty names e.g. "", "." are not valid.
106     DCHECK(!require_valid_internet_hostname);
107     return std::nullopt;
108   }
109   name[namelen++] = 0;  // This is the root label (of length 0).
110 
111   name.resize(namelen);
112   return name;
113 }
114 
NetworkToDottedName(base::span<const uint8_t> span,bool require_complete)115 std::optional<std::string> NetworkToDottedName(base::span<const uint8_t> span,
116                                                bool require_complete) {
117   auto reader = base::SpanReader(span);
118   return NetworkToDottedName(reader, require_complete);
119 }
120 
NetworkToDottedName(base::SpanReader<const uint8_t> & reader,bool require_complete)121 std::optional<std::string> NetworkToDottedName(
122     base::SpanReader<const uint8_t>& reader,
123     bool require_complete) {
124   std::string ret;
125   size_t octets_read = 0u;
126   while (reader.remaining() > 0u) {
127     // DNS name compression not allowed because it does not make sense without
128     // the context of a full DNS message.
129     if ((reader.remaining_span()[0u] & dns_protocol::kLabelMask) ==
130         dns_protocol::kLabelPointer) {
131       return std::nullopt;
132     }
133 
134     base::span<const uint8_t> label;
135     if (!ReadU8LengthPrefixed(reader, &label)) {
136       return std::nullopt;
137     }
138 
139     // Final zero-length label not included in size enforcement.
140     if (!label.empty()) {
141       octets_read += label.size() + 1u;
142     }
143 
144     if (label.size() > dns_protocol::kMaxLabelLength) {
145       return std::nullopt;
146     }
147     if (octets_read > dns_protocol::kMaxNameLength) {
148       return std::nullopt;
149     }
150 
151     if (label.empty()) {
152       return ret;
153     }
154 
155     if (!ret.empty()) {
156       ret.append(".");
157     }
158 
159     ret.append(base::as_string_view(label));
160   }
161 
162   if (require_complete) {
163     return std::nullopt;
164   }
165 
166   // If terminating zero-length label was not included in the input, no need to
167   // recheck against max name length because terminating zero-length label does
168   // not count against the limit.
169 
170   return ret;
171 }
172 
ReadU8LengthPrefixed(base::SpanReader<const uint8_t> & reader,base::span<const uint8_t> * out)173 bool ReadU8LengthPrefixed(base::SpanReader<const uint8_t>& reader,
174                           base::span<const uint8_t>* out) {
175   base::SpanReader<const uint8_t> inner_reader = reader;
176   uint8_t len;
177   if (!inner_reader.ReadU8BigEndian(len)) {
178     return false;
179   }
180   std::optional<base::span<const uint8_t>> bytes = inner_reader.Read(len);
181   if (!bytes) {
182     return false;
183   }
184   *out = *bytes;
185   reader = inner_reader;
186   return true;
187 }
188 
ReadU16LengthPrefixed(base::SpanReader<const uint8_t> & reader,base::span<const uint8_t> * out)189 bool ReadU16LengthPrefixed(base::SpanReader<const uint8_t>& reader,
190                            base::span<const uint8_t>* out) {
191   base::SpanReader<const uint8_t> inner_reader = reader;
192   uint16_t len;
193   if (!inner_reader.ReadU16BigEndian(len)) {
194     return false;
195   }
196   std::optional<base::span<const uint8_t>> bytes = inner_reader.Read(len);
197   if (!bytes) {
198     return false;
199   }
200   *out = *bytes;
201   reader = inner_reader;
202   return true;
203 }
204 
UrlCanonicalizeNameIfAble(std::string_view name)205 std::string UrlCanonicalizeNameIfAble(std::string_view name) {
206   std::string canonicalized;
207   url::StdStringCanonOutput output(&canonicalized);
208   url::CanonHostInfo host_info;
209   url::CanonicalizeHostVerbose(name.data(), url::Component(0, name.size()),
210                                &output, &host_info);
211 
212   if (host_info.family == url::CanonHostInfo::Family::BROKEN) {
213     return std::string(name);
214   }
215 
216   output.Complete();
217   return canonicalized;
218 }
219 
220 }  // namespace net::dns_names_util
221