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