/proc/self/cwd/source/common/http/header_utility.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/common/http/header_utility.h" |
2 | | |
3 | | #include "envoy/config/route/v3/route_components.pb.h" |
4 | | |
5 | | #include "source/common/common/json_escape_string.h" |
6 | | #include "source/common/common/matchers.h" |
7 | | #include "source/common/common/regex.h" |
8 | | #include "source/common/common/utility.h" |
9 | | #include "source/common/http/character_set_validation.h" |
10 | | #include "source/common/http/header_map_impl.h" |
11 | | #include "source/common/http/utility.h" |
12 | | #include "source/common/protobuf/utility.h" |
13 | | #include "source/common/runtime/runtime_features.h" |
14 | | |
15 | | #include "absl/strings/match.h" |
16 | | |
17 | | #ifdef ENVOY_NGHTTP2 |
18 | | #include "nghttp2/nghttp2.h" |
19 | | #endif |
20 | | #ifdef ENVOY_ENABLE_HTTP_DATAGRAMS |
21 | | #include "quiche/common/structured_headers.h" |
22 | | #endif |
23 | | #include "quiche/http2/adapter/header_validator.h" |
24 | | |
25 | | namespace Envoy { |
26 | | namespace Http { |
27 | | |
28 | | struct SharedResponseCodeDetailsValues { |
29 | | const absl::string_view InvalidAuthority = "http.invalid_authority"; |
30 | | const absl::string_view ConnectUnsupported = "http.connect_not_supported"; |
31 | | const absl::string_view InvalidMethod = "http.invalid_method"; |
32 | | const absl::string_view InvalidPath = "http.invalid_path"; |
33 | | const absl::string_view InvalidScheme = "http.invalid_scheme"; |
34 | | }; |
35 | | |
36 | | using SharedResponseCodeDetails = ConstSingleton<SharedResponseCodeDetailsValues>; |
37 | | |
38 | | // HeaderMatcher will consist of: |
39 | | // header_match_specifier which can be any one of exact_match, regex_match, range_match, |
40 | | // present_match, prefix_match or suffix_match. |
41 | | // Each of these also can be inverted with the invert_match option. |
42 | | // Absence of these options implies empty header value match based on header presence. |
43 | | // a.exact_match: value will be used for exact string matching. |
44 | | // b.regex_match: Match will succeed if header value matches the value specified here. |
45 | | // c.range_match: Match will succeed if header value lies within the range specified |
46 | | // here, using half open interval semantics [start,end). |
47 | | // d.present_match: Match will succeed if the header is present. |
48 | | // f.prefix_match: Match will succeed if header value matches the prefix value specified here. |
49 | | // g.suffix_match: Match will succeed if header value matches the suffix value specified here. |
50 | | HeaderUtility::HeaderData::HeaderData(const envoy::config::route::v3::HeaderMatcher& config, |
51 | | Server::Configuration::CommonFactoryContext& factory_context) |
52 | | : name_(config.name()), invert_match_(config.invert_match()), |
53 | 123k | treat_missing_as_empty_(config.treat_missing_header_as_empty()) { |
54 | 123k | switch (config.header_match_specifier_case()) { |
55 | 9.24k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kExactMatch: |
56 | 9.24k | header_match_type_ = HeaderMatchType::Value; |
57 | 9.24k | value_ = config.exact_match(); |
58 | 9.24k | break; |
59 | 19.2k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kSafeRegexMatch: |
60 | 19.2k | header_match_type_ = HeaderMatchType::Regex; |
61 | 19.2k | regex_ = Regex::Utility::parseRegex(config.safe_regex_match(), factory_context.regexEngine()); |
62 | 19.2k | break; |
63 | 7.68k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kRangeMatch: |
64 | 7.68k | header_match_type_ = HeaderMatchType::Range; |
65 | 7.68k | range_.set_start(config.range_match().start()); |
66 | 7.68k | range_.set_end(config.range_match().end()); |
67 | 7.68k | break; |
68 | 13.1k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPresentMatch: |
69 | 13.1k | header_match_type_ = HeaderMatchType::Present; |
70 | 13.1k | present_ = config.present_match(); |
71 | 13.1k | break; |
72 | 8.59k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPrefixMatch: |
73 | 8.59k | header_match_type_ = HeaderMatchType::Prefix; |
74 | 8.59k | value_ = config.prefix_match(); |
75 | 8.59k | break; |
76 | 10.6k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kSuffixMatch: |
77 | 10.6k | header_match_type_ = HeaderMatchType::Suffix; |
78 | 10.6k | value_ = config.suffix_match(); |
79 | 10.6k | break; |
80 | 8.28k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kContainsMatch: |
81 | 8.28k | header_match_type_ = HeaderMatchType::Contains; |
82 | 8.28k | value_ = config.contains_match(); |
83 | 8.28k | break; |
84 | 7.20k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kStringMatch: |
85 | 7.20k | header_match_type_ = HeaderMatchType::StringMatch; |
86 | 7.20k | string_match_ = |
87 | 7.20k | std::make_unique<Matchers::StringMatcherImpl<envoy::type::matcher::v3::StringMatcher>>( |
88 | 7.20k | config.string_match(), factory_context); |
89 | 7.20k | break; |
90 | 39.5k | case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase:: |
91 | 39.5k | HEADER_MATCH_SPECIFIER_NOT_SET: |
92 | 39.5k | FALLTHRU; |
93 | 39.5k | default: |
94 | 39.5k | header_match_type_ = HeaderMatchType::Present; |
95 | 39.5k | present_ = true; |
96 | 39.5k | break; |
97 | 123k | } |
98 | 123k | } |
99 | | |
100 | | bool HeaderUtility::matchHeaders(const HeaderMap& request_headers, |
101 | 44.3k | const std::vector<HeaderDataPtr>& config_headers) { |
102 | | // No headers to match is considered a match. |
103 | 44.3k | if (!config_headers.empty()) { |
104 | 10.4k | for (const HeaderDataPtr& cfg_header_data : config_headers) { |
105 | 10.4k | if (!matchHeaders(request_headers, *cfg_header_data)) { |
106 | 4.47k | return false; |
107 | 4.47k | } |
108 | 10.4k | } |
109 | 5.98k | } |
110 | | |
111 | 39.8k | return true; |
112 | 44.3k | } |
113 | | |
114 | | HeaderUtility::GetAllOfHeaderAsStringResult |
115 | | HeaderUtility::getAllOfHeaderAsString(const HeaderMap::GetResult& header_value, |
116 | 33.5k | absl::string_view separator) { |
117 | 33.5k | GetAllOfHeaderAsStringResult result; |
118 | | // In this case we concatenate all found headers using a delimiter before performing the |
119 | | // final match. We use an InlinedVector of absl::string_view to invoke the optimized join |
120 | | // algorithm. This requires a copying phase before we invoke join. The 3 used as the inline |
121 | | // size has been arbitrarily chosen. |
122 | | // TODO(mattklein123): Do we need to normalize any whitespace here? |
123 | 33.5k | absl::InlinedVector<absl::string_view, 3> string_view_vector; |
124 | 33.5k | string_view_vector.reserve(header_value.size()); |
125 | 159k | for (size_t i = 0; i < header_value.size(); i++) { |
126 | 126k | string_view_vector.push_back(header_value[i]->value().getStringView()); |
127 | 126k | } |
128 | 33.5k | result.result_backing_string_ = absl::StrJoin(string_view_vector, separator); |
129 | | |
130 | 33.5k | return result; |
131 | 33.5k | } |
132 | | |
133 | | HeaderUtility::GetAllOfHeaderAsStringResult |
134 | | HeaderUtility::getAllOfHeaderAsString(const HeaderMap& headers, const Http::LowerCaseString& key, |
135 | 274k | absl::string_view separator) { |
136 | 274k | GetAllOfHeaderAsStringResult result; |
137 | 274k | const auto header_value = headers.get(key); |
138 | | |
139 | 274k | if (header_value.empty()) { |
140 | | // Empty for clarity. Avoid handling the empty case in the block below if the runtime feature |
141 | | // is disabled. |
142 | 262k | } else if (header_value.size() == 1) { |
143 | 229k | result.result_ = header_value[0]->value().getStringView(); |
144 | 229k | } else { |
145 | 33.5k | return getAllOfHeaderAsString(header_value, separator); |
146 | 33.5k | } |
147 | | |
148 | 240k | return result; |
149 | 274k | } |
150 | | |
151 | 21.0k | bool HeaderUtility::matchHeaders(const HeaderMap& request_headers, const HeaderData& header_data) { |
152 | 21.0k | const auto header_value = getAllOfHeaderAsString(request_headers, header_data.name_); |
153 | | |
154 | 21.0k | if (!header_value.result().has_value() && !header_data.treat_missing_as_empty_) { |
155 | 4.74k | if (header_data.invert_match_) { |
156 | 1.33k | return header_data.header_match_type_ == HeaderMatchType::Present && header_data.present_; |
157 | 3.40k | } else { |
158 | 3.40k | return header_data.header_match_type_ == HeaderMatchType::Present && !header_data.present_; |
159 | 3.40k | } |
160 | 4.74k | } |
161 | | |
162 | | // If the header does not have value and the result is not returned in the |
163 | | // code above, it means treat_missing_as_empty_ is set to true and we should |
164 | | // treat the header value as empty. |
165 | 16.3k | const auto value = header_value.result().has_value() ? header_value.result().value() : ""; |
166 | 16.3k | bool match; |
167 | 16.3k | switch (header_data.header_match_type_) { |
168 | 2.49k | case HeaderMatchType::Value: |
169 | 2.49k | match = header_data.value_.empty() || value == header_data.value_; |
170 | 2.49k | break; |
171 | 907 | case HeaderMatchType::Regex: |
172 | 907 | match = header_data.regex_->match(value); |
173 | 907 | break; |
174 | 1.35k | case HeaderMatchType::Range: { |
175 | 1.35k | int64_t header_int_value = 0; |
176 | 1.35k | match = absl::SimpleAtoi(value, &header_int_value) && |
177 | 1.35k | header_int_value >= header_data.range_.start() && |
178 | 1.35k | header_int_value < header_data.range_.end(); |
179 | 1.35k | break; |
180 | 0 | } |
181 | 4.10k | case HeaderMatchType::Present: |
182 | 4.10k | match = header_data.present_; |
183 | 4.10k | break; |
184 | 1.63k | case HeaderMatchType::Prefix: |
185 | 1.63k | match = absl::StartsWith(value, header_data.value_); |
186 | 1.63k | break; |
187 | 1.55k | case HeaderMatchType::Suffix: |
188 | 1.55k | match = absl::EndsWith(value, header_data.value_); |
189 | 1.55k | break; |
190 | 1.74k | case HeaderMatchType::Contains: |
191 | 1.74k | match = absl::StrContains(value, header_data.value_); |
192 | 1.74k | break; |
193 | 2.52k | case HeaderMatchType::StringMatch: |
194 | 2.52k | match = header_data.string_match_->match(value); |
195 | 2.52k | break; |
196 | 16.3k | } |
197 | | |
198 | 16.3k | return match != header_data.invert_match_; |
199 | 16.3k | } |
200 | | |
201 | 761k | bool HeaderUtility::headerValueIsValid(const absl::string_view header_value) { |
202 | 761k | return http2::adapter::HeaderValidator::IsValidHeaderValue(header_value, |
203 | 761k | http2::adapter::ObsTextOption::kAllow); |
204 | 761k | } |
205 | | |
206 | 923k | bool HeaderUtility::headerNameIsValid(absl::string_view header_key) { |
207 | 923k | if (!header_key.empty() && header_key[0] == ':') { |
208 | 463k | #ifdef ENVOY_NGHTTP2 |
209 | 463k | if (!Runtime::runtimeFeatureEnabled( |
210 | 463k | "envoy.reloadable_features.sanitize_http2_headers_without_nghttp2")) { |
211 | | // For HTTP/2 pseudo header, use the HTTP/2 semantics for checking validity |
212 | 0 | return nghttp2_check_header_name(reinterpret_cast<const uint8_t*>(header_key.data()), |
213 | 0 | header_key.size()) != 0; |
214 | 0 | } |
215 | 463k | #endif |
216 | 463k | header_key.remove_prefix(1); |
217 | 463k | if (header_key.empty()) { |
218 | 1.19k | return false; |
219 | 1.19k | } |
220 | 463k | } |
221 | | // For all other header use HTTP/1 semantics. The only difference from HTTP/2 is that |
222 | | // uppercase characters are allowed. This allows HTTP filters to add header with mixed |
223 | | // case names. The HTTP/1 codec will send as is, as uppercase characters are allowed. |
224 | | // However the HTTP/2 codec will NOT convert these to lowercase when serializing the |
225 | | // header map, thus producing an invalid request. |
226 | | // TODO(yanavlasov): make validation in HTTP/2 case stricter. |
227 | 922k | bool is_valid = true; |
228 | 14.5M | for (auto iter = header_key.begin(); iter != header_key.end() && is_valid; ++iter) { |
229 | 13.6M | is_valid &= testCharInTable(kGenericHeaderNameCharTable, *iter); |
230 | 13.6M | } |
231 | 922k | return is_valid; |
232 | 923k | } |
233 | | |
234 | 14.3k | bool HeaderUtility::headerNameContainsUnderscore(const absl::string_view header_name) { |
235 | 14.3k | return header_name.find('_') != absl::string_view::npos; |
236 | 14.3k | } |
237 | | |
238 | | namespace { |
239 | | // This function validates the authority header for both HTTP/1 and HTTP/2. |
240 | | // Note the HTTP/1 spec allows "user-info@host:port" for the authority, whereas |
241 | | // the HTTP/2 spec only allows "host:port". Thus, this function permits all the |
242 | | // HTTP/2 valid characters (similar to oghttp2's implementation) and the "@" character. |
243 | | // Once UHV is used, this function should be removed, and the HTTP/1 and HTTP/2 |
244 | | // authority validations should be different. |
245 | 390k | bool check_authority_h1_h2(const absl::string_view header_value) { |
246 | 390k | static constexpr char ValidAuthorityChars[] = { |
247 | 390k | 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, |
248 | 390k | 0 /* EOT */, 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, |
249 | 390k | 0 /* BS */, 0 /* HT */, 0 /* LF */, 0 /* VT */, |
250 | 390k | 0 /* FF */, 0 /* CR */, 0 /* SO */, 0 /* SI */, |
251 | 390k | 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, |
252 | 390k | 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, |
253 | 390k | 0 /* CAN */, 0 /* EM */, 0 /* SUB */, 0 /* ESC */, |
254 | 390k | 0 /* FS */, 0 /* GS */, 0 /* RS */, 0 /* US */, |
255 | 390k | 0 /* SPC */, 1 /* ! */, 0 /* " */, 0 /* # */, |
256 | 390k | 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, |
257 | 390k | 1 /* ( */, 1 /* ) */, 1 /* * */, 1 /* + */, |
258 | 390k | 1 /* , */, 1 /* - */, 1 /* . */, 0 /* / */, |
259 | 390k | 1 /* 0 */, 1 /* 1 */, 1 /* 2 */, 1 /* 3 */, |
260 | 390k | 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, 1 /* 7 */, |
261 | 390k | 1 /* 8 */, 1 /* 9 */, 1 /* : */, 1 /* ; */, |
262 | 390k | 0 /* < */, 1 /* = */, 0 /* > */, 0 /* ? */, |
263 | 390k | 1 /* @ */, 1 /* A */, 1 /* B */, 1 /* C */, |
264 | 390k | 1 /* D */, 1 /* E */, 1 /* F */, 1 /* G */, |
265 | 390k | 1 /* H */, 1 /* I */, 1 /* J */, 1 /* K */, |
266 | 390k | 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */, |
267 | 390k | 1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, |
268 | 390k | 1 /* T */, 1 /* U */, 1 /* V */, 1 /* W */, |
269 | 390k | 1 /* X */, 1 /* Y */, 1 /* Z */, 1 /* [ */, |
270 | 390k | 0 /* \ */, 1 /* ] */, 0 /* ^ */, 1 /* _ */, |
271 | 390k | 0 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, |
272 | 390k | 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, |
273 | 390k | 1 /* h */, 1 /* i */, 1 /* j */, 1 /* k */, |
274 | 390k | 1 /* l */, 1 /* m */, 1 /* n */, 1 /* o */, |
275 | 390k | 1 /* p */, 1 /* q */, 1 /* r */, 1 /* s */, |
276 | 390k | 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, |
277 | 390k | 1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, |
278 | 390k | 0 /* | */, 0 /* } */, 1 /* ~ */, 0 /* DEL */, |
279 | 390k | 0 /* 0x80 */, 0 /* 0x81 */, 0 /* 0x82 */, 0 /* 0x83 */, |
280 | 390k | 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, 0 /* 0x87 */, |
281 | 390k | 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */, |
282 | 390k | 0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, |
283 | 390k | 0 /* 0x90 */, 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, |
284 | 390k | 0 /* 0x94 */, 0 /* 0x95 */, 0 /* 0x96 */, 0 /* 0x97 */, |
285 | 390k | 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, 0 /* 0x9b */, |
286 | 390k | 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */, |
287 | 390k | 0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, |
288 | 390k | 0 /* 0xa4 */, 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, |
289 | 390k | 0 /* 0xa8 */, 0 /* 0xa9 */, 0 /* 0xaa */, 0 /* 0xab */, |
290 | 390k | 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, 0 /* 0xaf */, |
291 | 390k | 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */, |
292 | 390k | 0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, |
293 | 390k | 0 /* 0xb8 */, 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, |
294 | 390k | 0 /* 0xbc */, 0 /* 0xbd */, 0 /* 0xbe */, 0 /* 0xbf */, |
295 | 390k | 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, 0 /* 0xc3 */, |
296 | 390k | 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */, |
297 | 390k | 0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, |
298 | 390k | 0 /* 0xcc */, 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, |
299 | 390k | 0 /* 0xd0 */, 0 /* 0xd1 */, 0 /* 0xd2 */, 0 /* 0xd3 */, |
300 | 390k | 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, 0 /* 0xd7 */, |
301 | 390k | 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, |
302 | 390k | 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, |
303 | 390k | 0 /* 0xe0 */, 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, |
304 | 390k | 0 /* 0xe4 */, 0 /* 0xe5 */, 0 /* 0xe6 */, 0 /* 0xe7 */, |
305 | 390k | 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, 0 /* 0xeb */, |
306 | 390k | 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */, |
307 | 390k | 0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, |
308 | 390k | 0 /* 0xf4 */, 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, |
309 | 390k | 0 /* 0xf8 */, 0 /* 0xf9 */, 0 /* 0xfa */, 0 /* 0xfb */, |
310 | 390k | 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, 0 /* 0xff */ |
311 | 390k | }; |
312 | | |
313 | 2.17M | for (const uint8_t c : header_value) { |
314 | 2.17M | if (!ValidAuthorityChars[c]) { |
315 | 432 | return false; |
316 | 432 | } |
317 | 2.17M | } |
318 | 390k | return true; |
319 | 390k | } |
320 | | } // namespace |
321 | | |
322 | 390k | bool HeaderUtility::authorityIsValid(const absl::string_view header_value) { |
323 | 390k | if (Runtime::runtimeFeatureEnabled( |
324 | 390k | "envoy.reloadable_features.internal_authority_header_validator")) { |
325 | 390k | return check_authority_h1_h2(header_value); |
326 | 390k | } |
327 | 0 | return http2::adapter::HeaderValidator::IsValidAuthority(header_value); |
328 | 390k | } |
329 | | |
330 | 32.3k | bool HeaderUtility::isSpecial1xx(const ResponseHeaderMap& response_headers) { |
331 | 32.3k | if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.proxy_104") && |
332 | 32.3k | response_headers.Status()->value() == "104") { |
333 | 73 | return true; |
334 | 73 | } |
335 | 32.2k | return response_headers.Status()->value() == "100" || |
336 | 32.2k | response_headers.Status()->value() == "102" || response_headers.Status()->value() == "103"; |
337 | 32.3k | } |
338 | | |
339 | 734k | bool HeaderUtility::isConnect(const RequestHeaderMap& headers) { |
340 | 734k | return headers.Method() && headers.Method()->value() == Http::Headers::get().MethodValues.Connect; |
341 | 734k | } |
342 | | |
343 | 80.7k | bool HeaderUtility::isConnectUdpRequest(const RequestHeaderMap& headers) { |
344 | 80.7k | return headers.Upgrade() && absl::EqualsIgnoreCase(headers.getUpgradeValue(), |
345 | 9.16k | Http::Headers::get().UpgradeValues.ConnectUdp); |
346 | 80.7k | } |
347 | | |
348 | 0 | bool HeaderUtility::isConnectUdpResponse(const ResponseHeaderMap& headers) { |
349 | | // In connect-udp case, Envoy will transform the H2 headers to H1 upgrade headers. |
350 | | // A valid response should have SwitchingProtocol status and connect-udp upgrade. |
351 | 0 | return headers.Upgrade() && Utility::getResponseStatus(headers) == 101 && |
352 | 0 | absl::EqualsIgnoreCase(headers.getUpgradeValue(), |
353 | 0 | Http::Headers::get().UpgradeValues.ConnectUdp); |
354 | 0 | } |
355 | | |
356 | | bool HeaderUtility::isConnectResponse(const RequestHeaderMap* request_headers, |
357 | 124k | const ResponseHeaderMap& response_headers) { |
358 | 124k | return request_headers && isConnect(*request_headers) && |
359 | 124k | static_cast<Http::Code>(Http::Utility::getResponseStatus(response_headers)) == |
360 | 49 | Http::Code::OK; |
361 | 124k | } |
362 | | |
363 | 8.46k | bool HeaderUtility::rewriteAuthorityForConnectUdp(RequestHeaderMap& headers) { |
364 | | // Per RFC 9298, the URI template must only contain ASCII characters in the range 0x21-0x7E. |
365 | 8.46k | absl::string_view path = headers.getPathValue(); |
366 | 4.68M | for (char c : path) { |
367 | 4.68M | unsigned char ascii_code = static_cast<unsigned char>(c); |
368 | 4.68M | if (ascii_code < 0x21 || ascii_code > 0x7e) { |
369 | 38 | ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a bad character in the path {}", path); |
370 | 38 | return false; |
371 | 38 | } |
372 | 4.68M | } |
373 | | |
374 | | // Extract target host and port from path using default template. |
375 | 8.42k | if (!absl::StartsWith(path, "/.well-known/masque/udp/")) { |
376 | 2.28k | ENVOY_LOG_MISC(warn, "CONNECT-UDP request path is not a well-known URI: {}", path); |
377 | 2.28k | return false; |
378 | 2.28k | } |
379 | | |
380 | 6.14k | std::vector<absl::string_view> path_split = absl::StrSplit(path, '/'); |
381 | 6.14k | if (path_split.size() != 7 || path_split[4].empty() || path_split[5].empty() || |
382 | 6.14k | !path_split[6].empty()) { |
383 | 2.33k | ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a malformed URI template in the path {}", path); |
384 | 2.33k | return false; |
385 | 2.33k | } |
386 | | |
387 | | // Utility::PercentEncoding::decode never returns an empty string if the input argument is not |
388 | | // empty. |
389 | 3.80k | std::string target_host = Utility::PercentEncoding::decode(path_split[4]); |
390 | | // Per RFC 9298, IPv6 Zone ID is not supported. |
391 | 3.80k | if (target_host.find('%') != std::string::npos) { |
392 | 2.61k | ENVOY_LOG_MISC(warn, "CONNECT-UDP request with a non-escpaed char (%) in the path {}", path); |
393 | 2.61k | return false; |
394 | 2.61k | } |
395 | 1.18k | std::string target_port = Utility::PercentEncoding::decode(path_split[5]); |
396 | | |
397 | | // If the host is an IPv6 address, surround the address with square brackets. |
398 | 1.18k | in6_addr sin6_addr; |
399 | 1.18k | bool is_ipv6 = (inet_pton(AF_INET6, target_host.c_str(), &sin6_addr) == 1); |
400 | 1.18k | std::string new_host = |
401 | 1.18k | absl::StrCat((is_ipv6 ? absl::StrCat("[", target_host, "]") : target_host), ":", target_port); |
402 | 1.18k | headers.setHost(new_host); |
403 | | |
404 | 1.18k | return true; |
405 | 3.80k | } |
406 | | |
407 | | #ifdef ENVOY_ENABLE_HTTP_DATAGRAMS |
408 | 25 | bool HeaderUtility::isCapsuleProtocol(const RequestOrResponseHeaderMap& headers) { |
409 | 25 | Http::HeaderMap::GetResult capsule_protocol = |
410 | 25 | headers.get(Envoy::Http::LowerCaseString("Capsule-Protocol")); |
411 | | // When there are multiple Capsule-Protocol header entries, it returns false. RFC 9297 specifies |
412 | | // that non-boolean value types must be ignored. If there are multiple header entries, the value |
413 | | // type becomes a List so the header field must be ignored. |
414 | 25 | if (capsule_protocol.size() != 1) { |
415 | 0 | return false; |
416 | 0 | } |
417 | | // Parses the header value and extracts the boolean value ignoring parameters. |
418 | 25 | absl::optional<quiche::structured_headers::ParameterizedItem> header_item = |
419 | 25 | quiche::structured_headers::ParseItem(capsule_protocol[0]->value().getStringView()); |
420 | 25 | return header_item && header_item->item.is_boolean() && header_item->item.GetBoolean(); |
421 | 25 | } |
422 | | #endif |
423 | | |
424 | 8.80k | bool HeaderUtility::requestShouldHaveNoBody(const RequestHeaderMap& headers) { |
425 | 8.80k | return (headers.Method() && |
426 | 8.80k | (headers.Method()->value() == Http::Headers::get().MethodValues.Get || |
427 | 8.80k | headers.Method()->value() == Http::Headers::get().MethodValues.Head || |
428 | 8.80k | headers.Method()->value() == Http::Headers::get().MethodValues.Delete || |
429 | 8.80k | headers.Method()->value() == Http::Headers::get().MethodValues.Trace || |
430 | 8.80k | headers.Method()->value() == Http::Headers::get().MethodValues.Connect)); |
431 | 8.80k | } |
432 | | |
433 | 2.55k | bool HeaderUtility::isEnvoyInternalRequest(const RequestHeaderMap& headers) { |
434 | 2.55k | const HeaderEntry* internal_request_header = headers.EnvoyInternalRequest(); |
435 | 2.55k | return internal_request_header != nullptr && |
436 | 2.55k | internal_request_header->value() == Headers::get().EnvoyInternalRequestValues.True; |
437 | 2.55k | } |
438 | | |
439 | 112 | void HeaderUtility::stripTrailingHostDot(RequestHeaderMap& headers) { |
440 | 112 | auto host = headers.getHostValue(); |
441 | | // If the host ends in a period, remove it. |
442 | 112 | auto dot_index = host.rfind('.'); |
443 | 112 | if (dot_index == std::string::npos) { |
444 | 16 | return; |
445 | 96 | } else if (dot_index == (host.size() - 1)) { |
446 | 68 | host.remove_suffix(1); |
447 | 68 | headers.setHost(host); |
448 | 68 | return; |
449 | 68 | } |
450 | | // If the dot is just before a colon, it must be preceding the port number. |
451 | | // IPv6 addresses may contain colons or dots, but the dot will never directly |
452 | | // precede the colon, so this check should be sufficient to detect a trailing port number. |
453 | 28 | if (host[dot_index + 1] == ':') { |
454 | 0 | headers.setHost(absl::StrCat(host.substr(0, dot_index), host.substr(dot_index + 1))); |
455 | 0 | } |
456 | 28 | } |
457 | | |
458 | 0 | bool HeaderUtility::hostHasPort(absl::string_view original_host) { |
459 | 0 | const absl::string_view::size_type port_start = getPortStart(original_host); |
460 | 0 | const absl::string_view port_str = original_host.substr(port_start + 1); |
461 | 0 | if (port_start == absl::string_view::npos) { |
462 | 0 | return false; |
463 | 0 | } |
464 | 0 | uint32_t port = 0; |
465 | 0 | if (!absl::SimpleAtoi(port_str, &port)) { |
466 | 0 | return false; |
467 | 0 | } |
468 | 0 | return true; |
469 | 0 | } |
470 | | |
471 | | absl::optional<uint32_t> HeaderUtility::stripPortFromHost(RequestHeaderMap& headers, |
472 | 131 | absl::optional<uint32_t> listener_port) { |
473 | 131 | const absl::string_view original_host = headers.getHostValue(); |
474 | 131 | const absl::string_view::size_type port_start = getPortStart(original_host); |
475 | 131 | if (port_start == absl::string_view::npos) { |
476 | 112 | return absl::nullopt; |
477 | 112 | } |
478 | 19 | const absl::string_view port_str = original_host.substr(port_start + 1); |
479 | 19 | uint32_t port = 0; |
480 | 19 | if (!absl::SimpleAtoi(port_str, &port)) { |
481 | 19 | return absl::nullopt; |
482 | 19 | } |
483 | 0 | if (listener_port.has_value() && port != listener_port) { |
484 | | // We would strip ports only if it is specified and they are the same, as local port of the |
485 | | // listener. |
486 | 0 | return absl::nullopt; |
487 | 0 | } |
488 | 0 | const absl::string_view host = original_host.substr(0, port_start); |
489 | 0 | headers.setHost(host); |
490 | 0 | return port; |
491 | 0 | } |
492 | | |
493 | 1.86k | absl::string_view::size_type HeaderUtility::getPortStart(absl::string_view host) { |
494 | 1.86k | const absl::string_view::size_type port_start = host.rfind(':'); |
495 | 1.86k | if (port_start == absl::string_view::npos) { |
496 | 1.78k | return absl::string_view::npos; |
497 | 1.78k | } |
498 | | // According to RFC3986 v6 address is always enclosed in "[]". section 3.2.2. |
499 | 78 | const auto v6_end_index = host.rfind(']'); |
500 | 78 | if (v6_end_index == absl::string_view::npos || v6_end_index < port_start) { |
501 | 69 | if ((port_start + 1) > host.size()) { |
502 | 0 | return absl::string_view::npos; |
503 | 0 | } |
504 | 69 | return port_start; |
505 | 69 | } |
506 | 9 | return absl::string_view::npos; |
507 | 78 | } |
508 | | |
509 | 611k | constexpr bool isInvalidToken(unsigned char c) { |
510 | 611k | if (c == '!' || c == '|' || c == '~' || c == '*' || c == '+' || c == '-' || c == '.' || |
511 | | // #, $, %, &, ' |
512 | 611k | (c >= '#' && c <= '\'') || |
513 | | // [0-9] |
514 | 611k | (c >= '0' && c <= '9') || |
515 | | // [A-Z] |
516 | 611k | (c >= 'A' && c <= 'Z') || |
517 | | // ^, _, `, [a-z] |
518 | 611k | (c >= '^' && c <= 'z')) { |
519 | 611k | return false; |
520 | 611k | } |
521 | 25 | return true; |
522 | 611k | } |
523 | | |
524 | | absl::optional<std::reference_wrapper<const absl::string_view>> |
525 | 200k | HeaderUtility::requestHeadersValid(const RequestHeaderMap& headers) { |
526 | | // Make sure the host is valid. |
527 | 200k | if (headers.Host() && !HeaderUtility::authorityIsValid(headers.Host()->value().getStringView())) { |
528 | 242 | return SharedResponseCodeDetails::get().InvalidAuthority; |
529 | 242 | } |
530 | 200k | if (headers.Method()) { |
531 | 200k | absl::string_view method = headers.Method()->value().getStringView(); |
532 | 200k | if (method.empty() || std::any_of(method.begin(), method.end(), isInvalidToken)) { |
533 | 28 | return SharedResponseCodeDetails::get().InvalidMethod; |
534 | 28 | } |
535 | 200k | } |
536 | 200k | if (headers.Scheme() && absl::StrContains(headers.Scheme()->value().getStringView(), ",")) { |
537 | 9 | return SharedResponseCodeDetails::get().InvalidScheme; |
538 | 9 | } |
539 | 200k | return absl::nullopt; |
540 | 200k | } |
541 | | |
542 | | bool HeaderUtility::shouldCloseConnection(Http::Protocol protocol, |
543 | 192k | const RequestOrResponseHeaderMap& headers) { |
544 | | // HTTP/1.0 defaults to single-use connections. Make sure the connection will be closed unless |
545 | | // Keep-Alive is present. |
546 | 192k | if (protocol == Protocol::Http10 && |
547 | 192k | (!headers.Connection() || |
548 | 280 | !Envoy::StringUtil::caseFindToken(headers.Connection()->value().getStringView(), ",", |
549 | 280 | Http::Headers::get().ConnectionValues.KeepAlive))) { |
550 | 280 | return true; |
551 | 280 | } |
552 | | |
553 | 192k | if (protocol == Protocol::Http11 && headers.Connection() && |
554 | 192k | Envoy::StringUtil::caseFindToken(headers.Connection()->value().getStringView(), ",", |
555 | 2.56k | Http::Headers::get().ConnectionValues.Close)) { |
556 | 26 | return true; |
557 | 26 | } |
558 | | |
559 | | // Note: Proxy-Connection is not a standard header, but is supported here |
560 | | // since it is supported by http-parser the underlying parser for http |
561 | | // requests. |
562 | 192k | if (protocol < Protocol::Http2 && headers.ProxyConnection() && |
563 | 192k | Envoy::StringUtil::caseFindToken(headers.ProxyConnection()->value().getStringView(), ",", |
564 | 1.46k | Http::Headers::get().ConnectionValues.Close)) { |
565 | 39 | return true; |
566 | 39 | } |
567 | 192k | return false; |
568 | 192k | } |
569 | | |
570 | 141k | Http::Status HeaderUtility::checkRequiredRequestHeaders(const Http::RequestHeaderMap& headers) { |
571 | 141k | if (!headers.Method()) { |
572 | 0 | return absl::InvalidArgumentError( |
573 | 0 | absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Method.get())); |
574 | 0 | } |
575 | 141k | bool is_connect = Http::HeaderUtility::isConnect(headers); |
576 | 141k | if (is_connect) { |
577 | 2.44k | if (!headers.Host()) { |
578 | | // Host header must be present for CONNECT request. |
579 | 0 | return absl::InvalidArgumentError( |
580 | 0 | absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Host.get())); |
581 | 0 | } |
582 | 2.44k | if (headers.Path() && !headers.Protocol()) { |
583 | | // Path and Protocol header should only be present for CONNECT for upgrade style CONNECT. |
584 | 120 | return absl::InvalidArgumentError( |
585 | 120 | absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Protocol.get())); |
586 | 120 | } |
587 | 2.32k | if (!headers.Path() && headers.Protocol()) { |
588 | | // Path and Protocol header should only be present for CONNECT for upgrade style CONNECT. |
589 | 0 | return absl::InvalidArgumentError( |
590 | 0 | absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Path.get())); |
591 | 0 | } |
592 | 139k | } else { |
593 | 139k | if (!headers.Path()) { |
594 | | // :path header must be present for non-CONNECT requests. |
595 | 0 | return absl::InvalidArgumentError( |
596 | 0 | absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Path.get())); |
597 | 0 | } |
598 | 139k | } |
599 | 141k | return Http::okStatus(); |
600 | 141k | } |
601 | | |
602 | 118k | Http::Status HeaderUtility::checkValidRequestHeaders(const Http::RequestHeaderMap& headers) { |
603 | 118k | if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.validate_upstream_headers")) { |
604 | 0 | return Http::okStatus(); |
605 | 0 | } |
606 | | |
607 | 118k | const HeaderEntry* invalid_entry = nullptr; |
608 | 118k | bool invalid_key = false; |
609 | 543k | headers.iterate([&invalid_entry, &invalid_key](const HeaderEntry& header) -> HeaderMap::Iterate { |
610 | 543k | if (!HeaderUtility::headerNameIsValid(header.key().getStringView())) { |
611 | 3.19k | invalid_entry = &header; |
612 | 3.19k | invalid_key = true; |
613 | 3.19k | return HeaderMap::Iterate::Break; |
614 | 3.19k | } |
615 | | |
616 | 540k | if (!HeaderUtility::headerValueIsValid(header.value().getStringView())) { |
617 | 414 | invalid_entry = &header; |
618 | 414 | invalid_key = false; |
619 | 414 | return HeaderMap::Iterate::Break; |
620 | 414 | } |
621 | | |
622 | 539k | return HeaderMap::Iterate::Continue; |
623 | 540k | }); |
624 | | |
625 | 118k | if (invalid_entry) { |
626 | | // The header key may contain non-printable characters. Escape the key so that the error |
627 | | // details can be safely presented. |
628 | 3.60k | const absl::string_view key = invalid_entry->key().getStringView(); |
629 | 3.60k | uint64_t extra_length = JsonEscaper::extraSpace(key); |
630 | 3.60k | const std::string escaped_key = JsonEscaper::escapeString(key, extra_length); |
631 | | |
632 | 3.60k | return absl::InvalidArgumentError( |
633 | 3.60k | absl::StrCat("invalid header ", invalid_key ? "name: " : "value for: ", escaped_key)); |
634 | 3.60k | } |
635 | 115k | return Http::okStatus(); |
636 | 118k | } |
637 | | |
638 | 125k | Http::Status HeaderUtility::checkRequiredResponseHeaders(const Http::ResponseHeaderMap& headers) { |
639 | 125k | const absl::optional<uint64_t> status = Utility::getResponseStatusOrNullopt(headers); |
640 | 125k | if (!status.has_value()) { |
641 | 0 | return absl::InvalidArgumentError( |
642 | 0 | absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Status.get())); |
643 | 0 | } |
644 | 125k | return Http::okStatus(); |
645 | 125k | } |
646 | | |
647 | 78.0k | bool HeaderUtility::isRemovableHeader(absl::string_view header) { |
648 | 78.0k | return (header.empty() || header[0] != ':') && |
649 | 78.0k | !absl::EqualsIgnoreCase(header, Headers::get().HostLegacy.get()); |
650 | 78.0k | } |
651 | | |
652 | 342k | bool HeaderUtility::isModifiableHeader(absl::string_view header) { |
653 | 342k | return (header.empty() || header[0] != ':') && |
654 | 342k | !absl::EqualsIgnoreCase(header, Headers::get().HostLegacy.get()); |
655 | 342k | } |
656 | | |
657 | | HeaderUtility::HeaderValidationResult HeaderUtility::checkHeaderNameForUnderscores( |
658 | | absl::string_view header_name, |
659 | | envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction |
660 | | headers_with_underscores_action, |
661 | 75 | HeaderValidatorStats& stats) { |
662 | 75 | if (headers_with_underscores_action == envoy::config::core::v3::HttpProtocolOptions::ALLOW || |
663 | 75 | !HeaderUtility::headerNameContainsUnderscore(header_name)) { |
664 | 75 | return HeaderValidationResult::ACCEPT; |
665 | 75 | } |
666 | 0 | if (headers_with_underscores_action == |
667 | 0 | envoy::config::core::v3::HttpProtocolOptions::DROP_HEADER) { |
668 | 0 | ENVOY_LOG_MISC(debug, "Dropping header with invalid characters in its name: {}", header_name); |
669 | 0 | stats.incDroppedHeadersWithUnderscores(); |
670 | 0 | return HeaderValidationResult::DROP; |
671 | 0 | } |
672 | 0 | ENVOY_LOG_MISC(debug, "Rejecting request due to header name with underscores: {}", header_name); |
673 | 0 | stats.incRequestsRejectedWithUnderscoresInHeaders(); |
674 | 0 | return HeaderUtility::HeaderValidationResult::REJECT; |
675 | 0 | } |
676 | | |
677 | | HeaderUtility::HeaderValidationResult |
678 | | HeaderUtility::validateContentLength(absl::string_view header_value, |
679 | | bool override_stream_error_on_invalid_http_message, |
680 | 0 | bool& should_close_connection, size_t& content_length_output) { |
681 | 0 | should_close_connection = false; |
682 | 0 | std::vector<absl::string_view> values = absl::StrSplit(header_value, ','); |
683 | 0 | absl::optional<uint64_t> content_length; |
684 | 0 | for (const absl::string_view& value : values) { |
685 | 0 | uint64_t new_value; |
686 | 0 | if (!absl::SimpleAtoi(value, &new_value) || |
687 | 0 | !std::all_of(value.begin(), value.end(), absl::ascii_isdigit)) { |
688 | 0 | ENVOY_LOG_MISC(debug, "Content length was either unparseable or negative"); |
689 | 0 | should_close_connection = !override_stream_error_on_invalid_http_message; |
690 | 0 | return HeaderValidationResult::REJECT; |
691 | 0 | } |
692 | 0 | if (!content_length.has_value()) { |
693 | 0 | content_length = new_value; |
694 | 0 | continue; |
695 | 0 | } |
696 | 0 | if (new_value != content_length.value()) { |
697 | 0 | ENVOY_LOG_MISC( |
698 | 0 | debug, |
699 | 0 | "Parsed content length {} is inconsistent with previously detected content length {}", |
700 | 0 | new_value, content_length.value()); |
701 | 0 | should_close_connection = !override_stream_error_on_invalid_http_message; |
702 | 0 | return HeaderValidationResult::REJECT; |
703 | 0 | } |
704 | 0 | } |
705 | 0 | content_length_output = content_length.value(); |
706 | 0 | return HeaderValidationResult::ACCEPT; |
707 | 0 | } |
708 | | |
709 | | std::vector<absl::string_view> |
710 | 0 | HeaderUtility::parseCommaDelimitedHeader(absl::string_view header_value) { |
711 | 0 | std::vector<absl::string_view> values; |
712 | 0 | for (absl::string_view s : absl::StrSplit(header_value, ',')) { |
713 | 0 | absl::string_view token = absl::StripAsciiWhitespace(s); |
714 | 0 | if (token.empty()) { |
715 | 0 | continue; |
716 | 0 | } |
717 | 0 | values.emplace_back(token); |
718 | 0 | } |
719 | 0 | return values; |
720 | 0 | } |
721 | | |
722 | 0 | absl::string_view HeaderUtility::getSemicolonDelimitedAttribute(absl::string_view value) { |
723 | 0 | return absl::StripAsciiWhitespace(StringUtil::cropRight(value, ";")); |
724 | 0 | } |
725 | | |
726 | | std::string HeaderUtility::addEncodingToAcceptEncoding(absl::string_view accept_encoding_header, |
727 | 0 | absl::string_view encoding) { |
728 | | // Append the content encoding only if it isn't already present in the |
729 | | // accept_encoding header. If it is present with a q-value ("gzip;q=0.3"), |
730 | | // remove the q-value to indicate that the content encoding setting that we |
731 | | // add has max priority (i.e. q-value 1.0). |
732 | 0 | std::vector<absl::string_view> newContentEncodings; |
733 | 0 | std::vector<absl::string_view> contentEncodings = |
734 | 0 | Http::HeaderUtility::parseCommaDelimitedHeader(accept_encoding_header); |
735 | 0 | for (absl::string_view contentEncoding : contentEncodings) { |
736 | 0 | absl::string_view strippedEncoding = |
737 | 0 | Http::HeaderUtility::getSemicolonDelimitedAttribute(contentEncoding); |
738 | 0 | if (strippedEncoding != encoding) { |
739 | | // Add back all content encodings back except for the content encoding that we want to |
740 | | // add. For example, if content encoding is "gzip", this filters out encodings "gzip" and |
741 | | // "gzip;q=0.6". |
742 | 0 | newContentEncodings.push_back(contentEncoding); |
743 | 0 | } |
744 | 0 | } |
745 | | // Finally add a single instance of our content encoding. |
746 | 0 | newContentEncodings.push_back(encoding); |
747 | 0 | return absl::StrJoin(newContentEncodings, ","); |
748 | 0 | } |
749 | | |
750 | 665 | bool HeaderUtility::isStandardConnectRequest(const Http::RequestHeaderMap& headers) { |
751 | 665 | return headers.getMethodValue() == Http::Headers::get().MethodValues.Connect && |
752 | 665 | headers.getProtocolValue().empty(); |
753 | 665 | } |
754 | | |
755 | 0 | bool HeaderUtility::isExtendedH2ConnectRequest(const Http::RequestHeaderMap& headers) { |
756 | 0 | return headers.getMethodValue() == Http::Headers::get().MethodValues.Connect && |
757 | 0 | !headers.getProtocolValue().empty(); |
758 | 0 | } |
759 | | |
760 | 225 | bool HeaderUtility::isPseudoHeader(absl::string_view header_name) { |
761 | 225 | return !header_name.empty() && header_name[0] == ':'; |
762 | 225 | } |
763 | | |
764 | | } // namespace Http |
765 | | } // namespace Envoy |