LCOV - code coverage report
Current view: top level - source/common/router - header_parser.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 79 139 56.8 %
Date: 2024-01-05 06:35:25 Functions: 7 9 77.8 %

          Line data    Source code
       1             : #include "source/common/router/header_parser.h"
       2             : 
       3             : #include <cctype>
       4             : #include <memory>
       5             : #include <regex>
       6             : #include <string>
       7             : 
       8             : #include "envoy/config/core/v3/base.pb.h"
       9             : 
      10             : #include "source/common/common/assert.h"
      11             : #include "source/common/formatter/substitution_formatter.h"
      12             : #include "source/common/http/header_utility.h"
      13             : #include "source/common/http/headers.h"
      14             : #include "source/common/json/json_loader.h"
      15             : #include "source/common/protobuf/utility.h"
      16             : 
      17             : #include "absl/strings/str_cat.h"
      18             : #include "absl/strings/str_replace.h"
      19             : 
      20             : namespace Envoy {
      21             : namespace Router {
      22             : 
      23             : namespace {
      24             : 
      25             : Formatter::FormatterPtr
      26         501 : parseHttpHeaderFormatter(const envoy::config::core::v3::HeaderValue& header_value) {
      27         501 :   const std::string& key = header_value.key();
      28             :   // PGV constraints provide this guarantee.
      29         501 :   ASSERT(!key.empty());
      30             :   // We reject :path/:authority rewriting, there is already a well defined mechanism to
      31             :   // perform this in the RouteAction, and doing this via request_headers_to_add
      32             :   // will cause us to have to worry about interaction with other aspects of the
      33             :   // RouteAction, e.g. prefix rewriting. We also reject other :-prefixed
      34             :   // headers, since it seems dangerous and there doesn't appear a use case.
      35             :   // Host is disallowed as it created confusing and inconsistent behaviors for
      36             :   // HTTP/1 and HTTP/2. It could arguably be allowed on the response path.
      37         501 :   if (!Http::HeaderUtility::isModifiableHeader(key)) {
      38           0 :     throwEnvoyExceptionOrPanic(":-prefixed or host headers may not be modified");
      39           0 :   }
      40             : 
      41             :   // UPSTREAM_METADATA and DYNAMIC_METADATA must be translated from JSON ["a", "b"] format to colon
      42             :   // format (a:b)
      43         501 :   std::string final_header_value = HeaderParser::translateMetadataFormat(header_value.value());
      44             :   // Change PER_REQUEST_STATE to FILTER_STATE.
      45         501 :   final_header_value = HeaderParser::translatePerRequestState(final_header_value);
      46             : 
      47             :   // Let the substitution formatter parse the final_header_value.
      48         501 :   return std::make_unique<Envoy::Formatter::FormatterImpl>(final_header_value, true);
      49         501 : }
      50             : 
      51             : } // namespace
      52             : 
      53             : HeadersToAddEntry::HeadersToAddEntry(const HeaderValueOption& header_value_option)
      54             :     : original_value_(header_value_option.header().value()),
      55         507 :       add_if_empty_(header_value_option.keep_empty_value()) {
      56             : 
      57         507 :   if (header_value_option.has_append()) {
      58             :     // 'append' is set and ensure the 'append_action' value is equal to the default value.
      59          56 :     if (header_value_option.append_action() != HeaderValueOption::APPEND_IF_EXISTS_OR_ADD) {
      60           6 :       throwEnvoyExceptionOrPanic("Both append and append_action are set and it's not allowed");
      61           6 :     }
      62             : 
      63          50 :     append_action_ = header_value_option.append().value()
      64          50 :                          ? HeaderValueOption::APPEND_IF_EXISTS_OR_ADD
      65          50 :                          : HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD;
      66         451 :   } else {
      67         451 :     append_action_ = header_value_option.append_action();
      68         451 :   }
      69             : 
      70         501 :   formatter_ = parseHttpHeaderFormatter(header_value_option.header());
      71         501 : }
      72             : 
      73             : HeadersToAddEntry::HeadersToAddEntry(const HeaderValue& header_value,
      74             :                                      HeaderAppendAction append_action)
      75           0 :     : original_value_(header_value.value()), append_action_(append_action) {
      76           0 :   formatter_ = parseHttpHeaderFormatter(header_value);
      77           0 : }
      78             : 
      79             : HeaderParserPtr
      80         382 : HeaderParser::configure(const Protobuf::RepeatedPtrField<HeaderValueOption>& headers_to_add) {
      81         382 :   HeaderParserPtr header_parser(new HeaderParser());
      82         552 :   for (const auto& header_value_option : headers_to_add) {
      83         507 :     header_parser->headers_to_add_.emplace_back(
      84         507 :         Http::LowerCaseString(header_value_option.header().key()),
      85         507 :         HeadersToAddEntry{header_value_option});
      86         507 :   }
      87             : 
      88         382 :   return header_parser;
      89         382 : }
      90             : 
      91             : HeaderParserPtr HeaderParser::configure(
      92             :     const Protobuf::RepeatedPtrField<envoy::config::core::v3::HeaderValue>& headers_to_add,
      93          35 :     HeaderAppendAction append_action) {
      94          35 :   HeaderParserPtr header_parser(new HeaderParser());
      95             : 
      96          35 :   for (const auto& header_value : headers_to_add) {
      97           0 :     header_parser->headers_to_add_.emplace_back(Http::LowerCaseString(header_value.key()),
      98           0 :                                                 HeadersToAddEntry{header_value, append_action});
      99           0 :   }
     100             : 
     101          35 :   return header_parser;
     102          35 : }
     103             : 
     104             : HeaderParserPtr
     105             : HeaderParser::configure(const Protobuf::RepeatedPtrField<HeaderValueOption>& headers_to_add,
     106         360 :                         const Protobuf::RepeatedPtrField<std::string>& headers_to_remove) {
     107         360 :   HeaderParserPtr header_parser = configure(headers_to_add);
     108             : 
     109         360 :   for (const auto& header : headers_to_remove) {
     110             :     // We reject :-prefix (e.g. :path) removal here. This is dangerous, since other aspects of
     111             :     // request finalization assume their existence and they are needed for well-formedness in most
     112             :     // cases.
     113         127 :     if (!Http::HeaderUtility::isRemovableHeader(header)) {
     114           2 :       throwEnvoyExceptionOrPanic(":-prefixed or host headers may not be removed");
     115           2 :     }
     116         125 :     header_parser->headers_to_remove_.emplace_back(header);
     117         125 :   }
     118             : 
     119         358 :   return header_parser;
     120         360 : }
     121             : 
     122             : void HeaderParser::evaluateHeaders(Http::HeaderMap& headers,
     123             :                                    const Formatter::HttpFormatterContext& context,
     124        1087 :                                    const StreamInfo::StreamInfo& stream_info) const {
     125        1087 :   evaluateHeaders(headers, context, &stream_info);
     126        1087 : }
     127             : 
     128             : void HeaderParser::evaluateHeaders(Http::HeaderMap& headers,
     129             :                                    const Formatter::HttpFormatterContext& context,
     130        1805 :                                    const StreamInfo::StreamInfo* stream_info) const {
     131             :   // Removing headers in the headers_to_remove_ list first makes
     132             :   // remove-before-add the default behavior as expected by users.
     133        1805 :   for (const auto& header : headers_to_remove_) {
     134           9 :     headers.remove(header);
     135           9 :   }
     136             : 
     137             :   // Temporary storage to hold evaluated values of headers to add and replace. This is required
     138             :   // to execute all formatters using the original received headers.
     139             :   // Only after all the formatters produced the new values of the headers, the headers are set.
     140             :   // absl::InlinedVector is optimized for 4 headers. After that it behaves as normal std::vector.
     141             :   // It is assumed that most of the use cases will add or modify fairly small number of headers
     142             :   // (<=4). If this assumption changes, the number of inlined capacity should be increased.
     143             :   // header_formatter_speed_test.cc provides micro-benchmark for evaluating speed of adding and
     144             :   // replacing headers and should be used when modifying the code below to access the performance
     145             :   // impact of code changes.
     146        1805 :   absl::InlinedVector<std::pair<const Http::LowerCaseString&, const std::string>, 4> headers_to_add,
     147        1805 :       headers_to_overwrite;
     148             :   // value_buffer is used only when stream_info is a valid pointer and stores header value
     149             :   // created by a formatter. It is declared outside of 'for' loop for performance reason to avoid
     150             :   // stack allocation and unnecessary std::string's memory adjustments for each iteration. The
     151             :   // actual value of the header is accessed via 'value' variable which is initialized differently
     152             :   // depending whether stream_info and valid or nullptr. Based on performance tests implemented in
     153             :   // header_formatter_speed_test.cc this approach strikes the best balance between performance and
     154             :   // readability.
     155        1805 :   std::string value_buffer;
     156        1860 :   for (const auto& [key, entry] : headers_to_add_) {
     157         351 :     absl::string_view value;
     158         351 :     if (stream_info != nullptr) {
     159         351 :       value_buffer = entry.formatter_->formatWithContext(context, *stream_info);
     160         351 :       value = value_buffer;
     161         351 :     } else {
     162           0 :       value = entry.original_value_;
     163           0 :     }
     164         351 :     if (!value.empty() || entry.add_if_empty_) {
     165         308 :       switch (entry.append_action_) {
     166           0 :         PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
     167         284 :       case HeaderValueOption::APPEND_IF_EXISTS_OR_ADD:
     168         284 :         headers_to_add.emplace_back(key, value);
     169         284 :         break;
     170           0 :       case HeaderValueOption::ADD_IF_ABSENT:
     171           0 :         if (auto header_entry = headers.get(key); header_entry.empty()) {
     172           0 :           headers_to_add.emplace_back(key, value);
     173           0 :         }
     174           0 :         break;
     175           0 :       case HeaderValueOption::OVERWRITE_IF_EXISTS:
     176           0 :         if (headers.get(key).empty()) {
     177           0 :           break;
     178           0 :         }
     179           0 :         FALLTHRU;
     180          24 :       case HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD:
     181          24 :         headers_to_overwrite.emplace_back(key, value);
     182          24 :         break;
     183         308 :       }
     184         308 :     }
     185         351 :   }
     186             : 
     187             :   // First overwrite all headers which need to be overwritten.
     188        1805 :   for (const auto& header : headers_to_overwrite) {
     189          24 :     headers.setReferenceKey(header.first, header.second);
     190          24 :   }
     191             : 
     192             :   // Now add headers which should be added.
     193        1805 :   for (const auto& header : headers_to_add) {
     194         284 :     headers.addReferenceKey(header.first, header.second);
     195         284 :   }
     196        1805 : }
     197             : 
     198             : Http::HeaderTransforms HeaderParser::getHeaderTransforms(const StreamInfo::StreamInfo& stream_info,
     199           0 :                                                          bool do_formatting) const {
     200           0 :   Http::HeaderTransforms transforms;
     201             : 
     202           0 :   for (const auto& [key, entry] : headers_to_add_) {
     203           0 :     if (do_formatting) {
     204           0 :       const std::string value = entry.formatter_->formatWithContext({}, stream_info);
     205           0 :       if (!value.empty() || entry.add_if_empty_) {
     206           0 :         switch (entry.append_action_) {
     207           0 :         case HeaderValueOption::APPEND_IF_EXISTS_OR_ADD:
     208           0 :           transforms.headers_to_append_or_add.push_back({key, value});
     209           0 :           break;
     210           0 :         case HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD:
     211           0 :           transforms.headers_to_overwrite_or_add.push_back({key, value});
     212           0 :           break;
     213           0 :         case HeaderValueOption::ADD_IF_ABSENT:
     214           0 :           transforms.headers_to_add_if_absent.push_back({key, value});
     215           0 :           break;
     216           0 :         default:
     217           0 :           break;
     218           0 :         }
     219           0 :       }
     220           0 :     } else {
     221           0 :       switch (entry.append_action_) {
     222           0 :       case HeaderValueOption::APPEND_IF_EXISTS_OR_ADD:
     223           0 :         transforms.headers_to_append_or_add.push_back({key, entry.original_value_});
     224           0 :         break;
     225           0 :       case HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD:
     226           0 :         transforms.headers_to_overwrite_or_add.push_back({key, entry.original_value_});
     227           0 :         break;
     228           0 :       case HeaderValueOption::ADD_IF_ABSENT:
     229           0 :         transforms.headers_to_add_if_absent.push_back({key, entry.original_value_});
     230           0 :         break;
     231           0 :       default:
     232           0 :         break;
     233           0 :       }
     234           0 :     }
     235           0 :   }
     236             : 
     237           0 :   transforms.headers_to_remove = headers_to_remove_;
     238             : 
     239           0 :   return transforms;
     240           0 : }
     241             : 
     242             : } // namespace Router
     243             : } // namespace Envoy

Generated by: LCOV version 1.15