LCOV - code coverage report
Current view: top level - source/common/http - header_utility.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 187 478 39.1 %
Date: 2024-01-05 06:35:25 Functions: 25 39 64.1 %

          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

Generated by: LCOV version 1.15