Coverage Report

Created: 2024-09-19 09:45

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