/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 | 404k | 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 | 404k | ASSERT((!result_.has_value() && result_backing_string_.empty()) || |
49 | 404k | (result_.has_value() ^ !result_backing_string_.empty())); |
50 | 404k | return !result_backing_string_.empty() ? result_backing_string_ : result_; |
51 | 404k | } |
52 | | |
53 | 171k | 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 | 103k | const Protobuf::RepeatedPtrField<envoy::config::route::v3::HeaderMatcher>& header_matchers) { |
97 | 103k | std::vector<HeaderUtility::HeaderDataPtr> ret; |
98 | 103k | for (const auto& header_matcher : header_matchers) { |
99 | 19.3k | ret.emplace_back(std::make_unique<HeaderUtility::HeaderData>(header_matcher)); |
100 | 19.3k | } |
101 | 103k | return ret; |
102 | 103k | } |
103 | | |
104 | | /** |
105 | | * Build a vector of HeaderMatcherSharedPtr given input config. |
106 | | */ |
107 | | static std::vector<Http::HeaderMatcherSharedPtr> buildHeaderMatcherVector( |
108 | 60.6k | const Protobuf::RepeatedPtrField<envoy::config::route::v3::HeaderMatcher>& header_matchers) { |
109 | 60.6k | std::vector<Http::HeaderMatcherSharedPtr> ret; |
110 | 60.6k | for (const auto& header_matcher : header_matchers) { |
111 | 23.4k | ret.emplace_back(std::make_shared<HeaderUtility::HeaderData>(header_matcher)); |
112 | 23.4k | } |
113 | 60.6k | return ret; |
114 | 60.6k | } |
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 |