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