/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 |