/proc/self/cwd/source/common/http/utility.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/common/http/utility.h" |
2 | | |
3 | | #include <http_parser.h> |
4 | | |
5 | | #include <cstdint> |
6 | | #include <string> |
7 | | #include <vector> |
8 | | |
9 | | #include "envoy/config/core/v3/http_uri.pb.h" |
10 | | #include "envoy/config/core/v3/protocol.pb.h" |
11 | | #include "envoy/http/header_map.h" |
12 | | |
13 | | #include "source/common/buffer/buffer_impl.h" |
14 | | #include "source/common/common/assert.h" |
15 | | #include "source/common/common/empty_string.h" |
16 | | #include "source/common/common/enum_to_int.h" |
17 | | #include "source/common/common/fmt.h" |
18 | | #include "source/common/common/utility.h" |
19 | | #include "source/common/grpc/status.h" |
20 | | #include "source/common/http/character_set_validation.h" |
21 | | #include "source/common/http/exception.h" |
22 | | #include "source/common/http/header_map_impl.h" |
23 | | #include "source/common/http/headers.h" |
24 | | #include "source/common/http/message_impl.h" |
25 | | #include "source/common/network/cidr_range.h" |
26 | | #include "source/common/network/utility.h" |
27 | | #include "source/common/protobuf/utility.h" |
28 | | #include "source/common/runtime/runtime_features.h" |
29 | | |
30 | | #include "absl/container/node_hash_set.h" |
31 | | #include "absl/strings/match.h" |
32 | | #include "absl/strings/numbers.h" |
33 | | #include "absl/strings/str_cat.h" |
34 | | #include "absl/strings/str_split.h" |
35 | | #include "absl/strings/string_view.h" |
36 | | #include "absl/types/optional.h" |
37 | | #include "quiche/http2/adapter/http2_protocol.h" |
38 | | |
39 | | namespace Envoy { |
40 | | namespace { |
41 | | |
42 | | // Get request host from the request header map, removing the port if the port |
43 | | // does not match the scheme, or if port is provided. |
44 | | absl::string_view processRequestHost(const Http::RequestHeaderMap& headers, |
45 | 88 | absl::string_view new_scheme, absl::string_view new_port) { |
46 | | |
47 | 88 | absl::string_view request_host = headers.getHostValue(); |
48 | 88 | size_t host_end; |
49 | 88 | if (request_host.empty()) { |
50 | 29 | return request_host; |
51 | 29 | } |
52 | | // Detect if IPv6 URI |
53 | 59 | if (request_host[0] == '[') { |
54 | 2 | host_end = request_host.rfind("]:"); |
55 | 2 | if (host_end != absl::string_view::npos) { |
56 | 0 | host_end += 1; // advance to : |
57 | 0 | } |
58 | 57 | } else { |
59 | 57 | host_end = request_host.rfind(':'); |
60 | 57 | } |
61 | | |
62 | 59 | if (host_end != absl::string_view::npos) { |
63 | 0 | absl::string_view request_port = request_host.substr(host_end); |
64 | | // In the rare case that X-Forwarded-Proto and scheme disagree (say http URL over an HTTPS |
65 | | // connection), do port stripping based on X-Forwarded-Proto so http://foo.com:80 won't |
66 | | // have the port stripped when served over TLS. |
67 | 0 | absl::string_view request_protocol = headers.getForwardedProtoValue(); |
68 | 0 | bool remove_port = !new_port.empty(); |
69 | |
|
70 | 0 | if (new_scheme != request_protocol) { |
71 | 0 | remove_port |= Http::Utility::schemeIsHttps(request_protocol) && request_port == ":443"; |
72 | 0 | remove_port |= Http::Utility::schemeIsHttp(request_protocol) && request_port == ":80"; |
73 | 0 | } |
74 | |
|
75 | 0 | if (remove_port) { |
76 | 0 | return request_host.substr(0, host_end); |
77 | 0 | } |
78 | 0 | } |
79 | | |
80 | 59 | return request_host; |
81 | 59 | } |
82 | | |
83 | | } // namespace |
84 | | namespace Http2 { |
85 | | namespace Utility { |
86 | | |
87 | | namespace { |
88 | | |
89 | | struct SettingsEntry { |
90 | | uint16_t settings_id; |
91 | | uint32_t value; |
92 | | }; |
93 | | |
94 | | struct SettingsEntryHash { |
95 | 1.22k | size_t operator()(const SettingsEntry& entry) const { |
96 | 1.22k | return absl::Hash<decltype(entry.settings_id)>()(entry.settings_id); |
97 | 1.22k | } |
98 | | }; |
99 | | |
100 | | struct SettingsEntryEquals { |
101 | 615 | bool operator()(const SettingsEntry& lhs, const SettingsEntry& rhs) const { |
102 | 615 | return lhs.settings_id == rhs.settings_id; |
103 | 615 | } |
104 | | }; |
105 | | |
106 | | absl::Status |
107 | 470k | validateCustomSettingsParameters(const envoy::config::core::v3::Http2ProtocolOptions& options) { |
108 | 470k | std::vector<std::string> parameter_collisions, custom_parameter_collisions; |
109 | 470k | absl::node_hash_set<SettingsEntry, SettingsEntryHash, SettingsEntryEquals> custom_parameters; |
110 | | // User defined and named parameters with the same SETTINGS identifier can not both be set. |
111 | 470k | for (const auto& it : options.custom_settings_parameters()) { |
112 | 612 | ASSERT(it.identifier().value() <= std::numeric_limits<uint16_t>::max()); |
113 | | // Check for custom parameter inconsistencies. |
114 | 612 | const auto result = custom_parameters.insert( |
115 | 612 | {static_cast<uint16_t>(it.identifier().value()), it.value().value()}); |
116 | 612 | if (!result.second) { |
117 | 248 | if (result.first->value != it.value().value()) { |
118 | 163 | custom_parameter_collisions.push_back( |
119 | 163 | absl::StrCat("0x", absl::Hex(it.identifier().value(), absl::kZeroPad2))); |
120 | | // Fall through to allow unbatched exceptions to throw first. |
121 | 163 | } |
122 | 248 | } |
123 | 612 | switch (it.identifier().value()) { |
124 | 16 | case http2::adapter::ENABLE_PUSH: |
125 | 16 | if (it.value().value() == 1) { |
126 | 1 | return absl::InvalidArgumentError( |
127 | 1 | "server push is not supported by Envoy and can not be enabled via a " |
128 | 1 | "SETTINGS parameter."); |
129 | 1 | } |
130 | 15 | break; |
131 | 15 | case http2::adapter::ENABLE_CONNECT_PROTOCOL: |
132 | | // An exception is made for `allow_connect` which can't be checked for presence due to the |
133 | | // use of a primitive type (bool). |
134 | 2 | return absl::InvalidArgumentError( |
135 | 2 | "the \"allow_connect\" SETTINGS parameter must only be configured " |
136 | 2 | "through the named field"); |
137 | 78 | case http2::adapter::HEADER_TABLE_SIZE: |
138 | 78 | if (options.has_hpack_table_size()) { |
139 | 49 | parameter_collisions.push_back("hpack_table_size"); |
140 | 49 | } |
141 | 78 | break; |
142 | 37 | case http2::adapter::MAX_CONCURRENT_STREAMS: |
143 | 37 | if (options.has_max_concurrent_streams()) { |
144 | 22 | parameter_collisions.push_back("max_concurrent_streams"); |
145 | 22 | } |
146 | 37 | break; |
147 | 111 | case http2::adapter::INITIAL_WINDOW_SIZE: |
148 | 111 | if (options.has_initial_stream_window_size()) { |
149 | 50 | parameter_collisions.push_back("initial_stream_window_size"); |
150 | 50 | } |
151 | 111 | break; |
152 | 368 | default: |
153 | | // Ignore unknown parameters. |
154 | 368 | break; |
155 | 612 | } |
156 | 612 | } |
157 | | |
158 | 470k | if (!custom_parameter_collisions.empty()) { |
159 | 71 | return absl::InvalidArgumentError(fmt::format( |
160 | 71 | "inconsistent HTTP/2 custom SETTINGS parameter(s) detected; identifiers = {{{}}}", |
161 | 71 | absl::StrJoin(custom_parameter_collisions, ","))); |
162 | 71 | } |
163 | 470k | if (!parameter_collisions.empty()) { |
164 | 13 | return absl::InvalidArgumentError(fmt::format( |
165 | 13 | "the {{{}}} HTTP/2 SETTINGS parameter(s) can not be configured through both named and " |
166 | 13 | "custom parameters", |
167 | 13 | absl::StrJoin(parameter_collisions, ","))); |
168 | 13 | } |
169 | 470k | return absl::OkStatus(); |
170 | 470k | } |
171 | | |
172 | | } // namespace |
173 | | |
174 | | absl::StatusOr<envoy::config::core::v3::Http2ProtocolOptions> |
175 | | initializeAndValidateOptions(const envoy::config::core::v3::Http2ProtocolOptions& options, |
176 | | bool hcm_stream_error_set, |
177 | 6.46k | const ProtobufWkt::BoolValue& hcm_stream_error) { |
178 | 6.46k | auto ret = initializeAndValidateOptions(options); |
179 | 6.46k | if (ret.status().ok() && !options.has_override_stream_error_on_invalid_http_message() && |
180 | 6.46k | hcm_stream_error_set) { |
181 | 446 | ret->mutable_override_stream_error_on_invalid_http_message()->set_value( |
182 | 446 | hcm_stream_error.value()); |
183 | 446 | } |
184 | 6.46k | return ret; |
185 | 6.46k | } |
186 | | |
187 | | absl::StatusOr<envoy::config::core::v3::Http2ProtocolOptions> |
188 | 470k | initializeAndValidateOptions(const envoy::config::core::v3::Http2ProtocolOptions& options) { |
189 | 470k | envoy::config::core::v3::Http2ProtocolOptions options_clone(options); |
190 | 470k | RETURN_IF_NOT_OK(validateCustomSettingsParameters(options)); |
191 | | |
192 | 470k | if (!options.has_override_stream_error_on_invalid_http_message()) { |
193 | 467k | options_clone.mutable_override_stream_error_on_invalid_http_message()->set_value( |
194 | 467k | options.stream_error_on_invalid_http_messaging()); |
195 | 467k | } |
196 | | |
197 | 470k | if (!options_clone.has_hpack_table_size()) { |
198 | 467k | options_clone.mutable_hpack_table_size()->set_value(OptionsLimits::DEFAULT_HPACK_TABLE_SIZE); |
199 | 467k | } |
200 | 470k | ASSERT(options_clone.hpack_table_size().value() <= OptionsLimits::MAX_HPACK_TABLE_SIZE); |
201 | 470k | if (!options_clone.has_max_concurrent_streams()) { |
202 | 467k | options_clone.mutable_max_concurrent_streams()->set_value( |
203 | 467k | OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS); |
204 | 467k | } |
205 | 470k | ASSERT( |
206 | 470k | options_clone.max_concurrent_streams().value() >= OptionsLimits::MIN_MAX_CONCURRENT_STREAMS && |
207 | 470k | options_clone.max_concurrent_streams().value() <= OptionsLimits::MAX_MAX_CONCURRENT_STREAMS); |
208 | 470k | if (!options_clone.has_initial_stream_window_size()) { |
209 | 467k | options_clone.mutable_initial_stream_window_size()->set_value( |
210 | 467k | OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE); |
211 | 467k | } |
212 | 470k | ASSERT(options_clone.initial_stream_window_size().value() >= |
213 | 470k | OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE && |
214 | 470k | options_clone.initial_stream_window_size().value() <= |
215 | 470k | OptionsLimits::MAX_INITIAL_STREAM_WINDOW_SIZE); |
216 | 470k | if (!options_clone.has_initial_connection_window_size()) { |
217 | 467k | options_clone.mutable_initial_connection_window_size()->set_value( |
218 | 467k | OptionsLimits::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE); |
219 | 467k | } |
220 | 470k | ASSERT(options_clone.initial_connection_window_size().value() >= |
221 | 470k | OptionsLimits::MIN_INITIAL_CONNECTION_WINDOW_SIZE && |
222 | 470k | options_clone.initial_connection_window_size().value() <= |
223 | 470k | OptionsLimits::MAX_INITIAL_CONNECTION_WINDOW_SIZE); |
224 | 470k | if (!options_clone.has_max_outbound_frames()) { |
225 | 467k | options_clone.mutable_max_outbound_frames()->set_value( |
226 | 467k | OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES); |
227 | 467k | } |
228 | 470k | if (!options_clone.has_max_outbound_control_frames()) { |
229 | 467k | options_clone.mutable_max_outbound_control_frames()->set_value( |
230 | 467k | OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES); |
231 | 467k | } |
232 | 470k | if (!options_clone.has_max_consecutive_inbound_frames_with_empty_payload()) { |
233 | 467k | options_clone.mutable_max_consecutive_inbound_frames_with_empty_payload()->set_value( |
234 | 467k | OptionsLimits::DEFAULT_MAX_CONSECUTIVE_INBOUND_FRAMES_WITH_EMPTY_PAYLOAD); |
235 | 467k | } |
236 | 470k | if (!options_clone.has_max_inbound_priority_frames_per_stream()) { |
237 | 467k | options_clone.mutable_max_inbound_priority_frames_per_stream()->set_value( |
238 | 467k | OptionsLimits::DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM); |
239 | 467k | } |
240 | 470k | if (!options_clone.has_max_inbound_window_update_frames_per_data_frame_sent()) { |
241 | 467k | options_clone.mutable_max_inbound_window_update_frames_per_data_frame_sent()->set_value( |
242 | 467k | OptionsLimits::DEFAULT_MAX_INBOUND_WINDOW_UPDATE_FRAMES_PER_DATA_FRAME_SENT); |
243 | 467k | } |
244 | | |
245 | 470k | return options_clone; |
246 | 470k | } |
247 | | |
248 | | } // namespace Utility |
249 | | } // namespace Http2 |
250 | | |
251 | | namespace Http3 { |
252 | | namespace Utility { |
253 | | |
254 | | envoy::config::core::v3::Http3ProtocolOptions |
255 | | initializeAndValidateOptions(const envoy::config::core::v3::Http3ProtocolOptions& options, |
256 | | bool hcm_stream_error_set, |
257 | 6.74k | const ProtobufWkt::BoolValue& hcm_stream_error) { |
258 | 6.74k | if (options.has_override_stream_error_on_invalid_http_message()) { |
259 | 228 | return options; |
260 | 228 | } |
261 | 6.52k | envoy::config::core::v3::Http3ProtocolOptions options_clone(options); |
262 | 6.52k | if (hcm_stream_error_set) { |
263 | 529 | options_clone.mutable_override_stream_error_on_invalid_http_message()->set_value( |
264 | 529 | hcm_stream_error.value()); |
265 | 5.99k | } else { |
266 | 5.99k | options_clone.mutable_override_stream_error_on_invalid_http_message()->set_value(false); |
267 | 5.99k | } |
268 | 6.52k | return options_clone; |
269 | 6.74k | } |
270 | | |
271 | | } // namespace Utility |
272 | | } // namespace Http3 |
273 | | |
274 | | namespace Http { |
275 | | |
276 | | static const char kDefaultPath[] = "/"; |
277 | | |
278 | | // If http_parser encounters an IP address [address] as the host it will set the offset and |
279 | | // length to point to 'address' rather than '[address]'. Fix this by adjusting the offset |
280 | | // and length to include the brackets. |
281 | | // @param absolute_url the absolute URL. This is usually of the form // http://host/path |
282 | | // but may be host:port for CONNECT requests |
283 | | // @param offset the offset for the first character of the host. For IPv6 hosts |
284 | | // this will point to the first character inside the brackets and will be |
285 | | // adjusted to point at the brackets |
286 | | // @param len the length of the host-and-port field. For IPv6 hosts this will |
287 | | // not include the brackets and will be adjusted to do so. |
288 | 8.32k | bool maybeAdjustForIpv6(absl::string_view absolute_url, uint64_t& offset, uint64_t& len) { |
289 | | // According to https://tools.ietf.org/html/rfc3986#section-3.2.2 the only way a hostname |
290 | | // may begin with '[' is if it's an ipv6 address. |
291 | 8.32k | if (offset == 0 || *(absolute_url.data() + offset - 1) != '[') { |
292 | 1.13k | return false; |
293 | 1.13k | } |
294 | | // Start one character sooner and end one character later. |
295 | 7.19k | offset--; |
296 | 7.19k | len += 2; |
297 | | // HTTP parser ensures that any [ has a closing ] |
298 | 7.19k | ASSERT(absolute_url.length() >= offset + len); |
299 | 7.19k | return true; |
300 | 7.19k | } |
301 | | |
302 | | void forEachCookie( |
303 | | const HeaderMap& headers, const LowerCaseString& cookie_header, |
304 | 35.7k | const std::function<bool(absl::string_view, absl::string_view)>& cookie_consumer) { |
305 | 35.7k | const Http::HeaderMap::GetResult cookie_headers = headers.get(cookie_header); |
306 | | |
307 | 141k | for (size_t index = 0; index < cookie_headers.size(); index++) { |
308 | 105k | auto cookie_header_value = cookie_headers[index]->value().getStringView(); |
309 | | |
310 | | // Split the cookie header into individual cookies. |
311 | 105k | for (const auto& s : StringUtil::splitToken(cookie_header_value, ";")) { |
312 | | // Find the key part of the cookie (i.e. the name of the cookie). |
313 | 45.5k | size_t first_non_space = s.find_first_not_of(' '); |
314 | 45.5k | size_t equals_index = s.find('='); |
315 | 45.5k | if (equals_index == absl::string_view::npos) { |
316 | | // The cookie is malformed if it does not have an `=`. Continue |
317 | | // checking other cookies in this header. |
318 | 5.74k | continue; |
319 | 5.74k | } |
320 | 39.7k | absl::string_view k = s.substr(first_non_space, equals_index - first_non_space); |
321 | 39.7k | absl::string_view v = s.substr(equals_index + 1, s.size() - 1); |
322 | | |
323 | | // Cookie values may be wrapped in double quotes. |
324 | | // https://tools.ietf.org/html/rfc6265#section-4.1.1 |
325 | 39.7k | if (v.size() >= 2 && v.back() == '"' && v[0] == '"') { |
326 | 253 | v = v.substr(1, v.size() - 2); |
327 | 253 | } |
328 | | |
329 | 39.7k | if (!cookie_consumer(k, v)) { |
330 | 9 | return; |
331 | 9 | } |
332 | 39.7k | } |
333 | 105k | } |
334 | 35.7k | } |
335 | | |
336 | | std::string parseCookie(const HeaderMap& headers, const std::string& key, |
337 | 17 | const LowerCaseString& cookie) { |
338 | 17 | std::string value; |
339 | | |
340 | | // Iterate over each cookie & return if its value is not empty. |
341 | 29 | forEachCookie(headers, cookie, [&key, &value](absl::string_view k, absl::string_view v) -> bool { |
342 | 29 | if (key == k) { |
343 | 9 | value = std::string{v}; |
344 | 9 | return false; |
345 | 9 | } |
346 | | |
347 | | // continue iterating until a cookie that matches `key` is found. |
348 | 20 | return true; |
349 | 29 | }); |
350 | | |
351 | 17 | return value; |
352 | 17 | } |
353 | | |
354 | | absl::flat_hash_map<std::string, std::string> |
355 | 0 | Utility::parseCookies(const RequestHeaderMap& headers) { |
356 | 0 | return Utility::parseCookies(headers, [](absl::string_view) -> bool { return true; }); |
357 | 0 | } |
358 | | |
359 | | absl::flat_hash_map<std::string, std::string> |
360 | | Utility::parseCookies(const RequestHeaderMap& headers, |
361 | 35.6k | const std::function<bool(absl::string_view)>& key_filter) { |
362 | 35.6k | absl::flat_hash_map<std::string, std::string> cookies; |
363 | | |
364 | 35.6k | forEachCookie(headers, Http::Headers::get().Cookie, |
365 | 39.7k | [&cookies, &key_filter](absl::string_view k, absl::string_view v) -> bool { |
366 | 39.7k | if (key_filter(k)) { |
367 | 33.2k | cookies.emplace(k, v); |
368 | 33.2k | } |
369 | | |
370 | | // continue iterating until all cookies are processed. |
371 | 39.7k | return true; |
372 | 39.7k | }); |
373 | | |
374 | 35.6k | return cookies; |
375 | 35.6k | } |
376 | | |
377 | 1.19k | bool Utility::Url::containsFragment() { return (component_bitmap_ & (1 << UcFragment)); } |
378 | | |
379 | 1.18k | bool Utility::Url::containsUserinfo() { return (component_bitmap_ & (1 << UcUserinfo)); } |
380 | | |
381 | 24.0k | bool Utility::Url::initialize(absl::string_view absolute_url, bool is_connect) { |
382 | 24.0k | struct http_parser_url u; |
383 | 24.0k | http_parser_url_init(&u); |
384 | 24.0k | const int result = |
385 | 24.0k | http_parser_parse_url(absolute_url.data(), absolute_url.length(), is_connect, &u); |
386 | | |
387 | 24.0k | if (result != 0) { |
388 | 10.1k | return false; |
389 | 10.1k | } |
390 | | |
391 | 13.8k | if ((u.field_set & (1 << UF_HOST)) != (1 << UF_HOST) && |
392 | 13.8k | (u.field_set & (1 << UF_SCHEMA)) != (1 << UF_SCHEMA)) { |
393 | 5.57k | return false; |
394 | 5.57k | } |
395 | | |
396 | 8.32k | component_bitmap_ = u.field_set; |
397 | 8.32k | scheme_ = absl::string_view(absolute_url.data() + u.field_data[UF_SCHEMA].off, |
398 | 8.32k | u.field_data[UF_SCHEMA].len); |
399 | | |
400 | 8.32k | uint64_t authority_len = u.field_data[UF_HOST].len; |
401 | 8.32k | if ((u.field_set & (1 << UF_PORT)) == (1 << UF_PORT)) { |
402 | 398 | authority_len = authority_len + u.field_data[UF_PORT].len + 1; |
403 | 398 | } |
404 | | |
405 | 8.32k | uint64_t authority_beginning = u.field_data[UF_HOST].off; |
406 | 8.32k | const bool is_ipv6 = maybeAdjustForIpv6(absolute_url, authority_beginning, authority_len); |
407 | 8.32k | host_and_port_ = absl::string_view(absolute_url.data() + authority_beginning, authority_len); |
408 | 8.32k | if (is_ipv6 && !parseAuthority(host_and_port_).is_ip_address_) { |
409 | 6.25k | return false; |
410 | 6.25k | } |
411 | | |
412 | | // RFC allows the absolute-uri to not end in /, but the absolute path form |
413 | | // must start with. Determine if there's a non-zero path, and if so determine |
414 | | // the length of the path, query params etc. |
415 | 2.06k | uint64_t path_etc_len = absolute_url.length() - (authority_beginning + hostAndPort().length()); |
416 | 2.06k | if (path_etc_len > 0) { |
417 | 541 | uint64_t path_beginning = authority_beginning + hostAndPort().length(); |
418 | 541 | path_and_query_params_ = absl::string_view(absolute_url.data() + path_beginning, path_etc_len); |
419 | 1.52k | } else if (!is_connect) { |
420 | 1.44k | ASSERT((u.field_set & (1 << UF_PATH)) == 0); |
421 | 1.44k | path_and_query_params_ = absl::string_view(kDefaultPath, 1); |
422 | 1.44k | } |
423 | 2.06k | return true; |
424 | 2.06k | } |
425 | | |
426 | 0 | std::string Utility::Url::toString() const { |
427 | 0 | return absl::StrCat(scheme_, "://", host_and_port_, path_and_query_params_); |
428 | 0 | } |
429 | | |
430 | | void Utility::appendXff(RequestHeaderMap& headers, |
431 | 67.8k | const Network::Address::Instance& remote_address) { |
432 | 67.8k | if (remote_address.type() != Network::Address::Type::Ip) { |
433 | 16 | return; |
434 | 16 | } |
435 | | |
436 | 67.8k | headers.appendForwardedFor(remote_address.ip()->addressAsString(), ","); |
437 | 67.8k | } |
438 | | |
439 | 224 | void Utility::appendVia(RequestOrResponseHeaderMap& headers, const std::string& via) { |
440 | | // TODO(asraa): Investigate whether it is necessary to append with whitespace here by: |
441 | | // (a) Validating we do not expect whitespace in via headers |
442 | | // (b) Add runtime guarding in case users have upstreams which expect it. |
443 | 224 | headers.appendVia(via, ", "); |
444 | 224 | } |
445 | | |
446 | | void Utility::updateAuthority(RequestHeaderMap& headers, absl::string_view hostname, |
447 | 125 | const bool append_xfh) { |
448 | 125 | const auto host = headers.getHostValue(); |
449 | | |
450 | | // Only append to x-forwarded-host if the value was not the last value appended. |
451 | 125 | const auto xfh = headers.getForwardedHostValue(); |
452 | | |
453 | 125 | if (append_xfh && !host.empty()) { |
454 | 8 | if (!xfh.empty()) { |
455 | 0 | const auto xfh_split = StringUtil::splitToken(xfh, ","); |
456 | 0 | if (!xfh_split.empty() && xfh_split.back() != host) { |
457 | 0 | headers.appendForwardedHost(host, ","); |
458 | 0 | } |
459 | 8 | } else { |
460 | 8 | headers.appendForwardedHost(host, ","); |
461 | 8 | } |
462 | 8 | } |
463 | | |
464 | 125 | headers.setHost(hostname); |
465 | 125 | } |
466 | | |
467 | 0 | std::string Utility::createSslRedirectPath(const RequestHeaderMap& headers) { |
468 | 0 | ASSERT(headers.Host()); |
469 | 0 | ASSERT(headers.Path()); |
470 | 0 | return fmt::format("https://{}{}", headers.getHostValue(), headers.getPathValue()); |
471 | 0 | } |
472 | | |
473 | 6.47k | Utility::QueryParamsMulti Utility::QueryParamsMulti::parseQueryString(absl::string_view url) { |
474 | 6.47k | size_t start = url.find('?'); |
475 | 6.47k | if (start == std::string::npos) { |
476 | 1.48k | return {}; |
477 | 1.48k | } |
478 | | |
479 | 4.99k | start++; |
480 | 4.99k | return Utility::QueryParamsMulti::parseParameters(url, start, /*decode_params=*/false); |
481 | 6.47k | } |
482 | | |
483 | | Utility::QueryParamsMulti |
484 | 23.8k | Utility::QueryParamsMulti::parseAndDecodeQueryString(absl::string_view url) { |
485 | 23.8k | size_t start = url.find('?'); |
486 | 23.8k | if (start == std::string::npos) { |
487 | 17.9k | return {}; |
488 | 17.9k | } |
489 | | |
490 | 5.87k | start++; |
491 | 5.87k | return Utility::QueryParamsMulti::parseParameters(url, start, /*decode_params=*/true); |
492 | 23.8k | } |
493 | | |
494 | | Utility::QueryParamsMulti Utility::QueryParamsMulti::parseParameters(absl::string_view data, |
495 | | size_t start, |
496 | 10.8k | bool decode_params) { |
497 | 10.8k | QueryParamsMulti params; |
498 | | |
499 | 1.33M | while (start < data.size()) { |
500 | 1.32M | size_t end = data.find('&', start); |
501 | 1.32M | if (end == std::string::npos) { |
502 | 10.1k | end = data.size(); |
503 | 10.1k | } |
504 | 1.32M | absl::string_view param(data.data() + start, end - start); |
505 | | |
506 | 1.32M | const size_t equal = param.find('='); |
507 | 1.32M | if (equal != std::string::npos) { |
508 | 38.3k | const auto param_name = StringUtil::subspan(data, start, start + equal); |
509 | 38.3k | const auto param_value = StringUtil::subspan(data, start + equal + 1, end); |
510 | 38.3k | params.add(decode_params ? PercentEncoding::decode(param_name) : param_name, |
511 | 38.3k | decode_params ? PercentEncoding::decode(param_value) : param_value); |
512 | 1.28M | } else { |
513 | 1.28M | const auto param_name = StringUtil::subspan(data, start, end); |
514 | 1.28M | params.add(decode_params ? PercentEncoding::decode(param_name) : param_name, ""); |
515 | 1.28M | } |
516 | | |
517 | 1.32M | start = end + 1; |
518 | 1.32M | } |
519 | | |
520 | 10.8k | return params; |
521 | 10.8k | } |
522 | | |
523 | 2.88k | void Utility::QueryParamsMulti::remove(absl::string_view key) { this->data_.erase(key); } |
524 | | |
525 | 1.32M | void Utility::QueryParamsMulti::add(absl::string_view key, absl::string_view value) { |
526 | 1.32M | auto result = this->data_.emplace(std::string(key), std::vector<std::string>{std::string(value)}); |
527 | 1.32M | if (!result.second) { |
528 | 1.17M | result.first->second.push_back(std::string(value)); |
529 | 1.17M | } |
530 | 1.32M | } |
531 | | |
532 | 2.92k | void Utility::QueryParamsMulti::overwrite(absl::string_view key, absl::string_view value) { |
533 | 2.92k | this->data_[key] = std::vector<std::string>{std::string(value)}; |
534 | 2.92k | } |
535 | | |
536 | 44.8k | absl::optional<std::string> Utility::QueryParamsMulti::getFirstValue(absl::string_view key) const { |
537 | 44.8k | auto it = this->data_.find(key); |
538 | 44.8k | if (it == this->data_.end()) { |
539 | 40.7k | return std::nullopt; |
540 | 40.7k | } |
541 | | |
542 | 4.08k | return absl::optional<std::string>{it->second.at(0)}; |
543 | 44.8k | } |
544 | | |
545 | 905 | absl::string_view Utility::findQueryStringStart(const HeaderString& path) { |
546 | 905 | absl::string_view path_str = path.getStringView(); |
547 | 905 | size_t query_offset = path_str.find('?'); |
548 | 905 | if (query_offset == absl::string_view::npos) { |
549 | 719 | query_offset = path_str.length(); |
550 | 719 | } |
551 | 905 | path_str.remove_prefix(query_offset); |
552 | 905 | return path_str; |
553 | 905 | } |
554 | | |
555 | 1.22k | std::string Utility::stripQueryString(const HeaderString& path) { |
556 | 1.22k | absl::string_view path_str = path.getStringView(); |
557 | 1.22k | size_t query_offset = path_str.find('?'); |
558 | 1.22k | return {path_str.data(), query_offset != path_str.npos ? query_offset : path_str.size()}; |
559 | 1.22k | } |
560 | | |
561 | 1.22k | std::string Utility::QueryParamsMulti::replaceQueryString(const HeaderString& path) const { |
562 | 1.22k | std::string new_path{Http::Utility::stripQueryString(path)}; |
563 | | |
564 | 1.22k | if (!this->data_.empty()) { |
565 | 1.03k | absl::StrAppend(&new_path, this->toString()); |
566 | 1.03k | } |
567 | | |
568 | 1.22k | return new_path; |
569 | 1.22k | } |
570 | | |
571 | 17 | std::string Utility::parseCookieValue(const HeaderMap& headers, const std::string& key) { |
572 | | // TODO(wbpcode): Modify the headers parameter type to 'RequestHeaderMap'. |
573 | 17 | return parseCookie(headers, key, Http::Headers::get().Cookie); |
574 | 17 | } |
575 | | |
576 | 0 | std::string Utility::parseSetCookieValue(const Http::HeaderMap& headers, const std::string& key) { |
577 | 0 | return parseCookie(headers, key, Http::Headers::get().SetCookie); |
578 | 0 | } |
579 | | |
580 | | std::string Utility::makeSetCookieValue(const std::string& key, const std::string& value, |
581 | | const std::string& path, const std::chrono::seconds max_age, |
582 | | bool httponly, |
583 | 7 | const Http::CookieAttributeRefVector attributes) { |
584 | 7 | std::string cookie_value; |
585 | | // Best effort attempt to avoid numerous string copies. |
586 | 7 | cookie_value.reserve(value.size() + path.size() + 30); |
587 | | |
588 | 7 | cookie_value = absl::StrCat(key, "=\"", value, "\""); |
589 | 7 | if (max_age != std::chrono::seconds::zero()) { |
590 | 3 | absl::StrAppend(&cookie_value, "; Max-Age=", max_age.count()); |
591 | 3 | } |
592 | 7 | if (!path.empty()) { |
593 | 4 | absl::StrAppend(&cookie_value, "; Path=", path); |
594 | 4 | } |
595 | | |
596 | 7 | for (auto const& attribute : attributes) { |
597 | 0 | if (attribute.get().value().empty()) { |
598 | 0 | absl::StrAppend(&cookie_value, "; ", attribute.get().name()); |
599 | 0 | } else { |
600 | 0 | absl::StrAppend(&cookie_value, "; ", attribute.get().name(), "=", attribute.get().value()); |
601 | 0 | } |
602 | 0 | } |
603 | | |
604 | 7 | if (httponly) { |
605 | 2 | absl::StrAppend(&cookie_value, "; HttpOnly"); |
606 | 2 | } |
607 | 7 | return cookie_value; |
608 | 7 | } |
609 | | |
610 | 291k | uint64_t Utility::getResponseStatus(const ResponseHeaderMap& headers) { |
611 | 291k | auto status = Utility::getResponseStatusOrNullopt(headers); |
612 | 291k | if (!status.has_value()) { |
613 | 0 | IS_ENVOY_BUG("No status in headers"); |
614 | 0 | return 0; |
615 | 0 | } |
616 | 291k | return status.value(); |
617 | 291k | } |
618 | | |
619 | 421k | absl::optional<uint64_t> Utility::getResponseStatusOrNullopt(const ResponseHeaderMap& headers) { |
620 | 421k | const HeaderEntry* header = headers.Status(); |
621 | 421k | uint64_t response_code; |
622 | 421k | if (!header || !absl::SimpleAtoi(headers.getStatusValue(), &response_code)) { |
623 | 4.10k | return absl::nullopt; |
624 | 4.10k | } |
625 | 417k | return response_code; |
626 | 421k | } |
627 | | |
628 | 482k | bool Utility::isUpgrade(const RequestOrResponseHeaderMap& headers) { |
629 | | // In firefox the "Connection" request header value is "keep-alive, Upgrade", |
630 | | // we should check if it contains the "Upgrade" token. |
631 | 482k | return (headers.Upgrade() && |
632 | 482k | Envoy::StringUtil::caseFindToken(headers.getConnectionValue(), ",", |
633 | 31.8k | Http::Headers::get().ConnectionValues.Upgrade)); |
634 | 482k | } |
635 | | |
636 | 22.7k | bool Utility::isH2UpgradeRequest(const RequestHeaderMap& headers) { |
637 | 22.7k | return headers.getMethodValue() == Http::Headers::get().MethodValues.Connect && |
638 | 22.7k | headers.Protocol() && !headers.Protocol()->value().empty() && |
639 | 22.7k | headers.Protocol()->value() != Headers::get().ProtocolValues.Bytestream; |
640 | 22.7k | } |
641 | | |
642 | 25 | bool Utility::isH3UpgradeRequest(const RequestHeaderMap& headers) { |
643 | 25 | return isH2UpgradeRequest(headers); |
644 | 25 | } |
645 | | |
646 | 2.33k | bool Utility::isWebSocketUpgradeRequest(const RequestHeaderMap& headers) { |
647 | 2.33k | return (isUpgrade(headers) && |
648 | 2.33k | absl::EqualsIgnoreCase(headers.getUpgradeValue(), |
649 | 2 | Http::Headers::get().UpgradeValues.WebSocket)); |
650 | 2.33k | } |
651 | | |
652 | | Utility::PreparedLocalReplyPtr Utility::prepareLocalReply(const EncodeFunctions& encode_functions, |
653 | 139k | const LocalReplyData& local_reply_data) { |
654 | 139k | Code response_code = local_reply_data.response_code_; |
655 | 139k | std::string body_text(local_reply_data.body_text_); |
656 | 139k | absl::string_view content_type(Headers::get().ContentTypeValues.Text); |
657 | | |
658 | 139k | ResponseHeaderMapPtr response_headers{createHeaderMap<ResponseHeaderMapImpl>( |
659 | 139k | {{Headers::get().Status, std::to_string(enumToInt(response_code))}})}; |
660 | | |
661 | 139k | if (encode_functions.modify_headers_) { |
662 | 123k | encode_functions.modify_headers_(*response_headers); |
663 | 123k | } |
664 | 139k | bool has_custom_content_type = false; |
665 | 139k | if (encode_functions.rewrite_) { |
666 | 123k | std::string content_type_value = std::string(response_headers->getContentTypeValue()); |
667 | 123k | encode_functions.rewrite_(*response_headers, response_code, body_text, content_type); |
668 | 123k | has_custom_content_type = (content_type_value != response_headers->getContentTypeValue()); |
669 | 123k | } |
670 | | |
671 | 139k | if (local_reply_data.is_grpc_) { |
672 | 264 | response_headers->setStatus(std::to_string(enumToInt(Code::OK))); |
673 | 264 | response_headers->setReferenceContentType(Headers::get().ContentTypeValues.Grpc); |
674 | | |
675 | 264 | if (response_headers->getGrpcStatusValue().empty()) { |
676 | 264 | response_headers->setGrpcStatus(std::to_string( |
677 | 264 | enumToInt(local_reply_data.grpc_status_ |
678 | 264 | ? local_reply_data.grpc_status_.value() |
679 | 264 | : Grpc::Utility::httpToGrpcStatus(enumToInt(response_code))))); |
680 | 264 | } |
681 | | |
682 | 264 | if (!body_text.empty() && !local_reply_data.is_head_request_) { |
683 | | // TODO(dio): Probably it is worth to consider caching the encoded message based on gRPC |
684 | | // status. |
685 | | // JsonFormatter adds a '\n' at the end. For header value, it should be removed. |
686 | | // https://github.com/envoyproxy/envoy/blob/main/source/common/formatter/substitution_formatter.cc#L129 |
687 | 141 | if (content_type == Headers::get().ContentTypeValues.Json && |
688 | 141 | body_text[body_text.length() - 1] == '\n') { |
689 | 0 | body_text = body_text.substr(0, body_text.length() - 1); |
690 | 0 | } |
691 | 141 | response_headers->setGrpcMessage(PercentEncoding::encode(body_text)); |
692 | 141 | } |
693 | | // The `modify_headers` function may have added content-length, remove it. |
694 | 264 | response_headers->removeContentLength(); |
695 | | |
696 | | // TODO(kbaichoo): In C++20 we will be able to use make_unique with |
697 | | // aggregate initialization. |
698 | | // NOLINTNEXTLINE(modernize-make-unique) |
699 | 264 | return PreparedLocalReplyPtr(new PreparedLocalReply{ |
700 | 264 | local_reply_data.is_grpc_, local_reply_data.is_head_request_, std::move(response_headers), |
701 | 264 | std::move(body_text), encode_functions.encode_headers_, encode_functions.encode_data_}); |
702 | 264 | } |
703 | | |
704 | 138k | if (!body_text.empty()) { |
705 | 22.9k | response_headers->setContentLength(body_text.size()); |
706 | | // If the content-type is not set, set it. |
707 | | // Alternately if the `rewrite` function has changed body_text and the config didn't explicitly |
708 | | // set a content type header, set the content type to be based on the changed body. |
709 | 22.9k | if (response_headers->ContentType() == nullptr || |
710 | 22.9k | (body_text != local_reply_data.body_text_ && !has_custom_content_type)) { |
711 | 22.9k | response_headers->setReferenceContentType(content_type); |
712 | 22.9k | } |
713 | 115k | } else { |
714 | 115k | response_headers->removeContentLength(); |
715 | 115k | response_headers->removeContentType(); |
716 | 115k | } |
717 | | |
718 | | // TODO(kbaichoo): In C++20 we will be able to use make_unique with |
719 | | // aggregate initialization. |
720 | | // NOLINTNEXTLINE(modernize-make-unique) |
721 | 138k | return PreparedLocalReplyPtr(new PreparedLocalReply{ |
722 | 138k | local_reply_data.is_grpc_, local_reply_data.is_head_request_, std::move(response_headers), |
723 | 138k | std::move(body_text), encode_functions.encode_headers_, encode_functions.encode_data_}); |
724 | 139k | } |
725 | | |
726 | 139k | void Utility::encodeLocalReply(const bool& is_reset, PreparedLocalReplyPtr prepared_local_reply) { |
727 | 139k | ASSERT(prepared_local_reply != nullptr); |
728 | 139k | ResponseHeaderMapPtr response_headers{std::move(prepared_local_reply->response_headers_)}; |
729 | | |
730 | 139k | if (prepared_local_reply->is_grpc_request_) { |
731 | | // Trailers only response |
732 | 264 | prepared_local_reply->encode_headers_(std::move(response_headers), true); |
733 | 264 | return; |
734 | 264 | } |
735 | | |
736 | 138k | if (prepared_local_reply->is_head_request_) { |
737 | 18 | prepared_local_reply->encode_headers_(std::move(response_headers), true); |
738 | 18 | return; |
739 | 18 | } |
740 | | |
741 | 138k | const bool bodyless_response = prepared_local_reply->response_body_.empty(); |
742 | 138k | prepared_local_reply->encode_headers_(std::move(response_headers), bodyless_response); |
743 | | // encode_headers() may have changed the referenced is_reset so we need to test it |
744 | 138k | if (!bodyless_response && !is_reset) { |
745 | 22.8k | Buffer::OwnedImpl buffer(prepared_local_reply->response_body_); |
746 | 22.8k | prepared_local_reply->encode_data_(buffer, true); |
747 | 22.8k | } |
748 | 138k | } |
749 | | |
750 | | void Utility::sendLocalReply(const bool& is_reset, const EncodeFunctions& encode_functions, |
751 | 138k | const LocalReplyData& local_reply_data) { |
752 | | // encode_headers() may reset the stream, so the stream must not be reset before calling it. |
753 | 138k | ASSERT(!is_reset); |
754 | 138k | PreparedLocalReplyPtr prepared_local_reply = |
755 | 138k | prepareLocalReply(encode_functions, local_reply_data); |
756 | | |
757 | 138k | encodeLocalReply(is_reset, std::move(prepared_local_reply)); |
758 | 138k | } |
759 | | |
760 | | bool Utility::remoteAddressIsTrustedProxy( |
761 | | const Envoy::Network::Address::Instance& remote, |
762 | 0 | absl::Span<const Network::Address::CidrRange> trusted_cidrs) { |
763 | 0 | for (const auto& cidr : trusted_cidrs) { |
764 | 0 | if (cidr.isInRange(remote)) { |
765 | 0 | return true; |
766 | 0 | } |
767 | 0 | } |
768 | 0 | return false; |
769 | 0 | } |
770 | | |
771 | | Utility::GetLastAddressFromXffInfo Utility::getLastNonTrustedAddressFromXFF( |
772 | | const Http::RequestHeaderMap& request_headers, |
773 | 0 | absl::Span<const Network::Address::CidrRange> trusted_cidrs) { |
774 | 0 | const auto xff_header = request_headers.getForwardedForValue(); |
775 | 0 | static constexpr size_t MAX_ALLOWED_XFF_ENTRIES = 20; |
776 | |
|
777 | 0 | absl::InlinedVector<absl::string_view, 3> xff_entries = absl::StrSplit(xff_header, ','); |
778 | | // If there are more than 20 entries in the XFF header, refuse to parse. |
779 | | // There are very few valid use cases for this many entries and parsing too many has |
780 | | // a performance impact. |
781 | 0 | if (xff_entries.size() > MAX_ALLOWED_XFF_ENTRIES) { |
782 | 0 | ENVOY_LOG_MISC(trace, "Too many entries in x-forwarded-for header"); |
783 | 0 | return {nullptr, false}; |
784 | 0 | } |
785 | | |
786 | 0 | Network::Address::InstanceConstSharedPtr last_valid_addr; |
787 | |
|
788 | 0 | for (auto it = xff_entries.rbegin(); it != xff_entries.rend(); it++) { |
789 | 0 | const absl::string_view entry = StringUtil::trim(*it); |
790 | 0 | auto addr = Network::Utility::parseInternetAddressNoThrow(std::string(entry)); |
791 | 0 | if (addr == nullptr) { |
792 | 0 | return {nullptr, false}; |
793 | 0 | } |
794 | 0 | last_valid_addr = addr; |
795 | |
|
796 | 0 | bool remote_address_is_trusted_proxy = false; |
797 | 0 | for (const auto& cidr : trusted_cidrs) { |
798 | 0 | if (cidr.isInRange(*addr.get())) { |
799 | 0 | remote_address_is_trusted_proxy = true; |
800 | 0 | break; |
801 | 0 | } |
802 | 0 | } |
803 | |
|
804 | 0 | if (remote_address_is_trusted_proxy) { |
805 | 0 | continue; |
806 | 0 | } |
807 | | |
808 | | // If we reach here we found a non-trusted address |
809 | 0 | return {addr, xff_entries.size() == 1}; |
810 | 0 | } |
811 | | // If we reach this point all addresses in XFF were considered trusted, so return |
812 | | // first IP in XFF (the last in the reverse-evaluated chain). |
813 | 0 | return {last_valid_addr, xff_entries.size() == 1}; |
814 | 0 | } |
815 | | |
816 | | Utility::GetLastAddressFromXffInfo |
817 | | Utility::getLastAddressFromXFF(const Http::RequestHeaderMap& request_headers, |
818 | 1.31k | uint32_t num_to_skip) { |
819 | 1.31k | const auto xff_header = request_headers.ForwardedFor(); |
820 | 1.31k | if (xff_header == nullptr) { |
821 | 1.28k | return {nullptr, false}; |
822 | 1.28k | } |
823 | | |
824 | 26 | absl::string_view xff_string(xff_header->value().getStringView()); |
825 | 26 | static constexpr absl::string_view separator(","); |
826 | | // Ignore the last num_to_skip addresses at the end of XFF. |
827 | 62 | for (uint32_t i = 0; i < num_to_skip; i++) { |
828 | 38 | const absl::string_view::size_type last_comma = xff_string.rfind(separator); |
829 | 38 | if (last_comma == absl::string_view::npos) { |
830 | 2 | return {nullptr, false}; |
831 | 2 | } |
832 | 36 | xff_string = xff_string.substr(0, last_comma); |
833 | 36 | } |
834 | | // The text after the last remaining comma, or the entirety of the string if there |
835 | | // is no comma, is the requested IP address. |
836 | 24 | const absl::string_view::size_type last_comma = xff_string.rfind(separator); |
837 | 24 | if (last_comma != absl::string_view::npos && last_comma + separator.size() < xff_string.size()) { |
838 | 7 | xff_string = xff_string.substr(last_comma + separator.size()); |
839 | 7 | } |
840 | | |
841 | | // Ignore the whitespace, since they are allowed in HTTP lists (see RFC7239#section-7.1). |
842 | 24 | xff_string = StringUtil::ltrim(xff_string); |
843 | 24 | xff_string = StringUtil::rtrim(xff_string); |
844 | | |
845 | | // This technically requires a copy because inet_pton takes a null terminated string. In |
846 | | // practice, we are working with a view at the end of the owning string, and could pass the |
847 | | // raw pointer. |
848 | | // TODO(mattklein123) PERF: Avoid the copy here. |
849 | 24 | Network::Address::InstanceConstSharedPtr address = |
850 | 24 | Network::Utility::parseInternetAddressNoThrow(std::string(xff_string)); |
851 | 24 | if (address != nullptr) { |
852 | 11 | return {address, last_comma == absl::string_view::npos && num_to_skip == 0}; |
853 | 11 | } |
854 | 13 | return {nullptr, false}; |
855 | 24 | } |
856 | | |
857 | 3.51k | bool Utility::sanitizeConnectionHeader(Http::RequestHeaderMap& headers) { |
858 | 3.51k | static constexpr size_t MAX_ALLOWED_NOMINATED_HEADERS = 10; |
859 | 3.51k | static constexpr size_t MAX_ALLOWED_TE_VALUE_SIZE = 256; |
860 | | |
861 | | // Remove any headers nominated by the Connection header. The TE header |
862 | | // is sanitized and removed only if it's empty after removing unsupported values |
863 | | // See https://github.com/envoyproxy/envoy/issues/8623 |
864 | 3.51k | const auto& cv = Http::Headers::get().ConnectionValues; |
865 | 3.51k | const auto& connection_header_value = headers.Connection()->value(); |
866 | | |
867 | 3.51k | StringUtil::CaseUnorderedSet headers_to_remove{}; |
868 | 3.51k | std::vector<absl::string_view> connection_header_tokens = |
869 | 3.51k | StringUtil::splitToken(connection_header_value.getStringView(), ",", false); |
870 | | |
871 | | // If we have 10 or more nominated headers, fail this request |
872 | 3.51k | if (connection_header_tokens.size() >= MAX_ALLOWED_NOMINATED_HEADERS) { |
873 | 353 | ENVOY_LOG_MISC(trace, "Too many nominated headers in request"); |
874 | 353 | return false; |
875 | 353 | } |
876 | | |
877 | | // Split the connection header and evaluate each nominated header |
878 | 9.60k | for (const auto& token : connection_header_tokens) { |
879 | | |
880 | 9.60k | const auto token_sv = StringUtil::trim(token); |
881 | | |
882 | | // Build the LowerCaseString for header lookup |
883 | 9.60k | const LowerCaseString lcs_header_to_remove{std::string(token_sv)}; |
884 | | |
885 | | // If the Connection token value is not a nominated header, ignore it here since |
886 | | // the connection header is removed elsewhere when the H1 request is upgraded to H2 |
887 | 9.60k | if ((lcs_header_to_remove.get() == cv.Close) || |
888 | 9.60k | (lcs_header_to_remove.get() == cv.Http2Settings) || |
889 | 9.60k | (lcs_header_to_remove.get() == cv.KeepAlive) || |
890 | 9.60k | (lcs_header_to_remove.get() == cv.Upgrade)) { |
891 | 667 | continue; |
892 | 667 | } |
893 | | |
894 | | // By default we will remove any nominated headers |
895 | 8.94k | bool keep_header = false; |
896 | | |
897 | | // Determine whether the nominated header contains invalid values |
898 | 8.94k | HeaderMap::GetResult nominated_header; |
899 | | |
900 | 8.94k | if (lcs_header_to_remove == Http::Headers::get().Connection) { |
901 | | // Remove the connection header from the nominated tokens if it's self nominated |
902 | | // The connection header itself is *not removed* |
903 | 200 | ENVOY_LOG_MISC(trace, "Skipping self nominated header [{}]", token_sv); |
904 | 200 | keep_header = true; |
905 | 200 | headers_to_remove.emplace(token_sv); |
906 | | |
907 | 8.74k | } else if ((lcs_header_to_remove == Http::Headers::get().ForwardedFor) || |
908 | 8.74k | (lcs_header_to_remove == Http::Headers::get().ForwardedHost) || |
909 | 8.74k | (lcs_header_to_remove == Http::Headers::get().ForwardedProto) || |
910 | 8.74k | !token_sv.find(':')) { |
911 | | |
912 | | // An attacker could nominate an X-Forwarded* header, and its removal may mask |
913 | | // the origin of the incoming request and potentially alter its handling. |
914 | | // Additionally, pseudo headers should never be nominated. In both cases, we |
915 | | // should fail the request. |
916 | | // See: https://nathandavison.com/blog/abusing-http-hop-by-hop-request-headers |
917 | | |
918 | 94 | ENVOY_LOG_MISC(trace, "Invalid nomination of {} header", token_sv); |
919 | 94 | return false; |
920 | 8.64k | } else { |
921 | | // Examine the value of all other nominated headers |
922 | 8.64k | nominated_header = headers.get(lcs_header_to_remove); |
923 | 8.64k | } |
924 | | |
925 | 8.84k | if (!nominated_header.empty()) { |
926 | | // NOTE: The TE header is an inline header, so by definition if we operate on it there can |
927 | | // only be a single value. In all other cases we remove the nominated header. |
928 | 1.54k | auto nominated_header_value_sv = nominated_header[0]->value().getStringView(); |
929 | | |
930 | 1.54k | const bool is_te_header = (lcs_header_to_remove == Http::Headers::get().TE); |
931 | | |
932 | | // reject the request if the TE header is too large |
933 | 1.54k | if (is_te_header && (nominated_header_value_sv.size() >= MAX_ALLOWED_TE_VALUE_SIZE)) { |
934 | 16 | ENVOY_LOG_MISC(trace, "TE header contains a value that exceeds the allowable length"); |
935 | 16 | return false; |
936 | 16 | } |
937 | | |
938 | 1.52k | if (is_te_header) { |
939 | 298 | ASSERT(nominated_header.size() == 1); |
940 | 298 | for (const auto& header_value : |
941 | 2.04k | StringUtil::splitToken(nominated_header_value_sv, ",", false)) { |
942 | | |
943 | 2.04k | const absl::string_view header_sv = StringUtil::trim(header_value); |
944 | | |
945 | | // If trailers exist in the TE value tokens, keep the header, removing any other values |
946 | | // that may exist |
947 | 2.04k | if (StringUtil::CaseInsensitiveCompare()(header_sv, |
948 | 2.04k | Http::Headers::get().TEValues.Trailers)) { |
949 | 152 | keep_header = true; |
950 | 152 | break; |
951 | 152 | } |
952 | 2.04k | } |
953 | | |
954 | 298 | if (keep_header) { |
955 | 152 | headers.setTE(Http::Headers::get().TEValues.Trailers); |
956 | 152 | } |
957 | 298 | } |
958 | 1.52k | } |
959 | | |
960 | 8.83k | if (!keep_header) { |
961 | 8.48k | ENVOY_LOG_MISC(trace, "Removing nominated header [{}]", token_sv); |
962 | 8.48k | headers.remove(lcs_header_to_remove); |
963 | 8.48k | headers_to_remove.emplace(token_sv); |
964 | 8.48k | } |
965 | 8.83k | } |
966 | | |
967 | | // Lastly remove extra nominated headers from the Connection header |
968 | 3.05k | if (!headers_to_remove.empty()) { |
969 | 2.83k | const std::string new_value = StringUtil::removeTokens(connection_header_value.getStringView(), |
970 | 2.83k | ",", headers_to_remove, ","); |
971 | | |
972 | 2.83k | if (new_value.empty()) { |
973 | 2.65k | headers.removeConnection(); |
974 | 2.65k | } else { |
975 | 184 | headers.setConnection(new_value); |
976 | 184 | } |
977 | 2.83k | } |
978 | | |
979 | 3.05k | return true; |
980 | 3.16k | } |
981 | | |
982 | 1.68k | const std::string& Utility::getProtocolString(const Protocol protocol) { |
983 | 1.68k | switch (protocol) { |
984 | 161 | case Protocol::Http10: |
985 | 161 | return Headers::get().ProtocolStrings.Http10String; |
986 | 444 | case Protocol::Http11: |
987 | 444 | return Headers::get().ProtocolStrings.Http11String; |
988 | 1.08k | case Protocol::Http2: |
989 | 1.08k | return Headers::get().ProtocolStrings.Http2String; |
990 | 0 | case Protocol::Http3: |
991 | 0 | return Headers::get().ProtocolStrings.Http3String; |
992 | 1.68k | } |
993 | | |
994 | 0 | return EMPTY_STRING; |
995 | 1.68k | } |
996 | | |
997 | | std::string Utility::buildOriginalUri(const Http::RequestHeaderMap& request_headers, |
998 | 10.5k | const absl::optional<uint32_t> max_path_length) { |
999 | 10.5k | if (!request_headers.Path()) { |
1000 | 0 | return ""; |
1001 | 0 | } |
1002 | 10.5k | absl::string_view path(request_headers.EnvoyOriginalPath() |
1003 | 10.5k | ? request_headers.getEnvoyOriginalPathValue() |
1004 | 10.5k | : request_headers.getPathValue()); |
1005 | | |
1006 | 10.5k | if (max_path_length.has_value() && path.length() > max_path_length) { |
1007 | 751 | path = path.substr(0, max_path_length.value()); |
1008 | 751 | } |
1009 | | |
1010 | 10.5k | return absl::StrCat(request_headers.getSchemeValue(), "://", request_headers.getHostValue(), |
1011 | 10.5k | path); |
1012 | 10.5k | } |
1013 | | |
1014 | | void Utility::extractHostPathFromUri(const absl::string_view& uri, absl::string_view& host, |
1015 | 5.32k | absl::string_view& path) { |
1016 | | /** |
1017 | | * URI RFC: https://www.ietf.org/rfc/rfc2396.txt |
1018 | | * |
1019 | | * Example: |
1020 | | * uri = "https://example.com:8443/certs" |
1021 | | * pos: ^ |
1022 | | * host_pos: ^ |
1023 | | * path_pos: ^ |
1024 | | * host = "example.com:8443" |
1025 | | * path = "/certs" |
1026 | | */ |
1027 | 5.32k | const auto pos = uri.find("://"); |
1028 | | // Start position of the host |
1029 | 5.32k | const auto host_pos = (pos == std::string::npos) ? 0 : pos + 3; |
1030 | | // Start position of the path |
1031 | 5.32k | const auto path_pos = uri.find('/', host_pos); |
1032 | 5.32k | if (path_pos == std::string::npos) { |
1033 | | // If uri doesn't have "/", the whole string is treated as host. |
1034 | 534 | host = uri.substr(host_pos); |
1035 | 534 | path = "/"; |
1036 | 4.78k | } else { |
1037 | 4.78k | host = uri.substr(host_pos, path_pos - host_pos); |
1038 | 4.78k | path = uri.substr(path_pos); |
1039 | 4.78k | } |
1040 | 5.32k | } |
1041 | | |
1042 | 0 | std::string Utility::localPathFromFilePath(const absl::string_view& file_path) { |
1043 | 0 | if (file_path.size() >= 3 && file_path[1] == ':' && file_path[2] == '/' && |
1044 | 0 | std::isalpha(file_path[0])) { |
1045 | 0 | return std::string(file_path); |
1046 | 0 | } |
1047 | 0 | return absl::StrCat("/", file_path); |
1048 | 0 | } |
1049 | | |
1050 | 5.25k | RequestMessagePtr Utility::prepareHeaders(const envoy::config::core::v3::HttpUri& http_uri) { |
1051 | 5.25k | absl::string_view host, path; |
1052 | 5.25k | extractHostPathFromUri(http_uri.uri(), host, path); |
1053 | | |
1054 | 5.25k | RequestMessagePtr message(new RequestMessageImpl()); |
1055 | 5.25k | message->headers().setPath(path); |
1056 | 5.25k | message->headers().setHost(host); |
1057 | | |
1058 | 5.25k | return message; |
1059 | 5.25k | } |
1060 | | |
1061 | 1.03k | std::string Utility::QueryParamsMulti::toString() const { |
1062 | 1.03k | std::string out; |
1063 | 1.03k | std::string delim = "?"; |
1064 | 29.5k | for (const auto& p : this->data_) { |
1065 | 55.8k | for (const auto& v : p.second) { |
1066 | 55.8k | absl::StrAppend(&out, delim, p.first, "=", v); |
1067 | 55.8k | delim = "&"; |
1068 | 55.8k | } |
1069 | 29.5k | } |
1070 | 1.03k | return out; |
1071 | 1.03k | } |
1072 | | |
1073 | 242 | const std::string Utility::resetReasonToString(const Http::StreamResetReason reset_reason) { |
1074 | 242 | switch (reset_reason) { |
1075 | 0 | case Http::StreamResetReason::LocalConnectionFailure: |
1076 | 0 | return "local connection failure"; |
1077 | 0 | case Http::StreamResetReason::RemoteConnectionFailure: |
1078 | 0 | return "remote connection failure"; |
1079 | 0 | case Http::StreamResetReason::ConnectionTimeout: |
1080 | 0 | return "connection timeout"; |
1081 | 40 | case Http::StreamResetReason::ConnectionTermination: |
1082 | 40 | return "connection termination"; |
1083 | 0 | case Http::StreamResetReason::LocalReset: |
1084 | 0 | return "local reset"; |
1085 | 0 | case Http::StreamResetReason::LocalRefusedStreamReset: |
1086 | 0 | return "local refused stream reset"; |
1087 | 0 | case Http::StreamResetReason::Overflow: |
1088 | 0 | return "overflow"; |
1089 | 0 | case Http::StreamResetReason::RemoteReset: |
1090 | 0 | return "remote reset"; |
1091 | 54 | case Http::StreamResetReason::RemoteRefusedStreamReset: |
1092 | 54 | return "remote refused stream reset"; |
1093 | 0 | case Http::StreamResetReason::ConnectError: |
1094 | 0 | return "remote error with CONNECT request"; |
1095 | 148 | case Http::StreamResetReason::ProtocolError: |
1096 | 148 | return "protocol error"; |
1097 | 0 | case Http::StreamResetReason::OverloadManager: |
1098 | 0 | return "overload manager reset"; |
1099 | 0 | case Http::StreamResetReason::Http1PrematureUpstreamHalfClose: |
1100 | 0 | return "HTTP/1 premature upstream half close"; |
1101 | 242 | } |
1102 | | |
1103 | 0 | return ""; |
1104 | 242 | } |
1105 | | |
1106 | 4.43k | void Utility::transformUpgradeRequestFromH1toH2(RequestHeaderMap& headers) { |
1107 | 4.43k | ASSERT(Utility::isUpgrade(headers)); |
1108 | | |
1109 | 4.43k | headers.setReferenceMethod(Http::Headers::get().MethodValues.Connect); |
1110 | 4.43k | headers.setProtocol(headers.getUpgradeValue()); |
1111 | 4.43k | headers.removeUpgrade(); |
1112 | 4.43k | headers.removeConnection(); |
1113 | | // nghttp2 rejects upgrade requests/responses with content length, so strip |
1114 | | // any unnecessary content length header. |
1115 | 4.43k | if (headers.getContentLengthValue() == "0") { |
1116 | 15 | headers.removeContentLength(); |
1117 | 15 | } |
1118 | 4.43k | } |
1119 | | |
1120 | 0 | void Utility::transformUpgradeRequestFromH1toH3(RequestHeaderMap& headers) { |
1121 | 0 | transformUpgradeRequestFromH1toH2(headers); |
1122 | 0 | } |
1123 | | |
1124 | 33 | void Utility::transformUpgradeResponseFromH1toH2(ResponseHeaderMap& headers) { |
1125 | 33 | if (getResponseStatus(headers) == 101) { |
1126 | 0 | headers.setStatus(200); |
1127 | 0 | } |
1128 | 33 | headers.removeUpgrade(); |
1129 | 33 | headers.removeConnection(); |
1130 | 33 | if (headers.getContentLengthValue() == "0") { |
1131 | 11 | headers.removeContentLength(); |
1132 | 11 | } |
1133 | 33 | } |
1134 | | |
1135 | 0 | void Utility::transformUpgradeResponseFromH1toH3(ResponseHeaderMap& headers) { |
1136 | 0 | transformUpgradeResponseFromH1toH2(headers); |
1137 | 0 | } |
1138 | | |
1139 | 0 | void Utility::transformUpgradeRequestFromH2toH1(RequestHeaderMap& headers) { |
1140 | 0 | ASSERT(Utility::isH2UpgradeRequest(headers)); |
1141 | | |
1142 | 0 | headers.setReferenceMethod(Http::Headers::get().MethodValues.Get); |
1143 | 0 | headers.setUpgrade(headers.getProtocolValue()); |
1144 | 0 | headers.setReferenceConnection(Http::Headers::get().ConnectionValues.Upgrade); |
1145 | 0 | headers.removeProtocol(); |
1146 | 0 | } |
1147 | | |
1148 | 0 | void Utility::transformUpgradeRequestFromH3toH1(RequestHeaderMap& headers) { |
1149 | 0 | transformUpgradeRequestFromH2toH1(headers); |
1150 | 0 | } |
1151 | | |
1152 | | void Utility::transformUpgradeResponseFromH2toH1(ResponseHeaderMap& headers, |
1153 | 18 | absl::string_view upgrade) { |
1154 | 18 | if (getResponseStatus(headers) == 200) { |
1155 | 3 | headers.setUpgrade(upgrade); |
1156 | 3 | headers.setReferenceConnection(Http::Headers::get().ConnectionValues.Upgrade); |
1157 | 3 | headers.setStatus(101); |
1158 | 3 | } |
1159 | 18 | } |
1160 | | |
1161 | | void Utility::transformUpgradeResponseFromH3toH1(ResponseHeaderMap& headers, |
1162 | 0 | absl::string_view upgrade) { |
1163 | 0 | transformUpgradeResponseFromH2toH1(headers, upgrade); |
1164 | 0 | } |
1165 | | |
1166 | | std::string Utility::PercentEncoding::encode(absl::string_view value, |
1167 | 149 | absl::string_view reserved_chars) { |
1168 | 149 | absl::flat_hash_set<char> reserved_char_set{reserved_chars.begin(), reserved_chars.end()}; |
1169 | 5.39k | for (size_t i = 0; i < value.size(); ++i) { |
1170 | 5.24k | const char& ch = value[i]; |
1171 | | // The escaping characters are defined in |
1172 | | // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses. |
1173 | | // |
1174 | | // We do checking for each char in the string. If the current char is included in the defined |
1175 | | // escaping characters, we jump to "the slow path" (append the char [encoded or not encoded] |
1176 | | // to the returned string one by one) started from the current index. |
1177 | 5.24k | if (ch < ' ' || ch >= '~' || reserved_char_set.find(ch) != reserved_char_set.end()) { |
1178 | 5 | return PercentEncoding::encode(value, i, reserved_char_set); |
1179 | 5 | } |
1180 | 5.24k | } |
1181 | 144 | return std::string(value); |
1182 | 149 | } |
1183 | | |
1184 | | std::string Utility::PercentEncoding::encode(absl::string_view value, const size_t index, |
1185 | 5 | const absl::flat_hash_set<char>& reserved_char_set) { |
1186 | 5 | std::string encoded; |
1187 | 5 | if (index > 0) { |
1188 | 3 | absl::StrAppend(&encoded, value.substr(0, index)); |
1189 | 3 | } |
1190 | | |
1191 | 67 | for (size_t i = index; i < value.size(); ++i) { |
1192 | 62 | const char& ch = value[i]; |
1193 | 62 | if (ch < ' ' || ch >= '~' || reserved_char_set.find(ch) != reserved_char_set.end()) { |
1194 | | // For consistency, URI producers should use uppercase hexadecimal digits for all |
1195 | | // percent-encodings. https://tools.ietf.org/html/rfc3986#section-2.1. |
1196 | 59 | absl::StrAppend(&encoded, fmt::format("%{:02X}", static_cast<const unsigned char&>(ch))); |
1197 | 59 | } else { |
1198 | 3 | encoded.push_back(ch); |
1199 | 3 | } |
1200 | 62 | } |
1201 | 5 | return encoded; |
1202 | 5 | } |
1203 | | |
1204 | 66.8k | std::string Utility::PercentEncoding::decode(absl::string_view encoded) { |
1205 | 66.8k | std::string decoded; |
1206 | 66.8k | decoded.reserve(encoded.size()); |
1207 | 3.70M | for (size_t i = 0; i < encoded.size(); ++i) { |
1208 | 3.63M | char ch = encoded[i]; |
1209 | 3.63M | if (ch == '%' && i + 2 < encoded.size()) { |
1210 | 195k | const char& hi = encoded[i + 1]; |
1211 | 195k | const char& lo = encoded[i + 2]; |
1212 | 195k | if (absl::ascii_isxdigit(hi) && absl::ascii_isxdigit(lo)) { |
1213 | 3.77k | if (absl::ascii_isdigit(hi)) { |
1214 | 1.13k | ch = hi - '0'; |
1215 | 2.63k | } else { |
1216 | 2.63k | ch = absl::ascii_toupper(hi) - 'A' + 10; |
1217 | 2.63k | } |
1218 | | |
1219 | 3.77k | ch *= 16; |
1220 | 3.77k | if (absl::ascii_isdigit(lo)) { |
1221 | 1.42k | ch += lo - '0'; |
1222 | 2.35k | } else { |
1223 | 2.35k | ch += absl::ascii_toupper(lo) - 'A' + 10; |
1224 | 2.35k | } |
1225 | 3.77k | i += 2; |
1226 | 3.77k | } |
1227 | 195k | } |
1228 | 3.63M | decoded.push_back(ch); |
1229 | 3.63M | } |
1230 | 66.8k | return decoded; |
1231 | 66.8k | } |
1232 | | |
1233 | | namespace { |
1234 | | // %-encode all ASCII character codepoints, EXCEPT: |
1235 | | // ALPHA | DIGIT | * | - | . | _ |
1236 | | // SPACE is encoded as %20, NOT as the + character |
1237 | | constexpr std::array<uint32_t, 8> kUrlEncodedCharTable = { |
1238 | | // control characters |
1239 | | 0b11111111111111111111111111111111, |
1240 | | // !"#$%&'()*+,-./0123456789:;<=>? |
1241 | | 0b11111111110110010000000000111111, |
1242 | | //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ |
1243 | | 0b10000000000000000000000000011110, |
1244 | | //`abcdefghijklmnopqrstuvwxyz{|}~ |
1245 | | 0b10000000000000000000000000011111, |
1246 | | // extended ascii |
1247 | | 0b11111111111111111111111111111111, |
1248 | | 0b11111111111111111111111111111111, |
1249 | | 0b11111111111111111111111111111111, |
1250 | | 0b11111111111111111111111111111111, |
1251 | | }; |
1252 | | |
1253 | | constexpr std::array<uint32_t, 8> kUrlDecodedCharTable = { |
1254 | | // control characters |
1255 | | 0b00000000000000000000000000000000, |
1256 | | // !"#$%&'()*+,-./0123456789:;<=>? |
1257 | | 0b01011111111111111111111111110101, |
1258 | | //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ |
1259 | | 0b11111111111111111111111111110101, |
1260 | | //`abcdefghijklmnopqrstuvwxyz{|}~ |
1261 | | 0b11111111111111111111111111100010, |
1262 | | // extended ascii |
1263 | | 0b00000000000000000000000000000000, |
1264 | | 0b00000000000000000000000000000000, |
1265 | | 0b00000000000000000000000000000000, |
1266 | | 0b00000000000000000000000000000000, |
1267 | | }; |
1268 | | |
1269 | 124k | bool shouldPercentEncodeChar(char c) { return testCharInTable(kUrlEncodedCharTable, c); } |
1270 | | |
1271 | 0 | bool shouldPercentDecodeChar(char c) { return testCharInTable(kUrlDecodedCharTable, c); } |
1272 | | } // namespace |
1273 | | |
1274 | 0 | std::string Utility::PercentEncoding::urlEncodeQueryParameter(absl::string_view value) { |
1275 | 0 | std::string encoded; |
1276 | 0 | encoded.reserve(value.size()); |
1277 | 0 | for (char ch : value) { |
1278 | 0 | if (shouldPercentEncodeChar(ch)) { |
1279 | | // For consistency, URI producers should use uppercase hexadecimal digits for all |
1280 | | // percent-encodings. https://tools.ietf.org/html/rfc3986#section-2.1. |
1281 | 0 | absl::StrAppend(&encoded, fmt::format("%{:02X}", static_cast<const unsigned char&>(ch))); |
1282 | 0 | } else { |
1283 | 0 | encoded.push_back(ch); |
1284 | 0 | } |
1285 | 0 | } |
1286 | 0 | return encoded; |
1287 | 0 | } |
1288 | | |
1289 | 5.86k | bool Utility::PercentEncoding::queryParameterIsUrlEncoded(absl::string_view value) { |
1290 | 124k | for (char ch : value) { |
1291 | 124k | if (shouldPercentEncodeChar(ch)) { |
1292 | 17 | return false; |
1293 | 17 | } |
1294 | 124k | } |
1295 | 5.84k | return true; |
1296 | 5.86k | } |
1297 | | |
1298 | 0 | std::string Utility::PercentEncoding::urlDecodeQueryParameter(absl::string_view encoded) { |
1299 | 0 | std::string decoded; |
1300 | 0 | decoded.reserve(encoded.size()); |
1301 | 0 | for (size_t i = 0; i < encoded.size(); ++i) { |
1302 | 0 | char ch = encoded[i]; |
1303 | 0 | if (ch == '%' && i + 2 < encoded.size()) { |
1304 | 0 | const char& hi = encoded[i + 1]; |
1305 | 0 | const char& lo = encoded[i + 2]; |
1306 | 0 | if (absl::ascii_isxdigit(hi) && absl::ascii_isxdigit(lo)) { |
1307 | 0 | if (absl::ascii_isdigit(hi)) { |
1308 | 0 | ch = hi - '0'; |
1309 | 0 | } else { |
1310 | 0 | ch = absl::ascii_toupper(hi) - 'A' + 10; |
1311 | 0 | } |
1312 | |
|
1313 | 0 | ch *= 16; |
1314 | 0 | if (absl::ascii_isdigit(lo)) { |
1315 | 0 | ch += lo - '0'; |
1316 | 0 | } else { |
1317 | 0 | ch += absl::ascii_toupper(lo) - 'A' + 10; |
1318 | 0 | } |
1319 | 0 | if (shouldPercentDecodeChar(ch)) { |
1320 | | // Decode the character only if it is present in the characters_to_decode |
1321 | 0 | decoded.push_back(ch); |
1322 | 0 | } else { |
1323 | | // Otherwise keep it as is. |
1324 | 0 | decoded.push_back('%'); |
1325 | 0 | decoded.push_back(hi); |
1326 | 0 | decoded.push_back(lo); |
1327 | 0 | } |
1328 | 0 | i += 2; |
1329 | 0 | } |
1330 | 0 | } else { |
1331 | 0 | decoded.push_back(ch); |
1332 | 0 | } |
1333 | 0 | } |
1334 | 0 | return decoded; |
1335 | 0 | } |
1336 | | |
1337 | 7.20k | Utility::AuthorityAttributes Utility::parseAuthority(absl::string_view host) { |
1338 | | // First try to see if there is a port included. This also checks to see that there is not a ']' |
1339 | | // as the last character which is indicative of an IPv6 address without a port. This is a best |
1340 | | // effort attempt. |
1341 | 7.20k | const auto colon_pos = host.rfind(':'); |
1342 | 7.20k | absl::string_view host_to_resolve = host; |
1343 | 7.20k | absl::optional<uint16_t> port; |
1344 | 7.20k | if (colon_pos != absl::string_view::npos && host_to_resolve.back() != ']') { |
1345 | 241 | const absl::string_view string_view_host = host; |
1346 | 241 | host_to_resolve = string_view_host.substr(0, colon_pos); |
1347 | 241 | const auto port_str = string_view_host.substr(colon_pos + 1); |
1348 | 241 | uint64_t port64; |
1349 | 241 | if (port_str.empty() || !absl::SimpleAtoi(port_str, &port64) || port64 > 65535) { |
1350 | | // Just attempt to resolve whatever we were given. This will very likely fail. |
1351 | 0 | host_to_resolve = host; |
1352 | 241 | } else { |
1353 | 241 | port = static_cast<uint16_t>(port64); |
1354 | 241 | } |
1355 | 241 | } |
1356 | | |
1357 | | // Now see if this is an IP address. We need to know this because some things (such as setting |
1358 | | // SNI) are special cased if this is an IP address. Either way, we still go through the normal |
1359 | | // resolver flow. We could short-circuit the DNS resolver in this case, but the extra code to do |
1360 | | // so is not worth it since the DNS resolver should handle it for us. |
1361 | 7.20k | bool is_ip_address = false; |
1362 | 7.20k | absl::string_view potential_ip_address = host_to_resolve; |
1363 | | // TODO(mattklein123): Optimally we would support bracket parsing in parseInternetAddress(), |
1364 | | // but we still need to trim the brackets to send the IPv6 address into the DNS resolver. For |
1365 | | // now, just do all the trimming here, but in the future we should consider whether we can |
1366 | | // have unified [] handling as low as possible in the stack. |
1367 | 7.20k | if (!potential_ip_address.empty() && potential_ip_address.front() == '[' && |
1368 | 7.20k | potential_ip_address.back() == ']') { |
1369 | 7.19k | potential_ip_address.remove_prefix(1); |
1370 | 7.19k | potential_ip_address.remove_suffix(1); |
1371 | 7.19k | } |
1372 | 7.20k | if (Network::Utility::parseInternetAddressNoThrow(std::string(potential_ip_address)) != nullptr) { |
1373 | 937 | is_ip_address = true; |
1374 | 937 | host_to_resolve = potential_ip_address; |
1375 | 937 | } |
1376 | | |
1377 | 7.20k | return {is_ip_address, host_to_resolve, port}; |
1378 | 7.20k | } |
1379 | | |
1380 | | absl::Status |
1381 | 321 | Utility::validateCoreRetryPolicy(const envoy::config::core::v3::RetryPolicy& retry_policy) { |
1382 | 321 | if (retry_policy.has_retry_back_off()) { |
1383 | 118 | const auto& core_back_off = retry_policy.retry_back_off(); |
1384 | | |
1385 | 118 | uint64_t base_interval_ms = PROTOBUF_GET_MS_REQUIRED(core_back_off, base_interval); |
1386 | 118 | uint64_t max_interval_ms = |
1387 | 118 | PROTOBUF_GET_MS_OR_DEFAULT(core_back_off, max_interval, base_interval_ms * 10); |
1388 | | |
1389 | 118 | if (max_interval_ms < base_interval_ms) { |
1390 | 7 | return absl::InvalidArgumentError( |
1391 | 7 | "max_interval must be greater than or equal to the base_interval"); |
1392 | 7 | } |
1393 | 118 | } |
1394 | 314 | return absl::OkStatus(); |
1395 | 321 | } |
1396 | | |
1397 | | envoy::config::route::v3::RetryPolicy |
1398 | | Utility::convertCoreToRouteRetryPolicy(const envoy::config::core::v3::RetryPolicy& retry_policy, |
1399 | 3.80k | const std::string& retry_on) { |
1400 | 3.80k | envoy::config::route::v3::RetryPolicy route_retry_policy; |
1401 | 3.80k | constexpr uint64_t default_base_interval_ms = 1000; |
1402 | 3.80k | constexpr uint64_t default_max_interval_ms = 10 * default_base_interval_ms; |
1403 | | |
1404 | 3.80k | uint64_t base_interval_ms = default_base_interval_ms; |
1405 | 3.80k | uint64_t max_interval_ms = default_max_interval_ms; |
1406 | | |
1407 | 3.80k | if (retry_policy.has_retry_back_off()) { |
1408 | 722 | const auto& core_back_off = retry_policy.retry_back_off(); |
1409 | | |
1410 | 722 | base_interval_ms = PROTOBUF_GET_MS_REQUIRED(core_back_off, base_interval); |
1411 | 722 | max_interval_ms = |
1412 | 722 | PROTOBUF_GET_MS_OR_DEFAULT(core_back_off, max_interval, base_interval_ms * 10); |
1413 | | |
1414 | 722 | if (max_interval_ms < base_interval_ms) { |
1415 | 0 | ENVOY_BUG(false, "max_interval must be greater than or equal to the base_interval"); |
1416 | 0 | base_interval_ms = max_interval_ms / 2; |
1417 | 0 | } |
1418 | 722 | } |
1419 | | |
1420 | 3.80k | route_retry_policy.mutable_num_retries()->set_value( |
1421 | 3.80k | PROTOBUF_GET_WRAPPED_OR_DEFAULT(retry_policy, num_retries, 1)); |
1422 | | |
1423 | 3.80k | auto* route_mutable_back_off = route_retry_policy.mutable_retry_back_off(); |
1424 | | |
1425 | 3.80k | route_mutable_back_off->mutable_base_interval()->CopyFrom( |
1426 | 3.80k | Protobuf::util::TimeUtil::MillisecondsToDuration(base_interval_ms)); |
1427 | 3.80k | route_mutable_back_off->mutable_max_interval()->CopyFrom( |
1428 | 3.80k | Protobuf::util::TimeUtil::MillisecondsToDuration(max_interval_ms)); |
1429 | | |
1430 | | // set all the other fields with appropriate values. |
1431 | 3.80k | if (!retry_on.empty()) { |
1432 | 3.79k | route_retry_policy.set_retry_on(retry_on); |
1433 | 3.79k | } else { |
1434 | 14 | route_retry_policy.set_retry_on(retry_policy.retry_on()); |
1435 | 14 | } |
1436 | 3.80k | route_retry_policy.mutable_per_try_timeout()->CopyFrom( |
1437 | 3.80k | route_retry_policy.retry_back_off().max_interval()); |
1438 | | |
1439 | 3.80k | if (retry_policy.has_retry_priority()) { |
1440 | 1.05k | route_retry_policy.mutable_retry_priority()->set_name(retry_policy.retry_priority().name()); |
1441 | 1.05k | route_retry_policy.mutable_retry_priority()->mutable_typed_config()->MergeFrom( |
1442 | 1.05k | retry_policy.retry_priority().typed_config()); |
1443 | 1.05k | } |
1444 | | |
1445 | 3.80k | if (!retry_policy.retry_host_predicate().empty()) { |
1446 | 1.99k | for (const auto& host_predicate : retry_policy.retry_host_predicate()) { |
1447 | 1.99k | auto* route_host_predicate = route_retry_policy.mutable_retry_host_predicate()->Add(); |
1448 | 1.99k | route_host_predicate->set_name(host_predicate.name()); |
1449 | 1.99k | route_host_predicate->mutable_typed_config()->MergeFrom(host_predicate.typed_config()); |
1450 | 1.99k | } |
1451 | 1.17k | } |
1452 | | |
1453 | 3.80k | route_retry_policy.set_host_selection_retry_max_attempts( |
1454 | 3.80k | retry_policy.host_selection_retry_max_attempts()); |
1455 | | |
1456 | 3.80k | return route_retry_policy; |
1457 | 3.80k | } |
1458 | | |
1459 | 1.81k | bool Utility::isSafeRequest(const Http::RequestHeaderMap& request_headers) { |
1460 | 1.81k | absl::string_view method = request_headers.getMethodValue(); |
1461 | 1.81k | return method == Http::Headers::get().MethodValues.Get || |
1462 | 1.81k | method == Http::Headers::get().MethodValues.Head || |
1463 | 1.81k | method == Http::Headers::get().MethodValues.Options || |
1464 | 1.81k | method == Http::Headers::get().MethodValues.Trace; |
1465 | 1.81k | } |
1466 | | |
1467 | 0 | Http::Code Utility::maybeRequestTimeoutCode(bool remote_decode_complete) { |
1468 | 0 | return remote_decode_complete ? Http::Code::GatewayTimeout |
1469 | | // Http::Code::RequestTimeout is more expensive because HTTP1 client |
1470 | | // cannot use the connection any more. |
1471 | 0 | : Http::Code::RequestTimeout; |
1472 | 0 | } |
1473 | | |
1474 | 70.0k | bool Utility::schemeIsValid(const absl::string_view scheme) { |
1475 | 70.0k | return schemeIsHttp(scheme) || schemeIsHttps(scheme); |
1476 | 70.0k | } |
1477 | | |
1478 | 70.0k | bool Utility::schemeIsHttp(const absl::string_view scheme) { |
1479 | 70.0k | return absl::EqualsIgnoreCase(scheme, Headers::get().SchemeValues.Http); |
1480 | 70.0k | } |
1481 | | |
1482 | 68.8k | bool Utility::schemeIsHttps(const absl::string_view scheme) { |
1483 | 68.8k | return absl::EqualsIgnoreCase(scheme, Headers::get().SchemeValues.Https); |
1484 | 68.8k | } |
1485 | | |
1486 | | std::string Utility::newUri(::Envoy::OptRef<const Utility::RedirectConfig> redirect_config, |
1487 | 88 | const Http::RequestHeaderMap& headers) { |
1488 | 88 | absl::string_view final_scheme; |
1489 | 88 | absl::string_view final_host; |
1490 | 88 | absl::string_view final_port; |
1491 | 88 | absl::string_view final_path; |
1492 | | |
1493 | 88 | if (redirect_config.has_value() && !redirect_config->scheme_redirect_.empty()) { |
1494 | 0 | final_scheme = redirect_config->scheme_redirect_; |
1495 | 88 | } else if (redirect_config.has_value() && redirect_config->https_redirect_) { |
1496 | 0 | final_scheme = Http::Headers::get().SchemeValues.Https; |
1497 | 88 | } else { |
1498 | | // Serve the redirect URL based on the scheme of the original URL, not the |
1499 | | // security of the underlying connection. |
1500 | 88 | final_scheme = headers.getSchemeValue(); |
1501 | 88 | } |
1502 | | |
1503 | 88 | if (redirect_config.has_value() && !redirect_config->port_redirect_.empty()) { |
1504 | 0 | final_port = redirect_config->port_redirect_; |
1505 | 88 | } else { |
1506 | 88 | final_port = ""; |
1507 | 88 | } |
1508 | | |
1509 | 88 | if (redirect_config.has_value() && !redirect_config->host_redirect_.empty()) { |
1510 | 0 | final_host = redirect_config->host_redirect_; |
1511 | 88 | } else { |
1512 | 88 | ASSERT(headers.Host()); |
1513 | 88 | final_host = processRequestHost(headers, final_scheme, final_port); |
1514 | 88 | } |
1515 | | |
1516 | 88 | std::string final_path_value; |
1517 | 88 | if (redirect_config.has_value() && !redirect_config->path_redirect_.empty()) { |
1518 | | // The path_redirect query string, if any, takes precedence over the request's query string, |
1519 | | // and it will not be stripped regardless of `strip_query`. |
1520 | 0 | if (redirect_config->path_redirect_has_query_) { |
1521 | 0 | final_path = redirect_config->path_redirect_; |
1522 | 0 | } else { |
1523 | 0 | const absl::string_view current_path = headers.getPathValue(); |
1524 | 0 | const size_t path_end = current_path.find('?'); |
1525 | 0 | const bool current_path_has_query = path_end != absl::string_view::npos; |
1526 | 0 | if (current_path_has_query) { |
1527 | 0 | final_path_value = redirect_config->path_redirect_; |
1528 | 0 | final_path_value.append(current_path.data() + path_end, current_path.length() - path_end); |
1529 | 0 | final_path = final_path_value; |
1530 | 0 | } else { |
1531 | 0 | final_path = redirect_config->path_redirect_; |
1532 | 0 | } |
1533 | 0 | } |
1534 | 88 | } else { |
1535 | 88 | final_path = headers.getPathValue(); |
1536 | 88 | } |
1537 | | |
1538 | 88 | if (!absl::StartsWith(final_path, "/")) { |
1539 | 0 | final_path_value = absl::StrCat("/", final_path); |
1540 | 0 | final_path = final_path_value; |
1541 | 0 | } |
1542 | | |
1543 | 88 | if (redirect_config.has_value() && !redirect_config->path_redirect_has_query_ && |
1544 | 88 | redirect_config->strip_query_) { |
1545 | 0 | const size_t path_end = final_path.find('?'); |
1546 | 0 | if (path_end != absl::string_view::npos) { |
1547 | 0 | final_path = final_path.substr(0, path_end); |
1548 | 0 | } |
1549 | 0 | } |
1550 | | |
1551 | 88 | return fmt::format("{}://{}{}{}", final_scheme, final_host, final_port, final_path); |
1552 | 88 | } |
1553 | | |
1554 | 19.8k | bool Utility::isValidRefererValue(absl::string_view value) { |
1555 | | |
1556 | | // First, we try to parse it as an absolute URL and |
1557 | | // ensure that there is no fragment or userinfo. |
1558 | | // If that fails, we can just parse it as a relative reference |
1559 | | // and expect no fragment |
1560 | | // NOTE: "about:blank" is incorrectly rejected here, because |
1561 | | // url.initialize uses http_parser_parse_url, which requires |
1562 | | // a host to be present if there is a schema. |
1563 | 19.8k | Utility::Url url; |
1564 | | |
1565 | 19.8k | if (url.initialize(value, false)) { |
1566 | 1.19k | return !(url.containsFragment() || url.containsUserinfo()); |
1567 | 1.19k | } |
1568 | | |
1569 | 18.6k | bool seen_slash = false; |
1570 | | |
1571 | 93.8k | for (char c : value) { |
1572 | 93.8k | switch (c) { |
1573 | 9.38k | case ':': |
1574 | 9.38k | if (!seen_slash) { |
1575 | | // First path segment cannot contain ':' |
1576 | | // https://www.rfc-editor.org/rfc/rfc3986#section-3.3 |
1577 | 9.38k | return false; |
1578 | 9.38k | } |
1579 | 0 | continue; |
1580 | 4.50k | case '/': |
1581 | 4.50k | seen_slash = true; |
1582 | 4.50k | continue; |
1583 | 79.9k | default: |
1584 | 79.9k | if (!testCharInTable(kUriQueryAndFragmentCharTable, c)) { |
1585 | 397 | return false; |
1586 | 397 | } |
1587 | 93.8k | } |
1588 | 93.8k | } |
1589 | | |
1590 | 8.89k | return true; |
1591 | 18.6k | } |
1592 | | |
1593 | | } // namespace Http |
1594 | | } // namespace Envoy |