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