Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/common/http/header_utility.h
Line
Count
Source (jump to first uncovered line)
1
#pragma once
2
3
#include <vector>
4
5
#include "envoy/common/matchers.h"
6
#include "envoy/common/regex.h"
7
#include "envoy/config/core/v3/protocol.pb.h"
8
#include "envoy/config/route/v3/route_components.pb.h"
9
#include "envoy/http/header_map.h"
10
#include "envoy/http/header_validator.h"
11
#include "envoy/http/protocol.h"
12
#include "envoy/type/v3/range.pb.h"
13
14
#include "source/common/http/status.h"
15
#include "source/common/protobuf/protobuf.h"
16
17
namespace Envoy {
18
namespace Http {
19
20
/**
21
 * Classes and methods for manipulating and checking HTTP headers.
22
 */
23
class HeaderUtility {
24
public:
25
  enum class HeaderMatchType {
26
    Value,
27
    Regex,
28
    Range,
29
    Present,
30
    Prefix,
31
    Suffix,
32
    Contains,
33
    StringMatch
34
  };
35
36
  /**
37
   * Get all header values as a single string. Multiple headers are concatenated with ','.
38
   */
39
  class GetAllOfHeaderAsStringResult {
40
  public:
41
    // The ultimate result of the concatenation. If absl::nullopt, no header values were found.
42
    // If the final string required a string allocation, the memory is held in
43
    // backingString(). This allows zero allocation in the common case of a single header
44
    // value.
45
527k
    absl::optional<absl::string_view> result() const {
46
      // This is safe for move/copy of this class as the backing string will be moved or copied.
47
      // Otherwise result_ is valid. The assert verifies that both are empty or only 1 is set.
48
527k
      ASSERT((!result_.has_value() && result_backing_string_.empty()) ||
49
527k
             (result_.has_value() ^ !result_backing_string_.empty()));
50
527k
      return !result_backing_string_.empty() ? result_backing_string_ : result_;
51
527k
    }
52
53
270k
    const std::string& backingString() const { return result_backing_string_; }
54
55
  private:
56
    absl::optional<absl::string_view> result_;
57
    // Valid only if result_ relies on memory allocation that must live beyond the call. See above.
58
    std::string result_backing_string_;
59
60
    friend class HeaderUtility;
61
  };
62
  static GetAllOfHeaderAsStringResult getAllOfHeaderAsString(const HeaderMap::GetResult& header,
63
                                                             absl::string_view separator = ",");
64
  static GetAllOfHeaderAsStringResult getAllOfHeaderAsString(const HeaderMap& headers,
65
                                                             const Http::LowerCaseString& key,
66
                                                             absl::string_view separator = ",");
67
68
  // A HeaderData specifies one of exact value or regex or range element
69
  // to match in a request's header, specified in the header_match_type_ member.
70
  // It is the runtime equivalent of the HeaderMatchSpecifier proto in RDS API.
71
  struct HeaderData : public HeaderMatcher {
72
    HeaderData(const envoy::config::route::v3::HeaderMatcher& config,
73
               Server::Configuration::CommonFactoryContext& factory_context);
74
75
    const LowerCaseString name_;
76
    HeaderMatchType header_match_type_;
77
    std::string value_;
78
    Regex::CompiledMatcherPtr regex_;
79
    envoy::type::v3::Int64Range range_;
80
    Matchers::StringMatcherPtr string_match_;
81
    const bool invert_match_;
82
    const bool treat_missing_as_empty_;
83
    bool present_;
84
85
    // HeaderMatcher
86
0
    bool matchesHeaders(const HeaderMap& headers) const override {
87
0
      return HeaderUtility::matchHeaders(headers, *this);
88
0
    };
89
  };
90
91
  using HeaderDataPtr = std::unique_ptr<HeaderData>;
92
93
  /**
94
   * Build a vector of HeaderDataPtr given input config.
95
   */
96
  static std::vector<HeaderUtility::HeaderDataPtr> buildHeaderDataVector(
97
      const Protobuf::RepeatedPtrField<envoy::config::route::v3::HeaderMatcher>& header_matchers,
98
139k
      Server::Configuration::CommonFactoryContext& context) {
99
139k
    std::vector<HeaderUtility::HeaderDataPtr> ret;
100
139k
    for (const auto& header_matcher : header_matchers) {
101
30.8k
      ret.emplace_back(std::make_unique<HeaderUtility::HeaderData>(header_matcher, context));
102
30.8k
    }
103
139k
    return ret;
104
139k
  }
105
106
  /**
107
   * Build a vector of HeaderMatcherSharedPtr given input config.
108
   */
109
  static std::vector<Http::HeaderMatcherSharedPtr> buildHeaderMatcherVector(
110
      const Protobuf::RepeatedPtrField<envoy::config::route::v3::HeaderMatcher>& header_matchers,
111
88.4k
      Server::Configuration::CommonFactoryContext& context) {
112
88.4k
    std::vector<Http::HeaderMatcherSharedPtr> ret;
113
88.4k
    for (const auto& header_matcher : header_matchers) {
114
73.1k
      ret.emplace_back(std::make_shared<HeaderUtility::HeaderData>(header_matcher, context));
115
73.1k
    }
116
88.4k
    return ret;
117
88.4k
  }
118
119
  /**
120
   * See if the headers specified in the config are present in a request.
121
   * @param request_headers supplies the headers from the request.
122
   * @param config_headers supplies the list of configured header conditions on which to match.
123
   * @return bool true if all the headers (and values) in the config_headers are found in the
124
   *         request_headers. If no config_headers are specified, returns true.
125
   */
126
  static bool matchHeaders(const HeaderMap& request_headers,
127
                           const std::vector<HeaderDataPtr>& config_headers);
128
129
  static bool matchHeaders(const HeaderMap& request_headers, const HeaderData& config_header);
130
131
  /**
132
   * Validates that a header value is valid, according to RFC 7230, section 3.2.
133
   * http://tools.ietf.org/html/rfc7230#section-3.2
134
   * @return bool true if the header values are valid, according to the aforementioned RFC.
135
   */
136
  static bool headerValueIsValid(const absl::string_view header_value);
137
138
  /**
139
   * Validates that a header name is valid, according to RFC 7230, section 3.2.
140
   * http://tools.ietf.org/html/rfc7230#section-3.2
141
   * @return bool true if the header name is valid, according to the aforementioned RFC.
142
   */
143
  static bool headerNameIsValid(absl::string_view header_key);
144
145
  /**
146
   * Checks if header name contains underscore characters.
147
   * Underscore character is allowed in header names by the RFC-7230 and this check is implemented
148
   * as a security measure due to systems that treat '_' and '-' as interchangeable. Envoy by
149
   * default allows headers with underscore characters.
150
   * @return bool true if header name contains underscore characters.
151
   */
152
  static bool headerNameContainsUnderscore(const absl::string_view header_name);
153
154
  /**
155
   * Validates that the characters in the authority are valid.
156
   * @return bool true if the header values are valid, false otherwise.
157
   */
158
  static bool authorityIsValid(const absl::string_view authority_value);
159
160
  /**
161
   * @brief return if the 1xx should be handled by the [encode|decode]1xx calls.
162
   */
163
  static bool isSpecial1xx(const ResponseHeaderMap& response_headers);
164
165
  /**
166
   * @brief a helper function to determine if the headers represent a CONNECT request.
167
   */
168
  static bool isConnect(const RequestHeaderMap& headers);
169
170
  /**
171
   * @brief a helper function to determine if the headers represent a CONNECT-UDP request.
172
   */
173
  static bool isConnectUdpRequest(const RequestHeaderMap& headers);
174
175
  /**
176
   * @brief a helper function to determine if the headers represent a CONNECT-UDP response.
177
   */
178
  static bool isConnectUdpResponse(const ResponseHeaderMap& headers);
179
180
  /**
181
   * @brief a helper function to determine if the headers represent an accepted CONNECT response.
182
   */
183
  static bool isConnectResponse(const RequestHeaderMap* request_headers,
184
                                const ResponseHeaderMap& response_headers);
185
186
  /**
187
   * @brief Rewrites the authority header field by parsing the path using the default CONNECT-UDP
188
   * URI template. Returns true if the parsing was successful, otherwise returns false.
189
   */
190
  static bool rewriteAuthorityForConnectUdp(RequestHeaderMap& headers);
191
192
#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS
193
  /**
194
   * @brief Returns true if the Capsule-Protocol header field (RFC 9297) is set to true. If the
195
   * header field is included multiple times, returns false as per RFC 9297.
196
   */
197
  static bool isCapsuleProtocol(const RequestOrResponseHeaderMap& headers);
198
#endif
199
200
  static bool requestShouldHaveNoBody(const RequestHeaderMap& headers);
201
202
  /**
203
   * @brief a helper function to determine if the headers represent an envoy internal request
204
   */
205
  static bool isEnvoyInternalRequest(const RequestHeaderMap& headers);
206
207
  /**
208
   * Determines if request headers pass Envoy validity checks.
209
   * @param headers to validate
210
   * @return details of the error if an error is present, otherwise absl::nullopt
211
   */
212
  static absl::optional<std::reference_wrapper<const absl::string_view>>
213
  requestHeadersValid(const RequestHeaderMap& headers);
214
215
  /**
216
   * Determines if the response should be framed by Connection: Close based on protocol
217
   * and headers.
218
   * @param protocol the protocol of the request
219
   * @param headers the request or response headers
220
   * @return if the response should be framed by Connection: Close
221
   */
222
  static bool shouldCloseConnection(Http::Protocol protocol,
223
                                    const RequestOrResponseHeaderMap& headers);
224
225
  /**
226
   * @brief Remove the trailing host dot from host/authority header.
227
   */
228
  static void stripTrailingHostDot(RequestHeaderMap& headers);
229
230
  /**
231
   * @return bool true if the provided host has a port, false otherwise.
232
   */
233
  static bool hostHasPort(absl::string_view host);
234
235
  /**
236
   * @brief Remove the port part from host/authority header if it is equal to provided port.
237
   * @return absl::optional<uint32_t> containing the port, if removed, else absl::nullopt.
238
   * If port is not passed, port part from host/authority header is removed.
239
   */
240
  static absl::optional<uint32_t> stripPortFromHost(RequestHeaderMap& headers,
241
                                                    absl::optional<uint32_t> listener_port);
242
243
  /**
244
   * @brief Return the index of the port, or npos if the host has no port
245
   *
246
   * Note this does not do validity checks on the port, it just finds the
247
   * trailing : which is not a part of an IP address.
248
   */
249
  static absl::string_view::size_type getPortStart(absl::string_view host);
250
251
  /* Does a common header check ensuring required request headers are present.
252
   * Required request headers include :method header, :path for non-CONNECT requests, and
253
   * host/authority for HTTP/1.1 or CONNECT requests.
254
   * @return Status containing the result. If failed, message includes details on which header was
255
   * missing.
256
   */
257
  static Http::Status checkRequiredRequestHeaders(const Http::RequestHeaderMap& headers);
258
259
  /* Does a common header check ensuring required response headers are present.
260
   * Current required response headers only includes :status.
261
   * @return Status containing the result. If failed, message includes details on which header was
262
   * missing.
263
   */
264
  static Http::Status checkRequiredResponseHeaders(const Http::ResponseHeaderMap& headers);
265
266
  /* Does a common header check ensuring that header keys and values are valid and do not contain
267
   * forbidden characters (e.g. valid HTTP header keys/values should never contain embedded NULLs
268
   * or new lines.)
269
   * @return Status containing the result. If failed, message includes details on which header key
270
   * or value was invalid.
271
   */
272
  static Http::Status checkValidRequestHeaders(const Http::RequestHeaderMap& headers);
273
274
  /**
275
   * Returns true if a header may be safely removed without causing additional
276
   * problems. Effectively, header names beginning with ":" and the "host" header
277
   * may not be removed.
278
   */
279
  static bool isRemovableHeader(absl::string_view header);
280
281
  /**
282
   * Returns true if a header may be safely modified without causing additional
283
   * problems. Currently header names beginning with ":" and the "host" header
284
   * may not be modified.
285
   */
286
  static bool isModifiableHeader(absl::string_view header);
287
288
  enum class HeaderValidationResult {
289
    ACCEPT = 0,
290
    DROP,
291
    REJECT,
292
  };
293
294
  /**
295
   * Check if the given header_name has underscore.
296
   * Return HeaderValidationResult and populate the given counters based on
297
   * headers_with_underscores_action.
298
   */
299
  static HeaderValidationResult checkHeaderNameForUnderscores(
300
      absl::string_view header_name,
301
      envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction
302
          headers_with_underscores_action,
303
      HeaderValidatorStats& stats);
304
305
  /**
306
   * Check if header_value represents a valid value for HTTP content-length header.
307
   * Return HeaderValidationResult and populate content_length_output if the value is valid,
308
   * otherwise populate should_close_connection according to
309
   * override_stream_error_on_invalid_http_message.
310
   */
311
  static HeaderValidationResult
312
  validateContentLength(absl::string_view header_value,
313
                        bool override_stream_error_on_invalid_http_message,
314
                        bool& should_close_connection, size_t& content_length_output);
315
316
  /**
317
   * Parse a comma-separated header string to the individual tokens. Discard empty tokens
318
   * and whitespace. Return a vector of the comma-separated tokens.
319
   */
320
  static std::vector<absl::string_view> parseCommaDelimitedHeader(absl::string_view header_value);
321
322
  /**
323
   * Return the part of attribute before first ';'-sign. For example,
324
   * "foo;bar=1" would return "foo".
325
   */
326
  static absl::string_view getSemicolonDelimitedAttribute(absl::string_view value);
327
328
  /**
329
   * Return a new AcceptEncoding header string vector.
330
   */
331
  static std::string addEncodingToAcceptEncoding(absl::string_view accept_encoding_header,
332
                                                 absl::string_view encoding);
333
334
  /**
335
   * Return `true` if the request is a standard HTTP CONNECT.
336
   * HTTP/1 RFC: https://datatracker.ietf.org/doc/html/rfc9110#section-9.3.6
337
   * HTTP/2 RFC: https://datatracker.ietf.org/doc/html/rfc9113#section-8.5
338
   */
339
  static bool isStandardConnectRequest(const Http::RequestHeaderMap& headers);
340
341
  /**
342
   * Return `true` if the request is an extended HTTP/2 CONNECT.
343
   * according to https://datatracker.ietf.org/doc/html/rfc8441#section-4
344
   */
345
  static bool isExtendedH2ConnectRequest(const Http::RequestHeaderMap& headers);
346
347
  /**
348
   * Return true if the given header name is a pseudo header.
349
   */
350
  static bool isPseudoHeader(absl::string_view header_name);
351
};
352
353
} // namespace Http
354
} // namespace Envoy