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