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