1
#include "source/common/http/utility.h"
2

            
3
#include <http_parser.h>
4

            
5
#include <cstdint>
6
#include <string>
7
#include <vector>
8

            
9
#include "envoy/config/core/v3/http_uri.pb.h"
10
#include "envoy/config/core/v3/protocol.pb.h"
11
#include "envoy/http/header_map.h"
12

            
13
#include "source/common/buffer/buffer_impl.h"
14
#include "source/common/common/assert.h"
15
#include "source/common/common/empty_string.h"
16
#include "source/common/common/enum_to_int.h"
17
#include "source/common/common/fmt.h"
18
#include "source/common/common/utility.h"
19
#include "source/common/grpc/status.h"
20
#include "source/common/http/character_set_validation.h"
21
#include "source/common/http/exception.h"
22
#include "source/common/http/header_map_impl.h"
23
#include "source/common/http/headers.h"
24
#include "source/common/http/message_impl.h"
25
#include "source/common/network/cidr_range.h"
26
#include "source/common/network/utility.h"
27
#include "source/common/protobuf/utility.h"
28
#include "source/common/runtime/runtime_features.h"
29

            
30
#include "absl/container/node_hash_set.h"
31
#include "absl/strings/match.h"
32
#include "absl/strings/numbers.h"
33
#include "absl/strings/str_cat.h"
34
#include "absl/strings/str_split.h"
35
#include "absl/strings/string_view.h"
36
#include "absl/types/optional.h"
37
#include "quiche/http2/adapter/http2_protocol.h"
38

            
39
namespace Envoy {
40
namespace {
41

            
42
// Get request host from the request header map, removing the port if the port
43
// does not match the scheme, or if port is provided.
44
absl::string_view processRequestHost(const Http::RequestHeaderMap& headers,
45
175
                                     absl::string_view new_scheme, absl::string_view new_port) {
46

            
47
175
  absl::string_view request_host = headers.getHostValue();
48
175
  size_t host_end;
49
175
  if (request_host.empty()) {
50
    return request_host;
51
  }
52
  // Detect if IPv6 URI
53
175
  if (request_host[0] == '[') {
54
4
    host_end = request_host.rfind("]:");
55
4
    if (host_end != absl::string_view::npos) {
56
3
      host_end += 1; // advance to :
57
3
    }
58
171
  } else {
59
171
    host_end = request_host.rfind(':');
60
171
  }
61

            
62
175
  if (host_end != absl::string_view::npos) {
63
9
    absl::string_view request_port = request_host.substr(host_end);
64
    // In the rare case that X-Forwarded-Proto and scheme disagree (say http URL over an HTTPS
65
    // connection), do port stripping based on X-Forwarded-Proto so http://foo.com:80 won't
66
    // have the port stripped when served over TLS.
67
9
    absl::string_view request_protocol = headers.getForwardedProtoValue();
68
9
    bool remove_port = !new_port.empty();
69

            
70
9
    if (new_scheme != request_protocol) {
71
6
      remove_port |= Http::Utility::schemeIsHttps(request_protocol) && request_port == ":443";
72
6
      remove_port |= Http::Utility::schemeIsHttp(request_protocol) && request_port == ":80";
73
6
    }
74

            
75
9
    if (remove_port) {
76
3
      return request_host.substr(0, host_end);
77
3
    }
78
9
  }
79

            
80
172
  return request_host;
81
175
}
82

            
83
} // namespace
84
namespace Http2 {
85
namespace Utility {
86

            
87
namespace {
88

            
89
struct SettingsEntry {
90
  uint16_t settings_id;
91
  uint32_t value;
92
};
93

            
94
struct SettingsEntryHash {
95
10
  size_t operator()(const SettingsEntry& entry) const {
96
10
    return absl::Hash<decltype(entry.settings_id)>()(entry.settings_id);
97
10
  }
98
};
99

            
100
struct SettingsEntryEquals {
101
6
  bool operator()(const SettingsEntry& lhs, const SettingsEntry& rhs) const {
102
6
    return lhs.settings_id == rhs.settings_id;
103
6
  }
104
};
105

            
106
absl::Status
107
286724
validateCustomSettingsParameters(const envoy::config::core::v3::Http2ProtocolOptions& options) {
108
286724
  std::vector<std::string> parameter_collisions, custom_parameter_collisions;
109
286724
  absl::node_hash_set<SettingsEntry, SettingsEntryHash, SettingsEntryEquals> custom_parameters;
110
  // User defined and named parameters with the same SETTINGS identifier can not both be set.
111
286724
  for (const auto& it : options.custom_settings_parameters()) {
112
17
    ASSERT(it.identifier().value() <= std::numeric_limits<uint16_t>::max());
113
    // Check for custom parameter inconsistencies.
114
17
    const auto result = custom_parameters.insert(
115
17
        {static_cast<uint16_t>(it.identifier().value()), it.value().value()});
116
17
    if (!result.second) {
117
2
      if (result.first->value != it.value().value()) {
118
1
        custom_parameter_collisions.push_back(
119
1
            absl::StrCat("0x", absl::Hex(it.identifier().value(), absl::kZeroPad2)));
120
        // Fall through to allow unbatched exceptions to throw first.
121
1
      }
122
2
    }
123
17
    switch (it.identifier().value()) {
124
2
    case http2::adapter::ENABLE_PUSH:
125
2
      if (it.value().value() == 1) {
126
2
        return absl::InvalidArgumentError(
127
2
            "server push is not supported by Envoy and can not be enabled via a "
128
2
            "SETTINGS parameter.");
129
2
      }
130
      break;
131
1
    case http2::adapter::ENABLE_CONNECT_PROTOCOL:
132
      // An exception is made for `allow_connect` which can't be checked for presence due to the
133
      // use of a primitive type (bool).
134
1
      return absl::InvalidArgumentError(
135
1
          "the \"allow_connect\" SETTINGS parameter must only be configured "
136
1
          "through the named field");
137
3
    case http2::adapter::HEADER_TABLE_SIZE:
138
3
      if (options.has_hpack_table_size()) {
139
3
        parameter_collisions.push_back("hpack_table_size");
140
3
      }
141
3
      break;
142
2
    case http2::adapter::MAX_CONCURRENT_STREAMS:
143
2
      if (options.has_max_concurrent_streams()) {
144
1
        parameter_collisions.push_back("max_concurrent_streams");
145
1
      }
146
2
      break;
147
    case http2::adapter::INITIAL_WINDOW_SIZE:
148
      if (options.has_initial_stream_window_size()) {
149
        parameter_collisions.push_back("initial_stream_window_size");
150
      }
151
      break;
152
9
    default:
153
      // Ignore unknown parameters.
154
9
      break;
155
17
    }
156
17
  }
157

            
158
286721
  if (!custom_parameter_collisions.empty()) {
159
1
    return absl::InvalidArgumentError(fmt::format(
160
1
        "inconsistent HTTP/2 custom SETTINGS parameter(s) detected; identifiers = {{{}}}",
161
1
        absl::StrJoin(custom_parameter_collisions, ",")));
162
1
  }
163
286720
  if (!parameter_collisions.empty()) {
164
3
    return absl::InvalidArgumentError(fmt::format(
165
3
        "the {{{}}} HTTP/2 SETTINGS parameter(s) can not be configured through both named and "
166
3
        "custom parameters",
167
3
        absl::StrJoin(parameter_collisions, ",")));
168
3
  }
169
286717
  return absl::OkStatus();
170
286720
}
171

            
172
} // namespace
173

            
174
absl::StatusOr<envoy::config::core::v3::Http2ProtocolOptions>
175
initializeAndValidateOptions(const envoy::config::core::v3::Http2ProtocolOptions& options,
176
                             bool hcm_stream_error_set,
177
9974
                             const Protobuf::BoolValue& hcm_stream_error) {
178
9974
  auto ret = initializeAndValidateOptions(options);
179
9974
  if (ret.status().ok() && !options.has_override_stream_error_on_invalid_http_message() &&
180
9974
      hcm_stream_error_set) {
181
7
    ret->mutable_override_stream_error_on_invalid_http_message()->set_value(
182
7
        hcm_stream_error.value());
183
7
  }
184
9974
  return ret;
185
9974
}
186

            
187
absl::StatusOr<envoy::config::core::v3::Http2ProtocolOptions>
188
286724
initializeAndValidateOptions(const envoy::config::core::v3::Http2ProtocolOptions& options) {
189
286724
  envoy::config::core::v3::Http2ProtocolOptions options_clone(options);
190
286724
  RETURN_IF_NOT_OK(validateCustomSettingsParameters(options));
191

            
192
286717
  if (!options.has_override_stream_error_on_invalid_http_message()) {
193
286664
    options_clone.mutable_override_stream_error_on_invalid_http_message()->set_value(
194
286664
        options.stream_error_on_invalid_http_messaging());
195
286664
  }
196

            
197
286717
  if (!options_clone.has_hpack_table_size()) {
198
286709
    options_clone.mutable_hpack_table_size()->set_value(OptionsLimits::DEFAULT_HPACK_TABLE_SIZE);
199
286709
  }
200
286717
  ASSERT(options_clone.hpack_table_size().value() <= OptionsLimits::MAX_HPACK_TABLE_SIZE);
201
286717
  const bool safe_http2_options =
202
286717
      Runtime::runtimeFeatureEnabled("envoy.reloadable_features.safe_http2_options");
203

            
204
286717
  if (!options_clone.has_max_concurrent_streams()) {
205
286651
    if (safe_http2_options) {
206
286650
      options_clone.mutable_max_concurrent_streams()->set_value(
207
286650
          OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS);
208
286650
    } else {
209
1
      options_clone.mutable_max_concurrent_streams()->set_value(
210
1
          OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS_LEGACY);
211
1
    }
212
286651
  }
213
286717
  ASSERT(
214
286717
      options_clone.max_concurrent_streams().value() >= OptionsLimits::MIN_MAX_CONCURRENT_STREAMS &&
215
286717
      options_clone.max_concurrent_streams().value() <= OptionsLimits::MAX_MAX_CONCURRENT_STREAMS);
216
286717
  if (!options_clone.has_initial_stream_window_size()) {
217
286326
    if (safe_http2_options) {
218
286325
      options_clone.mutable_initial_stream_window_size()->set_value(
219
286325
          OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE);
220
286325
    } else {
221
1
      options_clone.mutable_initial_stream_window_size()->set_value(
222
1
          OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE_LEGACY);
223
1
    }
224
286326
  }
225
286717
  ASSERT(options_clone.initial_stream_window_size().value() >=
226
286717
             OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE &&
227
286717
         options_clone.initial_stream_window_size().value() <=
228
286717
             OptionsLimits::MAX_INITIAL_STREAM_WINDOW_SIZE);
229
286717
  if (!options_clone.has_initial_connection_window_size()) {
230
286700
    if (safe_http2_options) {
231
286699
      options_clone.mutable_initial_connection_window_size()->set_value(
232
286699
          OptionsLimits::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE);
233
286699
    } else {
234
1
      options_clone.mutable_initial_connection_window_size()->set_value(
235
1
          OptionsLimits::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE_LEGACY);
236
1
    }
237
286700
  }
238
286717
  ASSERT(options_clone.initial_connection_window_size().value() >=
239
286717
             OptionsLimits::MIN_INITIAL_CONNECTION_WINDOW_SIZE &&
240
286717
         options_clone.initial_connection_window_size().value() <=
241
286717
             OptionsLimits::MAX_INITIAL_CONNECTION_WINDOW_SIZE);
242
286717
  if (!options_clone.has_max_outbound_frames()) {
243
286572
    options_clone.mutable_max_outbound_frames()->set_value(
244
286572
        OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES);
245
286572
  }
246
286717
  if (!options_clone.has_max_outbound_control_frames()) {
247
286572
    options_clone.mutable_max_outbound_control_frames()->set_value(
248
286572
        OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES);
249
286572
  }
250
286717
  if (!options_clone.has_max_consecutive_inbound_frames_with_empty_payload()) {
251
286713
    options_clone.mutable_max_consecutive_inbound_frames_with_empty_payload()->set_value(
252
286713
        OptionsLimits::DEFAULT_MAX_CONSECUTIVE_INBOUND_FRAMES_WITH_EMPTY_PAYLOAD);
253
286713
  }
254
286717
  if (!options_clone.has_max_inbound_priority_frames_per_stream()) {
255
286717
    options_clone.mutable_max_inbound_priority_frames_per_stream()->set_value(
256
286717
        OptionsLimits::DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM);
257
286717
  }
258
286717
  if (!options_clone.has_max_inbound_window_update_frames_per_data_frame_sent()) {
259
286717
    options_clone.mutable_max_inbound_window_update_frames_per_data_frame_sent()->set_value(
260
286717
        OptionsLimits::DEFAULT_MAX_INBOUND_WINDOW_UPDATE_FRAMES_PER_DATA_FRAME_SENT);
261
286717
  }
262

            
263
286717
  return options_clone;
264
286724
}
265

            
266
} // namespace Utility
267
} // namespace Http2
268

            
269
namespace Http3 {
270
namespace Utility {
271

            
272
envoy::config::core::v3::Http3ProtocolOptions
273
initializeAndValidateOptions(const envoy::config::core::v3::Http3ProtocolOptions& options,
274
                             bool hcm_stream_error_set,
275
9975
                             const Protobuf::BoolValue& hcm_stream_error) {
276
9975
  if (options.has_override_stream_error_on_invalid_http_message()) {
277
29
    return options;
278
29
  }
279
9946
  envoy::config::core::v3::Http3ProtocolOptions options_clone(options);
280
9946
  if (hcm_stream_error_set) {
281
6
    options_clone.mutable_override_stream_error_on_invalid_http_message()->set_value(
282
6
        hcm_stream_error.value());
283
9940
  } else {
284
9940
    options_clone.mutable_override_stream_error_on_invalid_http_message()->set_value(false);
285
9940
  }
286
9946
  return options_clone;
287
9975
}
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
1590
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
1590
  if (offset == 0 || *(absolute_url.data() + offset - 1) != '[') {
310
1582
    return false;
311
1582
  }
312
  // Start one character sooner and end one character later.
313
8
  offset--;
314
8
  len += 2;
315
  // HTTP parser ensures that any [ has a closing ]
316
8
  ASSERT(absolute_url.length() >= offset + len);
317
8
  return true;
318
1590
}
319

            
320
void forEachCookie(
321
    const HeaderMap& headers, const LowerCaseString& cookie_header,
322
1519
    const std::function<bool(absl::string_view, absl::string_view)>& cookie_consumer) {
323
1519
  const Http::HeaderMap::GetResult cookie_headers = headers.get(cookie_header);
324

            
325
2168
  for (size_t index = 0; index < cookie_headers.size(); index++) {
326
1196
    auto cookie_header_value = cookie_headers[index]->value().getStringView();
327

            
328
    // Split the cookie header into individual cookies.
329
2378
    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
2377
      size_t first_non_space = s.find_first_not_of(' ');
332
2377
      size_t equals_index = s.find('=');
333
2377
      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
372
        continue;
337
372
      }
338
2005
      absl::string_view k = s.substr(first_non_space, equals_index - first_non_space);
339
2005
      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
2005
      if (v.size() >= 2 && v.back() == '"' && v[0] == '"') {
344
24
        v = v.substr(1, v.size() - 2);
345
24
      }
346

            
347
2005
      if (!cookie_consumer(k, v)) {
348
547
        return;
349
547
      }
350
2005
    }
351
1196
  }
352
1519
}
353

            
354
std::string parseCookie(const HeaderMap& headers, const std::string& key,
355
1208
                        const LowerCaseString& cookie) {
356
1208
  std::string value;
357

            
358
  // Iterate over each cookie & return if its value is not empty.
359
2005
  forEachCookie(headers, cookie, [&key, &value](absl::string_view k, absl::string_view v) -> bool {
360
1368
    if (key == k) {
361
547
      value = std::string{v};
362
547
      return false;
363
547
    }
364

            
365
    // continue iterating until a cookie that matches `key` is found.
366
821
    return true;
367
1368
  });
368

            
369
1208
  return value;
370
1208
}
371

            
372
absl::flat_hash_map<std::string, std::string>
373
221
Utility::parseCookies(const RequestHeaderMap& headers) {
374
422
  return Utility::parseCookies(headers, [](absl::string_view) -> bool { return true; });
375
221
}
376

            
377
absl::flat_hash_map<std::string, std::string>
378
Utility::parseCookies(const RequestHeaderMap& headers,
379
307
                      const std::function<bool(absl::string_view)>& key_filter) {
380
307
  absl::flat_hash_map<std::string, std::string> cookies;
381

            
382
307
  forEachCookie(headers, Http::Headers::get().Cookie,
383
642
                [&cookies, &key_filter](absl::string_view k, absl::string_view v) -> bool {
384
626
                  if (key_filter(k)) {
385
590
                    cookies.emplace(k, v);
386
590
                  }
387

            
388
                  // continue iterating until all cookies are processed.
389
626
                  return true;
390
626
                });
391

            
392
307
  return cookies;
393
307
}
394

            
395
13
bool Utility::Url::containsFragment() { return (component_bitmap_ & (1 << UcFragment)); }
396

            
397
11
bool Utility::Url::containsUserinfo() { return (component_bitmap_ & (1 << UcUserinfo)); }
398

            
399
1628
bool Utility::Url::initialize(absl::string_view absolute_url, bool is_connect) {
400
1628
  struct http_parser_url u;
401
1628
  http_parser_url_init(&u);
402
1628
  const int result =
403
1628
      http_parser_parse_url(absolute_url.data(), absolute_url.length(), is_connect, &u);
404

            
405
1628
  if (result != 0) {
406
35
    return false;
407
35
  }
408

            
409
1593
  if ((u.field_set & (1 << UF_HOST)) != (1 << UF_HOST) &&
410
1593
      (u.field_set & (1 << UF_SCHEMA)) != (1 << UF_SCHEMA)) {
411
3
    return false;
412
3
  }
413

            
414
1590
  component_bitmap_ = u.field_set;
415
1590
  scheme_ = absl::string_view(absolute_url.data() + u.field_data[UF_SCHEMA].off,
416
1590
                              u.field_data[UF_SCHEMA].len);
417

            
418
1590
  uint64_t authority_len = u.field_data[UF_HOST].len;
419
1590
  if ((u.field_set & (1 << UF_PORT)) == (1 << UF_PORT)) {
420
353
    authority_len = authority_len + u.field_data[UF_PORT].len + 1;
421
353
  }
422

            
423
1590
  uint64_t authority_beginning = u.field_data[UF_HOST].off;
424
1590
  const bool is_ipv6 = maybeAdjustForIpv6(absolute_url, authority_beginning, authority_len);
425
1590
  host_and_port_ = absl::string_view(absolute_url.data() + authority_beginning, authority_len);
426
1590
  if (is_ipv6 && !parseAuthority(host_and_port_).is_ip_address_) {
427
5
    return false;
428
5
  }
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
1585
  uint64_t path_etc_len = absolute_url.length() - (authority_beginning + hostAndPort().length());
434
1585
  if (path_etc_len > 0) {
435
1057
    uint64_t path_beginning = authority_beginning + hostAndPort().length();
436
1057
    path_and_query_params_ = absl::string_view(absolute_url.data() + path_beginning, path_etc_len);
437
1518
  } else if (!is_connect) {
438
200
    ASSERT((u.field_set & (1 << UF_PATH)) == 0);
439
200
    path_and_query_params_ = absl::string_view(kDefaultPath, 1);
440
200
  }
441
1585
  return true;
442
1590
}
443

            
444
22
std::string Utility::Url::toString() const {
445
22
  return absl::StrCat(scheme_, "://", host_and_port_, path_and_query_params_);
446
22
}
447

            
448
void Utility::appendXff(RequestHeaderMap& headers,
449
3761
                        const Network::Address::Instance& remote_address) {
450
3761
  if (remote_address.type() != Network::Address::Type::Ip) {
451
1
    return;
452
1
  }
453

            
454
3760
  headers.appendForwardedFor(remote_address.ip()->addressAsString(), ",");
455
3760
}
456

            
457
208
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
208
  headers.appendVia(via, ", ");
462
208
}
463

            
464
void Utility::updateAuthority(RequestHeaderMap& headers, absl::string_view hostname,
465
51
                              bool append_xfh, bool keep_old) {
466
51
  if (const absl::string_view host = headers.getHostValue(); !host.empty()) {
467
    // Insert the x-envoy-original-host header if required.
468
43
    if (keep_old) {
469
38
      headers.setEnvoyOriginalHost(host);
470
38
    }
471

            
472
    // Append the x-forwarded-host header if required. Only append to x-forwarded-host
473
    // if the value was not the last value appended.
474
43
    if (append_xfh) {
475
10
      const absl::InlinedVector<absl::string_view, 4> xfh_split =
476
10
          absl::StrSplit(headers.getForwardedHostValue(), ',', absl::SkipWhitespace());
477
10
      if (xfh_split.empty() || xfh_split.back() != host) {
478
9
        headers.appendForwardedHost(host, ",");
479
9
      }
480
10
    }
481
43
  }
482

            
483
51
  headers.setHost(hostname);
484
51
}
485

            
486
16
std::string Utility::createSslRedirectPath(const RequestHeaderMap& headers) {
487
16
  ASSERT(headers.Host());
488
16
  ASSERT(headers.Path());
489
16
  return fmt::format("https://{}{}", headers.getHostValue(), headers.getPathValue());
490
16
}
491

            
492
681
Utility::QueryParamsMulti Utility::QueryParamsMulti::parseQueryString(absl::string_view url) {
493
681
  size_t start = url.find('?');
494
681
  if (start == std::string::npos) {
495
307
    return {};
496
307
  }
497

            
498
374
  start++;
499
374
  return Utility::QueryParamsMulti::parseParameters(url, start, /*decode_params=*/false);
500
681
}
501

            
502
Utility::QueryParamsMulti
503
728
Utility::QueryParamsMulti::parseAndDecodeQueryString(absl::string_view url) {
504
728
  size_t start = url.find('?');
505
728
  if (start == std::string::npos) {
506
257
    return {};
507
257
  }
508

            
509
471
  start++;
510
471
  return Utility::QueryParamsMulti::parseParameters(url, start, /*decode_params=*/true);
511
728
}
512

            
513
Utility::QueryParamsMulti Utility::QueryParamsMulti::parseParameters(absl::string_view data,
514
                                                                     size_t start,
515
894
                                                                     bool decode_params) {
516
894
  QueryParamsMulti params;
517

            
518
2479
  while (start < data.size()) {
519
1585
    size_t end = data.find('&', start);
520
1585
    if (end == std::string::npos) {
521
884
      end = data.size();
522
884
    }
523
1585
    absl::string_view param(data.data() + start, end - start);
524

            
525
1585
    const size_t equal = param.find('=');
526
1585
    if (equal != std::string::npos) {
527
1490
      const auto param_name = StringUtil::subspan(data, start, start + equal);
528
1490
      const auto param_value = StringUtil::subspan(data, start + equal + 1, end);
529
1490
      params.add(decode_params ? PercentEncoding::decode(param_name) : param_name,
530
1490
                 decode_params ? PercentEncoding::decode(param_value) : param_value);
531
1505
    } else {
532
95
      const auto param_name = StringUtil::subspan(data, start, end);
533
95
      params.add(decode_params ? PercentEncoding::decode(param_name) : param_name, "");
534
95
    }
535

            
536
1585
    start = end + 1;
537
1585
  }
538

            
539
894
  return params;
540
894
}
541

            
542
32
void Utility::QueryParamsMulti::remove(absl::string_view key) { this->data_.erase(key); }
543

            
544
2121
void Utility::QueryParamsMulti::add(absl::string_view key, absl::string_view value) {
545
2121
  auto result = this->data_.emplace(std::string(key), std::vector<std::string>{std::string(value)});
546
2121
  if (!result.second) {
547
27
    result.first->second.push_back(std::string(value));
548
27
  }
549
2121
}
550

            
551
637
void Utility::QueryParamsMulti::overwrite(absl::string_view key, absl::string_view value) {
552
637
  this->data_[key] = std::vector<std::string>{std::string(value)};
553
637
}
554

            
555
2409
absl::optional<std::string> Utility::QueryParamsMulti::getFirstValue(absl::string_view key) const {
556
2409
  auto it = this->data_.find(key);
557
2409
  if (it == this->data_.end()) {
558
1775
    return std::nullopt;
559
1775
  }
560

            
561
634
  return absl::optional<std::string>{it->second.at(0)};
562
2409
}
563

            
564
5
absl::string_view Utility::findQueryStringStart(const HeaderString& path) {
565
5
  absl::string_view path_str = path.getStringView();
566
5
  size_t query_offset = path_str.find('?');
567
5
  if (query_offset == absl::string_view::npos) {
568
4
    query_offset = path_str.length();
569
4
  }
570
5
  path_str.remove_prefix(query_offset);
571
5
  return path_str;
572
5
}
573

            
574
233
std::string Utility::stripQueryString(const HeaderString& path) {
575
233
  absl::string_view path_str = path.getStringView();
576
233
  size_t query_offset = path_str.find('?');
577
233
  return {path_str.data(), query_offset != path_str.npos ? query_offset : path_str.size()};
578
233
}
579

            
580
207
std::string Utility::QueryParamsMulti::replaceQueryString(const HeaderString& path) const {
581
207
  std::string new_path{Http::Utility::stripQueryString(path)};
582

            
583
207
  if (!this->data_.empty()) {
584
193
    absl::StrAppend(&new_path, this->toString());
585
193
  }
586

            
587
207
  return new_path;
588
207
}
589

            
590
1077
std::string Utility::parseCookieValue(const HeaderMap& headers, const std::string& key) {
591
  // TODO(wbpcode): Modify the headers parameter type to 'RequestHeaderMap'.
592
1077
  return parseCookie(headers, key, Http::Headers::get().Cookie);
593
1077
}
594

            
595
131
std::string Utility::parseSetCookieValue(const Http::HeaderMap& headers, const std::string& key) {
596
131
  return parseCookie(headers, key, Http::Headers::get().SetCookie);
597
131
}
598

            
599
std::string Utility::makeSetCookieValue(absl::string_view name, absl::string_view value,
600
                                        absl::string_view path, std::chrono::seconds max_age,
601
                                        bool httponly,
602
483
                                        absl::Span<const CookieAttribute> attributes) {
603
483
  std::string cookie_value;
604
  // Best effort attempt to avoid numerous string copies.
605
483
  cookie_value.reserve(value.size() + path.size() + 30);
606

            
607
483
  cookie_value = absl::StrCat(name, "=\"", value, "\"");
608
483
  if (max_age != std::chrono::seconds::zero()) {
609
324
    absl::StrAppend(&cookie_value, "; Max-Age=", max_age.count());
610
324
  }
611
483
  if (!path.empty()) {
612
22
    absl::StrAppend(&cookie_value, "; Path=", path);
613
22
  }
614

            
615
633
  for (auto const& attribute : attributes) {
616
306
    if (attribute.value_.empty()) {
617
3
      absl::StrAppend(&cookie_value, "; ", attribute.name_);
618
304
    } else {
619
303
      absl::StrAppend(&cookie_value, "; ", attribute.name_, "=", attribute.value_);
620
303
    }
621
306
  }
622

            
623
483
  if (httponly) {
624
479
    absl::StrAppend(&cookie_value, "; HttpOnly");
625
479
  }
626
483
  return cookie_value;
627
483
}
628

            
629
4
void Utility::removeCookieValue(HeaderMap& headers, const std::string& key) {
630
4
  const LowerCaseString& cookie_header = Http::Headers::get().Cookie;
631
4
  std::vector<std::string> new_cookies;
632

            
633
4
  forEachCookie(headers, cookie_header,
634
11
                [&new_cookies, &key](absl::string_view k, absl::string_view v) -> bool {
635
11
                  if (key != k) {
636
7
                    new_cookies.emplace_back(fmt::format("{}={}", k, v));
637
7
                  }
638

            
639
                  // continue iterating until all cookies are processed.
640
11
                  return true;
641
11
                });
642

            
643
  // Remove the existing Cookie header
644
4
  headers.remove(cookie_header);
645
4
  if (!new_cookies.empty()) {
646
2
    headers.setReferenceKey(cookie_header, absl::StrJoin(new_cookies, "; "));
647
2
  }
648
4
}
649

            
650
185302
uint64_t Utility::getResponseStatus(const ResponseHeaderMap& headers) {
651
185302
  auto status = Utility::getResponseStatusOrNullopt(headers);
652
185302
  if (!status.has_value()) {
653
1
    IS_ENVOY_BUG("No status in headers");
654
1
    return 0;
655
1
  }
656
185301
  return status.value();
657
185302
}
658

            
659
276095
absl::optional<uint64_t> Utility::getResponseStatusOrNullopt(const ResponseHeaderMap& headers) {
660
276095
  const HeaderEntry* header = headers.Status();
661
276095
  uint64_t response_code;
662
276095
  if (!header || !absl::SimpleAtoi(headers.getStatusValue(), &response_code)) {
663
42
    return absl::nullopt;
664
42
  }
665
276053
  return response_code;
666
276095
}
667

            
668
501654
bool Utility::isUpgrade(const RequestOrResponseHeaderMap& headers) {
669
  // In firefox the "Connection" request header value is "keep-alive, Upgrade",
670
  // we should check if it contains the "Upgrade" token.
671
501654
  return (headers.Upgrade() &&
672
501654
          Envoy::StringUtil::caseFindToken(headers.getConnectionValue(), ",",
673
1728
                                           Http::Headers::get().ConnectionValues.Upgrade));
674
501654
}
675

            
676
98608
bool Utility::isH2UpgradeRequest(const RequestHeaderMap& headers) {
677
98608
  return headers.getMethodValue() == Http::Headers::get().MethodValues.Connect &&
678
98608
         headers.Protocol() && !headers.Protocol()->value().empty() &&
679
98608
         headers.Protocol()->value() != Headers::get().ProtocolValues.Bytestream;
680
98608
}
681

            
682
3305
bool Utility::isH3UpgradeRequest(const RequestHeaderMap& headers) {
683
3305
  return isH2UpgradeRequest(headers);
684
3305
}
685

            
686
46883
bool Utility::isWebSocketUpgradeRequest(const RequestHeaderMap& headers) {
687
46883
  return (isUpgrade(headers) &&
688
46883
          absl::EqualsIgnoreCase(headers.getUpgradeValue(),
689
175
                                 Http::Headers::get().UpgradeValues.WebSocket));
690
46883
}
691

            
692
void Utility::removeUpgrade(RequestOrResponseHeaderMap& headers,
693
11
                            const std::vector<Matchers::StringMatcherPtr>& matchers) {
694
11
  if (headers.Upgrade()) {
695
11
    std::vector<absl::string_view> tokens =
696
11
        Envoy::StringUtil::splitToken(headers.getUpgradeValue(), ",", false, true);
697

            
698
17
    auto end = std::remove_if(tokens.begin(), tokens.end(), [&](absl::string_view token) {
699
17
      return std::any_of(
700
17
          matchers.begin(), matchers.end(),
701
21
          [&token](const Matchers::StringMatcherPtr& matcher) { return matcher->match(token); });
702
17
    });
703

            
704
11
    const std::string new_value = absl::StrJoin(tokens.begin(), end, ",");
705

            
706
11
    if (new_value.empty()) {
707
7
      headers.removeUpgrade();
708
7
    } else {
709
4
      headers.setUpgrade(new_value);
710
4
    }
711
11
  }
712
11
}
713

            
714
void Utility::removeConnectionUpgrade(RequestOrResponseHeaderMap& headers,
715
8
                                      const StringUtil::CaseUnorderedSet& tokens_to_remove) {
716
8
  if (headers.Connection()) {
717
8
    const std::string new_value =
718
8
        StringUtil::removeTokens(headers.getConnectionValue(), ",", tokens_to_remove, ",");
719
8
    if (new_value.empty()) {
720
5
      headers.removeConnection();
721
5
    } else {
722
3
      headers.setConnection(new_value);
723
3
    }
724
8
  }
725
8
}
726

            
727
Utility::PreparedLocalReplyPtr Utility::prepareLocalReply(const EncodeFunctions& encode_functions,
728
33204
                                                          const LocalReplyData& local_reply_data) {
729
33204
  Code response_code = local_reply_data.response_code_;
730
33204
  std::string body_text(local_reply_data.body_text_);
731
33204
  absl::string_view content_type(Headers::get().ContentTypeValues.Text);
732

            
733
33204
  ResponseHeaderMapPtr response_headers{createHeaderMap<ResponseHeaderMapImpl>(
734
33204
      {{Headers::get().Status, std::to_string(enumToInt(response_code))}})};
735

            
736
33204
  if (encode_functions.modify_headers_) {
737
10225
    encode_functions.modify_headers_(*response_headers);
738
10225
  }
739
33204
  bool has_custom_content_type = false;
740
33204
  if (encode_functions.rewrite_) {
741
10224
    std::string content_type_value = std::string(response_headers->getContentTypeValue());
742
10224
    encode_functions.rewrite_(*response_headers, response_code, body_text, content_type);
743
10224
    has_custom_content_type = (content_type_value != response_headers->getContentTypeValue());
744
10224
  }
745

            
746
33204
  if (local_reply_data.is_grpc_) {
747
398
    response_headers->setStatus(std::to_string(enumToInt(Code::OK)));
748
398
    response_headers->setReferenceContentType(Headers::get().ContentTypeValues.Grpc);
749

            
750
398
    if (response_headers->getGrpcStatusValue().empty()) {
751
397
      response_headers->setGrpcStatus(std::to_string(
752
397
          enumToInt(local_reply_data.grpc_status_
753
397
                        ? local_reply_data.grpc_status_.value()
754
397
                        : Grpc::Utility::httpToGrpcStatus(enumToInt(response_code)))));
755
397
    }
756

            
757
398
    if (!body_text.empty() && !local_reply_data.is_head_request_) {
758
      // TODO(dio): Probably it is worth to consider caching the encoded message based on gRPC
759
      // status.
760
      // JsonFormatter adds a '\n' at the end. For header value, it should be removed.
761
      // https://github.com/envoyproxy/envoy/blob/main/source/common/formatter/substitution_formatter.cc#L129
762
386
      if (content_type == Headers::get().ContentTypeValues.Json &&
763
386
          body_text[body_text.length() - 1] == '\n') {
764
11
        body_text = body_text.substr(0, body_text.length() - 1);
765
11
      }
766
386
      response_headers->setGrpcMessage(PercentEncoding::encode(body_text));
767
386
    }
768
    // The `modify_headers` function may have added content-length, remove it.
769
398
    response_headers->removeContentLength();
770

            
771
    // TODO(kbaichoo): In C++20 we will be able to use make_unique with
772
    // aggregate initialization.
773
    // NOLINTNEXTLINE(modernize-make-unique)
774
398
    return PreparedLocalReplyPtr(new PreparedLocalReply{
775
398
        local_reply_data.is_grpc_, local_reply_data.is_head_request_, std::move(response_headers),
776
398
        std::move(body_text), encode_functions.encode_headers_, encode_functions.encode_data_});
777
398
  }
778

            
779
32806
  if (!body_text.empty()) {
780
4113
    response_headers->setContentLength(body_text.size());
781
    // If the content-type is not set, set it.
782
    // Alternately if the `rewrite` function has changed body_text and the config didn't explicitly
783
    // set a content type header, set the content type to be based on the changed body.
784
4113
    if (response_headers->ContentType() == nullptr ||
785
4113
        (body_text != local_reply_data.body_text_ && !has_custom_content_type)) {
786
4090
      response_headers->setReferenceContentType(content_type);
787
4090
    }
788
32235
  } else {
789
28693
    response_headers->removeContentLength();
790
28693
    response_headers->removeContentType();
791
28693
  }
792

            
793
  // TODO(kbaichoo): In C++20 we will be able to use make_unique with
794
  // aggregate initialization.
795
  // NOLINTNEXTLINE(modernize-make-unique)
796
32806
  return PreparedLocalReplyPtr(new PreparedLocalReply{
797
32806
      local_reply_data.is_grpc_, local_reply_data.is_head_request_, std::move(response_headers),
798
32806
      std::move(body_text), encode_functions.encode_headers_, encode_functions.encode_data_});
799
33204
}
800

            
801
33204
void Utility::encodeLocalReply(const bool& is_reset, PreparedLocalReplyPtr prepared_local_reply) {
802
33204
  ASSERT(prepared_local_reply != nullptr);
803
33204
  ResponseHeaderMapPtr response_headers{std::move(prepared_local_reply->response_headers_)};
804

            
805
33204
  if (prepared_local_reply->is_grpc_request_) {
806
    // Trailers only response
807
398
    prepared_local_reply->encode_headers_(std::move(response_headers), true);
808
398
    return;
809
398
  }
810

            
811
32806
  if (prepared_local_reply->is_head_request_) {
812
32
    prepared_local_reply->encode_headers_(std::move(response_headers), true);
813
32
    return;
814
32
  }
815

            
816
32774
  const bool bodyless_response = prepared_local_reply->response_body_.empty();
817
32774
  prepared_local_reply->encode_headers_(std::move(response_headers), bodyless_response);
818
  // encode_headers() may have changed the referenced is_reset so we need to test it
819
32774
  if (!bodyless_response && !is_reset) {
820
4080
    Buffer::OwnedImpl buffer(prepared_local_reply->response_body_);
821
4080
    prepared_local_reply->encode_data_(buffer, true);
822
4080
  }
823
32774
}
824

            
825
void Utility::sendLocalReply(const bool& is_reset, const EncodeFunctions& encode_functions,
826
27825
                             const LocalReplyData& local_reply_data) {
827
  // encode_headers() may reset the stream, so the stream must not be reset before calling it.
828
27825
  ASSERT(!is_reset);
829
27825
  PreparedLocalReplyPtr prepared_local_reply =
830
27825
      prepareLocalReply(encode_functions, local_reply_data);
831

            
832
27825
  encodeLocalReply(is_reset, std::move(prepared_local_reply));
833
27825
}
834

            
835
bool Utility::remoteAddressIsTrustedProxy(
836
    const Envoy::Network::Address::Instance& remote,
837
13
    absl::Span<const Network::Address::CidrRange> trusted_cidrs) {
838
17
  for (const auto& cidr : trusted_cidrs) {
839
17
    if (cidr.isInRange(remote)) {
840
11
      return true;
841
11
    }
842
17
  }
843
2
  return false;
844
13
}
845

            
846
Utility::GetLastAddressFromXffInfo Utility::getLastNonTrustedAddressFromXFF(
847
    const Http::RequestHeaderMap& request_headers,
848
11
    absl::Span<const Network::Address::CidrRange> trusted_cidrs) {
849
11
  const auto xff_header = request_headers.getForwardedForValue();
850
11
  static constexpr size_t MAX_ALLOWED_XFF_ENTRIES = 20;
851

            
852
11
  absl::InlinedVector<absl::string_view, 3> xff_entries = absl::StrSplit(xff_header, ',');
853
  // If there are more than 20 entries in the XFF header, refuse to parse.
854
  // There are very few valid use cases for this many entries and parsing too many has
855
  // a performance impact.
856
11
  if (xff_entries.size() > MAX_ALLOWED_XFF_ENTRIES) {
857
1
    ENVOY_LOG_MISC(trace, "Too many entries in x-forwarded-for header");
858
1
    return {nullptr, false};
859
1
  }
860

            
861
10
  Network::Address::InstanceConstSharedPtr last_valid_addr;
862

            
863
23
  for (auto it = xff_entries.rbegin(); it != xff_entries.rend(); it++) {
864
20
    const absl::string_view entry = StringUtil::trim(*it);
865
20
    auto addr = Network::Utility::parseInternetAddressNoThrow(std::string(entry));
866
20
    if (addr == nullptr) {
867
2
      return {nullptr, false};
868
2
    }
869
18
    last_valid_addr = addr;
870

            
871
18
    bool remote_address_is_trusted_proxy = false;
872
29
    for (const auto& cidr : trusted_cidrs) {
873
29
      if (cidr.isInRange(*addr.get())) {
874
13
        remote_address_is_trusted_proxy = true;
875
13
        break;
876
13
      }
877
29
    }
878

            
879
18
    if (remote_address_is_trusted_proxy) {
880
13
      continue;
881
13
    }
882

            
883
    // If we reach here we found a non-trusted address
884
5
    return {addr, xff_entries.size() == 1};
885
18
  }
886
  // If we reach this point all addresses in XFF were considered trusted, so return
887
  // first IP in XFF (the last in the reverse-evaluated chain).
888
3
  return {last_valid_addr, xff_entries.size() == 1};
889
10
}
890

            
891
Utility::GetLastAddressFromXffInfo
892
Utility::getLastAddressFromXFF(const Http::RequestHeaderMap& request_headers,
893
89391
                               uint32_t num_to_skip) {
894
89391
  const auto xff_header = request_headers.ForwardedFor();
895
89391
  if (xff_header == nullptr) {
896
87934
    return {nullptr, false};
897
87934
  }
898

            
899
1457
  absl::string_view xff_string(xff_header->value().getStringView());
900
1457
  static constexpr absl::string_view separator(",");
901
  // Ignore the last num_to_skip addresses at the end of XFF.
902
1491
  for (uint32_t i = 0; i < num_to_skip; i++) {
903
38
    const absl::string_view::size_type last_comma = xff_string.rfind(separator);
904
38
    if (last_comma == absl::string_view::npos) {
905
4
      return {nullptr, false};
906
4
    }
907
34
    xff_string = xff_string.substr(0, last_comma);
908
34
  }
909
  // The text after the last remaining comma, or the entirety of the string if there
910
  // is no comma, is the requested IP address.
911
1453
  const absl::string_view::size_type last_comma = xff_string.rfind(separator);
912
1453
  if (last_comma != absl::string_view::npos && last_comma + separator.size() < xff_string.size()) {
913
24
    xff_string = xff_string.substr(last_comma + separator.size());
914
24
  }
915

            
916
  // Ignore the whitespace, since they are allowed in HTTP lists (see RFC7239#section-7.1).
917
1453
  xff_string = StringUtil::ltrim(xff_string);
918
1453
  xff_string = StringUtil::rtrim(xff_string);
919

            
920
  // This technically requires a copy because inet_pton takes a null terminated string. In
921
  // practice, we are working with a view at the end of the owning string, and could pass the
922
  // raw pointer.
923
  // TODO(mattklein123) PERF: Avoid the copy here.
924
1453
  Network::Address::InstanceConstSharedPtr address =
925
1453
      Network::Utility::parseInternetAddressNoThrow(std::string(xff_string));
926
1453
  if (address != nullptr) {
927
1447
    return {address, last_comma == absl::string_view::npos && num_to_skip == 0};
928
1447
  }
929
6
  return {nullptr, false};
930
1453
}
931

            
932
31
bool Utility::sanitizeConnectionHeader(Http::RequestHeaderMap& headers) {
933
31
  static constexpr size_t MAX_ALLOWED_NOMINATED_HEADERS = 10;
934
31
  static constexpr size_t MAX_ALLOWED_TE_VALUE_SIZE = 256;
935

            
936
  // Remove any headers nominated by the Connection header. The TE header
937
  // is sanitized and removed only if it's empty after removing unsupported values
938
  // See https://github.com/envoyproxy/envoy/issues/8623
939
31
  const auto& cv = Http::Headers::get().ConnectionValues;
940
31
  const auto& connection_header_value = headers.Connection()->value();
941

            
942
31
  StringUtil::CaseUnorderedSet headers_to_remove{};
943
31
  std::vector<absl::string_view> connection_header_tokens =
944
31
      StringUtil::splitToken(connection_header_value.getStringView(), ",", false);
945

            
946
  // If we have 10 or more nominated headers, fail this request
947
31
  if (connection_header_tokens.size() >= MAX_ALLOWED_NOMINATED_HEADERS) {
948
2
    ENVOY_LOG_MISC(trace, "Too many nominated headers in request");
949
2
    return false;
950
2
  }
951

            
952
  // Split the connection header and evaluate each nominated header
953
52
  for (const auto& token : connection_header_tokens) {
954

            
955
52
    const auto token_sv = StringUtil::trim(token);
956

            
957
    // Build the LowerCaseString for header lookup
958
52
    const LowerCaseString lcs_header_to_remove{std::string(token_sv)};
959

            
960
    // If the Connection token value is not a nominated header, ignore it here since
961
    // the connection header is removed elsewhere when the H1 request is upgraded to H2
962
52
    if ((lcs_header_to_remove.get() == cv.Close) ||
963
52
        (lcs_header_to_remove.get() == cv.Http2Settings) ||
964
52
        (lcs_header_to_remove.get() == cv.KeepAlive) ||
965
52
        (lcs_header_to_remove.get() == cv.Upgrade)) {
966
24
      continue;
967
24
    }
968

            
969
    // By default we will remove any nominated headers
970
28
    bool keep_header = false;
971

            
972
    // Determine whether the nominated header contains invalid values
973
28
    HeaderMap::GetResult nominated_header;
974

            
975
28
    if (lcs_header_to_remove == Http::Headers::get().Connection) {
976
      // Remove the connection header from the nominated tokens if it's self nominated
977
      // The connection header itself is *not removed*
978
2
      ENVOY_LOG_MISC(trace, "Skipping self nominated header [{}]", token_sv);
979
2
      keep_header = true;
980
2
      headers_to_remove.emplace(token_sv);
981

            
982
26
    } else if ((lcs_header_to_remove == Http::Headers::get().ForwardedFor) ||
983
26
               (lcs_header_to_remove == Http::Headers::get().ForwardedHost) ||
984
26
               (lcs_header_to_remove == Http::Headers::get().ForwardedProto) ||
985
26
               !token_sv.find(':')) {
986

            
987
      // An attacker could nominate an X-Forwarded* header, and its removal may mask
988
      // the origin of the incoming request and potentially alter its handling.
989
      // Additionally, pseudo headers should never be nominated. In both cases, we
990
      // should fail the request.
991
      // See: https://nathandavison.com/blog/abusing-http-hop-by-hop-request-headers
992

            
993
4
      ENVOY_LOG_MISC(trace, "Invalid nomination of {} header", token_sv);
994
4
      return false;
995
22
    } else {
996
      // Examine the value of all other nominated headers
997
22
      nominated_header = headers.get(lcs_header_to_remove);
998
22
    }
999

            
24
    if (!nominated_header.empty()) {
      // NOTE: The TE header is an inline header, so by definition if we operate on it there can
      // only be a single value. In all other cases we remove the nominated header.
18
      auto nominated_header_value_sv = nominated_header[0]->value().getStringView();
18
      const bool is_te_header = (lcs_header_to_remove == Http::Headers::get().TE);
      // reject the request if the TE header is too large
18
      if (is_te_header && (nominated_header_value_sv.size() >= MAX_ALLOWED_TE_VALUE_SIZE)) {
1
        ENVOY_LOG_MISC(trace, "TE header contains a value that exceeds the allowable length");
1
        return false;
1
      }
17
      if (is_te_header) {
9
        ASSERT(nominated_header.size() == 1);
9
        for (const auto& header_value :
10
             StringUtil::splitToken(nominated_header_value_sv, ",", false)) {
10
          const absl::string_view header_sv = StringUtil::trim(header_value);
          // If trailers exist in the TE value tokens, keep the header, removing any other values
          // that may exist
10
          if (StringUtil::CaseInsensitiveCompare()(header_sv,
10
                                                   Http::Headers::get().TEValues.Trailers)) {
7
            keep_header = true;
7
            break;
7
          }
10
        }
9
        if (keep_header) {
7
          headers.setTE(Http::Headers::get().TEValues.Trailers);
7
        }
9
      }
17
    }
23
    if (!keep_header) {
14
      ENVOY_LOG_MISC(trace, "Removing nominated header [{}]", token_sv);
14
      headers.remove(lcs_header_to_remove);
14
      headers_to_remove.emplace(token_sv);
14
    }
23
  }
  // Lastly remove extra nominated headers from the Connection header
24
  if (!headers_to_remove.empty()) {
6
    const std::string new_value = StringUtil::removeTokens(connection_header_value.getStringView(),
6
                                                           ",", headers_to_remove, ",");
6
    if (new_value.empty()) {
      headers.removeConnection();
6
    } else {
6
      headers.setConnection(new_value);
6
    }
6
  }
24
  return true;
29
}
73104
const std::string& Utility::getProtocolString(const Protocol protocol) {
73104
  switch (protocol) {
50
  case Protocol::Http10:
50
    return Headers::get().ProtocolStrings.Http10String;
13652
  case Protocol::Http11:
13652
    return Headers::get().ProtocolStrings.Http11String;
57219
  case Protocol::Http2:
57219
    return Headers::get().ProtocolStrings.Http2String;
2183
  case Protocol::Http3:
2183
    return Headers::get().ProtocolStrings.Http3String;
73104
  }
  return EMPTY_STRING;
73104
}
std::string Utility::buildOriginalUri(const Http::RequestHeaderMap& request_headers,
213
                                      const absl::optional<uint32_t> max_path_length) {
213
  if (!request_headers.Path()) {
10
    return "";
10
  }
203
  absl::string_view path(request_headers.EnvoyOriginalPath()
203
                             ? request_headers.getEnvoyOriginalPathValue()
203
                             : request_headers.getPathValue());
203
  if (max_path_length.has_value() && path.length() > max_path_length) {
3
    path = path.substr(0, max_path_length.value());
3
  }
203
  return absl::StrCat(request_headers.getSchemeValue(), "://", request_headers.getHostValue(),
203
                      path);
213
}
void Utility::extractSchemeHostPathFromUri(const absl::string_view& uri, absl::string_view& scheme,
1208
                                           absl::string_view& host, absl::string_view& path) {
  /**
   *  URI RFC: https://www.ietf.org/rfc/rfc2396.txt
   *
   *  Example:
   *  uri  = "https://example.com:8443/certs"
   *  pos:         ^
   *  host_pos:       ^
   *  path_pos:                       ^
   *  scheme = "https"
   *  host = "example.com:8443"
   *  path = "/certs"
   */
  // Find end of scheme.
1208
  const auto pos = uri.find("://");
1208
  scheme = uri.substr(0, (pos == std::string::npos) ? 0 : pos);
  // Start position of the host.
1208
  const auto host_pos = (pos == std::string::npos) ? 0 : pos + 3;
  // Start position of the path.
1208
  const auto path_pos = uri.find('/', host_pos);
1208
  if (path_pos == std::string::npos) {
    // If uri doesn't have "/", the whole string is treated as host.
182
    host = uri.substr(host_pos);
182
    path = "/";
1166
  } else {
1026
    host = uri.substr(host_pos, path_pos - host_pos);
1026
    path = uri.substr(path_pos);
1026
  }
1208
}
void Utility::extractHostPathFromUri(const absl::string_view& uri, absl::string_view& host,
941
                                     absl::string_view& path) {
941
  absl::string_view scheme;
941
  extractSchemeHostPathFromUri(uri, scheme, host, path);
941
}
7
std::string Utility::localPathFromFilePath(const absl::string_view& file_path) {
7
  if (file_path.size() >= 3 && file_path[1] == ':' && file_path[2] == '/' &&
7
      std::isalpha(file_path[0])) {
2
    return std::string(file_path);
2
  }
5
  return absl::StrCat("/", file_path);
7
}
RequestMessagePtr Utility::prepareHeaders(const envoy::config::core::v3::HttpUri& http_uri,
260
                                          bool include_scheme) {
260
  absl::string_view scheme, host, path;
260
  extractSchemeHostPathFromUri(http_uri.uri(), scheme, host, path);
260
  RequestMessagePtr message(new RequestMessageImpl());
260
  if (include_scheme && !scheme.empty()) {
85
    message->headers().setScheme(scheme);
85
  }
260
  message->headers().setPath(path);
260
  message->headers().setHost(host);
260
  return message;
260
}
206
std::string Utility::QueryParamsMulti::toString() const {
206
  std::string out;
206
  std::string delim = "?";
1100
  for (const auto& p : this->data_) {
1112
    for (const auto& v : p.second) {
1112
      absl::StrAppend(&out, delim, p.first, "=", v);
1112
      delim = "&";
1112
    }
1100
  }
206
  return out;
206
}
5172
const std::string Utility::resetReasonToString(const Http::StreamResetReason reset_reason) {
5172
  switch (reset_reason) {
91
  case Http::StreamResetReason::LocalConnectionFailure:
91
    return "local connection failure";
358
  case Http::StreamResetReason::RemoteConnectionFailure:
358
    return "remote connection failure";
3
  case Http::StreamResetReason::ConnectionTimeout:
3
    return "connection timeout";
3035
  case Http::StreamResetReason::ConnectionTermination:
3035
    return "connection termination";
40
  case Http::StreamResetReason::LocalReset:
40
    return "local reset";
1
  case Http::StreamResetReason::LocalRefusedStreamReset:
1
    return "local refused stream reset";
69
  case Http::StreamResetReason::Overflow:
69
    return "overflow";
1200
  case Http::StreamResetReason::RemoteReset:
1200
    return "remote reset";
7
  case Http::StreamResetReason::RemoteRefusedStreamReset:
7
    return "remote refused stream reset";
1
  case Http::StreamResetReason::ConnectError:
1
    return "remote error with CONNECT request";
358
  case Http::StreamResetReason::ProtocolError:
358
    return "protocol error";
1
  case Http::StreamResetReason::OverloadManager:
1
    return "overload manager reset";
8
  case Http::StreamResetReason::Http1PrematureUpstreamHalfClose:
8
    return "HTTP/1 premature upstream half close";
5172
  }
  return "";
5172
}
337
void Utility::transformUpgradeRequestFromH1toH2(RequestHeaderMap& headers) {
337
  ASSERT(Utility::isUpgrade(headers));
337
  headers.setReferenceMethod(Http::Headers::get().MethodValues.Connect);
337
  headers.setProtocol(headers.getUpgradeValue());
337
  headers.removeUpgrade();
337
  headers.removeConnection();
  // nghttp2 rejects upgrade requests/responses with content length, so strip
  // any unnecessary content length header.
337
  if (headers.getContentLengthValue() == "0") {
93
    headers.removeContentLength();
93
  }
337
}
83
void Utility::transformUpgradeRequestFromH1toH3(RequestHeaderMap& headers) {
83
  transformUpgradeRequestFromH1toH2(headers);
83
}
93
void Utility::transformUpgradeResponseFromH1toH2(ResponseHeaderMap& headers) {
93
  if (getResponseStatus(headers) == 101) {
85
    headers.setStatus(200);
85
  }
93
  headers.removeUpgrade();
93
  headers.removeConnection();
93
  if (headers.getContentLengthValue() == "0") {
1
    headers.removeContentLength();
1
  }
93
}
35
void Utility::transformUpgradeResponseFromH1toH3(ResponseHeaderMap& headers) {
35
  transformUpgradeResponseFromH1toH2(headers);
35
}
276
void Utility::transformUpgradeRequestFromH2toH1(RequestHeaderMap& headers) {
276
  ASSERT(Utility::isH2UpgradeRequest(headers));
276
  headers.setReferenceMethod(Http::Headers::get().MethodValues.Get);
276
  headers.setUpgrade(headers.getProtocolValue());
276
  headers.setReferenceConnection(Http::Headers::get().ConnectionValues.Upgrade);
276
  headers.removeProtocol();
276
}
84
void Utility::transformUpgradeRequestFromH3toH1(RequestHeaderMap& headers) {
84
  transformUpgradeRequestFromH2toH1(headers);
84
}
void Utility::transformUpgradeResponseFromH2toH1(ResponseHeaderMap& headers,
246
                                                 absl::string_view upgrade) {
246
  if (getResponseStatus(headers) == 200) {
168
    headers.setUpgrade(upgrade);
168
    headers.setReferenceConnection(Http::Headers::get().ConnectionValues.Upgrade);
168
    headers.setStatus(101);
168
  }
246
}
void Utility::transformUpgradeResponseFromH3toH1(ResponseHeaderMap& headers,
74
                                                 absl::string_view upgrade) {
74
  transformUpgradeResponseFromH2toH1(headers, upgrade);
74
}
std::string Utility::PercentEncoding::encode(absl::string_view value,
3319
                                             absl::string_view reserved_chars) {
3319
  absl::flat_hash_set<char> reserved_char_set{reserved_chars.begin(), reserved_chars.end()};
653678
  for (size_t i = 0; i < value.size(); ++i) {
650429
    const char& ch = value[i];
    // The escaping characters are defined in
    // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses.
    //
    // We do checking for each char in the string. If the current char is included in the defined
    // escaping characters, we jump to "the slow path" (append the char [encoded or not encoded]
    // to the returned string one by one) started from the current index.
650429
    if (ch < ' ' || ch >= '~' || reserved_char_set.find(ch) != reserved_char_set.end()) {
70
      return PercentEncoding::encode(value, i, reserved_char_set);
70
    }
650429
  }
3249
  return std::string(value);
3319
}
std::string Utility::PercentEncoding::encode(absl::string_view value, const size_t index,
70
                                             const absl::flat_hash_set<char>& reserved_char_set) {
70
  std::string encoded;
70
  if (index > 0) {
59
    absl::StrAppend(&encoded, value.substr(0, index));
59
  }
880
  for (size_t i = index; i < value.size(); ++i) {
810
    const char& ch = value[i];
810
    if (ch < ' ' || ch >= '~' || reserved_char_set.find(ch) != reserved_char_set.end()) {
      // For consistency, URI producers should use uppercase hexadecimal digits for all
      // percent-encodings. https://tools.ietf.org/html/rfc3986#section-2.1.
223
      absl::StrAppend(&encoded, fmt::format("%{:02X}", static_cast<const unsigned char&>(ch)));
658
    } else {
587
      encoded.push_back(ch);
587
    }
810
  }
70
  return encoded;
70
}
21656
std::string Utility::PercentEncoding::decode(absl::string_view encoded) {
21656
  std::string decoded;
21656
  decoded.reserve(encoded.size());
114882
  for (size_t i = 0; i < encoded.size(); ++i) {
93226
    char ch = encoded[i];
93226
    if (ch == '%' && i + 2 < encoded.size()) {
18372
      const char& hi = encoded[i + 1];
18372
      const char& lo = encoded[i + 2];
18372
      if (absl::ascii_isxdigit(hi) && absl::ascii_isxdigit(lo)) {
1660
        if (absl::ascii_isdigit(hi)) {
1124
          ch = hi - '0';
1215
        } else {
536
          ch = absl::ascii_toupper(hi) - 'A' + 10;
536
        }
1660
        ch *= 16;
1660
        if (absl::ascii_isdigit(lo)) {
592
          ch += lo - '0';
1080
        } else {
1068
          ch += absl::ascii_toupper(lo) - 'A' + 10;
1068
        }
1660
        i += 2;
1660
      }
18372
    }
93226
    decoded.push_back(ch);
93226
  }
21656
  return decoded;
21656
}
namespace {
// %-encode all ASCII character codepoints, EXCEPT:
// ALPHA | DIGIT | * | - | . | _
// SPACE is encoded as %20, NOT as the + character
constexpr std::array<uint32_t, 8> kUrlEncodedCharTable = {
    // control characters
    0b11111111111111111111111111111111,
    // !"#$%&'()*+,-./0123456789:;<=>?
    0b11111111110110010000000000111111,
    //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
    0b10000000000000000000000000011110,
    //`abcdefghijklmnopqrstuvwxyz{|}~
    0b10000000000000000000000000011111,
    // extended ascii
    0b11111111111111111111111111111111,
    0b11111111111111111111111111111111,
    0b11111111111111111111111111111111,
    0b11111111111111111111111111111111,
};
constexpr std::array<uint32_t, 8> kUrlDecodedCharTable = {
    // control characters
    0b00000000000000000000000000000000,
    // !"#$%&'()*+,-./0123456789:;<=>?
    0b01011111111111111111111111110101,
    //@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
    0b11111111111111111111111111110101,
    //`abcdefghijklmnopqrstuvwxyz{|}~
    0b11111111111111111111111111100010,
    // extended ascii
    0b00000000000000000000000000000000,
    0b00000000000000000000000000000000,
    0b00000000000000000000000000000000,
    0b00000000000000000000000000000000,
};
24245
bool shouldPercentEncodeChar(char c) { return testCharInTable(kUrlEncodedCharTable, c); }
283
bool shouldPercentDecodeChar(char c) { return testCharInTable(kUrlDecodedCharTable, c); }
} // namespace
725
std::string Utility::PercentEncoding::urlEncode(absl::string_view value) {
725
  std::string encoded;
725
  encoded.reserve(value.size());
24236
  for (char ch : value) {
24236
    if (shouldPercentEncodeChar(ch)) {
      // For consistency, URI producers should use uppercase hexadecimal digits for all
      // percent-encodings. https://tools.ietf.org/html/rfc3986#section-2.1.
3311
      absl::StrAppend(&encoded, fmt::format("%{:02X}", static_cast<const unsigned char&>(ch)));
21049
    } else {
20925
      encoded.push_back(ch);
20925
    }
24236
  }
725
  return encoded;
725
}
4
bool Utility::PercentEncoding::queryParameterIsUrlEncoded(absl::string_view value) {
9
  for (char ch : value) {
9
    if (shouldPercentEncodeChar(ch)) {
3
      return false;
3
    }
9
  }
1
  return true;
4
}
14
std::string Utility::PercentEncoding::urlDecodeQueryParameter(absl::string_view encoded) {
14
  std::string decoded;
14
  decoded.reserve(encoded.size());
597
  for (size_t i = 0; i < encoded.size(); ++i) {
583
    char ch = encoded[i];
583
    if (ch == '%' && i + 2 < encoded.size()) {
283
      const char& hi = encoded[i + 1];
283
      const char& lo = encoded[i + 2];
283
      if (absl::ascii_isxdigit(hi) && absl::ascii_isxdigit(lo)) {
283
        if (absl::ascii_isdigit(hi)) {
187
          ch = hi - '0';
187
        } else {
96
          ch = absl::ascii_toupper(hi) - 'A' + 10;
96
        }
283
        ch *= 16;
283
        if (absl::ascii_isdigit(lo)) {
177
          ch += lo - '0';
177
        } else {
106
          ch += absl::ascii_toupper(lo) - 'A' + 10;
106
        }
283
        if (shouldPercentDecodeChar(ch)) {
          // Decode the character only if it is present in the characters_to_decode
99
          decoded.push_back(ch);
188
        } else {
          // Otherwise keep it as is.
184
          decoded.push_back('%');
184
          decoded.push_back(hi);
184
          decoded.push_back(lo);
184
        }
283
        i += 2;
283
      }
403
    } else {
300
      decoded.push_back(ch);
300
    }
583
  }
14
  return decoded;
14
}
2773
Utility::AuthorityAttributes Utility::parseAuthority(absl::string_view host) {
  // First try to see if there is a port included. This also checks to see that there is not a ']'
  // as the last character which is indicative of an IPv6 address without a port. This is a best
  // effort attempt.
2773
  const auto colon_pos = host.rfind(':');
2773
  absl::string_view host_to_resolve = host;
2773
  absl::optional<uint16_t> port;
2773
  if (colon_pos != absl::string_view::npos && host_to_resolve.back() != ']') {
512
    const absl::string_view string_view_host = host;
512
    host_to_resolve = string_view_host.substr(0, colon_pos);
512
    const auto port_str = string_view_host.substr(colon_pos + 1);
512
    uint64_t port64;
512
    if (port_str.empty() || !absl::SimpleAtoi(port_str, &port64) || port64 > 65535) {
      // Just attempt to resolve whatever we were given. This will very likely fail.
1
      host_to_resolve = host;
511
    } else {
511
      port = static_cast<uint16_t>(port64);
511
    }
512
  }
  // Now see if this is an IP address. We need to know this because some things (such as setting
  // SNI) are special cased if this is an IP address. Either way, we still go through the normal
  // resolver flow. We could short-circuit the DNS resolver in this case, but the extra code to do
  // so is not worth it since the DNS resolver should handle it for us.
2773
  bool is_ip_address = false;
2773
  absl::string_view potential_ip_address = host_to_resolve;
  // TODO(mattklein123): Optimally we would support bracket parsing in parseInternetAddress(),
  // but we still need to trim the brackets to send the IPv6 address into the DNS resolver. For
  // now, just do all the trimming here, but in the future we should consider whether we can
  // have unified [] handling as low as possible in the stack.
2773
  if (!potential_ip_address.empty() && potential_ip_address.front() == '[' &&
2773
      potential_ip_address.back() == ']') {
20
    potential_ip_address.remove_prefix(1);
20
    potential_ip_address.remove_suffix(1);
20
  }
2773
  if (Network::Utility::parseInternetAddressNoThrow(std::string(potential_ip_address)) != nullptr) {
142
    is_ip_address = true;
142
    host_to_resolve = potential_ip_address;
142
  }
2773
  return {is_ip_address, host_to_resolve, port};
2773
}
absl::Status
5
Utility::validateCoreRetryPolicy(const envoy::config::core::v3::RetryPolicy& retry_policy) {
5
  if (retry_policy.has_retry_back_off()) {
3
    const auto& core_back_off = retry_policy.retry_back_off();
3
    uint64_t base_interval_ms = PROTOBUF_GET_MS_REQUIRED(core_back_off, base_interval);
3
    uint64_t max_interval_ms =
3
        PROTOBUF_GET_MS_OR_DEFAULT(core_back_off, max_interval, base_interval_ms * 10);
3
    if (max_interval_ms < base_interval_ms) {
1
      return absl::InvalidArgumentError(
1
          "max_interval must be greater than or equal to the base_interval");
1
    }
3
  }
4
  return absl::OkStatus();
5
}
envoy::config::route::v3::RetryPolicy
Utility::convertCoreToRouteRetryPolicy(const envoy::config::core::v3::RetryPolicy& retry_policy,
63
                                       const std::string& retry_on) {
63
  envoy::config::route::v3::RetryPolicy route_retry_policy;
63
  constexpr uint64_t default_base_interval_ms = 1000;
63
  constexpr uint64_t default_max_interval_ms = 10 * default_base_interval_ms;
63
  uint64_t base_interval_ms = default_base_interval_ms;
63
  uint64_t max_interval_ms = default_max_interval_ms;
63
  if (retry_policy.has_retry_back_off()) {
35
    const auto& core_back_off = retry_policy.retry_back_off();
35
    base_interval_ms = PROTOBUF_GET_MS_REQUIRED(core_back_off, base_interval);
35
    max_interval_ms =
35
        PROTOBUF_GET_MS_OR_DEFAULT(core_back_off, max_interval, base_interval_ms * 10);
35
    if (max_interval_ms < base_interval_ms) {
      ENVOY_BUG(false, "max_interval must be greater than or equal to the base_interval");
      base_interval_ms = max_interval_ms / 2;
    }
35
  }
63
  route_retry_policy.mutable_num_retries()->set_value(
63
      PROTOBUF_GET_WRAPPED_OR_DEFAULT(retry_policy, num_retries, 1));
63
  auto* route_mutable_back_off = route_retry_policy.mutable_retry_back_off();
63
  route_mutable_back_off->mutable_base_interval()->CopyFrom(
63
      Protobuf::util::TimeUtil::MillisecondsToDuration(base_interval_ms));
63
  route_mutable_back_off->mutable_max_interval()->CopyFrom(
63
      Protobuf::util::TimeUtil::MillisecondsToDuration(max_interval_ms));
  // set all the other fields with appropriate values.
63
  if (!retry_on.empty()) {
33
    route_retry_policy.set_retry_on(retry_on);
61
  } else {
30
    route_retry_policy.set_retry_on(retry_policy.retry_on());
30
  }
63
  route_retry_policy.mutable_per_try_timeout()->CopyFrom(
63
      route_retry_policy.retry_back_off().max_interval());
63
  if (retry_policy.has_retry_priority()) {
    route_retry_policy.mutable_retry_priority()->set_name(retry_policy.retry_priority().name());
    route_retry_policy.mutable_retry_priority()->mutable_typed_config()->MergeFrom(
        retry_policy.retry_priority().typed_config());
  }
63
  if (!retry_policy.retry_host_predicate().empty()) {
1
    for (const auto& host_predicate : retry_policy.retry_host_predicate()) {
1
      auto* route_host_predicate = route_retry_policy.mutable_retry_host_predicate()->Add();
1
      route_host_predicate->set_name(host_predicate.name());
1
      route_host_predicate->mutable_typed_config()->MergeFrom(host_predicate.typed_config());
1
    }
1
  }
63
  route_retry_policy.set_host_selection_retry_max_attempts(
63
      retry_policy.host_selection_retry_max_attempts());
63
  return route_retry_policy;
63
}
48723
bool Utility::isSafeRequest(const Http::RequestHeaderMap& request_headers) {
48723
  absl::string_view method = request_headers.getMethodValue();
48723
  return method == Http::Headers::get().MethodValues.Get ||
48723
         method == Http::Headers::get().MethodValues.Head ||
48723
         method == Http::Headers::get().MethodValues.Options ||
48723
         method == Http::Headers::get().MethodValues.Trace;
48723
}
291
Http::Code Utility::maybeRequestTimeoutCode(bool remote_decode_complete) {
291
  return remote_decode_complete ? Http::Code::GatewayTimeout
                                // Http::Code::RequestTimeout is more expensive because HTTP1 client
                                // cannot use the connection any more.
291
                                : Http::Code::RequestTimeout;
291
}
64520
bool Utility::schemeIsValid(const absl::string_view scheme) {
64520
  return schemeIsHttp(scheme) || schemeIsHttps(scheme);
64520
}
65639
bool Utility::schemeIsHttp(const absl::string_view scheme) {
65639
  return absl::EqualsIgnoreCase(scheme, Headers::get().SchemeValues.Http);
65639
}
9181
bool Utility::schemeIsHttps(const absl::string_view scheme) {
9181
  return absl::EqualsIgnoreCase(scheme, Headers::get().SchemeValues.Https);
9181
}
std::string Utility::newUri(::Envoy::OptRef<const Utility::RedirectConfig> redirect_config,
210
                            const Http::RequestHeaderMap& headers) {
210
  absl::string_view final_scheme;
210
  absl::string_view final_host;
210
  absl::string_view final_port;
210
  absl::string_view final_path;
210
  if (redirect_config.has_value() && !redirect_config->scheme_redirect_.empty()) {
19
    final_scheme = redirect_config->scheme_redirect_;
191
  } else if (redirect_config.has_value() && redirect_config->https_redirect_) {
15
    final_scheme = Http::Headers::get().SchemeValues.Https;
176
  } else {
    // Serve the redirect URL based on the scheme of the original URL, not the
    // security of the underlying connection.
176
    final_scheme = headers.getSchemeValue();
176
  }
210
  if (redirect_config.has_value() && !redirect_config->port_redirect_.empty()) {
18
    final_port = redirect_config->port_redirect_;
192
  } else {
192
    final_port = "";
192
  }
210
  if (redirect_config.has_value() && !redirect_config->host_redirect_.empty()) {
35
    final_host = redirect_config->host_redirect_;
175
  } else {
175
    ASSERT(headers.Host());
175
    final_host = processRequestHost(headers, final_scheme, final_port);
175
  }
210
  std::string final_path_value;
210
  if (redirect_config.has_value() && !redirect_config->path_redirect_.empty()) {
    // The path_redirect query string, if any, takes precedence over the request's query string,
    // and it will not be stripped regardless of `strip_query`.
29
    if (redirect_config->path_redirect_has_query_) {
4
      final_path = redirect_config->path_redirect_;
25
    } else {
25
      const absl::string_view current_path = headers.getPathValue();
25
      const size_t path_end = current_path.find('?');
25
      const bool current_path_has_query = path_end != absl::string_view::npos;
25
      if (current_path_has_query) {
2
        final_path_value = redirect_config->path_redirect_;
2
        final_path_value.append(current_path.data() + path_end, current_path.length() - path_end);
2
        final_path = final_path_value;
23
      } else {
23
        final_path = redirect_config->path_redirect_;
23
      }
25
    }
181
  } else {
181
    final_path = headers.getPathValue();
181
  }
210
  if (!absl::StartsWith(final_path, "/")) {
2
    final_path_value = absl::StrCat("/", final_path);
2
    final_path = final_path_value;
2
  }
210
  if (redirect_config.has_value() && !redirect_config->path_redirect_has_query_ &&
210
      redirect_config->strip_query_) {
6
    const size_t path_end = final_path.find('?');
6
    if (path_end != absl::string_view::npos) {
5
      final_path = final_path.substr(0, path_end);
5
    }
6
  }
210
  return fmt::format("{}://{}{}{}", final_scheme, final_host, final_port, final_path);
210
}
23
bool Utility::isValidRefererValue(absl::string_view value) {
  // First, we try to parse it as an absolute URL and
  // ensure that there is no fragment or userinfo.
  // If that fails, we can just parse it as a relative reference
  // and expect no fragment
  // NOTE: "about:blank" is incorrectly rejected here, because
  // url.initialize uses http_parser_parse_url, which requires
  // a host to be present if there is a schema.
23
  Utility::Url url;
23
  if (url.initialize(value, false)) {
13
    return !(url.containsFragment() || url.containsUserinfo());
13
  }
10
  bool seen_slash = false;
95
  for (char c : value) {
95
    switch (c) {
2
    case ':':
2
      if (!seen_slash) {
        // First path segment cannot contain ':'
        // https://www.rfc-editor.org/rfc/rfc3986#section-3.3
2
        return false;
2
      }
      continue;
6
    case '/':
6
      seen_slash = true;
6
      continue;
87
    default:
87
      if (!testCharInTable(kUriQueryAndFragmentCharTable, c)) {
3
        return false;
3
      }
95
    }
95
  }
5
  return true;
10
}
} // namespace Http
} // namespace Envoy