1 #include "quiche/http2/adapter/header_validator.h"
2
3 #include <array>
4 #include <bitset>
5
6 #include "absl/strings/ascii.h"
7 #include "absl/strings/escaping.h"
8 #include "absl/strings/numbers.h"
9 #include "absl/strings/str_cat.h"
10 #include "quiche/http2/adapter/header_validator_base.h"
11 #include "quiche/http2/http2_constants.h"
12 #include "quiche/common/platform/api/quiche_logging.h"
13
14 namespace http2 {
15 namespace adapter {
16
17 namespace {
18
19 // From RFC 9110 Section 5.6.2.
20 constexpr absl::string_view kHttpTokenChars =
21 "!#$%&'*+-.^_`|~0123456789"
22 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
23
24 constexpr absl::string_view kHttp2HeaderNameAllowedChars =
25 "!#$%&'*+-.0123456789"
26 "^_`abcdefghijklmnopqrstuvwxyz|~";
27
28 constexpr absl::string_view kHttp2HeaderValueAllowedChars =
29 "\t "
30 "!\"#$%&'()*+,-./"
31 "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
32 "abcdefghijklmnopqrstuvwxyz{|}~";
33
34 constexpr absl::string_view kHttp2StatusValueAllowedChars = "0123456789";
35
36 constexpr absl::string_view kValidAuthorityChars =
37 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()["
38 "]*+,;=:";
39
40 constexpr absl::string_view kValidPathChars =
41 "/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()"
42 "*+,;=:@?";
43
44 constexpr absl::string_view kValidPathCharsWithFragment =
45 "/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()"
46 "*+,;=:@?#";
47
48 using CharMap = std::array<bool, 256>;
49
BuildValidCharMap(absl::string_view valid_chars)50 constexpr CharMap BuildValidCharMap(absl::string_view valid_chars) {
51 CharMap map = {};
52 for (char c : valid_chars) {
53 // An array index must be a nonnegative integer, hence the cast to uint8_t.
54 map[static_cast<uint8_t>(c)] = true;
55 }
56 return map;
57 }
AllowObsText(CharMap map)58 constexpr CharMap AllowObsText(CharMap map) {
59 // Characters above 0x80 are allowed in header field values as `obs-text` in
60 // RFC 7230.
61 for (uint8_t c = 0xff; c >= 0x80; --c) {
62 map[c] = true;
63 }
64 return map;
65 }
66
AllCharsInMap(absl::string_view str,const CharMap & map)67 bool AllCharsInMap(absl::string_view str, const CharMap& map) {
68 for (char c : str) {
69 if (!map[static_cast<uint8_t>(c)]) {
70 return false;
71 }
72 }
73 return true;
74 }
75
IsValidStatus(absl::string_view status)76 bool IsValidStatus(absl::string_view status) {
77 static constexpr CharMap valid_chars =
78 BuildValidCharMap(kHttp2StatusValueAllowedChars);
79 return AllCharsInMap(status, valid_chars);
80 }
81
IsValidMethod(absl::string_view method)82 bool IsValidMethod(absl::string_view method) {
83 static constexpr CharMap valid_chars = BuildValidCharMap(kHttpTokenChars);
84 return AllCharsInMap(method, valid_chars);
85 }
86
87 } // namespace
88
StartHeaderBlock()89 void HeaderValidator::StartHeaderBlock() {
90 HeaderValidatorBase::StartHeaderBlock();
91 pseudo_headers_.reset();
92 pseudo_header_state_.reset();
93 authority_.clear();
94 }
95
RecordPseudoHeader(PseudoHeaderTag tag)96 void HeaderValidator::RecordPseudoHeader(PseudoHeaderTag tag) {
97 if (pseudo_headers_[tag]) {
98 pseudo_headers_[TAG_UNKNOWN_EXTRA] = true;
99 } else {
100 pseudo_headers_[tag] = true;
101 }
102 }
103
ValidateSingleHeader(absl::string_view key,absl::string_view value)104 HeaderValidator::HeaderStatus HeaderValidator::ValidateSingleHeader(
105 absl::string_view key, absl::string_view value) {
106 if (key.empty()) {
107 return HEADER_FIELD_INVALID;
108 }
109 if (max_field_size_.has_value() &&
110 key.size() + value.size() > *max_field_size_) {
111 QUICHE_VLOG(2) << "Header field size is " << key.size() + value.size()
112 << ", exceeds max size of " << *max_field_size_;
113 return HEADER_FIELD_TOO_LONG;
114 }
115 if (key[0] == ':') {
116 // Remove leading ':'.
117 key.remove_prefix(1);
118 if (key == "status") {
119 if (value.size() != 3 || !IsValidStatus(value)) {
120 QUICHE_VLOG(2) << "malformed status value: [" << absl::CEscape(value)
121 << "]";
122 return HEADER_FIELD_INVALID;
123 }
124 if (value == "101") {
125 // Switching protocols is not allowed on a HTTP/2 stream.
126 return HEADER_FIELD_INVALID;
127 }
128 status_ = std::string(value);
129 RecordPseudoHeader(TAG_STATUS);
130 } else if (key == "method") {
131 if (value == "OPTIONS") {
132 pseudo_header_state_[STATE_METHOD_IS_OPTIONS] = true;
133 } else if (value == "CONNECT") {
134 pseudo_header_state_[STATE_METHOD_IS_CONNECT] = true;
135 } else if (!IsValidMethod(value)) {
136 return HEADER_FIELD_INVALID;
137 }
138 RecordPseudoHeader(TAG_METHOD);
139 } else if (key == "authority") {
140 if (!ValidateAndSetAuthority(value)) {
141 return HEADER_FIELD_INVALID;
142 }
143 RecordPseudoHeader(TAG_AUTHORITY);
144 } else if (key == "path") {
145 if (value == "*") {
146 pseudo_header_state_[STATE_PATH_IS_STAR] = true;
147 } else if (value.empty()) {
148 pseudo_header_state_[STATE_PATH_IS_EMPTY] = true;
149 return HEADER_FIELD_INVALID;
150 } else if (validate_path_ &&
151 !IsValidPath(value, allow_fragment_in_path_)) {
152 return HEADER_FIELD_INVALID;
153 }
154 if (value[0] == '/') {
155 pseudo_header_state_[STATE_PATH_INITIAL_SLASH] = true;
156 }
157 RecordPseudoHeader(TAG_PATH);
158 } else if (key == "protocol") {
159 RecordPseudoHeader(TAG_PROTOCOL);
160 } else if (key == "scheme") {
161 RecordPseudoHeader(TAG_SCHEME);
162 } else {
163 pseudo_headers_[TAG_UNKNOWN_EXTRA] = true;
164 if (!IsValidHeaderName(key)) {
165 QUICHE_VLOG(2) << "invalid chars in header name: ["
166 << absl::CEscape(key) << "]";
167 return HEADER_FIELD_INVALID;
168 }
169 }
170 if (!IsValidHeaderValue(value, obs_text_option_)) {
171 QUICHE_VLOG(2) << "invalid chars in header value: ["
172 << absl::CEscape(value) << "]";
173 return HEADER_FIELD_INVALID;
174 }
175 } else {
176 std::string lowercase_key;
177 if (allow_uppercase_in_header_names_) {
178 // Convert header name to lowercase for validation and also for comparison
179 // to lowercase string literals below.
180 lowercase_key = absl::AsciiStrToLower(key);
181 key = lowercase_key;
182 }
183
184 if (!IsValidHeaderName(key)) {
185 QUICHE_VLOG(2) << "invalid chars in header name: [" << absl::CEscape(key)
186 << "]";
187 return HEADER_FIELD_INVALID;
188 }
189 if (!IsValidHeaderValue(value, obs_text_option_)) {
190 QUICHE_VLOG(2) << "invalid chars in header value: ["
191 << absl::CEscape(value) << "]";
192 return HEADER_FIELD_INVALID;
193 }
194 if (key == "host") {
195 if (pseudo_headers_[TAG_STATUS]) {
196 // Response headers can contain "Host".
197 } else {
198 if (!ValidateAndSetAuthority(value)) {
199 return HEADER_FIELD_INVALID;
200 }
201 pseudo_headers_[TAG_AUTHORITY] = true;
202 }
203 } else if (key == "content-length") {
204 const ContentLengthStatus status = HandleContentLength(value);
205 switch (status) {
206 case CONTENT_LENGTH_ERROR:
207 return HEADER_FIELD_INVALID;
208 case CONTENT_LENGTH_SKIP:
209 return HEADER_SKIP;
210 case CONTENT_LENGTH_OK:
211 return HEADER_OK;
212 default:
213 return HEADER_FIELD_INVALID;
214 }
215 } else if (key == "te" && value != "trailers") {
216 return HEADER_FIELD_INVALID;
217 } else if (key == "upgrade" || GetInvalidHttp2HeaderSet().contains(key)) {
218 // TODO(b/78024822): Remove the "upgrade" here once it's added to
219 // GetInvalidHttp2HeaderSet().
220 return HEADER_FIELD_INVALID;
221 }
222 }
223 return HEADER_OK;
224 }
225
226 // Returns true if all required pseudoheaders and no extra pseudoheaders are
227 // present for the given header type.
FinishHeaderBlock(HeaderType type)228 bool HeaderValidator::FinishHeaderBlock(HeaderType type) {
229 switch (type) {
230 case HeaderType::REQUEST:
231 return ValidateRequestHeaders(pseudo_headers_, pseudo_header_state_,
232 allow_extended_connect_);
233 case HeaderType::REQUEST_TRAILER:
234 return ValidateRequestTrailers(pseudo_headers_);
235 case HeaderType::RESPONSE_100:
236 case HeaderType::RESPONSE:
237 return ValidateResponseHeaders(pseudo_headers_);
238 case HeaderType::RESPONSE_TRAILER:
239 return ValidateResponseTrailers(pseudo_headers_);
240 }
241 return false;
242 }
243
IsValidHeaderName(absl::string_view name)244 bool HeaderValidator::IsValidHeaderName(absl::string_view name) {
245 static constexpr CharMap valid_chars =
246 BuildValidCharMap(kHttp2HeaderNameAllowedChars);
247 return AllCharsInMap(name, valid_chars);
248 }
249
IsValidHeaderValue(absl::string_view value,ObsTextOption option)250 bool HeaderValidator::IsValidHeaderValue(absl::string_view value,
251 ObsTextOption option) {
252 static constexpr CharMap valid_chars =
253 BuildValidCharMap(kHttp2HeaderValueAllowedChars);
254 static constexpr CharMap valid_chars_with_obs_text =
255 AllowObsText(BuildValidCharMap(kHttp2HeaderValueAllowedChars));
256 return AllCharsInMap(value, option == ObsTextOption::kAllow
257 ? valid_chars_with_obs_text
258 : valid_chars);
259 }
260
IsValidAuthority(absl::string_view authority)261 bool HeaderValidator::IsValidAuthority(absl::string_view authority) {
262 static constexpr CharMap valid_chars =
263 BuildValidCharMap(kValidAuthorityChars);
264 return AllCharsInMap(authority, valid_chars);
265 }
266
IsValidPath(absl::string_view path,bool allow_fragment)267 bool HeaderValidator::IsValidPath(absl::string_view path, bool allow_fragment) {
268 static constexpr CharMap valid_chars = BuildValidCharMap(kValidPathChars);
269 static constexpr CharMap valid_chars_with_fragment =
270 BuildValidCharMap(kValidPathCharsWithFragment);
271 if (allow_fragment) {
272 return AllCharsInMap(path, valid_chars_with_fragment);
273 } else {
274 return AllCharsInMap(path, valid_chars);
275 }
276 }
277
HandleContentLength(absl::string_view value)278 HeaderValidator::ContentLengthStatus HeaderValidator::HandleContentLength(
279 absl::string_view value) {
280 if (value.empty()) {
281 return CONTENT_LENGTH_ERROR;
282 }
283
284 if (status_ == "204" && value != "0") {
285 // There should be no body in a "204 No Content" response.
286 return CONTENT_LENGTH_ERROR;
287 }
288 if (!status_.empty() && status_[0] == '1' && value != "0") {
289 // There should also be no body in a 1xx response.
290 return CONTENT_LENGTH_ERROR;
291 }
292
293 size_t content_length = 0;
294 const bool valid = absl::SimpleAtoi(value, &content_length);
295 if (!valid) {
296 return CONTENT_LENGTH_ERROR;
297 }
298
299 if (content_length_.has_value()) {
300 return content_length == *content_length_ ? CONTENT_LENGTH_SKIP
301 : CONTENT_LENGTH_ERROR;
302 }
303 content_length_ = content_length;
304 return CONTENT_LENGTH_OK;
305 }
306
307 // Returns whether `authority` contains only characters from the `host` ABNF
308 // from RFC 3986 section 3.2.2.
ValidateAndSetAuthority(absl::string_view authority)309 bool HeaderValidator::ValidateAndSetAuthority(absl::string_view authority) {
310 if (!IsValidAuthority(authority)) {
311 return false;
312 }
313 if (!allow_different_host_and_authority_ && pseudo_headers_[TAG_AUTHORITY] &&
314 authority != authority_) {
315 return false;
316 }
317 if (!authority.empty()) {
318 pseudo_header_state_[STATE_AUTHORITY_IS_NONEMPTY] = true;
319 if (authority_.empty()) {
320 authority_ = authority;
321 } else {
322 absl::StrAppend(&authority_, ", ", authority);
323 }
324 }
325 return true;
326 }
327
ValidateRequestHeaders(const PseudoHeaderTagSet & pseudo_headers,const PseudoHeaderStateSet & pseudo_header_state,bool allow_extended_connect)328 bool HeaderValidator::ValidateRequestHeaders(
329 const PseudoHeaderTagSet& pseudo_headers,
330 const PseudoHeaderStateSet& pseudo_header_state,
331 bool allow_extended_connect) {
332 QUICHE_VLOG(2) << "Request pseudo-headers: [" << pseudo_headers
333 << "], pseudo_header_state: [" << pseudo_header_state
334 << "], allow_extended_connect: " << allow_extended_connect;
335 if (pseudo_header_state[STATE_METHOD_IS_CONNECT]) {
336 if (allow_extended_connect) {
337 // See RFC 8441. Extended CONNECT should have: authority, method, path,
338 // protocol and scheme pseudo-headers. The tags corresponding to status
339 // and unknown_extra should not be set.
340 static const auto* kExtendedConnectHeaders =
341 new PseudoHeaderTagSet(0b0011111);
342 if (pseudo_headers == *kExtendedConnectHeaders) {
343 return true;
344 }
345 }
346 // See RFC 7540 Section 8.3. Regular CONNECT should have authority and
347 // method, but no other pseudo headers.
348 static const auto* kConnectHeaders = new PseudoHeaderTagSet(0b0000011);
349 return pseudo_header_state[STATE_AUTHORITY_IS_NONEMPTY] &&
350 pseudo_headers == *kConnectHeaders;
351 }
352
353 if (pseudo_header_state[STATE_PATH_IS_EMPTY]) {
354 return false;
355 }
356 if (pseudo_header_state[STATE_PATH_IS_STAR]) {
357 if (!pseudo_header_state[STATE_METHOD_IS_OPTIONS]) {
358 return false;
359 }
360 } else if (!pseudo_header_state[STATE_PATH_INITIAL_SLASH]) {
361 return false;
362 }
363
364 // Regular HTTP requests require authority, method, path and scheme.
365 static const auto* kRequiredHeaders = new PseudoHeaderTagSet(0b0010111);
366 return pseudo_headers == *kRequiredHeaders;
367 }
368
ValidateRequestTrailers(const PseudoHeaderTagSet & pseudo_headers)369 bool HeaderValidator::ValidateRequestTrailers(
370 const PseudoHeaderTagSet& pseudo_headers) {
371 return pseudo_headers.none();
372 }
373
ValidateResponseHeaders(const PseudoHeaderTagSet & pseudo_headers)374 bool HeaderValidator::ValidateResponseHeaders(
375 const PseudoHeaderTagSet& pseudo_headers) {
376 // HTTP responses require only the status pseudo header.
377 static const auto* kRequiredHeaders = new PseudoHeaderTagSet(0b0100000);
378 return pseudo_headers == *kRequiredHeaders;
379 }
380
ValidateResponseTrailers(const PseudoHeaderTagSet & pseudo_headers)381 bool HeaderValidator::ValidateResponseTrailers(
382 const PseudoHeaderTagSet& pseudo_headers) {
383 return pseudo_headers.none();
384 }
385
386 } // namespace adapter
387 } // namespace http2
388