Coverage Report

Created: 2023-11-12 09:30

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