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 std::vector<uint8_t> name;
54 name.reserve(dns_protocol::kMaxNameLength);
55
56 auto iter = dotted_form_name.begin();
57 while (iter != dotted_form_name.end()) {
58 auto pos = std::find(iter, dotted_form_name.end(), '.');
59 size_t labellen = std::distance(iter, pos);
60 // Don't allow empty labels per http://crbug.com/456391.
61 if (!labellen) {
62 DCHECK(!require_valid_internet_hostname);
63 return std::nullopt;
64 }
65 // `2` includes the length byte and the terminating '\0' byte.
66 if (name.size() + labellen + 2 > dns_protocol::kMaxNameLength ||
67 labellen > dns_protocol::kMaxLabelLength) {
68 DCHECK(!require_valid_internet_hostname);
69 return std::nullopt;
70 }
71 // This cast is safe because kMaxLabelLength < 255.
72 name.push_back(static_cast<uint8_t>(labellen));
73 name.insert(name.end(), iter, pos);
74 if (pos == dotted_form_name.end()) {
75 break;
76 }
77 iter = pos + 1;
78 }
79
80 if (name.empty()) { // Empty names e.g. "", "." are not valid.
81 DCHECK(!require_valid_internet_hostname);
82 return std::nullopt;
83 }
84 name.push_back(0); // This is the root label (of length 0).
85
86 return name;
87 }
88
NetworkToDottedName(base::span<const uint8_t> span,bool require_complete)89 std::optional<std::string> NetworkToDottedName(base::span<const uint8_t> span,
90 bool require_complete) {
91 auto reader = base::SpanReader(span);
92 return NetworkToDottedName(reader, require_complete);
93 }
94
NetworkToDottedName(base::SpanReader<const uint8_t> & reader,bool require_complete)95 std::optional<std::string> NetworkToDottedName(
96 base::SpanReader<const uint8_t>& reader,
97 bool require_complete) {
98 std::string ret;
99 size_t octets_read = 0u;
100 while (reader.remaining() > 0u) {
101 // DNS name compression not allowed because it does not make sense without
102 // the context of a full DNS message.
103 if ((reader.remaining_span()[0u] & dns_protocol::kLabelMask) ==
104 dns_protocol::kLabelPointer) {
105 return std::nullopt;
106 }
107
108 base::span<const uint8_t> label;
109 if (!ReadU8LengthPrefixed(reader, &label)) {
110 return std::nullopt;
111 }
112
113 // Final zero-length label not included in size enforcement.
114 if (!label.empty()) {
115 octets_read += label.size() + 1u;
116 }
117
118 if (label.size() > dns_protocol::kMaxLabelLength) {
119 return std::nullopt;
120 }
121 if (octets_read > dns_protocol::kMaxNameLength) {
122 return std::nullopt;
123 }
124
125 if (label.empty()) {
126 return ret;
127 }
128
129 if (!ret.empty()) {
130 ret.append(".");
131 }
132
133 ret.append(base::as_string_view(label));
134 }
135
136 if (require_complete) {
137 return std::nullopt;
138 }
139
140 // If terminating zero-length label was not included in the input, no need to
141 // recheck against max name length because terminating zero-length label does
142 // not count against the limit.
143
144 return ret;
145 }
146
ReadU8LengthPrefixed(base::SpanReader<const uint8_t> & reader,base::span<const uint8_t> * out)147 bool ReadU8LengthPrefixed(base::SpanReader<const uint8_t>& reader,
148 base::span<const uint8_t>* out) {
149 base::SpanReader<const uint8_t> inner_reader = reader;
150 uint8_t len;
151 if (!inner_reader.ReadU8BigEndian(len)) {
152 return false;
153 }
154 std::optional<base::span<const uint8_t>> bytes = inner_reader.Read(len);
155 if (!bytes) {
156 return false;
157 }
158 *out = *bytes;
159 reader = inner_reader;
160 return true;
161 }
162
ReadU16LengthPrefixed(base::SpanReader<const uint8_t> & reader,base::span<const uint8_t> * out)163 bool ReadU16LengthPrefixed(base::SpanReader<const uint8_t>& reader,
164 base::span<const uint8_t>* out) {
165 base::SpanReader<const uint8_t> inner_reader = reader;
166 uint16_t len;
167 if (!inner_reader.ReadU16BigEndian(len)) {
168 return false;
169 }
170 std::optional<base::span<const uint8_t>> bytes = inner_reader.Read(len);
171 if (!bytes) {
172 return false;
173 }
174 *out = *bytes;
175 reader = inner_reader;
176 return true;
177 }
178
UrlCanonicalizeNameIfAble(std::string_view name)179 std::string UrlCanonicalizeNameIfAble(std::string_view name) {
180 std::string canonicalized;
181 url::StdStringCanonOutput output(&canonicalized);
182 url::CanonHostInfo host_info;
183 url::CanonicalizeHostVerbose(name.data(), url::Component(0, name.size()),
184 &output, &host_info);
185
186 if (host_info.family == url::CanonHostInfo::Family::BROKEN) {
187 return std::string(name);
188 }
189
190 output.Complete();
191 return canonicalized;
192 }
193
194 } // namespace net::dns_names_util
195