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