Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}