/src/suricata8/rust/htp/src/transaction.rs
Line | Count | Source |
1 | | use crate::{ |
2 | | bstr::Bstr, |
3 | | c_api::transaction::htp_tx_get_user_data, |
4 | | config::{Config, HtpUnwanted}, |
5 | | connection_parser::ParserData, |
6 | | decompressors::{Decompressor, HtpContentEncoding}, |
7 | | error::Result, |
8 | | headers::{Parser as HeaderParser, Side}, |
9 | | hook::DataHook, |
10 | | log::Logger, |
11 | | parsers::{parse_authorization, parse_content_length, parse_content_type, parse_hostport}, |
12 | | request::HtpMethod, |
13 | | uri::Uri, |
14 | | util::{validate_hostname, FlagOperations, HtpFlags}, |
15 | | HtpStatus, |
16 | | }; |
17 | | |
18 | | use std::any::Any; |
19 | | #[cfg(test)] |
20 | | use std::cmp::Ordering; |
21 | | |
22 | | #[derive(Debug, Clone)] |
23 | | /// This structure is used to pass transaction data (for example |
24 | | /// request and response body buffers) to callbacks. |
25 | | pub struct Data<'a> { |
26 | | /// Transaction pointer. |
27 | | tx: *mut Transaction, |
28 | | /// Ref to the parser data. |
29 | | data: &'a ParserData<'a>, |
30 | | } |
31 | | |
32 | | impl<'a> Data<'a> { |
33 | | /// Construct a new Data. |
34 | 7.95M | pub(crate) fn new(tx: *mut Transaction, data: &'a ParserData<'a>) -> Self { |
35 | 7.95M | Self { tx, data } |
36 | 7.95M | } |
37 | | |
38 | | /// Returns the transaction associated with the Data. |
39 | 7.17M | pub(crate) fn tx(&self) -> *mut Transaction { |
40 | 7.17M | self.tx |
41 | 7.17M | } |
42 | | |
43 | | /// Returns a pointer to the raw data associated with Data. |
44 | 7.10M | pub(crate) fn data(&self) -> *const u8 { |
45 | 7.10M | self.data.data_ptr() |
46 | 7.10M | } |
47 | | |
48 | | /// Returns the length of the data. |
49 | 21.1M | pub(crate) fn len(&self) -> usize { |
50 | 21.1M | self.data.len() |
51 | 21.1M | } |
52 | | |
53 | | /// Determine whether this data is empty. |
54 | 12.0M | pub(crate) fn is_empty(&self) -> bool { |
55 | 12.0M | self.len() == 0 |
56 | 12.0M | } |
57 | | |
58 | | /// Returns a reference to the internal ParserData struct. |
59 | 0 | pub(crate) fn parser_data(&self) -> &ParserData<'_> { |
60 | 0 | self.data |
61 | 0 | } |
62 | | } |
63 | | |
64 | | /// Enumerates the possible request and response body codings. |
65 | | #[repr(C)] |
66 | | #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
67 | | pub(crate) enum HtpTransferCoding { |
68 | | /// Body coding not determined yet. |
69 | | Unknown, |
70 | | /// No body. |
71 | | NoBody, |
72 | | /// Identity coding is used, which means that the body was sent as is. |
73 | | Identity, |
74 | | /// Chunked encoding. |
75 | | Chunked, |
76 | | /// We could not recognize the encoding. |
77 | | Invalid, |
78 | | } |
79 | | |
80 | | /// Enumerates the possible server personalities. |
81 | | #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
82 | | pub(crate) enum HtpResponseNumber { |
83 | | /// Default |
84 | | Unknown, |
85 | | /// Could not resolve response number |
86 | | Invalid, |
87 | | /// Valid response number |
88 | | Valid(u16), |
89 | | } |
90 | | |
91 | | impl HtpResponseNumber { |
92 | | /// Determine if the response status number is in the given range. |
93 | 57.8k | pub(crate) fn in_range(self, min: u16, max: u16) -> bool { |
94 | | use HtpResponseNumber::*; |
95 | 57.8k | match self { |
96 | 6.71k | Unknown | Invalid => false, |
97 | 51.1k | Valid(ref status) => status >= &min && status <= &max, |
98 | | } |
99 | 57.8k | } |
100 | | |
101 | | /// Determine if the response status number matches the |
102 | | /// given status number. |
103 | 46.3k | pub(crate) fn eq_num(self, num: u16) -> bool { |
104 | | use HtpResponseNumber::*; |
105 | 46.3k | match self { |
106 | 5.39k | Unknown | Invalid => false, |
107 | 40.9k | Valid(ref status) => status == &num, |
108 | | } |
109 | 46.3k | } |
110 | | } |
111 | | |
112 | | /// Represents a single request or response header. |
113 | | #[derive(Clone, Debug)] |
114 | | pub struct Header { |
115 | | /// Header name. |
116 | | pub name: Bstr, |
117 | | /// Header value. |
118 | | pub value: Bstr, |
119 | | /// Parsing flags; a combination of: HTP_FIELD_INVALID, HTP_FIELD_FOLDED, HTP_FIELD_REPEATED. |
120 | | pub flags: u64, |
121 | | } |
122 | | |
123 | | /// Table of request or response headers. |
124 | | #[derive(Clone, Debug)] |
125 | | pub struct Headers { |
126 | | /// Entries in the table. |
127 | | pub elements: Vec<Header>, |
128 | | } |
129 | | |
130 | | impl Headers { |
131 | | /// Make a new owned Headers Table with given capacity |
132 | 1.58M | pub(crate) fn with_capacity(size: usize) -> Self { |
133 | 1.58M | Self { |
134 | 1.58M | elements: Vec::with_capacity(size), |
135 | 1.58M | } |
136 | 1.58M | } |
137 | | |
138 | | /// Search the Headers table for the first tuple with a tuple key matching the given slice, ignoring ascii case and any zeros in self |
139 | | /// |
140 | | /// Returns None if no match is found. |
141 | 3.03M | pub(crate) fn get_nocase_nozero<K: AsRef<[u8]>>(&self, key: K) -> Option<&Header> { |
142 | 3.03M | self.elements |
143 | 3.03M | .iter() |
144 | 3.05M | .find(|x| x.name.cmp_nocase_nozero(key.as_ref())) <suricata_htp::transaction::Headers>::get_nocase_nozero::<&[u8]>::{closure#0}Line | Count | Source | 144 | 318k | .find(|x| x.name.cmp_nocase_nozero(key.as_ref())) |
<suricata_htp::transaction::Headers>::get_nocase_nozero::<&str>::{closure#0}Line | Count | Source | 144 | 2.74M | .find(|x| x.name.cmp_nocase_nozero(key.as_ref())) |
|
145 | 3.03M | } <suricata_htp::transaction::Headers>::get_nocase_nozero::<&[u8]> Line | Count | Source | 141 | 865k | pub(crate) fn get_nocase_nozero<K: AsRef<[u8]>>(&self, key: K) -> Option<&Header> { | 142 | 865k | self.elements | 143 | 865k | .iter() | 144 | 865k | .find(|x| x.name.cmp_nocase_nozero(key.as_ref())) | 145 | 865k | } |
<suricata_htp::transaction::Headers>::get_nocase_nozero::<&str> Line | Count | Source | 141 | 2.16M | pub(crate) fn get_nocase_nozero<K: AsRef<[u8]>>(&self, key: K) -> Option<&Header> { | 142 | 2.16M | self.elements | 143 | 2.16M | .iter() | 144 | 2.16M | .find(|x| x.name.cmp_nocase_nozero(key.as_ref())) | 145 | 2.16M | } |
|
146 | | |
147 | | /// Search the Headers table for the first tuple with a tuple key matching the given slice, ignoring ascii case and any zeros in self |
148 | | /// |
149 | | /// Returns None if no match is found. |
150 | 422k | pub(crate) fn get_nocase_nozero_mut<K: AsRef<[u8]>>(&mut self, key: K) -> Option<&mut Header> { |
151 | 422k | self.elements |
152 | 422k | .iter_mut() |
153 | 524k | .find(|x| x.name.cmp_nocase_nozero(key.as_ref())) |
154 | 422k | } |
155 | | |
156 | | /// Search the Headers table for the first tuple with a key matching the given slice, ingnoring ascii case in self |
157 | | /// |
158 | | /// Returns None if no match is found. |
159 | 3.26M | pub(crate) fn get_nocase_mut<K: AsRef<[u8]>>(&mut self, key: K) -> Option<&mut Header> { |
160 | 3.26M | self.elements |
161 | 3.26M | .iter_mut() |
162 | 5.99M | .find(|x| x.name.cmp_nocase(key.as_ref())) |
163 | 3.26M | } |
164 | | |
165 | | /// Search the Headers table for the first tuple with a key matching the given slice, ingnoring ascii case in self |
166 | | /// |
167 | | /// Returns None if no match is found. |
168 | 0 | pub(crate) fn get_nocase<K: AsRef<[u8]>>(&self, key: K) -> Option<&Header> { |
169 | 0 | self.elements |
170 | 0 | .iter() |
171 | 0 | .find(|x| x.name.cmp_nocase(key.as_ref())) |
172 | 0 | } |
173 | | |
174 | | /// Returns the number of elements in the Headers table |
175 | 14.1k | pub(crate) fn size(&self) -> usize { |
176 | 14.1k | self.elements.len() |
177 | 14.1k | } |
178 | | } |
179 | | |
180 | | impl<'a> IntoIterator for &'a Headers { |
181 | | type Item = &'a Header; |
182 | | type IntoIter = std::slice::Iter<'a, Header>; |
183 | | |
184 | 0 | fn into_iter(self) -> std::slice::Iter<'a, Header> { |
185 | 0 | self.elements.iter() |
186 | 0 | } |
187 | | } |
188 | | |
189 | | impl IntoIterator for Headers { |
190 | | type Item = Header; |
191 | | type IntoIter = std::vec::IntoIter<Header>; |
192 | | |
193 | 0 | fn into_iter(self) -> std::vec::IntoIter<Header> { |
194 | 0 | self.elements.into_iter() |
195 | 0 | } |
196 | | } |
197 | | |
198 | | impl Header { |
199 | | /// Construct a new header. |
200 | | #[cfg(test)] |
201 | | pub(crate) fn new(name: Bstr, value: Bstr) -> Self { |
202 | | Self::new_with_flags(name, value, 0) |
203 | | } |
204 | | |
205 | | /// Construct a new header with flags. |
206 | 3.26M | pub(crate) fn new_with_flags(name: Bstr, value: Bstr, flags: u64) -> Self { |
207 | 3.26M | Self { name, value, flags } |
208 | 3.26M | } |
209 | | } |
210 | | |
211 | | /// Possible states of a progressing transaction. Internally, progress will change |
212 | | /// to the next state when the processing activities associated with that state |
213 | | /// begin. For example, when we start to process request line bytes, the request |
214 | | /// state will change from NOT_STARTED to LINE.* |
215 | | #[repr(C)] |
216 | | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Debug)] |
217 | | pub enum HtpResponseProgress { |
218 | | /// Default state. |
219 | | NOT_STARTED, |
220 | | /// Response Line. |
221 | | LINE, |
222 | | /// Response Headers. |
223 | | HEADERS, |
224 | | /// Response Body. |
225 | | BODY, |
226 | | /// Trailer data. |
227 | | TRAILER, |
228 | | /// Response completed. |
229 | | COMPLETE, |
230 | | /// Error involving response side of transaction. |
231 | | ERROR, |
232 | | /// Response gap. |
233 | | GAP, |
234 | | } |
235 | | |
236 | | /// Possible states of a progressing transaction. Internally, progress will change |
237 | | /// to the next state when the processing activities associated with that state |
238 | | /// begin. For example, when we start to process request line bytes, the request |
239 | | /// state will change from NOT_STARTED to LINE.* |
240 | | #[repr(C)] |
241 | | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)] |
242 | | pub enum HtpRequestProgress { |
243 | | /// Default state. |
244 | | NOT_STARTED, |
245 | | /// In request line state. |
246 | | LINE, |
247 | | /// In request headers state. |
248 | | HEADERS, |
249 | | /// In request body state. |
250 | | BODY, |
251 | | /// Trailer data. |
252 | | TRAILER, |
253 | | /// Request is completed. |
254 | | COMPLETE, |
255 | | /// Error involving request side of transaction. |
256 | | ERROR, |
257 | | /// In request gap state. |
258 | | GAP, |
259 | | } |
260 | | |
261 | | /// Enumerates the possible values for authentication type. |
262 | | #[repr(C)] |
263 | | #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
264 | | pub enum HtpAuthType { |
265 | | /// This is the default value that is used before |
266 | | /// the presence of authentication is determined (e.g., |
267 | | /// before request headers are seen). |
268 | | Unknown, |
269 | | /// No authentication. |
270 | | NONE, |
271 | | /// HTTP Basic authentication used. |
272 | | BASIC, |
273 | | /// HTTP Digest authentication used. |
274 | | DIGEST, |
275 | | /// HTTP Bearer authentication used. |
276 | | BEARER, |
277 | | /// Unrecognized authentication method. |
278 | | UNRECOGNIZED = 9, |
279 | | /// Error retrieving the auth type. |
280 | | ERROR, |
281 | | } |
282 | | |
283 | | /// Protocol version constants. |
284 | | #[repr(C)] |
285 | | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)] |
286 | | pub enum HtpProtocol { |
287 | | /// Error with the transaction side. |
288 | | Error = -3, |
289 | | /// Could not resolve protocol version number. |
290 | | Invalid = -2, |
291 | | /// Default protocol value. |
292 | | Unknown = -1, |
293 | | /// HTTP/0.9 version. |
294 | | V0_9 = 9, |
295 | | /// HTTP/1.0 version. |
296 | | V1_0 = 100, |
297 | | /// HTTP/1.1 version. |
298 | | V1_1 = 101, |
299 | | } |
300 | | |
301 | | /// Represents a single HTTP transaction, which is a combination of a request and a response. |
302 | | pub struct Transaction { |
303 | | /// The logger structure associated with this transaction |
304 | | pub(crate) logger: Logger, |
305 | | /// The configuration structure associated with this transaction. |
306 | | pub(crate) cfg: &'static Config, |
307 | | /// The user data associated with this transaction. |
308 | | pub(crate) user_data: Option<Box<dyn Any>>, |
309 | | // Request fields |
310 | | /// Contains a count of how many empty lines were skipped before the request line. |
311 | | pub(crate) request_ignored_lines: u32, |
312 | | /// The first line of this request. |
313 | | pub(crate) request_line: Option<Bstr>, |
314 | | /// Request method. |
315 | | pub(crate) request_method: Option<Bstr>, |
316 | | /// Request method, as number. Available only if we were able to recognize the request method. |
317 | | pub(crate) request_method_number: HtpMethod, |
318 | | /// Request URI, raw, as given to us on the request line. This field can take different forms, |
319 | | /// for example authority for CONNECT methods, absolute URIs for proxy requests, and the query |
320 | | /// string when one is provided. Use Transaction::parsed_uri if you need to access to specific |
321 | | /// URI elements. Can be NULL if the request line contains only a request method (which is |
322 | | /// an extreme case of HTTP/0.9, but passes in practice. |
323 | | pub(crate) request_uri: Option<Bstr>, |
324 | | /// Request protocol, as text. Can be NULL if no protocol was specified. |
325 | | pub(crate) request_protocol: Option<Bstr>, |
326 | | /// Protocol version as a number. Multiply the high version number by 100, then add the low |
327 | | /// version number. You should prefer to work the pre-defined HtpProtocol constants. |
328 | | pub(crate) request_protocol_number: HtpProtocol, |
329 | | /// Is this request using HTTP/0.9? We need a separate field for this purpose because |
330 | | /// the protocol version alone is not sufficient to determine if HTTP/0.9 is used. For |
331 | | /// example, if you submit "GET / HTTP/0.9" to Apache, it will not treat the request |
332 | | /// as HTTP/0.9. |
333 | | pub(crate) is_protocol_0_9: bool, |
334 | | /// This structure holds the individual components parsed out of the request URI, with |
335 | | /// appropriate normalization and transformation applied, per configuration. No information |
336 | | /// is added. In extreme cases when no URI is provided on the request line, all fields |
337 | | /// will be NULL. (Well, except for port_number, which will be -1.) To inspect raw data, use |
338 | | /// Transaction::request_uri or Transaction::parsed_uri_raw. |
339 | | pub(crate) parsed_uri: Option<Uri>, |
340 | | /// This structure holds the individual components parsed out of the request URI, but |
341 | | /// without any modification. The purpose of this field is to allow you to look at the data as it |
342 | | /// was supplied on the request line. Fields can be NULL, depending on what data was supplied. |
343 | | /// The port_number field is always -1. |
344 | | pub(crate) parsed_uri_raw: Option<Uri>, |
345 | | /// This structure holds the whole normalized uri, including path, query, fragment, scheme, username, password, hostname, and port |
346 | | pub(crate) complete_normalized_uri: Option<Bstr>, |
347 | | /// This structure holds the normalized uri, including path, query, and fragment |
348 | | pub(crate) partial_normalized_uri: Option<Bstr>, |
349 | | /// HTTP 1.1 RFC |
350 | | /// |
351 | | /// 4.3 Message Body |
352 | | /// |
353 | | /// The message-body (if any) of an HTTP message is used to carry the |
354 | | /// entity-body associated with the request or response. The message-body |
355 | | /// differs from the entity-body only when a transfer-coding has been |
356 | | /// applied, as indicated by the Transfer-Encoding header field (section |
357 | | /// 14.41). |
358 | | /// |
359 | | /// ```text |
360 | | /// message-body = entity-body |
361 | | /// | <entity-body encoded as per Transfer-Encoding> |
362 | | /// ``` |
363 | | /// |
364 | | /// The length of the request message-body. In most cases, this value |
365 | | /// will be the same as request_entity_len. The values will be different |
366 | | /// if request compression or chunking were applied. In that case, |
367 | | /// request_message_len contains the length of the request body as it |
368 | | /// has been seen over TCP; request_entity_len contains length after |
369 | | /// de-chunking and decompression. |
370 | | pub(crate) request_message_len: u64, |
371 | | /// The length of the request entity-body. In most cases, this value |
372 | | /// will be the same as request_message_len. The values will be different |
373 | | /// if request compression or chunking were applied. In that case, |
374 | | /// request_message_len contains the length of the request body as it |
375 | | /// has been seen over TCP; request_entity_len contains length after |
376 | | /// de-chunking and decompression. |
377 | | pub(crate) request_entity_len: u64, |
378 | | /// Parsed request headers. |
379 | | pub(crate) request_headers: Headers, |
380 | | /// Request transfer coding. Can be one of UNKNOWN (body presence not |
381 | | /// determined yet), IDENTITY, CHUNKED, NO_BODY, |
382 | | /// and UNRECOGNIZED. |
383 | | pub(crate) request_transfer_coding: HtpTransferCoding, |
384 | | /// Request body compression, which indicates if compression is used |
385 | | /// for the request body. This field is an interpretation of the information |
386 | | /// available in request headers. |
387 | | pub(crate) request_content_encoding: HtpContentEncoding, |
388 | | /// Request body compression processing information, which is related to how |
389 | | /// the library is going to process (or has processed) a request body. Changing |
390 | | /// this field mid-processing can influence library actions. For example, setting |
391 | | /// this field to NONE in a request_headers callback will prevent |
392 | | /// decompression. |
393 | | pub(crate) request_content_encoding_processing: HtpContentEncoding, |
394 | | /// This field will contain the request content type when that information |
395 | | /// is available in request headers. The contents of the field will be converted |
396 | | /// to lowercase and any parameters (e.g., character set information) removed. |
397 | | pub(crate) request_content_type: Option<Bstr>, |
398 | | /// Request decompressor used to decompress request body data. |
399 | | pub(crate) request_decompressor: Option<Decompressor>, |
400 | | /// Contains the value specified in the Content-Length header. The value of this |
401 | | /// field will be None from the beginning of the transaction and until request |
402 | | /// headers are processed. It will stay None if the C-L header was not provided, |
403 | | /// or if the value in it cannot be parsed. |
404 | | pub(crate) request_content_length: Option<u64>, |
405 | | /// Transaction-specific REQUEST_BODY_DATA hook. Behaves as |
406 | | /// the configuration hook with the same name. |
407 | | pub(crate) hook_request_body_data: DataHook, |
408 | | /// Transaction-specific RESPONSE_BODY_DATA hook. Behaves as |
409 | | /// the configuration hook with the same name. |
410 | | pub(crate) hook_response_body_data: DataHook, |
411 | | /// Authentication type used in the request. |
412 | | pub(crate) request_auth_type: HtpAuthType, |
413 | | /// Authentication username. |
414 | | pub(crate) request_auth_username: Option<Bstr>, |
415 | | /// Authentication password. Available only when Transaction::request_auth_type is HTP_AUTH_BASIC. |
416 | | pub(crate) request_auth_password: Option<Bstr>, |
417 | | /// Authentication token. Available only when Transaction::request_auth_type is HTP_AUTH_BEARER. |
418 | | pub(crate) request_auth_token: Option<Bstr>, |
419 | | /// Request hostname. Per the RFC, the hostname will be taken from the Host header |
420 | | /// when available. If the host information is also available in the URI, it is used |
421 | | /// instead of whatever might be in the Host header. Can be NULL. This field does |
422 | | /// not contain port information. |
423 | | pub(crate) request_hostname: Option<Bstr>, |
424 | | /// Request port number, if presented. The rules for Transaction::request_host apply. Set to |
425 | | /// None by default. |
426 | | pub(crate) request_port_number: Option<u16>, |
427 | | |
428 | | // Response fields |
429 | | /// How many empty lines did we ignore before reaching the status line? |
430 | | pub(crate) response_ignored_lines: u32, |
431 | | /// Response line. |
432 | | pub(crate) response_line: Option<Bstr>, |
433 | | /// Response protocol, as text. Can be NULL. |
434 | | pub(crate) response_protocol: Option<Bstr>, |
435 | | /// Response protocol as number. Available only if we were able to parse the protocol version, |
436 | | /// INVALID otherwise. UNKNOWN until parsing is attempted. |
437 | | pub(crate) response_protocol_number: HtpProtocol, |
438 | | /// Response status code, as text. Starts as NULL and can remain NULL on |
439 | | /// an invalid response that does not specify status code. |
440 | | pub(crate) response_status: Option<Bstr>, |
441 | | /// Response status code, available only if we were able to parse it, HTP_STATUS_INVALID |
442 | | /// otherwise. HTP_STATUS_UNKNOWN until parsing is attempted. |
443 | | pub(crate) response_status_number: HtpResponseNumber, |
444 | | /// This field is set by the protocol decoder with it thinks that the |
445 | | /// backend server will reject a request with a particular status code. |
446 | | pub(crate) response_status_expected_number: HtpUnwanted, |
447 | | /// The message associated with the response status code. Can be NULL. |
448 | | pub(crate) response_message: Option<Bstr>, |
449 | | /// Have we seen the server respond with a 100 response? |
450 | | pub(crate) seen_100continue: bool, |
451 | | /// Parsed response headers. Contains instances of Header. |
452 | | pub(crate) response_headers: Headers, |
453 | | /// Is this a response a HTTP/2.0 upgrade? |
454 | | pub(crate) is_http_2_upgrade: bool, |
455 | | |
456 | | /// HTTP 1.1 RFC |
457 | | /// |
458 | | /// 4.3 Message Body |
459 | | /// |
460 | | /// The message-body (if any) of an HTTP message is used to carry the |
461 | | /// entity-body associated with the request or response. The message-body |
462 | | /// differs from the entity-body only when a transfer-coding has been |
463 | | /// applied, as indicated by the Transfer-Encoding header field (section |
464 | | /// 14.41). |
465 | | /// |
466 | | /// ```text |
467 | | /// message-body = entity-body |
468 | | /// | <entity-body encoded as per Transfer-Encoding> |
469 | | /// ``` |
470 | | /// |
471 | | /// The length of the response message-body. In most cases, this value |
472 | | /// will be the same as response_entity_len. The values will be different |
473 | | /// if response compression or chunking were applied. In that case, |
474 | | /// response_message_len contains the length of the response body as it |
475 | | /// has been seen over TCP; response_entity_len contains the length after |
476 | | /// de-chunking and decompression. |
477 | | pub(crate) response_message_len: u64, |
478 | | /// The length of the response entity-body. In most cases, this value |
479 | | /// will be the same as response_message_len. The values will be different |
480 | | /// if request compression or chunking were applied. In that case, |
481 | | /// response_message_len contains the length of the response body as it |
482 | | /// has been seen over TCP; response_entity_len contains length after |
483 | | /// de-chunking and decompression. |
484 | | pub(crate) response_entity_len: u64, |
485 | | /// Contains the value specified in the Content-Length header. The value of this |
486 | | /// field will be -1 from the beginning of the transaction and until response |
487 | | /// headers are processed. It will stay None if the C-L header was not provided, |
488 | | /// or if the value in it cannot be parsed. |
489 | | pub(crate) response_content_length: Option<u64>, |
490 | | /// Response transfer coding, which indicates if there is a response body, |
491 | | /// and how it is transported (e.g., as-is, or chunked). |
492 | | pub(crate) response_transfer_coding: HtpTransferCoding, |
493 | | /// Response body compression, which indicates if compression is used |
494 | | /// for the response body. This field is an interpretation of the information |
495 | | /// available in response headers. |
496 | | pub(crate) response_content_encoding: HtpContentEncoding, |
497 | | /// Response body compression processing information, which is related to how |
498 | | /// the library is going to process (or has processed) a response body. Changing |
499 | | /// this field mid-processing can influence library actions. For example, setting |
500 | | /// this field to NONE in a RESPONSE_HEADERS callback will prevent |
501 | | /// decompression. |
502 | | pub(crate) response_content_encoding_processing: HtpContentEncoding, |
503 | | /// This field will contain the response content type when that information |
504 | | /// is available in response headers. The contents of the field will be converted |
505 | | /// to lowercase and any parameters (e.g., character set information) removed. |
506 | | pub(crate) response_content_type: Option<Bstr>, |
507 | | /// Response decompressor used to decompress response body data. |
508 | | pub(crate) response_decompressor: Option<Decompressor>, |
509 | | |
510 | | // Common fields |
511 | | /// Parsing flags; a combination of: HTP_REQUEST_INVALID_T_E, HTP_INVALID_FOLDING, |
512 | | /// HTP_REQUEST_SMUGGLING, HTP_MULTI_PACKET_HEAD, and HTP_FIELD_UNPARSEABLE. |
513 | | pub(crate) flags: u64, |
514 | | /// Request progress. |
515 | | pub(crate) request_progress: HtpRequestProgress, |
516 | | /// Response progress. |
517 | | pub(crate) response_progress: HtpResponseProgress, |
518 | | /// Transaction index on the connection. |
519 | | pub(crate) index: usize, |
520 | | /// Total repetitions for headers in request. |
521 | | pub(crate) request_header_repetitions: u16, |
522 | | /// Total repetitions for headers in response. |
523 | | pub(crate) response_header_repetitions: u16, |
524 | | /// Request header parser |
525 | | pub(crate) request_header_parser: HeaderParser, |
526 | | /// Response header parser |
527 | | pub(crate) response_header_parser: HeaderParser, |
528 | | } |
529 | | |
530 | | impl std::fmt::Debug for Transaction { |
531 | 0 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
532 | 0 | f.debug_struct("Transaction") |
533 | 0 | .field("request_line", &self.request_line) |
534 | 0 | .field("request_method", &self.request_method) |
535 | 0 | .field("request_method_number", &self.request_method_number) |
536 | 0 | .field("request_uri", &self.request_uri) |
537 | 0 | .field("request_protocol", &self.request_protocol) |
538 | 0 | .field("request_protocol_number", &self.request_protocol_number) |
539 | 0 | .field("is_protocol_0_9", &self.is_protocol_0_9) |
540 | 0 | .field("parsed_uri", &self.parsed_uri) |
541 | 0 | .field("parsed_uri_raw", &self.parsed_uri_raw) |
542 | 0 | .field("complete_normalized_uri", &self.complete_normalized_uri) |
543 | 0 | .field("partial_normalized_uri", &self.partial_normalized_uri) |
544 | 0 | .field("request_message_len", &self.request_message_len) |
545 | 0 | .field("request_entity_len", &self.request_entity_len) |
546 | 0 | .field("request_headers", &self.request_headers) |
547 | 0 | .field("request_transfer_coding", &self.request_transfer_coding) |
548 | 0 | .field("request_content_encoding", &self.request_content_encoding) |
549 | 0 | .field( |
550 | 0 | "request_content_encoding_processing", |
551 | 0 | &self.request_content_encoding_processing, |
552 | 0 | ) |
553 | 0 | .field("request_content_type", &self.request_content_type) |
554 | 0 | .field("request_content_length", &self.request_content_length) |
555 | 0 | .field("request_auth_type", &self.request_auth_type) |
556 | 0 | .field("request_auth_username", &self.request_auth_username) |
557 | 0 | .field("request_auth_password", &self.request_auth_password) |
558 | 0 | .field("request_auth_token", &self.request_auth_token) |
559 | 0 | .field("request_hostname", &self.request_hostname) |
560 | 0 | .field("request_port_number", &self.request_port_number) |
561 | 0 | .field("request_ignored_lines", &self.request_ignored_lines) |
562 | 0 | .field("response_ignored_lines", &self.response_ignored_lines) |
563 | 0 | .field("response_line", &self.response_line) |
564 | 0 | .field("response_protocol", &self.response_protocol) |
565 | 0 | .field("response_protocol_number", &self.response_protocol_number) |
566 | 0 | .field("response_status", &self.response_status) |
567 | 0 | .field("response_status_number", &self.response_status_number) |
568 | 0 | .field( |
569 | 0 | "response_status_expected_number", |
570 | 0 | &self.response_status_expected_number, |
571 | 0 | ) |
572 | 0 | .field("response_message", &self.response_message) |
573 | 0 | .field("seen_100continue", &self.seen_100continue) |
574 | 0 | .field("response_headers", &self.response_headers) |
575 | 0 | .field("is_http_2_upgrade", &self.is_http_2_upgrade) |
576 | 0 | .field("response_message_len", &self.response_message_len) |
577 | 0 | .field("response_entity_len", &self.response_entity_len) |
578 | 0 | .field("response_content_length", &self.response_content_length) |
579 | 0 | .field("response_transfer_coding", &self.response_transfer_coding) |
580 | 0 | .field("response_content_encoding", &self.response_content_encoding) |
581 | 0 | .field( |
582 | 0 | "response_content_encoding_processing", |
583 | 0 | &self.response_content_encoding_processing, |
584 | 0 | ) |
585 | 0 | .field("response_content_type", &self.response_content_type) |
586 | 0 | .field("flags", &self.flags) |
587 | 0 | .field("request_progress", &self.request_progress) |
588 | 0 | .field("response_progress", &self.response_progress) |
589 | 0 | .field("index", &self.index) |
590 | 0 | .field( |
591 | 0 | "request_header_repetitions", |
592 | 0 | &self.request_header_repetitions, |
593 | 0 | ) |
594 | 0 | .field( |
595 | 0 | "response_header_repetitions", |
596 | 0 | &self.response_header_repetitions, |
597 | 0 | ) |
598 | 0 | .finish() |
599 | 0 | } |
600 | | } |
601 | | |
602 | | impl Drop for Transaction { |
603 | 794k | fn drop(&mut self) { |
604 | 794k | if self.user_data.is_none() { |
605 | 0 | return; |
606 | 794k | } |
607 | 794k | if let Some(cb) = self.cfg.hook_tx_destroy { |
608 | 794k | unsafe { cb(htp_tx_get_user_data(self)) }; |
609 | 794k | } |
610 | 794k | } |
611 | | } |
612 | | |
613 | | impl Transaction { |
614 | | /// Construct a new transaction. |
615 | 794k | pub(crate) fn new( |
616 | 794k | cfg: &'static Config, logger: &Logger, index: usize, req: bool, |
617 | 794k | ) -> Option<Self> { |
618 | 794k | let mut tx = Self { |
619 | 794k | logger: logger.clone(), |
620 | 794k | cfg, |
621 | 794k | user_data: None, |
622 | 794k | request_ignored_lines: 0, |
623 | 794k | request_line: None, |
624 | 794k | request_method: None, |
625 | 794k | request_method_number: HtpMethod::Unknown, |
626 | 794k | request_uri: None, |
627 | 794k | request_protocol: None, |
628 | 794k | request_protocol_number: HtpProtocol::Unknown, |
629 | 794k | is_protocol_0_9: false, |
630 | 794k | parsed_uri: None, |
631 | 794k | parsed_uri_raw: None, |
632 | 794k | complete_normalized_uri: None, |
633 | 794k | partial_normalized_uri: None, |
634 | 794k | request_message_len: 0, |
635 | 794k | request_entity_len: 0, |
636 | 794k | request_headers: Headers::with_capacity(32), |
637 | 794k | request_transfer_coding: HtpTransferCoding::Unknown, |
638 | 794k | request_content_encoding: HtpContentEncoding::None, |
639 | 794k | request_content_encoding_processing: HtpContentEncoding::None, |
640 | 794k | request_content_type: None, |
641 | 794k | request_content_length: None, |
642 | 794k | request_decompressor: None, |
643 | 794k | hook_request_body_data: DataHook::default(), |
644 | 794k | hook_response_body_data: DataHook::default(), |
645 | 794k | request_auth_type: HtpAuthType::Unknown, |
646 | 794k | request_auth_username: None, |
647 | 794k | request_auth_password: None, |
648 | 794k | request_auth_token: None, |
649 | 794k | request_hostname: None, |
650 | 794k | request_port_number: None, |
651 | 794k | response_ignored_lines: 0, |
652 | 794k | response_line: None, |
653 | 794k | response_protocol: None, |
654 | 794k | response_protocol_number: HtpProtocol::Unknown, |
655 | 794k | response_status: None, |
656 | 794k | response_status_number: HtpResponseNumber::Unknown, |
657 | 794k | response_status_expected_number: HtpUnwanted::Ignore, |
658 | 794k | response_message: None, |
659 | 794k | seen_100continue: false, |
660 | 794k | response_headers: Headers::with_capacity(32), |
661 | 794k | is_http_2_upgrade: false, |
662 | 794k | response_message_len: 0, |
663 | 794k | response_entity_len: 0, |
664 | 794k | response_content_length: None, |
665 | 794k | response_transfer_coding: HtpTransferCoding::Unknown, |
666 | 794k | response_content_encoding: HtpContentEncoding::None, |
667 | 794k | response_content_encoding_processing: HtpContentEncoding::None, |
668 | 794k | response_content_type: None, |
669 | 794k | response_decompressor: None, |
670 | 794k | flags: 0, |
671 | 794k | request_progress: HtpRequestProgress::NOT_STARTED, |
672 | 794k | response_progress: HtpResponseProgress::NOT_STARTED, |
673 | 794k | index, |
674 | 794k | request_header_repetitions: 0, |
675 | 794k | response_header_repetitions: 0, |
676 | 794k | request_header_parser: HeaderParser::new(Side::Request), |
677 | 794k | response_header_parser: HeaderParser::new(Side::Response), |
678 | 794k | }; |
679 | 794k | if let Some(cb) = cfg.hook_tx_create { |
680 | 794k | let r = unsafe { cb(req) }; |
681 | 794k | if r.is_null() { |
682 | 0 | return None; |
683 | 794k | } |
684 | 794k | tx.set_user_data(Box::new(r)); |
685 | 0 | } |
686 | 794k | Some(tx) |
687 | 794k | } |
688 | | |
689 | | /// Has this transaction started? |
690 | 214M | pub(crate) fn is_started(&self) -> bool { |
691 | 214M | !(self.request_progress == HtpRequestProgress::NOT_STARTED |
692 | 8.84M | && self.response_progress == HtpResponseProgress::NOT_STARTED) |
693 | 214M | } |
694 | | |
695 | | /// Set the user data. |
696 | 794k | pub(crate) fn set_user_data(&mut self, data: Box<dyn Any + 'static>) { |
697 | 794k | self.user_data = Some(data); |
698 | 794k | } |
699 | | |
700 | | /// Get a reference to the user data. |
701 | 118M | pub(crate) fn user_data<T: 'static>(&self) -> Option<&T> { |
702 | 118M | self.user_data |
703 | 118M | .as_ref() |
704 | 118M | .and_then(|ud| ud.downcast_ref::<T>()) |
705 | 118M | } |
706 | | |
707 | | /// Get a mutable reference to the user data. |
708 | | #[cfg(test)] |
709 | | pub(crate) fn user_data_mut<T: 'static>(&mut self) -> Option<&mut T> { |
710 | | self.user_data |
711 | | .as_mut() |
712 | | .and_then(|ud| ud.downcast_mut::<T>()) |
713 | | } |
714 | | |
715 | | /// Determine if the request has a body. |
716 | 431k | pub(crate) fn request_has_body(&self) -> bool { |
717 | 431k | self.request_transfer_coding == HtpTransferCoding::Identity |
718 | 431k | || self.request_transfer_coding == HtpTransferCoding::Chunked |
719 | 431k | } |
720 | | |
721 | | /// Process the extracted request headers and set the appropriate flags |
722 | 422k | pub(crate) fn process_request_headers(&mut self) -> Result<()> { |
723 | | // Determine if we have a request body, and how it is packaged. |
724 | 422k | let cl_opt = self.request_headers.get_nocase_nozero("content-length"); |
725 | | // Check for the Transfer-Encoding header, which would indicate a chunked request body. |
726 | 422k | if let Some(te) = self.request_headers.get_nocase_nozero("transfer-encoding") { |
727 | | // Make sure it contains "chunked" only. |
728 | | // TODO The HTTP/1.1 RFC also allows the T-E header to contain "identity", which |
729 | | // presumably should have the same effect as T-E header absence. However, Apache |
730 | | // (2.2.22 on Ubuntu 12.04 LTS) instead errors out with "Unknown Transfer-Encoding: identity". |
731 | | // And it behaves strangely, too, sending a 501 and proceeding to process the request |
732 | | // (e.g., PHP is run), but without the body. It then closes the connection. |
733 | 0 | if te.value.index_of_nocase_nozero("chunked").is_none() { |
734 | | // Invalid T-E header value. |
735 | 0 | self.request_transfer_coding = HtpTransferCoding::Invalid; |
736 | 0 | self.flags.set(HtpFlags::REQUEST_INVALID_T_E); |
737 | 0 | self.flags.set(HtpFlags::REQUEST_INVALID) |
738 | | } else { |
739 | | // Chunked encoding is a HTTP/1.1 feature, so check that an earlier protocol |
740 | | // version is not used. The flag will also be set if the protocol could not be parsed. |
741 | | // |
742 | | // TODO IIS 7.0, for example, would ignore the T-E header when it |
743 | | // it is used with a protocol below HTTP 1.1. This should be a |
744 | | // personality trait. |
745 | 0 | if self.request_protocol_number < HtpProtocol::V1_1 { |
746 | 0 | self.flags.set(HtpFlags::REQUEST_INVALID_T_E); |
747 | 0 | self.flags.set(HtpFlags::REQUEST_SMUGGLING); |
748 | 0 | } |
749 | | // If the T-E header is present we are going to use it. |
750 | 0 | self.request_transfer_coding = HtpTransferCoding::Chunked; |
751 | | // We are still going to check for the presence of C-L. |
752 | 0 | if cl_opt.is_some() { |
753 | | // According to the HTTP/1.1 RFC (section 4.4): |
754 | | // |
755 | | // "The Content-Length header field MUST NOT be sent |
756 | | // if these two lengths are different (i.e., if a Transfer-Encoding |
757 | | // header field is present). If a message is received with both a |
758 | | // Transfer-Encoding header field and a Content-Length header field, |
759 | | // the latter MUST be ignored." |
760 | | // |
761 | 0 | self.flags.set(HtpFlags::REQUEST_SMUGGLING) |
762 | 0 | } |
763 | | } |
764 | 422k | } else if let Some(cl) = cl_opt { |
765 | | // Check for a folded C-L header. |
766 | 0 | if cl.flags.is_set(HtpFlags::FIELD_FOLDED) { |
767 | 0 | self.flags.set(HtpFlags::REQUEST_SMUGGLING) |
768 | 0 | } |
769 | | // Check for multiple C-L headers. |
770 | 0 | if cl.flags.is_set(HtpFlags::FIELD_REPEATED) { |
771 | 0 | self.flags.set(HtpFlags::REQUEST_SMUGGLING) |
772 | | // TODO Personality trait to determine which C-L header to parse. |
773 | | // At the moment we're parsing the combination of all instances, |
774 | | // which is bound to fail (because it will contain commas). |
775 | 0 | } |
776 | | // Get the body length. |
777 | 0 | self.request_content_length = |
778 | 0 | parse_content_length(cl.value.as_slice(), Some(&mut self.logger)); |
779 | 0 | if self.request_content_length.is_some() { |
780 | | // We have a request body of known length. |
781 | 0 | self.request_transfer_coding = HtpTransferCoding::Identity |
782 | | } else { |
783 | 0 | self.request_transfer_coding = HtpTransferCoding::Invalid; |
784 | 0 | self.flags.set(HtpFlags::REQUEST_INVALID_C_L); |
785 | 0 | self.flags.set(HtpFlags::REQUEST_INVALID) |
786 | | } |
787 | | } else { |
788 | | // No body. |
789 | 422k | self.request_transfer_coding = HtpTransferCoding::NoBody |
790 | | } |
791 | | // If we could not determine the correct body handling, |
792 | | // consider the request invalid. |
793 | 422k | if self.request_transfer_coding == HtpTransferCoding::Unknown { |
794 | 0 | self.request_transfer_coding = HtpTransferCoding::Invalid; |
795 | 0 | self.flags.set(HtpFlags::REQUEST_INVALID) |
796 | 422k | } |
797 | | |
798 | | // Determine hostname. |
799 | | // Use the hostname from the URI, when available. |
800 | 422k | if let Some(hostname) = self.get_parsed_uri_hostname() { |
801 | 59.1k | self.request_hostname = Some(Bstr::from(hostname.as_slice())); |
802 | 363k | } |
803 | | |
804 | 422k | if let Some(port_number) = self.get_parsed_uri_port_number() { |
805 | 1.46k | self.request_port_number = Some(*port_number); |
806 | 420k | } |
807 | | // Examine the Host header. |
808 | 422k | if let Some(header) = self.request_headers.get_nocase_nozero_mut("host") { |
809 | | // Host information available in the headers. |
810 | 14.0k | if let Ok((_, (hostname, port_nmb, valid))) = parse_hostport(&header.value) { |
811 | 13.2k | if !valid { |
812 | 11.0k | self.flags.set(HtpFlags::HOSTH_INVALID) |
813 | 2.13k | } |
814 | | // The host information in the headers is valid. |
815 | | // Is there host information in the URI? |
816 | 13.2k | if self.request_hostname.is_none() { |
817 | | // There is no host information in the URI. Place the |
818 | | // hostname from the headers into the parsed_uri structure. |
819 | 8.78k | let mut hostname = Bstr::from(hostname); |
820 | 8.78k | hostname.make_ascii_lowercase(); |
821 | 8.78k | self.request_hostname = Some(hostname); |
822 | 8.78k | if let Some((_, port)) = port_nmb { |
823 | 1.14k | self.request_port_number = port; |
824 | 7.63k | } |
825 | | } else { |
826 | | // The host information appears in the URI and in the headers. The |
827 | | // HTTP RFC states that we should ignore the header copy. |
828 | | // Check for different hostnames. |
829 | 4.42k | if let Some(host) = &self.request_hostname { |
830 | 4.42k | if !host.cmp_nocase(hostname) { |
831 | 4.21k | self.flags.set(HtpFlags::HOST_AMBIGUOUS) |
832 | 213 | } |
833 | 0 | } |
834 | | |
835 | 4.42k | if let Some((_, port)) = port_nmb { |
836 | | // Check for different ports. |
837 | 620 | if self.request_port_number.is_some() && self.request_port_number != port { |
838 | 347 | self.flags.set(HtpFlags::HOST_AMBIGUOUS) |
839 | 273 | } |
840 | 3.80k | } |
841 | | } |
842 | 876 | } else if self.request_hostname.is_some() { |
843 | | // Invalid host information in the headers. |
844 | | // Raise the flag, even though the host information in the headers is invalid. |
845 | 798 | self.flags.set(HtpFlags::HOST_AMBIGUOUS) |
846 | 78 | } |
847 | | } else { |
848 | | // No host information in the headers. |
849 | | // HTTP/1.1 requires host information in the headers. |
850 | 408k | if self.request_protocol_number >= HtpProtocol::V1_1 { |
851 | 920 | self.flags.set(HtpFlags::HOST_MISSING) |
852 | 407k | } |
853 | | } |
854 | | // Determine Content-Type. |
855 | 422k | if let Some(ct) = self.request_headers.get_nocase_nozero("content-type") { |
856 | 7.00k | self.request_content_type = Some(parse_content_type(ct.value.as_slice())?); |
857 | 415k | } |
858 | | // Parse authentication information. |
859 | 422k | parse_authorization(self).or_else(|rc| { |
860 | 0 | if rc == HtpStatus::DECLINED { |
861 | | // Don't fail the stream if an authorization header is invalid, just set a flag. |
862 | 0 | self.flags.set(HtpFlags::AUTH_INVALID); |
863 | 0 | Ok(()) |
864 | | } else { |
865 | 0 | Err(rc) |
866 | | } |
867 | 0 | })?; |
868 | 422k | Ok(()) |
869 | 422k | } |
870 | | |
871 | | /// Sanity check the response line, logging if there is an invalid protocol or status number. |
872 | 19.9k | pub(crate) fn validate_response_line(&mut self) { |
873 | | // Is the response line valid? |
874 | 19.9k | if self.response_protocol_number == HtpProtocol::Invalid { |
875 | 17.3k | htp_warn!( |
876 | 17.3k | self.logger, |
877 | 17.3k | HtpLogCode::RESPONSE_LINE_INVALID_PROTOCOL, |
878 | 17.3k | "Invalid response line: invalid protocol" |
879 | 17.3k | ); |
880 | 17.3k | self.flags.set(HtpFlags::STATUS_LINE_INVALID) |
881 | 2.67k | } |
882 | 19.9k | if !self.response_status_number.in_range(100, 999) { |
883 | 2.64k | htp_warn!( |
884 | 2.64k | self.logger, |
885 | 2.64k | HtpLogCode::RESPONSE_LINE_INVALID_RESPONSE_STATUS, |
886 | 2.64k | "Invalid response line: invalid response status." |
887 | 2.64k | ); |
888 | 2.64k | self.response_status_number = HtpResponseNumber::Invalid; |
889 | 2.64k | self.flags.set(HtpFlags::STATUS_LINE_INVALID) |
890 | 17.3k | } |
891 | 19.9k | } |
892 | | |
893 | | /// Parse the raw request line |
894 | 434k | pub(crate) fn parse_request_line(&mut self) -> Result<()> { |
895 | | // Determine how to process the request URI. |
896 | 434k | let mut parsed_uri = Uri::with_config(self.cfg.decoder_cfg); |
897 | 434k | if self.request_method_number == HtpMethod::CONNECT { |
898 | | // When CONNECT is used, the request URI contains an authority string. |
899 | 4.05k | parsed_uri.parse_uri_hostport( |
900 | 4.05k | self.request_uri.as_ref().ok_or(HtpStatus::ERROR)?, |
901 | 4.02k | &mut self.flags, |
902 | | ); |
903 | 430k | } else if let Some(uri) = self.request_uri.as_ref() { |
904 | 211k | parsed_uri.parse_uri(uri.as_slice()); |
905 | 219k | } |
906 | 434k | self.parsed_uri_raw = Some(parsed_uri); |
907 | | // Parse the request URI into Transaction::parsed_uri_raw. |
908 | | // Build Transaction::parsed_uri, but only if it was not explicitly set already. |
909 | 434k | if self.parsed_uri.is_none() { |
910 | 434k | // Keep the original URI components, but create a copy which we can normalize and use internally. |
911 | 434k | self.normalize_parsed_uri(); |
912 | 434k | } |
913 | | |
914 | | // Check parsed_uri hostname. |
915 | 434k | if let Some(hostname) = self.get_parsed_uri_hostname() { |
916 | 61.3k | if !validate_hostname(hostname.as_slice()) { |
917 | 45.7k | self.flags.set(HtpFlags::HOSTU_INVALID) |
918 | 15.6k | } |
919 | 373k | } |
920 | 434k | Ok(()) |
921 | 434k | } |
922 | | |
923 | | #[cfg(test)] |
924 | | /// Determines if both request and response are complete. |
925 | | pub(crate) fn is_complete(&self) -> bool { |
926 | | // A transaction is considered complete only when both the request and |
927 | | // response are complete. (Sometimes a complete response can be seen |
928 | | // even while the request is ongoing.) |
929 | | self.request_progress == HtpRequestProgress::COMPLETE |
930 | | && self.response_progress == HtpResponseProgress::COMPLETE |
931 | | } |
932 | | |
933 | | /// Return a reference to the uri hostname. |
934 | 857k | pub(crate) fn get_parsed_uri_hostname(&self) -> Option<&Bstr> { |
935 | 857k | self.parsed_uri |
936 | 857k | .as_ref() |
937 | 857k | .and_then(|parsed_uri| parsed_uri.hostname.as_ref()) |
938 | 857k | } |
939 | | |
940 | | /// Return a reference to the uri port_number. |
941 | 422k | pub(crate) fn get_parsed_uri_port_number(&self) -> Option<&u16> { |
942 | 422k | self.parsed_uri |
943 | 422k | .as_ref() |
944 | 422k | .and_then(|parsed_uri| parsed_uri.port_number.as_ref()) |
945 | 422k | } |
946 | | |
947 | | /// Normalize a previously-parsed request URI. |
948 | 434k | pub(crate) fn normalize_parsed_uri(&mut self) { |
949 | 434k | let mut uri = Uri::with_config(self.cfg.decoder_cfg); |
950 | 434k | if let Some(incomplete) = &self.parsed_uri_raw { |
951 | 434k | uri.scheme = incomplete.normalized_scheme(); |
952 | 434k | uri.username = incomplete.normalized_username(&mut self.flags); |
953 | 434k | uri.password = incomplete.normalized_password(&mut self.flags); |
954 | 434k | uri.hostname = incomplete.normalized_hostname(&mut self.flags); |
955 | 434k | uri.port_number = incomplete.normalized_port(&mut self.flags); |
956 | 434k | uri.query = incomplete.query.clone(); |
957 | 434k | uri.fragment = incomplete.normalized_fragment(&mut self.flags); |
958 | 434k | uri.path = incomplete |
959 | 434k | .normalized_path(&mut self.flags, &mut self.response_status_expected_number); |
960 | 434k | } |
961 | 434k | self.parsed_uri = Some(uri); |
962 | 434k | } |
963 | | } |
964 | | |
965 | | impl PartialEq for Transaction { |
966 | | /// Determines if other references the same transaction. |
967 | 0 | fn eq(&self, other: &Self) -> bool { |
968 | 0 | self.index == other.index |
969 | 0 | } |
970 | | } |
971 | | |
972 | | #[test] |
973 | | fn GetNocaseNozero() { |
974 | | let mut t = Headers::with_capacity(2); |
975 | | let v1 = Bstr::from("Value1"); |
976 | | let mut k = Bstr::from("K\x00\x00\x00\x00ey\x001"); |
977 | | let mut h = Header::new(k, v1.clone()); |
978 | | t.elements.push(h); |
979 | | k = Bstr::from("K\x00e\x00\x00Y2"); |
980 | | let v2 = Bstr::from("Value2"); |
981 | | h = Header::new(k, v2.clone()); |
982 | | t.elements.push(h); |
983 | | |
984 | | let mut result = t.get_nocase_nozero("key1"); |
985 | | let mut res = result.unwrap(); |
986 | | assert_eq!( |
987 | | Ordering::Equal, |
988 | | res.name.cmp_slice("K\x00\x00\x00\x00ey\x001") |
989 | | ); |
990 | | assert_eq!(v1, res.value); |
991 | | |
992 | | result = t.get_nocase_nozero("KeY1"); |
993 | | res = result.unwrap(); |
994 | | assert_eq!( |
995 | | Ordering::Equal, |
996 | | res.name.cmp_slice("K\x00\x00\x00\x00ey\x001") |
997 | | ); |
998 | | assert_eq!(v1, res.value); |
999 | | |
1000 | | result = t.get_nocase_nozero("KEY2"); |
1001 | | res = result.unwrap(); |
1002 | | assert_eq!(Ordering::Equal, res.name.cmp_slice("K\x00e\x00\x00Y2")); |
1003 | | assert_eq!(v2, res.value); |
1004 | | |
1005 | | result = t.get_nocase("key1"); |
1006 | | assert!(result.is_none()); |
1007 | | } |