Coverage Report

Created: 2023-11-12 09:30

/proc/self/cwd/source/common/http/header_utility.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/common/http/header_utility.h"
2
3
#include "envoy/config/route/v3/route_components.pb.h"
4
5
#include "source/common/common/json_escape_string.h"
6
#include "source/common/common/matchers.h"
7
#include "source/common/common/regex.h"
8
#include "source/common/common/utility.h"
9
#include "source/common/http/character_set_validation.h"
10
#include "source/common/http/header_map_impl.h"
11
#include "source/common/http/utility.h"
12
#include "source/common/protobuf/utility.h"
13
#include "source/common/runtime/runtime_features.h"
14
15
#include "absl/strings/match.h"
16
#include "nghttp2/nghttp2.h"
17
#include "quiche/common/structured_headers.h"
18
#include "quiche/http2/adapter/header_validator.h"
19
20
namespace Envoy {
21
namespace Http {
22
23
struct SharedResponseCodeDetailsValues {
24
  const absl::string_view InvalidAuthority = "http.invalid_authority";
25
  const absl::string_view ConnectUnsupported = "http.connect_not_supported";
26
  const absl::string_view InvalidMethod = "http.invalid_method";
27
  const absl::string_view InvalidPath = "http.invalid_path";
28
  const absl::string_view InvalidScheme = "http.invalid_scheme";
29
};
30
31
using SharedResponseCodeDetails = ConstSingleton<SharedResponseCodeDetailsValues>;
32
33
// HeaderMatcher will consist of:
34
//   header_match_specifier which can be any one of exact_match, regex_match, range_match,
35
//   present_match, prefix_match or suffix_match.
36
//   Each of these also can be inverted with the invert_match option.
37
//   Absence of these options implies empty header value match based on header presence.
38
//   a.exact_match: value will be used for exact string matching.
39
//   b.regex_match: Match will succeed if header value matches the value specified here.
40
//   c.range_match: Match will succeed if header value lies within the range specified
41
//     here, using half open interval semantics [start,end).
42
//   d.present_match: Match will succeed if the header is present.
43
//   f.prefix_match: Match will succeed if header value matches the prefix value specified here.
44
//   g.suffix_match: Match will succeed if header value matches the suffix value specified here.
45
HeaderUtility::HeaderData::HeaderData(const envoy::config::route::v3::HeaderMatcher& config)
46
    : name_(config.name()), invert_match_(config.invert_match()),
47
48.0k
      treat_missing_as_empty_(config.treat_missing_header_as_empty()) {
48
48.0k
  switch (config.header_match_specifier_case()) {
49
2.34k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kExactMatch:
50
2.34k
    header_match_type_ = HeaderMatchType::Value;
51
2.34k
    value_ = config.exact_match();
52
2.34k
    break;
53
5.03k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kSafeRegexMatch:
54
5.03k
    header_match_type_ = HeaderMatchType::Regex;
55
5.03k
    regex_ = Regex::Utility::parseRegex(config.safe_regex_match());
56
5.03k
    break;
57
2.02k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kRangeMatch:
58
2.02k
    header_match_type_ = HeaderMatchType::Range;
59
2.02k
    range_.set_start(config.range_match().start());
60
2.02k
    range_.set_end(config.range_match().end());
61
2.02k
    break;
62
1.49k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPresentMatch:
63
1.49k
    header_match_type_ = HeaderMatchType::Present;
64
1.49k
    present_ = config.present_match();
65
1.49k
    break;
66
3.11k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPrefixMatch:
67
3.11k
    header_match_type_ = HeaderMatchType::Prefix;
68
3.11k
    value_ = config.prefix_match();
69
3.11k
    break;
70
3.32k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kSuffixMatch:
71
3.32k
    header_match_type_ = HeaderMatchType::Suffix;
72
3.32k
    value_ = config.suffix_match();
73
3.32k
    break;
74
2.49k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kContainsMatch:
75
2.49k
    header_match_type_ = HeaderMatchType::Contains;
76
2.49k
    value_ = config.contains_match();
77
2.49k
    break;
78
6.61k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kStringMatch:
79
6.61k
    header_match_type_ = HeaderMatchType::StringMatch;
80
6.61k
    string_match_ =
81
6.61k
        std::make_unique<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>(
82
6.61k
            config.string_match());
83
6.61k
    break;
84
21.6k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::
85
21.6k
      HEADER_MATCH_SPECIFIER_NOT_SET:
86
21.6k
    FALLTHRU;
87
21.6k
  default:
88
21.6k
    header_match_type_ = HeaderMatchType::Present;
89
21.6k
    present_ = true;
90
21.6k
    break;
91
48.0k
  }
92
48.0k
}
93
94
bool HeaderUtility::matchHeaders(const HeaderMap& request_headers,
95
43.9k
                                 const std::vector<HeaderDataPtr>& config_headers) {
96
  // No headers to match is considered a match.
97
43.9k
  if (!config_headers.empty()) {
98
12.5k
    for (const HeaderDataPtr& cfg_header_data : config_headers) {
99
12.5k
      if (!matchHeaders(request_headers, *cfg_header_data)) {
100
4.77k
        return false;
101
4.77k
      }
102
12.5k
    }
103
6.62k
  }
104
105
39.1k
  return true;
106
43.9k
}
107
108
HeaderUtility::GetAllOfHeaderAsStringResult
109
HeaderUtility::getAllOfHeaderAsString(const HeaderMap::GetResult& header_value,
110
55.6k
                                      absl::string_view separator) {
111
55.6k
  GetAllOfHeaderAsStringResult result;
112
  // In this case we concatenate all found headers using a delimiter before performing the
113
  // final match. We use an InlinedVector of absl::string_view to invoke the optimized join
114
  // algorithm. This requires a copying phase before we invoke join. The 3 used as the inline
115
  // size has been arbitrarily chosen.
116
  // TODO(mattklein123): Do we need to normalize any whitespace here?
117
55.6k
  absl::InlinedVector<absl::string_view, 3> string_view_vector;
118
55.6k
  string_view_vector.reserve(header_value.size());
119
286k
  for (size_t i = 0; i < header_value.size(); i++) {
120
231k
    string_view_vector.push_back(header_value[i]->value().getStringView());
121
231k
  }
122
55.6k
  result.result_backing_string_ = absl::StrJoin(string_view_vector, separator);
123
124
55.6k
  return result;
125
55.6k
}
126
127
HeaderUtility::GetAllOfHeaderAsStringResult
128
HeaderUtility::getAllOfHeaderAsString(const HeaderMap& headers, const Http::LowerCaseString& key,
129
230k
                                      absl::string_view separator) {
130
230k
  GetAllOfHeaderAsStringResult result;
131
230k
  const auto header_value = headers.get(key);
132
133
230k
  if (header_value.empty()) {
134
    // Empty for clarity. Avoid handling the empty case in the block below if the runtime feature
135
    // is disabled.
136
203k
  } else if (header_value.size() == 1) {
137
147k
    result.result_ = header_value[0]->value().getStringView();
138
147k
  } else {
139
55.6k
    return getAllOfHeaderAsString(header_value, separator);
140
55.6k
  }
141
142
175k
  return result;
143
230k
}
144
145
12.9k
bool HeaderUtility::matchHeaders(const HeaderMap& request_headers, const HeaderData& header_data) {
146
12.9k
  const auto header_value = getAllOfHeaderAsString(request_headers, header_data.name_);
147
148
12.9k
  if (!header_value.result().has_value() && !header_data.treat_missing_as_empty_) {
149
2.46k
    if (header_data.invert_match_) {
150
392
      return header_data.header_match_type_ == HeaderMatchType::Present && header_data.present_;
151
2.07k
    } else {
152
2.07k
      return header_data.header_match_type_ == HeaderMatchType::Present && !header_data.present_;
153
2.07k
    }
154
2.46k
  }
155
156
  // If the header does not have value and the result is not returned in the
157
  // code above, it means treat_missing_as_empty_ is set to true and we should
158
  // treat the header value as empty.
159
10.5k
  const auto value = header_value.result().has_value() ? header_value.result().value() : "";
160
10.5k
  bool match;
161
10.5k
  switch (header_data.header_match_type_) {
162
905
  case HeaderMatchType::Value:
163
905
    match = header_data.value_.empty() || value == header_data.value_;
164
905
    break;
165
158
  case HeaderMatchType::Regex:
166
158
    match = header_data.regex_->match(value);
167
158
    break;
168
787
  case HeaderMatchType::Range: {
169
787
    int64_t header_int_value = 0;
170
787
    match = absl::SimpleAtoi(value, &header_int_value) &&
171
787
            header_int_value >= header_data.range_.start() &&
172
787
            header_int_value < header_data.range_.end();
173
787
    break;
174
0
  }
175
4.64k
  case HeaderMatchType::Present:
176
4.64k
    match = header_data.present_;
177
4.64k
    break;
178
886
  case HeaderMatchType::Prefix:
179
886
    match = absl::StartsWith(value, header_data.value_);
180
886
    break;
181
519
  case HeaderMatchType::Suffix:
182
519
    match = absl::EndsWith(value, header_data.value_);
183
519
    break;
184
772
  case HeaderMatchType::Contains:
185
772
    match = absl::StrContains(value, header_data.value_);
186
772
    break;
187
1.83k
  case HeaderMatchType::StringMatch:
188
1.83k
    match = header_data.string_match_->match(value);
189
1.83k
    break;
190
10.5k
  }
191
192
10.5k
  return match != header_data.invert_match_;
193
10.5k
}
194
195
889k
bool HeaderUtility::headerValueIsValid(const absl::string_view header_value) {
196
889k
  return http2::adapter::HeaderValidator::IsValidHeaderValue(header_value,
197
889k
                                                             http2::adapter::ObsTextOption::kAllow);
198
889k
}
199
200
636k
bool HeaderUtility::headerNameIsValid(const absl::string_view header_key) {
201
636k
  if (!header_key.empty() && header_key[0] == ':') {
202
    // For HTTP/2 pseudo header, use the HTTP/2 semantics for checking validity
203
425k
    return nghttp2_check_header_name(reinterpret_cast<const uint8_t*>(header_key.data()),
204
425k
                                     header_key.size()) != 0;
205
425k
  }
206
  // For all other header use HTTP/1 semantics. The only difference from HTTP/2 is that
207
  // uppercase characters are allowed. This allows HTTP filters to add header with mixed
208
  // case names. The HTTP/1 codec will send as is, as uppercase characters are allowed.
209
  // However the HTTP/2 codec will NOT convert these to lowercase when serializing the
210
  // header map, thus producing an invalid request.
211
  // TODO(yanavlasov): make validation in HTTP/2 case stricter.
212
211k
  bool is_valid = true;
213
3.01M
  for (auto iter = header_key.begin(); iter != header_key.end() && is_valid; ++iter) {
214
2.80M
    is_valid &= testCharInTable(kGenericHeaderNameCharTable, *iter);
215
2.80M
  }
216
211k
  return is_valid;
217
636k
}
218
219
18.6k
bool HeaderUtility::headerNameContainsUnderscore(const absl::string_view header_name) {
220
18.6k
  return header_name.find('_') != absl::string_view::npos;
221
18.6k
}
222
223
359k
bool HeaderUtility::authorityIsValid(const absl::string_view header_value) {
224
359k
  if (Runtime::runtimeFeatureEnabled(
225
359k
          "envoy.reloadable_features.http2_validate_authority_with_quiche")) {
226
359k
    return http2::adapter::HeaderValidator::IsValidAuthority(header_value);
227
359k
  } else {
228
0
    return nghttp2_check_authority(reinterpret_cast<const uint8_t*>(header_value.data()),
229
0
                                   header_value.size()) != 0;
230
0
  }
231
359k
}
232
233
33.5k
bool HeaderUtility::isSpecial1xx(const ResponseHeaderMap& response_headers) {
234
33.5k
  return response_headers.Status()->value() == "100" ||
235
33.5k
         response_headers.Status()->value() == "102" || response_headers.Status()->value() == "103";
236
33.5k
}
237
238
692k
bool HeaderUtility::isConnect(const RequestHeaderMap& headers) {
239
692k
  return headers.Method() && headers.Method()->value() == Http::Headers::get().MethodValues.Connect;
240
692k
}
241
242
79.0k
bool HeaderUtility::isConnectUdpRequest(const RequestHeaderMap& headers) {
243
79.0k
  return headers.Upgrade() && absl::EqualsIgnoreCase(headers.getUpgradeValue(),
244
9.11k
                                                     Http::Headers::get().UpgradeValues.ConnectUdp);
245
79.0k
}
246
247
0
bool HeaderUtility::isConnectUdpResponse(const ResponseHeaderMap& headers) {
248
  // In connect-udp case, Envoy will transform the H2 headers to H1 upgrade headers.
249
  // A valid response should have SwitchingProtocol status and connect-udp upgrade.
250
0
  return headers.Upgrade() && Utility::getResponseStatus(headers) == 101 &&
251
0
         absl::EqualsIgnoreCase(headers.getUpgradeValue(),
252
0
                                Http::Headers::get().UpgradeValues.ConnectUdp);
253
0
}
254
255
bool HeaderUtility::isConnectResponse(const RequestHeaderMap* request_headers,
256
115k
                                      const ResponseHeaderMap& response_headers) {
257
115k
  return request_headers && isConnect(*request_headers) &&
258
115k
         static_cast<Http::Code>(Http::Utility::getResponseStatus(response_headers)) ==
259
215
             Http::Code::OK;
260
115k
}
261
262
7.90k
bool HeaderUtility::rewriteAuthorityForConnectUdp(RequestHeaderMap& headers) {
263
  // Per RFC 9298, the URI template must only contain ASCII characters in the range 0x21-0x7E.
264
7.90k
  absl::string_view path = headers.getPathValue();
265
6.04M
  for (char c : path) {
266
6.04M
    unsigned char ascii_code = static_cast<unsigned char>(c);
267
6.04M
    if (ascii_code < 0x21 || ascii_code > 0x7e) {
268
82
      ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a bad character in the path {}", path);
269
82
      return false;
270
82
    }
271
6.04M
  }
272
273
  // Extract target host and port from path using default template.
274
7.82k
  if (!absl::StartsWith(path, "/.well-known/masque/udp/")) {
275
2.42k
    ENVOY_LOG_MISC(warn, "CONNECT-UDP request path is not a well-known URI: {}", path);
276
2.42k
    return false;
277
2.42k
  }
278
279
5.39k
  std::vector<absl::string_view> path_split = absl::StrSplit(path, '/');
280
5.39k
  if (path_split.size() != 7 || path_split[4].empty() || path_split[5].empty() ||
281
5.39k
      !path_split[6].empty()) {
282
1.33k
    ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a malformed URI template in the path {}", path);
283
1.33k
    return false;
284
1.33k
  }
285
286
  // Utility::PercentEncoding::decode never returns an empty string if the input argument is not
287
  // empty.
288
4.06k
  std::string target_host = Utility::PercentEncoding::decode(path_split[4]);
289
  // Per RFC 9298, IPv6 Zone ID is not supported.
290
4.06k
  if (target_host.find('%') != std::string::npos) {
291
3.59k
    ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a non-escpaed char (%) in the path {}", path);
292
3.59k
    return false;
293
3.59k
  }
294
474
  std::string target_port = Utility::PercentEncoding::decode(path_split[5]);
295
296
  // If the host is an IPv6 address, surround the address with square brackets.
297
474
  in6_addr sin6_addr;
298
474
  bool is_ipv6 = (inet_pton(AF_INET6, target_host.c_str(), &sin6_addr) == 1);
299
474
  std::string new_host =
300
474
      absl::StrCat((is_ipv6 ? absl::StrCat("[", target_host, "]") : target_host), ":", target_port);
301
474
  headers.setHost(new_host);
302
303
474
  return true;
304
4.06k
}
305
306
#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS
307
3.69k
bool HeaderUtility::isCapsuleProtocol(const RequestOrResponseHeaderMap& headers) {
308
3.69k
  Http::HeaderMap::GetResult capsule_protocol =
309
3.69k
      headers.get(Envoy::Http::LowerCaseString("Capsule-Protocol"));
310
  // When there are multiple Capsule-Protocol header entries, it returns false. RFC 9297 specifies
311
  // that non-boolean value types must be ignored. If there are multiple header entries, the value
312
  // type becomes a List so the header field must be ignored.
313
3.69k
  if (capsule_protocol.size() != 1) {
314
1.81k
    return false;
315
1.81k
  }
316
  // Parses the header value and extracts the boolean value ignoring parameters.
317
1.88k
  absl::optional<quiche::structured_headers::ParameterizedItem> header_item =
318
1.88k
      quiche::structured_headers::ParseItem(capsule_protocol[0]->value().getStringView());
319
1.88k
  return header_item && header_item->item.is_boolean() && header_item->item.GetBoolean();
320
3.69k
}
321
#endif
322
323
11.4k
bool HeaderUtility::requestShouldHaveNoBody(const RequestHeaderMap& headers) {
324
11.4k
  return (headers.Method() &&
325
11.4k
          (headers.Method()->value() == Http::Headers::get().MethodValues.Get ||
326
11.4k
           headers.Method()->value() == Http::Headers::get().MethodValues.Head ||
327
11.4k
           headers.Method()->value() == Http::Headers::get().MethodValues.Delete ||
328
11.4k
           headers.Method()->value() == Http::Headers::get().MethodValues.Trace ||
329
11.4k
           headers.Method()->value() == Http::Headers::get().MethodValues.Connect));
330
11.4k
}
331
332
3.36k
bool HeaderUtility::isEnvoyInternalRequest(const RequestHeaderMap& headers) {
333
3.36k
  const HeaderEntry* internal_request_header = headers.EnvoyInternalRequest();
334
3.36k
  return internal_request_header != nullptr &&
335
3.36k
         internal_request_header->value() == Headers::get().EnvoyInternalRequestValues.True;
336
3.36k
}
337
338
114
void HeaderUtility::stripTrailingHostDot(RequestHeaderMap& headers) {
339
114
  auto host = headers.getHostValue();
340
  // If the host ends in a period, remove it.
341
114
  auto dot_index = host.rfind('.');
342
114
  if (dot_index == std::string::npos) {
343
16
    return;
344
98
  } else if (dot_index == (host.size() - 1)) {
345
95
    host.remove_suffix(1);
346
95
    headers.setHost(host);
347
95
    return;
348
95
  }
349
  // If the dot is just before a colon, it must be preceding the port number.
350
  // IPv6 addresses may contain colons or dots, but the dot will never directly
351
  // precede the colon, so this check should be sufficient to detect a trailing port number.
352
3
  if (host[dot_index + 1] == ':') {
353
0
    headers.setHost(absl::StrCat(host.substr(0, dot_index), host.substr(dot_index + 1)));
354
0
  }
355
3
}
356
357
0
bool HeaderUtility::hostHasPort(absl::string_view original_host) {
358
0
  const absl::string_view::size_type port_start = getPortStart(original_host);
359
0
  const absl::string_view port_str = original_host.substr(port_start + 1);
360
0
  if (port_start == absl::string_view::npos) {
361
0
    return false;
362
0
  }
363
0
  uint32_t port = 0;
364
0
  if (!absl::SimpleAtoi(port_str, &port)) {
365
0
    return false;
366
0
  }
367
0
  return true;
368
0
}
369
370
absl::optional<uint32_t> HeaderUtility::stripPortFromHost(RequestHeaderMap& headers,
371
142
                                                          absl::optional<uint32_t> listener_port) {
372
142
  const absl::string_view original_host = headers.getHostValue();
373
142
  const absl::string_view::size_type port_start = getPortStart(original_host);
374
142
  if (port_start == absl::string_view::npos) {
375
112
    return absl::nullopt;
376
112
  }
377
30
  const absl::string_view port_str = original_host.substr(port_start + 1);
378
30
  uint32_t port = 0;
379
30
  if (!absl::SimpleAtoi(port_str, &port)) {
380
26
    return absl::nullopt;
381
26
  }
382
4
  if (listener_port.has_value() && port != listener_port) {
383
    // We would strip ports only if it is specified and they are the same, as local port of the
384
    // listener.
385
0
    return absl::nullopt;
386
0
  }
387
4
  const absl::string_view host = original_host.substr(0, port_start);
388
4
  headers.setHost(host);
389
4
  return port;
390
4
}
391
392
2.67k
absl::string_view::size_type HeaderUtility::getPortStart(absl::string_view host) {
393
2.67k
  const absl::string_view::size_type port_start = host.rfind(':');
394
2.67k
  if (port_start == absl::string_view::npos) {
395
2.59k
    return absl::string_view::npos;
396
2.59k
  }
397
  // According to RFC3986 v6 address is always enclosed in "[]". section 3.2.2.
398
87
  const auto v6_end_index = host.rfind(']');
399
87
  if (v6_end_index == absl::string_view::npos || v6_end_index < port_start) {
400
76
    if ((port_start + 1) > host.size()) {
401
0
      return absl::string_view::npos;
402
0
    }
403
76
    return port_start;
404
76
  }
405
11
  return absl::string_view::npos;
406
87
}
407
408
576k
constexpr bool isInvalidToken(unsigned char c) {
409
576k
  if (c == '!' || c == '|' || c == '~' || c == '*' || c == '+' || c == '-' || c == '.' ||
410
      // #, $, %, &, '
411
576k
      (c >= '#' && c <= '\'') ||
412
      // [0-9]
413
576k
      (c >= '0' && c <= '9') ||
414
      // [A-Z]
415
576k
      (c >= 'A' && c <= 'Z') ||
416
      // ^, _, `, [a-z]
417
576k
      (c >= '^' && c <= 'z')) {
418
576k
    return false;
419
576k
  }
420
49
  return true;
421
576k
}
422
423
absl::optional<std::reference_wrapper<const absl::string_view>>
424
188k
HeaderUtility::requestHeadersValid(const RequestHeaderMap& headers) {
425
  // Make sure the host is valid.
426
188k
  if (headers.Host() && !HeaderUtility::authorityIsValid(headers.Host()->value().getStringView())) {
427
419
    return SharedResponseCodeDetails::get().InvalidAuthority;
428
419
  }
429
187k
  if (headers.Method()) {
430
187k
    absl::string_view method = headers.Method()->value().getStringView();
431
187k
    if (method.empty() || std::any_of(method.begin(), method.end(), isInvalidToken)) {
432
72
      return SharedResponseCodeDetails::get().InvalidMethod;
433
72
    }
434
187k
  }
435
187k
  if (headers.Scheme() && absl::StrContains(headers.Scheme()->value().getStringView(), ",")) {
436
24
    return SharedResponseCodeDetails::get().InvalidScheme;
437
24
  }
438
187k
  return absl::nullopt;
439
187k
}
440
441
bool HeaderUtility::shouldCloseConnection(Http::Protocol protocol,
442
176k
                                          const RequestOrResponseHeaderMap& headers) {
443
  // HTTP/1.0 defaults to single-use connections. Make sure the connection will be closed unless
444
  // Keep-Alive is present.
445
176k
  if (protocol == Protocol::Http10 &&
446
176k
      (!headers.Connection() ||
447
209
       !Envoy::StringUtil::caseFindToken(headers.Connection()->value().getStringView(), ",",
448
209
                                         Http::Headers::get().ConnectionValues.KeepAlive))) {
449
209
    return true;
450
209
  }
451
452
176k
  if (protocol == Protocol::Http11 && headers.Connection() &&
453
176k
      Envoy::StringUtil::caseFindToken(headers.Connection()->value().getStringView(), ",",
454
2.30k
                                       Http::Headers::get().ConnectionValues.Close)) {
455
26
    return true;
456
26
  }
457
458
  // Note: Proxy-Connection is not a standard header, but is supported here
459
  // since it is supported by http-parser the underlying parser for http
460
  // requests.
461
176k
  if (protocol < Protocol::Http2 && headers.ProxyConnection() &&
462
176k
      Envoy::StringUtil::caseFindToken(headers.ProxyConnection()->value().getStringView(), ",",
463
1.10k
                                       Http::Headers::get().ConnectionValues.Close)) {
464
39
    return true;
465
39
  }
466
176k
  return false;
467
176k
}
468
469
147k
Http::Status HeaderUtility::checkRequiredRequestHeaders(const Http::RequestHeaderMap& headers) {
470
147k
  if (!headers.Method()) {
471
0
    return absl::InvalidArgumentError(
472
0
        absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Method.get()));
473
0
  }
474
147k
  bool is_connect = Http::HeaderUtility::isConnect(headers);
475
147k
  if (is_connect) {
476
4.89k
    if (!headers.Host()) {
477
      // Host header must be present for CONNECT request.
478
139
      return absl::InvalidArgumentError(
479
139
          absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Host.get()));
480
139
    }
481
4.75k
    if (headers.Path() && !headers.Protocol()) {
482
      // Path and Protocol header should only be present for CONNECT for upgrade style CONNECT.
483
100
      return absl::InvalidArgumentError(
484
100
          absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Protocol.get()));
485
100
    }
486
4.65k
    if (!headers.Path() && headers.Protocol()) {
487
      // Path and Protocol header should only be present for CONNECT for upgrade style CONNECT.
488
0
      return absl::InvalidArgumentError(
489
0
          absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Path.get()));
490
0
    }
491
142k
  } else {
492
142k
    if (!headers.Path()) {
493
      // :path header must be present for non-CONNECT requests.
494
59
      return absl::InvalidArgumentError(
495
59
          absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Path.get()));
496
59
    }
497
142k
  }
498
147k
  return Http::okStatus();
499
147k
}
500
501
125k
Http::Status HeaderUtility::checkValidRequestHeaders(const Http::RequestHeaderMap& headers) {
502
125k
  if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.validate_upstream_headers")) {
503
0
    return Http::okStatus();
504
0
  }
505
506
125k
  const HeaderEntry* invalid_entry = nullptr;
507
125k
  bool invalid_key = false;
508
586k
  headers.iterate([&invalid_entry, &invalid_key](const HeaderEntry& header) -> HeaderMap::Iterate {
509
586k
    if (!HeaderUtility::headerNameIsValid(header.key().getStringView())) {
510
13.2k
      invalid_entry = &header;
511
13.2k
      invalid_key = true;
512
13.2k
      return HeaderMap::Iterate::Break;
513
13.2k
    }
514
515
573k
    if (!HeaderUtility::headerValueIsValid(header.value().getStringView())) {
516
744
      invalid_entry = &header;
517
744
      invalid_key = false;
518
744
      return HeaderMap::Iterate::Break;
519
744
    }
520
521
572k
    return HeaderMap::Iterate::Continue;
522
573k
  });
523
524
125k
  if (invalid_entry) {
525
    // The header key may contain non-printable characters. Escape the key so that the error
526
    // details can be safely presented.
527
13.9k
    const absl::string_view key = invalid_entry->key().getStringView();
528
13.9k
    uint64_t extra_length = JsonEscaper::extraSpace(key);
529
13.9k
    const std::string escaped_key = JsonEscaper::escapeString(key, extra_length);
530
531
13.9k
    return absl::InvalidArgumentError(
532
13.9k
        absl::StrCat("invalid header ", invalid_key ? "name: " : "value for: ", escaped_key));
533
13.9k
  }
534
111k
  return Http::okStatus();
535
125k
}
536
537
118k
Http::Status HeaderUtility::checkRequiredResponseHeaders(const Http::ResponseHeaderMap& headers) {
538
118k
  const absl::optional<uint64_t> status = Utility::getResponseStatusOrNullopt(headers);
539
118k
  if (!status.has_value()) {
540
0
    return absl::InvalidArgumentError(
541
0
        absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Status.get()));
542
0
  }
543
118k
  return Http::okStatus();
544
118k
}
545
546
75.6k
bool HeaderUtility::isRemovableHeader(absl::string_view header) {
547
75.6k
  return (header.empty() || header[0] != ':') &&
548
75.6k
         !absl::EqualsIgnoreCase(header, Headers::get().HostLegacy.get());
549
75.6k
}
550
551
381k
bool HeaderUtility::isModifiableHeader(absl::string_view header) {
552
381k
  return (header.empty() || header[0] != ':') &&
553
381k
         !absl::EqualsIgnoreCase(header, Headers::get().HostLegacy.get());
554
381k
}
555
556
HeaderUtility::HeaderValidationResult HeaderUtility::checkHeaderNameForUnderscores(
557
    absl::string_view header_name,
558
    envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction
559
        headers_with_underscores_action,
560
45.1k
    HeaderValidatorStats& stats) {
561
45.1k
  if (headers_with_underscores_action == envoy::config::core::v3::HttpProtocolOptions::ALLOW ||
562
45.1k
      !HeaderUtility::headerNameContainsUnderscore(header_name)) {
563
45.1k
    return HeaderValidationResult::ACCEPT;
564
45.1k
  }
565
0
  if (headers_with_underscores_action ==
566
0
      envoy::config::core::v3::HttpProtocolOptions::DROP_HEADER) {
567
0
    ENVOY_LOG_MISC(debug, "Dropping header with invalid characters in its name: {}", header_name);
568
0
    stats.incDroppedHeadersWithUnderscores();
569
0
    return HeaderValidationResult::DROP;
570
0
  }
571
0
  ENVOY_LOG_MISC(debug, "Rejecting request due to header name with underscores: {}", header_name);
572
0
  stats.incRequestsRejectedWithUnderscoresInHeaders();
573
0
  return HeaderUtility::HeaderValidationResult::REJECT;
574
0
}
575
576
HeaderUtility::HeaderValidationResult
577
HeaderUtility::validateContentLength(absl::string_view header_value,
578
                                     bool override_stream_error_on_invalid_http_message,
579
421
                                     bool& should_close_connection, size_t& content_length_output) {
580
421
  should_close_connection = false;
581
421
  std::vector<absl::string_view> values = absl::StrSplit(header_value, ',');
582
421
  absl::optional<uint64_t> content_length;
583
421
  for (const absl::string_view& value : values) {
584
421
    uint64_t new_value;
585
421
    if (!absl::SimpleAtoi(value, &new_value) ||
586
421
        !std::all_of(value.begin(), value.end(), absl::ascii_isdigit)) {
587
3
      ENVOY_LOG_MISC(debug, "Content length was either unparseable or negative");
588
3
      should_close_connection = !override_stream_error_on_invalid_http_message;
589
3
      return HeaderValidationResult::REJECT;
590
3
    }
591
418
    if (!content_length.has_value()) {
592
418
      content_length = new_value;
593
418
      continue;
594
418
    }
595
0
    if (new_value != content_length.value()) {
596
0
      ENVOY_LOG_MISC(
597
0
          debug,
598
0
          "Parsed content length {} is inconsistent with previously detected content length {}",
599
0
          new_value, content_length.value());
600
0
      should_close_connection = !override_stream_error_on_invalid_http_message;
601
0
      return HeaderValidationResult::REJECT;
602
0
    }
603
0
  }
604
418
  content_length_output = content_length.value();
605
418
  return HeaderValidationResult::ACCEPT;
606
421
}
607
608
std::vector<absl::string_view>
609
0
HeaderUtility::parseCommaDelimitedHeader(absl::string_view header_value) {
610
0
  std::vector<absl::string_view> values;
611
0
  for (absl::string_view s : absl::StrSplit(header_value, ',')) {
612
0
    absl::string_view token = absl::StripAsciiWhitespace(s);
613
0
    if (token.empty()) {
614
0
      continue;
615
0
    }
616
0
    values.emplace_back(token);
617
0
  }
618
0
  return values;
619
0
}
620
621
0
absl::string_view HeaderUtility::getSemicolonDelimitedAttribute(absl::string_view value) {
622
0
  return absl::StripAsciiWhitespace(StringUtil::cropRight(value, ";"));
623
0
}
624
625
std::string HeaderUtility::addEncodingToAcceptEncoding(absl::string_view accept_encoding_header,
626
0
                                                       absl::string_view encoding) {
627
  // Append the content encoding only if it isn't already present in the
628
  // accept_encoding header. If it is present with a q-value ("gzip;q=0.3"),
629
  // remove the q-value to indicate that the content encoding setting that we
630
  // add has max priority (i.e. q-value 1.0).
631
0
  std::vector<absl::string_view> newContentEncodings;
632
0
  std::vector<absl::string_view> contentEncodings =
633
0
      Http::HeaderUtility::parseCommaDelimitedHeader(accept_encoding_header);
634
0
  for (absl::string_view contentEncoding : contentEncodings) {
635
0
    absl::string_view strippedEncoding =
636
0
        Http::HeaderUtility::getSemicolonDelimitedAttribute(contentEncoding);
637
0
    if (strippedEncoding != encoding) {
638
      // Add back all content encodings back except for the content encoding that we want to
639
      // add. For example, if content encoding is "gzip", this filters out encodings "gzip" and
640
      // "gzip;q=0.6".
641
0
      newContentEncodings.push_back(contentEncoding);
642
0
    }
643
0
  }
644
  // Finally add a single instance of our content encoding.
645
0
  newContentEncodings.push_back(encoding);
646
0
  return absl::StrJoin(newContentEncodings, ",");
647
0
}
648
649
594
bool HeaderUtility::isStandardConnectRequest(const Http::RequestHeaderMap& headers) {
650
594
  return headers.method() == Http::Headers::get().MethodValues.Connect &&
651
594
         headers.getProtocolValue().empty();
652
594
}
653
654
0
bool HeaderUtility::isExtendedH2ConnectRequest(const Http::RequestHeaderMap& headers) {
655
0
  return headers.method() == Http::Headers::get().MethodValues.Connect &&
656
0
         !headers.getProtocolValue().empty();
657
0
}
658
659
135k
bool HeaderUtility::isPseudoHeader(absl::string_view header_name) {
660
135k
  return !header_name.empty() && header_name[0] == ':';
661
135k
}
662
663
} // namespace Http
664
} // namespace Envoy