/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 |