Coverage Report

Created: 2023-11-12 09:30

/proc/self/cwd/source/common/router/header_parser.cc
Line
Count
Source (jump to first uncovered line)
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
362k
parseHttpHeaderFormatter(const envoy::config::core::v3::HeaderValue& header_value) {
27
362k
  const std::string& key = header_value.key();
28
  // PGV constraints provide this guarantee.
29
362k
  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
362k
  if (!Http::HeaderUtility::isModifiableHeader(key)) {
38
37
    throwEnvoyExceptionOrPanic(":-prefixed or host headers may not be modified");
39
37
  }
40
41
  // UPSTREAM_METADATA and DYNAMIC_METADATA must be translated from JSON ["a", "b"] format to colon
42
  // format (a:b)
43
362k
  std::string final_header_value = HeaderParser::translateMetadataFormat(header_value.value());
44
  // Change PER_REQUEST_STATE to FILTER_STATE.
45
362k
  final_header_value = HeaderParser::translatePerRequestState(final_header_value);
46
47
  // Let the substitution formatter parse the final_header_value.
48
362k
  return std::make_unique<Envoy::Formatter::FormatterImpl>(final_header_value, true);
49
362k
}
50
51
} // namespace
52
53
HeadersToAddEntry::HeadersToAddEntry(const HeaderValueOption& header_value_option)
54
    : original_value_(header_value_option.header().value()),
55
350k
      add_if_empty_(header_value_option.keep_empty_value()) {
56
57
350k
  if (header_value_option.has_append()) {
58
    // 'append' is set and ensure the 'append_action' value is equal to the default value.
59
53.5k
    if (header_value_option.append_action() != HeaderValueOption::APPEND_IF_EXISTS_OR_ADD) {
60
44
      throwEnvoyExceptionOrPanic("Both append and append_action are set and it's not allowed");
61
44
    }
62
63
53.5k
    append_action_ = header_value_option.append().value()
64
53.5k
                         ? HeaderValueOption::APPEND_IF_EXISTS_OR_ADD
65
53.5k
                         : HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD;
66
296k
  } else {
67
296k
    append_action_ = header_value_option.append_action();
68
296k
  }
69
70
350k
  formatter_ = parseHttpHeaderFormatter(header_value_option.header());
71
350k
}
72
73
HeadersToAddEntry::HeadersToAddEntry(const HeaderValue& header_value,
74
                                     HeaderAppendAction append_action)
75
12.5k
    : original_value_(header_value.value()), append_action_(append_action) {
76
12.5k
  formatter_ = parseHttpHeaderFormatter(header_value);
77
12.5k
}
78
79
HeaderParserPtr
80
91.9k
HeaderParser::configure(const Protobuf::RepeatedPtrField<HeaderValueOption>& headers_to_add) {
81
91.9k
  HeaderParserPtr header_parser(new HeaderParser());
82
349k
  for (const auto& header_value_option : headers_to_add) {
83
349k
    header_parser->headers_to_add_.emplace_back(
84
349k
        Http::LowerCaseString(header_value_option.header().key()),
85
349k
        HeadersToAddEntry{header_value_option});
86
349k
  }
87
88
91.9k
  return header_parser;
89
91.9k
}
90
91
HeaderParserPtr HeaderParser::configure(
92
    const Protobuf::RepeatedPtrField<envoy::config::core::v3::HeaderValue>& headers_to_add,
93
2.06k
    HeaderAppendAction append_action) {
94
2.06k
  HeaderParserPtr header_parser(new HeaderParser());
95
96
12.5k
  for (const auto& header_value : headers_to_add) {
97
12.5k
    header_parser->headers_to_add_.emplace_back(Http::LowerCaseString(header_value.key()),
98
12.5k
                                                HeadersToAddEntry{header_value, append_action});
99
12.5k
  }
100
101
2.06k
  return header_parser;
102
2.06k
}
103
104
HeaderParserPtr
105
HeaderParser::configure(const Protobuf::RepeatedPtrField<HeaderValueOption>& headers_to_add,
106
87.5k
                        const Protobuf::RepeatedPtrField<std::string>& headers_to_remove) {
107
87.5k
  HeaderParserPtr header_parser = configure(headers_to_add);
108
109
87.5k
  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
75.6k
    if (!Http::HeaderUtility::isRemovableHeader(header)) {
114
77
      throwEnvoyExceptionOrPanic(":-prefixed or host headers may not be removed");
115
77
    }
116
75.5k
    header_parser->headers_to_remove_.emplace_back(header);
117
75.5k
  }
118
119
87.5k
  return header_parser;
120
87.5k
}
121
122
void HeaderParser::evaluateHeaders(Http::HeaderMap& headers,
123
                                   const Formatter::HttpFormatterContext& context,
124
11.3k
                                   const StreamInfo::StreamInfo& stream_info) const {
125
11.3k
  evaluateHeaders(headers, context, &stream_info);
126
11.3k
}
127
128
void HeaderParser::evaluateHeaders(Http::HeaderMap& headers,
129
                                   const Formatter::HttpFormatterContext& context,
130
42.8k
                                   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
42.8k
  for (const auto& header : headers_to_remove_) {
134
7.89k
    headers.remove(header);
135
7.89k
  }
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
42.8k
  absl::InlinedVector<std::pair<const Http::LowerCaseString&, const std::string>, 4> headers_to_add,
147
42.8k
      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
42.8k
  std::string value_buffer;
156
348k
  for (const auto& [key, entry] : headers_to_add_) {
157
348k
    absl::string_view value;
158
348k
    if (stream_info != nullptr) {
159
336k
      value_buffer = entry.formatter_->formatWithContext(context, *stream_info);
160
336k
      value = value_buffer;
161
336k
    } else {
162
11.7k
      value = entry.original_value_;
163
11.7k
    }
164
348k
    if (!value.empty() || entry.add_if_empty_) {
165
257k
      switch (entry.append_action_) {
166
0
        PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
167
188k
      case HeaderValueOption::APPEND_IF_EXISTS_OR_ADD:
168
188k
        headers_to_add.emplace_back(key, value);
169
188k
        break;
170
14.9k
      case HeaderValueOption::ADD_IF_ABSENT:
171
14.9k
        if (auto header_entry = headers.get(key); header_entry.empty()) {
172
14.0k
          headers_to_add.emplace_back(key, value);
173
14.0k
        }
174
14.9k
        break;
175
1.58k
      case HeaderValueOption::OVERWRITE_IF_EXISTS:
176
1.58k
        if (headers.get(key).empty()) {
177
1.05k
          break;
178
1.05k
        }
179
1.58k
        FALLTHRU;
180
52.8k
      case HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD:
181
52.8k
        headers_to_overwrite.emplace_back(key, value);
182
52.8k
        break;
183
257k
      }
184
257k
    }
185
348k
  }
186
187
  // First overwrite all headers which need to be overwritten.
188
52.8k
  for (const auto& header : headers_to_overwrite) {
189
52.8k
    headers.setReferenceKey(header.first, header.second);
190
52.8k
  }
191
192
  // Now add headers which should be added.
193
202k
  for (const auto& header : headers_to_add) {
194
202k
    headers.addReferenceKey(header.first, header.second);
195
202k
  }
196
42.8k
}
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