Coverage Report

Created: 2024-09-19 09:45

/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
17
#ifdef ENVOY_NGHTTP2
18
#include "nghttp2/nghttp2.h"
19
#endif
20
#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS
21
#include "quiche/common/structured_headers.h"
22
#endif
23
#include "quiche/http2/adapter/header_validator.h"
24
25
namespace Envoy {
26
namespace Http {
27
28
struct SharedResponseCodeDetailsValues {
29
  const absl::string_view InvalidAuthority = "http.invalid_authority";
30
  const absl::string_view ConnectUnsupported = "http.connect_not_supported";
31
  const absl::string_view InvalidMethod = "http.invalid_method";
32
  const absl::string_view InvalidPath = "http.invalid_path";
33
  const absl::string_view InvalidScheme = "http.invalid_scheme";
34
};
35
36
using SharedResponseCodeDetails = ConstSingleton<SharedResponseCodeDetailsValues>;
37
38
// HeaderMatcher will consist of:
39
//   header_match_specifier which can be any one of exact_match, regex_match, range_match,
40
//   present_match, prefix_match or suffix_match.
41
//   Each of these also can be inverted with the invert_match option.
42
//   Absence of these options implies empty header value match based on header presence.
43
//   a.exact_match: value will be used for exact string matching.
44
//   b.regex_match: Match will succeed if header value matches the value specified here.
45
//   c.range_match: Match will succeed if header value lies within the range specified
46
//     here, using half open interval semantics [start,end).
47
//   d.present_match: Match will succeed if the header is present.
48
//   f.prefix_match: Match will succeed if header value matches the prefix value specified here.
49
//   g.suffix_match: Match will succeed if header value matches the suffix value specified here.
50
HeaderUtility::HeaderData::HeaderData(const envoy::config::route::v3::HeaderMatcher& config,
51
                                      Server::Configuration::CommonFactoryContext& factory_context)
52
    : name_(config.name()), invert_match_(config.invert_match()),
53
123k
      treat_missing_as_empty_(config.treat_missing_header_as_empty()) {
54
123k
  switch (config.header_match_specifier_case()) {
55
9.24k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kExactMatch:
56
9.24k
    header_match_type_ = HeaderMatchType::Value;
57
9.24k
    value_ = config.exact_match();
58
9.24k
    break;
59
19.2k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kSafeRegexMatch:
60
19.2k
    header_match_type_ = HeaderMatchType::Regex;
61
19.2k
    regex_ = Regex::Utility::parseRegex(config.safe_regex_match(), factory_context.regexEngine());
62
19.2k
    break;
63
7.68k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kRangeMatch:
64
7.68k
    header_match_type_ = HeaderMatchType::Range;
65
7.68k
    range_.set_start(config.range_match().start());
66
7.68k
    range_.set_end(config.range_match().end());
67
7.68k
    break;
68
13.1k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPresentMatch:
69
13.1k
    header_match_type_ = HeaderMatchType::Present;
70
13.1k
    present_ = config.present_match();
71
13.1k
    break;
72
8.59k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPrefixMatch:
73
8.59k
    header_match_type_ = HeaderMatchType::Prefix;
74
8.59k
    value_ = config.prefix_match();
75
8.59k
    break;
76
10.6k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kSuffixMatch:
77
10.6k
    header_match_type_ = HeaderMatchType::Suffix;
78
10.6k
    value_ = config.suffix_match();
79
10.6k
    break;
80
8.28k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kContainsMatch:
81
8.28k
    header_match_type_ = HeaderMatchType::Contains;
82
8.28k
    value_ = config.contains_match();
83
8.28k
    break;
84
7.20k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kStringMatch:
85
7.20k
    header_match_type_ = HeaderMatchType::StringMatch;
86
7.20k
    string_match_ =
87
7.20k
        std::make_unique<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>(
88
7.20k
            config.string_match(), factory_context);
89
7.20k
    break;
90
39.5k
  case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::
91
39.5k
      HEADER_MATCH_SPECIFIER_NOT_SET:
92
39.5k
    FALLTHRU;
93
39.5k
  default:
94
39.5k
    header_match_type_ = HeaderMatchType::Present;
95
39.5k
    present_ = true;
96
39.5k
    break;
97
123k
  }
98
123k
}
99
100
bool HeaderUtility::matchHeaders(const HeaderMap& request_headers,
101
44.3k
                                 const std::vector<HeaderDataPtr>& config_headers) {
102
  // No headers to match is considered a match.
103
44.3k
  if (!config_headers.empty()) {
104
10.4k
    for (const HeaderDataPtr& cfg_header_data : config_headers) {
105
10.4k
      if (!matchHeaders(request_headers, *cfg_header_data)) {
106
4.47k
        return false;
107
4.47k
      }
108
10.4k
    }
109
5.98k
  }
110
111
39.8k
  return true;
112
44.3k
}
113
114
HeaderUtility::GetAllOfHeaderAsStringResult
115
HeaderUtility::getAllOfHeaderAsString(const HeaderMap::GetResult& header_value,
116
33.5k
                                      absl::string_view separator) {
117
33.5k
  GetAllOfHeaderAsStringResult result;
118
  // In this case we concatenate all found headers using a delimiter before performing the
119
  // final match. We use an InlinedVector of absl::string_view to invoke the optimized join
120
  // algorithm. This requires a copying phase before we invoke join. The 3 used as the inline
121
  // size has been arbitrarily chosen.
122
  // TODO(mattklein123): Do we need to normalize any whitespace here?
123
33.5k
  absl::InlinedVector<absl::string_view, 3> string_view_vector;
124
33.5k
  string_view_vector.reserve(header_value.size());
125
159k
  for (size_t i = 0; i < header_value.size(); i++) {
126
126k
    string_view_vector.push_back(header_value[i]->value().getStringView());
127
126k
  }
128
33.5k
  result.result_backing_string_ = absl::StrJoin(string_view_vector, separator);
129
130
33.5k
  return result;
131
33.5k
}
132
133
HeaderUtility::GetAllOfHeaderAsStringResult
134
HeaderUtility::getAllOfHeaderAsString(const HeaderMap& headers, const Http::LowerCaseString& key,
135
274k
                                      absl::string_view separator) {
136
274k
  GetAllOfHeaderAsStringResult result;
137
274k
  const auto header_value = headers.get(key);
138
139
274k
  if (header_value.empty()) {
140
    // Empty for clarity. Avoid handling the empty case in the block below if the runtime feature
141
    // is disabled.
142
262k
  } else if (header_value.size() == 1) {
143
229k
    result.result_ = header_value[0]->value().getStringView();
144
229k
  } else {
145
33.5k
    return getAllOfHeaderAsString(header_value, separator);
146
33.5k
  }
147
148
240k
  return result;
149
274k
}
150
151
21.0k
bool HeaderUtility::matchHeaders(const HeaderMap& request_headers, const HeaderData& header_data) {
152
21.0k
  const auto header_value = getAllOfHeaderAsString(request_headers, header_data.name_);
153
154
21.0k
  if (!header_value.result().has_value() && !header_data.treat_missing_as_empty_) {
155
4.74k
    if (header_data.invert_match_) {
156
1.33k
      return header_data.header_match_type_ == HeaderMatchType::Present && header_data.present_;
157
3.40k
    } else {
158
3.40k
      return header_data.header_match_type_ == HeaderMatchType::Present && !header_data.present_;
159
3.40k
    }
160
4.74k
  }
161
162
  // If the header does not have value and the result is not returned in the
163
  // code above, it means treat_missing_as_empty_ is set to true and we should
164
  // treat the header value as empty.
165
16.3k
  const auto value = header_value.result().has_value() ? header_value.result().value() : "";
166
16.3k
  bool match;
167
16.3k
  switch (header_data.header_match_type_) {
168
2.49k
  case HeaderMatchType::Value:
169
2.49k
    match = header_data.value_.empty() || value == header_data.value_;
170
2.49k
    break;
171
907
  case HeaderMatchType::Regex:
172
907
    match = header_data.regex_->match(value);
173
907
    break;
174
1.35k
  case HeaderMatchType::Range: {
175
1.35k
    int64_t header_int_value = 0;
176
1.35k
    match = absl::SimpleAtoi(value, &header_int_value) &&
177
1.35k
            header_int_value >= header_data.range_.start() &&
178
1.35k
            header_int_value < header_data.range_.end();
179
1.35k
    break;
180
0
  }
181
4.10k
  case HeaderMatchType::Present:
182
4.10k
    match = header_data.present_;
183
4.10k
    break;
184
1.63k
  case HeaderMatchType::Prefix:
185
1.63k
    match = absl::StartsWith(value, header_data.value_);
186
1.63k
    break;
187
1.55k
  case HeaderMatchType::Suffix:
188
1.55k
    match = absl::EndsWith(value, header_data.value_);
189
1.55k
    break;
190
1.74k
  case HeaderMatchType::Contains:
191
1.74k
    match = absl::StrContains(value, header_data.value_);
192
1.74k
    break;
193
2.52k
  case HeaderMatchType::StringMatch:
194
2.52k
    match = header_data.string_match_->match(value);
195
2.52k
    break;
196
16.3k
  }
197
198
16.3k
  return match != header_data.invert_match_;
199
16.3k
}
200
201
761k
bool HeaderUtility::headerValueIsValid(const absl::string_view header_value) {
202
761k
  return http2::adapter::HeaderValidator::IsValidHeaderValue(header_value,
203
761k
                                                             http2::adapter::ObsTextOption::kAllow);
204
761k
}
205
206
923k
bool HeaderUtility::headerNameIsValid(absl::string_view header_key) {
207
923k
  if (!header_key.empty() && header_key[0] == ':') {
208
463k
#ifdef ENVOY_NGHTTP2
209
463k
    if (!Runtime::runtimeFeatureEnabled(
210
463k
            "envoy.reloadable_features.sanitize_http2_headers_without_nghttp2")) {
211
      // For HTTP/2 pseudo header, use the HTTP/2 semantics for checking validity
212
0
      return nghttp2_check_header_name(reinterpret_cast<const uint8_t*>(header_key.data()),
213
0
                                       header_key.size()) != 0;
214
0
    }
215
463k
#endif
216
463k
    header_key.remove_prefix(1);
217
463k
    if (header_key.empty()) {
218
1.19k
      return false;
219
1.19k
    }
220
463k
  }
221
  // For all other header use HTTP/1 semantics. The only difference from HTTP/2 is that
222
  // uppercase characters are allowed. This allows HTTP filters to add header with mixed
223
  // case names. The HTTP/1 codec will send as is, as uppercase characters are allowed.
224
  // However the HTTP/2 codec will NOT convert these to lowercase when serializing the
225
  // header map, thus producing an invalid request.
226
  // TODO(yanavlasov): make validation in HTTP/2 case stricter.
227
922k
  bool is_valid = true;
228
14.5M
  for (auto iter = header_key.begin(); iter != header_key.end() && is_valid; ++iter) {
229
13.6M
    is_valid &= testCharInTable(kGenericHeaderNameCharTable, *iter);
230
13.6M
  }
231
922k
  return is_valid;
232
923k
}
233
234
14.3k
bool HeaderUtility::headerNameContainsUnderscore(const absl::string_view header_name) {
235
14.3k
  return header_name.find('_') != absl::string_view::npos;
236
14.3k
}
237
238
namespace {
239
// This function validates the authority header for both HTTP/1 and HTTP/2.
240
// Note the HTTP/1 spec allows "user-info@host:port" for the authority, whereas
241
// the HTTP/2 spec only allows "host:port". Thus, this function permits all the
242
// HTTP/2 valid characters (similar to oghttp2's implementation) and the "@" character.
243
// Once UHV is used, this function should be removed, and the HTTP/1 and HTTP/2
244
// authority validations should be different.
245
390k
bool check_authority_h1_h2(const absl::string_view header_value) {
246
390k
  static constexpr char ValidAuthorityChars[] = {
247
390k
      0 /* NUL  */, 0 /* SOH  */, 0 /* STX  */, 0 /* ETX  */,
248
390k
      0 /* EOT  */, 0 /* ENQ  */, 0 /* ACK  */, 0 /* BEL  */,
249
390k
      0 /* BS   */, 0 /* HT   */, 0 /* LF   */, 0 /* VT   */,
250
390k
      0 /* FF   */, 0 /* CR   */, 0 /* SO   */, 0 /* SI   */,
251
390k
      0 /* DLE  */, 0 /* DC1  */, 0 /* DC2  */, 0 /* DC3  */,
252
390k
      0 /* DC4  */, 0 /* NAK  */, 0 /* SYN  */, 0 /* ETB  */,
253
390k
      0 /* CAN  */, 0 /* EM   */, 0 /* SUB  */, 0 /* ESC  */,
254
390k
      0 /* FS   */, 0 /* GS   */, 0 /* RS   */, 0 /* US   */,
255
390k
      0 /* SPC  */, 1 /* !    */, 0 /* "    */, 0 /* #    */,
256
390k
      1 /* $    */, 1 /* %    */, 1 /* &    */, 1 /* '    */,
257
390k
      1 /* (    */, 1 /* )    */, 1 /* *    */, 1 /* +    */,
258
390k
      1 /* ,    */, 1 /* -    */, 1 /* . */,    0 /* /    */,
259
390k
      1 /* 0    */, 1 /* 1    */, 1 /* 2    */, 1 /* 3    */,
260
390k
      1 /* 4    */, 1 /* 5    */, 1 /* 6    */, 1 /* 7    */,
261
390k
      1 /* 8    */, 1 /* 9    */, 1 /* :    */, 1 /* ;    */,
262
390k
      0 /* <    */, 1 /* =    */, 0 /* >    */, 0 /* ?    */,
263
390k
      1 /* @    */, 1 /* A    */, 1 /* B    */, 1 /* C    */,
264
390k
      1 /* D    */, 1 /* E    */, 1 /* F    */, 1 /* G    */,
265
390k
      1 /* H    */, 1 /* I    */, 1 /* J    */, 1 /* K    */,
266
390k
      1 /* L    */, 1 /* M    */, 1 /* N    */, 1 /* O    */,
267
390k
      1 /* P    */, 1 /* Q    */, 1 /* R    */, 1 /* S    */,
268
390k
      1 /* T    */, 1 /* U    */, 1 /* V    */, 1 /* W    */,
269
390k
      1 /* X    */, 1 /* Y    */, 1 /* Z    */, 1 /* [    */,
270
390k
      0 /* \    */, 1 /* ]    */, 0 /* ^    */, 1 /* _    */,
271
390k
      0 /* `    */, 1 /* a    */, 1 /* b    */, 1 /* c    */,
272
390k
      1 /* d    */, 1 /* e    */, 1 /* f    */, 1 /* g    */,
273
390k
      1 /* h    */, 1 /* i    */, 1 /* j    */, 1 /* k    */,
274
390k
      1 /* l    */, 1 /* m    */, 1 /* n    */, 1 /* o    */,
275
390k
      1 /* p    */, 1 /* q    */, 1 /* r    */, 1 /* s    */,
276
390k
      1 /* t    */, 1 /* u    */, 1 /* v    */, 1 /* w    */,
277
390k
      1 /* x    */, 1 /* y    */, 1 /* z    */, 0 /* {    */,
278
390k
      0 /* |    */, 0 /* }    */, 1 /* ~    */, 0 /* DEL  */,
279
390k
      0 /* 0x80 */, 0 /* 0x81 */, 0 /* 0x82 */, 0 /* 0x83 */,
280
390k
      0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, 0 /* 0x87 */,
281
390k
      0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */,
282
390k
      0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */,
283
390k
      0 /* 0x90 */, 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */,
284
390k
      0 /* 0x94 */, 0 /* 0x95 */, 0 /* 0x96 */, 0 /* 0x97 */,
285
390k
      0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, 0 /* 0x9b */,
286
390k
      0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */,
287
390k
      0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */,
288
390k
      0 /* 0xa4 */, 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */,
289
390k
      0 /* 0xa8 */, 0 /* 0xa9 */, 0 /* 0xaa */, 0 /* 0xab */,
290
390k
      0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, 0 /* 0xaf */,
291
390k
      0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */,
292
390k
      0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */,
293
390k
      0 /* 0xb8 */, 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */,
294
390k
      0 /* 0xbc */, 0 /* 0xbd */, 0 /* 0xbe */, 0 /* 0xbf */,
295
390k
      0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, 0 /* 0xc3 */,
296
390k
      0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */,
297
390k
      0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */,
298
390k
      0 /* 0xcc */, 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */,
299
390k
      0 /* 0xd0 */, 0 /* 0xd1 */, 0 /* 0xd2 */, 0 /* 0xd3 */,
300
390k
      0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, 0 /* 0xd7 */,
301
390k
      0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */,
302
390k
      0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */,
303
390k
      0 /* 0xe0 */, 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */,
304
390k
      0 /* 0xe4 */, 0 /* 0xe5 */, 0 /* 0xe6 */, 0 /* 0xe7 */,
305
390k
      0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, 0 /* 0xeb */,
306
390k
      0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */,
307
390k
      0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */,
308
390k
      0 /* 0xf4 */, 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */,
309
390k
      0 /* 0xf8 */, 0 /* 0xf9 */, 0 /* 0xfa */, 0 /* 0xfb */,
310
390k
      0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, 0 /* 0xff */
311
390k
  };
312
313
2.17M
  for (const uint8_t c : header_value) {
314
2.17M
    if (!ValidAuthorityChars[c]) {
315
432
      return false;
316
432
    }
317
2.17M
  }
318
390k
  return true;
319
390k
}
320
} // namespace
321
322
390k
bool HeaderUtility::authorityIsValid(const absl::string_view header_value) {
323
390k
  if (Runtime::runtimeFeatureEnabled(
324
390k
          "envoy.reloadable_features.internal_authority_header_validator")) {
325
390k
    return check_authority_h1_h2(header_value);
326
390k
  }
327
0
  return http2::adapter::HeaderValidator::IsValidAuthority(header_value);
328
390k
}
329
330
32.3k
bool HeaderUtility::isSpecial1xx(const ResponseHeaderMap& response_headers) {
331
32.3k
  if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.proxy_104") &&
332
32.3k
      response_headers.Status()->value() == "104") {
333
73
    return true;
334
73
  }
335
32.2k
  return response_headers.Status()->value() == "100" ||
336
32.2k
         response_headers.Status()->value() == "102" || response_headers.Status()->value() == "103";
337
32.3k
}
338
339
734k
bool HeaderUtility::isConnect(const RequestHeaderMap& headers) {
340
734k
  return headers.Method() && headers.Method()->value() == Http::Headers::get().MethodValues.Connect;
341
734k
}
342
343
80.7k
bool HeaderUtility::isConnectUdpRequest(const RequestHeaderMap& headers) {
344
80.7k
  return headers.Upgrade() && absl::EqualsIgnoreCase(headers.getUpgradeValue(),
345
9.16k
                                                     Http::Headers::get().UpgradeValues.ConnectUdp);
346
80.7k
}
347
348
0
bool HeaderUtility::isConnectUdpResponse(const ResponseHeaderMap& headers) {
349
  // In connect-udp case, Envoy will transform the H2 headers to H1 upgrade headers.
350
  // A valid response should have SwitchingProtocol status and connect-udp upgrade.
351
0
  return headers.Upgrade() && Utility::getResponseStatus(headers) == 101 &&
352
0
         absl::EqualsIgnoreCase(headers.getUpgradeValue(),
353
0
                                Http::Headers::get().UpgradeValues.ConnectUdp);
354
0
}
355
356
bool HeaderUtility::isConnectResponse(const RequestHeaderMap* request_headers,
357
124k
                                      const ResponseHeaderMap& response_headers) {
358
124k
  return request_headers && isConnect(*request_headers) &&
359
124k
         static_cast<Http::Code>(Http::Utility::getResponseStatus(response_headers)) ==
360
49
             Http::Code::OK;
361
124k
}
362
363
8.46k
bool HeaderUtility::rewriteAuthorityForConnectUdp(RequestHeaderMap& headers) {
364
  // Per RFC 9298, the URI template must only contain ASCII characters in the range 0x21-0x7E.
365
8.46k
  absl::string_view path = headers.getPathValue();
366
4.68M
  for (char c : path) {
367
4.68M
    unsigned char ascii_code = static_cast<unsigned char>(c);
368
4.68M
    if (ascii_code < 0x21 || ascii_code > 0x7e) {
369
38
      ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a bad character in the path {}", path);
370
38
      return false;
371
38
    }
372
4.68M
  }
373
374
  // Extract target host and port from path using default template.
375
8.42k
  if (!absl::StartsWith(path, "/.well-known/masque/udp/")) {
376
2.28k
    ENVOY_LOG_MISC(warn, "CONNECT-UDP request path is not a well-known URI: {}", path);
377
2.28k
    return false;
378
2.28k
  }
379
380
6.14k
  std::vector<absl::string_view> path_split = absl::StrSplit(path, '/');
381
6.14k
  if (path_split.size() != 7 || path_split[4].empty() || path_split[5].empty() ||
382
6.14k
      !path_split[6].empty()) {
383
2.33k
    ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a malformed URI template in the path {}", path);
384
2.33k
    return false;
385
2.33k
  }
386
387
  // Utility::PercentEncoding::decode never returns an empty string if the input argument is not
388
  // empty.
389
3.80k
  std::string target_host = Utility::PercentEncoding::decode(path_split[4]);
390
  // Per RFC 9298, IPv6 Zone ID is not supported.
391
3.80k
  if (target_host.find('%') != std::string::npos) {
392
2.61k
    ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a non-escpaed char (%) in the path {}", path);
393
2.61k
    return false;
394
2.61k
  }
395
1.18k
  std::string target_port = Utility::PercentEncoding::decode(path_split[5]);
396
397
  // If the host is an IPv6 address, surround the address with square brackets.
398
1.18k
  in6_addr sin6_addr;
399
1.18k
  bool is_ipv6 = (inet_pton(AF_INET6, target_host.c_str(), &sin6_addr) == 1);
400
1.18k
  std::string new_host =
401
1.18k
      absl::StrCat((is_ipv6 ? absl::StrCat("[", target_host, "]") : target_host), ":", target_port);
402
1.18k
  headers.setHost(new_host);
403
404
1.18k
  return true;
405
3.80k
}
406
407
#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS
408
25
bool HeaderUtility::isCapsuleProtocol(const RequestOrResponseHeaderMap& headers) {
409
25
  Http::HeaderMap::GetResult capsule_protocol =
410
25
      headers.get(Envoy::Http::LowerCaseString("Capsule-Protocol"));
411
  // When there are multiple Capsule-Protocol header entries, it returns false. RFC 9297 specifies
412
  // that non-boolean value types must be ignored. If there are multiple header entries, the value
413
  // type becomes a List so the header field must be ignored.
414
25
  if (capsule_protocol.size() != 1) {
415
0
    return false;
416
0
  }
417
  // Parses the header value and extracts the boolean value ignoring parameters.
418
25
  absl::optional<quiche::structured_headers::ParameterizedItem> header_item =
419
25
      quiche::structured_headers::ParseItem(capsule_protocol[0]->value().getStringView());
420
25
  return header_item && header_item->item.is_boolean() && header_item->item.GetBoolean();
421
25
}
422
#endif
423
424
8.80k
bool HeaderUtility::requestShouldHaveNoBody(const RequestHeaderMap& headers) {
425
8.80k
  return (headers.Method() &&
426
8.80k
          (headers.Method()->value() == Http::Headers::get().MethodValues.Get ||
427
8.80k
           headers.Method()->value() == Http::Headers::get().MethodValues.Head ||
428
8.80k
           headers.Method()->value() == Http::Headers::get().MethodValues.Delete ||
429
8.80k
           headers.Method()->value() == Http::Headers::get().MethodValues.Trace ||
430
8.80k
           headers.Method()->value() == Http::Headers::get().MethodValues.Connect));
431
8.80k
}
432
433
2.55k
bool HeaderUtility::isEnvoyInternalRequest(const RequestHeaderMap& headers) {
434
2.55k
  const HeaderEntry* internal_request_header = headers.EnvoyInternalRequest();
435
2.55k
  return internal_request_header != nullptr &&
436
2.55k
         internal_request_header->value() == Headers::get().EnvoyInternalRequestValues.True;
437
2.55k
}
438
439
112
void HeaderUtility::stripTrailingHostDot(RequestHeaderMap& headers) {
440
112
  auto host = headers.getHostValue();
441
  // If the host ends in a period, remove it.
442
112
  auto dot_index = host.rfind('.');
443
112
  if (dot_index == std::string::npos) {
444
16
    return;
445
96
  } else if (dot_index == (host.size() - 1)) {
446
68
    host.remove_suffix(1);
447
68
    headers.setHost(host);
448
68
    return;
449
68
  }
450
  // If the dot is just before a colon, it must be preceding the port number.
451
  // IPv6 addresses may contain colons or dots, but the dot will never directly
452
  // precede the colon, so this check should be sufficient to detect a trailing port number.
453
28
  if (host[dot_index + 1] == ':') {
454
0
    headers.setHost(absl::StrCat(host.substr(0, dot_index), host.substr(dot_index + 1)));
455
0
  }
456
28
}
457
458
0
bool HeaderUtility::hostHasPort(absl::string_view original_host) {
459
0
  const absl::string_view::size_type port_start = getPortStart(original_host);
460
0
  const absl::string_view port_str = original_host.substr(port_start + 1);
461
0
  if (port_start == absl::string_view::npos) {
462
0
    return false;
463
0
  }
464
0
  uint32_t port = 0;
465
0
  if (!absl::SimpleAtoi(port_str, &port)) {
466
0
    return false;
467
0
  }
468
0
  return true;
469
0
}
470
471
absl::optional<uint32_t> HeaderUtility::stripPortFromHost(RequestHeaderMap& headers,
472
131
                                                          absl::optional<uint32_t> listener_port) {
473
131
  const absl::string_view original_host = headers.getHostValue();
474
131
  const absl::string_view::size_type port_start = getPortStart(original_host);
475
131
  if (port_start == absl::string_view::npos) {
476
112
    return absl::nullopt;
477
112
  }
478
19
  const absl::string_view port_str = original_host.substr(port_start + 1);
479
19
  uint32_t port = 0;
480
19
  if (!absl::SimpleAtoi(port_str, &port)) {
481
19
    return absl::nullopt;
482
19
  }
483
0
  if (listener_port.has_value() && port != listener_port) {
484
    // We would strip ports only if it is specified and they are the same, as local port of the
485
    // listener.
486
0
    return absl::nullopt;
487
0
  }
488
0
  const absl::string_view host = original_host.substr(0, port_start);
489
0
  headers.setHost(host);
490
0
  return port;
491
0
}
492
493
1.86k
absl::string_view::size_type HeaderUtility::getPortStart(absl::string_view host) {
494
1.86k
  const absl::string_view::size_type port_start = host.rfind(':');
495
1.86k
  if (port_start == absl::string_view::npos) {
496
1.78k
    return absl::string_view::npos;
497
1.78k
  }
498
  // According to RFC3986 v6 address is always enclosed in "[]". section 3.2.2.
499
78
  const auto v6_end_index = host.rfind(']');
500
78
  if (v6_end_index == absl::string_view::npos || v6_end_index < port_start) {
501
69
    if ((port_start + 1) > host.size()) {
502
0
      return absl::string_view::npos;
503
0
    }
504
69
    return port_start;
505
69
  }
506
9
  return absl::string_view::npos;
507
78
}
508
509
611k
constexpr bool isInvalidToken(unsigned char c) {
510
611k
  if (c == '!' || c == '|' || c == '~' || c == '*' || c == '+' || c == '-' || c == '.' ||
511
      // #, $, %, &, '
512
611k
      (c >= '#' && c <= '\'') ||
513
      // [0-9]
514
611k
      (c >= '0' && c <= '9') ||
515
      // [A-Z]
516
611k
      (c >= 'A' && c <= 'Z') ||
517
      // ^, _, `, [a-z]
518
611k
      (c >= '^' && c <= 'z')) {
519
611k
    return false;
520
611k
  }
521
25
  return true;
522
611k
}
523
524
absl::optional<std::reference_wrapper<const absl::string_view>>
525
200k
HeaderUtility::requestHeadersValid(const RequestHeaderMap& headers) {
526
  // Make sure the host is valid.
527
200k
  if (headers.Host() && !HeaderUtility::authorityIsValid(headers.Host()->value().getStringView())) {
528
242
    return SharedResponseCodeDetails::get().InvalidAuthority;
529
242
  }
530
200k
  if (headers.Method()) {
531
200k
    absl::string_view method = headers.Method()->value().getStringView();
532
200k
    if (method.empty() || std::any_of(method.begin(), method.end(), isInvalidToken)) {
533
28
      return SharedResponseCodeDetails::get().InvalidMethod;
534
28
    }
535
200k
  }
536
200k
  if (headers.Scheme() && absl::StrContains(headers.Scheme()->value().getStringView(), ",")) {
537
9
    return SharedResponseCodeDetails::get().InvalidScheme;
538
9
  }
539
200k
  return absl::nullopt;
540
200k
}
541
542
bool HeaderUtility::shouldCloseConnection(Http::Protocol protocol,
543
192k
                                          const RequestOrResponseHeaderMap& headers) {
544
  // HTTP/1.0 defaults to single-use connections. Make sure the connection will be closed unless
545
  // Keep-Alive is present.
546
192k
  if (protocol == Protocol::Http10 &&
547
192k
      (!headers.Connection() ||
548
280
       !Envoy::StringUtil::caseFindToken(headers.Connection()->value().getStringView(), ",",
549
280
                                         Http::Headers::get().ConnectionValues.KeepAlive))) {
550
280
    return true;
551
280
  }
552
553
192k
  if (protocol == Protocol::Http11 && headers.Connection() &&
554
192k
      Envoy::StringUtil::caseFindToken(headers.Connection()->value().getStringView(), ",",
555
2.56k
                                       Http::Headers::get().ConnectionValues.Close)) {
556
26
    return true;
557
26
  }
558
559
  // Note: Proxy-Connection is not a standard header, but is supported here
560
  // since it is supported by http-parser the underlying parser for http
561
  // requests.
562
192k
  if (protocol < Protocol::Http2 && headers.ProxyConnection() &&
563
192k
      Envoy::StringUtil::caseFindToken(headers.ProxyConnection()->value().getStringView(), ",",
564
1.46k
                                       Http::Headers::get().ConnectionValues.Close)) {
565
39
    return true;
566
39
  }
567
192k
  return false;
568
192k
}
569
570
141k
Http::Status HeaderUtility::checkRequiredRequestHeaders(const Http::RequestHeaderMap& headers) {
571
141k
  if (!headers.Method()) {
572
0
    return absl::InvalidArgumentError(
573
0
        absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Method.get()));
574
0
  }
575
141k
  bool is_connect = Http::HeaderUtility::isConnect(headers);
576
141k
  if (is_connect) {
577
2.44k
    if (!headers.Host()) {
578
      // Host header must be present for CONNECT request.
579
0
      return absl::InvalidArgumentError(
580
0
          absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Host.get()));
581
0
    }
582
2.44k
    if (headers.Path() && !headers.Protocol()) {
583
      // Path and Protocol header should only be present for CONNECT for upgrade style CONNECT.
584
120
      return absl::InvalidArgumentError(
585
120
          absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Protocol.get()));
586
120
    }
587
2.32k
    if (!headers.Path() && headers.Protocol()) {
588
      // Path and Protocol header should only be present for CONNECT for upgrade style CONNECT.
589
0
      return absl::InvalidArgumentError(
590
0
          absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Path.get()));
591
0
    }
592
139k
  } else {
593
139k
    if (!headers.Path()) {
594
      // :path header must be present for non-CONNECT requests.
595
0
      return absl::InvalidArgumentError(
596
0
          absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Path.get()));
597
0
    }
598
139k
  }
599
141k
  return Http::okStatus();
600
141k
}
601
602
118k
Http::Status HeaderUtility::checkValidRequestHeaders(const Http::RequestHeaderMap& headers) {
603
118k
  if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.validate_upstream_headers")) {
604
0
    return Http::okStatus();
605
0
  }
606
607
118k
  const HeaderEntry* invalid_entry = nullptr;
608
118k
  bool invalid_key = false;
609
543k
  headers.iterate([&invalid_entry, &invalid_key](const HeaderEntry& header) -> HeaderMap::Iterate {
610
543k
    if (!HeaderUtility::headerNameIsValid(header.key().getStringView())) {
611
3.19k
      invalid_entry = &header;
612
3.19k
      invalid_key = true;
613
3.19k
      return HeaderMap::Iterate::Break;
614
3.19k
    }
615
616
540k
    if (!HeaderUtility::headerValueIsValid(header.value().getStringView())) {
617
414
      invalid_entry = &header;
618
414
      invalid_key = false;
619
414
      return HeaderMap::Iterate::Break;
620
414
    }
621
622
539k
    return HeaderMap::Iterate::Continue;
623
540k
  });
624
625
118k
  if (invalid_entry) {
626
    // The header key may contain non-printable characters. Escape the key so that the error
627
    // details can be safely presented.
628
3.60k
    const absl::string_view key = invalid_entry->key().getStringView();
629
3.60k
    uint64_t extra_length = JsonEscaper::extraSpace(key);
630
3.60k
    const std::string escaped_key = JsonEscaper::escapeString(key, extra_length);
631
632
3.60k
    return absl::InvalidArgumentError(
633
3.60k
        absl::StrCat("invalid header ", invalid_key ? "name: " : "value for: ", escaped_key));
634
3.60k
  }
635
115k
  return Http::okStatus();
636
118k
}
637
638
125k
Http::Status HeaderUtility::checkRequiredResponseHeaders(const Http::ResponseHeaderMap& headers) {
639
125k
  const absl::optional<uint64_t> status = Utility::getResponseStatusOrNullopt(headers);
640
125k
  if (!status.has_value()) {
641
0
    return absl::InvalidArgumentError(
642
0
        absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Status.get()));
643
0
  }
644
125k
  return Http::okStatus();
645
125k
}
646
647
78.0k
bool HeaderUtility::isRemovableHeader(absl::string_view header) {
648
78.0k
  return (header.empty() || header[0] != ':') &&
649
78.0k
         !absl::EqualsIgnoreCase(header, Headers::get().HostLegacy.get());
650
78.0k
}
651
652
342k
bool HeaderUtility::isModifiableHeader(absl::string_view header) {
653
342k
  return (header.empty() || header[0] != ':') &&
654
342k
         !absl::EqualsIgnoreCase(header, Headers::get().HostLegacy.get());
655
342k
}
656
657
HeaderUtility::HeaderValidationResult HeaderUtility::checkHeaderNameForUnderscores(
658
    absl::string_view header_name,
659
    envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction
660
        headers_with_underscores_action,
661
75
    HeaderValidatorStats& stats) {
662
75
  if (headers_with_underscores_action == envoy::config::core::v3::HttpProtocolOptions::ALLOW ||
663
75
      !HeaderUtility::headerNameContainsUnderscore(header_name)) {
664
75
    return HeaderValidationResult::ACCEPT;
665
75
  }
666
0
  if (headers_with_underscores_action ==
667
0
      envoy::config::core::v3::HttpProtocolOptions::DROP_HEADER) {
668
0
    ENVOY_LOG_MISC(debug, "Dropping header with invalid characters in its name: {}", header_name);
669
0
    stats.incDroppedHeadersWithUnderscores();
670
0
    return HeaderValidationResult::DROP;
671
0
  }
672
0
  ENVOY_LOG_MISC(debug, "Rejecting request due to header name with underscores: {}", header_name);
673
0
  stats.incRequestsRejectedWithUnderscoresInHeaders();
674
0
  return HeaderUtility::HeaderValidationResult::REJECT;
675
0
}
676
677
HeaderUtility::HeaderValidationResult
678
HeaderUtility::validateContentLength(absl::string_view header_value,
679
                                     bool override_stream_error_on_invalid_http_message,
680
0
                                     bool& should_close_connection, size_t& content_length_output) {
681
0
  should_close_connection = false;
682
0
  std::vector<absl::string_view> values = absl::StrSplit(header_value, ',');
683
0
  absl::optional<uint64_t> content_length;
684
0
  for (const absl::string_view& value : values) {
685
0
    uint64_t new_value;
686
0
    if (!absl::SimpleAtoi(value, &new_value) ||
687
0
        !std::all_of(value.begin(), value.end(), absl::ascii_isdigit)) {
688
0
      ENVOY_LOG_MISC(debug, "Content length was either unparseable or negative");
689
0
      should_close_connection = !override_stream_error_on_invalid_http_message;
690
0
      return HeaderValidationResult::REJECT;
691
0
    }
692
0
    if (!content_length.has_value()) {
693
0
      content_length = new_value;
694
0
      continue;
695
0
    }
696
0
    if (new_value != content_length.value()) {
697
0
      ENVOY_LOG_MISC(
698
0
          debug,
699
0
          "Parsed content length {} is inconsistent with previously detected content length {}",
700
0
          new_value, content_length.value());
701
0
      should_close_connection = !override_stream_error_on_invalid_http_message;
702
0
      return HeaderValidationResult::REJECT;
703
0
    }
704
0
  }
705
0
  content_length_output = content_length.value();
706
0
  return HeaderValidationResult::ACCEPT;
707
0
}
708
709
std::vector<absl::string_view>
710
0
HeaderUtility::parseCommaDelimitedHeader(absl::string_view header_value) {
711
0
  std::vector<absl::string_view> values;
712
0
  for (absl::string_view s : absl::StrSplit(header_value, ',')) {
713
0
    absl::string_view token = absl::StripAsciiWhitespace(s);
714
0
    if (token.empty()) {
715
0
      continue;
716
0
    }
717
0
    values.emplace_back(token);
718
0
  }
719
0
  return values;
720
0
}
721
722
0
absl::string_view HeaderUtility::getSemicolonDelimitedAttribute(absl::string_view value) {
723
0
  return absl::StripAsciiWhitespace(StringUtil::cropRight(value, ";"));
724
0
}
725
726
std::string HeaderUtility::addEncodingToAcceptEncoding(absl::string_view accept_encoding_header,
727
0
                                                       absl::string_view encoding) {
728
  // Append the content encoding only if it isn't already present in the
729
  // accept_encoding header. If it is present with a q-value ("gzip;q=0.3"),
730
  // remove the q-value to indicate that the content encoding setting that we
731
  // add has max priority (i.e. q-value 1.0).
732
0
  std::vector<absl::string_view> newContentEncodings;
733
0
  std::vector<absl::string_view> contentEncodings =
734
0
      Http::HeaderUtility::parseCommaDelimitedHeader(accept_encoding_header);
735
0
  for (absl::string_view contentEncoding : contentEncodings) {
736
0
    absl::string_view strippedEncoding =
737
0
        Http::HeaderUtility::getSemicolonDelimitedAttribute(contentEncoding);
738
0
    if (strippedEncoding != encoding) {
739
      // Add back all content encodings back except for the content encoding that we want to
740
      // add. For example, if content encoding is "gzip", this filters out encodings "gzip" and
741
      // "gzip;q=0.6".
742
0
      newContentEncodings.push_back(contentEncoding);
743
0
    }
744
0
  }
745
  // Finally add a single instance of our content encoding.
746
0
  newContentEncodings.push_back(encoding);
747
0
  return absl::StrJoin(newContentEncodings, ",");
748
0
}
749
750
665
bool HeaderUtility::isStandardConnectRequest(const Http::RequestHeaderMap& headers) {
751
665
  return headers.getMethodValue() == Http::Headers::get().MethodValues.Connect &&
752
665
         headers.getProtocolValue().empty();
753
665
}
754
755
0
bool HeaderUtility::isExtendedH2ConnectRequest(const Http::RequestHeaderMap& headers) {
756
0
  return headers.getMethodValue() == Http::Headers::get().MethodValues.Connect &&
757
0
         !headers.getProtocolValue().empty();
758
0
}
759
760
225
bool HeaderUtility::isPseudoHeader(absl::string_view header_name) {
761
225
  return !header_name.empty() && header_name[0] == ':';
762
225
}
763
764
} // namespace Http
765
} // namespace Envoy