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