Coverage Report

Created: 2026-02-14 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/libhtp/htp/htp_response_generic.c
Line
Count
Source
1
/***************************************************************************
2
 * Copyright (c) 2009-2010 Open Information Security Foundation
3
 * Copyright (c) 2010-2013 Qualys, Inc.
4
 * All rights reserved.
5
 * 
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are
8
 * met:
9
 * 
10
 * - Redistributions of source code must retain the above copyright
11
 *   notice, this list of conditions and the following disclaimer.
12
13
 * - Redistributions in binary form must reproduce the above copyright
14
 *   notice, this list of conditions and the following disclaimer in the
15
 *   documentation and/or other materials provided with the distribution.
16
17
 * - Neither the name of the Qualys, Inc. nor the names of its
18
 *   contributors may be used to endorse or promote products derived from
19
 *   this software without specific prior written permission.
20
 * 
21
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
 ***************************************************************************/
33
34
/**
35
 * @file
36
 * @author Ivan Ristic <ivanr@webkreator.com>
37
 */
38
39
#include "htp_config_auto.h"
40
41
#include "htp_private.h"
42
43
/**
44
 * Generic response line parser.
45
 * 
46
 * @param[in] connp
47
 * @return HTP status
48
 */
49
47.7k
htp_status_t htp_parse_response_line_generic(htp_connp_t *connp) {
50
47.7k
    htp_tx_t *tx = connp->out_tx;
51
47.7k
    unsigned char *data = bstr_ptr(tx->response_line);
52
47.7k
    size_t len = bstr_len(tx->response_line);
53
47.7k
    size_t pos = 0;
54
55
47.7k
    tx->response_protocol = NULL;
56
47.7k
    tx->response_protocol_number = HTP_PROTOCOL_INVALID;
57
47.7k
    tx->response_status = NULL;
58
47.7k
    tx->response_status_number = HTP_STATUS_INVALID;
59
47.7k
    tx->response_message = NULL;
60
61
    // Ignore whitespace at the beginning of the line.
62
88.0k
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;
63
64
47.7k
    size_t start = pos;
65
66
    // Find the end of the protocol string.
67
984k
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;    
68
47.7k
    if (pos - start == 0) return HTP_OK;
69
    
70
47.7k
    tx->response_protocol = bstr_dup_mem(data + start, pos - start);
71
47.7k
    if (tx->response_protocol == NULL) return HTP_ERROR;    
72
73
47.7k
    tx->response_protocol_number = htp_parse_protocol(tx->response_protocol);
74
75
    #ifdef HTP_DEBUG
76
    fprint_raw_data(stderr, "Response protocol", bstr_ptr(tx->response_protocol), bstr_len(tx->response_protocol));
77
    fprintf(stderr, "Response protocol number: %d\n", tx->response_protocol_number);
78
    #endif
79
80
    // Ignore whitespace after the response protocol.
81
199k
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;
82
47.7k
    if (pos == len) return HTP_OK;
83
84
40.4k
    start = pos;
85
86
    // Find the next whitespace character.
87
368k
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;    
88
40.4k
    if (pos - start == 0) return HTP_OK;
89
    
90
40.4k
    tx->response_status = bstr_dup_mem(data + start, pos - start);
91
40.4k
    if (tx->response_status == NULL) return HTP_ERROR;
92
93
40.4k
    tx->response_status_number = htp_parse_status(tx->response_status);
94
95
    #ifdef HTP_DEBUG
96
    fprint_raw_data(stderr, "Response status (as text)", bstr_ptr(tx->response_status), bstr_len(tx->response_status));
97
    fprintf(stderr, "Response status number: %d\n", tx->response_status_number);
98
    #endif
99
100
    // Ignore whitespace that follows the status code.
101
86.9k
    while ((pos < len) && (isspace(data[pos]))) pos++;
102
40.4k
    if (pos == len) return HTP_OK;
103
104
    // Assume the message stretches until the end of the line.    
105
28.0k
    tx->response_message = bstr_dup_mem(data + pos, len - pos);
106
28.0k
    if (tx->response_message == NULL) return HTP_ERROR;    
107
108
    #ifdef HTP_DEBUG
109
    fprint_raw_data(stderr, "Response status message", bstr_ptr(tx->response_message), bstr_len(tx->response_message));
110
    #endif
111
112
28.0k
    return HTP_OK;
113
28.0k
}
114
115
/**
116
 * Generic response header parser.
117
 * 
118
 * @param[in] connp
119
 * @param[in] h
120
 * @param[in] data
121
 * @param[in] len
122
 * @return HTP status
123
 */
124
349k
htp_status_t htp_parse_response_header_generic(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) {
125
349k
    size_t name_start, name_end;
126
349k
    size_t value_start, value_end;
127
349k
    size_t prev;
128
129
349k
    htp_chomp(data, &len);
130
131
349k
    name_start = 0;
132
133
    // Look for the first colon.
134
349k
    size_t colon_pos = 0;
135
11.9M
    while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++;
136
137
349k
    if (colon_pos == len) {
138
        // Header line with a missing colon.
139
140
126k
        h->flags |= HTP_FIELD_UNPARSEABLE;
141
126k
        h->flags |= HTP_FIELD_INVALID;
142
143
126k
        if (!(connp->out_tx->flags & HTP_FIELD_UNPARSEABLE)) {
144
            // Only once per transaction.
145
17.3k
            connp->out_tx->flags |= HTP_FIELD_UNPARSEABLE;
146
17.3k
            connp->out_tx->flags |= HTP_FIELD_INVALID;
147
17.3k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: missing colon.");
148
17.3k
        }
149
       
150
        // Reset the position. We're going to treat this invalid header
151
        // as a header with an empty name. That will increase the probability
152
        // that the content will be inspected.
153
126k
        colon_pos = 0;
154
126k
        (void)colon_pos; // suppress scan-build warning
155
126k
        name_end = 0;
156
126k
        value_start = 0;
157
223k
    } else {
158
        // Header line with a colon.
159
        
160
223k
        if (colon_pos == 0) {
161
            // Empty header name.
162
163
6.15k
            h->flags |= HTP_FIELD_INVALID;
164
165
6.15k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
166
                // Only once per transaction.
167
2.41k
                connp->out_tx->flags |= HTP_FIELD_INVALID;
168
2.41k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: empty name.");
169
2.41k
            }
170
6.15k
        }
171
172
223k
        name_end = colon_pos;
173
174
        // Ignore unprintable after field-name.
175
223k
        prev = name_end;
176
236k
        while ((prev > name_start) && htp_is_space(data[prev - 1])) {
177
13.5k
            prev--;
178
13.5k
            name_end--;
179
180
13.5k
            h->flags |= HTP_FIELD_INVALID;
181
182
13.5k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
183
                // Only once per transaction.
184
2.89k
                connp->out_tx->flags |= HTP_FIELD_INVALID;
185
2.89k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: LWS after name.");
186
2.89k
            }
187
13.5k
        }
188
189
223k
        value_start = colon_pos + 1;
190
223k
    }
191
192
    // Header value.   
193
194
    // Ignore LWS before field-content.
195
522k
    while ((value_start < len) && (htp_is_lws(data[value_start]))) {
196
172k
        value_start++;
197
172k
    }
198
199
    // Look for the end of field-content.
200
349k
    value_end = len;    
201
    
202
    // Check that the header name is a token.
203
349k
    size_t i = name_start;
204
2.50M
    while (i < name_end) {
205
2.20M
        if (!htp_is_token(data[i])) {
206
54.3k
            h->flags |= HTP_FIELD_INVALID;
207
208
54.3k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
209
8.63k
                connp->out_tx->flags |= HTP_FIELD_INVALID;
210
8.63k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header name is not a token.");
211
8.63k
            }
212
213
54.3k
            break;
214
54.3k
        }
215
216
2.15M
        i++;
217
2.15M
    }
218
5.97M
    for (i = value_start; i < value_end; i++) {
219
5.70M
        if (data[i] == 0) {
220
74.9k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header value contains null.");
221
74.9k
            break;
222
74.9k
        }
223
5.70M
    }
224
    // Ignore LWS after field-content.
225
349k
    prev = value_end - 1;
226
361k
    while ((prev > value_start) && (htp_is_lws(data[prev]))) {
227
11.6k
        prev--;
228
11.6k
        value_end--;
229
11.6k
    }
230
231
    // Now extract the name and the value.
232
349k
    h->name = bstr_dup_mem(data + name_start, name_end - name_start);
233
349k
    h->value = bstr_dup_mem(data + value_start, value_end - value_start);
234
349k
    if ((h->name == NULL) || (h->value == NULL)) {
235
0
        bstr_free(h->name);
236
0
        bstr_free(h->value);
237
0
        return HTP_ERROR;
238
0
    }
239
240
349k
    return HTP_OK;
241
349k
}
242
243
/**
244
 * Generic response header line(s) processor, which assembles folded lines
245
 * into a single buffer before invoking the parsing function.
246
 * 
247
 * @param[in] connp
248
 * @param[in] data
249
 * @param[in] len
250
 * @return HTP status
251
 */
252
349k
htp_status_t htp_process_response_header_generic(htp_connp_t *connp, unsigned char *data, size_t len) {
253
    // Create a new header structure.
254
349k
    htp_header_t *h = calloc(1, sizeof (htp_header_t));
255
349k
    if (h == NULL) return HTP_ERROR;
256
257
349k
    if (htp_parse_response_header_generic(connp, h, data, len) != HTP_OK) {
258
0
        free(h);
259
0
        return HTP_ERROR;
260
0
    }
261
262
    #ifdef HTP_DEBUG
263
    fprint_bstr(stderr, "Header name", h->name);
264
    fprint_bstr(stderr, "Header value", h->value);
265
    #endif
266
267
    // Do we already have a header with the same name?
268
349k
    htp_header_t *h_existing = htp_table_get(connp->out_tx->response_headers, h->name);
269
349k
    if (h_existing != NULL) {
270
        // Keep track of repeated same-name headers.
271
158k
        if ((h_existing->flags & HTP_FIELD_REPEATED) == 0) {
272
            // This is the second occurence for this header.
273
34.3k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Repetition for header");
274
124k
        } else {
275
            // For simplicity reasons, we count the repetitions of all headers
276
124k
            if (connp->out_tx->res_header_repetitions < HTP_MAX_HEADERS_REPETITIONS) {
277
82.8k
                connp->out_tx->res_header_repetitions++;
278
82.8k
            } else {
279
41.2k
                bstr_free(h->name);
280
41.2k
                bstr_free(h->value);
281
41.2k
                free(h);
282
41.2k
                return HTP_OK;
283
41.2k
            }
284
124k
        }
285
117k
        h_existing->flags |= HTP_FIELD_REPEATED;
286
287
        // Having multiple C-L headers is against the RFC but many
288
        // browsers ignore the subsequent headers if the values are the same.
289
117k
        if (bstr_cmp_c_nocase(h->name, "Content-Length") == 0) {
290
            // Don't use string comparison here because we want to
291
            // ignore small formatting differences.
292
293
17.5k
            int64_t existing_cl, new_cl;
294
295
17.5k
            existing_cl = htp_parse_content_length(h_existing->value, NULL);
296
17.5k
            new_cl = htp_parse_content_length(h->value, NULL);
297
17.5k
            if ((existing_cl == -1) || (new_cl == -1) || (existing_cl != new_cl)) {
298
                // Ambiguous response C-L value.
299
9.80k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Ambiguous response C-L value");
300
9.80k
            }
301
302
            // Ignoring the new C-L header that has the same value as the previous ones.
303
99.6k
        } else {            
304
            // Add to the existing header.
305
306
99.6k
            bstr *new_value = bstr_expand(h_existing->value, bstr_len(h_existing->value) + 2 + bstr_len(h->value));
307
99.6k
            if (new_value == NULL) {
308
0
                bstr_free(h->name);
309
0
                bstr_free(h->value);
310
0
                free(h);
311
0
                return HTP_ERROR;
312
0
            }
313
314
99.6k
            h_existing->value = new_value;
315
99.6k
            bstr_add_mem_noex(h_existing->value, (unsigned char *) ", ", 2);
316
99.6k
            bstr_add_noex(h_existing->value, h->value);
317
99.6k
        }
318
319
        // The new header structure is no longer needed.
320
117k
        bstr_free(h->name);
321
117k
        bstr_free(h->value);
322
117k
        free(h);       
323
191k
    } else {
324
191k
        if (htp_table_size(connp->out_tx->response_headers) > connp->cfg->number_headers_limit) {
325
0
            if (!(connp->out_tx->flags & HTP_HEADERS_TOO_MANY)) {
326
0
                connp->out_tx->flags |= HTP_HEADERS_TOO_MANY;
327
0
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Too many response headers");
328
0
            }
329
0
            bstr_free(h->name);
330
0
            bstr_free(h->value);
331
0
            free(h);
332
0
            return HTP_ERROR;
333
0
        }
334
        // Add as a new header.
335
191k
        if (htp_table_add(connp->out_tx->response_headers, h->name, h) != HTP_OK) {
336
0
            bstr_free(h->name);
337
0
            bstr_free(h->value);
338
0
            free(h);
339
0
            return HTP_ERROR;
340
0
        }
341
191k
    }
342
   
343
308k
    return HTP_OK;
344
349k
}