xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/http2/adapter/header_validator.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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