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 <string>
11 #include <vector>
12
13 #include "base/big_endian.h"
14 #include "base/check.h"
15 #include "base/containers/span.h"
16 #include "base/strings/string_piece.h"
17 #include "net/base/ip_address.h"
18 #include "net/base/url_util.h"
19 #include "net/dns/public/dns_protocol.h"
20 #include "third_party/abseil-cpp/absl/types/optional.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(base::StringPiece dotted_form_name)27 bool IsValidDnsName(base::StringPiece dotted_form_name) {
28 return DottedNameToNetwork(dotted_form_name,
29 /*require_valid_internet_hostname=*/false)
30 .has_value();
31 }
32
IsValidDnsRecordName(base::StringPiece dotted_form_name)33 bool IsValidDnsRecordName(base::StringPiece 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(base::StringPiece dotted_form_name,bool require_valid_internet_hostname)42 absl::optional<std::vector<uint8_t>> DottedNameToNetwork(
43 base::StringPiece 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 absl::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 absl::nullopt;
71 }
72 if (namelen + labellen + 1 > name.size()) {
73 DCHECK(!require_valid_internet_hostname);
74 return absl::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 absl::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 absl::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 absl::nullopt;
104 }
105 if (namelen == 0) { // Empty names e.g. "", "." are not valid.
106 DCHECK(!require_valid_internet_hostname);
107 return absl::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> dns_network_wire_name,bool require_complete)115 absl::optional<std::string> NetworkToDottedName(
116 base::span<const uint8_t> dns_network_wire_name,
117 bool require_complete) {
118 base::BigEndianReader reader(dns_network_wire_name.data(),
119 dns_network_wire_name.size());
120 return NetworkToDottedName(reader, require_complete);
121 }
122
NetworkToDottedName(base::StringPiece dns_network_wire_name,bool require_complete)123 absl::optional<std::string> NetworkToDottedName(
124 base::StringPiece dns_network_wire_name,
125 bool require_complete) {
126 auto reader = base::BigEndianReader::FromStringPiece(dns_network_wire_name);
127 return NetworkToDottedName(reader, require_complete);
128 }
129
NetworkToDottedName(base::BigEndianReader & reader,bool require_complete)130 absl::optional<std::string> NetworkToDottedName(base::BigEndianReader& reader,
131 bool require_complete) {
132 std::string ret;
133 size_t octets_read = 0;
134 while (reader.remaining() > 0) {
135 // DNS name compression not allowed because it does not make sense without
136 // the context of a full DNS message.
137 if ((*reader.ptr() & dns_protocol::kLabelMask) ==
138 dns_protocol::kLabelPointer)
139 return absl::nullopt;
140
141 base::StringPiece label;
142 if (!reader.ReadU8LengthPrefixed(&label))
143 return absl::nullopt;
144
145 // Final zero-length label not included in size enforcement.
146 if (label.size() != 0)
147 octets_read += label.size() + 1;
148
149 if (label.size() > dns_protocol::kMaxLabelLength)
150 return absl::nullopt;
151 if (octets_read > dns_protocol::kMaxNameLength)
152 return absl::nullopt;
153
154 if (label.size() == 0)
155 return ret;
156
157 if (!ret.empty())
158 ret.append(".");
159
160 ret.append(label.data(), label.size());
161 }
162
163 if (require_complete)
164 return absl::nullopt;
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
UrlCanonicalizeNameIfAble(base::StringPiece name)173 std::string UrlCanonicalizeNameIfAble(base::StringPiece name) {
174 std::string canonicalized;
175 url::StdStringCanonOutput output(&canonicalized);
176 url::CanonHostInfo host_info;
177 url::CanonicalizeHostVerbose(name.data(), url::Component(0, name.size()),
178 &output, &host_info);
179
180 if (host_info.family == url::CanonHostInfo::Family::BROKEN) {
181 return std::string(name);
182 }
183
184 output.Complete();
185 return canonicalized;
186 }
187
188 } // namespace net::dns_names_util
189